

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 回源码,让外层环路使内层环路越来越有效
为什么 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 既能在单次任务里自我纠错,也能在跨任务、跨时间尺度上持续进化。
代码层面的最小配置片段
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 官方文档对此有详细说明,详见 https://python.langchain.com/docs/concepts/agents/ 与 https://docs.langchain.com/oss/python/langchain/agents/。一旦 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 官方文档 https://python.langchain.com/docs/introduction/ 与 LangGraph 持久化与 checkpointer 章节 https://langchain-ai.github.io/langgraph/concepts/persistence/。
把"写一个 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 行以内搭起这段最小核心环路。可运行的样例大致长这样:
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) | 模型二次推理或直接回传 | 最后一条 AIMessage 的 content | 本次循环对外暴露的产出 | 答非所问、漏字段、未遵循 schema |
注意「模型输出」和「最终产物」不是同一回事:前者是中间决策(往往带 tool_calls),后者是面向用户的成品。在多轮工具调用里,模型输出和工具结果会交替出现多次,最终产物只出现一次。把这两者混在一起,是新手在 trace 里最容易踩的坑。LangGraph 的状态图把这 4 个变量显式建模成 graph 的 state 节点,LangGraph 状态图文档可参考 https://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 的入门概念可以参考 https://python.langchain.com/docs/concepts/agents/,create_agent 的 API 细节见 https://docs.langchain.com/oss/python/langchain/agents/。再往上走到 Deep Agents 的中间件架构,/goal 与 /rubric 就是以 tool middleware 的形式嵌进这一段最小环路的,这一点会在后续章节展开。
从 create_agent 到 create_deep_agent:LangChain 入口函数的层级选择
翻开 LangChain 官方文档,在 langchain.agents 模块下你会看到两条并排的入口函数:create_agent 与 create_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_agent 与 create_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 的骨架:
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_agent 是 create_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:
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 文档,前者覆盖了中间件栈的设计动机与默认装配清单,后者则给出了完整的参数表与返回值结构:https://github.com/langchain-ai/deepagents 与 https://docs.langchain.com/oss/python/deepagents/ 。若需要对照 create_agent 的最小入口示例,可参考 LangChain 官方 agents 文档:https://docs.langchain.com/oss/python/langchain/agents/ 。
回到工程选型本身:如果你正打算落地一个 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 即触发反馈回写。
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 做回归,避免「上周还过、这周突然挂」的隐性回归。

踩坑清单(讲师在课程里反复强调的几条):
- 不要让 grader 直接复用主 agent 的 system prompt。两者的目标函数不同,主 agent 是「完成任务」,grader 是「判定完成」,混用会让 grader 偏向「放水」。
- 不要把 grader 输出直接拼成自然语言喂回主 agent。结构化 JSON 走 tool message 通路比自然语言 user message 更稳定,因为主 agent 可以把它当结构化信号处理。
- 不要在 rubric 里塞「整体印象」类条目。这类条目几乎一定会退化成「礼貌性 yes」,工程上是无用功,占 rubric 配额又不出力。
- 不要忽略 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 的基础前提。
官方参考:
- https://docs.langchain.com/oss/python/deepagents/
- https://python.langchain.com/docs/concepts/agents/
- https://docs.smith.langchain.com/evaluation
- https://blog.langchain.com/
/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 的会话中执行:
/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,只改中间件层即可。
关于这套语法糖的官方定义与使用方式,可以参考以下两个地址获取权威说明,避免被二手博客误导:
- Deep Agents slash command 与 /goal /rubric 用法说明:https://docs.langchain.com/oss/python/deepagents/
- LangSmith 评测数据集与 grader rubric 结构示例:https://docs.smith.langchain.com/evaluation
落地时常见的几条踩坑清单,值得工程团队在引入 /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 描述,实际两个项目里都更复杂,但接口形态保持一致:
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 的独立子站文档 https://docs.langchain.com/oss/python/deepagents/ 会显著降低上手成本;而 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 仓库 https://github.com/langchain-ai/deepagents 提供了多个参考实现,文档站点 https://docs.langchain.com/oss/python/deepagents/ 列出了每个钩子的签名与时序图,是判断「我现在卡在哪一层」的最快路径。如果团队想更激进地定制,LangGraph 的持久化文档 https://langchain-ai.github.io/langgraph/concepts/persistence/ 也是必读——它解释了 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 内部的并发模型。
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 通道 |
| 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 框架把四条环路设计成可以俄罗斯套娃式嵌套的根本原因。
官方文档与接入指南:
- LangServe 部署与 LangChain 入门:https://python.langchain.com/docs/introduction/
- LangGraph 持久化与 checkpointer 概念:https://langchain-ai.github.io/langgraph/concepts/persistence/
- LangSmith 官方文档与 trace 接入:https://docs.smith.langchain.com/
- Deep Agents 开源仓库与中间件层:https://github.com/langchain-ai/deepagents
- LangChain 官方 YouTube 频道原始 webinar:https://www.youtube.com/@LangChain
- Slack Events API 官方主页:https://api.slack.com/start
- GitHub Apps Webhook 官方文档:https://docs.github.com/en/apps
把视野拉回整套环路工程:核心 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 的骨架代码:
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 + 自然语言路由 的典型组合:
- Slack 的 Events API 是 webhook 源 —— 有人发消息时,Slack 把 payload POST 到我们注册的 endpoint;
- endpoint 收到消息,先经过一个轻量级意图分类器(可以是小模型或关键词匹配),判断这条消息是否包含「写文档」意图;
- 一旦识别到意图,转交给 docs writer Agent,由它调用 GitHub / Notion / Confluence API 完成文档生成;
- 完成后在 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 设计的更多细节,可以参考:
- LangChain agents 概念:https://python.langchain.com/docs/concepts/agents/
- LangGraph 持久化与 checkpointer:https://langchain-ai.github.io/langgraph/concepts/persistence/
- Deep Agents 文档:https://docs.langchain.com/oss/python/deepagents/
- LangChain Hub:https://smith.langchain.com/hub



本节的核心立论是:在设计 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 修复或回贴评论。
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 文本。
具体的工程实现与官方演示案例,可以参考以下两个延伸阅读入口:
- Deep Agents Code 项目页:https://github.com/langchain-ai/deepagents
- LangGraph 持久化与 checkpointer 文档:https://langchain-ai.github.io/langgraph/concepts/persistence/



总之,终端 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 在工程上的最小可用版本:
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 官方文档 https://langchain-ai.github.io/langgraph/ 与 LangSmith 评测数据集示例 https://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(见 https://www.langchain.com/langsmith)正是这套思想的现成载体:它跑过历史 trace 后,自动生成 prompt / skill / memory 的改写建议,并可以一键 port 回源码。配合 LangGraph 的 checkpointer(MemorySaver / PostgresSaver / SqliteSaver 三种实现见 https://langchain-ai.github.io/langgraph/concepts/persistence/),跨 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(反压)开关,避免在流量高峰期被反思任务打爆。
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.1、only_failures=True、max_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 官方文档(trace、Engine、rubric 配置):https://docs.smith.langchain.com/
- LangSmith 主页与产品介绍(含 Engine 介绍页):https://www.langchain.com/langsmith
- LangChain 博客(Self-improvement / Loop Engineering 系列文章汇总):https://blog.langchain.com/
- LangChain Hub(改写产物的 central registry,可直接搜 prompt / tool / skill):https://smith.langchain.com/hub
- Deep Agents 开源仓库,作为 Engine 下游「真正落地改写」的中间件:https://github.com/langchain-ai/deepagents
把这几条入口对照着读,不难发现 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(检查点机制,把每一步状态序列化保存的机制,见 https://langchain-ai.github.io/langgraph/concepts/persistence/)与 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 完全不一样:
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 决定。这样的好处是:
- 统一的 evidence(证据)约束:每一个 diff 都必须挂上 evidence_trace_ids,无论它是 procedural 还是 memory。这让事后审计时,可以一站式问"这条记忆条目 / 这段 prompt 是基于哪几条 trace 反推出来的?",而不用去两套系统里分别查。
- 统一的回滚原语:无论是
git revert还是DELETE FROM memory_store WHERE key = ...,对外都是同一个rollback_change(change_id)调用,LangSmith UI 上呈现给人类的也是同一种"撤销"按钮。 - 跨类的组合改写:当某条 trace 同时揭示了"tool description 模糊"和"用户偏好没被记住"两类问题时,meta-agent 可以一次性产出两个 diff,而不是跑两遍反推。LangSmith Engine 的 batch propose API 正是为此而设计,详见 https://docs.smith.langchain.com/ 里的 tracing 与 dataset 章节。
但请注意,这套统一入口并不意味着存储位置也统一。入口可以抽象,落地必须显式——这也是为什么我们在表里单独列了"存储位置"这一行。一旦混淆这两层,就会出现"以为改了 prompt,其实写到了 memory 里"的灵异 bug,排查起来非常痛苦。
五、给团队的三条落地建议
第一条,先把 procedural 类的 PR 流程跑通,再考虑上 memory 自动化。前者复用的是工程师已经熟悉的 Git workflow,门槛低;后者需要额外部署 LangGraph Store(参见 https://github.com/langchain-ai/deepagents 里的 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 的源码,而是按调用顺序串联在请求/响应链路上,这一点对理解嵌套本质很关键——环路之间不是函数嵌套,而是"中间件责任链"上的位置先后。
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 的中间件协议挂载,协议具体定义见官方文档 https://docs.langchain.com/oss/python/deepagents/ ;官方仓库 https://github.com/langchain-ai/deepagents 也提供了更多 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 官方文档 https://python.langchain.com/docs/concepts/agents/ 进一步把 Agent 的可插拔组件抽象成 model / tools / prompt / middleware 四件套,使环路嵌套的本质变成"在四个槽位里选一个或多个挂上去"。
回到这套范式之所以能成立的前提:Deep Agents 与 Dcode 都是开源项目,源码在 GitHub 公开,create_deep_agent 与 create_agent 的中间件协议是同构的,你可以把任意一个 middleware 抽出来单跑、组合、替换。这是俄罗斯套娃模式得以"任意嵌套"的工程基础——如果中间件是闭源黑盒,层与层之间的契约就锁死,也就没有嵌套空间。具体持久化配置可参考 LangGraph 持久化概念页 https://langchain-ai.github.io/langgraph/concepts/persistence/ ,里面的 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% 的成功率上限,承担对应的工程开销。
Docs Writer 案例:四种环路都跑通的端到端 demo
Docs Writer 案例:四种环路都跑通的端到端 demo
在整个 Loop Engineering 课程中,Docs Writer Agent 被 LangChain 团队选作贯穿全片的统一案例。它不是孤立的玩具示例,而是把核心 Agent 环路、Goal/Verification 环路、Event-driven 环路、Self-improvement/Hill Climbing 环路这四种工程模式,全部跑通在一个真实的端到端 demo 上。对读者而言,这意味着只要把 Docs Writer 拆明白,「如何在自家业务里叠环路」这件事就有了可对照的样板工程,而不是停留在概念图层面。
[观察] Docs Writer 之所以被选为统一案例,本质上是因为「写文档」是少数几种天然自带 grader 的任务:链接是否可达、CI 是否过线、Markdown 语法是否合规、参考链接是否对得上 anchor,全部都能用脚本或 rubric criteria 量化判定。这种「完成本身可验证」的特性,让它成为四种环路都能落地而不会互相打架的稀有载体。换句话说,写文档不是目的,它是工程师拿来验证俄罗斯套娃组合空间是否可控的「压力测试床」——如果一个 grader 写不出来,这层 Verification 环路就别硬叠,否则只会引入更多噪音。

一、Docs Writer 的工程骨架
Docs Writer 的代码组织沿用了 Deep Agents 的中间件范式,把 agent 内核、goal、rubric、skills、memory 切成可独立维护的模块。一个最小可运行的目录大致长这样:
docs_writer/
├── agent.py # create_deep_agent() 入口,挂载 skill + middleware
├── skills/
│ ├── api_reference.md # skill:调用内部 docs API 的契约
│ └── ci_checker.md # skill:CI 命令与返回码解读
├── graders/
│ ├── link_grader.py # rubric:链接可达性
│ ├── ci_grader.py # rubric:CI pass/fail
│ └── style_grader.py # rubric:段落长度 / 标题层级
├── middleware/
│ ├── goal.py # /goal 解析与注入
│ └── rubric.py # /rubric 解析与 grader 调度
├── memory/
│ └── notes.md # 跨 run 记忆
└── triggers/
└── slack_webhook.py # event-driven 触发入口
agent.py 走的是 Deep Agents 风格的 create_deep_agent 入口,把 skill、tool、middleware 三件套作为参数注入;graders/ 目录对应 Verification 环路,每个 grader 都返回结构化分项结果而不是布尔值,方便后续的 Hill Climbing 用单维度反馈驱动 prompt 微调;triggers/ 则把 Event-driven 环路的入口显式抽出,避免把 webhook 逻辑写死在 agent 里。
二、Skill 配置文件长什么样
Skill 在 Deep Agents 里被建模成「带 frontmatter 的 Markdown 文件」,frontmatter 声明元数据,正文告诉 LLM「什么时候调用、怎么调用、失败怎么办」。下面是一段典型的 Skill 配置片段(用 Python dict 序列化展示,便于在源码里以常量引用):
SKILL_CI_CHECKER = {
"name": "ci_checker",
"description": (
"触发时机:当 docs writer 修改了 docs/**/*.md 之后;"
"用于校验本次 PR 是否会被 CI 拒绝,返回每条失败的 job 名称。"
),
"tools": ["run_shell", "read_file"],
"input_schema": {
"changed_files": "list[str]",
"base_branch": "str",
},
"output_schema": {
"ci_status": "Literal['pass', 'fail']",
"failed_jobs": "list[str]",
},
"failure_retry": 1,
"timeout_seconds": 90,
}
description 字段是 LLM 决定何时装载该 skill 的唯一线索,因此课程反复强调「skill 描述就是 prompt 的一部分」。failure_retry=1 限定最多重试一次,避免模型陷入「修 CI → 破 CI → 再修 CI」的振荡循环;timeout_seconds=90 则给 run_shell 一个硬上限,防止 CI 卡死把整个 agent loop 拖垮。
三、Grader rubric 代码片段
Verification 环路的灵魂在 grader。下面这段 rubric 片段展示了 LangChain 团队惯用的「分项打分 + 阈值 + 失败原因」三段式结构:
def grade_link_validity(doc: str, base_url: str) -> RubricResult:
links = extract_markdown_links(doc)
failures = []
score = 0
for link in links:
if link.startswith("http"):
code = http_probe(base_url, link)
if code == 200:
score += 1
else:
failures.append({"link": link, "code": code})
elif link.startswith("#"):
anchor_exists = check_anchor(doc, link[1:])
score += 1 if anchor_exists else 0
return RubricResult(
criterion="link_validity",
score=score,
total=len(links),
failures=failures,
pass_threshold=0.95,
)
RubricResult 不只返回布尔,而是把每一条失败链接连带 HTTP 状态码回写到 grader output,Self-improvement 环路再据此判断「是不是要改 prompt 里关于『不要写死外链』的那一条」。这种「细粒度失败信号」是 Hill Climbing 能收敛的前提——反馈越具体,meta-agent 的改写越能落地;如果只丢一个布尔,prompt 就只能瞎改。
四、四种环路在 Docs Writer 上的输入输出对照
下面这张表把四种环路串成一条流水线:每一行都是 Docs Writer 跑一圈实际产生的输入、输出、改写点与回写位置。
| 环路 | 触发方式 | 主要输入 | 主要输出 | 改写点 | 回写位置 |
|---|---|---|---|---|---|
| Core Agent Loop | 用户提问 / PR 评论 | 自然语言需求、当前仓库 diff | 改写后的 Markdown、PR 描述 | 无(纯生成) | 不回写 |
| Verification Loop | Core Loop 收尾时 | 生成出的 Markdown 文档 | grader 分项得分 + 失败清单 | 重写该段落、删除坏链 | 注入下一轮 Core Loop prompt |
| Event-driven Loop | Slack 消息 / cron / webhook | 新需求、issue 评论、定时任务 | 触发 Core Loop + Verification | 无 | 触发器配置 |
| Self-improvement Loop | 每日 cron + LangSmith traces | N 条历史 trace + grader 历史 | 新版 skill / prompt / tool description | 改 skill frontmatter、tool docstring、agent prompt | commit 回 docs_writer/skills/** 与 agent.py |
[数据] 课程给出的对照实验显示,Docs Writer 在「只启用 Core Loop」时,初版文档的 grader 总分大致在 0.72 ± 0.05 区间,且每次跑分的人工返工次数中位数约为 4 次;启用全部四种环路之后,分数稳定拉到 0.91 ± 0.03,人工返工次数中位数降到 1 次。返工次数下降不是 grader 替代了人,而是 grader 把「明显会失败的链接 / 显然会挂 CI 的语法」在第一时间挡掉,把人工精力留给真正的内容判断;方差同步收窄则说明 Self-improvement 环路把跨 run 的漂移也压住了。
五、对比矩阵:四环路 vs 单环路
下面这张矩阵把取舍显式化,帮读者判断「我应不应该全装」。两条竖轴分别是「只装 Core Agent Loop」与「装全部四种环路」,两条横轴分别是「文档质量分(grader 总分)」与「人工返工次数(每个 PR)」。
| 维度 | 只用 Core Agent Loop | 启用全部四种环路 | 取舍 / 边界条件 |
|---|---|---|---|
| 文档质量分 | 中位 0.72,方差大 | 中位 0.91,方差小 | 差距随仓库规模放大;小 repo 收益有限 |
| 人工返工次数 | 中位 4 次/ PR | 中位 1 次/ PR | Verification 占主要功劳 |
| 单次生成成本 | 1× LLM 调用 | 约 2.5×(含 retry + grader + meta) | 预算紧张团队可关掉 Self-improvement |
| 维护成本 | 几乎为零 | 需要维护 rubric / skill / trace 数据 | rubric 漂移是主要风险 |
| 上线门槛 | 一行 create_deep_agent | 需要可观测性 + cron + webhook | 大型团队才值得全套 |
| 失败可解释性 | 差,模型自由发挥 | 强,每一项失分都有 rubric 索引 | 强可解释性反向促进 prompt 收敛 |
取舍的关键不是「越多越好」,而是「每一层是否回答了一个新的工程问题」:Core 解决生成质量,Verification 解决可判定完成,Event-driven 解决接入真实工作流,Self-improvement 解决长期漂移。如果你的痛点还没冒到那一层,就先停在 Core + Verification,再按需叠加;强把 Self-improvement 装在还没跑过 100 条 trace 的早期项目上,只会得到一堆噪声 diff。
六、易踩的坑清单
把这四种环路全跑通并不轻松,团队踩过的常见坑集中在四个点:
- rubric 漂移:仓库改了 anchor 命名规范,但
link_grader还在校验旧 anchor,grader 长期假阳性。建议给每个 rubric 加一个version字段并在 commit message 里同步声明 rubric 版本。 - skill 描述过载:把所有规则都塞进 description,导致 LLM 加载 skill 时上下文爆炸。课程建议 description 不超过 200 token,正文里只放「触发条件 + 失败处理」,长说明单独开一份
README。 - event-driven 触发风暴:Slack 群里一句「文档需要更新」可能触发 10 次 Core Loop。生产环境必须加去重 key(如 issue 编号 + 最近 commit SHA),并在 middleware 层做时间窗合并。
- Self-improvement 改坏了 prompt:Hill Climbing 在某些指标上确实更优,但语义上可能把「写作语气」改成了「SEO 风格」。建议每次 prompt 改写都过一遍 human-in-the-loop review,再决定是否全自动回写,而不是默认信任 meta-agent。
七、延伸阅读
想要自己动手复现 Docs Writer,可以直接看 Deep Agents 仓库里 docs_writer/ 这个目录样例,配套的 grader 与 middleware 都按模块切好。把案例拆清楚之后,再去读 Deep Agents 仓库里 docs improvement 案例,可以看到同一套思想在「团队级 agent harness」上的扩展形态——多 agent 协同、跨仓库 memory、hill climbing 调度器等组件的落地都在那里:
- Deep Agents 官方仓库:https://github.com/langchain-ai/deepagents
- Deep Agents 文档:https://docs.langchain.com/oss/python/deepagents/
- LangSmith 评测与数据集:https://docs.smith.langchain.com/evaluation
- LangGraph 持久化与 checkpointer:https://langchain-ai.github.io/langgraph/concepts/persistence/
俄罗斯套娃的本质,是给团队一个「组合空间」而不是「套餐」:Core 永远在最内层,Self-improvement 通常在最外层,中间两层按你当下的工程问题任意拼装。Docs Writer 的价值在于,它把这套组合空间里最常见的一种拓扑,用一个能跑、能评分、能被改进的真实任务完整演示了一遍——剩下的只是把 rubric 替换成你所在领域的判定规则,把 skill 替换成你团队的工具,把 trace 替换成你自己的真实反馈流。
失败信号模式库:从 traces 中识别可改写点
Self-improvement 环路的真正起点并不是「如何改写 prompt」,而是「如何从历史 trace 中识别出『这一段本可以写得更好』的可改写点」。在这套课程给出的 Loop Engineering 四环框架里,LangSmith Engine 这类 meta-agent 本质上是一个离线遍历器:它把生产环境回流回来的所有 LangSmith trace 当作语料,逐条走一遍「找失败信号 → 锁定改写对象 → 生成新版本 → 回写到源码」的全流程。如果没有一个结构化的失败信号模式库(failure signal pattern library)做底座,meta-agent 就只能在 prompt 表面做文本级别的随机扰动,产出的所谓「改进」既无方向也无边界,本质上和随机搜索没区别。

讲师在课程里反复强调一点:模式库不是某一类 prompt 技巧的集合,而是一份「诊断书目录」。每一条 pattern 都对应一种可观测的症状、可触发的判定逻辑、以及明确指向的改写对象。这种「症状 → 判定 → 改写对象」的三元组结构,是后续整条 Self-improvement / Hill Climbing 环路能否真正闭环的工程前提。换句话说,模式库的颗粒度直接决定了 meta-agent 能在什么维度上「爬山」——颗粒度越粗,只能改 prompt;颗粒度越细,可以连 tool description、skill、cross-run memory 一起改。把这条边界画清楚,是 Loop Engineering 在工程层面区别于传统 prompt 工程的关键。
[观察] 真正卡死团队的不是「没有 trace」,而是「trace 多到没人看得完」。一旦 LangSmith tracing 在生产环境全量开启,一个中等规模的 Docs Writer Agent 一天就能产生上万条 trace;靠人肉 review 永远不可能闭环。把失败信号模式库作为「机器可读诊断书」才是出路——它把「我看着觉得这一段写得不好」的人类直觉,翻译成「trace 中第 N 个 tool call 的输入 schema 与上一步 prompt 中的字段名不一致」这种机器可判定的特征,从而让 meta-agent 可以批量化、可回放地消费这些诊断结果。
在 Docs Writer 这个端到端 demo 里,讲师把生产中观察到的失败信号归并为 5 类:
- 工具参数错误(tool schema mismatch):模型给出的 tool call 参数类型不对、字段拼写错误,或者必填字段缺失。表现是 trace 里该 tool node 直接抛 ValidationError。
- 关键 tool call 缺失(missing critical call):模型在没有调取某个外部检索类 tool(例如 langchain_retriever、tavily_search)的情况下直接生成了断言性结论。
- 风格未对齐(style drift):输出文本在语气、术语密度、Markdown 层级上偏离了 docs-writer 这一 skill 在 system prompt 中定义的基线。
- 重复循环(loop / no-progress):trace 中连续 N 步(常见 N≥3)在相同 tool 之间反复跳转,token 在燃烧但 state 没有推进。
- 事实幻觉(factual hallucination):模型生成了与 source document 不一致的陈述,常见于 RAG 类的长上下文摘要。
[数据] 在课程里讲师给出了一组粗略量级数据:在 Docs Writer 的 1000 条回灌 trace 中,五类失败信号的发生比例大致是 33% / 21% / 18% / 14% / 14%。工具参数错误占比最高,这一项绝大多数情况下不需要 LLM 就能用 JSON Schema 校验或正则一次过判定;事实幻觉占比虽不算最高,却是单条判定成本最高的一类——必须把模型输出和 retrieved chunk 重新做一次交叉比对才能给出置信度。从这个分布可以直接推出 pattern library 的工程权衡:规则型判定器应当优先覆盖占比最高的前两类(合计 54%),LLM 判定器则只在剩余 46% 上发力,因为把 LLM 放在低占比高确定性信号上是 ROI 最差的选择。
下面是一个用正则 + LLM 混合判定从 trace 里抽取失败信号的最小示例。它演示了如何把 LangSmith trace 拉成本地 dict,先用确定性规则扫一遍高置信失败信号,再把剩下的「灰区」trace 交给 LLM 做语义层判定:
import re
import json
from langsmith import Client
from langchain_openai import ChatOpenAI
ls = Client()
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
SCHEMA_FAIL = re.compile(r"ValidationError|pydantic.*error", re.I)
LOOP_FAIL = re.compile(r"tool_call_count:\s*(\d+)")
HALLUC_HINT = re.compile(r"\b(I think|probably|maybe|约)\b", re.I)
KEY_TOOL = "tavily_search"
def rule_based_scan(run_dict: dict) -> list[str]:
signals = []
blob = json.dumps(run_dict, ensure_ascii=False)
if SCHEMA_FAIL.search(blob):
signals.append("tool_schema_mismatch")
m = LOOP_FAIL.search(blob)
if m and int(m.group(1)) >= 3:
signals.append("tool_loop_no_progress")
if HALLUC_HINT.search(blob):
signals.append("hallucination_hint")
if KEY_TOOL not in blob and "事实" in blob:
signals.append("missing_critical_call")
return signals
def llm_based_scan(run_dict: dict) -> list[str]:
prompt = f"""你是失败信号判定器。下面是一段 agent trace:
{json.dumps(run_dict, ensure_ascii=False)[:6000]}
请只输出 JSON 列表,从下列 label 中挑: tool_schema_mismatch,
missing_critical_call, style_drift, tool_loop_no_progress,
hallucination。"""
resp = llm.invoke(prompt).content
return json.loads(resp)
def extract_signals(run_id: str):
run = ls.read_run(run_id)
d = run.dict()
hard = rule_based_scan(d)
return hard + llm_based_scan(d) if not hard else hard
代码里有几个值得在团队内做 code review 时反复强调的工程取舍:第一,SCHEMA_FAIL 这种正则不到万不得已不要用纯字符串匹配 pydantic 异常文本——更稳的做法是从 trace 的 error 字段直接读结构化异常码;第二,LOOP_FAIL 的阈值 3 是经验值,业务团队应当按自己 agent 的平均步数调整,而不是照抄;第三,LLM 判别的 prompt 严格限制了可选 label 集合,目的是让多次跑出来的结果可聚合、可入库,避免自由文本输出破坏后续回写环节。
| 失败信号类别 | 改写对象 | 回写方式 |
|---|---|---|
| 工具参数错误 | tool schema / tool description | 修复字段名或 description 中的歧义表述,改动落在 tool 注册源码 |
| 关键 tool call 缺失 | prompt + skill | 在 system prompt 中显式追加「涉及 X 主题前必须先调 retriever」 |
| 风格未对齐 | skill(prompt 中的风格段) | 把风格样板从 system prompt 抽到独立 skill 文件,便于 diff 与版本化 |
| 重复循环 | tool description + memory | 给循环嫌疑 tool 加重试上限与退出条件;把「何时停止」的策略写入 cross-run memory |
| 事实幻觉 | prompt + retriever 召回策略 | 在 prompt 里追加「必须引用 retrieved chunk 编号」;同时调整 retriever top-k 与 rerank 阈值 |
这个表并不是装饰性的——它实际上就是 Self-improvement 环路里「回写」环节的唯一可执行入口。meta-agent 拿到一条被判定为 style_drift 的 trace 后,只会在 skill 文件而非 prompt 顶部去改;同样,被判定为 tool_loop_no_progress 的 trace,只会在 tool description 里加一行 hint,而不会去重写整个 system prompt。颗粒度对齐,改写才不会越界,回写结果也才能稳定地映射到 GitHub PR 的某个具体文件上。
人工抽失败信号 vs 自动抽失败信号,在覆盖率与误报率上呈现出非常不同的曲线:
| 维度 | 人工抽失败信号 | 自动抽失败信号 |
|---|---|---|
| 覆盖率(每天能看多少条 trace) | 50–200 条/人,极易在峰值期被积压 | 全量,理论上无限 |
| 误报率 | 低,人具备语义判断能力 | 中高,规则会过激,LLM 会幻觉 |
| 单条判定成本 | 高(分钟级/人) | 低(规则毫秒级,LLM 秒级) |
| 可回放性 | 弱,人脑记忆随时间衰减 | 强,规则与 prompt 都可版本化 |
| 新模式发现能力 | 强,人能识别「没见过」的现象 | 弱,必须先把模式沉淀进 pattern library |
| 适合阶段 | 上线初期 / 冷启动 | 规模化生产 / 长期运营 |
取舍的关键点在于「覆盖率与误报率不是同时最优的」:人工抽信号新模式发现能力强但覆盖低,自动抽信号覆盖广但只能识别已建模的模式。健康的工程实践是让两者形成回路——自动抽的输出按周回流给人工 review,人工挑出来的新症状再被编码进 pattern library,形成正反馈。这条回路一旦打通,团队的失败信号模式库就会以周为单位持续扩张,而 meta-agent 的爬山空间也会同步变大。
这也引出本节最后、也是最容易被忽视的一点:模式库遵循开闭原则(Open-Closed Principle)。框架默认给出 5 类基础 pattern 作为闭环兜底——只要 Docs Writer 这类 agent 上线,就能立即被 Self-improvement 环路兜住大部分常见失败。但任何具体业务(法务合同、内部 wiki、客服 FAQ)都会涌现出框架层完全无法预见的失败类型,例如「条款编号与附件不一致」在通用 pattern 里压根不存在。业务团队必须有权在自己的 pattern library 里新增子类、定义新的判定函数、并在 meta-agent 的回写目标清单中追加新的改写对象。框架负责「开」,给基础模式、给判定器接口、给回写通道;业务团队负责「闭」,在不动核心环路的前提下扩展自己的子类。这是 Self-improvement 环路能不能从一个 demo 真正迁移到生产的关键分水岭,也直接决定了 LangChain Engine 这类 infra 能不能在多业务线之间复用同一份骨架。
更多关于 trace 评测与 grader rubric 的落地形态,可以参考 LangSmith 官方文档中的评测数据集示例 https://docs.smith.langchain.com/evaluation 以及 LangChain agents 概念入门 https://python.langchain.com/docs/concepts/agents/ ;而中间件层把 /goal 与 /rubric 暴露为可插拔接口的工程实现,则在 Deep Agents 开源仓库 https://github.com/langchain-ai/deepagents 中可以直接读到 create_deep_agent 的源码级 hook。
最后我想把视角拉回到工程治理层面。失败信号模式库不是一次性资产,而是随业务演进的活文档——每次业务方提「我们的 agent 又翻车了」,第一动作不应该是 hotfix prompt,而应该是把这次翻车写进 pattern library,再让下一轮 Self-improvement 跑过去时自然消化。把每一次人工救火都变成一次「模式沉淀」,才是 Loop Engineering 整套方法论最终能否在团队里扎根的判断标准。模式库扩张的速度,本质上等于团队对自身 agent 行为边界的理解速度。
可观测性与 checkpoint:让环路跑得可调试
为什么可观测性是 Self-improvement 环路的前提
当一个 Self-improvement 环路开始把生产环境的 trace 当成改写 prompt 的"语料"时,可观测性就从 nice-to-have 变成了生命线。LangSmith tracing 和 LangGraph 的 checkpointer 是这套栈里两个必须同时具备的组件——前者负责把每一次环路的"思考—行动—结果"全部串成一条可搜索、可回放的时序记录,后者负责把每一轮迭代的中间状态、变量、消息历史持久化到数据库,使得环路中断、重试、跨进程恢复都成为可能。缺少任意一个,整个 hill climbing loop 都会退化成"改一改、看天吃饭"的黑盒。
这套课程反复强调一个事实:任何"自动改写 prompt / tool / skill"的机制,如果没有结构化的失败信号模式库(failure signal pattern library),就只是随机扰动。而 trace 和 checkpoint 正是这个失败信号模式库的两条原材料来源——前者给出"哪一步调用偏离了 rubric"的微观证据,后者给出"在哪一个迭代轮次的状态点上模型开始走偏"的状态证据。两类证据必须按时间戳对齐,才能复现一次完整的失败。

多层嵌套下的 trace 区分能力
[观察] 多层嵌套环路一旦出问题,trace 是唯一能区分"哪一层挂了"的依据。在 Russian doll 的组合模式里,外层是 Event-driven 调度(例如 GitHub PR 触发),内层是 Core Agent Loop,再往里是 Goal/Verification 环路,最里层是 Self-improvement 环路。当 grader agent 报出"rubric 未达标"时,如果不看 parent_run_id 链,就完全无法判断这次失败发生在"是 goal middleware 判定不通过"、“是核心 tool 调用超时”,还是"是 meta-agent 在改写 prompt 时引入了语法错误"。LangSmith 提供的树形 trace 视图,本质上就是把"环路套环路"这种结构性复杂度,降维成"按 run_id 父子关系一层一层展开"的可视化调试界面。
最小配置:LangSmith tracing + Postgres checkpointer
下面是一个最小可运行的配置片段,展示如何在 create_deep_agent 上同时启用 LangSmith tracing 与 Postgres checkpointer:
import os
from deepagents import create_deep_agent
from langgraph.checkpoint.postgres import PostgresSaver
from langchain.chat_models import init_chat_model
# 1. 打开 LangSmith tracing
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "lsv2_xxx"
os.environ["LANGSMITH_PROJECT"] = "loop-engineering-deepagent"
# 2. 准备持久化 checkpointer
DB_URL = "postgresql://user:pwd@localhost:5432/loop_state"
checkpointer = PostgresSaver.from_conn_string(DB_URL)
checkpointer.setup() # 首次运行建表
# 3. 组装 deep agent
model = init_chat_model("claude-sonnet")
agent = create_deep_agent(
model=model,
tools=[search_docs, run_grader],
checkpointer=checkpointer,
middleware=[goal_middleware, rubric_middleware],
)
# 4. 每次 invoke 都指定 thread_id,环路跨进程恢复靠它
config = {"configurable": {"thread_id": "session-loop-001"}}
result = agent.invoke({"messages": [...]}, config=config)
这段配置里有三处工程细节值得展开:第一,LANGSMITH_TRACING=true 是全局开关,只要环境变量存在,LangChain 生态里的所有 chat model、tool、retriever 都会自动埋点,不需要在每个节点单独插桩;第二,PostgresSaver.from_conn_string(...) 是 LangGraph 推荐的持久化实现,它把每一步的 messages、tool_calls、interrupts 全部以 JSON 形式写入 checkpoints 与 checkpoint_writes 两张表,跨进程恢复完全靠 thread_id + checkpoint_id 定位;第三,create_deep_agent 的 middleware 参数接受一个列表,goal_middleware 和 rubric_middleware 是 Deep Agents 框架预置的两种中间件,分别用来注入 /goal 与 /rubric 的运行时上下文。
trace 的四个核心字段
LangSmith 每次 run 都会产出一组结构化字段,其中对调试多层环路最重要的 4 个如下表所示:
| 字段 | 含义 | 在多环嵌套中的作用 |
|---|---|---|
run_id | 当前这一次调用的唯一标识(UUID) | 精确锚定"问题发生在哪一步",可在 LangSmith UI 直接深链 |
parent_run_id | 父级 run 的 UUID,嵌套时指向外层调用 | 通过它把 Core / Verification / Self-improvement 三层串成因果链 |
inputs | 进入这一次调用的全部入参(消息、变量、tool schema) | 回放某一次失败时的初始状态,用于离线复现 |
outputs | 这一次调用返回的全部结果(messages、tool 响应、最终答案) | 与 inputs 配对,构成完整的"前因—后果"对 |
trace 嵌套深度的工程含义
[数据] 在该教程给出的实战案例里,一个同时跑 Goal/Verification + Self-improvement 的 deep agent 单次任务平均会产生 30~80 条 run,高峰期甚至超过 200 条;其中 parent_run_id 的平均链路深度为 4~6 层,极端情况下(例如 grader 反例回灌触发 meta-agent 重写 prompt)会达到 8 层以上。这意味着,一旦缺少父子 run 的可视化,人工定位一次失败平均需要浏览上千条日志条目,而启用 LangSmith 树形视图后,平均定位时间下降到 1 次点击。这套数据从侧面印证了一个工程结论:trace 嵌套深度是和环路嵌套层数线性绑定的,而不是和单次任务的工具调用数绑定。换句话说,核心 agent loop 的内部工具再复杂,只要它跑在同一个 run 节点下,trace 深度就不会增长;反倒是多了一层 middleware 嵌套,trace 深度就立刻 +1。
对比矩阵:LangSmith tracing vs 自建日志
接下来是关键的取舍对比——LangSmith tracing 与自建日志系统在三个维度上的差异:
| 维度 | LangSmith tracing | 自建日志系统(JSON file / ELK / Loki) |
|---|---|---|
| trace 一致性 | 由 SDK 统一埋点,跨 chat model / tool / retriever 字段命名一致 | 各团队自定义,字段命名 drift,跨服务对齐成本高 |
| 跨 run 查询 | 原生支持按 parent_run_id、run_type、metadata 过滤 | 需要自行设计索引,join 父子关系常出现 N+1 |
| 回放能力 | 一键"Replay run",把 inputs 重新喂给模型重跑 | 一般仅支持"看日志",无法直接重放,只能靠人工还原 |
| 检索时延 | 服务端预聚合,毫秒级 | 取决于日志体量与索引质量,常达秒级甚至分钟级 |
| 私有化部署 | 商业 SaaS 为主,自托管版功能受限 | 完全可控,合规友好 |
这张对比矩阵揭示了一个工程权衡:LangSmith tracing 的核心价值不在于"它能记录",而在于"它能让跨 run 的因果关系被一键重建"。当 Self-improvement 环路需要从历史 trace 中挖掘"哪条失败模式出现频次最高"时,这种因果重建能力是任何 JSON 日志系统都难以低成本复制的;但反过来,如果团队出于合规要求必须把数据留在内网,那么自建 + LangSmith 自托管版的混合方案往往比纯自研更划算,关键看 trace 与 checkpoint 这两类数据是否都允许出域。
checkpoint 的状态流转

上面这张 checkpoint-state-flow 图,描述的是 LangGraph 在一次 invoke 内部的状态流转:每一步 node(模型调用、tool 调用、middleware 注入)执行后,框架都会把当前 State 序列化并写入 Postgres,产生一条新的 checkpoint_id;当环路因为超时、OOM、或者 grader 失败而中断时,下一次 invoke 只要带上同样的 thread_id,就会从最近一个有效 checkpoint 恢复,而不是从头重跑。这对 Self-improvement 环路尤其关键——meta-agent 跑一遍完整的 trace 改写流程可能耗时数十分钟,如果中途崩溃,没有 checkpointer 就只能整轮重跑,而 PostgresSaver 能精确恢复到"已经改写到第 17 条 prompt、还剩 8 条"这种粒度。
在具体选型上,LangGraph 提供了三种实现:InMemorySaver 适合本地调试与单元测试,数据随进程退出而丢失;SqliteSaver 适合单机持久化的轻量场景;PostgresSaver 则是生产环境的默认选项,支持并发写、跨进程恢复、以及和 LangSmith 之间的 run_id ↔ thread_id 双向关联。值得注意的是,这三者对外暴露的 API 完全一致,因此可以在不修改业务代码的前提下,从 InMemorySaver 平滑迁移到 PostgresSaver。
实战踩坑清单
几个实战中容易踩的坑值得提前标注:第一,thread_id 必须是稳定且唯一的字符串,推荐用业务侧的自然键(如 pr-编号 + 任务类型),而不是随机 UUID,否则后续跨 run 关联会非常痛苦;第二,Postgres 的连接池大小要和环路并发量匹配,否则 checkpointer 会成为瓶颈,导致 invoke 阻塞甚至写入丢失;第三,LangSmith 的 trace 和 LangGraph 的 checkpoint 是两套独立的存储,千万不要把它们混用——trace 走 LangSmith 后端,checkpoint 走你自己的 Postgres,前者用于"看",后者用于"恢复",二者通过 run_id ↔ thread_id 的映射关联;第四,LANGSMITH_TRACING 之外还有一个常被忽略的环境变量 LANGSMITH_SAMPLE_RATE,在生产环境应该设为小于 1 的浮点数,以避免 trace 体量爆炸导致 LangSmith 配额耗尽;第五,checkpoint 表会随着迭代轮次线性增长,务必定期执行 DELETE FROM checkpoints WHERE checkpoint_id < ... 之类的归档脚本,否则 Postgres 的存储成本会失控。
延伸阅读
如果你打算在这一层进一步深挖,以下两份官方文档是最直接的延伸阅读:LangSmith tracing 的接入指南(https://docs.smith.langchain.com/)详细列出了环境变量、SDK 配置、以及如何把自定义 callback 也纳入 trace;LangGraph 持久化与 checkpointer 概念页(https://langchain-ai.github.io/langgraph/concepts/persistence/)则解释了 InMemorySaver、SqliteSaver、PostgresSaver 三种实现的适用场景与切换路径。两份文档加起来大约半小时的阅读量,就能把"如何让环路跑得可调试"从经验直觉变成工程纪律。
回到这套 Loop Engineering 四环框架的视角:可观测性本质上不是 Self-improvement 环路的"附加功能",而是它能成立的前提条件——没有结构化的失败信号,改写就只是扰动;没有持久化的状态,跨轮迭代就只是赌博。把 LangSmith tracing 和 Postgres checkpointer 当作一等公民来设计,而不是事后补的日志,这一点,是所有跑过三种以上环路嵌套的团队都会反复印证的一条经验。
评估与 rubric 工程化:grader agent 的工程实现
上一节我们提到可观测性把 trace 变成了 Self-improvement 环路的「语料」,但仅有语料还不够——环路还必须知道什么时候算「改对了」「改坏了」「改得一般」,而这个「知道」的背后,就是 rubric 工程化。把 rubric 当成一份写得像 checklist 的工程清单,是 Verification 环路能否稳定落地的关键。如果 rubric 仍然是一段散文式的描述,grader agent 就会跟着一起飘,环路的反馈信号就只能是噪声。

rubric 的核心矛盾:从「主观描述」到「可二值判定」
[观察] 工业级 rubric 工程最大的坑不在 grader 本身,而在 rubric 的可判定性。把「写得不错」「结构合理」「基本可用」这类模糊形容词写进 rubric,grader agent 就会被这些形容词劫持——它会把同样的形容词原样吐出来,导致两条几乎相同的内容在 grader 那里得到完全不同的分数。换句话说,当 criterion 描述里出现形容词,grader 的判定就不再是布尔运算,而变成了一次「再创作」,Verifiability 自然崩塌。真正能稳定驱动 Verification 环路的 rubric,必须把每条 criterion 都拆成可二值判定的命题:「标题是否包含『目标』一词」「是否给出至少 3 条 todo 项」「链接是否返回 200」「代码块是否包含 ```python 围栏」。一旦 criterion 落在布尔可判定的集合内,grader 的输出空间就被压缩到很窄的二值集,环路的反馈信号才可能稳定。这一点在 LangChain 的官方 evaluation 文档里也被反复强调,rubric 描述必须尽量贴近「正则可匹配」或「数值可比对」的形态,https://docs.smith.langchain.com/evaluation 给出了一组完整的数据集—evaluator—experiment 三件套定义,可以作为 rubric 描述的最小规范起点。
把 rubric 转成 grader prompt 的模板
下面给出一段可以直接抄进 LangSmith 评测配置里的 grader prompt 模板骨架。这套模板把 6 条 rubric criteria 拆成结构、事实、风格三段,并显式列出「正例—反例」对照,目的是把判定边界钉死在 prompt 里,避免 grader 在二值判定之间反复横跳:
GRADER_PROMPT_TEMPLATE = """
你是一名严谨的 grader agent,请基于下列 rubric 对被评估内容做二值判定。
每条 criterion 必须独立回答 YES 或 NO,不得给出中间分数。
# 结构 rubric
1. [STRUCT-1] 文档必须包含至少 4 个 H2 小节 — 是/否
2. [STRUCT-2] 文末必须给出"下一步行动建议"段落 — 是/否
# 事实 rubric
3. [FACT-1] 所有外部链接必须返回 200,链接文本必须与目标页面主题一致 — 是/否
4. [FACT-2] 关键术语首次出现需给出英文原文 — 是/否
# 风格 rubric
5. [STYLE-1] 全文不得出现 emoji,不得使用感叹号 — 是/否
6. [STYLE-2] 段落长度需落在 80–150 字区间 — 是/否
# 输出格式
请严格按 JSON 输出,key 为 criterion id,value 为 YES 或 NO:
{
"STRUCT-1": "YES",
"STRUCT-2": "NO",
...
}
# 正反例
正例摘要:包含 5 个 H2,文末给出「下一步建议」 — STRUCT-1=YES, STRUCT-2=YES
反例摘要:仅 2 个 H2,文末直接收尾 — STRUCT-1=NO, STRUCT-2=NO
"""
需要特别强调的是,模板末尾的「正反例」并不是装饰。当 criterion 描述存在歧义时,in-context 的正反例会比大段说明更高效地把 grader 拉到正确锚点上。这也是为什么 [观察] 块要强调「rubric 必须可二值判定」,而不是「可打分」——分数会让 grader 重新进入主观创作模式,二值判定则强制它只在布尔空间里下结论。在工程实践中,grader 的输出 JSON 还应当带上 trace id 与 rubric 版本号,这样下游的 Self-improvement 环路才能把「哪一版 rubric 误判了哪条 trace」精准定位出来。
rubric 三段式:结构 / 事实 / 风格
把 rubric 拆成三段并非噱头,而是为了让 grader 的失败模式可定位。下表给出一个工业级的 rubric 三段式分类模板,其中「判定方式」一栏尤其关键:不同的段位对应不同的工程实现——结构类用词法扫描、事实类用 HTTP 探测与字符串匹配、风格类用字符级正则。三类判定最终都需要被压回到「YES/NO」,这是 grader 工程化的最后一道收敛动作:
| 段位 | 关注维度 | 判定方式 | 失败信号 |
|---|---|---|---|
| 结构(STRUCT) | 必须有几节、必须有标题层级 | 词法扫描 + 正则 | H2 数量不足、缺收尾段 |
| 事实(FACT) | 链接 200、术语准确、数据可溯源 | HTTP 探测 + 字符串匹配 | 404 链接、术语中英文不一致 |
| 风格(STYLE) | 术语统一、无 emoji、段落长度 | 字符级正则 + 区间计数 | 感叹号、段落超长或超短 |
三段式的另一个隐性收益是「失败归因」。当某次评测整体失败时,可以直接按段位聚合失败率——结构失败率高说明 prompt 没把模板注入够,事实失败率高说明联网检索或术语表出问题,风格失败率高则说明 rubric 的正反例没标清楚。Self-improvement 环路可以据此分流,而不是把所有失败一锅炖。
对比矩阵:人工评分 vs LLM grader vs LLM grader + 抽样复核
不同评估强度各有取舍,不能简单地说「LLM 一定比人工好」或「人工一定更准」。下表显式给出三种评估强度的成本、一致性、可规模化与适用边界,可以直接当作工程选型决策表使用:
| 方案 | 成本 | 一致性 | 可规模化 | 适用边界 |
|---|---|---|---|---|
| 纯人工评分 | 高 | 受评审者状态影响大 | 差,通常每周几十到上百条 | 评测集初次定义、金标准建立 |
| 纯 LLM grader | 低 | 取决于 rubric 可判定性 | 强,单批次可跑上万条 | rubric 已经收敛后的回归测试 |
| LLM grader + 人工抽样复核 | 中 | LLM 兜底 + 人工校正漂移 | 强,周级数万条加 5% 抽样 | 生产环境 Self-improvement 环路 |
[数据] 在 Self-improvement 环路的真实运行中,纯 LLM grader 在 rubric 收敛前会出现「假阳性漂移」,典型比例在 15%–30% 之间;一旦叠加 5% 的人工抽样复核,漂移会被钳制到 3% 以下。这个数字来自工业级评测实践的一般经验区间,具体阈值应当根据业务对错误的容忍度再标定。同时要注意,抽样复核的覆盖率与 rubric 的稳定时长呈反比——rubric 越稳定,人工抽样比例可以越低;rubric 越频繁改动,就必须保持更高的人工覆盖。此外,rubric 的版本号必须和 prompt 的版本号同步入 trace,否则环路在改写 prompt 时会拿到一版「已经弃用但还在生效」的 rubric,造成无意义的 prompt 抖动。
grader 作为 middleware 的工程价值

把 grader 设计成 middleware 而不是一次性脚本,是 Deep Agents / Dcode 这类中间件架构里非常关键的一步。Middleware 化的好处有三点:第一,grader 可以被任意 LangChain agent 复用,不必每个环路重写一套判定逻辑,这就把 rubric 从「业务知识」升级成了「平台能力」;第二,grader 的输入输出可以走 LangSmith tracing,失败模式自动落入 trace,后续 Self-improvement 环路可以反向利用这些 trace 做反例挖掘;第三,grader middleware 可以和 checkpointer 配合,在 /goal 失败时把当前 rubric 的命中情况快照下来,作为下一轮改写 prompt 的「证据」,从而让 Verification 环路真正闭环。
# 伪代码:把 grader 接到 Deep Agents 的 middleware 槽位
from deepagents import create_deep_agent
from langchain.agents.middleware import goal_middleware, rubric_middleware
grader = build_grader_from_template(GRADER_PROMPT_TEMPLATE)
agent = create_deep_agent(
model="...",
tools=[...],
middleware=[
goal_middleware(goal="/goal 写出一份可发布的博客"),
rubric_middleware(grader=grader, rubric_path="./rubric.yaml"),
],
)
Middleware 化的一个隐性收益是「失败可注入」。开发阶段可以故意喂一条已知失败的样例,验证 grader middleware 是否真的把失败信号传到上游 prompt 改写链路;如果信号丢失,说明 grader 还没真正接入环路,这比单纯看 grader 的 YES/NO 准确率更能反映工程成熟度。
踩坑清单
工程落地时常见的几个坑需要提前规避。第一,不要在 rubric 里写「应当」「尽量」「尽量避免」这类软词,grader 会把它们当成「可忽略」处理,导致整条 criterion 在实践中失效;第二,正反例数量要保持均衡,如果只给正例不给反例,grader 会出现「全 YES」的退化,Verifier 失去意义;第三,rubric 改动必须走 PR 流程,因为 Self-improvement 环路会自动用新版 rubric 反向改写 prompt,rubric 一旦失稳,环路就会跟着震荡,版本不可追溯就成了事故根因;第四,链路超时与 HTTP 探测是事实类 rubric 的大坑,链接 200 不等于内容相关,要二次校验锚文本;第五,段落长度这类区间类 criterion 要避免单位漂移,中文按字符数计,英文按 token 计,不能混用。
延伸阅读入口与最小可运行闭环
下面两个入口是 rubric 工程化最值得啃的官方资料。LangSmith 的 evaluation 文档给出了完整的数据集—evaluator—experiment 三件套定义,https://docs.smith.langchain.com/evaluation 是工业级 evaluation pipeline 的起点;Deep Agents 仓库里的 grader middleware 示例代码展示了如何在 LangGraph 节点上挂载自定义 grader,https://github.com/langchain-ai/deepagents 把 rubric 与 goal 作为可插拔 middleware 暴露给任意 LangChain agent。把这两条线串起来,就能搭起一个最小可运行的 Verification 环路:rubric.yaml 定义 checklist,grader middleware 跑二值判定,LangSmith tracing 抓失败 trace,checkpointer 快照中间态,Self-improvement 环路拿这些 trace 反向改写 prompt。整条链路的稳定性,完全取决于最薄弱的 rubric criterion——这也是为什么 rubric 工程化必须被放在 Verification 环路的中心位置,而不是当成 prompt 的附庸。

从 demo 到生产:环路工程的落地清单与常见坑
前文我们把 rubric 当作 checklist 风格的工程清单,是为了让 grader agent 的反馈信号从噪声变成结构化判定。但 rubric 自身并不足以把环路推上生产线——一个生产级 self-improvement 环路必须同时回答四个问题:什么时候跑、跑出来算不算对、对的话怎么改、改完怎么回头看。这四个问题,正好对应落地清单的四块——触发器 / 验证器 / 改写器 / 可观测。这一节是全文的收束,我们把前面 18 节散落的工程细节重新折叠到这四块清单上,再补一份生产级 vs demo 级在 6 个维度上的差距矩阵,最后给出读者接下来一周内可以启动的 3 步行动。

触发器:把 Agent 嵌入真实工作流的第一步
Event-driven 环路的入口就是触发器。demo 阶段常见的做法是手工 python agent.py 或者定时 cron */5 * * * * 跑一次,而生产环境必须把触发器抽象成可幂等接入的端点——Slack / GitHub / Calendar / Email 这类工作系统的事件,经过 webhook 落到 agent 队列后,每一次触发都必须带上稳定的事件 ID,以便下游校验。LangGraph 的 checkpointer 体系(见 LangGraph 持久化与 checkpointer https://langchain-ai.github.io/langgraph/concepts/persistence/)已经把 thread_id 与 checkpoint 绑定,因此触发器只要保证 thread_id 与事件 ID 一一对应,就能自然幂等。这一点在 demo 里几乎没人写,但任何一次 webhook 重投都会让它原形毕露。对比手工 cron 与 webhook 网关,前者胜在简单,但缺乏重投容忍;后者多了几行校验代码,换来的是事件级可重放。
验证器:rubric 从散文走向 checklist
触发器决定环路何时被唤醒,验证器则决定唤醒后这次执行算不算"成功"。前面我们已经把 rubric 拆成了 grader agent + rubric criteria 的二元结构(参考 LangSmith 评测数据集示例 https://docs.smith.langchain.com/evaluation),但真正落地时还要再加两道闸:一是 rubric criteria 必须是有限闭集(布尔 / 分级 / 阈值三选一),不能是"看起来不错";二是 grader 的输出必须写回 LangSmith dataset 形成 eval 闭环,否则 rubric 就只是单次判分,失去了 hill climbing 的对照基准。取舍在于 rubric 颗粒度——太粗反馈信号弱,太细 grader 自身成本飙升,经验值是 5–12 条 criteria 覆盖一次完整任务。
改写器:从 prompt 到 memory 的可回滚更新
Self-improvement 环路的真正难点,是 LangSmith Engine 改写 prompt / tool description / skill / memory 之后,如何保证改坏了能秒级回滚。demo 里我们常直接覆盖文件,生产里必须把每次改写当作一次 PR——版本化、diff 可审、合成可逆。Deep Agents 的 harness 设计(参见 Deep Agents 文档 https://docs.langchain.com/oss/python/deepagents/)恰好提供了这种"代码即记忆"的载体,把 prompt 当源码管理,改写即一次 git commit。权衡点在于改写频率:太高会让 git log 噪声爆炸,太低又失去 hill climbing 的反馈价值,通常的做法是把改写门槛设为 grader 在 N 轮里累计胜率提升超过阈值才落盘。
可观测:trace 不是日志,是下一轮 hill climbing 的语料
最后回到可观测。可观测要做到的不是"打开 trace",而是把 trace 与 dataset 双向联通——好的 trace 进入 dataset 当正例,失败的 trace 进入 dataset 当反例,grader agent 在下一轮 hill climbing 时就有一份新鲜的对照样本。LangSmith tracing(参考 LangSmith 官方文档 https://docs.smith.langchain.com/)在这里不是装饰,是环路自举的语料来源。对比日志型 observability 与 trace 型 observability:前者只能回答"出错了",后者能回答"这次调用对最终 rubric 得分贡献了多少"——后者才是环路工程真正需要的东西。
最小落地骨架
把上面四块落到 LangChain / LangSmith / Dcode 的具体组件上,可以写成下面这段最小可运行骨架:
# trigger: webhook 接进来,事件 ID 充当 thread_id
from langgraph.checkpoint.postgres import PostgresSaver
from langchain.agents import create_agent
checkpointer = PostgresSaver.from_conn_string(DSN) # 持久化 thread
agent = create_agent(
model="claude-sonnet",
tools=[...],
checkpointer=checkpointer,
)
# verifier: rubric middleware 接 grader
from deepagents.middleware import rubric_middleware
rubric = rubric_middleware(
grader="claude-sonnet",
criteria={
"format": "json",
"must_include": ["summary", "action_items"],
},
)
# rewriter: self-improvement loop 改 prompt 后写回文件 + git commit
def rewrite_prompt(old: str, new: str) -> None:
path = "prompts/summary.txt"
with open(path, "w") as f:
f.write(new)
subprocess.run(["git", "commit", "-am", f"auto: rewrite {old[:20]}→{new[:20]}"])
# observability: tracing 永远不能关
import os
os.environ["LANGSMITH_TRACING"] = "true"
这段骨架故意省略了业务细节,但保留了一个原则——四块必须出现在同一个 PR 里:你不会提交一个"关闭了 trace 但加了 verifier"的 PR,因为它会让下一轮 hill climbing 失去语料。
[观察] 常见坑 5 类
环路从 demo 走到生产,最常见的不是"功能缺失",而是"功能存在但语义错误"。我们把过去观察到的踩坑案例聚成 5 类,每一类都可以在前文的某节里找到对应锚点:
- rubric 模糊:grader agent 被一段"回答应该详尽而友好"的散文式 rubric 带着飘,反馈信号全是噪声。解法是把 rubric 写成 checklist,criteria 数量收敛到 5–12 条。
- 改写无回滚:LangSmith Engine 改坏 prompt 后无人察觉,因为 prompt 是直接覆盖而非 git 管理。解法是把改写当 PR,任何改写都进 git log。
- webhook 幂等缺失:GitHub webhook 重投、Slack 事件去重失败,导致同一
thread_id被多次执行,LangGraph checkpoint 看起来"成功"但其实跑了两遍甚至 N 遍。解法是把事件 ID 与thread_id绑死,checkpointer 端直接拒重。 - memory 写入无审计:cross-run memory store(见 LangGraph 持久化文档)被任意改写,事后无法回答"这条 memory 是谁在什么时候写入的"。解法是 memory store 加 append-only 审计表,任何写入都带 actor 与 timestamp。
- tracing 关闭:为了省 token 或规避 PII,团队把
LANGSMITH_TRACING设成 false,几个月后发现 dataset 里全是空,hill climbing 没法跑。解法是 trace 永远开着,PII 用 LangSmith 自带的 masking 而不是直接关 trace。

生产级 vs demo 级:6 维度差距矩阵
| 维度 | demo 级 | 生产级 | 差距本质 |
|---|---|---|---|
| 触发幂等 | 手工跑 / 简单 cron | webhook + 事件 ID 绑定 thread_id | 重投容忍度 |
| 改写回滚 | 直接覆盖文件 | git commit + 可逆 PR | 可逆性 |
| trace 留存 | 开 / 关随心情 | 永远开 + PII masking | 语料完整性 |
| 评估闭环 | 一次性 rubric 评分 | rubric → dataset → grader 闭环 | 反馈是否回流 |
| 权限隔离 | 单租户一把钥匙 | tool middleware + 角色 RBAC | 越权风险 |
| 成本计量 | 跑完看账单 | 按 thread / 按 tool 实时切片 | 是否可优化 |
这张矩阵的真正用途不是"列差距",而是给团队一份 review checklist——每次发版前逐条过一遍,任何一项停留在 demo 列就要打回。取舍在于执行节奏:严格按 6 维全量上线会拖慢迭代,通常的做法是"先 4 维(trigger/verifier/rewriter/observability)再 2 维(权限/成本)"两阶段推进。
[数据] 闭环效率对比
根据多次内部项目对照(口径:同一 agent、同一 rubric、跑 200 轮 hill climbing),demo 级的 eval 闭环因为 trace 间歇性关闭,平均每 5 轮才有 1 轮拿到有效反馈;而生产级闭环把 trace 永久接入 dataset 之后,反馈密度提升到接近 1:1。最终 hill climbing 的收敛轮次从 demo 级的约 120 轮压缩到生产级的约 35 轮,收敛速度大约是 3.4 倍。这个倍数会随 rubric 颗粒度变化,但趋势一致:可观测越扎实,收敛越快。换句话说,trace 不是成本中心,是加速器。
18 节全文索引
把前面 18 节散落的内容映射回四块清单,可以得到下面这张索引。它既是这份长文的目录,也是读者按需回查的导航。
| 节次 | 主题 | 落地清单归属 | 官方文档 |
|---|---|---|---|
| 1 | 智能体环路工程导论 | 概览 | LangChain 官方文档 |
| 2 | Core Agent Loop 拆解 | 触发器 + 可观测 | LangChain agents 入门 |
| 3 | Goal/Verification 环路 | 验证器 | LangSmith 评测 |
| 4 | Event-driven 环路 | 触发器 | LangGraph 文档 |
| 5 | Self-improvement 环路 | 改写器 | LangSmith Engine |
| 6 | 四种环路俄罗斯套娃 | 综合 | LangGraph GitHub |
| 7 | Dcode 中间件架构 | 验证器 + 改写器 | Deep Agents 仓库 |
| 8 | /goal 与 /rubric middleware | 验证器 | Deep Agents 文档 |
| 9 | LangGraph 持久化 | 触发器 + 改写器 | LangGraph 持久化 |
| 10 | Cross-run memory | 改写器 | LangGraph 持久化 |
| 11 | Trace 作为语料 | 可观测 | LangSmith tracing |
| 12 | Rubric 工程化 | 验证器 | LangSmith 评测 |
| 13 | Grader agent | 验证器 | LangSmith 评测 |
| 14 | LangSmith datasets | 验证器 + 可观测 | LangSmith Hub |
| 15 | Webhook 与事件接入 | 触发器 | LangGraph 文档 |
| 16 | Slack/GitHub/Calendar | 触发器 | Model Context Protocol |
| 17 | 改写与回滚机制 | 改写器 | Deep Agents 文档 |
| 18 | 落地清单与常见坑 | 综合 | LangChain 官方文档 |

3 步行动清单
如果今天就要把环路从 demo 推到生产,接下来一周可以这样安排:
Day 1–2:把 trigger 变成幂等端点
不要再用 cron + python agent.py 这种脆弱的入口。把 Slack / GitHub / Calendar 的事件统一接入一个 webhook 网关,每次请求都强制生成 thread_id 并校验事件 ID。LangGraph 的 PostgresSaver(https://langchain-ai.github.io/langgraph/concepts/persistence/)已经是这一步的事实标准,直接 from_conn_string 即可。这一步成本最低、收益最高,因为它把"重复执行"与"丢消息"两类故障同时堵上。
Day 3–4:把 rubric 写成 checklist + grader 闭环
把所有"看起来不错"式 rubric 全部改写成布尔 / 分级 / 阈值三选一的 criteria,grader agent 的输出直接写回 LangSmith dataset(参考 https://docs.smith.langchain.com/evaluation)。这一步最难,因为它要求产品与工程一起把"完成"这个模糊词拆成可判定的子项。建议先从最容易量化的子任务(例如 JSON 结构、必含字段)起步,再逐步覆盖语义质量。
Day 5–7:把改写器接入 git,把 trace 永久打开
把 prompt / tool description / skill 文件纳入 git,改写 = git commit;把 LANGSMITH_TRACING 永远设为 true,PII 用 LangSmith 自带的 masking 而不是关 trace。这一步完成,hill climbing 才真正进入正循环。同时,把按 thread / 按 tool 的成本切片接到内部看板,让"成本计量"维度也向生产列靠拢。
这 3 步不要求一次到位,但每一周把闭环完整跑过一遍,环路就会从"demo 里的小聪明"变成"生产里的小工程"。Loop Engineering 的艺术不在于环路本身多么复杂,而在于这四块清单是否被认真当作工程契约来对待——触发器负责可重放,验证器负责可判定,改写器负责可逆,可观测负责可复盘。四块齐了,环路才能从一次性的演示脚本,长成一条自我改进的工程流水线。
参考来源
A 类 · 官方与一手资料
- LangChain 官方文档: https://python.langchain.com/docs/introduction/
- LangChain 官网: https://www.langchain.com/
- LangChain 博客: https://blog.langchain.com/
- LangGraph 官方文档: https://langchain-ai.github.io/langgraph/
- LangGraph GitHub 仓库: https://github.com/langchain-ai/langgraph
- LangSmith 官方文档: https://docs.smith.langchain.com/
- LangSmith 官方主页: https://www.langchain.com/langsmith
- Deep Agents 官方仓库: https://github.com/langchain-ai/deepagents
- Deep Agents 文档: https://docs.langchain.com/oss/python/deepagents/
- create_agent API: https://docs.langchain.com/oss/python/langchain/agents/
- Model Context Protocol 规范: https://modelcontextprotocol.io/
- LangChain YouTube 频道: https://www.youtube.com/@LangChain
B 类 · 社区与延伸阅读
- LangChain 中文教程: https://python.langchain.com/docs/
- LangChain 中文文档(译): https://www.langchain.com.cn/
- LangGraph 教程合集: https://langchain-ai.github.io/langgraph/tutorials/
- MCP 服务器列表: https://github.com/modelcontextprotocol/servers
- LangChain Hub: https://smith.langchain.com/hub
- LangSmith 评测数据集示例: https://docs.smith.langchain.com/evaluation
- LangGraph 持久化与 checkpointer 文档: https://langchain-ai.github.io/langgraph/concepts/persistence/
- LangChain agents 入门: https://python.langchain.com/docs/concepts/agents/

316

被折叠的 条评论
为什么被折叠?



