Week 2 -- Day 4:Agent 系统(上)— 工具与 ReAct
如果说前面三天我们学习的 LLM、提示词模板和 RAG 都是在增强模型的输入那么从今天开始我们要进入一个完全不同的维度让模型拥有行动的能力。一个普通的 Chat Model 只能被动地回答它训练数据中已有的知识但一个 Agent 可以调用外部工具获取实时数据、执行计算、查询数据库、甚至操控其他软件在这个意义上Agent 系统让大语言模型从一台知识问答机进化成了能够自主决策的数字助手。工具定义给模型装上手脚工具Tool是 Agent 系统中最基础的单元。本质上一个工具就是一个带有类型标注和描述文档的 Python 函数LangChain 会把它包装成模型可以理解的结构化接口。模型通过函数名和描述来判断什么时候该用它通过参数类型和字段描述来决定该传什么参数工具的执行结果则以消息的形式返回给模型形成一轮闭环。在 LangChain v1 中创建工具最简单的方式是使用tool装饰器它把类型标注type hints自动转换为工具的输入 schema把函数的 docstring 提取为工具的用途描述模型就是靠这两样信息来决策何时调用工具的。fromlangchain.toolsimporttooltooldefsearch_database(query:str,limit:int10)-str:搜索客户数据库中的匹配记录。returnf在数据库中找到了{limit}条与 {query} 相关的结果。这段代码虽然短小但暗含了三层关键信息。函数的类型标注query: str, limit: int告诉模型调用时该提供哪些参数以及各自的类型docstring 用自然语言向模型解释了这个工具是做什么用的返回的字符串会在工具执行完成后重新送回模型成为它下一步推理的依据。你不需要手动编写 JSON Schematool会自动完成这些转换但如果你需要更精细的控制比如给参数添加自然语言描述或设置枚举值约束可以通过args_schema传入一个 Pydantic 模型来定义更复杂的输入结构。frompydanticimportBaseModel,FieldfromtypingimportLiteralclassWeatherInput(BaseModel):天气查询的输入参数。city:strField(description城市名称例如北京、东京或纽约)units:Literal[celsius,fahrenheit]Field(defaultcelsius,description温度单位celsius摄氏度或 fahrenheit华氏度)tool(args_schemaWeatherInput)defget_weather(city:str,units:strcelsius)-str:获取指定城市的当前天气信息。temp22ifunitscelsiuselse72returnf{city}当前天气晴{temp}°{CifunitscelsiuselseF}这里WeatherInput中的Field(description...)是至关重要的模型在决定是否调用get_weather以及如何填充参数时依赖的就是这些 description 字段。如果描述模糊或不够具体模型可能会在错误的情境下调用工具或者填入不合理的参数。一个值得注意的命名惯例是工具名应当使用 snake_case如web_search避免空格和特殊字符因为部分模型提供商会拒绝包含特殊字符的函数名。在实际项目中你可能会编写多种不同类型的工具比如一个调用外部 API 获取实时数据的工具、一个执行本地计算的处理工具或者一个查询本地数据库的数据检索工具。这些工具在代码层面没有本质区别都是遵循统一接口的 Python callable但它们的描述应该足够精准让模型能够根据用户意图在多工具场景下做出正确的选择。LangChain v1 还支持将工具的运行时上下文如对话历史、用户身份、持久化存储注入到工具函数中通过ToolRuntime参数实现不过这部分内容我们在后续深入探讨 Agent 状态管理时会详细展开。ReAct 模式推理与行动交替进行ReActReasoning Acting是 Agent 系统中最经典也是最重要的思维框架。这个由 Google Research 在 2022 年提出的模式后来成为 LangChain 中最早一批通用 Agent 的理论基础。它解决的问题非常朴素却深刻当模型面对一个无法单靠自身知识回答的复杂问题时它该怎么办ReAct 给出的答案是不要一次性输出最终答案而是像人类解决问题那样在思考和行动之间来回切换。它的执行流程可以用一条简单但优雅的循环链条来描述Question → Thought → Action → Observation → Thought → … → Final Answer。当用户提出一个问题Question模型首先产生一个思考Thought分析当前已知的信息判断是否需要调用外部工具。如果决定需要它会生成一个行动Action指定要调用哪个工具以及传入什么参数。工具执行后返回的观察结果Observation会注入到对话上下文中模型基于新增的信息再次进入思考阶段评估是否已经足够回答问题。如果还不够它会再次行动、再次观察直到信息充分后模型给出最终答案Final Answer循环结束。想象这样一个场景用户问请帮我查一下北京今天的气温如果高于 30°C 就提醒我带防晒霜。在 ReAct 的框架下模型的第一轮思考可能是我需要知道北京今天的实际温度这超出了我的训练数据范围应该调用天气查询工具。“于是它执行 Action调用get_weather工具并传入city北京。工具返回的 Observation 可能是北京当前 33°C晴”。模型收到这个信息后进入第二轮思考33°C 高于 30°C 的阈值我需要提醒用户带防晒霜。此时信息已经充分不再需要额外的工具调用模型直接生成 Final Answer“北京今天 33°C天气很热建议您出门带上防晒霜。”这个过程中有一个微妙的点值得留意什么决定了模型选择停下来并给出最终答案而不是无限地继续调用工具在早期的 ReAct Agent 实现中这依赖提示词工程通过 system prompt 告知模型在信息充足时直接输出最终答案。在 LangChain v1 的create_agent中这个停止条件被内建到了 Agent 的执行循环里当模型在一次模型调用中不再请求任何工具调用而是直接生成了文本回复时循环即告终止。另外max_iterations在旧版AgentExecutor中或中间件层面的迭代限制也可以作为安全阀防止 Agent 陷入无限循环。ReAct 的美妙之处在于它把推理和行动这两个原本在语言模型中完全分离的能力统一成了一个可迭代、可观测的过程。每一步思考都有明确的文本记录每一次工具调用都有可追溯的输入输出这为调试、审计和优化 Agent 行为提供了极大的便利。当我们开启verboseTrue旧版 API或通过 LangSmith 追踪新版推荐方式就能清晰地看到 Thought-Action-Observation 每一步的具体内容从而理解模型当时为什么做出了某个决策。Agent 构建从 ReAct 到 create_agent在 LangChain 的历史上构建 ReAct Agent 的 API 经历了显著的演变。早期约 2023 年的做法是使用langchain.agents中的create_react_agent搭配AgentExecutor通过手动传入 ReAct 风格的提示词模板和工具列表来构建 Agent再用AgentExecutor控制迭代次数、是否输出详细日志以及如何处理解析错误。这个 API 组合在工作了两年之后于 LangChain v1约 2025 年底被标记为 deprecated取而代之的是一个更简洁、更强大的新入口langchain.agents.create_agent。旧版 API 的核心组件是AgentExecutor它的职责是运行 Agent 的思考-行动循环管理max_iterations最大迭代次数、verbose详细输出和handle_parsing_errors解析错误处理等行为参数。新版create_agent将所有这些能力重构为了可组合的中间件Middleware系统你可以通过ModelRetryMiddleware自动重试模型调用失败通过ToolRetryMiddleware在工具执行出错时自动重试通过wrap_tool_call自定义工具异常的处理策略甚至可以接入HumanInTheLoopMiddleware在关键操作前暂停等待人工审批。这种核心循环 可插拔中间件的架构设计比旧版的一个大而全的 Executor 类要灵活得多也更符合面向生产环境的实际需求。尽管 API 发生了变化但底层的 ReAct 循环本质上是一样的。用新版create_agent构建一个带工具的 Agent 只需要寥寥几行fromdotenvimportload_dotenvfromlangchain.agentsimportcreate_agentfromlangchain.toolsimporttoolfromlangchain_openaiimportChatOpenAI load_dotenv()tooldefget_weather(city:str)-str:获取指定城市的天气returnf{city}晴25度tooldefcalculate(expression:str)-str:执行数学计算输入一个算术表达式返回计算结果returnstr(eval(expression))tooldefquery_database(sql:str)-str:执行 SQL 查询并返回结果。仅支持 SELECT 语句。return查询结果[...]modelChatOpenAI(modelQwen/Qwen3.6-35B-A3B,temperature0,base_urlhttps://api.siliconflow.cn/v1)agentcreate_agent(modelmodel,tools[get_weather,calculate,query_database],system_prompt你是一个智能助手可以查询天气、执行计算和检索数据库。遇到不确定的信息时优先使用工具。)resultagent.invoke({messages:[{role:user,content:北京今天多少度华氏度是多少公式℉ ℃ × 9/5 32}]})print(result[messages][-1].content)当你运行这段代码时Agent 在后台执行的正是 ReAct 循环。模型首先收到用户问题进入 Thought 阶段它意识到需要两个步骤先调用get_weather获取北京的温度再用calculate将摄氏度转换为华氏度。于是它发出第一个 Action调用get_weather(city北京)得到 Observation “北京晴25°C”。接着在第二轮思考中模型知道下一步需要计算25 × 9/5 32发出第二个 Action调用calculate(expression25 * 9/5 32)得到 Observation “77.0”。最后信息齐全模型直接输出 Final Answer “北京今天 25°C换算成华氏度约为 77°F。”这个例子也展示了一个重要的点Agent 的每一次工具调用都是一次独立的模型推理。调用get_weather后模型接收到了观测结果它需要重新思考下一步该做什么是继续调用工具还是给出最终答案。这种边看边走的执行模式赋予了 Agent 处理复杂多步任务的能力但同时也意味着如果工具描述不够精确模型可能在中途迷路。因此工具的 docstring 和参数 description 绝不是可有可无的注释它们直接决定了 Agent 的决策质量。错误处理当工具出错时生产环境中的 Agent 不可能一帆风顺。API 可能超时数据库可能连接失败模型可能生成格式错误的工具调用参数。在旧版AgentExecutor中handle_parsing_errorsTrue是一个粗暴但有效的开关当模型输出的格式无法被解析时Executor 会把错误信息回传给模型让它自行修正。新版create_agent提供了更精细的控制手段最常用的是通过中间件在工具调用层面捕获异常并将其转化为模型可理解的错误消息。fromlangchain.agentsimportcreate_agentfromlangchain.agents.middlewareimportwrap_tool_callfromlangchain.tools.tool_nodeimportToolCallRequestfromlangchain.messagesimportToolMessagefromcollections.abcimportCallablefromlangchain_openaiimportChatOpenAIfromlangchain.toolsimporttoolfromdotenvimportload_dotenv load_dotenv()tooldefget_weather(city:str)-str:获取指定城市的天气returnf{city}晴25度tooldefcalculate(expression:str)-str:执行数学计算输入一个算术表达式返回计算结果returnstr(eval(expression))tooldefquery_database(sql:str)-str:执行 SQL 查询并返回结果。仅支持 SELECT 语句。return查询结果[...]wrap_tool_calldefhandle_tool_errors(request:ToolCallRequest,handler:Callable[[ToolCallRequest],ToolMessage])-ToolMessage:try:returnhandler(request)exceptExceptionase:returnToolMessage(contentf工具执行出错请检查输入参数后重试。错误详情{e},tool_call_idrequest.tool_call[id],)modelChatOpenAI(modelQwen/Qwen3.6-35B-A3B,temperature0,base_urlhttps://api.siliconflow.cn/v1)agentcreate_agent(modelmodel,tools[get_weather,calculate,query_database],middleware[handle_tool_errors],system_prompt你是一个智能助手可以查询天气、执行计算和检索数据库。遇到不确定的信息时优先使用工具。)resultagent.invoke({messages:[{role:user,content:北京今天多少度华氏度是多少公式℉ ℃ × 9/5 32}]})print(result[messages][-1].content)这里wrap_tool_call中间件像一个透明的拦截器包裹在每一个工具调用之外。如果工具成功执行它的返回值照常传回模型如果抛出了异常异常被捕获后不是让整个 Agent 崩溃而是被包装成一条ToolMessage包含了出错提示和原始的工具调用 ID模型收到这条消息后会像对待正常的工具返回结果一样分析它然后决定是重试、换一种参数还是告知用户当前无法完成请求。这与 ReAct 的核心哲学一脉相承把一切包括失败都转化为可推理的信息。另外如果模型调用本身由于网络波动或服务端限流而失败ModelRetryMiddleware可以在中间件层面自动重试配合指数退避策略让瞬态错误不会中断整个 Agent 执行如果你有多个模型提供商ModelFallbackMiddleware还能在主力模型宕机时自动切换到备用模型。这些面向容错的中间件构成了 LangChain v1 对生产级 Agent 的核心保障也体现了从原型能跑到线上稳定的工程思维跃迁。练习任务编写 3 个自定义工具一个调用公开 API 获取实时数据如天气、汇率或新闻一个执行本地计算如四则运算或单位换算一个模拟数据查询如从本地 JSON 文件或内存字典中检索信息。每个工具需包含完整的args_schema定义参数 description 应足够具体。使用create_agentLangChain v1 推荐构建一个 ReAct Agent向它提出一个需要至少两次工具调用才能回答的复合问题观察并记录 Agent 的完整推理过程。如果你使用的是旧版 LangChain可用create_react_agentAgentExecutor并开启verboseTrue。将 Agent 输出的 Thought-Action-Observation 步骤逐轮标记出来用三列表格也可以手工画成流程图展示每一步的思考、行动和观察内容分析模型在多步推理中的决策逻辑。刻意制造一个工具执行异常的场景例如在工具函数内部抛出异常或传入格式错误的参数观察并记录 Agent 的错误恢复行为。如果你使用的是新版 API请使用wrap_tool_call中间件来自定义异常处理如果使用旧版 API请测试handle_parsing_errorsTrue的效果。考核点 ✅工具编写提交 3 个自定义工具函数的完整代码每个都必须使用tool装饰器并包含args_schemaPydantic 模型或 JSON Schema 均可。工具的 docstring 和参数 description 需要足够具体使得另一个人类开发者仅凭这些信息就能判断何时使用该工具以及如何传参。ReAct 运行提交完整的 ReAct Agent 代码对一个需要两次或以上工具调用的复合问题输出完整推理过程。在输出中标注每一轮的 Thought、Action、Observation 和最终的 Final Answer形成清晰的步骤对应关系。过程分析用文字或图表分析 Agent 的推理链路。至少包含以下要素用户问题经过了几轮 Thought-Action-Observation 循环、每一轮中模型选择调用哪个工具的依据是什么、工具返回了什么信息、这些信息如何影响了下一轮的决策以及最终答案是基于哪些观测结果得出的。错误处理演示工具返回异常时 Agent 的恢复行为。在新的create_agent体系下提交使用wrap_tool_call中间件进行自定义异常处理的代码在旧的AgentExecutor体系下提交handle_parsing_errorsTrue的配置及运行效果。无论哪种方式都需要展示 Agent 收到错误信息后的后续行为——它是否尝试了修正输入并重试、是否切换到了备选方案、还是在无法恢复时向用户坦承失败。