跳到主要内容

创建日期:2026-09-20 | 最近更新:2026-09-20 本系列基于一套 Agent 工程化示范代码(TypeScript,Mock 驱动,无需 API Key)。所有运行输出均为本机真实执行结果;脚本与完整源码留档在 source/agent-engineering/。 写作约定:代码全部贴出、不省略(用「……」截断的示例在这种题材里没有参考价值);文中出现的 @offerai/shared 是该示范工程内部的公共包,与本文主题无关。

Agent 工程化总览:两道门槛、两条阶梯、五个控制杆

Demo 能跑通和能上线之间,隔着两个工程化门槛:

  1. 结构化输出不可靠——模型返回的 JSON 有 3%~5% 是坏的(截断、单引号、尾逗号、Markdown 包裹、字段类型错、枚举越界),裸 JSON.parse 直接崩;
  2. 没有容错的调用链——一个慢下游就能把单线程的 Node 服务拖垮,一次限流就能让重试风暴把成本打穿。

这个系列把这两件事各拆成 五层阶梯(L0 → L4),逐层加上一层防护,每层都给出完整可运行的实现,并用真实运行结果说明「上一层为什么不够」。

一、先看一组真实对比:L0 与 L4 的差距​

同一个故障场景:20 个并发请求,下游失败率 80%。本机真实输出:

L0 无保护: 成功=1, 失败=19, 耗时=87ms
L4 完整弹性链: 成功=20, 降级=0, 总耗时=1008ms
熔断器状态: closed
模型调用统计: { 'gpt-4o-mini': { success: 20, failed: 0 } }

「成功 1 / 20」到「成功 20 / 20」 就是这四层防护换来的。代价是耗时从 87ms 变成 1008ms——这是很划算的一笔交易:多等 0.9 秒,换掉 95% 的失败。

二、两道门槛,两条阶梯​

阶梯一:结构化输出五层保障链​

L0 裸调用 + JSON.parse() ← 3-5% 失败率,直接 crash
│ 加 try-catch + JSON Mode
L1 JSON Mode + try-catch ← 强制 JSON 格式,但截断/转义错误仍在
│ 加 Zod 运行时校验
L2 Zod Schema 校验 ← 编译时类型 + 运行时校验,错误信息具体到字段
│ 加 withStructuredOutput + jsonrepair
L3 结构化输出 + 自动修复 ← 80%+ 的格式错误自动修好
│ 加 错误分类 + 智能重试 + DLQ
L4 完整五层防护链 ← 语义错误也能重试,重试耗尽不丢消息

每一层解决的是不同性质的问题,这是关键——它们不能互相替代:

层解决解决不了
L0 → L1JSON 语法大部分情况下合法字段类型/枚举/必填
L1 → L2数据结构不符合 schema格式本身已经坏了(解析就失败)
L2 → L3坏格式(截断、包裹、单引号)语义错误(值是错的,但结构合法)
L3 → L4语义错误也能重试、不丢消息—

一句话概括这条阶梯的分工:jsonrepair 修「格式」,zod 管「结构」,重试与 DLQ 兜「语义」。三者顺序不能换。

阶梯二:弹性容错五层链​

L0 裸调用 LLM ← 超时/限流直接打挂服务,成本失控
│ 加 指数退避重试
L1 重试 ← 偶发抖动能救了,但制造重试风暴
│ 加 熔断器三态机
L2 熔断器 ← 故障雪崩时快速失败,一个请求都不发
│ 加 模型级联降级
L3 模型降级链 ← 失败降级到便宜模型,质量差升级到贵模型
│ 加 舱壁隔离限流
L4 完整弹性链 ← 限流+熔断+重试+降级+舱壁,一个「高可用网关」

调用链的顺序(这个顺序本身就是结论):

请求 → 舱壁(排队/限流) → 熔断器(快速失败) → 重试(退避) → 路由(选模型) → LLM
├─ 失败 → 降级到更便宜的模型
├─ 质量差 → 升级到更强的模型
└─ 都失败 → 静态兜底

为什么是这个顺序:先限流(入口自治)→ 再熔断(判断下游健康,不健康就别浪费)→ 再重试(下游既然健康,偶发失败值得重试)→ 最后降级(重试也不行,换模型/缓存/兜底)。

Node.js 为什么特别需要熔断器​

这条值得单独强调,因为它常被当成「Java 那套搬过来」:

  • Node 是单线程事件循环:一个下游超时 30 秒,所有请求都在等超时回调,事件循环被占满,进程连健康检查都答不了,然后被 k8s 杀掉,雪崩;
  • Java 有线程池隔离,一个池满了不影响别的业务;Node 没有这层隔离,所以熔断器就是最主要的防线;
  • 对 LLM 应用还有一层额外收益:熔断器 Open 状态一个请求都不发,Token 消耗为 0——微服务熔断省的是 CPU,Agent 熔断省的是真金白银。

三、五个控制杆​

除了两条阶梯,还有五个「控制杆」(levers),它们是给 Agent 做减法和加约束的地方:

控制杆管什么一句话
输入校验进来的查询空、超长、危险关键词(SQL 注入式)直接拒
上下文/记忆喂给模型的历史滑动窗口 + 超窗部分压成摘要
工具输入调工具的参数用 schema 校验工具的入参,而不是相信模型
引导(guidance)提示词Few-shot 示例固化输出格式(NL2SQL 场景)
兜底(fallback)失败之后缓存 → 主模型 → 备用模型 → 静态兜底,四级

四、可观测性与成本​

  • tracing:接 LangSmith / Langfuse(本系列另有一篇专讲 Langfuse 与 LangSmith,包括「不 flush 就丢 trace」那个坑);
  • token 计数:按模型分别累计 input/output token 并折算成本。Agent 应用的钱是按 token 烧的,没有这个计数器,成本就是一笔糊涂账。

五、本系列怎么读​

篇主题你会拿到
0(本文)总览与地图两条阶梯 + 五个控制杆的全貌
1结构化输出五层防护链L0–L4 完整实现 + 错误分类器 + 死信队列
2弹性容错五层链L0–L4 完整实现 + 舱壁 + 模型路由器
3五个控制杆与可观测性levers 全部实现 + token 计数 + tracing

建议读法:先看第 1 篇的 L0,你会发现「这不就是我写的代码吗」;再顺着往下,每加一层都问一句「上一层为什么不够」。这个「逐层追问」的过程,比记住五个层名有用得多。

六、一页速查​

结构化输出:jsonrepair(格式) → zod(结构) → 错误分类(值不值得重试) → 重试 → DLQ(不丢消息)
弹性容错 :舱壁(隔离) → 熔断(快速失败) → 重试(退避) → 降级(成本) → 兜底(体验)
五个控制杆:输入校验 / 记忆窗口 / 工具入参 / Few-shot 引导 / 四级 fallback
可观测 :tracing(LangSmith|Langfuse)+ token 计数与成本
铁律 :不要让模型「自由发挥」,每一处都给它一个 schema 或一条兜底路径

关联​

自测​

  1. 结构化输出的五层里,jsonrepair 与 zod 各自负责哪一类错误?为什么顺序不能换?
  2. 「L0 成功 1/20 → L4 成功 20/20」这组数字里,代价是什么?为什么说划算?
  3. 弹性链为什么是「舱壁 → 熔断 → 重试 → 降级」这个顺序?
  4. Node.js 相比 Java,为什么更需要熔断器?
  5. 熔断器 Open 状态对 LLM 应用额外省下了什么?

参考​

  • 本系列完整源码与运行日志(随仓库留档):source/agent-engineering/——含两条阶梯的全部实现、五个控制杆、可观测性模块,以及本文引用的真实 demo 输出
  • 运行环境:Node v24.14.1;依赖 @langchain/core、zod、p-retry、opossum、bottleneck、jsonrepair
  • 说明:文中所有输出为 Mock 模式(无需 API Key)的真实运行结果;本机未运行单元测试(测试运行器未安装),故本文不含测试结论