‹ 博客列表

LangChain 1.2 入门(二十四):绑定工具与 ReAct 循环——Agent 才真正成立

从 start→model→end 长到带 tools 节点的图:绑工具、接内置 Tavily、多工具并行调用、ReAct 五步走查,以及靠提示词约定实现的重试与六个常见问题。

LangChain 1.2 入门(二十四):绑定工具与 ReAct 循环——Agent 才真正成立

只给 create_agent 一个模型,得到的是一张 start → model → end 的直线图,说白了还是个问答机。接上工具之后,图里才会长出 tools 节点和回边——那才是 Agent。

24.1 绑定一个工具

工具就是上一章定义过的 @tool 函数,直接放进 tools 列表:

from langchain.agents import create_agent
from langchain.tools import tool

@tool(parse_docstring=True)
def get_weather(city: str) -> str:
    """
    天气查询工具
    Args:
        city: 城市名称
    """
    return f"{city} 的天气为晴朗,25°C。"

agent = create_agent(model=model, tools=[get_weather])

resp = agent.invoke({
    "messages": [
        {"role": "system", "content": "你是一个天气查询助手,只回答天气相关的问题"},
        {"role": "user", "content": "北京的天气怎么样?"},
    ]
})

返回的消息列表能清楚看到四步:

SystemMessage     你是一个天气查询助手...
HumanMessage      北京的天气怎么样?
AIMessage         tool_calls=[{'name': 'get_weather', 'args': {'city': '北京'}}]
ToolMessage       北京的天气为晴朗,25°C。
AIMessage         北京天气晴朗,25°C。

这就是一次典型的 Function Calling:模型给调用请求,框架执行工具,结果回传,模型再说人话。整个循环由 Agent 替你跑完,不用像工具那一章那样自己写 while True。

24.2 内置工具:少写代码,多接能力

LangChain 生态集成了大量现成工具(官方列表见 integrations/tools)。以 Tavily 搜索为例,先到官网注册拿 API-KEY 写进 .env 的 TAVILY_API_KEY:

from langchain_tavily import TavilySearch

web_search = TavilySearch(max_results=2)
web_search.invoke("请问 2026 年足球世界杯有哪些参赛队?")
# {'query': '...', 'results': [{'url': ..., 'title': ..., 'content': ..., 'score': 0.997}]}

agent = create_agent(model=model, tools=[web_search])
result = agent.invoke({"messages": [
    {"role": "user", "content": "请帮我查询 2024 年诺贝尔物理学奖得主是谁?"}
]})
print(result["messages"][-1].content)

内置工具的价值是省掉「定义 + 解析」的活,但代价是黑盒:它的 docstring 你改不了,模型误用时只能靠提示词纠正。

24.3 多个工具:一次响应可以要两个

@tool(parse_docstring=True)
def get_weather(city: str):
    """
    天气查询工具
    Args:
        city: 城市名称
    """
    return f"{city} 今天天气挺好"

@tool(parse_docstring=True)
def get_news():
    """
    新闻查询工具
    """
    return "近期废旧手机回收市场迎来涨价潮。"

agent = create_agent(model, tools=[get_weather, get_news])
response = agent.invoke({
    "messages": ["你好,杭州今天的天气如何?今天有哪些新闻?"]
})

模型在一个 AIMessage 里同时给出两个 tool_calls(get_weather(city="杭州") 和 get_news()),框架并行执行、各自回一条 ToolMessage,最后一条 AIMessage 汇总。

工具数量要克制:一般 2–5 个最佳。工具太多,模型在相似描述之间反复犹豫,选错率明显上升。

24.4 图长什么样:ReAct 循环

接上工具后,图会变成 start → model → (tools → model)* → end。在 LangGraph 里 tools 是条件边的一个分支,判断依据就是「这条 AIMessage 里有没有 tool_calls」。

AI 智能体工作流:用户输入 → Agent 分析 → 判断是否需要工具 → 是则调用工具并获取结果 → 生成回答;否则直接回答

这张图换成实现视角,就是经典的 ReAct 结构——「思考-行动-观察」不断循环:

ReAct 循环:左侧 LangGraph 图中 model 节点分支到 tools 或 output,右侧对应 用户输入 → LLM 推理 → 执行工具 → 返回观察结果 → 最终输出

流程可以概括成一句话:用户问题 → AI 思考 → 调用工具 → 观察结果 → 继续思考 → … → 最终答案。结束条件是某次模型输出里不再包含工具调用。

举个具体例子,用户问「找出当前最流行的无线耳机并检查库存」:

  1. 输入解析与初始推理:模型判断「要判断‘最流行’,需要最新市场信息,先搜索」;
  2. 第一次行动与观察:调用 search_products("wireless headphones"),拿到「找到 5 款,Top 结果 WH-1000XM5」;
  3. 迭代推理:认定 WH-1000XM5 就是答案候选,但还缺库存;
  4. 第二次行动与观察:调用 check_inventory("WH-1000XM5"),得到「库存 10 件」;
  5. 最终输出:信息齐了,生成答案并且不再调用工具。

这五步没有任何一步是你在代码里写的——你只提供了两个工具和一段提示词。

24.5 重试机制:靠约定,不靠魔法

工具失败时 Agent 可以自主重试,但前提是你把失败信息写成模型能看懂的约定。下面这个工具前两次返回固定前缀的错误串,第三次才成功:

from langchain.messages import SystemMessage, HumanMessage

flag = 0

@tool
def get_weather(city: str):
    """
    天气查询工具
    Args:
        city: 城市名称
    """
    global flag
    flag += 1
    if flag < 3:
        return "TEMP_UNAVAILABLE: 天气服务暂时不可用,请稍后重试"
    return f"{city} 今天天气挺好"

messages = [
    SystemMessage("""
    你是一个天气助手。
    当工具返回以 'TEMP_UNAVAILABLE:' 开头的结果时,
    说明是临时故障,不要立即放弃;
    你应再次调用同一个工具,最多重试 3 次。
    如果 3 次后仍失败,再向用户说明服务暂时不可用。
    """),
    HumanMessage("你好,杭州今天的天气如何?"),
]

agent = create_agent(model, tools=[get_weather])
response = agent.invoke({"messages": messages})

输出里能看到三轮 get_weather 调用:前两次拿到 TEMP_UNAVAILABLE,第三次成功,最后给出答案。两个细节值得记:

  • 用返回值表达失败,而不是抛异常。抛异常会中断流程(除非中间件接管),返回约定字符串则把决策权交回模型;
  • 重试策略必须写在提示词里,包括「什么算临时故障」「最多几次」「几次之后怎么回」。

24.6 常见问题

Agent 怎么选工具?靠工具的 docstring。模型综合问题内容和每个工具的描述,选最匹配的那个。描述是唯一的信息来源,这一点和上一章讲工具时完全一致。

为什么没调用我的工具?三种可能:docstring 不清晰;问题表述模糊;模型判断不需要工具。前两种改描述和提示词,第三种加一句「必须使用工具核实」。

为什么选错工具?多个工具功能描述相似,或者工具太多。解决办法是只给必要的工具、把描述写出明确区分、在 system_prompt 里说明各自的使用场景。

# ❌ 太模糊,模型无法判断
@tool
def tool1(x: str) -> str:
    """做一些事情"""

# ✅ 用途、参数、返回值都写清楚
@tool
def get_weather(city: str) -> str:
    """
    获取指定城市的实时天气信息
    Args:
        city: 城市名称,如 "北京"、"上海"
    """

怎么知道 Agent 完成了?看 AIMessage 有没有 tool_calls,没有就是最终答案:

for msg in response["messages"]:
    if isinstance(msg, AIMessage):
        if hasattr(msg, "tool_calls") and msg.tool_calls:
            print("还在调用工具...")
        else:
            print("完成!最终答案:", msg.content)

能调用多少次工具?默认没有限制,直到模型给出最终答案。风险是超时、撞上 token 上限,或者模型自己停不下来。要限制就用 recursion_limit:

config = {"recursion_limit": 5}   # 最多 5 步
response = agent.invoke({"messages": messages}, config=config)

24.7 小结

接上工具,Agent 才真正成立:调用一次 invoke,内部可能是多轮 ReAct 循环;工具靠 docstring 被选中,数量控制在 2–5 个;失败重试要用「返回值 + 提示词约定」来实现,而不是依赖框架猜你的意图;要防止无限循环,用 recursion_limit。

下一篇讲 Agent 的三个高级开关:给 Agent 起名字、写系统提示词,以及最容易被低估的——让 Agent 输出结构化数据。

上一篇:Agent 是什么——create_agent 与第一张图;系列第一篇:为什么需要 LangChain。

本文根据公开课程课件整理,并结合 LangChain 官方文档核对与补充。

本文来自 https://aimock.cn/blog,站内重新编排。
内容仅供学习交流,转载请注明出处。