深入学LangChain 官方文档(五)Agent 核心循环首讲

深入学 LangChain 官方文档(五)Agent 核心循环首讲

本篇对应的官方文档

本篇讲解范围

本篇只讲 Agent 最核心的一件事:一次用户请求怎样在模型与工具之间往返,直到形成最终答案或被运行边界截停。工具 schema、ToolRuntime 和返回值放在第 06 篇;Runtime Context 与 State 的数据边界放在第 07 篇;Memory 与 Middleware 还会在后续文章单独展开。

前四篇我们已经把 LangChain 的几个基础对象分开了:Model 负责推理,Message 承载上下文,Structured Output 把结果变成可校验对象。下面我们真正进入Agent模块。到了 Agent,问题不再是"怎样调用一次模型",而是"当一次推理无法完成任务时,谁负责继续往下走"。

设想一个售后客服请求:用户说耳机右侧没有声音,想知道还能不能换货。模型可以解释一般规则,却不知道这副耳机什么时候购买、订单是否属于当前用户、是否已经过了售后期。要给出可靠答复,系统必须先查订单,再查售后规则,必要时补问序列号,最后才能组织回复。下一步依赖上一步结果,路径也无法在请求开始前完全写死,这正是 Agent 发挥作用的位置。

这条交替链是全文的路线:用户消息先进入模型,模型可以直接回答,也可以提出工具调用;工具执行后把事实写回消息序列,模型依据新增事实继续判断。循环的价值不在"多调用几次模型",而在每一轮都能获得新的外部信息或完成一个受控动作。

本篇会先判断什么任务真的需要 Agent,再拆开 Model 与 harness 的职责,随后沿一次 invoke 追踪 messages、tool_callsToolMessage 怎样推进,最后讨论停止条件、权限和排障顺序。读完以后,你应该能把"Agent 很聪明"改写成一条可观察、可限制、可定位的执行链。

1. 先判断任务是否真的需要 Agent

Agent 不是所有 LLM 应用的默认形态。很多需求只需要一次模型调用,例如改写一段文案、总结一页文本、按给定 schema 抽取字段。输入已经包含完成任务所需的信息,输出也不需要访问外部系统,那么增加工具循环只会引入更多延迟、成本和失败点。

另一类需求虽然包含多个步骤,却仍不一定需要 Agent。比如"读取文件、校验列名、写入数据库、生成报告"可以预先确定严格顺序,任何一步失败都应该停止。这种任务更适合普通代码或固定 workflow:分支由程序判断,状态转换明确,执行路径容易测试。让模型临时决定是否跳过校验,反而会削弱可靠性。

Agent 更适合第三类问题:目标明确,但下一步需要根据运行时结果决定。售后客服可能先查订单;如果订单不存在,需要补问;如果仍在换货期,需要检查库存;如果涉及高价值商品,还要转人工审批。这里的分支来自语义判断和外部事实,组合数量又不适合全部写成硬编码流程。

三种形态的关键区别是"下一步由谁决定"。固定代码把路径写在程序里;workflow 允许预先定义的分支;Agent 把部分选择交给模型,但可用工具和执行边界仍由应用控制。是否使用 Agent,应该由路径的不确定性决定,而不是由任务听起来是否"智能"决定。

可以用三个问题做快速筛选:完成任务是否缺少运行时事实?获得事实后是否可能改变下一步?这些变化是否需要自然语言语义判断?三个问题都成立,Agent 才有明显价值。如果只是为了让代码看起来更像 AI 项目,保持简单通常更稳。

Agent 解决的是动态决策与受控行动的组合,不是替代普通程序。 这条边界决定了后面怎样理解核心循环:循环不是无条件重复,而是在新信息出现后重新选择下一步。

还可以从"错误是否允许被模型解释"判断边界。税率计算、库存扣减和权限校验的结果必须由确定性程序给出,模型只能决定何时请求、怎样向用户说明;如果把计算规则本身交给模型临场发挥,Agent 会把本来可测试的逻辑变成概率输出。适合交给模型的是语义路由和信息组织,不适合交给模型的是必须逐次一致的业务约束。

动态也不等于无限自由。售后 Agent 可以在"查订单、查规则、补问信息、转人工"之间选择,但不应拥有任意 SQL、任意 HTTP 请求或任意脚本执行。工具集合越接近最小业务动作,模型的选择空间越清楚,应用也越容易逐项授权、记录和回放。

2. Agent = Model + Harness 到底在分什么

LangChain 官方文档用一句很短的话概括 Agent:Agent = Model + Harness。Model 是推理组件,harness 是围绕循环的运行系统。两者一起工作,但职责不能混在一起。

Model 接收当前上下文,生成下一步意图。它可能输出自然语言,也可能输出结构化的 tool_calls。它擅长根据用户目标、系统指令和已有事实做选择,却不会因为生成了一个函数名,就自动获得数据库连接、文件权限或支付能力。

Harness 负责把意图变成一次受控运行。它把 system prompt、messages、可用 tools 和 middleware 组织起来;发现 tool_calls 后调用真实函数;把结果转换成 ToolMessage;再次调用模型;还可以增加重试、调用次数限制、人工审批、日志和上下文管理。

图中的分界线很重要:模型负责"建议下一步",harness 负责"这一步能否以及怎样执行"。工具权限、参数校验、超时、审计和停止条件都不能只靠 prompt 约束;它们必须落实在模型之外的运行层。

这也解释了一个常见现象:同一个模型放进不同 Agent,行为差异可能很大。差异未必来自模型能力,而可能来自工具描述、system prompt、上下文选择、middleware 或错误处理。反过来,一个工具失败,也不能简单归因于"模型不够聪明",它可能根本没有被正确注册,或者返回结果没有回到对应的调用。

把 Model 和 harness 分开后,Agent 就不再是一个黑盒名称,而是两组可以分别检查的对象。推理错误看模型输入与输出;执行错误看工具和权限;循环错误看 messages 与状态推进;运行边界错误看 harness 配置。

这层分工还决定了测试策略。Model 的选择可以用固定输入检查工具命中率、参数完整性和最终回答质量;Tool 可以脱离模型做普通单元测试,验证异常、超时和权限;Harness 则用一条完整 trace 检查节点顺序、状态变化和出口。把三类测试混成一次端到端请求,失败时很难知道究竟是哪一层变了。

评估也应保留同样的边界。最终答案正确,不代表过程安全:模型可能调用了不必要的高成本工具,或者先访问了无权读取的数据。反过来,一次工具调用失败也不必直接判定整体任务失败,harness 可能按照明确策略降级到补问或人工处理。结果质量和运行质量需要分别记录。

3. create_agent 装配的是一套运行关系

LangChain 使用 create_agent 建立 Agent。最基础的三个输入是 modeltoolssystem_prompt。它们不是"配置项列表",而是分别回答三个运行问题:谁做推理、允许做什么、按照什么角色和原则做判断。

model 可以是模型标识或已经初始化的模型实例。本系列统一使用 qwen3.7-plus,但在 Agent 语境中,真正要关注的不是供应商连接字段,而是模型每一轮都接收当前 messages 和可用工具定义,并返回下一步消息。

tools 是应用愿意暴露给模型选择的能力集合。工具越多,不代表 Agent 越强。名称相近、描述重叠或权限过大的工具会让选择变难,也会扩大风险面。第 06 篇会完整讲 tool schema;本篇只把它看成循环中的"可选动作集合"。

system_prompt 用于设定角色、目标和行为原则。例如客服 Agent 可以被要求先核实订单,再回答售后结论;缺少事实时应明确补问;不得编造订单状态。这些指令会影响模型决策,但不会自动形成代码权限。即使 prompt 写了"不要执行未经确认的退款",高风险工具仍应在运行层校验身份并要求审批。

装配关系形成后,create_agent 返回的是一个可调用图,而不是一段更长的 prompt。模型节点负责推理,工具节点负责执行;state 在节点之间传递;middleware 可以在调用前后介入。底层图结构让循环具备可观察和可扩展的运行位置。

动态指令也不应该通过不断修改全局字符串实现。官方文档把运行时动态 prompt 放到 middleware:它可以根据用户角色、会话阶段或 state 构造当前轮指令。固定原则适合直接写进 system_prompt,随运行变化的信息则应进入 Runtime Context、State 或 middleware。把所有内容拼进一个巨型 prompt,会在第 08 篇的 Context Engineering 中看到更多问题。

create_agent 还可以接收 response_format、checkpointer、context schema 和 middleware 等配置。它们分别改变结果合同、会话恢复、运行依赖和生命周期行为,但不应在首讲里被压成一张"参数大全"。理解扩展项的办法仍然是问它修改了循环的哪个位置:进入模型前、工具执行前后、state 写入时,还是运行结束后。

例如第 04 篇的 response_format 影响最终结果怎样进入 structured_response,却不会取消工具循环;checkpointer 让同一 thread_id 的历史能够被恢复,却不负责长期用户画像;context 把本次运行依赖交给工具和 middleware,却不必让模型看到。把配置映射到运行位置,比背参数名更能避免误用。

4. 一次 invoke 如何变成多轮循环

调用 Agent 时,输入不是裸字符串,而是对 state 的一次更新。所有 LangChain Agent state 都包含 messages 序列,因此最小输入通常是:

python 复制代码
{
    "messages": [
        {
            "role": "user",
            "content": "订单 A1024 的耳机右侧没有声音,还能换货吗?",
        }
    ]
}

这条 user message 加入 state 后进入模型节点。模型会看到 system prompt、当前消息历史和工具 schema。如果现有信息足够,它可以直接生成最终 AIMessage;如果缺少订单事实,则会生成带 tool_callsAIMessage,其中包含工具名称、参数和调用标识。

Harness 读取 tool_calls,找到对应工具并执行。订单查询工具返回购买日期、商品状态和售后记录后,结果会包装成 ToolMessage,并通过 tool_call_id 与原请求配对。新的消息序列再次进入模型,模型这次不再只依赖用户描述,而是拥有了订单事实。

循环推进的载体是 state,而 messages 是其中最基础的字段。每一轮不会抹掉上一轮的意图和结果:用户请求、模型的工具调用、工具结果、模型的后续判断按顺序保留。模型因此能够知道"刚才查了什么、返回了什么、现在还缺什么"。

这条消息链也带来成本。工具返回整份数据库记录、日志或 HTML,会迅速占满上下文窗口;错误结果如果没有清楚语义,模型可能不断重复同一次调用。因此,工具输出既要包含完成下一步所需的事实,又要控制大小和敏感信息。循环能否稳定,常常取决于结果怎样进入 messages,而不仅是工具本身能不能运行。

还要区分"一次 run 内的多轮循环"和"用户发起的下一轮会话"。前者发生在同一个 invoke 中,模型与工具可以往返多次;后者是用户过一会儿又发送新消息。若希望第二次调用恢复前文,需要配置 checkpointer 并复用 thread_id。没有持久化配置,不能因为第一次 result 里有完整 messages,就推断下一次调用会自动记住。

这种区分会直接影响故障复现。一次 run 内重复调用工具,要检查当前 trace 的节点和消息;跨 turn 丢失上下文,要检查 checkpointer 与 thread_id。两者表面上都像"Agent 忘了",实际位于不同边界。

模型生成工具调用,应用执行工具,结果再作为消息回到模型。 这三个动作缺一不可。只打印 tool_calls 不执行,任务不会前进;执行工具却不返回 ToolMessage,模型不知道发生了什么;返回结果没有正确配对,并发调用时就可能把库存结果当成订单结果。

这一次往返是 Agent loop 的最小可执行单元。AIMessage 表达模型提出的动作,工具节点把动作落到真实系统,ToolMessage 把可消费的结果送回同一条上下文链。第 06 篇会继续拆开 schema、隐藏参数和不同返回值,本篇先把它们放回循环顺序中。

5. Agent 怎样知道应该停下来

最理想的出口是模型生成最终答案,并且不再包含 tool_calls。在客服案例中,订单事实和售后规则都已获取,模型可以明确说明是否在换货期、还需提供什么材料。Harness 识别到没有新的工具请求,本次运行结束。

但生产系统不能只等待模型"自觉结束"。模型可能因为工具描述含糊而在两个工具间来回选择,也可能在外部服务持续失败时重复尝试。调用次数限制、超时、重试预算和总成本预算需要由运行层配置;达到边界后,系统应返回可识别的失败或转交状态。

第三类出口是业务错误。订单不存在、用户无权访问、售后规则服务不可用,都不是"继续推理就能自动解决"的问题。工具应返回清楚、可分类的结果,模型才能补问、降级或向用户说明。把堆栈信息直接塞进上下文,既可能泄露内部实现,也无法提供可靠恢复策略。

第四类出口是人工介入。退款、删除数据、发送外部消息等高影响动作,即使模型参数生成正确,也可能需要人确认。人工审批不是一句 system prompt,而是工具执行前的程序闸门。审批拒绝后,结果同样应回到运行链,让 Agent 能够解释当前状态而不是假装动作成功。

四个出口对应不同责任:最终回答来自模型判断;调用限制由 harness 执行;业务错误由工具与应用定义;人工转交由权限和审批流程决定。把它们都写成"停止条件"没有错,但排障时必须知道具体是谁让循环结束。

停止以后还要保留可诊断信息。至少应能回答:最后一次模型输入包含什么,模型请求了哪个工具,工具参数是否通过校验,外部调用是否成功,结果是否进入 state,以及最终出口属于哪一类。没有这些证据,Agent 一旦在中间停住,就只能靠猜。

调用限制也应按风险拆开。只设置一个很大的迭代上限,无法阻止一次昂贵工具调用或一次不可逆写操作;只限制工具次数,又可能让模型在没有工具的情况下反复生成。工程上通常同时考虑总运行时间、模型调用次数、特定工具调用次数和单次工具超时,并为高影响动作设置独立审批。

当边界触发时,返回值要能区分"任务没有答案"和"系统选择不再继续"。前者可能需要补充业务数据,后者可能是预算耗尽、权限拒绝或等待人工。把所有出口都改写成一句"抱歉,处理失败",会丢掉前端提示、重试和运维诊断所需的信息。

6. 用 qwen3.7-plus 跑通最小订单查询循环

下面的代码只实现一项只读能力:根据订单号查询模拟订单。这样可以看清核心循环,又不会把退款写入、权限系统和持久化混进首讲。

python 复制代码
import os

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


ORDERS = {
    "A1024": {
        "product": "无线耳机",
        "purchased_at": "2026-07-02",
        "after_sales_days": 30,
        "status": "delivered",
    }
}


@tool
def lookup_order(order_id: str) -> dict:
    """按订单号查询商品、购买日期、售后期限和订单状态。"""
    return ORDERS.get(order_id, {"error": "order_not_found"})


model = ChatOpenAI(
    model="qwen3.7-plus",
    api_key=os.environ["DASHSCOPE_API_KEY"],
    base_url=os.environ["DASHSCOPE_BASE_URL"],
)

agent = create_agent(
    model=model,
    tools=[lookup_order],
    system_prompt=(
        "你是售后客服助手。涉及具体订单时必须先查询订单;"
        "没有订单事实就补问信息,不得猜测;只解释规则,不执行退款或换货。"
    ),
)

result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "订单 A1024 的耳机右侧没有声音,还能换货吗?",
            }
        ]
    }
)

print(result["messages"][-1].content)

执行路径可以按四个对象追踪。第一,user message 进入 state。第二,模型发现问题依赖具体订单,生成 lookup_order 调用。第三,harness 执行 Python 函数,把字典结果转成工具消息。第四,模型结合购买日期和售后期限生成最终回复,不再请求工具,本次循环结束。

这段代码没有证明"用户一定能换货"。订单数据只提供了购买日期和期限,真实业务还需要当前日期、故障类型、商品状态以及公司规则。Agent 可以编排查询过程,不能替业务系统发明结论。要执行换货,还必须有独立的权限校验、幂等键、审批或库存检查。

如果输出不符合预期,不要先反复修改 system prompt。先打印 messages:模型是否产生 tool_calls;工具名和参数是否正确;是否出现对应的 ToolMessage;结果中有没有模型需要的字段;最后一条 AIMessage 是最终答复还是又一次调用。消息链能把"没调用""调用失败""结果不可用""模型误判"分成不同问题。

7. 按运行阶段定位 Agent 故障

Agent 报错时,最容易做的事情是把整个链统称为"模型问题"。更有效的办法是沿执行顺序检查四层。

第一层是 model decision。模型是否看到了正确 system prompt、必要 messages 和合适的工具描述?如果它选择了错误工具或参数,先检查上下文和 schema,而不是检查数据库连接。

第二层是 tool execution。函数是否真的被调用,参数是否通过验证,外部服务是否超时,权限是否允许?这里的失败属于程序和基础设施,即使换更强模型也不会自动恢复失效凭证。

第三层是 state transition。tool_callsToolMessage 是否配对,工具结果是否进入 messages,自定义字段是否按 reducer 更新?运行已经成功但模型仍重复调用,经常是结果没有进入下一轮可见上下文。

第四层是 harness boundary。调用限制、middleware、checkpointer、人工审批或异常包装是否改变了路径?同一模型和工具在两个环境行为不同,应比较 harness 配置,而不是只比较 prompt。

这条排障顺序与核心循环使用同一组对象。模型层解释"为什么选择",工具层解释"是否执行",state 层解释"结果是否推进",harness 层解释"运行为何被改变或停止"。按层保留日志,才能把一次看似随机的失败还原成确定步骤。

生产环境还应补充两类指标:循环长度和工具分布。循环突然变长,可能是工具结果不完整、上下文膨胀或外部服务反复失败;某个工具调用率异常升高,可能是描述重叠或模型策略变化。只统计最终成功率,会掩盖成本和风险正在累积。

Trace 中还值得保留每轮上下文的摘要,而不是无条件记录全部敏感内容。订单号、用户身份和工具返回可能受访问控制约束,调试记录同样要脱敏并设置保留期限。可观察性不是把所有数据复制到日志系统,而是在必要字段、权限和审计之间取得平衡。

回归测试可以从真实失败 trace 提炼最小用例:某条描述导致工具误选,就固定用户输入并检查 tool name;某次结果过长导致循环漂移,就固定精简后的返回合同;某个权限拒绝没有正确终止,就验证对应 ToolMessage 和出口。这样 Agent 的"经验"才会沉淀成可重复验证的工程资产。

8. 回到开篇:Agent 的核心不是"自动",而是"可控地继续"

现在可以重新回答开篇的售后问题。单次模型调用只有用户描述,无法知道订单事实;Agent 允许模型提出查询请求,由 harness 执行工具,把结果写回 messages,再让模型基于新事实继续判断。每一轮都有明确输入、动作和结果,直到生成最终答复或触发运行边界。

整条链可以压缩为五步:用户目标进入 state;模型选择回答或工具;harness 执行工具;ToolMessage 带着结果回流;模型依据更新后的上下文继续或结束。create_agent 把模型、工具和指令装配起来,middleware、权限和调用限制则让这条循环能够进入真实工程。

是否采用 Agent,仍要回到任务形态。输入已经充分、只需一次推理,就直接调用 Model;步骤固定、分支可枚举,就写 workflow;只有下一步依赖运行结果并需要语义判断时,才让 Agent 在受控工具集合中选择。

最后保留一个最实用的记法:Model 决定下一步意图,Tool 接触外部世界,State 保存运行进展,Harness 负责让循环安全地继续或停止。 在下一篇 06 篇,我们会把其中最容易被误解的一段拆开:模型生成的 Tool Calling 到底是什么,Python 函数怎样变成工具,结果又怎样可靠地回到对应调用。

相关推荐
minhuan10 小时前
LangChain+LangGraph架构拆解:基于复杂AI任务编排与状态管理的大模型应用开发21.4
人工智能·langchain·大模型应用·langgraph·复杂ai任务编排
Lei活在当下1 天前
从 Token 到 RAG:我这一周搭起的大模型基础认知地图
人工智能·langchain·openai
sugar__salt1 天前
Document 切割:RAG 知识库数据预处理实战
javascript·langchain·embedding·js·rag·cheerio
小二·1 天前
多模态RAG实战:用LangChain+Milvus打造支持图文音视频的企业级知识库
langchain·音视频·milvus
BreezeJiang1 天前
AI 应用的稳定性,藏在模型调用前后的数据边界里
langchain
吃饱了得干活1 天前
LangChain 提示词模板:从拼接字符串到组合流水线
langchain·llm
夜影风1 天前
智能体开发的“脚手架“:主流框架(LangChain、AutoGen等)选型指南
人工智能·langchain·ai agent
海上彼尚2 天前
Nodejs也能写Agent - 16.LangGraph篇 - 条件分支与循环
前端·后端·langchain·node.js
Darling噜啦啦2 天前
从 URL 到 Chunk:网页爬取与语义切割实战,构建 RAG 知识库的第一步
langchain