第 28 章:实践篇精要 —— 从零到生产的 7 阶段
构建生产级 AI Agent 的完整路径可以拆成 7 个递进阶段,每个阶段解决一类核心问题:基础能力 → 知识增强 → 推理规划 → 感知扩展 → 多体协作 → 生产化 → 综合落地。
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:#fff28.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'。这就是感知-思考-行动循环体的最简形态:
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'包裹,内含name、description、parameters(JSON Schema 格式)finish_reason: 'tool_calls'是模型需要调用工具的停止信号role: 'tool'消息通过tool_call_id与请求关联- 模型不一定调用工具——如果问题可以直接回答,模型直接返回
stop
28.1.2 多轮对话与 Token 预算控制
多轮对话的本质:把每轮的用户输入和模型回复都追加到 messages 数组,下次调用时一起发给模型。LLM API 是无状态的——每次调用都是独立 HTTP 请求,模型不保存任何上下文。
Token 预算问题随之而来。历史越长,成本线性上涨,最终超过 context window 限制。解决方案是 滑动窗口裁剪:
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 出现后才能解析执行。
// 文本片段直接打印
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 错误 + 指数退避重试
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。不是模型"记住"了,而是我们每次把记忆"喂给"模型。
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
从"全量塞入"到"按需检索"的关键跃升。每条记忆从裸字符串升级为结构化条目:
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 是解决模型知识截止日期和私有文档问题的通行方案:
文档 → 分块 → 向量化 → 存储
↓
用户问题 → 向量化 → 相似度检索 → 取 top-K → 注入上下文 → 模型生成分块策略:文档不能整体向量化,否则向量平均掉所有细节。固定大小分块 + 重叠(overlap)是基础方案——重叠让相邻块保留部分重复内容,降低关键信息被切断的概率。
| 文档类型 | 推荐分块大小 | 原因 |
|---|---|---|
| API 文档、代码注释 | 100-200 字符 | 信息密度高 |
| 技术文章、手册 | 300-500 字符 | 需要叙述上下文 |
| 长篇报告 | 500-800 字符 | 摘要级检索 |
向量化:生产环境使用 Embedding 模型(如 text-embedding-3-large)把文本映射到高维向量空间,语义相似的文本距离近。检索用余弦相似度:
cosine(a, b) = (a · b) / (|a| × |b|)L2 归一化后简化为点积。检索结果格式化时带来源标注,帮助模型区分参考资料和自身知识。
28.2.4 GraphRAG — 知识图谱增强
向量 RAG 不理解关系。"张三的同事负责什么项目?"需要多跳推理(张三 → 同事 → 李四 → 负责 → 项目A),余弦相似度做不到。
知识图谱用节点(实体)和有向边(关系)表示信息:
张三 --[同事]--> 李四
李四 --[负责]--> 项目AGraphRAG 用 BFS 图遍历检索 N 跳以内的实体和关系,格式化为文本注入 LLM:
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
// 遍历出边和入边,收集邻居
}
}| 维度 | 向量 RAG | GraphRAG |
|---|---|---|
| 擅长 | 语义相似检索 | 关系推理、多跳问答 |
| 数据结构 | 向量索引 | 图(节点 + 边) |
| 检索方式 | 余弦排序 | BFS/DFS 遍历 |
28.2.5 混合检索与 RRF 融合
单一检索不够:关键词检索精确但不懂语义,向量检索懂语义但可能返回"感觉相似"但不精确的结果。混合检索让多路并行,用 RRF(Reciprocal Rank Fusion)融合排名:
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)在每次行动前强制模型先写出推理过程:
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:
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 拆成两个阶段:
- 规划阶段:LLM 将目标一次性分解为有序步骤列表
- 执行阶段:逐步执行,通过累积上下文传递中间结果
interface PlanStep {
id: string
description: string
status: 'pending' | 'running' | 'done' | 'failed'
result?: string
}步骤间信息传递靠累积上下文字符串:每步执行完后结果追加到 context,下一步带着完整 context 继续。
重新规划机制:步骤失败时把失败信息和已完成步骤摘要告知规划器,规划器返回新的剩余步骤列表。输入必须包含已完成步骤摘要,否则会产生重复步骤。
Plan-and-Execute vs ReAct:
| 维度 | ReAct | Plan-and-Execute |
|---|---|---|
| 决策视角 | 局部(单步) | 全局(完整计划) |
| 适用场景 | 短任务、探索性 | 复杂任务、有明确子目标 |
| 可预测性 | 低 | 高(步骤数提前确定) |
实际产品经常组合:外层 Planning 确定结构,内层每步用 ReAct 执行。
28.3.3 Reflection — 生成-评审-改进循环
LLM 评估一段文本的能力往往优于它第一次生成这段文本的能力——"读者视角"比"写作视角"更客观。Reflection 模式利用这个洞察:
Generator(生成者)→ 产出初稿
↓
Critic(评审者) → 结构化评审(JSON:passed/score/suggestions)
↓
Generator → 根据具体建议改进
↓ 循环直到 score >= 阈值 或 达到 maxIterations结构化评审反馈的关键:
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 数组,混合传入文本和图像:
messages: [{
role: 'user',
content: [
{ type: 'image_url', image_url: { url: 'data:image/png;base64,...' } },
{ type: 'text', text: '这张图里有什么?' }
]
}]两种图像输入方式:
| 方式 | 格式 | 适用场景 |
|---|---|---|
| URL | https://example.com/img.png | 公开图片 |
| Base64 | data:image/png;base64,<data> | 本地/私有图片 |
图像 Token 按尺寸计算,不是按字符。缩小到 1280px 以内通常减少 30%-50% Token 消耗。核心实现是 ImageSource 联合类型 + toImageContent 转换函数,统一处理两种来源到 SDK 格式的映射。
28.4.2 MCP 协议 — 工具与 Agent 解耦
MCP(Model Context Protocol)把工具从 Agent 代码分离为独立服务器。工具代码和 Agent 代码强耦合时,多个 Agent 共享工具需要各维护一份实现,改一处同步三处。
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 更高效时不要过度设计。
三种编排模式:
| 模式 | 结构 | 适用场景 |
|---|---|---|
| Sequential | A → B → C | 流水线,前一步输出是下一步输入 |
| Parallel | A → [B,C,D] → A | 独立子任务,同时执行 |
| Orchestrator-Worker | O → [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 像工程师,有自己的电脑和开发工具,独立完成后交付成果。
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 的核心逻辑:
- 复杂度分类:用启发式规则(代码长度、关键词检测、指令复杂度)判断任务难度为 low/medium/high
- 模型选择:low → Mini(便宜快速)、medium → Standard(平衡)、high → Large(最强)
- 降级链:Large 不可用 → Standard → Mini,沿链回退
- 成本追踪:每次调用记录 prompt_tokens / completion_tokens,按模型单价计算
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 的典型检测模式:
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 | 聚合统计 | 总调用次数、平均延迟、错误率 |
用装饰器模式实现,不修改业务逻辑:
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.call、tool.execute 子 Span,每个 Span 记录开始时间、结束时间、属性(model、token_count 等)。运行结束后输出完整 Trace 树和指标摘要(总耗时、LLM 调用次数、Token 用量分布)。
28.5.4 评估与基准测试
LLM 输出是非确定性的,同一输入跑两次可能得到不同措辞但都正确的回答。评估需要比传统单元测试更灵活的维度。
评估框架由三个组件构成:
测试用例:
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 质量需要不同视角)、输出可度量(文件、行号、严重等级)。
完整流水线:
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 拒绝。桶里有固定数量的令牌,每次请求消耗一个,按固定速率补充。桶空则等待。
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 连续失败时快速失败并自动恢复。三态模型:
Closed(正常)→ 连续 N 次失败 → Open(熔断,快速拒绝)
Open → 超时后 → Half-Open(试探一次)
Half-Open → 成功 → Closed / 失败 → Open3. 请求超时
用 Promise.race 实现 LLM 调用超时中止:
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 探针调用:
// 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 生产部署 |