大语言模型擅长生成文本,却无法自己查询天气、执行SQL或发送HTTP请求。要让模型具备行动能力,需要在推理和工具之间建立一个受控的循环,LangChain的Agent模块正是为此设计。其中Zero-shot ReAct Agent不要求为每个工具准备演示示例,适合大多数初步接入场景。下面会从工作机制开始,再到代码实现、运行流程和选型注意,完整走一遍。

一、Zero-shot ReAct Agent 的核心机制
ReAct的全称是Reasoning + Acting,也就是推理与行动交替进行。模型不会一次性给出最终答案,而是按照固定的循环步骤推进。每一步模型都要输出一个Thought,说明它当前在思考什么;接着输出一个Action,指定要调用哪个工具;然后系统执行该工具并把返回结果作为Observation重新喂给模型。这个循环会持续到模型认为信息足够,输出Final Answer为止。
Zero-shot的含义是不为每个工具单独编写Few-shot示例。在Few-shot模式中,提示词会包含若干条完整的输入输出范本,帮助模型理解工具用法。而Zero-shot模式只提供工具名称、参数说明和格式约束,完全依赖模型自身的语义理解能力。这意味着提示词更短、维护成本更低,但对模型的基础能力要求更高。好在当前主流模型对常见的ReAct格式已经非常熟悉,Zero-shot模式在多数简单场景下表现稳定。
可以将这个循环拆成下表所示的几个环节:
| 步骤 | 模型输出 | 代理动作 |
|---|---|---|
| 思考 | Thought: 需要先获取单词长度 | 等待模型继续输出 |
| 行动 | Action: get_word_length | 根据工具名匹配函数 |
| 参数 | Action Input: {"word": "strawberry"} | 解析参数并执行工具 |
| 观察 | Observation: 10 | 把结果追加到上下文 |
| 结束 | Final Answer: strawberry有10个字母 | 返回给用户 |
理解了机制之后,下一步就可以直接写代码来验证。一个典型的工具定义如下:
from langchain.agents import tool
@tool
def get_word_length(word: str) -> int:
"""返回一个英文单词的字母数量。"""
return len(word)
这里使用@tool装饰器把普通函数包装成LangChain可以识别的工具。函数的文档字符串至关重要,因为它会作为工具描述传给模型,模型根据这段描述决定何时调用该工具。描述写得越清楚,模型越不容易出错。
二、用LangChain快速搭建一个可运行示例
要创建一个Agent,需要准备四样东西:一个语言模型、一个工具列表、一个提示词模板和一个执行器。语言模型负责推理和决策,工具列表提供可执行的能力,提示词模板约束输出格式,执行器则把整个循环串起来。
先安装必要的依赖:
pip install langchain langchain-openai
接着初始化模型和工具。下面这段代码会从LangChain Hub拉取官方提供的ReAct提示词,然后创建Agent和AgentExecutor:
from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [get_word_length]
prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = executor.invoke({"input": "单词 strawberry 有几个字母?"})
print(result["output"])
创建Agent时使用create_react_agent,它会绑定模型、工具和提示词。AgentExecutor是真正执行循环的组件,它负责调用模型、解析Action、执行工具并更新上下文。verbose=True可以打印完整执行过程,对调试非常有用。
在这个例子中,模型会先输出Thought,然后给出Action和Action Input。AgentExecutor解析出工具名get_word_length和参数{"word": "strawberry"},调用函数得到结果10,再把这个结果作为Observation拼回上下文。模型看到Observation后判断信息已经足够,于是输出Final Answer。整个过程不需要人工干预。
三、运行流程拆解与常见问题
上面的执行过程表面看起来简单,但背后涉及多个组件协同。提示词模板通常包含tools、tool_names和agent_scratchpad三个关键变量,分别对应工具描述列表、工具名列表和历史执行记录。每次循环开始时,执行器会把最新的Observation追加到agent_scratchpad,然后重新渲染提示词并请求模型。
一次典型的执行记录可以简化成下面这段文本:
Thought: 我需要计算 strawberry 的长度
Action: get_word_length
Action Input: {"word": "strawberry"}
Observation: 10
Thought: 我已经得到结果
Final Answer: strawberry 有 10 个字母
模型输出的Action部分会被输出解析器捕获。解析器会按照约定格式提取工具名和参数,如果提取失败,AgentExecutor会抛出解析错误。这是实际开发中最常见的故障之一。解决办法通常是降低temperature、更换更强的模型,或者改写提示词使格式要求更明确。
另一个高频问题是死循环。模型可能在得到Observation后反复调用同一个工具,却始终不输出Final Answer。对此可以在AgentExecutor中设置最大迭代次数和提前停止策略:
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=5,
early_stopping_method="force",
)
当循环次数达到上限时,early_stopping_method="force"会强制返回当前阶段生成的文本,避免任务无限挂起。此外,工具描述不够清晰也可能导致模型在多个工具之间反复横跳。建议每个工具的描述都明确说明适用场景、参数含义和返回值格式。
四、Zero-shot 与 Few-shot 的选择及生产落地
Zero-shot和Few-shot并非绝对优劣,而是适应不同阶段。Zero-shot优势在于提示词短、维护成本低,非常适合工具数量不多、工具语义清晰的场景。一旦工具数量增加到十几个,或者某些工具参数复杂、容易出现歧义,Few-shot示例的价值就会凸显出来。示例可以直观地告诉模型如何处理边界情况,减少解析错误和误调用。
但在生产环境中选择Zero-shot时,需要额外关注几个问题。首先是工具执行超时。如果某个工具需要访问外部API或数据库,长时间无响应会拖垮整个Agent循环。其次是工具权限控制。模型生成的参数可能包含危险操作,例如执行删除或写入命令,必须在工具实现层做白名单校验。最后是日志记录。生产环境建议记录每一轮Thought、Action和Observation,便于复盘模型决策链。
一个更贴近生产的工具示例如下,它在函数内部增加了异常捕获,避免异常直接中断Agent循环:
@tool
def search_database(query: str) -> str:
"""根据 SQL 查询业务数据库,返回 JSON 字符串。"""
try:
# 实际项目里替换为连接池查询
return '{"rows": 3, "status": "ok"}'
except Exception as e:
return f"error: {e}"
从整体来看,Zero-shot ReAct Agent是一种低成本、高灵活性的工具调用方案。它把语言模型的推理能力与外部函数连接起来,让模型从只会生成文本进化到可以执行具体动作。虽然生产环境中还面临解析稳定性、超时控制和工具安全等问题,但只要把这些基础环节处理好,Zero-shot ReAct Agent完全可以作为自动化流程的第一版落地架构。后续再根据实际任务复杂度,逐步加入Few-shot示例、更严格的输出解析器或更细粒度的工具权限控制。
LangChain AgentZero-shot ReActReAct框架修改时间:2026-09-23 20:06:12