跳到主要内容

agent 与工具的通用机制

本章讲:所有专家 agent 共享的骨架——怎么问 LLM、怎么从回复里抽代码块、怎么执行并自修。

3.1 它要解决的小问题

LLM 只会输出文本。怎么让它“真的跑一段代码”或“真的发一次搜索”?主流做法是 OpenAI 的 function-calling(模型输出结构化 JSON)。AgenticSeek 故意不用,因为本地小模型未必支持 function-calling。

3.2 思路/直觉:Markdown 代码块当“函数调用”

AgenticSeek 用每个 LLM 都会写的 Markdown 代码围栏 当工具调用协议。每个工具有一个 tag,LLM 想用哪个工具就写那个 tag 的围栏:

想跑 Python 就写:
```python
print("hello")
```

想搜索就写:
```web_search
best cafes in Rennes
```

执行时,每个工具都去扫一遍 answer,只抓自己 tag 的块(load_exec_blockstart_tag = 三反引号 + tag)。谁的 tag 出现了,谁就被执行。

一个巧妙点:围栏第一行如果含冒号,冒号后的部分被当作 保存路径(如 python:game.py),代码会写进那个文件(tools.py:197-199)。

3.3 图示:ReAct 执行循环

以 coder agent 为例(code_agent.py:46-87)。怎么读:这是个“写->跑->错了再写”的自修环,最多 5 轮。

推系统信息进 prompt
v
+-> 问 LLM (llm_request)
| v
| 含 REQUEST_CLARIFICATION?-yes-> 返回问用户
| | no
| v
| 没有代码块?-yes-> 直接结束(纯聊天回复)
| | no
| v
| execute_modules(answer) 扫块、执行、拿 feedback
| v
| 成功 且 最后不是 bash?-yes-> break
| | 否则(失败或是 bash)
+---- attempt+1,把错误推回 memory,重试

为什么“最后是 bash 也要继续循环”(code_agent.py:77)?bash 常用来装环境/跑辅助命令,成功不代表任务完成,所以不提前退出。

3.4 真实实现:execute_modules

所有 agent 共享的执行入口在 agent.py:255-285Agent.execute_modules),逻辑:

遍历 self.tools 里每个 (name, tool):
blocks, save_path = tool.load_exec_block(answer) # 只抓自己 tag 的块
若有 blocks:
逐块 tool.execute([block])
feedback = tool.interpreter_feedback(output)
success = not tool.execution_failure_check(output)
记录到 blocks_result
若失败 -> 把 feedback 推回 memory('user'),立即 return False
全部成功 -> feedback 推回 memory,若有 save_path 则落盘

三个抽象方法是工具的“三件套”(tools.py:76-108):execute(真跑)、execution_failure_check(看输出是不是失败)、interpreter_feedback(生成给 LLM 的反馈文本)。

失败检测是关键词匹配。 比如 bash 用一堆正则(faileddeniednot found…)扫输出(BashInterpreter.py:87-117),命中即判失败。这是个实用但易误报的启发式(输出里出现 “not found” 字样就算失败,哪怕是正常结果)。

3.5 真实实现:load_exec_block 怎么抽块

tools.py:154-204Tools.load_exec_block)是协议的心脏。几个值得看的处理:

  • 处理缩进。 LLM 可能把代码块整体缩进(如在列表里),load_exec_block 记下 start_tag 前的 leading whitespace,逐行剥掉(tools.py:180-195)。
  • 抽保存路径。 首行含冒号则拆出路径(tools.py:197-199)。
  • 多块都抽。 while 循环一直找下一个 start_tag(tools.py:175-202)。

3.6 原理演示:reasoning 抽取

DeepSeek 这类推理模型输出带 <think>…</think>。agent 把它剖开:

# 示意,非源码
thought = "<think>用户要 snake</think> 代码块..."
# answer = 去掉 reasoning(取 </think> 之后)
# reasoning = 取 <think>...</think> 那段
reasoning = extract_reasoning_text(thought) # 给人看的思考
answer = remove_reasoning_text(thought) # 进后续执行的正文

真实实现在 agent.py:138-158remove_reasoning_text / extract_reasoning_text),靠 rfind("</think>") 定位。注意只有 answer 被推进 memory(agent.py:177),reasoning 不进上下文。

3.7 各 agent 怎么装工具

agentrole工具文件
CasualAgenttalk无(纯聊天)casual_agent.py
CoderAgentcodebash/python/c/go/java + file_findercode_agent.py
FileAgentfilesfile_finder + bashfile_agent.py
BrowserAgentwebsearxSearch + Browserbrowser_agent.py
PlannerAgentplanificationjson(用来解析计划)planner_agent.py
McpAgentmcpmcp_finder(WIP)mcp_agent.py

注意不同 agent 的 process() 循环不同:casual 只问一次(casual_agent.py:26-32);file 是“直到执行成功”的 while(file_agent.py:31-39);coder 是有上限 5 次的自修环。

3.8 边界/坑

  • Python 执行无沙箱。 PyInterpreter.execute 直接 exec(code, global_vars),重定向 stdout 抓输出(PyInterpreter.py:29-57)。global_vars 还主动注入了 os/sysPyInterpreter.py:31-36)。
  • Bash 默认 cd 到 work_dirBashInterpreter.py:44),shell=True 跑,5 分钟超时。
  • “用 bash 跑语言”会被拦。 如果 bash 块里写 python xxx.py,默认会被跳过(BashInterpreter.py:23-33,49-50)——因为 AI 写的代码会被对应语言工具自动执行,不该再用 bash 跑一次。

3.9 代码地图

主题文件符号
agent 基类sources/agents/agent.pyAgent
执行入口sources/agents/agent.pyexecute_modules
LLM 请求 + reasoning 抽取sources/agents/agent.pysync_llm_requestextract_reasoning_text
工具基类三件套sources/tools/tools.pyTools.executeexecution_failure_checkinterpreter_feedback
block 解析sources/tools/tools.pyload_exec_block
Python 执行sources/tools/PyInterpreter.pyPyInterpreter.execute
Bash 执行sources/tools/BashInterpreter.pyBashInterpreter.executelanguage_bash_attempt
coder 自修环sources/agents/code_agent.pyCoderAgent.process
危险命令黑名单sources/tools/safety.pyis_unsafeis_any_unsafe