本来想先聊概念但我觉得更实在的是先回答一个大家反复问的问题为什么我明明接上了大模型API也给了文档它还是没法老老实实把活干完答案往往就是缺了工具这一层。大模型再聪明它的大脑里没有实时数据没有业务系统的权限它只能想不能做。LangChain Agent要解决的正是让模型从只会想变成能调工具去执行。这篇博文的内容就是围绕LangChain Agent实战展开从核心原理到一个能跑起来的多工具智能体再到生产环境会踩的坑一条线捋清楚。适合刚掌握LangChain基础、准备往Agent方向进阶的开发者也适合已经在做Agent但想系统梳理工具调用链路的人。1. 你需要在动手之前搞清楚的Agent核心原理1.1 智能体不是套壳聊天机器人模型、工具、编排三元组在我看过的大量半成品项目里最常见的误解是接一个LLM写一段System Prompt叫它你是智能助手可以调用工具然后就没有然后了。这样做的结果是模型确实会在回复里假装调用工具比如它会输出让我为您查询天气或者计算结果为42但从不真正执行任何函数。这种体验本质上是套壳聊天机器人不是Agent。真正的Agent至少要包含三个角色模型Model负责理解意图、规划步骤、决定该调哪个工具、传什么参数。它是大脑。工具Tools负责真正落地执行比如查数据库、调API、算数、发邮件。它是手脚。编排Orchestration负责把模型和工具的循环跑起来拿到模型输出的调用指令执行工具再把执行结果喂回给模型让模型继续决策。它是神经传导系统。理解这三者的分工比记住任何API都重要。因为LangChain只是帮你把这三件事接起来但怎么接、接到什么程度决定你做出来的东西是花架子还是真能用。1.2 工具调用Tool Calling到底发生了什么从模型输出到函数执行很多人第一次接触Tool Calling时会困惑模型是怎么知道我有这个工具的难道我上传了函数代码不是。LangChain在请求模型时会把你的工具定义函数名、参数结构、docstring描述通过API的tools参数一并传过去。OpenAI、Claude、Gemini这些主流模型在预训练阶段就被专门训练过看工具定义、输出结构化调用意图的能力。流程是这样的你定义工具LangChain将工具转换成模型能理解的JSON Schema格式。用户提问LangChain把用户消息加上工具定义一起发给模型。模型决定需要调用工具时不直接执行而是返回一个结构化的tool_calls请求里面包含工具名、参数JSON、调用ID。LangChain收到tool_calls后在你的进程里执行对应的Python函数。执行结果作为一条工具消息返回给模型。模型根据工具返回的真实结果生成最终回复。这个机制有点像你去餐厅点菜你模型拿到菜单工具schema决定点宫保鸡丁tool_calls后厨工具函数做菜服务员编排层把菜端回你面前你再评价好不好吃生成最终回复。理解了这层你就能明白一个关键结论工具的真正逻辑永远跑在你本地或者你的服务器上模型只负责决定怎么用。这也是为什么Agent能把业务系统、私有数据安全地接入模型——敏感数据不需要全部塞进上下文只需要把查询能力暴露成工具。1.3 ReAct循环为什么智能体知道下一步该做什么你可能会问如果Agent要连续调多个工具才能完成任务比如先查用户ID再查订单再算总价这个流程是谁控制的答案是模型自己控制通过一种叫ReActReasoning Acting的模式。这个模式下模型的每一步输出都会包含思考和动作两部分。推理轨迹会显式地呈现我看到了什么、所以我决定做什么。LangChain的AgentExecutor或者LangGraph的状态循环本质上就是把这个思考-行动-观察的过程循环执行直到模型认为自己已经拿到足够信息生成最终答案。一个真实的多步调用轨迹大概是这样的模型思考用户想知道上海明天适合穿什么我需要先获取上海的天气。模型动作调用get_weather(city上海, date明天)观察工具返回明天中雨气温18-22度模型再思考下雨且温度不高建议带伞、穿薄外套。模型动作生成最终答案不再调用工具。ReAct的价值在于它让工具调用有了连贯的策略而不是靠代码硬编码一个个if else。你不需要提前把所有可能路径写死模型会根据临场情况动态决定路线。当然这也意味着你需要为路线失控留好保险——这一点后面实战部分会专门讲。2. 环境准备与第一版可运行Agent2.1 依赖清单与版本选择先说明版本问题。LangChain迭代非常快API变动也频繁。这篇文章的示例基于langchain 0.3.x和langchain-openai 0.3.x目前这是比较稳定的组合。如果你用的还是0.1或0.2代码可能会有小出入建议直接升级。我推荐的最小依赖清单如下。注意装包时统一用langchain-openai不要只装openai因为LangChain的模型封装在里面。pip install langchain pip install langchain-openai pip install langchain-community pip install langgraph pip install python-dotenv模型我建议至少用支持tool calling的型号。在OpenAI里这是gpt-4o级别起步gpt-3.5-turbo也能调工具但规划能力偏弱。如果用Anthropic对应选claude-3-5-sonnet或claude-3-5-haiku。开源模型里qwen2.5-72b-instruct和llama-3.1-70b对工具调用的支持也进入了可用范围但性能仍有差距。环境变量放在.env文件里用dotenv加载OPENAI_API_KEYsk-xxxx2.2 定义一个能被模型正确调用的工具docstring是灵魂LanagChain里定义一个工具最方便的是用tool装饰器。来看一个非常简单的加法和一个模拟搜索工具from langchain_core.tools import tool tool def add(a: float, b: float) - float: 计算两个数字的和。当用户需要求和、累加时使用。 Args: a: 第一个加数。 b: 第二个加数。 return a b tool def search_news(keyword: str) - str: 根据关键词搜索最新新闻资讯。当用户需要查询热点事件、行业动态、新闻头条时使用。 Args: keyword: 搜索关键词尽量简洁。 # 这里假装请求了一个新闻接口 return f关于{keyword}的最新新闻暂无结果请稍后重试。这里有个极其重要的点docstring就是工具的灵魂。模型不会看你的代码实现它只根据工具名、参数名和docstring判断什么时候该用这个工具。所以docstring要写清楚三件事这个工具是干什么的、什么时候用它、参数是什么意思。我见过太多人把docstring写成Add two numbers就完事模型遇到帮我把预算加上报销金额这种问题就会犹豫它知道是加法但不知道要不要用这个工具于是干脆不调。把docstring写详细、写场景化模型调用准确率会明显上涨这是投入产出比最高的一步。2.3 组装Agentcreate_tool_calling_agent AgentExecutor定义好工具接下来用create_tool_calling_agent创建一个会调用工具的Agent再用AgentExecutor跑起来。AgentExecutor是LangChain封装好的ReAct循环它会自动完成模型决定调用工具 - 执行工具 - 结果回传 - 模型再决定这个循环。import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain.agents import create_tool_calling_agent from langchain.agents import AgentExecutor load_dotenv() model ChatOpenAI( modelgpt-4o, temperature0, ) prompt ChatPromptTemplate.from_messages([ (system, 你是一个智能助手可以调用工具来完成任务。), (placeholder, {chat_history}), (human, {input}), (placeholder, {agent_scratchpad}), ]) tools [add, search_news] agent create_tool_calling_agent(model, tools, prompt) executor AgentExecutor(agentagent, toolstools, verboseTrue) result executor.invoke({input: 帮我算一下 25 加 37 等于多少顺便查一下最近AI行业的新闻。}) print(result[output])注意prompt里的{agent_scratchpad}占位符LangChain用它来记录Agent往期的思考与工具调用轨迹。这个不能省否则Agent会丢失我之前已经调过哪些工具的信息容易重复调用。verboseTrue在调试阶段很有用它能打印出完整思考轨迹——模型是怎么想的、决定调哪个工具、工具返回什么。我第一次跑通时盯着输出看才真正理解了ReAct循环是怎么回事。跑一下这段代码你会看到Agent依次调用add和search_news最后汇总成一句回答。到这里一个可调用工具的Agent就算真的跑起来了。3. 实战案例构建一个能搜资讯、能算账、能查天气的多工具Agent3.1 场景设计为什么选这三个工具光会跑加法没意思实战要面向具体场景。我选一个运营日报助手它服务的是一个小型运营团队每天要回答类似昨天的转化率是多少北京明天下雨吗帮我算一下这个月预算还剩多少的问题。我故意选了三种不同性质的工具组合查询类工具搜资讯模型需要根据关键词去外网抓数据考验的是参数抽取能力和结果解析能力。确定性计算工具计算器考验模型能不能把自然语言里的数字和运算关系正确映射成工具参数。结构化数据工具查天气考验模型处理实时数据的能力而且天气接口通常返回JSON正好演示结构化解析。只用一个工具是玩具两三个性质不同的工具组合才能真正暴露出编排层的价值。3.2 工具层实现真实API接入与异常处理先看代码。这是三个工具的实现我特意把异常处理写进去因为这一步在正式项目里必须做但网上教程很少讲。import requests from langchain_core.tools import tool tool def search_news(keyword: str) - str: 搜索最新新闻资讯返回标题和摘要。当用户需要查询热点、行业动态或特定事件时使用。 Args: keyword: 搜索关键词。 try: # 这里用一个真实的公开接口做示例实际项目换成你的业务搜索接口 url https://api.example-news.com/search resp requests.get(url, params{q: keyword}, timeout5) resp.raise_for_status() data resp.json() items data.get(items, [])[:3] if not items: return f关于「{keyword}」没有找到相关新闻。 formatted \n.join( f{i1}. {item[title]}{item[source]} for i, item in enumerate(items) ) return f关于「{keyword}」的新闻如下\n{formatted} except Exception as e: return f搜索新闻接口调用失败{str(e)}这个工具的关键点在于成功时返回格式化好的文本失败时返回明确的错误说明。注意工具返回的内容是给模型看的不是给用户看的。所以格式要尽量方便模型摘取要点不要返回一堆没用的HTML或者JSON。模型做最终回答时会基于你返回的文本重新组织语言。tool def compute(expression: str) - float: 计算一个数学表达式的值。支持加减乘除、括号、乘方。当用户需要做数学运算时使用。 Args: expression: 数学表达式例如 25 37 * 2 或 (100-80)/2。 import ast import operator # 安全起见用ast解析而不是eval防止恶意代码执行 allowed_operators { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Pow: operator.pow, ast.USub: operator.neg, } def eval_node(node): if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in allowed_operators: left eval_node(node.left) right eval_node(node.right) return allowed_operators[type(node.op)](left, right) if isinstance(node, ast.UnaryOp) and type(node.op) in allowed_operators: operand eval_node(node.operand) return allowed_operators[type(node.op)](operand) raise ValueError(不支持的计算表达式) try: return eval_node(ast.parse(expression, modeeval).body) except Exception as e: return f表达式计算失败{str(e)}很多人在这个工具上会偷懒直接eval()我强烈不建议。一个会被传进任意字符串的工具在安全敏感场景就是被告。用ast解析白名单运算符代码量多一点但靠谱得多。tool def get_weather(city: str, date: str 今天) - str: 查询指定城市当天的天气情况包括温度、天气现象和出行建议。 Args: city: 城市名如“北京”“上海”。 date: 日期支持“今天”“明天”或具体的“2025-03-15”默认今天。 try: url https://api.example-weather.com/v1/weather resp requests.get(url, params{city: city, date: date}, timeout5) resp.raise_for_status() data resp.json() return ( f{city} {date}天气{data[condition]} f温度{data[low]}~{data[high]}度 f风力{data[wind]}。建议{data[suggestion]} ) except Exception as e: return f天气查询失败{str(e)}这里有个设计细节date参数我设置了默认值今天。模型不是每次都主动传日期如果你把参数设为必填会导致模型频繁问你请问您要查哪天的天气。设置默认值能减少一次不必要的对话往返。3.3 指令层设计System Prompt的边界与写法很多人觉得Agent的System Prompt随便写写就行这是大错特错。Agent的提示词和管理对话的提示词不一样它更像工作手册而不是人设文案。我验证下来比较有效的写法是以下三层身份与目标一句话说清楚你是什么、服务谁。工具协作规则什么时候应该调工具、调完工具怎么组织答案。硬性边界不该做什么、不能编造什么。给我的运营日报助手写一个system_prompt 你是一个忙碌的运营团队的数字助理。 你的工作性质是能够利用提供的工具查询实时信息并在此基础上给出简洁、数据准确的回答。 工具使用规则 1. 当用户提及任何需要实时信息的问题例如新闻、天气、计算必须先调用对应工具不能凭借训练知识猜测。 2. 如果工具返回错误如实告诉用户当前查询失败不要编造结果。 3. 调用计算工具时将用户口语描述转换成数学表达式例如预算还剩多少应转换为减法表达式。 4. 回答保持简洁用3-5条要点组织数据类信息。 禁止事项 - 禁止编造工具返回的数据。 - 禁止回答与运营工作无关的闲聊问题。 这份提示词里第1条和第3条是真正的Agent增强——它们直接影响了模型对工具的使用频率和准确率。比如没有第3条模型可能遇到帮我把预算减去报销就不自觉地自己心算而不是调计算器。对大模型来说心算三位数加减无所谓但涉及百分比、复合运算时自己算错概率很高让它调用工具才是正确路径。3.4 运行效果与思考轨迹解读把工具和提示词组合起来跑一次完整对话tools [search_news, compute, get_weather] agent create_tool_calling_agent(model, tools, prompt) executor AgentExecutor(agentagent, toolstools, verboseTrue) result executor.invoke({ input: 查一下北京今天的天气。如果下午的预算还剩1800元晚上团建人均花费不能超过多少按10个人算 })verboseTrue模式下你会看到这样的中间输出 Entering new AgentExecutor chain... Invoking: get_weather with {city: 北京, date: 今天} 北京 今天天气多云转晴温度18~26度风力3级。建议适合户外活动早晚加一件薄外套。 Invoking: compute with {expression: 1800 / 10} 1800 / 10 180整个链路清晰展示了模型先查天气、再做除法、最后汇总答案的过程。很多人第一次看到完整轨迹会很兴奋因为它确实体现了Agent在推理-行动之间来回切换的本质。如果你希望用户只看到最终回答不看到中间工具调用细节把verboseFalse即可。但调试阶段务必开着排查问题时这些轨迹是定位模型选错工具 / 参数传错的第一手证据。4. 从Demo到可靠调参、记忆与人类介入4.1 控制循环recursion_limit、max_iterations与死循环防御Demo跑通之后你需要立刻考虑一件事如果Agent不停调错工具、反复循环怎么办ReAct循环虽然强大但它没有天然的停止条件。模型只要认为自己还没拿到答案就会继续调工具。在复杂任务里真有可能出现调了七八次还在绕圈子的情况。所以必须设置硬性上限。在AgentExecutor层面主要有两个参数executor AgentExecutor( agentagent, toolstools, verboseTrue, max_iterations5, # 最多允许5轮工具调用 early_stopping_methodforce, )max_iterations限制的是思考-行动循环的最大轮数。5轮不够可以根据任务复杂度放大但不能没有上限。early_stopping_methodforce表示达到上限后强制停止由模型基于已有信息做最终回答。还有一个底层参数recursion_limit它控制LangChain内部递归调用的最大次数。如果你发现Agent在max_iterations没触顶的情况下报RecursionError或提示递归超限可以调大这个值executor.recursion_limit 50实际项目中我建议把max_iterations控制在3~8之间。太短会过早截断复杂任务太长会放大成本和延迟。这个数字没有标准答案需要根据你业务的工具链路复杂度去试。4.2 给Agent装上记忆MemorySaver与上下文窗口的取舍上一节的Agent是无状态的每次对话都是独立请求它不记得你5分钟前说的北京指哪个城市。对真正的Agent应用来说记忆几乎是刚需。LangChain提供了一套历史消息管理机制。最简单的方式是给Agent挂载持久化的消息历史from langgraph.checkpoint.memory import MemorySaver from langchain_community.chat_message_histories import ChatMessageHistory from langchain_core.runnables.history import RunnableWithMessageHistory不过在Agent场景中我更推荐使用LangGraph的MemorySaver作为checkpoint机制因为它在保存对话历史的同时还能保存Agent的完整运行状态。配置方式如下from langgraph.checkpoint.memory import MemorySaver memory MemorySaver() agent_executor create_agent_executor( # 内部使用LangGraph的StateGraph modelmodel, toolstools, checkpointermemory, ) # 通过thread_id区分不同会话 result agent_executor.invoke( {messages: [(user, 帮我查一下北京的天气)]}, config{configurable: {thread_id: session-001}}, )这里我提前用了LangGraph的概念。简单理解就是给Agent一个记忆抽屉同一个thread_id的对话可以互相继承不同thread_id彼此隔离。没有这个Agent每次调用都像失忆患者用户根本没法进行多轮连续任务。但记忆不是越多越好。上下文窗口是有限的塞太多历史消息会让模型注意力分散还增加token成本。我的经验是对于工具调用场景真正重要的往往只是最近3~5轮对话更早的信息提取成摘要缓存即可。LangChain的SummarizationMemory可以解决长会话压缩但会引入一层额外延迟小项目初期不必上手动截断历史消息就够用。4.3 Human-in-the-Loop关键操作前的确认机制再往下走你迟早会遇到一个场景Agent要执行一个不可逆的操作比如发送邮件扣减库存删除文件。这时候你不可能让模型自己直接调工具——万一它判断错了呢Human-in-the-loop人类介入就是为了解决这个问题。LangGraph原生支持这个机制它可以让Agent在图执行的某个节点暂停等人工确认后再继续。from langgraph.graph import StateGraph, START, END from langgraph.graph import MessagesState from langgraph.prebuilt import ToolNode, tools_condition graph StateGraph(MessagesState) def call_model(state): response model_with_tools.invoke(state[messages]) return {messages: [response]} graph.add_node(model, call_model) graph.add_node(tools, ToolNode(tools)) graph.add_edge(START, model) graph.add_conditional_edges( model, tools_condition, {tools: tools, END: END}, ) # 执行tools节点前暂停等待人工确认 graph.add_edge(tools, model) compiled_graph graph.compile( checkpointermemory, interrupt_before[tools], # 关键在执行工具前停下来 )在这个图里每次Agent准备调用工具前程序都会暂停。你需要人工检查这次调用是否合理async for event in compiled_graph.astream(input, configconfig): if __interrupt__ in event: interrupted_state event[__interrupt__] tool_call interrupted_state[0].value # 要执行的工具调用 print(需要人工确认的工具调用, tool_call) # 这里做人工审核同意则继续拒绝则修改消息后重放这种模式在金融、客服、内容审核等场景几乎是必须的。比如一个银行客服Agent它能看到用户账户余额但自动转账这种动作如果没人确认出了事责任没法界定。以我在类似项目里的经验加这一层并不难但是救命的东西——它能防住模型在最意想不到的时刻做出离谱决策。模型判断失误不可怕可怕的是没有兜底机制让错误直接落地。5. 选型指南LangChain Agent还是LangGraph真正的边界在这里5.1 两者的本质差异链式封装与图状态机这是一个反复被问到的问题也是Link到Agent开发绕不开的岔路口。LangChain AgentAgentExecutor本质是对ReAct循环的高度封装。你提供模型、工具、提示词它把所有控制流都藏起来默认行为就是循环直到有答案。好处是快速、简洁适合大多数标准工具调用场景。LangGraph本质是一个图状态机。节点是函数边是条件路由状态由checkpointer管理。好处是完全可控你可以精确到哪个节点前暂停哪个条件下的工具调用需要人工审核失败重试走哪条路径。坏处是需要自己写更多结构代码上手门槛高。用大白话说AgentExecutor像自动挡汽车踩油门就走LangGraph像手动挡每个齿轮咬合你都能看见、都能干预。对比维度LangChain Agent (AgentExecutor)LangGraph控制粒度整体循环内部逻辑封装节点级控制每一步都可干预人机协同需要额外处理原生支持interrupt机制状态持久化需要配合消息历史组件内置checkpointer状态可持久化复杂分支依赖模型自行规划可硬编码分支也能让模型规划上手成本低中高适用场景标准ReAct工具调用、MVP生产级复杂流程、多类人员参与、严格审计要求5.2 用LangGraph改写多工具AgentStateGraph与ToolNode如果你的场景确实需要精细控制我用LangGraph把前面那个运营助手改写一遍。先看核心结构from typing import Annotated, TypedDict from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode from langchain_openai import ChatOpenAI class AgentState(TypedDict): messages: Annotated[list, add_messages] tools [search_news, compute, get_weather] model_with_tools ChatOpenAI(modelgpt-4o, temperature0).bind_tools(tools) def call_model(state: AgentState): response model_with_tools.invoke(state[messages]) return {messages: [response]} graph StateGraph(AgentState) graph.add_node(model, call_model) graph.add_node(tools, ToolNode(tools)) graph.add_edge(START, model) graph.add_conditional_edges( model, tools_condition, {tools: tools, END: END}, ) graph.add_edge(tools, model) app graph.compile(checkpointerMemorySaver())这段代码做的事情和之前的AgentExecutor差不多但你现在可以在这个结构上做更多文章比如在model节点和tools节点之间插入human_review节点实现人工审核。用 Python 的 if 条件控制路由比如当用户提到删除时必须经过确认节点。给每个节点配置独立的超时和重试策略。从我的时间投入来看只要涉及生产环境、多人协作或不可逆操作直接上LangGraph反而省心因为AgentExecutor后期改造到LangGraph的成本不低工具链一复杂很多控制逻辑很难在不重构的情况下塞进去。反过来如果你只是做原型验证、内部小工具AgentExecutor完全够用没必要求重。5.3 我的选型建议和适用场景判断选型这件事我总结了几个比较朴素的判断标准不一定对但能帮你快速做决定如果你的Agent只会调1~3个只读工具返回结果后就结束用AgentExecutor别折腾。如果涉及多种业务动作尤其是查询-确认-执行-通知这种多重形态的链路直接用LangGraph设计整个状态机。如果未来必须做多Agent协作或者Agent之间互相通信跳过AgentExecutor直接学LangGraph。如果团队里有人要审计Agent每一步决策例如金融、医疗合规LangGraph的checkpoint机制是无可替代的。顺便说一句今年很多工程团队把LangChain和LangGraph同时用业务原型用LangChain生产系统用LangGraph。反正两者底层互相兼容工具定义和模型接口可以复用迁移成本不算太高。先跑通再重构是大多数产品的正常路径。6. 生产环境里那些文档不会告诉你的坑6.1 工具返回的信息质量决定Agent智商这是我踩过最深的一个坑分享出来希望大家少走弯路。你的Agent智能程度很大程度上不是由模型决定的而是由工具返回信息的结构决定的。举例来说如果你的搜索工具返回一整页乱糟糟的爬虫文本模型要被淹没在噪声里很难提取出用户关心的点。如果直接在工具里做粗加工把标题关键信息时间整理成一两行模型的表现会立刻上一个台阶。我常用的做法是工具内部做结构化清洗返回给模型的永远是最精简、最有决策价值的文本。天气工具只返回温度区间和天气现象搜索工具只返回标题和来源数据库工具只返回查询结果的行数和关键字段。模型不需要看链路里所有噪声它只需要可执行的结论。另外工具返回错误信息时千万不要只抛一个空字符串或者None。如果模型收到一个空结果它不知道是查不到还是接口故障就很容易编造一个查无结果。工具要么返回结构化错误描述要么返回一句天气查询失败服务不可用这既避免了模型胡编也让最终用户能看到真实原因。6.2 并行调用、流式输出与延时心智LangChain工具调用在实际运行中有一个容易被忽略的特性同一个模型输出里可能包含多个tool_callsLangChain会并行执行它们。模型一次返回调用get_weather和调用search_news框架会同时发起两个请求。这对用户是好事减少了等待时间但你要注意工具实现里的共享资源冲突。比如两个工具同时写同一个日志文件、同时更新同一个计数器就可能出问题。我在一个项目里因为两个工具同时更新一个全局状态导致数据互相覆盖排查了很久。工具设计时就要考虑并发安全实在不行给资源访问加锁。另外如果工具本身耗时太长比如超过30秒整个Agent的响应时间会被拖得很厉害。用户感知到的不是Agent聪明而是好慢。遇到大型耗时任务建议拆分成多个小工具或者在工具内部用缓存机制。同一个关键词的搜索5分钟内直接走缓存能在不损失信息时效性的前提下大幅降低延迟。6.3 降级策略和监控Agent出错时怎么办没有人能保证大模型每次都100%正确调用工具。生产环境必须假设模型可能选错工具、传错参数、或者干脆陷入死循环。我给生产级Agent配置了这样几层保护第一层工具层兜底。每个工具内部捕获所有可预知异常返回结构化的错误文本而不是抛异常。第二层循环控制。max_iterations设上限达到上限后强制生成最终答案即使答案可能不完整。第三层人工兜底。对于高影响动作引入human-in-the-loop自然语言模型再聪明核心决策还是由人来做。第四层日志监控。把每次工具调用的入参、出参、耗时、模型轨迹全部记录下来。出了问题回放轨迹定位原因比看聊天记录高效得多。我见过很多团队在调试阶段觉得Agent很神奇上线后又因为一次离谱的工具调用被吓到下线。我想说的是Agent不是不需要监控的魔法黑盒它是有状态、有幻觉得分、有工具执行误差的完整系统。用什么框架不重要真正重要的是你把它当生产系统对待给它加上控制、日志、人工兜底。做好了这些LangChain Agent也好LangGraph也好才能真正从Demo走向业务价值。