Agent-10-02.ToolCalling与Agent执行循环

系列:00 索引 · 上一篇:01 LLM · 下一篇:03 MCP


1. 行业常见问题

现象 原因
模型编造数据库/API 结果 无工具、只能「猜」
单次 Prompt 塞满 API 文档 上下文浪费、仍可能调错
调用了工具但参数 JSON 非法 缺 schema 与校验
无限循环调同一工具 无 max_steps / 无终止条件

科学/企业场景典型需求:查文献、拉序列、跑脚本、读文件——都需要 模型决策 + 外部执行 分离。


2. 该技术如何解决

Tool Calling(函数调用):模型输出「要调哪个函数、参数是什么」,运行时执行后把结果作为 tool 消息 回填,模型再总结或继续调用。

ReAct(Reason + Act):推理与行动交替;工程上体现为 while 循环直到无 tool_calls


3. 核心原理

1
2
3
4
5
6
7
8
messages = [system, user]
loop:
response = LLM(messages, tools=tool_schemas)
if response.tool_calls empty:
return response.text
for each tool_call:
result = execute(tool_call.name, tool_call.arguments)
messages += assistant(tool_calls) + tool(result)

关键设计:

  1. Tool schema(名称、描述、参数 JSON Schema)决定模型「会不会选对工具」。
  2. 描述要写清输入输出,比函数名更重要。
  3. max_steps 防止死循环与费用爆炸。
  4. 错误也要回填(如 {"error": "..."}),让模型有机会修正。

4. 典型实现与代码示例

Tool loop 由 AgentRuntime.chat 统一入口驱动,核心循环在 agent/uni/runtime/tool_loop.py;LLM 经 LLMClient 抽象(OpenAI SDK),MCP 工具经 StdioMcpClient 注入。

4.1 ReAct 循环(uni 内核)

1
2
3
4
5
6
7
8
9
10
# agent/uni/runtime/tool_loop.py(节选)
def run_tool_loop(client, *, model, messages, tools, impls, on_event=None, max_steps=8):
for _ in range(max_steps):
response = client.chat(model=model, messages=working, tools=tools, ...)
if not response.get("tool_calls"):
return content, working # 无 tool → 结束
working.append({"role": "assistant", "tool_calls": tool_calls})
for call in tool_calls:
result = impls[name](**args) # MCP 或内置实现
working.append({"role": "tool", "tool_call_id": ..., "content": json.dumps(result)})

on_event 推送 tool_start / tool_end,供 FastAPI SSE(Agent-10-05)与 Web UI 消费。

4.2 AgentRuntime:拼 prompt + 挂载 MCP

1
2
3
4
5
6
7
8
9
10
# agent/uni/runtime/agent_runtime.py(节选)
def chat(self, user_message, *, on_event=None):
system = build_system_prompt_from_config(self.config, self._skill_paths)
messages = [{"role": "system", "content": system}, {"role": "user", "content": user_message}]
reply, full = run_tool_loop(
self._llm, model=self.config.model.name, messages=messages,
tools=self._tool_schemas(), # BUILTIN + MCP tools/list 转 OpenAI schema
impls=self._tool_impls(), # StdioMcpClient.tool_impls()
on_event=on_event,
)

MCP tool schema 转换见 agent/uni/mcp_client/stdio.py_mcp_tool_to_openai_schema

4.3 工程验收

1
2
3
4
5
6
7
8
9
# Mock LLM + echo 工具(无需 API Key)
uv run python -m agent.uni.cli chat --mock-llm "tool: hello"

# Literature Agent:真实/ Mock LLM + query_hybrid MCP
uv run python -m agent.literature.cli chat --mock-llm \
"请先 query_hybrid 检索 brca1_gene:BRCA1 转录本选择依据"

# 编排内 Specialist(LangGraph 节点调 AgentRuntime,见 09)
uv run python -m agent.orchestrator.cli run --symbol BRCA1 --llm-agents --mock-llm

5. 替代方案与优缺点

方案 优点 缺点
手写 ReAct 循环 透明、可控 需自管重试、日志
LangChain bind_tools 生态丰富 版本耦合
OpenAI Agents SDK 官方、轻量 绑定 OpenAI 栈为主
纯 Prompt「请输出要执行的 bash」 原型快 安全风险极高,生产禁用
固定 DAG 无 LLM 选工具 稳定可审计 失去灵活对话

6. 自检题

  1. 为什么 tool 的 descriptionname 更影响选型质量?
  2. tool 执行失败时,为什么要回填错误而不是直接抛异常终止?
  3. max_steps 与 token 预算如何一起设计?

7. 延伸阅读

-------------本文结束感谢您的阅读-------------