Skip to content

第 28 章:实践篇精要 —— 从零到生产的 7 阶段

构建生产级 AI Agent 的完整路径可以拆成 7 个递进阶段,每个阶段解决一类核心问题:基础能力 → 知识增强 → 推理规划 → 感知扩展 → 多体协作 → 生产化 → 综合落地。

mermaid
graph LR
    P1["Phase 1<br/>Agent 基础"] --> P2["Phase 2<br/>记忆与知识"]
    P2 --> P3["Phase 3<br/>推理与规划"]
    P3 --> P4["Phase 4-5<br/>感知扩展<br/>多 Agent"]
    P4 --> P5["Phase 6-7<br/>生产化<br/>综合实战"]

    style P1 fill:#0d9488,color:#fff
    style P2 fill:#3b82f6,color:#fff
    style P3 fill:#8b5cf6,color:#fff
    style P4 fill:#ec4899,color:#fff
    style P5 fill:#f97316,color:#fff

28.1 Phase 1:Agent 基础 — 最小 Agent → 多轮 → 流式 → 错误处理

Phase 1 解决的核心问题:一个最小可运行的 Agent 需要哪些骨架组件? 答案是四个:工具调用循环、多轮上下文管理、流式输出、错误防护。

28.1.1 最小 Agent 与工具调用生命周期

Agent 和普通 LLM 调用的本质区别:Agent 有工具,模型可以主动调用它们。一次工具调用经历四个阶段:

阶段关键行为数据流向
声明告诉模型有哪些工具、参数 Schema代码 → API
决策模型判断是否调用、调用哪个API 内部
执行代码接收 tool_calls,调用真实函数API → 代码 → 真实世界
整合工具结果放回对话,模型生成最终回复代码 → API → 用户

最小 Agent 的核心是一个 while(true) 循环,持续运行直到 finish_reason === 'stop'。这就是感知-思考-行动循环体的最简形态:

typescript
async function runAgent(userMessage: string): Promise<void> {
  const messages: ChatCompletionMessageParam[] = [
    { role: 'user', content: userMessage },
  ]
  while (true) {
    const response = await client.chat.completions.create({
      model: 'gpt-4o', tools, messages,
    })
    const message = response.choices[0].message
    messages.push(message)

    if (response.choices[0].finish_reason === 'stop') return

    // 模型要调用工具,逐个执行并收集结果
    for (const toolCall of message.tool_calls ?? []) {
      const result = executeTool(toolCall.function.name,
        JSON.parse(toolCall.function.arguments))
      messages.push({
        role: 'tool', tool_call_id: toolCall.id, content: result,
      })
    }
  }
}

关键设计点:

  • tools 声明用 type: 'function' 包裹,内含 namedescriptionparameters(JSON Schema 格式)
  • finish_reason: 'tool_calls' 是模型需要调用工具的停止信号
  • role: 'tool' 消息通过 tool_call_id 与请求关联
  • 模型不一定调用工具——如果问题可以直接回答,模型直接返回 stop

28.1.2 多轮对话与 Token 预算控制

多轮对话的本质:把每轮的用户输入和模型回复都追加到 messages 数组,下次调用时一起发给模型。LLM API 是无状态的——每次调用都是独立 HTTP 请求,模型不保存任何上下文。

Token 预算问题随之而来。历史越长,成本线性上涨,最终超过 context window 限制。解决方案是 滑动窗口裁剪

typescript
class ChatSession {
  private messages: ChatCompletionMessageParam[] = []

  trimHistory(maxTokenEstimate: number): void {
    while (
      this.messages.length > 2 &&
      this.estimateHistoryTokens() > maxTokenEstimate
    ) {
      this.messages.splice(0, 2) // 每次删最旧的 user+assistant 对
    }
  }
}

两种截断策略对比:

策略做法优点缺点
保留最近 N 条直接裁剪数组前部简单丢失早期关键信息
滑动窗口 + system 保留system prompt 不动,裁剪中间历史保留系统指令仍可能丢失用户偏好

粗略 Token 估算用 Math.ceil(text.length / 4) 足够做预算控制。生产环境中,更精细的方案包括摘要压缩(用模型把旧历史总结为一段文字)和向量检索(把历史存入向量库,每轮检索最相关的几条)。

28.1.3 流式输出

流式输出让用户感知等待从"5 秒空白"变成"立刻开始有内容"。底层用 HTTP 分块传输,服务器边生成边推送。

核心 API 差异:

方法返回类型行为
create()Promise<ChatCompletion>等全部生成,一次返回
create({ stream: true })Stream<ChatCompletionChunk>AsyncIterable,逐 chunk 推送

流式 + 工具调用的关键处理:工具调用参数 JSON 是分片推送的(delta.tool_calls[].function.arguments),需要在流消费阶段按 index 累积字符串,等 finish_reason 出现后才能解析执行。

typescript
// 文本片段直接打印
if (delta?.content) {
  process.stdout.write(delta.content)
  textContent += delta.content
}

// 工具调用增量累积
if (delta?.tool_calls) {
  for (const tc of delta.tool_calls) {
    if (currentToolCall?.index !== tc.index) {
      // 新工具调用开始,保存上一个
      if (currentToolCall) toolCalls.push(finalize(currentToolCall))
      currentToolCall = { index: tc.index, id: tc.id ?? '', ... }
    } else {
      currentToolCall.arguments += tc.function?.arguments ?? ''
    }
  }
}

流式与非流式 Token 消耗完全相同——只是改变了传输方式。

28.1.4 错误处理三层防护

生产环境的 Agent 面临三类错误,需要三种处理方式:

第一层:API 错误 + 指数退避重试

typescript
const RETRYABLE_STATUS = new Set([429, 500, 503])

async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 4): Promise<T> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn()
    } catch (err) {
      if (!(err instanceof OpenAI.APIError)) throw err
      if (!RETRYABLE_STATUS.has(err.status ?? 0)) throw err
      const delay = Math.min(1000 * 2 ** attempt + Math.random() * 500, 30000)
      await sleep(delay)
    }
  }
  throw lastError
}

退避公式:delay = baseDelay * 2^attempt + random(0, jitter)。400/401 立即抛出不重试。

第二层:工具错误降级

工具执行异常时,不让异常向上冒泡,把错误信息包装成 role: 'tool' 消息返回给模型。模型看到错误后可能自主决定重试、换参数、或告知用户——这比在代码层面硬编码"失败就停止"更灵活。

第三层:循环次数保护

maxIterations 计数器防止模型行为异常(重复调用同一工具、陷入循环)导致无限运行。正常 Agent 很少需要超过 10 轮工具调用。

28.2 Phase 2:记忆与知识 — 记忆架构 → RAG → GraphRAG → 混合检索

Phase 2 解决的核心问题:如何让 Agent 突破 LLM 的无状态限制,拥有持久记忆和外部知识?

28.2.1 三层记忆架构

LLM API 每次调用独立,模型不保存任何状态。记忆的本质是上下文管理。三层模型直接对应人类认知系统:

记忆类型生命周期存储位置注入方式典型内容
短期记忆当前会话内存 messages[]直接传入 API本轮对话历史
工作记忆当前任务内存 state{}不传给模型任务进度、中间状态
长期记忆跨会话磁盘/数据库通过 system prompt 注入用户偏好、历史事实

长期记忆注入机制:每次对话前从存储加载,拼入 system prompt。不是模型"记住"了,而是我们每次把记忆"喂给"模型。

typescript
private buildSystemPrompt(): string {
  const entries = Object.entries(this.longTerm.getAll())
  const memoryLines = entries.map(([k, v]) => `- ${k}:${v}`).join('\n')
  return `你是有记忆能力的 AI 助手。\n\n[已知用户信息]\n${memoryLines}`
}

全量注入的局限:记忆条目增多后 Token 浪费,且不相关信息干扰模型。解决方案是按需检索注入。

28.2.2 记忆增强检索 — MemoryBank

从"全量塞入"到"按需检索"的关键跃升。每条记忆从裸字符串升级为结构化条目:

typescript
interface MemoryEntry {
  id: string
  content: string
  tags: string[]        // 检索核心依据
  importance: number    // 1-10,决定注入优先级
  createdAt: string
}

检索得分公式:命中词数 x 重要性。高重要性记忆即使只命中一个关键词,也能排在低重要性多命中的记忆前面。检索后取 top-K(通常 3-5 条)注入 system prompt,控制 Token 成本。

关键词检索的根本局限:无法处理语义相似但词面不同的情况("整洁度"无法命中标签"代码风格")。

28.2.3 RAG 基础 — 检索增强生成

RAG 是解决模型知识截止日期和私有文档问题的通行方案:

text
文档 → 分块 → 向量化 → 存储

用户问题 → 向量化 → 相似度检索 → 取 top-K → 注入上下文 → 模型生成

分块策略:文档不能整体向量化,否则向量平均掉所有细节。固定大小分块 + 重叠(overlap)是基础方案——重叠让相邻块保留部分重复内容,降低关键信息被切断的概率。

文档类型推荐分块大小原因
API 文档、代码注释100-200 字符信息密度高
技术文章、手册300-500 字符需要叙述上下文
长篇报告500-800 字符摘要级检索

向量化:生产环境使用 Embedding 模型(如 text-embedding-3-large)把文本映射到高维向量空间,语义相似的文本距离近。检索用余弦相似度:

text
cosine(a, b) = (a · b) / (|a| × |b|)

L2 归一化后简化为点积。检索结果格式化时带来源标注,帮助模型区分参考资料和自身知识。

28.2.4 GraphRAG — 知识图谱增强

向量 RAG 不理解关系。"张三的同事负责什么项目?"需要多跳推理(张三 → 同事 → 李四 → 负责 → 项目A),余弦相似度做不到。

知识图谱用节点(实体)和有向边(关系)表示信息:

text
张三 --[同事]--> 李四
李四 --[负责]--> 项目A

GraphRAG 用 BFS 图遍历检索 N 跳以内的实体和关系,格式化为文本注入 LLM:

typescript
getNeighbors(entityId: string, maxHops = 2) {
  const queue: Array<[string, number]> = [[entityId, 0]]
  while (queue.length > 0) {
    const [currentId, hop] = queue.shift()!
    if (hop >= maxHops) continue
    // 遍历出边和入边,收集邻居
  }
}
维度向量 RAGGraphRAG
擅长语义相似检索关系推理、多跳问答
数据结构向量索引图(节点 + 边)
检索方式余弦排序BFS/DFS 遍历

28.2.5 混合检索与 RRF 融合

单一检索不够:关键词检索精确但不懂语义,向量检索懂语义但可能返回"感觉相似"但不精确的结果。混合检索让多路并行,用 RRF(Reciprocal Rank Fusion)融合排名:

text
RRF_score(d) = Σ( 1 / (k + rank_i(d)) )

k=60 是经验值,防止排名第 1 的文档分数"垄断"。核心直觉:跨多个检索系统的一致认可比单系统的高排名更可靠

RRF 只关心排名不关心绝对分数,因此词频分数和余弦相似度混合融合没有问题。rrfFusion 接受任意数量的结果列表,三路、四路扩展只需传入更多列表。

28.3 Phase 3:推理与规划 — ReAct Loop → Planning → Reflection

Phase 3 解决的核心问题:如何让 Agent 从"黑盒决策"升级为"可调试的推理",从"边想边做"升级为"先规划后执行"?

28.3.1 ReAct Loop — 推理与行动交替

ReAct(Reasoning and Acting)在每次行动前强制模型先写出推理过程:

text
Thought: 需要查询北京实时天气才能判断
Action: get_weather
Action Input: {"city": "北京"}
Observation: 晴,22°C,东南风 3 级
Thought: 22°C 晴天,风力不大,适合户外运动
Final Answer: 今天北京适合跑步...

ReAct 与 CoT(Chain-of-Thought)的区别:CoT 只推理不行动(靠训练数据猜),ReAct 把推理和真实世界数据打通。

两种实现策略:

策略做法优点缺点
System Prompt 格式约束用正则解析模型文本输出简单,接近论文原始设计模型不一定严格遵守格式
原生工具调用 + thinking用 SDK tools 参数工具调用更可靠推理和行动耦合不如文本直观

解析推理链的核心是从文本提取 Thought/Action/Action Input/Final Answer

typescript
type ReActOutput =
  | { type: 'action'; thought: string; action: string; actionInput: Record<string, string> }
  | { type: 'final'; thought: string; answer: string }
  | { type: 'unknown'; raw: string }

观察到工具结果后以 Observation: 作为 user 消息推回对话历史,形成完整的推理链上下文。maxSteps 防止死循环。

28.3.2 Plan-and-Execute — 先规划后执行

ReAct 的局限:模型没有全局视图,每步都是局部最优。Plan-and-Execute 拆成两个阶段:

  1. 规划阶段:LLM 将目标一次性分解为有序步骤列表
  2. 执行阶段:逐步执行,通过累积上下文传递中间结果
typescript
interface PlanStep {
  id: string
  description: string
  status: 'pending' | 'running' | 'done' | 'failed'
  result?: string
}

步骤间信息传递靠累积上下文字符串:每步执行完后结果追加到 context,下一步带着完整 context 继续。

重新规划机制:步骤失败时把失败信息和已完成步骤摘要告知规划器,规划器返回新的剩余步骤列表。输入必须包含已完成步骤摘要,否则会产生重复步骤。

Plan-and-Execute vs ReAct:

维度ReActPlan-and-Execute
决策视角局部(单步)全局(完整计划)
适用场景短任务、探索性复杂任务、有明确子目标
可预测性高(步骤数提前确定)

实际产品经常组合:外层 Planning 确定结构,内层每步用 ReAct 执行。

28.3.3 Reflection — 生成-评审-改进循环

LLM 评估一段文本的能力往往优于它第一次生成这段文本的能力——"读者视角"比"写作视角"更客观。Reflection 模式利用这个洞察:

text
Generator(生成者)→ 产出初稿

Critic(评审者)  → 结构化评审(JSON:passed/score/suggestions)

Generator         → 根据具体建议改进
         ↓ 循环直到 score >= 阈值 或 达到 maxIterations

结构化评审反馈的关键:

typescript
interface ReflectionResult {
  passed: boolean       // 是否通过(true 则停止迭代)
  score: number         // 1-10 分
  feedback: string      // 总体评价
  suggestions: string[] // 具体改进建议
}

即使用同一个模型,Generator 和 Critic 用不同 system prompt 激活不同认知模式。有效评审标准的三个特征:可操作(不是"更好"而是"加一个代码示例")、可打分(明确通过标准)、与任务绑定(来自任务要求)。

实践中 2-3 轮迭代通常足够。超过 5 轮 Critic 还不满意,问题在 prompt 或模型能力,不在迭代次数。

28.4 Phase 4-5:感知扩展与多 Agent — 多模态 → MCP → 编排 → 子 Agent → 通信

Phase 4-5 解决两个问题:如何突破纯文本输入边界?如何让多个 Agent 协作完成单 Agent 做不了的事?

28.4.1 多模态 Agent

OpenAI Chat Completions API 的 content 字段支持 content 数组,混合传入文本和图像:

typescript
messages: [{
  role: 'user',
  content: [
    { type: 'image_url', image_url: { url: 'data:image/png;base64,...' } },
    { type: 'text', text: '这张图里有什么?' }
  ]
}]

两种图像输入方式:

方式格式适用场景
URLhttps://example.com/img.png公开图片
Base64data:image/png;base64,<data>本地/私有图片

图像 Token 按尺寸计算,不是按字符。缩小到 1280px 以内通常减少 30%-50% Token 消耗。核心实现是 ImageSource 联合类型 + toImageContent 转换函数,统一处理两种来源到 SDK 格式的映射。

28.4.2 MCP 协议 — 工具与 Agent 解耦

MCP(Model Context Protocol)把工具从 Agent 代码分离为独立服务器。工具代码和 Agent 代码强耦合时,多个 Agent 共享工具需要各维护一份实现,改一处同步三处。

text
Agent(MCP Client)  ←JSON-RPC→  工具服务(MCP Server)
  - 连接 Server                     - 注册工具
  - 发现工具                         - 执行工具
  - 转发调用                         - 返回结果

两个核心 JSON-RPC 方法:

  • tools/list:Client 问 Server 有哪些工具
  • tools/call:Client 请求 Server 执行工具

MCP 工具描述 → OpenAI SDK 格式需要转换:MCP 用扁平结构 { name, inputSchema },OpenAI 用嵌套 { type: 'function', function: { name, parameters } }

stdio 传输适合开发阶段(Client 启动 Server 子进程,通过管道通信);SSE 传输适合生产(Server 部署为 HTTP 服务,多个 Agent 同时连接)。

28.4.3 Orchestrator-Worker 多 Agent 编排

多 Agent 的核心判断标准:子任务是否需要不同的专业视角。单 Agent 更高效时不要过度设计。

三种编排模式:

模式结构适用场景
SequentialA → B → C流水线,前一步输出是下一步输入
ParallelA → [B,C,D] → A独立子任务,同时执行
Orchestrator-WorkerO → [W1,W2,...] → O动态任务拆分

Orchestrator 的三项职责:任务拆解、Worker 分配、结果聚合。Orchestrator 通过一个 dispatch_workers 工具来分派任务,对模型来说就是一次普通的工具调用。

Worker 设计原则:

  • 隔离:互不可见,各有独立 system prompt 和上下文
  • 并行Promise.all 同时执行,总耗时 = max(各 Worker)
  • 分层用模型:Orchestrator 用强模型做推理聚合,Worker 用性价比模型做执行

聚合不是拼接。Orchestrator 收到 Worker 结果后要整合、去重、排序、解决冲突。

28.4.4 子 Agent — 从单次调用到完整循环

Worker 只是单次 LLM 调用,没有工具也没有循环。SubAgent 是升级版:拥有独立工具集、自己的 Agent 循环、自己的停止条件。

类比:Worker 像实习生口头回答;SubAgent 像工程师,有自己的电脑和开发工具,独立完成后交付成果。

typescript
class SubAgent {
  constructor(
    private tools: ChatCompletionTool[],
    private systemPrompt: string,
    private maxIterations: number,
  ) {}

  async run(task: string): Promise<string> {
    // 完整的 Agent 循环:多轮工具调用 → 直到 finish_reason='stop'
  }
}

Orchestrator 根据任务描述动态创建异构 SubAgent(不同工具集),加上超时控制。

28.4.5 Agent 间通信

Worker 隔离在子任务独立时是优势,但协作场景需要 Agent 间通信。三种模式:

模式数据流适用场景
共享黑板(Blackboard)所有 Agent 读写一块公共状态多方协作、信息聚合
消息传递(Message Passing)Agent 之间点对点发送消息有序依赖、反馈回路
Handoff(控制权移交)把对话上下文连同控制权交给下一个 Agent流水线、专家切换

共享黑板实现是一个带锁的键值存储,多 Agent 通过读写它共享中间状态。Handoff 是连同上下文一起移交——接手方获得完整的对话历史。

28.5 Phase 6-7:生产化 — 模型路由 → 安全 → 可观测 → 评估 → 完整项目 → 部署

Phase 6-7 解决的核心问题:本地跑通和生产稳定运行之间的差距是什么?如何把散落的技术点整合为一个完整产品?

28.5.1 多模型路由与成本控制

硬编码 model: 'gpt-4o' 的三个问题:简单任务浪费钱、复杂任务效果差、模型不可用没有兜底。

ModelRouter 的核心逻辑:

  1. 复杂度分类:用启发式规则(代码长度、关键词检测、指令复杂度)判断任务难度为 low/medium/high
  2. 模型选择:low → Mini(便宜快速)、medium → Standard(平衡)、high → Large(最强)
  3. 降级链:Large 不可用 → Standard → Mini,沿链回退
  4. 成本追踪:每次调用记录 prompt_tokens / completion_tokens,按模型单价计算
typescript
class ModelRouter {
  async route(messages: ChatCompletionMessageParam[]): Promise<string> {
    const complexity = this.classify(messages)
    const models = this.getFallbackChain(complexity)
    for (const model of models) {
      try {
        return await this.callModel(model, messages)
      } catch {
        continue // 降级到下一个
      }
    }
    throw new Error('All models failed')
  }
}

成本追踪用 Token 用量 x 模型单价,按 input/output 分别计算。

28.5.2 安全与防注入 — 四层纵深防御

Prompt Injection 是 AI Agent 头号安全威胁。间接注入尤其危险:恶意指令藏在 Agent 处理的外部数据(文档、网页)中,模型无法区分"数据"和"指令"。

四层防御体系:

层次组件职责
第一层InputGuard输入到达模型前,检测并清洗注入尝试
第二层ToolPermission限制每个角色可用的工具和参数范围
第三层OutputValidator验证模型输出不包含敏感信息泄露
第四层SandboxExecutor工具执行在受限环境中运行

InputGuard 的典型检测模式:

typescript
const INJECTION_PATTERNS = [
  /忽略.*(?:之前|以上|所有).*(?:指令|规则|提示)/i,
  /ignore.*(?:previous|above|all).*(?:instructions|rules|prompts)/i,
  /system\s*prompt/i,
  /ASSISTANT:\s/,
  /\<\/?(?:system|user|assistant)\>/i,
]

ToolPermission 实现基于角色的访问控制:不同用户角色只能调用特定工具的特定参数范围。SandboxExecutor 对文件操作限制路径前缀,对 Shell 命令限制可执行的命令白名单。

28.5.3 可观测性 — 日志、追踪、指标

三根支柱解决不同问题:

支柱解决的问题Agent 场景
Logs谁/什么时候/做了什么/结果如何工具调用失败时的参数上下文
Traces一次请求的完整调用链路定位是 LLM 慢还是工具慢
Metrics聚合统计总调用次数、平均延迟、错误率

用装饰器模式实现,不修改业务逻辑:

typescript
class ObservableAgent {
  private tracer: Tracer

  async chat(message: string): Promise<string> {
    const span = this.tracer.startSpan('agent.chat')
    try {
      const result = await this.innerAgent.chat(message)
      span.setStatus('ok')
      return result
    } catch (err) {
      span.setStatus('error', err)
      throw err
    } finally {
      span.end()
    }
  }
}

Span 组织为树形结构:agent.chat 下挂 llm.calltool.execute 子 Span,每个 Span 记录开始时间、结束时间、属性(model、token_count 等)。运行结束后输出完整 Trace 树和指标摘要(总耗时、LLM 调用次数、Token 用量分布)。

28.5.4 评估与基准测试

LLM 输出是非确定性的,同一输入跑两次可能得到不同措辞但都正确的回答。评估需要比传统单元测试更灵活的维度。

评估框架由三个组件构成:

测试用例

typescript
interface EvalCase {
  id: string
  input: string
  expectedBehavior: string
  criteria: EvalCriteria[]  // 评估标准
}

双评审机制

评审器原理适合场景
LLM-as-Judge用另一个 LLM 评估输出质量开放式回答、文本质量
确定性校验正则匹配、关键词检测、JSON Schema格式要求、必含信息

A/B 对比:同一批测试用例跑两套配置(不同 model / temperature / system prompt),比较评分分布。EvalRunner 批量运行测试并生成评估报告,每次 prompt 修改后重跑,防止改了 3 个 case 改好但第 4 个变差。

28.5.5 完整项目 — Code Review Agent

综合前 21 章技术的毕业设计。选 Code Review 的原因:输入结构化(git diff 可精确解析)、维度正交(安全 vs 质量需要不同视角)、输出可度量(文件、行号、严重等级)。

完整流水线:

bash
git diff 文本

DiffParser(解析为结构化文件变更)

ReviewOrchestrator(分派审查任务)

┌──────────────┬──────────────┐
│SecurityReviewer│QualityReviewer│ 并行执行(P15 编排)
└──────────────┴──────────────┘

ResultAggregator(聚合 + 去重 + 排序)

ReportGenerator(Markdown 审查报告)

技术整合点:

  • Diff 解析:确定性文本处理,不需要 LLM
  • 多维度审查:P15 Orchestrator-Worker 模式
  • 安全检测:P19 的 InputGuard 思路用于检测代码中的危险模式
  • 结构化输出:每个发现都是 { file, line, severity, description } 结构
  • 可观测性:P20 的 Span 追踪审查各阶段耗时

28.5.6 生产部署清单 — 五层防护

把实验 Agent 变成生产服务需要经典分布式系统工程:

1. 令牌桶限流(Token Bucket)

主动限流而非等被 429 拒绝。桶里有固定数量的令牌,每次请求消耗一个,按固定速率补充。桶空则等待。

typescript
class TokenBucket {
  private tokens: number
  private lastRefill: number

  async acquire(): Promise<void> {
    this.refill()
    while (this.tokens < 1) {
      await sleep(this.refillInterval)
      this.refill()
    }
    this.tokens--
  }
}

2. 熔断器(Circuit Breaker)

Provider 连续失败时快速失败并自动恢复。三态模型:

text
Closed(正常)→ 连续 N 次失败 → Open(熔断,快速拒绝)
Open → 超时后 → Half-Open(试探一次)
Half-Open → 成功 → Closed / 失败 → Open

3. 请求超时

Promise.race 实现 LLM 调用超时中止:

typescript
async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
  return Promise.race([
    promise,
    new Promise<never>((_, reject) =>
      setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
    ),
  ])
}

4. 优雅降级

主模型不可用时返回兜底响应(P18 降级链的扩展)。三级降级:主模型 → 备选模型 → 预设静态回答。

5. 健康检查端点

报告 Agent 各组件状态,用 HTTP 端点供负载均衡器 / k8s 探针调用:

typescript
// GET /health
{
  status: 'healthy',
  components: {
    llm: { status: 'up', latency_ms: 234 },
    circuitBreaker: { state: 'closed' },
    rateLimiter: { tokens_remaining: 8 }
  }
}

28.5.7 生产部署清单总览

类别检查项对应技术
API 防护指数退避重试Phase 1 错误处理
成本控制模型路由 + Token 预算Phase 6 模型路由
安全输入清洗 + 工具沙箱 + 输出验证Phase 6 安全防注入
可靠性限流 + 熔断 + 超时 + 降级Phase 7 生产部署
可观测结构化日志 + 分布式追踪 + 指标聚合Phase 6 可观测性
质量自动化评估 + A/B 测试Phase 6 评估体系
运维健康检查 + 告警Phase 7 生产部署