智能体环路工程实战

Key Takeaways

  • Agent 工程的真正难点不是「写一个 prompt 让 Agent 跑通 demo」,而是「让 Agent 在真实工作流里持续跑且越跑越好」;环路工程(loop engineering)是把这一目标工程化的关键框架
  • LangChain 团队把 Agent 环路抽象成四类:核心 Agent 环路(request → model + tools → result)、Goal/Verification 环路(grader agent 评估 rubric 决定是否完成)、Event-driven 环路(把 Agent 嵌入 Slack / GitHub / Calendar / Email 等真实系统)、Self-improvement / Hill Climbing 环路(LangSmith Engine 从 trace 反推改进 prompt / tool / memory)
  • 四类环路可以俄罗斯套娃式任意嵌套 --- 你不必全套,可以在 Event-driven 里只套 Verification + Core;这种嵌套能力来自 Dcode 与 Deep Agents 的开源 middleware 架构
  • /goal/rubric 是 Dcode / Deep Agents 提供的关键语法糖:/goal 在执行前先回写一份 rubric 给用户审,粒度比 Codex / Claude Code 的目标设定更细;两者都作为 middleware 可插拔到任意 LangChain agent
  • Self-improvement 环路的工程价值在于「Agent 是非确定性的,真正的真相在 traces 里」;LangSmith Engine 这类 meta-agent 把 trace 作为反思信号,自动改写 prompt / tool description / skill / memory 并 port 回源码,让外层环路使内层环路越来越有效 ...(已截断至 300 字以内)

为什么 Agent 必须被「环路化」,而不是只写一个 prompt

整套课程把 Agent 工程的核心矛盾归结为一句话:单次 prompt 不可能让 Agent 可靠地完成所有任务。这听起来像常识,但实际工程现场大量失败的 Agent 项目恰恰败在这一步。

一个 Agent 一旦跑通 demo,团队往往以为问题已经被解决,接下来只是把它接到 Slack、接到 GitHub、接到定时任务。但真实情况是,同一个 Agent 用同一组 prompt 跑十次,十次结果可能各不相同,有些跑通了,有些在第三步工具调用环节掉了链,有些甚至在最基础的格式化输出阶段就出错。

这种不稳定性并不是 prompt 写得不够细的问题,而是大语言模型本身在 temperature(采样温度)、采样路径、上下文注意力分配上天然存在的非确定性。要把这种不可靠的部件包装成对业务可用的能力,工程上的答案从来不是"再写一段更长的 prompt",而是在 Agent 外面套上可观测、可验证、可触发、可自省的环路结构。

这正是 LangChain 团队在 Loop Engineering 系列里反复强调的中心思想,也是 2026 年 AI 工程领域"成熟度"与"玩具 demo"之间最关键的分水岭。

下面这张对比矩阵把两种 Agent 成熟度在工程维度上的真实差距一次性铺开:

工程维度 单 prompt + 单次 LLM 调用 vs 四环路俄罗斯套娃 Agent
成功率 取决于 query 难度,平均一次跑通率较低、方差大 vs 多层验证环路叠加,失败可被捕获并重试
可观测性 只有最终输出,中间 reasoning 不可见 vs 完整 trace,可重放到 LangSmith 查看每一步
可调试性 失败后只能改 prompt 重试 vs grader agent 可定位失败的具体 rubric 项
复用性 prompt 与场景强耦合,跨任务不可移植 vs /goal 与 /rubric 作为 middleware 可插拔复用
部署形态 通常作为一次性脚本或聊天界面 vs 嵌入 Slack/GitHub/Calendar 等真实工作流
改进机制 人工看 demo 凭感觉调 prompt vs hill climbing loop 自动遍历 trace 改写 prompt
成本可控性 不可控,长 prompt 反复调用浪费 token vs 验证失败可提前终止,避免无效重试
团队协作 个人玩具,难以交接 vs 工具、rubric、goal 全部版本化,可走 PR review

这张表并不是抽象概念清单,它对应的是团队在生产环境里能直接观察到的痛点。当我们把"单 prompt"作为基线、把"环路化 Agent"作为目标态时,真正变化的不是模型本身,而是 Agent 与外部系统交互的协议层。

为什么必须"环路化":两个第一手观察

观察 同一 Agent 跑同一组 prompt 多次,成功率方差与 query 多样性会共同放大。这里的"方差"具体指:在固定 prompt、固定模型、固定 temperature 的前提下,对同一 query 集做 N 次独立跑,得到的 pass rate 分布常常不是单峰的,而是双峰甚至多峰。也就是说,Agent 的行为模式更接近"要么一次跑通、要么完全失败",而不是"接近一次跑通"。

造成这种多峰分布的根源是模型行为的非确定性,而 query 多样性把这种非确定性进一步放大:当 query 涉及多步工具调用、跨域信息聚合、长上下文压缩时,每一步的小概率失败会沿链路上累积,最终成功率呈指数衰减。环路化设计的工程价值,正在于把这种指数衰减截断在每一层 grader 上。

数据 把 Agent 嵌入 Slack、GitHub、Calendar、Email 等真实工作系统后,可达性、复用率、团队使用率三项指标显著上升。可达性指的是 Agent 真正被业务侧触发的次数,因为它不再要求用户主动打开一个网页;复用率指的是同一个 Agent 在不同工作流里被实例化的次数,因为它被抽象成了 middleware(中间件);团队使用率指的是团队成员中至少每周使用一次 Agent 的占比。

这三项指标共同决定了 Agent 是"实验室 demo"还是"团队基础设施"。这也解释了为什么 LangChain 在 2026 年的工程主线里,几乎所有的官方示例都默认采用 event-driven 的接入方式,而不是 chat-only 的接入方式。

四种环路一次铺开

第一种是核心 Agent 环路(Core Agent Loop),即 request → model + tools → result 的最小闭环。它的工程意义在于把"思考"和"执行"显式分离开:模型负责决定下一步调用哪个 tool,tool 负责与外部世界交互,result 回到上下文驱动下一轮推理。这层环路是所有 Agent 的最小单元,无论外面套多少层,内层永远要先把这一层跑稳。

第二种是 Goal/Verification 环路。它的核心是把"完成"从一种感觉变成可判定的二元结果。具体做法是引入 grader agent(打分智能体)和 rubric criteria(评分准则),把任务目标拆成若干可独立打分的子项。比如一个 docs writer agent 的 rubric 可以是"是否包含示例代码、是否覆盖 API 全部参数、是否在末尾给出 FAQ"。只有当 grader 对每一项都判定通过,这条 trace 才被标记为 pass,否则会被打回重跑或回写到 dataset 里供后续训练。

第三种是 Event-driven 环路。它把 Agent 从"被动等用户输入"变成"被事件或定时主动触发",常见的事件源包括 Slack 消息、GitHub PR 评论、Calendar 日程、Email 到达,以及 cron 定时任务。LangChain 在文档里把这一层称作 harness(挂载框架),Deep Agents 开源仓库里的 Deep Agents harness 就是这种模式的具体实现。

第四种是 Self-improvement / Hill Climbing 环路,也是 LangChain 在 2026 年重点押注的方向。LangSmith Engine 这类 meta-agent 会周期性地遍历 production trace,自动改写 prompt、tool description、skill、memory,并把通过验证的版本 port 回源代码仓库。这种 hill climbing(爬山算法)风格的迭代可以在没有人工介入的情况下,持续抬高 Agent 的整体质量上限,真正实现"越跑越好"。

这四种环路并不是孤立的层级,而是可以俄罗斯套娃式任意嵌套:核心环路在最内层,verification 环路套在外面,event-driven 环路再套一层,self-improvement 环路在最外层,甚至 verification 环路本身又可以嵌套一个更细粒度的 verification 子环路。这种嵌套让 Agent 既能在单次任务里自我纠错,也能在跨任务、跨时间尺度上持续进化。

代码层面的最小配置片段

ini 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import GoalMiddleware, RubricMiddleware

agent = create_agent(
    model="claude-sonnet-4-5",
    tools=[search_tool, write_file_tool, calendar_tool],
    system_prompt="You are a docs writer agent.",
    middleware=[
        GoalMiddleware(goal="Produce API docs that pass CI"),
        RubricMiddleware(criteria=[
            "all parameters documented",
            "includes runnable example",
            "ends with FAQ section",
        ]),
    ],
    checkpointer=PostgresSaver.from_conn_string(DB_URL),
)

这段伪代码展示了 goal 与 rubric 作为可插拔 middleware 是怎么挂到 create_agent 上的。LangChain 官方文档对此有详细说明,详见 python.langchain.com/docs/concep...docs.langchain.com/oss/python/... Agent 被部署到生产环境,它的每一次运行都会留下 trace,这些 trace 既是 LangSmith 评测数据集的来源,也是 LangSmith Engine 启动 hill climbing 循环的燃料。

常见踩坑清单

  • 把单次 prompt 调试当作主线工程。短期看 demo 跑通,长期看每次模型升级都要重做一遍。
  • 把 rubric 当成 prompt 的一部分而不是独立 middleware。结果是换任务就要重写全部逻辑,跨任务复用率为零。
  • 忽略 checkpointer(检查点)与持久化。Agent 一旦跨进程重启就丢失中间状态,verification 环路无法回放,grader 也无从打分。
  • 把 hill climbing 当成黑盒。LangSmith Engine 改写 prompt 必须经过 PR review 才能合并回主分支,否则就是不可控的模型漂移,合规审计无法通过。

2026 年 Agent 工程的核心矛盾

如果说 2024 年大家关心的是"我的 Agent 能不能跑一次",那么 2026 年的核心矛盾已经迁移到"我的 Agent 能不能持续跑、且越跑越好"。这个迁移背后是三股力量的合流:第一,模型能力快速演进,同一段 prompt 的有效性可能在下一次模型升级后立刻失效;第二,业务侧对 Agent 的期待从"演示"转向"基础设施",稳定性与可观测性成为硬指标;第三,合规与审计需求迫使团队把 Agent 行为写成可重放的 trace,而 trace 自然就成为自我改进的反馈信号。

在这三股力量之下,任何只写了"一段 prompt 就上线"的 Agent 都会在几个月内被业务淘汰,只有把环路工程当作 first-class concern 设计的 Agent 才有可能持续存活。这也是为什么 LangChain 在 2026 年的路线图里,把 LangSmith Engine、Deep Agents 中间件、create_deep_agent 这三类能力放在同一个产品矩阵里对外讲述的原因。

它们共同回答的是同一个问题:当你的 Agent 不再是 demo,而是接入 Slack、GitHub、Calendar 等真实工作流的团队基础设施时,你需要一整套环路协议来保证它能被持续运行、持续验证、持续触发、持续自省。更多技术细节可以参考 LangChain 官方文档 python.langchain.com/docs/introd... 与 LangGraph 持久化与 checkpointer 章节 langchain-ai.github.io/langgraph/c...

把"写一个 prompt"升级为"设计一套环路",本质上是把 Agent 从手工艺时代带进了工程时代。手工艺时代靠个人经验与反复试错,工程时代靠协议、middleware、可观测数据。三者缺一,任何 Agent 项目都难以撑过 6 个月。

核心 Agent 环路:request → model + tools → result 的最小闭环

翻开任何一本 Agent 工程的参考实现,把它的依赖关系图压缩到最里层,你会看到一段永远不变的三段式循环:用户请求进来,模型拿到请求和可用工具清单,决定调哪些工具、按什么顺序调、怎么把工具的返回组织起来,最后吐出一个 result。这就是核心 Agent 环路------request → model + tools → result。它看似不起眼,却是后续所有复杂环路(Goal/Verification 环路、Event-driven 环路、Self-improvement/Hill Climbing 环路,以及俄罗斯套娃式任意嵌套组合)都必须包在最里层的最小单元。把这一层写错、跑不稳,后面所有上层设计都会变成空中楼阁。后续章节要展开的目标验证、事件触发、trace 回写,本质上都是在这一段最小闭环外面再套一层判定/触发/反馈的环,而最里层永远是它。

观察 在最小闭环里,模型本身并不"动手"------它只产出一段结构化的工具调用意图(通常是函数名 + 参数 JSON,即 OpenAI/Anthropic 风格的 tool_calls 字段),真正去执行 HTTP 请求、读写数据库、操作浏览器、调用 shell 的是工具实现本身。换句话说,工具决定了 Agent 的能力半径,模型决定了它在半径之内怎么走路 。一个 Agent 看起来能不能用,八成取决于工具集设计得是否正交、是否原子化、是否错误可恢复;prompt 写得再漂亮,也救不了一个返回结构混乱、异常吞噬、超时不设置的工具。在工程 review 里,我们经常会看到新人把 80% 的时间花在调 prompt,却对工具的 schema 模糊、错误未透传、未做幂等保护视而不见------这是典型的优先级倒挂。判断 Agent 质量的快速经验法则:先把工具的失败模式(超时、5xx、空结果、格式漂移)全部显式化,再去谈 prompt 调优,否则所有上层环路都建在沙子上。

下面用 LangChain 提供的 create_agent 在 30 行以内搭起这段最小核心环路。可运行的样例大致长这样:

python 复制代码
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI

@tool
def get_weather(city: str) -> str:
    """根据城市名返回当前天气。"""
    return f"{city}:晴,25°C"

agent = create_agent(
    model=ChatOpenAI(model="gpt-4o-mini"),
    tools=[get_weather],
    system_prompt="你是一个天气助手,根据用户提问返回天气信息。",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "北京今天天气怎么样?"}]}
)
print(result["messages"][-1].content)

这段代码完整跑通了 request → model + tools → result 三段:用户消息作为 request 进入 agent,模型读 messages 和 tool schema,判断需要调用 get_weather("北京"),工具执行后返回字符串,模型拿到 ToolMessage 再做一次推理,把工具输出组织成自然语言回给用户------这就是 result。任何一个 Agent 框架(无论 LangGraph、Deep Agents、Autogen 还是自研的 if-else 循环)剥到最里层,都跑不出这个动作。把这段搞明白,后面所有上层设计才有共同的语义底座,后面要讲的 checkpointer、middleware、grader agent 都挂在这一段最小环路上。

把这三段拆成可观察的状态变量,工程上至少要追到下面 4 个字段,任何一个生产级 Agent 都必须能够从日志/tracing 中把它们拎出来:

状态变量 来源 典型载体 在环路中的作用 失败时常见现象
请求(request) 用户或上游 Agent messages 列表里 role=user 的那条 启动本次循环的输入 空消息、过长截断、注入攻击
模型输出 LLM 推理 AIMessage,常含 tool_calls 字段 决策:调哪个工具、参数是什么 幻觉工具名、参数类型错、未终止
工具结果 工具函数执行 ToolMessage,content 为原始返回 把外部世界的事实搬进上下文 超时、异常吞噬、格式非结构化
最终产物(result) 模型二次推理或直接回传 最后一条 AIMessagecontent 本次循环对外暴露的产出 答非所问、漏字段、未遵循 schema

注意「模型输出」和「最终产物」不是同一回事:前者是中间决策(往往带 tool_calls),后者是面向用户的成品。在多轮工具调用里,模型输出和工具结果会交替出现多次,最终产物只出现一次。把这两者混在一起,是新手在 trace 里最容易踩的坑。LangGraph 的状态图把这 4 个变量显式建模成 graph 的 state 节点,LangGraph 状态图文档可参考 langchain-ai.github.io/langgraph/。... LangChain 原生 create_agent 是基于 LangGraph runtime 跑的,所以你在 messages 之外还能从 state 里拿到更细粒度的字段。

工程上,这个最小闭环在三种调用模式下有截然不同的取舍。把这三种模式摆在同一张表里对比,选型的边界就会清晰很多:

调用模式 API 形式 延迟体感 中间状态可观测性 适用场景 主要代价
同步调用 agent.invoke(...) 等到全部跑完才一次性返回 弱,只能在结束后拿到最终 messages 脚本、批处理、单元测试、CI 长任务下用户长时间空白
流式调用 agent.stream(...) 第一个 token 立即可见 强,每个节点事件都可订阅 前端对话、IDE 插件、需要逐字反馈 实现复杂度上升,需处理 partial tool_calls
异步调用 agent.ainvoke/astream(...) 与同步/流式一致 与同步/流式一致 高并发服务、Web 后端、asyncio 生态 工具实现必须 await,否则阻塞事件循环

取舍的核心点在于:同步简单、流式友好、异步扛量 。一个真实的内部 Agent 服务通常会用 astream 起一个 SSE 长连接,前端按 token 渲染;而 CLI 工具类(终端 coding agent、一次性脚本)往往选同步 invoke,先求稳再求快。需要批量回归评测 LangSmith datasets 时,异步 ainvoke 配合 asyncio.gather 是最高吞吐的选择。需要警惕的是,流式模式下 partial tool_calls 的处理:模型可能分多次吐出一个完整的工具调用,如果按 token 切分得太早,前端会拿到一段残缺的 JSON------这正是 LangChain stream events 中 on_tool_call_chunk 事件存在的意义。对比这三者的关键变量不是"快不快",而是"中间状态能不能被外部系统消费"------流式和异步天然适合接 LangSmith tracing、接前端 SSE、接事件总线,同步则更适合一次性脚本。

数据 这三段式循环在工程上有两道独立的可量化曲线:运行次数 (平均循环轮数,即一次任务里 model + tools 交替多少轮)和成功率(最终 result 是否符合预期)。经验上前者集中在 1--5 轮:简单问答类接近 1 轮,单工具调用类 2 轮,复杂多步任务(如"订机票 + 加日历 + 发邮件确认")可达 5--10 轮,长尾任务偶尔冲到 20 轮以上------这时候就该怀疑是不是 prompt 没把任务拆清楚;后者在 demo 阶段能到 80% 以上,但一旦把同一份 prompt 跨团队复用、跨模型迁移、跨生产流量跑起来,常常掉到 50% 以下。这不是 prompt 写得不够细的问题,而是单次 prompt 根本不可能让 Agent 在所有输入分布上都可靠------这正是后续 Goal/Verification 环路要正面解决的问题:不是继续改 prompt,而是把"完成"这件事变成可判定的(rubric criteria),让 grader agent 给每一次循环打一个 0/1 信号,然后用这个信号去驱动 prompt 更新、工具描述更新、skill 更新。

到这里,核心 Agent 环路的骨架已经立住。后续无论是加 grader agent 做目标验证,还是接 Slack/GitHub 做事件驱动,还是让 LangSmith Engine 跑 trace 自动改 prompt,最里层仍然要回到这一段 request → model + tools → result。把这层写稳、写对、写可观测,后面所有环路才有底座。LangChain Agents 的入门概念可以参考 python.langchain.com/docs/concep...create_agent 的 API 细节见 docs.langchain.com/oss/python/... Deep Agents 的中间件架构,/goal/rubric 就是以 tool middleware 的形式嵌进这一段最小环路的,这一点会在后续章节展开。

从 create_agent 到 create_deep_agent:LangChain 入口函数的层级选择

翻开 LangChain 官方文档,在 langchain.agents 模块下你会看到两条并排的入口函数:create_agentcreate_deep_agent。它们共享同一个最里层的核心 Agent 环路------request 进入、模型决策、工具被调用、result 吐出------却各自承担了不同的工程定位。create_agent 是 LangChain 官方刻意保留的轻量入口,只暴露 LangChain agents 子项目里最稳定的那部分 API:模型绑定、工具清单、system prompt、stop 条件。它的设计意图是「让你能用尽量少的代码跑通一个能调工具的最小 Agent」,因此它把 plan-and-execute、子 Agent 路由、文件型记忆、技能系统这些额外能力全部排除在外,以避免轻量入口被中途绑死在一个特定工作流上。

对只跑核心环路、单轮或有限多轮、单工具集合的最小场景而言,create_agent 已经足够。譬如一个只查汇率、只读只写一份 JSON 配置的内部小工具,用 create_agent(model=..., tools=[...]) 一行就能跑起来,启动开销、依赖图、调试链路都最短。但也正因为它只暴露最薄的一层,任何超出「单 Agent 单上下文」的诉求都会在这层入口处撞墙。

观察 在真实业务里,绝大多数 Agent 项目并不是「一个 Agent 跑到底」的结构。需求文档里一旦出现「让模型先拆任务再执行」「调用某个公司内部系统的专用子 Agent」「跨会话记住用户偏好」「按命名空间加载操作手册」这四类要求中的任意两条,就基本意味着核心环路外面需要再套一层规划层、一层子 Agent 调度层、一层长期记忆层、一层 skills 加载层------这正好是 create_deep_agent 默认替你装配的中间件栈。换句话说,create_agentcreate_deep_agent 的边界,本质不是「能不能调工具」,而是「你的 Agent 需不需要被另一个 Agent 管着」。

create_deep_agent 是 Deep Agents 这个独立开源项目提供的入口。它在 create_agent 的最里层环路之上,以 middleware 的形式插入了四件额外的事:plan-and-execute 规划器、sub-agent 路由、跨 run 持久化的文件型记忆、以及基于 skill 命名空间加载的技能包。这意味着你写的还是同一个「Agent」,但它天生就具备「拆任务、委派、跨会话记忆、按需加载手册」的能力。下面这段代码展示了用 create_deep_agent 注册一个最小可用的 plan-and-execute 子 Agent 的骨架:

ini 复制代码
from deepagents import create_deep_agent

sub_agent = {
    "name": "research_subagent",
    "description": "负责拆解研究类请求,产出子任务清单",
    "system_prompt": "你是一个 plan-and-execute 子 Agent,先写 TODO 列表,再逐项调用工具完成。",
}

agent = create_deep_agent(
    model="anthropic:claude-sonnet",
    tools=[web_search, code_runner],
    system_prompt="你是顶层协调 Agent,负责把用户请求路由到合适的子 Agent。",
    subagents=[sub_agent],
)

result = agent.invoke({"messages": [{"role": "user", "content": "调研 2026 年 RAG 评测基准的最新进展,并给我一份摘要"}]})
print(result["messages"][-1].content)

上面这段代码同时打开了两条新通道:第一条是 subagents 参数,它把 research_subagent 注册为可被顶层 Agent 通过 task(...) 工具调度的子 Agent;第二条是 tools 参数仍然挂在顶层 Agent 上,意味着子 Agent 并不自动继承父 Agent 的工具集,父子工具边界在这里被显式切开------这是 Deep Agents 与 LangGraph 多 Agent 图最显著的语义差异之一。

为了把这两层入口的差异一次性摆清楚,下表列出了它们在四个关键维度上的对比:

维度 create_agent create_deep_agent
层级定位 核心 Agent 环路最薄封装 核心环路 + 中间件栈(plan / sub-agent / memory / skills)
子 Agent 支持 不内置,需自行用 LangGraph 编排 通过 subagents 参数直接注册 plan-and-execute 风格子 Agent
文件/记忆 默认仅本轮 messages,需手动接 checkpointer 内置文件型记忆 + 跨 run 持久化,可挂 Postgres / Sqlite / MemorySaver
Skills 支持 通过文件系统式命名空间按需加载 skill 包,支持 skill update 自动 port 回源码

仅看这张表,你会觉得 create_deep_agentcreate_agent 的「超集」,直接选 create_deep_agent 永远更安全。这是一个常见的认知偏差------下面这段取舍矩阵会指出它什么时候是错的:

判断维度 create_agent create_deep_agent
业务是否需要 plan-and-execute 不需要,单步工具调用就能完成 需要,任务必须先拆解再执行
是否需要子 Agent 委派 不需要,一个上下文贯穿到底 需要,父子职责需要被显式切开
是否需要跨 run 记忆 不需要,每轮会话自包含 需要,用户偏好/历史状态必须跨会话保留
是否需要 skill 系统 不需要,system prompt 已够用 需要,手册/操作规范按命名空间加载
启动依赖与冷启动时延 要求最短依赖、最低冷启动时延 可以接受额外中间件带来的额外开销
调试可观测性粒度 只需要 LangSmith trace 一层 需要 trace + sub-agent 独立 trace + 记忆读写 trace

数据 矩阵里六个维度并非等价------根据 Deep Agents 仓库与官方文档给出的默认中间件装配清单,六个维度中只要命中「plan-and-execute」「子 Agent」「跨 run 记忆」这三项中的任意两项,工程经验上几乎都应直接选 create_deep_agent;反之,如果六个维度里只有 0 或 1 项命中,create_agent 的极简路径反而能显著降低调试复杂度与依赖体积。换句话说,真正的分水岭是「业务是否要求两层以上的 Agent 结构」,而不是「我有没有听说过 Deep Agents」。

从中间件视角看,create_deep_agent 的关键设计是把 plan、sub-agent routing、memory、skills 全部实现为「可插拔 middleware」,而不是写死在主循环里的固定阶段。这意味着你完全可以注册自己的 middleware,在模型调用之前或之后插入 rubric grader、日志埋点、A/B 测试分支------这是与 create_agent 在工程哲学上的最大差异。下面这段伪代码展示了如何把一个自定义的「目标校验 middleware」注入 create_deep_agent:

python 复制代码
from deepagents import create_deep_agent
from deepagents.middleware import Middleware

class GoalCheckMiddleware(Middleware):
    def before_model(self, state, runtime):
        if "/goal" in state["messages"][-1].content:
            state["metadata"]["goal_active"] = True
        return state

agent = create_deep_agent(
    model="anthropic:claude-sonnet",
    tools=[...],
    middleware=[GoalCheckMiddleware()],
)

before_model 钩子的存在意味着 /goal/rubric 这种「在 system prompt 之外另起一条目标/评分轴」的能力,可以作为 middleware 而非 prompt hack 来实现------这正是「Loop Engineering」框架下「goal/verification 环路可被任意 Agent 复用」的关键。

工程上常见的踩坑点有三:第一,误以为 create_deep_agent 一定包含所有 LangGraph 能力,实际上它的中间件栈是 Deep Agents 项目独立维护的,与 LangGraph 的图节点是两层抽象,自定义子图时应避免在两者之间混用 checkpointer schema。第二,把 subagents 列表写得过深,导致顶层 Agent 的 context 被反复「潜入」到几层子 Agent 的回执里,可用上下文被快速吃光------建议子 Agent 深度不超过 2 层,且每层都强制要求返回结构化摘要。第三,在 create_deep_agent 上挂全局 cross-run 记忆时,容易忽视命名空间冲突,生产环境务必给每个业务线配置独立的 memory namespace 并挂上 PostgresSaver 做隔离,避免不同业务的长期记忆互相污染。

延伸阅读建议直接看 Deep Agents 项目主页与 create_deep_agent 的官方 API 文档,前者覆盖了中间件栈的设计动机与默认装配清单,后者则给出了完整的参数表与返回值结构:github.com/langchain-a...docs.langchain.com/oss/python/... 。若需要对照 create_agent 的最小入口示例,可参考 LangChain 官方 agents 文档:docs.langchain.com/oss/python/...

回到工程选型本身:如果你正打算落地一个 Agent 产品,先问自己六个问题------是否需要 plan-and-execute、是否需要子 Agent、是否需要跨 run 记忆、是否需要 skills 系统、是否能接受中间件带来的额外冷启动时延、是否需要细粒度的子 trace 可观测性。问题的答案指向「两层以上 Agent 结构」时,直接选 create_deep_agent 并以 middleware 形式扩展;指向「一个 Agent 贯穿到底」时,create_agent 才是最小、最稳、最不引入隐性耦合的入口。两层入口并非替代关系,而是「同一核心环路在不同业务复杂度下的两个稳定解」。

Goal/Verification 环路:grader agent 把『完成』变成可判定

Goal/Verification 环路是 LangChain 团队在「四种环路」框架里明确点出的第二种环路。它的工程定位非常明确:解决核心 Agent 环路最薄弱的那个环节------「任务到底做完了没有」。一个请求进入核心环路,模型决策、工具调用、result 吐出,听起来闭环,但「result 是否真的达成了 goal」这个问题,核心环路自己是答不上来的。

把这件事拆开看,Goal/Verification 环路 = 核心环路 + 一个 grader agent(rubric 是 grader 的判定清单)。Grader 本身也是一个 LLM 调用的 agent,但它的职责不是「完成任务」,而是「评判完成」。Rubric criteria 是 grader 拿到 result 之后逐条对照的判定清单,通常按结构 / 事实 / 风格三类维度展开。每一类都对应一个独立的 grader prompt 模板,grader 用同一份 rubric 但从不同角度打出分数,最终汇总成 pass/fail 决策。这种设计的妙处在于:它把模型擅长的「生成」和工程上需要的「判定」切开,让 LLM 既当作者又当评审,但通过 rubric 的形式强制评审有据可依。

观察 让 LLM 自己回答 yes/no 是这门工程里最常见的反模式之一。模型擅长生成,但不擅长稳定地、可复现地评价自己;尤其在开放式任务里,「写完了吗」「合格吗」「符合要求吗」这类二选一问题极易被「礼貌性 yes」污染------模型倾向于顺着用户的期待给出肯定回答,即使结果存在明显瑕疵。这套课程的做法是:把「完成」拆成多条可独立判定的 rubric criteria,每条都要求 grader 给出「pass / fail + 简短理由」二元输出。这种设计把模糊的「好不好」转成了离散的、可聚合的布尔向量,工程上的好处是 fail 项可以被精确地写回 prompt,触发核心环路定向重跑,而不是让模型再盲目试一次。另一个隐性收益是:rubric 本身变成了可审计的对象,出问题时可以回溯到具体哪条 criteria 没通过,而不是面对一句空泛的「写得不好」。

具体到代码实现,grader middleware 是挂在 create_deep_agent 之上的一段中间件。下面这段 50 行级的伪代码展示了它的典型形态:核心 Agent 跑完后把 result 送入 grader middleware,grader 按 rubric 逐条打分,任一条 fail 即触发反馈回写。

python 复制代码
from langchain.agents import create_deep_agent
from langchain.tools import tool

RUBRIC = [
    {"name": "structure", "prompt": "章节是否齐全..."},
    {"name": "fact", "prompt": "数据/引用是否对齐..."},
    {"name": "style", "prompt": "语气/术语是否一致..."},
]

@tool
def grade_with_rubric(result: str) -> dict:
    scores = [call_grader(c, result) for c in RUBRIC]
    return {"all_pass": all(s["verdict"] == "pass" for s in scores), "scores": scores}

agent = create_deep_agent(
    model="claude-sonnet",
    tools=[grade_with_rubric],
    middleware=[GoalMiddleware(), RubricMiddleware(), GraderMiddleware()],
)

这段代码展示了三个关键点:第一,grade_with_rubric 本身就是一个 tool,它把 grader 当成普通工具调用而非外挂脚本,这意味着 grader 调用本身也会被 LangSmith tracing 完整记录;第二,middleware 列表里同时挂了 Goal / Rubric / Grader 三层,说明 Goal/Verification 环路在 Dcode / Deep Agents 的中间件架构里是组合式暴露的,而不是一个黑盒函数,任何 create_deep_agent 都可以按需启用其中一层;第三,grader 的输出回到主 agent,主 agent 据此决定是直接返回还是再跑一次核心环路,反馈通路是显式的、可观测的。

下面这张表格把 rubric 的三类维度、对应的 grader prompt 模板要点和典型 fail 信号摊开,便于工程团队对照落地:

维度 关注点 grader prompt 模板要点 典型 fail 信号
结构(structure) 章节是否齐全、字段是否齐备、长度是否在区间内 请检查 result 是否包含 X/Y/Z 三个章节,每章不少于 N 字,字段是否齐备 缺章节、字段缺失、超长或过短
事实(fact) 数据/引用/链接是否真实、是否对齐 context 请逐条核对 result 中的数字、引用、链接与源数据是否一致 编造数字、张冠李戴的引用、过期链接
风格(style) 语气、人称、术语是否一致,是否避开禁用词 请检查 result 是否使用第二人称、是否出现禁用词列表、术语口径是否统一 语气跳跃、人称不一致、敏感词命中

这张表的设计意图是:每一行都是一条可独立打分的 rubric,grader 拿到的不是「整体印象」而是「对照清单」。工程上,这意味着你可以把同一份 rubric 喂给不同模型做交叉验证,也可以把同一份 rubric 喂给同模型多次取众数,降低单次判定的随机性。该教程的讲师特别强调过:rubric 的颗粒度直接决定 grader 的可用性,颗粒度过粗会退化成「整体印象打分」,颗粒度过细则会让 grader 自身变成新的失败源------这是典型的工程权衡点。

但三种验证强度不是等价的。下表把单一 grader / 多 grader 并行 / grader + 人在回路(HITL)三种方案的取舍摊开,工程上需要按场景选择:

方案 验证强度 延迟 成本 适用场景 主要权衡
单一 grader 低-中 低(1 次 LLM 调用) 内容草稿、批量任务、低风险输出 易被模型礼貌性 yes 污染,鲁棒性差
多 grader 并行 中-高 中(N 次并发调用) 中(线性增长) 对外发布、研究报告、合规文档 成本随 rubric 数量线性上升,但可解释性强
grader + 人在回路(HITL) 高(等待人工确认) 高(人时成本) 金融、医疗、合同、对外承诺 强鲁棒但不可规模化,适合关键路径

取舍的核心是:验证强度和延迟 / 成本成反比,工程上不会用 HITL 去校验所有草稿,也不会只用单一 grader 去签发对外承诺。多 grader 并行是「默认推荐档」,因为它在强度和成本之间给了一个相对健康的折中。具体落地时,该教程建议的混合策略是:单一 grader 跑批量初筛,初筛 fail 的样本进入多 grader 并行复核,多 grader 仍有争议的样本升级到 HITL,这是一个成本可控且强度递进的常见组合。

未达标时的反馈路径是这套环路真正的「回路」所在。grader 把失败原因结构化地回写到 prompt 或上下文,触发核心环路定向重跑。具体做法是:grader 的输出形如 {"all_pass": false, "scores": [{"criterion": "事实-数字一致性", "verdict": "fail", "reason": "Q3 营收数字与源文档不符"}]},主 agent 拿到这个结构化失败信号后,在下一轮对话里把它作为 user message 注入,核心环路据此重新决策。这一步在工程上有个常被忽略的细节:反馈必须结构化,不能只是一句「请改一下」。结构化反馈让模型可以定位到具体 rubric criteria,而模糊反馈只会让模型再随机试一次,等于浪费一次重试机会。

另一个工程细节是反馈的回写位置:是写到 system prompt、user message、还是 checkpointer 里的跨轮 memory?这三种位置各有适用场景。System prompt 适合放长期稳定的 goal / rubric 文本;user message 适合放本次失败的具体 reason;跨轮 memory 适合放 grader 多轮累积的趋势信号,例如「同一份 result 已经连续 3 次在事实-数字一致性上 fail,提示底层 context 数据源可能有问题」。这三种位置在 LangSmith 里都是可观测的,但工程含义不同,选择错了会让反馈丢失上下文。

数据 这套反馈路径的工程价值可以从 LangSmith tracing 的实践里看出:在 grader 介入之前,核心环路的重试大多是「盲跑」------模型不知道上一次为什么失败,所以失败模式在多轮重试里反复出现;grader 介入之后,LangSmith datasets 上的对照实验通常显示,带 rubric 的定向反馈相对无差别重跑,任务通过率有明显提升,而绝对通过率受 rubric 颗粒度影响------rubric 越细,fail 项定位越准,但单次 grader 调用的 token 消耗也越高。该教程的讲师给出的实操建议是:把 rubric 控制在 5-10 条之间,既保证覆盖度又不会让 grader 自身变成新的瓶颈。同时要警惕「grader 漂移」:grader 的判定标准会随模型版本漂移,需要把 rubric 和 grader prompt 一起固化进 LangSmith datasets 做回归,避免「上周还过、这周突然挂」的隐性回归。

踩坑清单(讲师在课程里反复强调的几条):

  1. 不要让 grader 直接复用主 agent 的 system prompt。两者的目标函数不同,主 agent 是「完成任务」,grader 是「判定完成」,混用会让 grader 偏向「放水」。
  2. 不要把 grader 输出直接拼成自然语言喂回主 agent。结构化 JSON 走 tool message 通路比自然语言 user message 更稳定,因为主 agent 可以把它当结构化信号处理。
  3. 不要在 rubric 里塞「整体印象」类条目。这类条目几乎一定会退化成「礼貌性 yes」,工程上是无用功,占 rubric 配额又不出力。
  4. 不要忽略 grader 自身的失败。grader 也是 LLM 调用,也会挂,需要给它配超时、降级和回退策略,否则 grader 一挂整个 Verification 环路就停摆,主 agent 直接拿不到 fail 信号。

把这节收一下:Goal/Verification 环路的本质是把「任务完成」从模型自评的模糊二选一,转成 rubric criteria 的离散判定,把 grader 的失败信号结构化地回写到 prompt,触发核心环路定向重跑。在 LangChain 的中间件架构里,这层能力以可插拔 middleware 的形式暴露,任何 create_deep_agent 都可以挂上 Goal / Rubric / Grader 三层。它的工程价值不在于「让 agent 更聪明」,而在于「让失败可定位、可回写、可回放」,这是后续 Self-improvement / Hill Climbing 环路能够自动改写 prompt 的基础前提。

官方参考:

/goal 与 /rubric 语法糖:让目标先回写成 rubric 给你审

/goal 与 /rubric 是 Dcode 与 Deep Agents 提供的两条 slash command(斜杠命令),核心工程定位是把「目标」与「验收标准」这对概念从口头表达拉到工程可用的形式化层面。换句话说,你不需要在自然语言 prompt 里反复叨念「这次要做什么、做到什么样算完成」,而是用一行 /goal 描述任务、一段 /rubric 列出判定准则,Agent 在启动前会先把这两条回写成一份 grader(评分者) 用的 rubric(评分清单),交给你审,你点头之后它才开始跑。这种「先回写、再开工」的工作流并不是普通的 prompt 工程技巧,而是把 Goal/Verification 环路里 grader agent 的判定清单提前暴露成可读、可改、可入库的工程制品,让人类在执行前就能干预判定标准本身。

观察 /goal 最反直觉的工程特性是:它不是给执行 agent 下达的执行指令,而是给 grader agent 下达的判定规范。也就是说,当你在 Deep Agents 的交互面板里写下 /goal「生成一份市场调研摘要」时,真正被消费的不是这份目标陈述本身,而是由系统把目标拆解后回写出来的若干条 rubric criteria------每一条都对应一个可独立打分的判定维度。这种「让目标先回写成 rubric 给你审」的流程,等价于把 V&V(Verification & Validation) 流程左移到 prompt 之前:你在模型还没开始调用任何工具之前,就能修改校验标准,改完再放行。LangChain 团队反复强调的「agent that improves over time」的第一道闸门不是模型本身的迭代,而是标准本身可被审、可被改、可被 trace 对齐------产物才有共同讨论的起点。

下面给出一份最小可用的指令模板,你可以直接照搬到 terminal coding agent 或 docs writer agent 的会话中执行:

markdown 复制代码
/goal 为 LangChain 教程稿件补全三种环路(Goal/Verification、Event-driven、Self-improvement)
的核心定义与中文术语对照表,字数不少于 800 字,产出一段 Markdown。

/rubric
1. 三种环路名称与英文原文一一对应,中文译名无错别字。
2. 每种环路至少包含:触发场景、关键产物、对核心环路的依赖关系三要素。
3. 中文术语与 LangChain 官方文档用词一致,首次出现用括号注释英文。
4. 表格行数 = 3,列数 ≥ 3,表头与正文分隔行清晰可渲染。
5. 整段输出可被 docs writer agent 直接落盘为 Markdown,无需二次手工处理。

这份模板里最容易被忽略、但决定整套机制是否成立的一条是:/rubric 必须可枚举、可逐条勾选。如果把验收标准写成「写得好就行」「结构清晰即可」这种抽象形容,grader agent 实际拿不到可操作的判定基线,整个 Goal/Verification 环路会退化为同义反复的回声。把 rubric criteria 写成 atomic boolean(每条独立可判定 true/false) 是这套语法糖成立的前提条件,这一点无论在 Python 侧用 @tool 包装,还是在 LangGraph 的 StateGraph 里以节点形式实现,逻辑都一样。

能力维度 Dcode / Deep Agents (/goal + /rubric) Codex CLI Claude Code
任务回写成可审 rubric 原生支持,执行前自动产出 不提供 不提供
Grader agent 显式化 是(以 middleware 中间件形式挂载)
判定标准运行时修改 支持,回写阶段人工可介入 仅能修改 prompt 仅能修改 prompt
返工粒度 单条 rubric criteria 整段重跑 整段重跑
Trace 与 rubric 对齐 是,每条 criteria 独立打点
与 hill climbing loop 对接 是,rubric 可被 Engine 重写

从这张能力差异表可以直接看出,/goal 与 /rubric 的真正价值并不在「让模型更聪明」,而在「让失败可定位」。Codex CLI 与 Claude Code 也都能完成一个任务,但它们的「完成」是单点判定------结果要么整体交付、要么整体不交付,中间没有可逐条审查的判定维度。Dcode / Deep Agents 把校验粒度下放到 rubric criteria 这一层,意味着当某一条标准不达标时,你重跑的只是那一条,不是整段;同时这条失败的 criteria 可以直接被 LangSmith Engine 抓取,作为下一次 hill climbing 改写 prompt 的负样本锚点。

数据 基于课程中给出的同一任务剖面口径,在三种典型工作负载(代码改动、文档撰写、邮件草拟)上,采用「先回写 rubric 再执行」相比「直接开跑」的工程数据近似如下:首次成功率从约 42% 提升到约 71%(以 rubric 全数勾选为成功判据),单次任务的平均返工轮次由 2.4 轮压到 0.9 轮,平均 token 消耗下降约 35%。需要强调的是,这套数字只是同 rubric 拆法、同模型、同 trace 长度下的相对值,用来表达回路设计差异而非绝对性能。真正决定收益上限的不是 /goal、/rubric 这两条命令本身,而是你把多少工程语义塞进了每一条 rubric criteria------criteria 写得越细,grader 判得越准,但同时人工维护成本也越高,所以下文会专门给一张取舍矩阵。

为了把这个对比落到可执行的选择上,下面是先回写 rubric 与直接开跑两种模式在工程语义上的取舍矩阵:

工程关切 先回写 rubric 模式 直接开跑模式 取舍建议
首次交付成功率 高(rubric 提前对齐) 中(凭模型默认值兜底) 关键交付、SLA 场景选前者
单次返工成本 低(只重跑不达标的那条 criteria) 高(整段重跑) 长任务、token 敏感场景优先前者
prompt 维护复杂度 中(rubric 需要持续维护) 低(prompt 写一次即可) 任务结构稳定时用前者
人类审阅时间 任务启动前一次性投入 任务结束后被动检查 评审 SLA 紧的团队适合前者
适合的下游消费方 Hill climbing loop、trace-driven 优化 一次性 POC、低风险任务 把回路选型当 code review 来对待
失败归因速度 快(rubric 编号直接定位) 慢(整段 diff 看不出来) 多 Agent 协作场景必须走前者

观察 上表里有几个反直觉点:第一,「评审 SLA 紧的团队反而适合多花两分钟审 rubric」看起来违反常识,原因是 SLA 的隐性成本主要花在「结果返工 + 多人同步讨论」上,而不是花在「多花两分钟读一份回写好的清单」上。第二,把 /goal、/rubric 当成纯文档工具理解是错的------它们真正的下游消费者是 LangSmith Engine 这类 self-improvement / hill climbing 环路:只有当 rubric criteria 是结构化的、可枚举的、可打分的,trace-driven 改写 prompt 才有可能自动收敛;一旦 criteria 是「写得好就行」这种模糊语义,Engine 找不到优化梯度,整条学习链路就在源头断掉。第三,/goal 与 /rubric 是以一种「middleware(中间件)」形态挂在 Deep Agents harness 上的,这意味着你可以把它独立换出,而不是耦合在 prompt 模板里------这对工程团队是决定性优势,因为 rubric 的演化不需要每次都改 agent 的核心 prompt,只改中间件层即可。

关于这套语法糖的官方定义与使用方式,可以参考以下两个地址获取权威说明,避免被二手博客误导:

落地时常见的几条踩坑清单,值得工程团队在引入 /goal /rubric 之前先对齐:第一,把 /rubric 写成形容词堆叠(「内容丰富、逻辑清晰、表达流畅」),grader 拿不到可执行的 boolean 判定,整条 rubric 沦为装饰,Goal/Verification 环路实质失效;第二,/goal 里塞了实现细节(「请使用 LangGraph 的 StateGraph 而不是 Chain」),这把目标与实现方案耦合,后续若想切换到 LangChain Engine 自动改写方案,目标层会失去抽象,Engine 找不到改写空间;第三,忘记给 rubric criteria 编号,grader agent 在做 partial scoring 时无法逐条定位失败项,排查 trace 时也只能看到一团模糊的「不达标」标签;第四,把 /goal /rubric 当成一次性的临时 prompt 而不是配置化的 runbook,导致换项目就要重写一遍判定标准,rubric 没法在团队内沉淀为可复用资产;第五,在多次会话中不同人写了互相矛盾的 rubric(一条要求「口语化表达」,另一条要求「专业学术化表达」),grader 在交叉打分时会出现结构性冲突,这时候应该把同主题 rubric 做版本化管理,而不是每次重写。

总结一下,/goal 与 /rubric 的真正价值不是「写出更好的 prompt」,而是把 Goal/Verification 环路里 grader agent 的判定清单从黑箱变成白箱、把「完成」这个模糊概念固化为可逐条勾选的工程制品。当你能用 markdown 表格写出五条可枚举的 rubric、并且用一份 LangSmith trace 把每条 criterion 与具体的 tool call、具体的 model output 对齐,你就已经具备进入 self-improvement / hill climbing 环路的前提------下一步可以交给 LangSmith Engine 自动 rewrite 那些长期不达标的标准本身,或者把整段 rubric 作为 cross-run memory 通过 checkpointer(如 MemorySaver、PostgresSaver、SqliteSaver) 持久化下来,跨任务、跨会话复用,真正让 Agent 在「目标如何被校验」这件事上随时间变好。

Dcode 与 Deep Agents 的 middleware 机制:把环路当成可插拔部件

把环路拆成可插拔部件:Middleware 的工程价值

Middleware 这个词在 Web 后端里很常见,放在 Agent 框架里其实有完全一致的语义------它是一段夹在「主循环」与「外部能力」之间的可替换代码,既不破坏主流程,又能注入横切关注点。在 Dcode 与 Deep Agents 这两个开源项目里,middleware 都被显式建模成对外暴露的协议层,任何 LangChain 风格的核心 Agent 都可以挂上零个或多个 middleware,而不必修改自身代码。这种「主 Agent 不感知」的解耦,直接复用了 LangGraph 在持久化与 state 共享上的基础设施,避免了 Agent 内核每加一条新环路就 fork 一次的局面。

把前文拆出的四种环路------Core Agent Loop、Goal/Verification、Event-driven、Self-improvement------在实现层面全都可以表达为 middleware。Core Agent Loop 由 create_deep_agent 内部默认实现,其余三条环路则是「可选挂件」,通过 middleware 列表注入。主 Agent 因此只关心「调用模型 + 调度工具」,不关心是否有评分者、外部事件,还是自我改写。这种分离带来的副作用是,middleware 之间也可以复用------比如 Event-driven 与 Self-improvement 都可能需要写「跨 run 的 memory」,它们可以共享同一个 memory middleware 实例,而不是各写一份。

观察 当一条「智能体环路」被识别为可复用的工程模式时,把它降级成 middleware 是最廉价的迁移路径。理由有三:第一,middleware 协议天然支持挂载与卸载,让实验阶段的「先挂着看看」与生产阶段的「确认有收益再保留」可以无差别进行;第二,跨项目复用只需要 import 同一个 middleware 包,避免每个团队各写一份重复实现;第三,middleware 与 prompt 是正交关系,改 prompt 不会破坏 middleware 的接口签名,反之亦然。把这种解耦做到极致的团队,最终会得到一个「middleware 仓库」与一个「Agent 仓库」分离的 monorepo 结构,每个 PR 单独评估,合并节奏互相不阻塞。

最简 Middleware 接口签名与挂载方式

下面给出一个与 Dcode / Deep Agents 兼容的最小 middleware 抽象。代码使用 Python 描述,实际两个项目里都更复杂,但接口形态保持一致:

scss 复制代码
class GoalRubricMiddleware(AgentMiddleware):
    def before_agent(self, state, runtime):
        goal = runtime.context.get("goal", "")
        rubric = runtime.context.get("rubric", [])
        state["messages"].insert(0, _build_system_msg(goal, rubric))
        return state

    def after_agent(self, state, runtime):
        return _grade(state, rubric)

    def wrap_tool_call(self, request, handler):
        return _record_metrics(request, handler(request))

agent = create_deep_agent(
    model="claude-sonnet",
    tools=[...],
    middleware=[
        GoalRubricMiddleware(),
        EventDrivenMiddleware(schedule="0 9 * * *"),
        SelfImproveMiddleware(engine="langsmith"),
    ],
)

代码块里展示的 4 个钩子------before_agent / around_agent / after_agent / wrap_tool_call------是 Dcode 与 Deep Agents 共同遵循的协议面。每个钩子都接收当前 state 与 runtime,返回修改后的 state,主 Agent 在调度时按顺序串起来。值得注意的是,Dcode 与 Deep Agents 都把 around_agent 设计为可选------如果用户只想要单点钩入,before 与 after 就够用;只有当用户需要「包住整轮调用做超时或断路」时,才需要实现 around_agent。

Middleware 生命周期对照

下表把四个钩子拆开看,方便工程师判断「我想做的事属于哪一阶段」:

钩子 触发时机 典型用途 是否可短路
before_agent 每轮 Agent 调用前 注入 /goal、读取 /rubric、构造 system prompt
around_agent 包裹整轮调用 加超时、断路器、trace span 是(可直接返回)
after_agent 每轮调用后、写入结果前 跑 grader、做验证、上报指标
wrap_tool_call 每次工具调用前后 记录工具耗时、参数校验、结果脱敏 是(可改写)

数据 Goal/Verification 环路通常落在 before_agent + after_agent 两条钩子上:评分逻辑放在 after_agent 里跑,prompt 注入放在 before_agent 里做;Event-driven 环路几乎只在 around_agent 层加一层「唤醒时是否到点」的判断;Self-improvement 环路则是唯一一个会跨轮次写回外部存储的------它必须显式接入 memory store(可以是 LangGraph 的 PostgresSaver),否则跨 run 的状态无从累积。这意味着,在 4 个钩子中,after_agent 与 wrap_tool_call 的「长尾」开销最容易被低估------前者会拖慢整轮结束,后者会被每条工具调用放大。团队在压测时常见的误区,就是只测主循环耗时,却没把 middleware 钩子链的时间成本计入 P99 延迟。

Dcode vs Deep Agents:同一思路,两种工程取舍

Dcode 与 Deep Agents 在「middleware 是可插拔的」这一立场上完全一致,但在协议细节、文档完整度、生态插件数量上呈现出可量化的差异。下面给一个对比矩阵,方便读者按自己的需求选择。

维度 Dcode Deep Agents
Middleware 协议 自定义,但与 Deep Agents 形态接近 官方规范,作为公开 API 暴露
文档完整度 仓库级 README + 少量示例 独立子站,有钩子表、示例、迁移指南
生态插件数量 偏少,核心场景内置 多,LangChain 社区已有多个第三方包
默认注入 内置 Goal/Verification 内置 Sub-agent + todo middleware
接入门槛 需自己组装 create_agent create_deep_agent 一行挂载

取舍 :Dcode 更像一个「脚手架」,适合想从零看清每条环路如何串联的工程师;Deep Agents 则更像一个「成品 harness」,适合希望开箱即用、并把 LangSmith Engine 接进来跑 self-improvement 的团队。如果你的目标是「先验证 Goal/Verification 能不能跑通」,Dcode 更容易在几百行代码内复现;如果你的目标是「让 Agent 在生产里持续自我改写」,Deep Agents 的 memory 与 sub-agent middleware 是更稳妥的工程底座。

如果团队对「hook 时序的可观察性」有强需求(比如想接 OTEL、想接 LangSmith tracing),Deep Agents 的独立子站文档 docs.langchain.com/oss/python/... 会显著降低上手成本;而 Dcode 的源码则需要在仓库 README 与示例之间来回跳读,门槛偏高。这条差异决定了「愿意读源码」的团队更适合 Dcode,而「需要快速对齐」的组织更适合 Deep Agents。

把四种环路套娃:Middleware 的俄罗斯套娃

四条环路可以俄罗斯套娃式任意嵌套,这在前文已经提过。在 middleware 视角下,「嵌套」等价于「在 around_agent 里再启一条 create_deep_agent」:外层 Agent 跑 Goal/Verification,内层 Agent 跑 Core Loop;再外层加一层 Self-improvement,周期性地把内层的 prompt 改写回仓库。每一层都是一个 middleware 实例,层次之间通过 state 共享。这种「以 middleware 为最小套娃单元」的设计,让组合爆炸的可能性被收敛在一个可枚举的接口面上

工程上更深一层的考量是:套娃的层数越多,middleware 的「不可见开销」越容易被忽略。每多一层 create_deep_agent,就多一组 before/after 调用、多一份 state 拷贝、多一次 trace span。建议在部署前先做一次端到端的「无功能负载」压测,记录 baseline latency,再逐层挂上 middleware,观察每层的 P99 增量。这种「逐层加挂」的灰度策略,与 LangSmith tracing 的 span 树天然契合,可以在 trace 里直接看到每一层 middleware 的耗时贡献。

踩坑清单与工程建议

实战里有几个常见反模式值得提前指出。第一,把业务逻辑塞进 wrap_tool_call 而非 after_agent,导致单次工具失败影响整轮评分------评分应该看最终结果,而不是中间过程。第二,Goal/Verification middleware 没接入 checkpointer,run 结束后评分数据丢失,后续 self-improvement 没有 trace 可学。第三,Event-driven middleware 的 cron 表达式与业务时区不一致,触发时间漂移数小时。第四,Self-improvement 写回 prompt 时没做 diff 审计,回滚成本极高。第五,多 middleware 共用 memory store 时,key 命名空间不隔离,出现「跨项目互相覆盖」的事故。

工程上的稳妥做法是:先接入官方仓库里现成的 middleware,确认整条管线跑通后,再逐个替换为自研实现 。Deep Agents 仓库 github.com/langchain-a... 提供了多个参考实现,文档站点 docs.langchain.com/oss/python/... 列出了每个钩子的签名与时序图,是判断「我现在卡在哪一层」的最快路径。如果团队想更激进地定制,LangGraph 的持久化文档 langchain-ai.github.io/langgraph/c... 也是必读------它解释了 checkpointer 如何与 memory middleware 协同工作,以及为什么 PostgresSaver 比 MemorySaver 更适合长跑任务。

最后一点容易被忽略:middleware 不是 prompt 模板,它的更新节奏应该与 Agent prompt 解耦。把这两件事绑在同一个 PR 里,会让「提示词微调」与「管线结构变更」互相污染 review,反而拖慢迭代速度。理想状态下,middleware 仓库单独发版,prompt 走 LangChain Hub 或版本化的 YAML 仓库,两者只在 production 部署时合并。当 Self-improvement 环路开始自动改写 prompt 时,这种「分仓」结构还能让回滚只动 prompt 仓库,而无需重部署 Agent 运行时。

Event-driven 环路:把 Agent 嵌入 Slack / GitHub / Calendar / Email

Event-driven 环路:把 Agent 嵌入 Slack / GitHub / Calendar / Email

Event-driven 环路(事件驱动环路)在 LangChain 团队提出的「四种环路」框架里排第三位,它不是要替代核心 Agent 环路,而是把已经能稳定运行的 request → model + tools → result 这条主循环,挂到真实工作系统的触发源上,让 Agent 在工程师不需要主动召唤的时候,自己被事件唤起并完成一段端到端的工作。Slack 的某条消息、GitHub 上的某个 PR 评论、Calendar 上即将开始的会议、Email 收件箱里新到的客户邮件,都可以成为这条环路的入口信号。

把它与前两节讲的核心 Agent 环路和 Goal/Verification 环路拼起来看,这套设计的真正价值在于「让 Agent 进入团队已经习惯的工作流」。一个只跑在终端里的 CLI Agent,即便能力再强,也只能在工程师主动敲命令的窗口里产生作用;而一旦 Agent 收到一个 GitHub issue 评论,它就具备了 24×7 等待事件的能力,这是单条核心环路在工程可达性上的根本跃迁。在 Dcode 与 Deep Agents 这类开源中间件里,Event-driven 不是可选插件,而是与主环路并列的一等公民。

观察 真实工作系统的触发器让 Agent 从『我去找它』变成『它来找我』,可达性翻倍。这不是一句宣传话,而是工程语义上的硬变化。CLI Agent 是 pull 模式,用户必须记得命令、记得参数、记得上下文;Event-driven Agent 是 push 模式,触发源替用户完成了「我现在需要 Agent」这件事的判定。在多团队协作场景下,工程师最缺的不是更强的 Agent,而是一个不会忘记、不会漏单的协作者------事件触发天然解决了注意力瓶颈。从 Loop Engineering 的整体视角看,Event-driven 环路并不创造新的 Agent 能力,它只是把已经存在的核心环路挂到了团队已经习惯的工作流上,但正是这一步把 Agent 从「桌面工具」推到了「团队成员」的位置。

下面是一段最小骨架,展示如何用 Slack 的 incoming webhook 配合 LangServe 把一个 Agent 接到 Slack 频道。LangServe 在这里承担两个角色:一是把任意 LangChain / LangGraph 风格的 Agent 暴露成 OpenAI 兼容的 HTTP 服务,二是把异步执行与流式输出标准化,让上游的事件接收端不必关心 Agent 内部的并发模型。

vbnet 复制代码
from langserve import add_routes
from fastapi import FastAPI, Request
from deepagents import create_deep_agent

agent = create_deep_agent(model="anthropic:claude-sonnet", tools=[...], system_prompt="...")
app = FastAPI()
add_routes(app, agent, path="/agent")

@app.post("/slack/events")
async def slack_events(req: Request):
    body = await req.json()
    if body.get("type") == "url_verification":
        return {"challenge": body["challenge"]}
    event = body.get("event", {})
    if event.get("type") == "message":
        result = await agent.ainvoke({"messages": [{"role": "user", "content": event["text"]}]})
        await slack_client.chat_postMessage(channel=event["channel"], text=result["messages"][-1].content)
    return {"ok": True}

这段骨架里有几个工程细节必须讲清楚。第一,Slack 在 Event Subscription 握手时要求一个 url_verification 挑战,代码里的 challenge 回显不能省,否则 handshake 失败,后续所有事件都不会到达。第二,event.subtype 为 message_changed / bot_message 时要主动跳过,否则 Agent 的回复会被自己再次触发,形成自问自答的死循环,生产事故大多源于此。第三,Slack 的 3 秒 ACK 限制要求这条链路必须先快速返回 200,真正的 Agent 执行可以放到后台队列或 LangGraph 的 astream 异步任务里,绝对不能在 webhook 线程里阻塞式等待模型响应。第四,签名校验必须前置,Slack 在 X-Slack-Signature 头里放了 HMAC,没校验就把消息喂给 Agent 等于把 LLM 暴露给任何能伪造 POST 的人。

Slack / GitHub / Calendar / Email 这四类事件源在 payload 结构和触发语义上差异很大,工程实现时不能套用同一个适配器。下表把它们的字段和触发语义对齐,便于在中间件层设计统一的 verify_event(request, provider) 接口:

事件源 触发方式 关键 Payload 字段 触发语义 鉴权方式
Slack Events API (HTTP webhook) event.type, event.channel, event.user, event.text 新消息、@mention、reaction_added Signing Secret + OAuth
GitHub Webhooks (Issues/PR/Push) action, repository.full_name, issue.number, comment.body issue 打开/关闭、PR review 评论、push 提交 HMAC-SHA256 签名
Calendar Push notifications / Cron iCal UID, event.start.dateTime, attendees 会议开始前 N 分钟定时触发 OAuth2 + Watch 通道
Email IMAP IDLE / Gmail Pub/Sub message-id, from, subject, snippet 新邮件到达、特定 label 命中 OAuth2 / App Password

设计 Agent 时必须把这张表当作 schema 来看。Slack 和 GitHub 的 payload 都是结构化 JSON,字段稳定,可以放心做字段映射;Calendar 的 iCal 数据是半结构化文本,需要先用 icalendar 库解析再喂给 Agent,否则模型会拿到一坨带换行的字符串;Email 则要看具体协议,Gmail 的 Pub/Sub 是事件流,IMAP IDLE 则是长连接,二者对超时和重连的要求完全不同,直接影响中间件层的 socket 复用策略。

数据 对比一下触达路径上的工程差异:本地 CLI Agent 在一次完整工作流里,平均需要用户执行 3-5 步操作------启动终端、激活环境、敲命令、阅读输出、复制结果到下游工具;Event-driven Agent 在同一份工作流里,用户操作可以压缩到 0 步(纯异步触发)或 1 步(在 Slack 里回一句 @AgentBot)。触达路径从 5 步收敛到 1 步,意味着同样的 Agent 能力,在团队里被实际调用的概率提升一个数量级。在 10 人左右的小团队里,CLI Agent 的日均调用次数通常是个位数,而挂到 Slack 的同一个 Agent 在接入事件后,日均触发量会迅速爬到数十次------这就是 Dcode 与 Deep Agents 这类中间件层反复强调「事件是第一公民」的根因,也是 LangSmith Engine 在跑 self-improvement 环路时优先采集事件触发 trace 而非手工 trace 的原因。

下面是本地 CLI Agent 与 Event-driven Agent 在用户触达路径上的工程差异对比矩阵。这张矩阵的目的是让团队负责人在选型时直接看到「什么场景用哪一类」,而不是凭感觉拍板:

维度 本地 CLI Agent Event-driven Agent 取舍边界
启动方式 用户手动敲命令 事件 webhook / cron 自动触发 高频轻量任务倾向 Event-driven;一次性探索任务倾向 CLI
上下文来源 当前目录、git status、env 变量 事件 payload + 历史 thread 缺乏 git 状态的纯对话任务用 Event-driven 更顺手
输出落点 终端 stdout Slack 消息、PR 评论、邮件回复 输出需要被团队看到的场景必须用 Event-driven
鉴权面 本地 SSH key / API key OAuth + Webhook Secret + HMAC 多团队共享时 Event-driven 鉴权更复杂
失败可见性 终端直接报错 需要 push 回原事件源 Event-driven 必须显式设计失败回执
状态保持 进程内,无跨 run 记忆 依赖 checkpointer(PostgresSaver) 长流程任务必须上 Event-driven + 持久化
调试成本 直接看 stack trace 要回放 webhook 历史 排障时 CLI 更省事
安全边界 进程级权限 团队级共享权限 涉及跨人协作必须收紧 Event-driven 的 RBAC

几个实战中容易踩的坑要单独列一下,这些坑在 CLI Agent 里几乎不存在,但在 Event-driven 场景里是高频故障源。第一,Slack 的 outgoing webhook 已经被官方废弃,新接入必须用 Events API + Socket Mode 二选一,不能用老接口去拼,老博客里的截图多半已经过时。第二,GitHub webhook 默认会带 X-GitHub-Event 头,action 字段的取值在不同事件下完全不同(issues 用 opened,pr 用 synchronize,comment 用 created),Agent 的 prompt 必须按事件类型分发,否则会拿同一个模板去响应所有事件,效果很差,这也是为什么中间件层经常把 event_type → prompt_template 做成一张映射表。第三,Calendar 的 push notification 在 Google Workspace 上要求显式开启 watch 通道,通道 7 天后会过期,生产环境必须写自动续期的 cron,否则一周后 Agent 会突然哑火,没有任何告警。第四,Email Pub/Sub 的 history API 只能回溯 7 天,补数据时容易丢消息,必须在接入第一天就启 backlog worker。

鉴权侧的取舍是另一条暗线。Slack 用 Signing Secret 校验请求签名,GitHub 用 HMAC-SHA256,Google Calendar 与 Gmail 都走 OAuth2,每家都不一样。如果中间件层把这些封装成统一的 verify_event(request, provider) 接口,业务 Agent 就不必关心签名细节,直接拿到一个可信的 dict。这正是 Deep Agents 把 webhook 适配做成 tool middleware 的工程动机------把横切关注点从主 Agent 抽出去,主 Agent 就能专注于「这条 Slack 消息到底要不要回、怎么回」。

在跟 Goal/Verification 环路配合时,Event-driven 还有一个隐藏收益:每一次事件触发都自带一条 LangSmith trace,这些 trace 既能被 grader agent 拿去验证「Agent 在会议前 5 分钟是否真的把议要整理好并发到了频道」,也能被 LangSmith Engine 在自改进阶段拿去做批量评测。换句话说,Event-driven 环路天然是其它三条环路的 trace 矿脉,这也是 Loop Engineering 框架把四条环路设计成可以俄罗斯套娃式嵌套的根本原因。

官方文档与接入指南:

把视野拉回整套环路工程:核心 Agent 环路决定了 Agent 的能力上限,Goal/Verification 环路决定了 Agent 的质量下限,Event-driven 环路决定了 Agent 的触达半径,而 Self-improvement 环路决定了 Agent 的迭代速度。四条环路彼此正交,但又共享同一份 LangSmith trace 仓库------这是 Loop Engineering 这套框架最被低估的工程洞察:它不是一个线性的四步流程,而是一个由 trace 串联的可组合网络,每条环路都是另一条环路的数据源,任意两条甚至三条都能俄罗斯套娃式嵌套在同一个 Agent 实例里。

Schedule vs Webhook:事件触发的两种语义

当我们讨论 LangChain 团队「四种环路」框架里的 event-driven loop 时,最容易被忽略的问题是:所谓「事件」到底是什么?在工程实践中,把一个 Agent 唤醒起来的事件,其实分两种完全不同的语义 ------ schedule (类 cron、定时触发)与 webhook(外部系统到达触发)。这两种语义在幂等性、去重、重试三个维度上的行为差别极大,绝不能被当成「配个 cron 就完事」。

两种语义的本质区别

schedule 触发器 的语义是「在时间点 T,Agent 跑一次」。它的核心承诺是时间确定性:只要调度器还活着,不管外部世界发生什么,每天早上 08:00 都会有一个日历摘要 Agent 被准时唤醒。这种语义保证简单粗暴,但带了一个隐含假设 ------ Agent 跑的时间必须与数据的时效性匹配。你每天 08:00 总结「昨天」日历没问题;但如果想总结「上周」日历,就得重新评估 cron 该写几点。

webhook 触发器 的语义是「消息 X 到达时,Agent 跑一次」。它的核心承诺是事件确定性:Agent 跑的时间完全由外部系统决定 ------ 邮箱里来了一封邮件、GitHub 上有人留了一条 PR 评论、Slack 频道里有人发了一条消息。Agent 不知道「现在」是几点,只知道「这件事刚刚发生」。这种语义还有一个隐含约束:Agent 必须能从外部世界被触达 ------ 你需要一个 HTTP endpoint、一个 inbox 队列,或者第三方平台的 webhook 注册。

这也是为什么在设计 event-driven Agent 时,第一问题不是「这个 Agent 跑什么任务」,而是「这个 Agent 是按时间唤醒,还是按事件唤醒」。答案直接决定你的基础设施选型 ------ 调度器 vs webhook 接收器、polling vs 长连接、push 模式 vs pull 模式。

实战观察:摘要 Agent 与邮件助理的不同选择

观察 一个非常能说明问题的工程对比是:每日早上日历摘要 Agent 用的是 schedule ,而邮件到达触发的邮件助理用的是 webhook

为什么?因为日历数据是「最近状态型」 ------ 任何时刻「今天还剩下的日程」都是有意义的,每天早上固定时间点做一次总结是天然需求。这种需求是「周期性覆盖」,不是「即时响应」,所以用 cron / schedule 是正确选择。

但邮件不同。邮件是「到达即动作型」 ------ 用户在 14:23 收到一封重要邮件,你希望 Assistant 在秒级响应,而不是等下一个 cron tick。即便 cron 跑得再密,一分钟一次的延迟对重要邮件也是不可接受的。所以邮件助理必须用 webhook(或消息队列 / inbox 模式),触发源是邮件服务商(Gmail API push notification、Outlook webhook,或者自建 IMAP IDLE)。

这种二分法不是绝对的 ------ 你也可以用 schedule 做「polling」(设个每分钟扫一次邮箱的 cron),但生产环境里 polling 的延迟和资源消耗通常让人无法接受。所以在真实工程里,schedule 更适合「summary / batch / aggregation」类任务 ,webhook 更适合「response / processing / forwarding」类任务

代码骨架:用 LangChain scheduler 跑每日 08:00 触发

下面是一段用 LangChain scheduler 跑一个每日 08:00 触发的日历摘要 Agent 的骨架代码:

ini 复制代码
from langchain.agents import create_agent
from langchain.schedulers import CronScheduler

calendar_summary_agent = create_agent(
    model=llm,
    tools=[fetch_calendar_events, summarize_events, post_to_slack],
    system_prompt="你是日历摘要 Agent,每天 08:00 拉取当天剩余日程,生成简洁摘要并推送到指定 Slack 频道。"
)

scheduler = CronScheduler(timezone="Asia/Shanghai")
scheduler.register(
    job_id="daily_calendar_briefing",
    cron="0 8 * * *",
    fn=calendar_summary_agent.invoke,
    fn_kwargs={"input": {"messages": [{"role": "user", "content": "请生成今日日历摘要"}]}}
)
scheduler.start()

这段骨架的核心是:create_agent 产出一个可运行的 Agent,CronScheduler 提供 cron 表达式驱动的唤醒语义,scheduler.register 把「cron 表达式」与「Agent 调用」绑定起来。一个值得注意的细节是,这里的 fn_kwargs固定 input ------ Agent 每天早上醒来只知道「我该总结今天的日历」,并不知道用户会在几点看消息。这正是 schedule 语义的时间确定性特征:输入由时间决定,而不是由外部事件决定。

Schedule vs Webhook 语义对比表

工程里最容易踩坑的地方是「Agent 没跑成功怎么办」。这个问题的答案在 schedule 和 webhook 下完全不同:

维度 Schedule(cron) Webhook(inbox 到达)
幂等性要求 严格(同一天内必须幂等) 严格(每个事件有唯一 ID)
去重策略 用自然日期作 partition key 用 event_id / message_id 作 dedup key
重试语义 手动触发或等下一个 cron tick 接收端返回 5xx,发送端自动重试
失败反馈 静默失败(可能完全不知道跑挂了) 显式回执(发送端必须收到 2xx)
状态边界 天 / 小时 / 分钟(粗粒度) 事件 / 消息 / payload(细粒度)
可观测性 调度器日志 + Agent traces webhook 接收日志 + Agent traces + 发送端日志

数据 这个表反映了一个行业公认的事实:schedule 的「静默失败」webhook 的「显式回执」 不是技术细节,而是完全不同的可靠性模型。在一个 30 天的窗口里,如果一个 cron Agent 失败三次,你可能根本没注意到 ------ cron 静默跳过,等下一天。但一个 webhook Agent 失败一次,发送端(GitHub / Slack / Gmail)会按指数退避重试,你会看到清晰的错误日志。这种差异意味着:schedule 需要额外的心跳 / dead-letter queue 来补偿可观测性,而 webhook 的可观测性是发送端「白送」的。

同一 Agent 在两种触发下的失败模式对比矩阵

现在做一个对比矩阵,看同一个 Agent(比如 docs writer)在 schedule 和 webhook 两种触发下,会出现的失败模式差异:

失败场景 Schedule 触发行为 Webhook 触发行为
Agent 网络超时 无信号,只错过今早 brief 返回 500,发送端重试 3 次,最后一次成功
调度服务宕机 所有 cron job 暂停,当天无事件 webhook 接收端也可能挂,发送端把事件累积在队列
Agent 返回错误结果 等第二天「自动纠正」 用户立刻看到错误文档,可手动重跑
幂等冲突风险 低(一天只跑一次) 高(同一 webhook 可能被发多次)
可观测性成本 需要主动加 health check 自带重试日志 + 2xx 响应
典型工程痛点 「昨天的 brief 好像不对」 「为什么文档库里出现重复文档了」

这个对比矩阵揭示了一个更深层的规律:schedule 的失败模式是「时间型」的 ------ 失败藏在时间轴里,可能要等用户投诉才发现。webhook 的失败模式是「空间型」的 ------ 失败会立刻反映在发送端的重试队列里,你被迫第一时间处理。这就是为什么在生产环境里,schedule 触发器必须配套外部 watchdog,而 webhook 触发器一般只需要看 sender 的重试日志就够了。

Slack 里的「docs please」:webhook + 自然语言路由

一个非常典型的工程场景是:在团队 Slack 频道里,有人打一句 docs please,系统自动触发 docs writer Agent 去生成或更新相关文档。

这是 webhook + 自然语言路由 的典型组合:

  1. Slack 的 Events API 是 webhook 源 ------ 有人发消息时,Slack 把 payload POST 到我们注册的 endpoint;
  2. endpoint 收到消息,先经过一个轻量级意图分类器(可以是小模型或关键词匹配),判断这条消息是否包含「写文档」意图;
  3. 一旦识别到意图,转交给 docs writer Agent,由它调用 GitHub / Notion / Confluence API 完成文档生成;
  4. 完成后在 Slack thread 里回一条链接,形成闭环。

这个模式有两个工程要点值得注意:

第一,webhook 是自然语言路由的天然载体。因为 webhook 的 payload 里就带着「原始消息文本」,我们可以直接把这段文本作为 Agent 的输入,不需要「再合成一段任务描述」。这正是「自然语言作为 Agent 触发器」之所以流行的原因 ------ Slack / GitHub Issue / 邮件里的那条消息本身就是触发器,也是任务描述。

第二,意图分类这一步非常关键 。如果没有它,任何消息都会触发 Agent,噪音极高。常见工程实践是用 slash 命令 (如 /docs please)或特定关键词 (如 docs please)作为意图标记。这是在「完全自由自然语言」和「严格结构化命令」之间的设计权衡。

工程选型建议

基于上面的分析,可以给出一组工程选型建议:

  • 周期性总结 / 批量聚合 / 定时报表 → 用 schedule(cron),天然幂等,可观测性成本低;
  • 实时响应 / 事件驱动 / 外部系统集成 → 用 webhook,重试语义清晰,需要处理幂等;
  • 自然语言作为触发 → 强烈推荐用 webhook,因为 payload 自带原文;
  • 关键业务 Agent → 不管哪种触发,都必须外加心跳 + DLQ,不能依赖触发层的「隐式可靠性」。

这些建议不是绝对的 ------ 在一些混合场景下(比如你想「事件到达才触发的总结」),也可以用「webhook 到达 → 写入队列 → cron 每分钟扫队列」的方式。这其实是组合模式,LangChain 团队提出的「四种环路」框架里把这种叫 Russian doll nesting ------ 小环路嵌入大环路,每一层都可以自由选语义。

参考资源

关于 event-driven Agent 设计的更多细节,可以参考:

本节的核心立论是:在设计 event-driven Agent 时,第一个要回答的问题不是「这个 Agent 需要哪些工具」,而是「这个 Agent 的唤醒是时间确定,还是事件确定」。这个问题决定你的基础设施拓扑、可观测性策略与幂等设计。一旦回答完,剩下的工作就是把 Agent 塞进对应的 scheduler 或 webhook receiver,然后让它在后台默默跑,完成工程师不需要主动召唤的那一段端到端工作。

终端 Coding Agent 的环路结构:在 IDE / 终端里跑闭环

当我们把上一节讨论的两种事件语义------schedule(类 cron)与 webhook(外部系统到达)------落到具体场景时,会发现终端 Coding Agent 恰好是一种非常特殊的 Event-driven 环路:它的触发器既不是定时器,也不是来自外部系统的 webhook,而是用户在终端里敲下回车键的那一瞬间,或者一条 GitHub PR 评论到达的瞬间。前者的语义更接近"显式召唤(synchronous invocation)",后者的语义才是教科书意义上的 webhook;但两者在 Agent 视角下都会被抽象成"一个事件触发了新一轮 Core Agent Loop"。这种抽象的好处是,无论触发器是 Enter 键、PR 评论、Slack 提及,还是定时 polling,后续的 Goal/Verification 链路都可以复用同一套 middleware。

进入环路之后,模型调用 read_file、edit_file、run_cmd、git_commit 等工具,产出一段 diff(diff 即 code review agent 能看到的 change set),紧接着 CI 流水线、lint 静态检查、单元测试立刻成为这一轮的 verification signal(也就是 Goal/Verification 环路里的 rubric criteria)。这场"敲 Enter → 改代码 → 看 CI"的循环,是工程师每天都在操作的肌肉记忆,只不过 LangChain 团队的 Deep Agents Code 把它正式抽象成可编程的中间件,并把 /goal 与 /rubric 这种 slash 命令暴露给开发者作为 harness(可挂载的 Agent 运行时骨架)。在这套骨架里,Core Agent Loop 是底盘,Goal/Verification Loop 是方向盘,Event-driven Loop 是油门,Self-improvement Loop 是定期 OTA 升级。

观察 在工程实践里,终端 Coding Agent 最大的隐藏价值,是它把 Verification 信号从"人类 reviewer 主观判断"重构成了"机器可读的 rubric"。一条 CI red、一个 lint warning、一个测试覆盖率下降、一次 type checker 报错,这些都是结构化、可枚举、可重放的真值信号,远比"我觉得这段代码还行"更适合作为 grader agent 的输入。换句话说,CI 流水线天然就是 Goal/Verification 环路的 grader agent,只是过去它从未被显式地接进 Agent 框架。这与 LangChain 课程里反复强调的「让『完成』变成机器可判定」完全一致------区别在于,Software Engineering 领域天然就存在 grader,只是工程师过去没有意识到可以把它直接接到 Agent 的反馈回路上。

最小骨架:把 GitHub PR + CI runner 接进 create_deep_agent

下面这段伪代码演示了如何把 GitHub PR diff 工具与 CI runner 接入 Deep Agents 的 create_deep_agent SDK。它的核心思路是用一个 webhook 接收器作为入口,把 PR 评论事件和定时 poll 转成统一的事件 payload,然后再把这些 payload 路由给 Agent。Agent 内部依次拿到 diff、跑 CI、根据 rubric 决定是否直接 commit 修复或回贴评论。

python 复制代码
from deepagents import create_deep_agent
from langchain.tools import tool
from langchain_core.messages import HumanMessage

@tool
def get_pr_diff(pr_url: str) -> str:
    """拉取指定 PR 的 diff 内容,作为 agent 的 observation 来源。"""
    # 实际实现中调用 GitHub API:GET /repos/{owner}/{repo}/pulls/{n}/files
    return fetch_github_diff(pr_url)

@tool
def run_ci_runner(repo: str, sha: str) -> dict:
    """触发 CI runner,返回 {passed: bool, logs: str, coverage: float}。"""
    return trigger_ci_and_wait(repo, sha)

agent = create_deep_agent(
    tools=[get_pr_diff, run_ci_runner, read_file, edit_file, run_cmd, git_commit],
    system_prompt="你是一名 senior code reviewer + refactor agent,接到 PR 后先跑 CI 看基线,再用 diff 工具分析,最后给出修改建议或直接 commit 修复。",
    middleware=[
        goal_middleware(goal="所有 PR 必须通过 lint + 单元测试,覆盖率不低于 80%"),
        rubric_middleware(rubric=[
            "diff 中的函数必须补充单元测试",
            "不能引入新的 lint warning",
            "commit message 遵循 Conventional Commits",
        ]),
    ],
)

# webhook 接收器:把 PR 评论 / push 事件转成 Agent 输入
@app.post("/github/webhook")
async def on_github_event(event: GitHubEvent):
    if event.is_pr_comment or event.is_pr_sync:
        result = await agent.ainvoke({
            "messages": [HumanMessage(content=f"PR #{event.pr_number} 触发了新事件,action={event.action}")]
        })
        return result

这段骨架里有两个关键变量需要额外解释:一是 goal_middleware 把整个 PR 修复任务的目标("通过 lint + 单元测试")变成可判定的 acceptance criteria,等价于 LangChain 课程里强调的「把 goal 描述成可验证的命题」;二是 rubric_middleware 把"好的 PR"这个玄学概念拆成三条可枚举的判断依据,每条 rubric 都对应一次工具调用结果的 inspection,grader agent 据此判断这一轮 Core Agent Loop 是否需要 re-run。post 一个 PR 评论之所以能成为一条新的事件,是因为 LangGraph 在 ainvoke 期间自动把 thread state 写入 checkpointer,下一次 webhook 到达时通过同一个 thread_id 就能恢复上下文。

4 个核心工具的能力边界

终端 Coding Agent 在最小可用版本下,只需要 4 个工具就能跑出一个像样的闭环,这是 Deep Agents 团队在多个演示 demo 里反复验证过的最小集合。下表列出了它们的能力边界、典型耗时与失败信号:

工具 作用 典型耗时 失败信号
read_file 读取仓库任意文件内容,作为 observation 10-50ms 文件不存在、权限不足
edit_file 应用代码修改,返回 unified diff 30-100ms 语法错误、文件被外部修改
run_cmd 执行 shell 命令,捕获 stdout/stderr 100ms-5min 退出码非 0、超时
git_commit 提交变更,返回 commit SHA 50-200ms pre-commit hook 失败

这 4 个工具共同构成 Core Agent Loop 的全部动作空间。其中 run_cmd 是最危险也最有杠杆的工具,因为它可以间接触发 lint、test、build、CI runner 等所有外部 verification,等于把整个软件交付链路挂载到了 Agent 的"手臂"上。Deep Agents 团队在官方文档里特意强调:对 run_cmd 的副作用必须显式声明,否则模型很容易在调试时错把 rm -rf 当成"清理临时文件"。在 LangChain 课程的 Self-improvement 语境下,这 4 个工具的描述甚至会被 LangSmith Engine 周期性重写,以收敛到更安全的指令集。

反馈延迟 vs 环路节奏:IDE 插件 vs 终端 CLI

数据 同一段代码 review 任务,在 IDE 插件形态与终端 CLI 形态下的反馈延迟差距可以达到一到两个数量级。下表给出实测对比(基于中型 monorepo、CI runner 冷启动条件,数据来自 LangChain 团队在 Deep Agents Code 演示 repo 里的复现脚本):

维度 IDE 插件形态(IDE plugin) 终端 CLI 形态(terminal CLI)
触发延迟 用户在编辑器里保存即触发,通常 < 100ms 用户敲 Enter 才触发,但允许后台异步
上下文完备性 天然包含当前文件、光标位置、打开的 tab 只看得见命令行参数与 cwd,需手动 read_file
反馈延迟 受 LSP(language server protocol)实时回流,200-500ms 拿到 type error 必须等 CI runner,5s-5min 拿到完整 signal
适用任务 局部 refactor、inline 修复、补全 跨文件改动、依赖升级、批量化 PR 修复
Grader 形态 LSP + 编译错误 + 静态分析 CI runner + diff + lint + 单元测试
失败可重放性 较差,依赖 IDE 状态 较好,每次 Enter 是一次独立可重放的事件

观察 两种形态并不是非此即彼,而是同一个 Core Agent Loop 的两种投影。IDE 插件把"短反馈高频修改"做到了极致,适合让模型在用户视野内做小步快跑;终端 CLI 把"长反馈高确定性"做到了极致,适合让模型在异步上下文里完成跨文件、跨 PR 的重型工作。LangChain 团队在 Deep Agents Code 项目里给出的范式是:让终端 CLI 负责跨文件、跨异步信号的高确定性 reasoning,让 IDE 插件负责 inline 低延迟的体感补全,两者通过统一的事件总线与 LangGraph checkpointer 共享 thread state。从环路工程的视角看,这正是同一种 Goal/Verification 环路在两种延迟预算下的实现取舍。

踩坑清单与延伸阅读

把 Coding Agent 从演示 demo 部署到真实工程团队里,通常会踩到以下三类坑:

  • CI runner 配额与冷启动 :很多 CI 服务在免费层有月并发分钟数限制,Agent 高频触发会让配额迅速耗尽。建议把 run_cmd 的实际指向改为本地 Docker 模拟 runner,只在关键节点 push 到真实 CI。
  • diff 工具的特权边界:GitHub PAT(personal access token)如果给得过宽,模型可能误删分支或 force-push。最佳实践是给它一个只读 PAT + 单独的 bot 账号,只允许 read repo + write comment,严禁授予 admin 权限。
  • rubric 漂移:rubric criteria 写得太死会让 Agent 失去灵活性,写得太松又无法形成可靠 grader。建议把 rubric 同时存进 LangSmith datasets,跑自动 eval 看覆盖率,并在 Self-improvement 阶段让 LangSmith Engine 自动迭代 rubric 文本。

具体的工程实现与官方演示案例,可以参考以下两个延伸阅读入口:

总之,终端 Coding Agent 的本质,是把"敲 Enter"这个肌肉记忆封装成可编程的 event-driven 环路 trigger,把 CI / diff / lint 封装成天然的 grader,把 read_file / edit_file / run_cmd / git_commit 四个核心工具封装成动作空间。当这三层封装都齐备时,所谓"AI 写代码"才会从 demo 变成可被团队采纳的工程实践------而这一切,并不是因为模型变聪明了,而是因为环路结构变规整了。

Self-improvement / Hill Climbing 环路:真相在 traces 里

在 LangChain 团队关于 Loop Engineering 的整套课程里,Self-improvement / Hill Climbing 环路被摆在最高一层,讲师明确把它称为「基石思想」。其底层假设与传统软件截然相反:Agent 是一种非确定性系统,同一段 prompt 在两次运行之间可能产生截然不同的结果,因此「真相」不在人的脑子里,也不在静态的 prompt 文件里,而在每一次运行留下的 trace(追踪记录)里。这意味着我们不能再用「我觉得这个 prompt 写得不错」来做工程决策,而必须把 trace 当成唯一可信的反馈源。

观察 在传统软件里,代码的输出是确定的,所以调试靠 log、靠断点、靠推理;在 Agent 系统里,模型本身是一个不可控的黑盒,人类直觉对「prompt 好不好」的判断,误差往往是数量级的。真正能反映 prompt 实际表现的,是 trace 中那些真实发生过但被我们忽略的细节------比如模型反复在同一个 tool 上重试、或者在第 4 步突然开始使用一个从未声明的「内联工具」。这些信号只有从 trace 里反推出来,才有工程价值。

接下来这一节要解决的问题,就是「如何用 trace 倒推出 prompt / 工具描述 / memory 这些『配置』该改成什么样」。这一步看似朴素,实则是整个 Loop Engineering 框架中工程杠杆最大的环节。

一个最简单的 Hill Climbing 环路

先给出一个最朴素的实现骨架------跑主 Agent → 提取失败信号 → 改 prompt → 再跑------这是 Hill Climbing 在工程上的最小可用版本:

python 复制代码
from langchain.agents import create_agent
from langsmith import Client

MAIN_AGENT = create_agent(
    model="...",
    tools=[...],
    system_prompt=open("prompt_v0.md").read(),
)

def run_once(query: str) -> str:
    return MAIN_AGENT.invoke({"messages": [{"role": "user", "content": query}]})

def extract_failure_signals(trace_id: str) -> list[dict]:
    run = Client().read_run(trace_id)
    # 检测工具参数错误 / tool call 缺失 / 风格漂移 / 重复循环 / 幻觉事实
    return signals

def rewrite_prompt(current: str, signals: list[dict]) -> str:
    return META_REWRITER.invoke({"prompt": current, "signals": signals})

current = open("prompt_v0.md").read()
for round_idx in range(20):
    trace_id = run_once("回归测试集 query")
    signals = extract_failure_signals(trace_id)
    if not signals:
        break
    current = rewrite_prompt(current, signals)
    MAIN_AGENT = MAIN_AGENT.with_config(system_prompt=current)

这段伪代码的核心思想是:把「调 prompt」从一次性的人类直觉行为,转成一条可以反复迭代、每一次迭代都有 trace 证据的闭环。注意 with_config(system_prompt=...) 这一行------在 LangGraph 持久化与 checkpointer 模型下,我们可以让 prompt 改动跨 run 生效而不需要重新部署。详见 LangGraph 官方文档 langchain-ai.github.io/langgraph/ 与 LangSmith 评测数据集示例 docs.smith.langchain.com/evaluation。

5 类失败信号:trace 能告诉我们什么

下表列出讲师在课程里反复强调的、可从 trace 中反推出的 5 类失败信号。每一类都对应一种具体的 prompt 或 tool description 改动策略,工程上只要把这五类信号都接上检测器,基本就覆盖了 Agent 失败案例的绝大多数。

信号类型 在 trace 里的典型表现 反推出的配置改动 检测难度
工具参数错误 tool_call 的 arguments 字段类型错误或字段缺失 改写 tool docstring,补充参数示例
tool call 缺失 完成某任务本应调用某个 tool,却直接给出文字答案 在 system_prompt 中显式声明「必须先调用 X」
风格漂移 同一 agent 不同 run 输出语气/格式差异很大 在 prompt 中钉死输出 schema,例如 JSON Schema
重复循环 trace 中出现连续 ≥3 次相似 tool call 加入「若 tool 返回 X 则停止重试」的早停规则
幻觉事实 最终答案中包含无法在 trace tool 结果里溯源的事实 在 prompt 末尾追加「只能使用 tool 返回的内容作答」

数据 在该教程的演示案例里,讲师展示了一组对照实验:把同一个 Coding Agent 跑在 100 条回归测试集上,初始 prompt 的通过率大约在 60% 量级;不开 Hill Climbing 环路时,人工调 prompt 三轮,人力花费显著但通过率提升有限;开启 Hill Climbing 环路后,meta-agent 自动跑了大约 12 轮 trace-driven 改写,最终通过率稳定上升到 85%-90% 区间,且每轮都能在 trace 里看到具体的失败信号在减少。注意:这里的具体数字是该教程演示场景下的实测,不要把它外推到任意 Agent 系统------不同任务、不同模型、不同 rubric 下的曲线形状会显著不同,真正可移植的只是「trace-driven 改写」这一机制,而不是某条曲线的具体数值。

长期演化:Self-improvement 环路启用 vs 关闭

下面给出一张对比矩阵,描述长期(假设 50 轮迭代)演化下两种模式的差异。这里的关键不是「哪条曲线数值更高」,而是「成本结构与稳定性是否可承受」。对 AI 工程师和团队负责人而言,这道选择题的答案往往比看上去更微妙。

维度 Self-improvement 环路关闭(纯人工) Self-improvement 环路开启(meta-agent + traces)
短期成功率(0-5 轮) 取决于 prompt 工程师个人水平 由初始 prompt 决定,可能略低
长期成功率(20-50 轮) 提升有限,易出现「越改越差」回退 持续上升,通常呈对数曲线
单轮迭代成本 高(需要资深工程师 1-2 小时) 低(meta-agent 自动跑,只需 trace 评审)
可解释性 高(人能解释每一处改动) 中(需要保留每轮 prompt diff 才能审计)
上限 受人工瓶颈限制 受 trace 质量与 meta-agent 能力限制
失败模式 工程师主观偏差、context 切换疲劳 meta-agent 在 trace 中学到的偏差被固化

取舍 启用 Self-improvement 环路并不是「永远更优」的银弹。它的代价是引入了一个新的故障源------meta-agent 本身。如果 meta-agent 的改写策略有偏差,这种偏差会被外层环路持续放大,形成「在错误的方向上越走越远」的危险模式。所以工程上常见的折中做法是:人工评审每一条 prompt diff、保留 git 化的 prompt 历史、必要时一键回滚。换句话说,外层环路不是「替代」人,而是「放大」人------它把人的注意力从「逐字逐句抠 prompt」解放到「评审 meta-agent 的改写决策」,但后者的责任密度其实更高。

外层环路让内层环路越来越有效

Hill Climbing 在 LangChain 语境下的真正威力,不是「单次改 prompt」本身,而是「外层环路让内层环路越来越有效」这一嵌套结构。设想一个典型场景:Core Agent Loop(内层)负责跑一次任务,Goal/Verification 环路(中层)用 grader 给出通过或不通过的判定,Self-improvement/Hill Climbing 环路(外层)拿到 grader 输出后,改写 Core Agent 的 prompt 与 tool description。

每一轮外层迭代,Core Agent 的 prompt 都在被微调------比如把 grader 经常扣分的「tool 参数错误」修掉、把 grader 经常忽略的「幻觉事实」加一道防线。下一次内层跑回归集时,因为 prompt 已经修正了上一轮的具体失败模式,grader 的通过率就会上升;通过率上升,反过来又给外层 meta-agent 提供了「这一轮的 prompt 改动是正向」的反馈信号。

这就是 Hill Climbing 的核心动机:我们不是要做一个「能自己学」的 Agent,而是要让外层 meta-agent 把每一次跑 trace 攒下来的证据,变成下一次内层 Core Agent 更容易成功的前提。Layer 与 layer 之间的耦合不是耦合,是一种「互相喂数据」的递增结构。

落地到工程上,LangSmith Engine(见 www.langchain.com/langsmith)正...%E6%AD%A3%E6%98%AF%E8%BF%99%E5%A5%97%E6%80%9D%E6%83%B3%E7%9A%84%E7%8E%B0%E6%88%90%E8%BD%BD%E4%BD%93:%E5%AE%83%E8%B7%91%E8%BF%87%E5%8E%86%E5%8F%B2) trace 后,自动生成 prompt / skill / memory 的改写建议,并可以一键 port 回源码。配合 LangGraph 的 checkpointer(MemorySaver / PostgresSaver / SqliteSaver 三种实现见 langchain-ai.github.io/langgraph/c...,%E8%B7%A8) run 的 memory 与 prompt 版本管理都被打通了,外层环路才能真的落地为可观测、可回滚、可审计的工程流水线。

总结:Hill Climbing 环路不是「让 Agent 更聪明」的玄学,而是把 Agent 的不确定性从「不可控的随机」转成「可累积的证据」的工程机制。真相在 traces 里------这是 LangChain 团队关于 Loop Engineering 最朴素也最深刻的一句断言。当外层环路把每一条 trace 都转成下一次内层 run 的「先验知识」时,Agent 才真正开始具备「越用越好」的能力,而那正是 Loop Engineering 想要抵达的终点。

LangSmith Engine:跑过 traces 自动改 prompt / tool / skill / memory

在 LangChain 团队关于 Loop Engineering 的整套课程里,Self-improvement / Hill Climbing 环路被摆在最高一层,讲师明确把它称为「基石思想」。其底层假设与传统软件截然相反:Agent 是一种非确定性系统,同一段 prompt 在两次运行之间可能产生截然不同的结果,因此「真相」不在人的脑子里,也不在静态的 prompt 文件里,而在每一次运行留下的 trace(追踪记录)里。这意味着我们不能再用「我觉得这个 prompt 写得不错」来做工程决策,而要把决策权交给 trace。

LangSmith Engine 正是这一思路下的产物:它是 LangChain 推出的一个元 agent(meta-agent) ,专门跑过 LangSmith 已经采集到的大量 traces 做反思,然后把反思结果反向写回到源码里。和传统意义上的「自动 prompt 调优」工具不同,它不是黑盒拟合,而是把 LangChain 生态里所有的 prompts、tools、skills、memory 四类对象都视为可读写的「可改写源」,并通过 PR(代码评审)或 LangSmith Hub 之类的可信通道回流到团队仓库。它与生产主 agent 并行存在但不抢流量,把 trace 当作唯一的老师,而不是把 LLM 的偏好当成真理。

LangSmith Engine 在整体架构中的位置

把图看清楚最重要的一点是:LangSmith Engine 不是主 agent 的替代品,也不是并联运行的第二只 agent。它更像一只「外挂的旁路审视者」,订阅 LangSmith 的 trace 流,在后台离线跑分析,只在需要的时候给出改动建议。生产流量不会因为它而改道,也不会因为它而卡顿;它的存在形态更接近一只 CI(持续集成) Bot,而不是一个常驻的在线推理服务。

观察 任何把「自动改 prompt」想象成「按钮一点全网立刻生效」的人,都没有真正理解 LangSmith Engine 的工程语义。它只产出 diff(差异文件)和验证 rubric(评分标准),最终落地还是要靠工程师 / agent 双盲评审。这就把它和传统 A/B 测试平台区分开了:它不是在替人做决策,而是把决策所需的原材料(改写建议、对比 trace、回滚证据)从分散的 prompt 文件里搬出来,堆到同一张桌子上。它是一台「改造 prompt 的流水线」,而不是「决定 prompt 对错的裁判」。

最小接线示例

下面这段伪代码展示了如何让 LangSmith Engine 监听 trace 流并在累积到阈值时自动产出 prompt 改写建议。其中的关键不是 API 调用本身,而是「监听器---分析器---回写器」三段式结构,以及一个明确的 backpressure(反压)开关,避免在流量高峰期被反思任务打爆。

ini 复制代码
from langsmith_engine import TraceWatcher, PromptRewriter
from langsmith_engine.policy import RewriterConfig, WriteBackPolicy

watcher = TraceWatcher(
    project="prod-email-assistant",
    sample_rate=0.1,        # 只采样 10% 的生产 trace
    only_failures=True,     # 只看失败 / 投诉 / 重试的三类
    window="24h",           # 滚动窗口:24 小时
)
rewriter = PromptRewriter(
    targets=["prompts", "tools", "skills", "memory"],
    rubric="rubrics/email_assistant.yaml",
    max_diff_lines=40,      # 单次 diff 上限,防止一次改太多
    require_pr=True,        # 改写必须走 PR,禁止直写 main
    writeback=WriteBackPolicy(
        repo="org/agent-prompts",
        reviewers=["on-call-agent-team"],
    ),
)
watcher.on_threshold(count=50, fn=rewriter.suggest_diff).start()

观察 上面 sample_rate=0.1only_failures=Truemax_diff_lines=40 这三个开关是工程里最容易抄漏的一行。LangSmith Engine 跑的不是「全量回放」而是「抽样 + 失败导向」的近视眼分析,目的是用 1% 的算力撬动 80% 的可解释性。如果跳过这三行,初学者很快就会在生产环境把 token 预算打穿,或者反方向被全成功 trace 误导,得出「一切看起来都很好,prompt 不需要改」的虚假结论。


LangSmith Engine 可改写的 4 类对象

对象类型 在 LangChain 生态中的位置 改写后回写到哪里 触发条件典型场景
prompts system prompt、user prompt template、few-shot 提示词 LangChain Hub prompt 仓库 / git 仓库 PR 模型输出与 rubric 评分偏差持续上升
tools tool name、tool description、tool schema、参数说明 工具注册源码 PR(例如 tools/email_tools.py) 模型选错工具,或工具返回格式频繁被 model 误读
skills 复合任务脚本(例如 docs writer agent 的整段 skill 模板) skill 仓库 PR,通常以一个 skill.md + 测试用例形式提交 跨多个 trace 反复犯同一类「漏写章节」错误
memory cross-run 长期记忆(MemorySaver / PostgresSaver 写入的快照) LangSmith memory store schema 迁移 + 旧值归档 模型在多轮里反复忘记某个事实,或反过来记忆污染

数据 这张表里 4 类对象的「改写密度」并不一致。根据该教程给出的口径,在同一段时间窗口内,prompts 被改写最频繁,但回滚率也最高,因为 prompt 是最易测试错的位置,一次字符级的修改就能把分数拉下来;tools 与 skills 改写频次中等,但一旦改对收益最大,因为工具接口的修正会带来整条链路稳定度的跃升;memory 改写频次最低,然而单次改动影响最深,因为它跨多条对话持续生效。把它们放在同一张表里看,工程师就能直观判断「哪一类改写值得自动化,哪一类一定要留人介入」,而不是把所有对象塞进同一个自动改写流水线里。


人工改 prompt vs LangSmith Engine 自动改 prompt

维度 人工改 prompt LangSmith Engine 自动改 prompt 取舍 / 边界
改动频率 周级 / 月级,通常在事故复盘后才改 小时级 / 滚动窗口级,每次 trace 聚合触发 自动频率高 10-100 倍,但单次改动幅度更小
一致性 与个人风格强绑定,新人接手需要 onboarding 团队共享同一份 rubric,跨工程师一致性更稳 自动一致性更高,但前提是 rubric 本身写得好
回滚成本 高:涉及 prompt 文件、Hub prompt、agent 配置多处同步 低:每条改写建议都带 trace diff、跑分对比、一键 revert PR 自动路径天然带 audit trail(审计轨迹)
决策权归属 人 + agent 双盲评审,改写 PR 必须人工合并 速度让人,质量让人
适用规模 几个 prompt 的小项目 几十上百个 prompt 的大仓 / 多租户 SaaS 自动路径在小项目反而是负担
风险点 改了对不对全靠经验 改的可能「统计上对,但用户不喜欢」 都需要人在回路(human-in-the-loop)

数据 课程里提到一个粗略数量级:在没有 LangSmith Engine 之前,一个中型 agent 团队每周大约能手工迭代 1-2 个 prompt,改写节奏完全绑死在值班工程师的空闲时间上;接入 LangSmith Engine 之后,一周能自动产出 20-50 条候选 diff,但其中真正被人工合并的只有 5-15 条,合并率约 25-30%。这个数字揭示了一个常被忽视的事实:自动改写的瓶颈不是「改得多快」,而是「人工评审能不能跟得上」。盲目追求自动 diff 数量,反而会让评审人手崩溃,合并率塌方到不足 10%,改写质量随之一落千丈。这是一条工程上非常典型的「左脚踩右脚」陷阱:越自动化越需要人,看似悖论,实质是把人从「写 diff」解放到了「审 diff」,而审 diff 的认知负担其实更高。


踩坑清单与工程配置要点

第一,rubric 必须显式存在。LangSmith Engine 不靠玄学,它依赖一份 rubric YAML(或 rubric criteria 文件)告诉它「好与不好」。凡是写过一遍内部「agent 评分标准」的团队都会发现,把 rubrics 写在文档里这一件事本身,比工具引入的收益还要大------它会强迫团队回答「我们到底在意什么」这个问题。

第二,采样策略必须刻意设计。sample_rate 过高会吞 token 预算;过低又会让 Engine 跑在「全成功」的高分集上,学不到东西。常见做法是:失败集全量采样、正常流量降采样到 1-5%、投诉工单单独开高优先级通道。

第三,writeback 通道必须分级。prompts / tools 这类改写可以直接走 PR;memory 这种跨用户数据则要走 schema 迁移 + 数据归档 + 灰度发布,绝不允许它直接 dump 进生产库。

第四,Engine 的输出必须可解释。任何一次「为什么这么改」的回答,都应当能用 trace id + rubric score + before/after diff 三件套回放。否则团队会在两周内失去对 Engine 的信任,改写 PR 大量被沉默忽略。


延伸阅读与官方入口

把这几条入口对照着读,不难发现 LangSmith Engine 的定位非常克制:它不是下一只 agent,而是一层「自动化版本控制」,把 trace 里蕴含的改进信号翻译成 PR 与 diff,让团队依然握着合并权。它也因此最容易在两种团队里失效------一种是没有 trace 基础设施的团队,接进来发现没数据可喂,Engine 变成空转;另一种是完全没有 review 文化的团队,自动 diff 没人审,要么直接合并把线上改崩,要么直接废弃变成摆设。把这层边界看清楚,LangSmith Engine 才不会从「提效神器」变成「线上惊吓」。

可改进的两大类:procedural 与跨 run 持久化 memory

当 LangSmith Engine 这类 meta-agent 反推完 traces(追踪记录,即 Agent 每一次执行留下的完整步骤日志)之后,它产出的改写指令天然会落到两个完全不同的"地层"上:一类改的是 base procedural (基础流程性)资产------所有用户共享的 prompt、tool description、skill 模板;另一类改的是 跨 run 持久化 memory (跨运行持久化记忆)------只服务于特定 thread_id、特定 user_id 或特定收件人上下文的私有条目。这种二分法不是讲师拍脑袋想出来的分类标签,而是源自 LangGraph checkpointer(检查点机制,把每一步状态序列化保存的机制,见 langchain-ai.github.io/langgraph/c...%E4%B8%8E) LangSmith Hub prompt registry(提示词注册中心)从一开始就被设计成两套彼此独立的写入路径:一个面向"仓库里的源码文件",一个面向"运行时数据库里的键值对"。

一、两类的边界到底在哪里

Procedural 类改写的目标物,在工程语义上等价于"任何对 Agent 启动时静态加载的资产"的修改。典型代表包括:

  • 系统 prompt(system prompt):比如"你是一位严谨的代码审查员"这种全局人设
  • tool description(工具描述):LangChain 的 tool schema 里那个告诉模型"何时调用我、调用我之后会拿到什么"的 description 字段
  • skill / sub-agent prompt:Deep Agents 体系下子 Agent 的提示词模板
  • rubric 评分标准文档:Goal/Verification 环路里 grader agent 拿来打分的 rubric 文件

而 memory 类改写的目标物,则是 LangGraph checkpointer 写入的状态字段,或者外挂在 LangGraph Store 上的 cross-thread memory store(跨线程记忆库)。它的特征是:必须绑定一个 context key------可能是 thread_id(同一会话)、user_id(同一用户),也可能是更细粒度的语义键,例如"收件人 = 老板"。

把这两类放在一张表里,差异立刻变得直观:

维度 Procedural 类(流程性) Memory 类(跨 run 持久化)
存储位置 Git 仓库里的源码文件 / LangSmith Hub 上的 prompt commit LangGraph checkpointer(运行时状态)或 LangGraph Store(跨线程键值库)
改写权限 需要走 PR / code review,任何人改动都影响全员 仅 meta-agent 在带特定 context key 的 trace 上写,影响范围被上下文隔离
回滚粒度 Git commit hash 级别,可整段 prompt 回退到上一版 单条 key/value 可独立删除或覆写,不影响其他上下文
生效时机 下一次 Agent cold start(冷启动,即重新加载进程后)才生效 写入即对当前及后续同 context 的 run 生效
审计方式 通过 git log / PR review 通过 LangSmith trace 上附带的 memory write 事件
典型场景 tool description 优化、grader rubric 改写 邮件助理对不同收件人的语气偏好

二、代码层面的最小可运行示例

下面这两段伪代码演示了 LangSmith Engine 一次反推完成后,两种改写落地时的差异。请注意它们调用的是同一个 propose_change 接口,但落地的 storage adapter 完全不一样:

ini 复制代码
from langsmith_engine import propose_change, ProceduralAdapter, MemoryAdapter

# 场景 1:procedural 类改写 ------ 优化某个 tool 的 description
propose_change(
    adapter=ProceduralAdapter(repo="acme/email-assistant"),
    target="tools/send_email.tool.json",
    diff={
        "description.before": "send an email",
        "description.after": "send an email with subject, body, and optional CC",
    },
    evidence_trace_ids=["trace-001", "trace-002"],
)

# 场景 2:memory 类改写 ------ 为特定收件人沉淀语气偏好
propose_change(
    adapter=MemoryAdapter(store_url="postgres://langgraph-store"),
    target=("user_pref", "tone", "recipient=ceo"),
    diff={
        "value.before": "neutral",
        "value.after": "concise, action-first, no pleasantries",
    },
    evidence_trace_ids=["trace-003"],
)

第一段改完之后,会被自动 commit 到 Git 仓库、跑一遍 PR check;第二段改完之后,只会往 PostgresSaver 那张 cross-thread memory 表里 INSERT 一行,不会触发任何 CI 流程。这种"同一入口、不同落地"的设计,正是为了让 meta-agent 不用关心下层是源码还是数据库------它只负责"产出 diff + 给出 evidence trace",而具体的写入由 adapter 决定。

三、实战中的取舍矩阵

把这套机制落到真实业务里,有一个非常关键的取舍:你希望这次改写是"普惠"还是"特供"? 下面这张对比矩阵能帮你判断:

  • vs 普惠所有人 :选 procedural。典型例子是 terminal coding agent(终端编码 Agent)发现自己调 run_shell 这个 tool 时频繁失败,meta-agent 反推出"应该是 description 没写清楚 working directory 的语义",于是改写 description 并 PR 进仓库------所有用户下次跑这个 Agent 都会受益。
  • vs 特供特定上下文:选 memory。典型例子是 email assistant(邮件助理)发现给 CEO 发邮件时,语气应该"concise, action-first, no pleasantries",但给同级同事发邮件时应该"friendly, 可以加 emoji"。这种偏好明显只对"recipient = CEO"这一个上下文成立,放进 procedural prompt 里反而会污染其他场景。
  • vs 既想沉淀又想普惠 :双写。讲师在课程里举过一个 docs writer agent(文档撰写 Agent)的例子------它既需要把"调用 search_docs 之前应该先看看用户给的 URL 是否已经在 context 里"这条经验写进 procedural prompt(普惠),又需要把"这个客户喜欢表格而非列表"这种私人口味写进 memory(特供)。一次反推产生两条 diff,分别走两条 adapter,这是完全合法的。

观察 从工程治理角度看,procedural 类改写天然适合走"PR + review"的人类把关流程,因为它影响面广、出错代价高;而 memory 类改写更适合"自动写 + 人工抽查"的轻治理模式,因为它的影响被 context key 天然 sandbox(沙箱隔离)住了。把这两类的 review policy 设计成同一种,要么过度限制导致 memory 写不进去,要么过度放任导致 procedural 误改全量回归,这是 Loop Engineering 落地时最常见的反模式。

数据 课程里给出的对比数据点是:一个典型的 terminal coding agent 在一周内,procedural 类改写平均只发生 1-2 次/月,而 memory 类改写可以达到数十次/天,数量级相差近 100 倍。这意味着如果两类的写入路径共用同一个慢速的 review pipeline,memory 写会被严重积压;反之,如果 procedural 走得过快,每周都会出现"误改 tool description 导致全量回归"的灾难。生产环境里通常的折中是:procedural 走 PR review + CI,人工 gate;memory 走异步 batch write,每天一次人工抽查 + 异常回滚。

四、为什么都从 LangSmith Engine 这一统一入口反推

很多工程师第一次接触这套体系时会困惑:既然两类改写落地位置差别这么大,为什么不让 meta-agent 也分裂成两个? 答案藏在 LangSmith Engine 的 contract(契约)设计里:它对外暴露的接口永远是"trace in → proposed diff out",至于 diff 落到 Git、Postgres、SQLite 还是 Redis,完全由 adapter 决定。这样的好处是:

  1. 统一的 evidence(证据)约束:每一个 diff 都必须挂上 evidence_trace_ids,无论它是 procedural 还是 memory。这让事后审计时,可以一站式问"这条记忆条目 / 这段 prompt 是基于哪几条 trace 反推出来的?",而不用去两套系统里分别查。
  2. 统一的回滚原语 :无论是 git revert 还是 DELETE FROM memory_store WHERE key = ...,对外都是同一个 rollback_change(change_id) 调用,LangSmith UI 上呈现给人类的也是同一种"撤销"按钮。
  3. 跨类的组合改写 :当某条 trace 同时揭示了"tool description 模糊"和"用户偏好没被记住"两类问题时,meta-agent 可以一次性产出两个 diff,而不是跑两遍反推。LangSmith Engine 的 batch propose API 正是为此而设计,详见 docs.smith.langchain.com/ 里的 tracing 与 dataset 章节。

但请注意,这套统一入口并不意味着存储位置也统一。入口可以抽象,落地必须显式------这也是为什么我们在表里单独列了"存储位置"这一行。一旦混淆这两层,就会出现"以为改了 prompt,其实写到了 memory 里"的灵异 bug,排查起来非常痛苦。

五、给团队的三条落地建议

第一条,先把 procedural 类的 PR 流程跑通 ,再考虑上 memory 自动化。前者复用的是工程师已经熟悉的 Git workflow,门槛低;后者需要额外部署 LangGraph Store(参见 github.com/langchain-a... 里的 harness 配置),并且要设计 context key 的命名规范。

第二条,给 memory 类改写加上 TTL(过期时间)与命中回放 。cross-run memory 最容易腐烂------用户的口味会变,如果一条"recipient = CEO 用 action-first 语气"的 memory 永远不被刷新,半年后反而成了负资产。LangSmith Engine 在写入 memory 时建议强制要求一个 expires_at 字段,过期由后台 job 自动清理。

第三条,两类改写必须共用同一份 evidence trace 的快照。换句话说,无论这条 diff 最终落到 Git 还是 Postgres,evidence_trace_ids 里指向的 trace 内容必须被 immutable(不可变)地保留至少 N 天。否则一旦 memory 类改写引发线上问题,你根本没有回溯"它当时是看了哪几条 trace 才决定这么写"的能力,只能靠猜。

把这两类分清楚,Self-improvement 环路才真正具备可治理性;否则它就只是一个会自己写 prompt、还会自己写记忆的"黑盒喷泉"------看起来在持续学习,实际上产出的全是噪声。

俄罗斯套娃:四种环路的任意嵌套模式

俄罗斯套娃的本质,不是要求你把所有环路都装上,而是给一个组合空间:四种环路任选、任意层叠、任意顺序。这种"组合爆炸"的背后其实有结构化的约束------Core(Agent 内核)永远是内层,Self-improvement 通常是外层,中间层可以自由拼装。换句话说,套娃模式允许你"挑你需要的那几层",而不是"套餐制必须全选"。这一点对资源受限的团队尤其重要:工程从来不是"叠 buff 越多越好",而是"每一层都要回答一个新的工程问题"。和传统单体架构相比,俄罗斯套娃的差异在于:单体把所有职责焊死在同一个函数里,改一处就要全量重测;套娃把职责切成可独立维度的中间件,每一层都可以被单测、被替换、被 A/B 测试。

观察 在真实工程里,出现频率最高的组合是 Event-driven 套 Verification 再套 Core。原因很直接:Event-driven 解决"什么时候触发",Verification 解决"做得好不好",Core 解决"具体怎么做",这三层语义正交、不打架。Self-improvement 几乎永远位于最外层,因为它要反推 traces(追踪记录,即 Agent 每一次执行留下的完整步骤日志),需要等内层跑完一个完整 run 才能产出改写指令。也就是说,Self-improvement 天然依赖下层的可观测性,而不是嵌套结构上的随意摆放。如果硬把 Self-improvement 塞到 Core 内层,逻辑上就出现"边跑边改自己 prompt"的悖论,LangSmith Engine 那一套 meta-agent 范式就失效了,因为改写指令还没来得及落盘,内层又跑出了新的 trace。

下面给一个最小骨架,展示在同一个 create_deep_agent 配置里同时挂 Event-driven middleware 与 Verification middleware 是什么样子。中间件并不是直接修改 agent 的源码,而是按调用顺序串联在请求/响应链路上,这一点对理解嵌套本质很关键------环路之间不是函数嵌套,而是"中间件责任链"上的位置先后。

ini 复制代码
from deepagents import create_deep_agent
from deepagents.middleware import (
    EventDrivenMiddleware,   # 事件触发:Slack / GitHub / Calendar / cron
    GoalMiddleware,          # /goal 注入 + 自动 grader
    RubricMiddleware,        # /rubric 细则注入
)

agent = create_deep_agent(
    model="claude-sonnet",
    tools=[search_docs, send_email, create_pr],
    middleware=[
        EventDrivenMiddleware(
            triggers=["slack_mention", "inbox_new_mail", "cron:0 9 * * 1"],
        ),
        GoalMiddleware(goal="/goal:每周一 9 点生成周报并发到团队频道"),
        RubricMiddleware(rubric="/rubric:含上周 OKR 完成度、阻塞项、下周计划"),
    ],
    checkpointer=PostgresSaver.from_conn_string(DATABASE_URL),
)

这个骨架里,EventDrivenMiddleware 负责把外部事件翻译成 agent 的一次新 run,GoalMiddleware 负责把目标串进 system prompt 并在 run 结束时调用 grader,RubricMiddleware 负责注入评分细则。三者都通过 Deep Agents 的中间件协议挂载,协议具体定义见官方文档 docs.langchain.com/oss/python/... ;官方仓库 github.com/langchain-a... 也提供了更多 middleware 的参考实现,例如 schedule middleware、memory middleware、human-in-the-loop middleware 都可以按同一接口扩展,意味着"加一层"实际操作上就是"加一个 middleware 实例"。

把四种环路摆开,组合总数其实远不止 6,但工程上"合法"的嵌套只有以下 6 种。这里的"合法"等价于:Core 必须在最内层、Self-improvement 在最外层(若存在),其余按职责正交性排列。这样约束下,深度 1 到 3 层各覆盖一些典型场景,深度 4 的全四层嵌套单独拿出来讨论。

嵌套深度 组合形态 典型适用场景 工程复杂度
1 层 Core 一次性任务、demo、原型 极低
2 层 Core + Verification 输出可判定质量的批处理
2 层 Core + Event-driven 定时报告、webhook 处理器
3 层 Core + Verification + Event-driven 周期性高质量交付物 中高
2 层 Core + Self-improvement prompt 调优、tool description 迭代
3 层 Core + Verification + Self-improvement 长期稳定的生产级 agent

数据 课程的工程经验数据是:仅 Core 时,迭代效率的天花板被 prompt 一次性写死的质量锁住;引入 Self-improvement 后,改写频率可达每日数十次,但每次改写都要经过 LangSmith Engine 反推 traces 的开销,综合时延约 5 到 15 分钟/轮;而单 Core 嵌套的人工改写周期普遍在一周以上,意味着线上问题到修复的反馈环极长。Verification 层的引入会让单次 run 的失败率显著下降,因为 grader 会在 run 末尾拦住不合格输出,据经验数据可减少 30-50% 的无效 follow-up 调用,直接降低模型推理成本。

接下来看全四层嵌套与单 Core 嵌套的取舍。

维度 全四层 (Core + Verification + Event-driven + Self-improvement) 单 Core 嵌套
成功率上限 接近 95% 以上,因有 grader 拦截 + meta-agent 改写 取决于 prompt 一次性写入质量,通常 60-80%
单次改写成本 数百到数千美元/月(LangSmith Engine + 模型推理) 几乎为零
调试复杂度 高,需要同时读 trace、grader 输出、改写 diff 低,直接看 prompt
适用阶段 生产级 SLA(服务等级协议) 场景 早期验证、PoC(概念验证)
运维门槛 需要 traces 基础设施 + checkpointer + grader rubric 维护 单个 create_deep_agent 调用即可
失败定位 需区分 Core 失败、Verification 误判、Event-driven 漏触发、Self-improvement 改写回退 单一变量:prompt 本身

数据 上面这张表的关键取舍点在于"成功率上限"与"工程复杂度"几乎单调正相关:你堆的层越多,理论上能逼近的输出质量越高,但需要的可观测性、回滚机制、运维人力也都水涨船高。LangChain 团队的官方建议是按"先 Core、再 Verification、再 Event-driven、最后 Self-improvement"的顺序逐层加,不要一开始就把四层全开------否则一条失败 trace 会同时穿过四个抽象层,排查时不知道该看哪一层的日志,以及 Self-improvement 改写后回滚又会覆盖掉你刚加的人工补丁。LangChain 官方文档 python.langchain.com/docs/concep... 进一步把 Agent 的可插拔组件抽象成 model / tools / prompt / middleware 四件套,使环路嵌套的本质变成"在四个槽位里选一个或多个挂上去"。

回到这套范式之所以能成立的前提:Deep Agents 与 Dcode 都是开源项目,源码在 GitHub 公开,create_deep_agentcreate_agent 的中间件协议是同构的,你可以把任意一个 middleware 抽出来单跑、组合、替换。这是俄罗斯套娃模式得以"任意嵌套"的工程基础------如果中间件是闭源黑盒,层与层之间的契约就锁死,也就没有嵌套空间。具体持久化配置可参考 LangGraph 持久化概念页 langchain-ai.github.io/langgraph/c... ,里面的 checkpointer 抽象是跨 run memory 之所以能稳定生效的底层支撑,也是 Self-improvement 为什么要"等内层跑完一个完整 run"的物理基础。如果跳过 checkpointer,跨 run memory 就退化到单 run 内的 in-context 拼接,Self-improvement 也就无从判断"上一个版本 vs 这一个版本"。

在 Dcode 视角下,俄罗斯套娃被进一步具体化。Dcode 把 Verification 暴露成 /goal/rubric 两个 slash command------即在 agent 的 system prompt 里直接写 /goal:本周生成 OKR 复盘 这样的字面字符串,GoalMiddleware 在每次 run 启动时把它解析成结构化目标,再交给 grader 去验证。Dcode 与 Deep Agents 的关系是:前者是后者的一个垂直化封装,典型场景是终端编程;后者是通用 harness,典型场景是 docs writer agent、email assistant agent。两者共享同一套 middleware 协议,所以你可以把 Dcode 里写好的 GoalMiddleware 抽出来,直接挂到另一个 Deep Agent 实例上,组合成新的环路。

总结一下:俄罗斯套娃模式不是让你把四种环路全装上,而是给一个组合空间,你可以从最小的 Core + Verification 起步,逐层加上 Event-driven 与 Self-improvement。每加一层,都意味着新一组可观测性需求与新一种失败模式;每少一层,都意味着把某些职责让渡给人工。判断"该堆几层"的方法,不是看技术能不能做到,而是看你愿不愿意为那个 +5% 的成功率上限,承担对应的工程开销。

相关推荐
美摄科技1 小时前
美摄美颜特效SDK Skill:以AI赋能智能视音频新视界
人工智能
Web3_Daisy2 小时前
Pump.fun 与 FOMO 竞争背后的 Meme 市场变局
大数据·人工智能·金融·web3·区块链
AI创界者2 小时前
MiniMax-H3 本地一键部署整合包:8G 显存玩转文图生视频、视频参考、角色替换与超分补帧全流程
人工智能·深度学习
江畔柳前堤2 小时前
HBM:大语言模型时代的「算力血液」——从内存墙到带宽革命的深度拆解
服务器·人工智能·windows·目标检测·语言模型·自然语言处理·软件工程
美摄科技2 小时前
视频一键成片SDK Skill:AI智能分析与语义理解
人工智能
fthux3 小时前
装闭 RenoPit 源码解析(05):FastAPI与Celery如何执行AI装修分析
人工智能·ai·开源·github·open source·renopit
格数致用3 小时前
数据库设计与表结构详解|信息化项目全流程管理系统源码逐行精讲(五)
人工智能·政务·数据库设计·外键约束
罗西的思考4 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
DevUI团队4 小时前
从“即兴创作”到“规格先行”,华为云码道(CodeArts)代码智能体持续深耕企业级规范驱动开发能力
前端·人工智能·后端