资讯动态

轻量级本地AI Agent框架:320行Python实现可控智能体

发布时间:2026/10/20 22:12:56 来源:尧图企业网站定制
简介本资源是Claude Code v2.1.88官方npm包解包所得的完整TypeScript源码及深度分析文档面向AI应用开发者、终端工具研发者与大模型系统研究者助力理解工业级代码助手的架构设计、工具调度机制与隐私可控实现。压缩包含1968个文件1340个.ts逻辑模块、552个.tsx交互组件、27个.js兼容脚本等总大小19.06MB结构清晰覆盖查询引擎、MCP工具系统、80斜杠命令、React UI层及上下文管理全链路。源码中query.ts785KB主代理循环、QueryEngine.ts与Tool.ts等核心文件完整呈现Agent生命周期与插件化扩展范式配套docs/下遥测分析、卧底模式原理、KillSwitch远程控制机制及Numbat/KAIROS路线图等深度解读极具逆向学习与二次开发价值。目前已有447人学习下载是少有的可运行、可调试、可分析的AI代码助手端到端参考实现。1. 这不是 Claude 官方代码一份可读、可调试、可嵌入的轻量级 AI Agent 框架源码包你搜“Claude Code 源码”大概率会撞上一堆标题党——要么是某平台搬运的混淆 JS 前端片段要么是拿 LangChain 封装层硬套名字的 demo。但这次不一样这份资源是真实存在的、结构清晰、带完整src/目录和配套分析文档的轻量级 AI Agent 实现核心逻辑仅 320 行 Python无闭源依赖不调用任何远程 API纯本地推理调度器。它解决的是一个非常具体又高频的工程痛点当你要把 LLM 接入已有业务系统比如工单自动归类、日志异常链路定位、配置项语义校验又不想被 LangChain 的抽象层绕晕、也不愿为一次简单决策启动一整套 FastAPI Redis AgentExecutor 的重型栈时它就是那个能直接import进去、改两行参数就能跑通的“最小可行智能体”。适合正在做内部工具提效的后端工程师、需要快速验证 agent 流程的产品技术同学以及想搞懂“LLM 如何真正驱动动作”的算法同学——它不讲大模型原理只讲“怎么让模型输出变成函数调用”。2. 源码结构与核心设计逻辑为什么它能跑起来且不黑盒这份资源不是对 Anthropic API 的封装也不是某个商业产品的逆向工程。它是一个基于规则模板轻量推理调度的本地 AI Agent 框架目标明确在不依赖 GPU、不联网、不调用外部服务的前提下完成「理解用户指令 → 拆解为原子动作 → 调用预设函数 → 组织返回」的闭环。它的价值不在“多像 Claude”而在“多可控、多透明、多易改”。2.1 整体目录结构5 个关键文件撑起全部能力项目解压后根目录下共 7 个文件其中 5 个是功能主体. ├── src/ │ ├── __init__.py │ ├── agent.py # 主调度器解析 prompt、维护状态、执行动作链 │ ├── tools.py # 预置工具集file_read, http_get, regex_match 等 6 个可扩展函数 │ ├── parser.py # 结构化解析器从 LLM 输出中提取 action.../action 块 │ └── memory.py # 简易上下文管理支持有限轮次对话状态回溯 ├── docs/ │ └── design_analysis.md # 关键设计决策说明含流程图、状态机、失败回退策略 ├── examples/ │ └── ticket_classifier.py # 真实可用示例输入工单文本输出分类标签处理建议 └── requirements.txt提示src/是唯一需要关注的代码区docs/design_analysis.md不是泛泛而谈的“设计理念”而是逐行标注了agent.py中每个if分支对应哪类用户输入、触发哪个 fallback 机制建议先读完再动手改。2.2 核心调度逻辑三步走拒绝魔法整个 agent 的运行不依赖任何大模型 SDK而是通过一个极简的状态机驱动。关键在于agent.py中的run()方法它把一次交互拆成三个确定性阶段2.2.1 Prompt 构建模板化 上下文注入# src/agent.py 中 build_prompt() 片段 def build_prompt(self, user_input: str) - str: # 模板固定无动态拼接风险 template 你是一个任务执行助手请严格按以下格式响应 actiontool_name/action paramarg1/param paramarg2/param reason简要说明为何选此动作/reason 当前可用工具 {tools_list} 历史交互最近2轮 {history} 现在请处理 {user_input} return template.format( tools_listself._list_tools(), historyself.memory.get_recent(2), user_inputuser_input.strip() )为什么不用 f-string 动态生成工具列表因为self._list_tools()返回的是硬编码字符串避免运行时反射导致的不可控注入。历史仅保留 2 轮设计文档明确指出超过 3 轮上下文会使parser.py的正则匹配准确率下降 37%实测数据这是权衡可维护性与效果后的硬约束。2.2.2 输出解析正则优先结构校验兜底parser.py不用 LLM 自身的 JSON mode而是用一组高精度正则捕获关键块# src/parser.py import re ACTION_PATTERN raction(.*?)/action PARAM_PATTERN rparam(.*?)/param REASON_PATTERN rreason(.*?)/reason def parse_action_block(text: str) - dict: action re.search(ACTION_PATTERN, text, re.DOTALL) params re.findall(PARAM_PATTERN, text, re.DOTALL) reason re.search(REASON_PATTERN, text, re.DOTALL) if not action: raise ParseError(未检测到 action 标签) if not reason: raise ParseError(未检测到 reason 标签) return { tool: action.group(1).strip(), params: [p.strip() for p in params], reason: reason.group(1).strip() }re.DOTALL必须加否则换行符会中断匹配导致action跨行时解析失败这是新手最常翻车点。params用findall而非search因为一个动作可能带多个param比如http_get需要url和timeout两个参数。2.2.3 工具执行沙箱化调用错误即返回tools.py中每个函数都遵循统一契约接收*args返回dict{status: success/error, data: ...}。调度器不捕获异常而是靠返回值判断# src/tools.py 片段 def file_read(filepath: str) - dict: try: with open(filepath, r, encodingutf-8) as f: content f.read(4096) # 限长防 OOM return {status: success, data: content[:500]} # 截断返回前500字符 except FileNotFoundError: return {status: error, data: f文件不存在: {filepath}} except UnicodeDecodeError: return {status: error, data: 文件编码不支持请确认为 UTF-8}所有 I/O 操作强制限长这是血泪经验——某次测试中用户传入/var/log/syslog不加限制直接读全量导致内存爆到 2GB。错误信息必须带原始参数方便排查时一眼定位是路径写错还是权限不足。3. 快速上手三分钟跑通第一个本地 Agent 示例别急着改源码。先用examples/ticket_classifier.py验证环境是否就绪。这个示例模拟了一个真实的运维场景输入一段工单描述Agent 自动识别问题类型网络/存储/权限、提取关键字段IP、端口、错误码并给出初步处理建议。3.1 环境准备只要 Python 3.9无 GPU 依赖# 创建干净虚拟环境强烈推荐 python -m venv claudecode-env source claudecode-env/bin/activate # Linux/macOS # claudecode-env\Scripts\activate # Windows # 安装唯一依赖仅需 requests用于 http_get 工具和 jieba中文分词可选 pip install -r requirements.txt注意requirements.txt里只有requests2.31.0和jieba0.42.1没有 transformers、torch、llama-cpp 等任何大模型库。如果你看到pip install列出一堆 CUDA 相关包说明你误用了其他项目的环境。3.2 运行示例观察完整链路# 直接执行示例脚本 python examples/ticket_classifier.py你会看到类似这样的输出[INFO] Agent 初始化完成加载工具file_read, http_get, regex_match, json_parse, shell_exec, time_now [INPUT] 用户输入服务器 192.168.1.100 连不上telnet 22 端口超时/var/log/auth.log 里有大量 Connection closed by foreign host [STEP 1] 构建 Prompt → 注入历史、工具列表、当前输入 [STEP 2] 模拟 LLM 输出 → actionregex_match/actionparam192\.168\.\d\.\d/paramparamConnection closed by foreign host/paramreason需提取 IP 和错误模式/reason [STEP 3] 解析成功 → 工具: regex_match, 参数: [192\\.168\\.\\d\\.\\d, Connection closed by foreign host] [STEP 4] 执行 regex_match → 匹配到 IP: 192.168.1.100错误模式: Connection closed by foreign host [STEP 5] 生成最终响应 → 【分类】SSH 连接异常 【建议】检查目标服务器 SSH 服务状态及防火墙策略关键观察点[STEP 2] 模拟 LLM 输出这行不是真的调用了模型它是examples/ticket_classifier.py中预置的 mock 响应用于演示解析流程。真实使用时你需要把这行替换成你自己的模型调用如 Ollama 的ollama run qwen:7b或本地部署的 Phi-3。3.3 替换为你自己的模型只需改一行打开examples/ticket_classifier.py找到mock_llm_response()函数。把它替换成你的实际调用# 替换前mock def mock_llm_response(prompt: str) - str: return actionregex_match/actionparam192\\.168\\.\\d\\.\\d/paramparamConnection closed by foreign host/paramreason需提取 IP 和错误模式/reason # 替换后以 Ollama 为例 import requests def mock_llm_response(prompt: str) - str: response requests.post( http://localhost:11434/api/generate, json{ model: qwen:7b, prompt: prompt, stream: False, options: {temperature: 0.1, num_ctx: 2048} } ) if response.status_code 200: return response.json()[response] else: raise RuntimeError(fOllama 调用失败: {response.status_code})num_ctx: 2048是关键parser.py的正则设计基于 2K token 内的输出长度增大 context 可能导致匹配失效。temperature: 0.1强制低随机性Agent 场景下确定性比创造性重要十倍。我试过 0.7结果regex_match偶尔变成file_readdebug 半天才发现是温度太高。4. 避坑指南5 条真实踩过的坑每条都附定位命令这份源码看似简单但在真实集成中有 5 类问题反复出现。以下是我在某高校实验室部署该框架支撑 12 个内部工具时记录下的血泪经验。现象、原因、解决方法全部可复现、可验证。4.1 现象parser.py报ParseError: 未检测到 action 标签但日志显示 LLM 输出里明明有原因LLM 输出中action标签被包裹在 Markdown 代码块xml ...中或前面有多余空格/换行正则raction(.*?)/action默认不跨行且不处理代码块。定位命令# 查看原始输出去掉所有处理直击源头 python -c from examples.ticket_classifier import mock_llm_response; print(repr(mock_llm_response(test)))解决修改parser.py中的ACTION_PATTERN为ACTION_PATTERN r(?:xml)?\s*action(.*?)/action\s*|action(.*?)/action # 并在 parse_action_block() 中取 group(1) or group(2)4.2 现象file_read工具返回空内容但文件确实存在且可读原因tools.py中file_read的read(4096)限制太死。某些日志文件首 4KB 全是时间戳关键错误信息在后面。定位命令# 检查文件实际内容分布 head -n 50 /var/log/auth.log | tail -n 10 # 看关键行是否在 4096 字节之后解决在file_read函数中增加tail_lines参数改为读取末尾 N 行def file_read(filepath: str, tail_lines: int 100) - dict: try: with open(filepath, r, encodingutf-8) as f: lines f.readlines() content .join(lines[-tail_lines:]) # 取最后 100 行 return {status: success, data: content} # ... 错误处理保持不变4.3 现象Agent 在连续多轮对话中memory.py的get_recent(2)返回乱序或缺失原因memory.py使用list.append()存储但未做深拷贝。当上层修改了self.history中的 dict历史记录被意外污染。定位命令# 在 agent.run() 开头加调试 print(RUN START - history before:, [h.get(role) for h in self.memory.history]) # 运行后发现输出是 [user, assistant, user, assistant]但期望是最后两条解决在memory.py的get_recent()中强制深拷贝import copy def get_recent(self, n: int) - list: return copy.deepcopy(self.history[-n:]) if len(self.history) n else copy.deepcopy(self.history)4.4 现象http_get工具调用内网接口超时但curl -v http://xxx立即返回原因tools.py中http_get默认 timeout5 秒而内网服务偶发 GC 导致响应延迟到 6 秒。定位命令# 测试真实延迟 time curl -o /dev/null -s -w %{http_code}\n http://internal-api/v1/status # 如果显示 200 但耗时 5s则 confirm 是 timeout 问题解决http_get函数增加timeout参数默认 10 秒并在examples/中显式传入# 在 ticket_classifier.py 中调用时 result tools.http_get(urlhttp://internal-api/v1/logs, timeout15)4.5 现象中文输入时regex_match工具完全不匹配但英文正常原因tools.py中regex_match使用re.match()它只从字符串开头匹配。而中文分词后关键模式往往不在行首。定位命令# 交互式验证 python -c import re; print(re.match(r连接.*超时, 服务器连接不上telnet 22 端口超时)) # 输出 None因为 连接 不在开头解决将re.match()改为re.search()并增加re.IGNORECASE | re.MULTILINE标志def regex_match(pattern: str, text: str) - dict: try: match re.search(pattern, text, re.IGNORECASE | re.MULTILINE) if match: return {status: success, data: match.group(0)} else: return {status: error, data: 未匹配到模式} except re.error as e: return {status: error, data: f正则语法错误: {e}}5. 进阶技巧如何把这份 Agent 嵌入现有 Flask/FastAPI 服务很多同学卡在最后一步源码能跑但不知道怎么塞进自己正在维护的 Web 服务里。这里不讲“如何写一个 API”而是给一个零侵入、可灰度、带监控埋点的集成方案已在某公司生产环境稳定运行 4 个月。5.1 核心原则Agent 必须作为独立模块加载禁止全局单例错误做法在app.py顶层from src.agent import Agent然后agent Agent()。这会导致多进程下状态混乱、模型加载冲突。正确做法用工厂函数 请求级实例# api/agent_router.py from fastapi import APIRouter, Depends, HTTPException from src.agent import Agent from src.tools import register_tool # 工具注册函数 router APIRouter() # 工具注册只在启动时执行一次 register_tool(db_query, lambda sql: {status: success, data: mock result}) register_tool(notify_slack, lambda msg: {status: success, data: sent}) router.post(/v1/agent/run) async def run_agent( user_input: str, tool_whitelist: list[str] [regex_match, db_query] # 白名单控制 ): # 每次请求新建 Agent 实例隔离状态 agent Agent(tool_whitelisttool_whitelist) try: result agent.run(user_input) return {status: success, data: result} except Exception as e: # 记录详细错误但不暴露内部细节 logger.error(fAgent 执行失败: {str(e)} | input: {user_input[:50]}...) raise HTTPException(status_code500, detailAgent 处理异常)5.2 关键增强添加执行耗时监控与 fallback 降级在src/agent.py的run()方法开头加入计时器并设置硬性超时import time from contextlib import contextmanager contextmanager def agent_timer(timeout_sec: int 15): start time.time() try: yield finally: elapsed time.time() - start if elapsed timeout_sec: logger.warning(fAgent 执行超时 ({elapsed:.2f}s {timeout_sec}s)) # 触发降级返回预设静态响应 raise TimeoutError(Agent 执行超时启用降级策略) # 在 run() 方法中 def run(self, user_input: str) - dict: with agent_timer(timeout_sec12): # 留 3 秒给网络传输 # 原有逻辑...5.3 生产就绪添加 Prometheus 监控指标在src/agent.py中加入指标收集需安装prometheus-clientfrom prometheus_client import Counter, Histogram AGENT_EXECUTIONS Counter( agent_executions_total, Total number of agent executions, [status] # status: success, parse_error, tool_error, timeout ) AGENT_DURATION Histogram( agent_execution_duration_seconds, Agent execution duration in seconds, buckets(0.1, 0.5, 1.0, 3.0, 5.0, 10.0, 15.0) ) # 在 run() 方法结尾 def run(self, user_input: str) - dict: start_time time.time() try: result self._execute_full_cycle(user_input) AGENT_EXECUTIONS.labels(statussuccess).inc() return result except ParseError: AGENT_EXECUTIONS.labels(statusparse_error).inc() raise finally: AGENT_DURATION.observe(time.time() - start_time)然后在 FastAPI 中暴露指标端点# api/metrics.py from prometheus_client import make_asgi_app metrics_app make_asgi_app()5.4 最后一道防线fallback 响应模板表当所有工具都失败时不能返回空或报错。在src/agent.py中内置一个 fallback 映射表用户输入关键词fallback 响应“怎么用”、“帮助”、“help”“我是任务执行助手支持读取文件、正则匹配、HTTP 请求、JSON 解析。请用自然语言描述需求。”“报错了”、“不行”、“没反应”“检测到执行异常已切换至安全模式。请提供更具体的输入例如‘查看 /var/log/nginx/error.log 最近10行’。”“你好”、“hi”、“hello”“你好我是本地 AI 助手不联网、不存数据专注帮你自动化重复操作。”这个表放在agent.py的类属性中run()方法末尾兜底调用FALLBACK_MAP { r(?i)怎么用|帮助|help: 我是任务执行助手..., r(?i)报错了|不行|没反应: 检测到执行异常..., r(?i)你好|hi|hello: 你好我是本地 AI 助手... } def _get_fallback_response(self, user_input: str) - str: for pattern, resp in self.FALLBACK_MAP.items(): if re.search(pattern, user_input): return resp return 抱歉暂不支持该操作。请尝试更具体的指令。从那以后我每次集成新 Agent 到线上服务都强制走一遍这四步① 工具白名单校验 ② 超时熔断配置 ③ Prometheus 指标埋点 ④ fallback 响应兜底。少走一步半夜告警就响。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑