当前位置: 首页 > news >正文

Week 2 -- Day 4:Agent 系统(上)— 工具与 ReAct

如果说前面三天我们学习的 LLM、提示词模板和 RAG 都是在"增强模型的输入",那么从今天开始,我们要进入一个完全不同的维度,让模型拥有"行动的能力"。一个普通的 Chat Model 只能被动地回答它训练数据中已有的知识,但一个 Agent 可以调用外部工具获取实时数据、执行计算、查询数据库、甚至操控其他软件,在这个意义上,Agent 系统让大语言模型从一台"知识问答机"进化成了能够自主决策的数字助手。

工具定义:给模型装上手脚

工具(Tool)是 Agent 系统中最基础的单元。本质上,一个工具就是一个带有类型标注和描述文档的 Python 函数,LangChain 会把它包装成模型可以理解的结构化接口。模型通过函数名和描述来判断"什么时候该用它",通过参数类型和字段描述来决定"该传什么参数",工具的执行结果则以消息的形式返回给模型,形成一轮闭环。在 LangChain v1 中,创建工具最简单的方式是使用@tool装饰器,它把类型标注(type hints)自动转换为工具的输入 schema,把函数的 docstring 提取为工具的用途描述,模型就是靠这两样信息来决策何时调用工具的。

fromlangchain.toolsimporttool@tooldefsearch_database(query:str,limit:int=10)->str:"""搜索客户数据库中的匹配记录。"""returnf"在数据库中找到了{limit}条与 '{query}' 相关的结果。"

这段代码虽然短小,但暗含了三层关键信息。函数的类型标注query: str, limit: int告诉模型调用时该提供哪些参数以及各自的类型,docstring 用自然语言向模型解释了这个工具是做什么用的,返回的字符串会在工具执行完成后重新送回模型,成为它下一步推理的依据。你不需要手动编写 JSON Schema,@tool会自动完成这些转换,但如果你需要更精细的控制,比如给参数添加自然语言描述或设置枚举值约束,可以通过args_schema传入一个 Pydantic 模型来定义更复杂的输入结构。

frompydanticimportBaseModel,FieldfromtypingimportLiteralclassWeatherInput(BaseModel):"""天气查询的输入参数。"""city:str=Field(description="城市名称,例如北京、东京或纽约")units:Literal["celsius","fahrenheit"]=Field(default="celsius",description="温度单位:celsius(摄氏度)或 fahrenheit(华氏度)")@tool(args_schema=WeatherInput)defget_weather(city:str,units:str="celsius")->str:"""获取指定城市的当前天气信息。"""temp=22ifunits=="celsius"else72returnf"{city}当前天气:晴,{temp}°{'C'ifunits=='celsius'else'F'}"

这里WeatherInput中的Field(description=...)是至关重要的,模型在决定是否调用get_weather以及如何填充参数时,依赖的就是这些 description 字段。如果描述模糊或不够具体,模型可能会在错误的情境下调用工具,或者填入不合理的参数。一个值得注意的命名惯例是工具名应当使用 snake_case(如web_search),避免空格和特殊字符,因为部分模型提供商会拒绝包含特殊字符的函数名。

在实际项目中,你可能会编写多种不同类型的工具,比如一个调用外部 API 获取实时数据的工具、一个执行本地计算的处理工具,或者一个查询本地数据库的数据检索工具。这些工具在代码层面没有本质区别,都是遵循统一接口的 Python callable,但它们的描述应该足够精准,让模型能够根据用户意图在多工具场景下做出正确的选择。LangChain v1 还支持将工具的运行时上下文(如对话历史、用户身份、持久化存储)注入到工具函数中,通过ToolRuntime参数实现,不过这部分内容我们在后续深入探讨 Agent 状态管理时会详细展开。

ReAct 模式:推理与行动交替进行

ReAct(Reasoning + 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 行为提供了极大的便利。当我们开启verbose=True(旧版 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"查询结果:[...]"model=ChatOpenAI(model="Qwen/Qwen3.6-35B-A3B",temperature=0,base_url="https://api.siliconflow.cn/v1")agent=create_agent(model=model,tools=[get_weather,calculate,query_database],system_prompt="你是一个智能助手,可以查询天气、执行计算和检索数据库。遇到不确定的信息时优先使用工具。")result=agent.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(expression="25 * 9/5 + 32"),得到 Observation “77.0”。最后,信息齐全,模型直接输出 Final Answer “北京今天 25°C,换算成华氏度约为 77°F。”

这个例子也展示了一个重要的点,Agent 的每一次工具调用都是一次独立的模型推理。调用get_weather后,模型接收到了观测结果,它需要重新思考"下一步该做什么",是继续调用工具还是给出最终答案。这种"边看边走"的执行模式赋予了 Agent 处理复杂多步任务的能力,但同时也意味着如果工具描述不够精确,模型可能在中途"迷路"。因此工具的 docstring 和参数 description 绝不是可有可无的注释,它们直接决定了 Agent 的决策质量。

错误处理:当工具出错时

生产环境中的 Agent 不可能一帆风顺。API 可能超时,数据库可能连接失败,模型可能生成格式错误的工具调用参数。在旧版AgentExecutor中,handle_parsing_errors=True是一个粗暴但有效的开关,当模型输出的格式无法被解析时,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(content=f"工具执行出错,请检查输入参数后重试。错误详情:{e}",tool_call_id=request.tool_call["id"],)model=ChatOpenAI(model="Qwen/Qwen3.6-35B-A3B",temperature=0,base_url="https://api.siliconflow.cn/v1")agent=create_agent(model=model,tools=[get_weather,calculate,query_database],middleware=[handle_tool_errors],system_prompt="你是一个智能助手,可以查询天气、执行计算和检索数据库。遇到不确定的信息时优先使用工具。")result=agent.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_agent(LangChain v1 推荐)构建一个 ReAct Agent,向它提出一个需要至少两次工具调用才能回答的复合问题,观察并记录 Agent 的完整推理过程。如果你使用的是旧版 LangChain,可用create_react_agent+AgentExecutor并开启verbose=True
  • 将 Agent 输出的 Thought-Action-Observation 步骤逐轮标记出来,用三列表格(也可以手工画成流程图)展示每一步的思考、行动和观察内容,分析模型在多步推理中的决策逻辑。
  • 刻意制造一个工具执行异常的场景(例如在工具函数内部抛出异常,或传入格式错误的参数),观察并记录 Agent 的错误恢复行为。如果你使用的是新版 API,请使用wrap_tool_call中间件来自定义异常处理;如果使用旧版 API,请测试handle_parsing_errors=True的效果。

考核点 ✅

  1. 工具编写:提交 3 个自定义工具函数的完整代码,每个都必须使用@tool装饰器,并包含args_schema(Pydantic 模型或 JSON Schema 均可)。工具的 docstring 和参数 description 需要足够具体,使得另一个人类开发者仅凭这些信息就能判断何时使用该工具以及如何传参。
  2. ReAct 运行:提交完整的 ReAct Agent 代码,对一个需要两次或以上工具调用的复合问题输出完整推理过程。在输出中标注每一轮的 Thought、Action、Observation 和最终的 Final Answer,形成清晰的步骤对应关系。
  3. 过程分析:用文字或图表分析 Agent 的推理链路。至少包含以下要素:用户问题经过了几轮 Thought-Action-Observation 循环、每一轮中模型选择调用哪个工具的依据是什么、工具返回了什么信息、这些信息如何影响了下一轮的决策,以及最终答案是基于哪些观测结果得出的。
  4. 错误处理:演示工具返回异常时 Agent 的恢复行为。在新的create_agent体系下,提交使用wrap_tool_call中间件进行自定义异常处理的代码;在旧的AgentExecutor体系下,提交handle_parsing_errors=True的配置及运行效果。无论哪种方式,都需要展示 Agent 收到错误信息后的后续行为——它是否尝试了修正输入并重试、是否切换到了备选方案、还是在无法恢复时向用户坦承失败。
http://www.cnnetsun.cn/news/2779784.html

相关文章:

  • 拆解一颗芯片的诞生:手把手图解MOSFET制造中的12个关键步骤(附工艺对照表)
  • PowerBuilder 12.5 实战:用自定义可视对象(Custom Visual)快速搞定日期范围查询组件
  • 2024青岛烧烤实测!那些年一起吃串的地方,本地人私藏老牌连锁餐厅
  • 别再死记硬背了!用这5个真实业务场景,彻底搞懂数据库关系代数(附SQL对照)
  • 【2024智能娱乐生产力跃迁】:仅用3类开源AI工具+1套标准化API协议,将内容生产效率提升470%(实测数据)
  • 别再死记硬背数组地址公式了!用Python模拟龙书6.4节习题,彻底搞懂行/列优先存储
  • 给PL/0编译器“打补丁”:手把手教你用C语言实现IF-ELSE和复合运算符
  • 新手友好:在快马平台上从零开始构建你的第一个winhance工具
  • Claude Code多文件实战:跨文件操作和项目管理的最佳实践
  • 【Claude情景规划实战指南】:20年AI架构师亲授5大高阶技巧,避开90%团队踩过的认知陷阱
  • 如何3分钟破解JSXBIN加密文件:Jsxer反编译工具终极指南
  • 新手入门网页开发,用快马AI生成带注释的谷歌邮箱注册页面代码
  • 别再傻傻分不清了!SystemVerilog里logic、reg和wire到底该用哪个?(附代码避坑指南)
  • 探秘近 50 年 ANSI 编码:如何成就多彩终端交互体验?
  • 从零到一:用TensorFlow 2.3和MobileNet构建一个高精度果蔬识别App(附完整代码和数据集)
  • 实战派指南:用Python脚本自动查询LTE频段参数与计算EARFCN
  • 告别理论懵圈!用Multisim动画演示高频谐振功放LC回路调谐与效率关系
  • 告别命令行恐惧:用Docker一键部署Viper(炫彩蛇)图形化渗透平台
  • 网站突然崩溃卡顿?带你彻底读懂 DDoS 攻击与防御
  • 免费分享一个站长域名筛选工具:Domain Finder Pro
  • 别再乱用fread了!C语言文件读取的5个实战避坑指南(含Windows/Linux差异)
  • 【计算机毕业设计案例】基于springboot+微信小程序的新冠疫情防控信息管理系统(程序+文档+讲解+定制)
  • 语义压缩,才是提示词工程的底层心法
  • 为什么AI搞不定Base64?一个开源项目Issue里的“暗号”告诉你真相
  • 医疗大模型临床应用突围战(FDA/国药监双认证实操手册)
  • 拆解柔性线路板原材料定价底层逻辑
  • 清新个性网站制作
  • 2026年佛山三水矿泉水灌装机,高效灌装新标杆
  • 便携车载 CAN 数据记录仪|CANFDLog-OTL4-X:告别车载拖线电脑,离线搞定 CAN FD+XCP 全量数据采集
  • AI伦理风险暴雷前夜:7类高频违规场景、3级预警机制及即刻自查指南