🎯 课程主题
深入分析Agent工具调用的底层流程(ReAct模式),并梳理开发中的常见问题与避坑指南。
📝 核心知识点
1. ReAct 执行模式
- 概念说明:Agent的工具调用遵循经典的 ReAct(Reasoning + Acting)模式,底层由LangGraph图结构编排。
- 关键细节:
- Reasoning(思考):模型分析用户问题,判断是否需要工具、需要哪个工具,体现在 AIMessage 中包含
tool_calls。 - Acting(行动):执行工具调用,产生 ToolMessage。
- Observation(观察):模型接收 ToolMessage,整合结果判断是否需要继续调用工具。
- 循环退出条件:某次模型输出的 AIMessage 中不再包含
tool_calls时结束。
- Reasoning(思考):模型分析用户问题,判断是否需要工具、需要哪个工具,体现在 AIMessage 中包含
2. 两种循环调用场景
- 概念说明:Agent在工具调用过程中可能出现循环,主要有两种情况。
- 场景一:递进工具调用
- 用户问题需要多个工具协作完成(如先搜索商品,再查库存)
- 流程:HumanMessage → AIMessage(tool_calls: search_product) → ToolMessage → AIMessage(tool_calls: check_stock) → ToolMessage → AIMessage(最终结果)
- 场景二:重试机制
- 同一工具因临时故障(服务暂时不可用)被反复调用
- LangChain会在工具返回异常时自动重试,直到成功或达到限制次数
flag = 0
@tool
def get_weather(city: str) -> str:
"""查询天气"""
global flag
flag += 1
if flag < 3:
return "天气服务暂时不可用,请稍后重试"
return f"{city}天气晴朗,温度15℃"
3. 工具选择机制
- 概念说明:Agent根据工具的 docstring / description 与用户问题的语义匹配来自动选择工具。
- 关键细节:本质上是借助Agent内部的LLM进行语义判断,而非硬编码的规则匹配。
4. Agent完成标志
- 概念说明:当 AIMessage 的
tool_calls为空列表时,表示Agent已完成任务。 - 关键细节:只要
tool_calls中还有内容,Agent就会继续执行工具调用。
5. 限制工具调用次数
- 概念说明:通过
invoke()中的config参数设置recursion_limit来限制最大执行步数。 - 关键细节:与裸模型调用时的
config机制相同。
response = agent.invoke(
{"messages": ["北京天气如何?"]},
config={"recursion_limit": 5}
)
🏗️ 架构与工作流
HumanMessage (用户问题)
↓
┌─ Reasoning ──────────────────────────────────────┐
│ 模型分析:需要调用工具? 调用哪个工具? │
│ 输出:AIMessage (含 tool_calls 或为空) │
└──────────────────────────────────────────────────┘
↓ tool_calls 非空 ↓ tool_calls 为空
┌─ Acting ───────────┐ ┌─ 结束 ──────┐
│ 执行对应工具 │ │ 最终 AIMessage│
│ 输出:ToolMessage │ │ (不含tool_calls)│
└────────────────────┘ └─────────────┘
↓
┌─ Observation ──────┐
│ 模型接收ToolMessage │
│ 判断是否继续 │──→ 回到 Reasoning
└────────────────────┘
💻 代码实战
import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_deepseek import ChatDeepSeek
from langchain_core.tools import tool
load_dotenv()
# ========== 重试机制示例 ==========
call_count = 0
@tool
def get_weather(city: str) -> str:
"""查询指定城市的天气信息"""
global call_count
call_count += 1
if call_count < 3:
return "天气服务暂时不可用,请稍后重试"
return f"{city}天气晴朗,温度15℃"
model = ChatDeepSeek(model="deepseek-chat")
agent = create_agent(model=model, tools=[get_weather])
response = agent.invoke({
"messages": [
{"role": "system", "content": "你是天气查询助手。当工具返回'暂时不可用'时请重试,最多3次后告知用户天气不可用。"},
{"role": "user", "content": "杭州天气如何?"}
]
})
# 查看完整的消息流转过程
for msg in response["messages"]:
print(f"[{msg.__class__.__name__}] {msg.content[:100] if hasattr(msg, 'content') else str(msg)[:100]}")
# ========== 限制调用次数 ==========
response = agent.invoke(
{"messages": ["北京天气如何?"]},
config={"recursion_limit": 5}
)
⚠️ 常见问题与避坑指南
问题1:Agent如何选择工具?
- 答案:依据工具的
docstring/description,由内部LLM做语义匹配。 - 建议:工具描述要清晰准确,准确描述工具的功能和适用场景。
问题2:为什么Agent没有调用工具?
- 工具描述不清晰,模型无法识别
- 用户问题表述不明确,模型不知道需要调用工具
- 模型认为自己可以直接回答,不需要调用工具
问题3:Agent选错了工具怎么办?
- 原因:多个工具描述太相似;一次性绑定了太多工具导致混淆
- 解决:
- 给出必要的工具即可,建议2~5个为最佳
- 每个工具的描述要有明确的区分度
- 在system prompt中说明各工具的使用场景
- 工具太多(几十个)且边界模糊时,调用会非常不稳定
问题4:如何知道Agent何时完成?
- 答案:AIMessage中
tool_calls列表为空时即完成。
问题5:Agent可以调用多少次工具?
- 理论上无限制,但可能因以下原因提前终止:
- 超时
- 达到Token限制
- 模型自行决定终止
- 可通过
config={"recursion_limit": N}硬性限制。
💡 个人总结与延伸
ReAct模式的"思考→行动→观察"循环是当前LLM Agent的主流范式,LangChain通过LangGraph将其编排为有向图结构。理解这一流程有助于调试Agent行为异常(如循环不终止、选错工具等)。工具数量的控制(2~5个)和描述清晰度是保证Agent稳定性的关键工程实践。在实际生产环境中,建议对每次工具调用的结果和推理过程做日志记录,便于追溯和优化。