序:90% 的 AI 应用,死在了”上线”这一步
2026 年上半年,一个残酷的现实正在浮现:Gartner 报告指出,超过 85% 的 AI 项目从未真正进入生产环境。 不是因为模型不够好,不是因为功能没实现,而是因为——它们无法稳定地、安全地、经济地在真实流量下持续运行。
在过去四篇文章中,我们从概念走到代码,构建了完整的 AI Agent 开发能力链:
- 第一篇:理解了 AI Agent 的范式跃迁,用 Vercel AI SDK 跑通了第一个 Agent
- 第二篇:设计了 Agent 的工具系统,掌握了 Function Calling 与 MCP
- 第三篇:编排了多 Agent 协作,构建了覆盖前后端的 AI 开发团队
- 第四篇:实现了 Agent 的记忆与状态管理,让它跨会话”记住”用户
但在本地的 npm run dev 里跑得再漂亮,也只是温室里的花朵。一旦推向真实的生产环境,你将面对一系列全新的问题:
API 在凌晨 3 点超时了,为什么?Token 消耗突然飙升了 300%,是谁在用?一个恶意用户注入了 Prompt,把系统指令泄露了,怎么办?大模型厂商限流了,你的服务怎么办?灰度发布时,如何对比两个模型版本的输出质量?
这些问题,没有一篇论文会教你,没有一个 SDK 能帮你解决。它们属于工程化的领域——一个前端开发者可能不太熟悉,但在 AI 时代必须掌握的领域。
从原型到生产,AI Agent 应用需要的不是”更强的模型”,而是”更硬的工程”。
本篇是《前端 + AI Agent 开发实战》系列的收官之作。我们将从部署架构、安全防护、可观测性、成本优化、弹性伸缩到 CI/CD,系统性地拆解 AI Agent 应用的生产化路径。
一、生产环境:一场完全不同的游戏
1.1 为什么”能跑”不等于”能上线”
在开发阶段,你的 Agent 面对的环境是理想化的:
1 2 3 4 5 6 7 8
| 开发环境(理想化) 生产环境(真实世界) ───────────────── ────────────────── 单用户,低并发 → 多用户,高并发 LLM API 响应稳定 → 限流、超时、降级 无恶意输入 → Prompt 注入、滥用 不计成本 → Token 费用按月计费 无状态崩溃 → 需要优雅降级 手动测试验证 → 需要自动化评估
|
这意味着,你在本地用 50 个测试用例验证通过的 Agent,到了生产环境可能在第 51 个真实请求上就崩溃了。
1.2 AI Agent 应用的特殊运维挑战
与传统 Web 应用相比,AI Agent 应用在生产环境中有五个独特挑战:
挑战一:非确定性输出。 传统 API 对于相同输入,永远返回相同输出。但 LLM 是概率模型——同一个 Prompt,两次调用可能返回不同结果。这让传统的断言式测试和监控几乎失效。
挑战二:长时延迟。 一个普通 API 的响应时间在 50-200ms 之间。而一次 Agent 推理(含多轮工具调用)可能需要 10-60 秒。这彻底改变了超时策略、连接管理、用户体验设计。
挑战三:Token 经济学。 传统应用的成本是固定的(服务器+带宽)。AI 应用的成本是变量——每次调用都消耗 Token,而 Token 价格随模型和输入长度变化。一个用户的一天高频使用,可能花掉你一整个月的预算。
挑战四:安全新维度。 传统安全关注的是 SQL 注入、XSS 攻击。AI Agent 还要面对 Prompt 注入——攻击者通过用户输入劫持 Agent 的指令,让它执行未授权操作。更棘手的是,Agent 拥有工具调用能力,一个被劫持的 Agent 可能直接修改数据库。
挑战五:质量评估。 传统应用的质量是二元的——功能正确或错误。AI 应用的输出质量是连续的——“足够好”到”完全不能用”之间有广阔的灰色地带,而且没有标准答案。
1 2 3 4 5 6 7 8 9 10
| async function handleUserMessage(userId: string, message: string) { const response = await agent.generate(message); return response; }
|
理解了这些挑战,我们就能有针对性地设计生产架构。
二、部署架构:从单机到分布式
2.1 容器化:一切的基础
AI Agent 应用的容器化与普通 Web 应用类似,但有几个关键差异需要处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./ RUN npm ci --production=false
COPY . . RUN npm run build
FROM node:22-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S nodejs && \ adduser -S nextjs -u 1001 -G nodejs
COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist COPY --from=builder /app/package.json ./
USER nextjs
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["node", "--max-old-space-size=1024", "dist/server.js"]
|
关键点解释:
- 内存限制。 AI Agent 应用在处理长上下文时内存消耗大。
--max-old-space-size=1024 限制 Node.js 堆内存,防止单个请求 OOM。
- 多阶段构建。 构建产物不含 devDependencies,镜像体积从 1.2GB 降到 180MB。
- 非 root 用户。 安全最佳实践——如果 Agent 被劫持,攻击者拿到的不是 root 权限。
2.2 健康检查:AI 应用的特殊设计
普通应用的健康检查是”HTTP 200”。AI Agent 应用的健康检查需要分层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import { Router } from 'express';
const healthRouter = Router();
healthRouter.get('/health', (_req, res) => { res.status(200).json({ status: 'alive' }); });
healthRouter.get('/ready', async (_req, res) => { const checks = await Promise.allSettled([ checkLLMConnection(), checkDatabaseConnection(), checkRedisConnection(), checkVectorDBConnection(), ]);
const results = checks.map((c, i) => ({ service: ['llm', 'database', 'redis', 'vectorDB'][i], status: c.status === 'fulfilled' ? 'healthy' : 'unhealthy', latency: c.status === 'fulfilled' ? c.value : null, }));
const allHealthy = results.every(r => r.status === 'healthy'); res.status(allHealthy ? 200 : 503).json({ status: allHealthy ? 'ready' : 'degraded', checks: results, timestamp: new Date().toISOString(), }); });
async function checkLLMConnection(): Promise<number> { const start = Date.now(); const response = await fetch(`${process.env.LLM_BASE_URL}/v1/models`, { headers: { 'Authorization': `Bearer ${process.env.LLM_API_KEY}` }, signal: AbortSignal.timeout(5000), }); if (!response.ok) throw new Error(`LLM health check failed: ${response.status}`); return Date.now() - start; }
export { healthRouter };
|
为什么分层? Kubernetes 的 Liveness Probe 失败会重启 Pod,Readiness Probe 失败只会从负载均衡中摘除。如果 LLM API 临时不可用,你应该停止接收新请求(Readiness 失败),但不应该重启应用(Liveness 通过),因为重启并不能解决 LLM 的问题。
2.3 整体架构图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| ┌──────────────────────────────────────────┐ │ 用户(浏览器 / API) │ └──────────────────┬───────────────────────┘ │ ┌────────▼────────┐ │ CDN / WAF │ ← DDoS 防护、静态资源缓存 └────────┬────────┘ │ ┌────────▼────────┐ │ API Gateway │ ← 认证、限流、路由 │ (Kong/Traefik) │ └────────┬────────┘ │ ┌──────────────────┼──────────────────┐ │ │ │ ┌────────▼──────┐ ┌────────▼───────┐ ┌───────▼───────┐ │ Agent Service │ │ Agent Service │ │ Agent Service │ ← 水平伸缩 │ (Pod #1) │ │ (Pod #2) │ │ (Pod #N) │ └───────┬───────┘ └───────┬────────┘ └───────┬───────┘ │ │ │ ┌────────┴─────────────────┴──────────────────┴────────┐ │ 服务层 │ ├──────────┬──────────┬──────────┬──────────┬──────────┤ │ LLM │ Database │ Redis │ VectorDB │ Queue │ │ Proxy │PostgreSQL│ (Cache) │ Qdrant │ BullMQ │ └──────────┴──────────┴──────────┴──────────┴──────────┘ │ ┌────────▼────────┐ │ LLM Provider │ ← OpenAI / Anthropic / 自托管 │ (外部 API) │ └─────────────────┘
|
2.4 LLM 代理层:隔离外部依赖
LLM API 是外部依赖,可能限流、超时、价格变动。在应用和 LLM 之间加一层代理是生产环境的标配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
| import { OpenAI } from 'openai';
const providers = [ { name: 'primary', client: new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), model: 'gpt-4o', weight: 80, }, { name: 'fallback', client: new OpenAI({ apiKey: process.env.ANTHROPIC_API_KEY, baseURL: 'https://api.anthropic.com/v1/', }), model: 'claude-sonnet-4-20250514', weight: 20, }, ];
export class LLMProxy { private circuitBreaker = new Map<string, { failures: number; lastFailure: number }>(); async chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse> { const provider = this.selectProvider(); if (this.isCircuitOpen(provider.name)) { const fallback = providers.find(p => p.name !== provider.name)!; return this.callWithRetry(fallback, messages, options); } return this.callWithRetry(provider, messages, options); } private async callWithRetry( provider: typeof providers[0], messages: ChatMessage[], options?: ChatOptions, maxRetries = 3, ): Promise<ChatResponse> { let lastError: Error; for (let attempt = 0; attempt < maxRetries; attempt++) { try { const response = await provider.client.chat.completions.create({ model: options?.model || provider.model, messages, temperature: options?.temperature ?? 0.7, max_tokens: options?.maxTokens ?? 4096, }, { timeout: 30000 + attempt * 10000, maxRetries: 0, }); this.resetCircuit(provider.name); return { content: response.choices[0].message.content, usage: { promptTokens: response.usage!.prompt_tokens, completionTokens: response.usage!.completion_tokens, }, provider: provider.name, }; } catch (error) { lastError = error as Error; this.recordFailure(provider.name); const delay = Math.min(1000 * Math.pow(2, attempt), 8000); await this.sleep(delay); } } throw new LLMProxyError(`All retries exhausted: ${lastError.message}`, { provider: provider.name, attempts: maxRetries, }); } private selectProvider() { const totalWeight = providers.reduce((s, p) => s + p.weight, 0); let random = Math.random() * totalWeight; for (const p of providers) { random -= p.weight; if (random <= 0) return p; } return providers[0]; } private isCircuitOpen(name: string): boolean { const state = this.circuitBreaker.get(name); if (!state) return false; if (state.failures >= 5) { const elapsed = Date.now() - state.lastFailure; if (elapsed < 60000) return true; this.circuitBreaker.set(name, { failures: 0, lastFailure: 0 }); } return false; } private recordFailure(name: string) { const state = this.circuitBreaker.get(name) || { failures: 0, lastFailure: 0 }; state.failures++; state.lastFailure = Date.now(); this.circuitBreaker.set(name, state); } private resetCircuit(name: string) { this.circuitBreaker.set(name, { failures: 0, lastFailure: 0 }); } private sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } }
|
这层代理解决了三个核心问题:
- 故障转移:主 Provider 挂了,自动切换到备用
- 熔断保护:连续失败时停止重试,避免雪崩
- 超时控制:指数退避重试,防止请求堆积
三、流量入口与安全防护
3.1 API Gateway:第一道防线
在生产环境中,每一个进入 Agent 的请求都必须经过 API Gateway。它负责认证、限流、路由和日志记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| import { Request, Response, NextFunction } from 'express'; import rateLimit from 'express-rate-limit'; import RedisStore from 'rate-limit-redis';
export function authMiddleware(req: Request, res: Response, next: NextFunction) { const apiKey = req.headers['x-api-key'] as string; if (!apiKey) { return res.status(401).json({ error: 'Missing API key' }); } const user = validateApiKey(apiKey); if (!user) { return res.status(401).json({ error: 'Invalid API key' }); } req.user = user; next(); }
export function createRateLimiters(redisClient: any) { const redisStore = new RedisStore({ sendCommand: (...args: string[]) => redisClient.call(...args), }); return { global: rateLimit({ store: redisStore, windowMs: 60 * 1000, max: 1000, message: { error: 'Server is busy, please try later' }, }), perUser: rateLimit({ store: redisStore, windowMs: 60 * 1000, max: 30, keyGenerator: (req) => req.user.id, message: { error: 'Rate limit exceeded for your account' }, }), tokenBudget: async (req: Request, res: Response, next: NextFunction) => { const userId = req.user.id; const today = new Date().toISOString().split('T')[0]; const key = `token_budget:${userId}:${today}`; const consumed = parseInt(await redisClient.get(key) || '0', 10); const dailyLimit = req.user.plan === 'pro' ? 500000 : 50000; if (consumed >= dailyLimit) { return res.status(429).json({ error: 'Daily token budget exceeded', consumed, limit: dailyLimit, resetAt: 'tomorrow 00:00 UTC', }); } next(); }, }; }
|
三维度限流的意义: 全局限流保护服务、用户限流防滥用、Token 限流控成本。三者缺一不可——光有 QPS 限制,一个用户可以慢慢消耗你所有的 Token 预算;光有 Token 限制,一个用户的突发请求可能拖垮整个服务。
3.2 Prompt 注入防护
这是 AI Agent 安全面临的最独特威胁。攻击者通过用户输入,试图覆盖 Agent 的系统指令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
|
export function detectPromptInjection(input: string): { safe: boolean; threats: string[] } { const threats: string[] = []; const lower = input.toLowerCase(); const systemOverridePatterns = [ /ignore\s+(all\s+)?(previous|above|prior)\s+instructions?/i, /disregard\s+(all\s+)?(previous|your)\s+instructions?/i, /you\s+are\s+now\s+(a|an)\s+/i, /new\s+instructions?\s*:/i, /system\s*:\s*/i, /\[system\]/i, ]; const extractionPatterns = [ /(repeat|show|print|reveal|output)\s+(your|the)\s+(system\s+)?(prompt|instructions?|rules?)/i, /what\s+(are|is)\s+your\s+(system\s+)?(prompt|instructions?)/i, ]; const jailbreakPatterns = [ /let'?s\s+play\s+a\s+game/i, /pretend\s+(you\s+are|to\s+be)/i, /roleplay\s+as/i, /hypothetical\s+scenario/i, ]; const delimiterEscapePatterns = [ /```+/, /<\|system\|>/i, /<\|im_start\|>/i, ]; const allPatterns = [ ...systemOverridePatterns, ...extractionPatterns, ...jailbreakPatterns, ...delimiterEscapePatterns, ]; for (const pattern of allPatterns) { if (pattern.test(input)) { threats.push(pattern.source); } } return { safe: threats.length === 0, threats, }; }
export function sanitizeOutput(output: string, systemPrompt: string): string { const systemChunks = systemPrompt.split('\n').filter(c => c.length > 20); let sanitized = output; for (const chunk of systemChunks) { if (sanitized.includes(chunk)) { sanitized = sanitized.replace(chunk, '[REDACTED]'); } } return sanitized; }
export function promptSecurityMiddleware(req: Request, res: Response, next: NextFunction) { const { message } = req.body; if (message) { const check = detectPromptInjection(message); if (!check.safe) { logger.warn('Prompt injection detected', { userId: req.user.id, threats: check.threats, input: message.substring(0, 200), }); return res.status(400).json({ error: 'Input rejected by security policy', code: 'PROMPT_INJECTION_DETECTED', }); } } next(); }
|
3.3 数据安全:最小权限原则
Agent 拥有工具调用能力——它可以读写数据库、执行代码、访问 API。这要求你以最小权限原则设计每一个工具:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
enum Permission { READ_ONLY = 'read', WRITE_OWN = 'write_own', WRITE_ALL = 'write_all', EXECUTE = 'execute', }
const toolPermissions: Record<string, { requiredPermission: Permission; rateLimit?: { calls: number; windowMs: number }; auditLog: boolean; }> = { 'search_documents': { requiredPermission: Permission.READ_ONLY, rateLimit: { calls: 20, windowMs: 60_000 }, auditLog: false, }, 'create_task': { requiredPermission: Permission.WRITE_OWN, rateLimit: { calls: 10, windowMs: 60_000 }, auditLog: true, }, 'delete_task': { requiredPermission: Permission.WRITE_OWN, rateLimit: { calls: 5, windowMs: 60_000 }, auditLog: true, }, 'execute_code': { requiredPermission: Permission.EXECUTE, rateLimit: { calls: 3, windowMs: 60_000 }, auditLog: true, sandbox: true, }, };
export function checkToolPermission( toolName: string, userId: string, userPermissions: Permission[], ) { const config = toolPermissions[toolName]; if (!config) { throw new Error(`Unknown tool: ${toolName}`); } if (!userPermissions.includes(config.requiredPermission)) { throw new PermissionDeniedError( `Tool "${toolName}" requires ${config.requiredPermission} permission`, ); } return config; }
|
核心原则: Agent 不应该拥有比用户更大的权限。如果一个用户没有删除数据的权限,那么 Agent 代用户操作时也不应该能删除数据。
四、可观测性:让 Agent 的行为可见
4.1 三大支柱:日志、指标、链路追踪
传统应用的可观测性关注”请求是否成功”。AI Agent 应用的可观测性还需要回答”Agent 做了什么决策、用了多少 Token、质量如何”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import pino from 'pino';
export const logger = pino({ level: process.env.LOG_LEVEL || 'info', formatters: { level: (label) => ({ level: label }), }, ...(process.env.NODE_ENV === 'production' ? {} : { transport: { target: 'pino-pretty' } }), });
export interface AgentLogEntry { timestamp: string; level: 'debug' | 'info' | 'warn' | 'error'; traceId: string; spanId: string; agentId: string; userId: string; sessionId: string; event: 'tool_call' | 'llm_request' | 'llm_response' | 'error' | 'decision'; data: { [key: string]: unknown; }; tokenUsage?: { prompt: number; completion: number; total: number; estimatedCost: number; }; }
|
4.2 链路追踪:看见 Agent 的每一步
一次 Agent 请求可能涉及多轮 LLM 调用、多个工具执行、数据库查询。链路追踪让你看见完整的执行路径:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| import { trace, context, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('ai-agent-app');
export async function tracedAgentExecution( userId: string, sessionId: string, message: string, ) { return tracer.startActiveSpan('agent.execute', async (span) => { try { span.setAttributes({ 'agent.user_id': userId, 'agent.session_id': sessionId, 'agent.message_length': message.length, 'agent.message_hash': hashMessage(message), }); const intent = await tracer.startActiveSpan('agent.understand_intent', async (s) => { const result = await understandIntent(message); s.setAttribute('agent.intent', result.type); s.setAttribute('agent.confidence', result.confidence); s.end(); return result; }); const toolResults = await tracer.startActiveSpan('agent.execute_tools', async (s) => { const tools = selectTools(intent); s.setAttribute('agent.tools_selected', tools.map(t => t.name).join(',')); const results = await Promise.all( tools.map(tool => executeToolTraced(tool, intent)), ); s.end(); return results; }); const response = await tracer.startActiveSpan('agent.generate_response', async (s) => { const result = await generateResponse(message, toolResults); s.setAttribute('llm.model', result.model); s.setAttribute('llm.tokens_prompt', result.usage.promptTokens); s.setAttribute('llm.tokens_completion', result.usage.completionTokens); s.setAttribute('llm.latency_ms', result.latencyMs); s.end(); return result; }); span.setAttributes({ 'agent.total_tokens': response.usage.totalTokens, 'agent.total_duration_ms': Date.now() - span.startTime?.[0]! * 1000, }); span.setStatus({ code: SpanStatusCode.OK }); return response; } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR, message: (error as Error).message, }); throw error; } finally { span.end(); } }); }
async function executeToolTraced(tool: Tool, intent: Intent) { return tracer.startActiveSpan(`tool.${tool.name}`, async (span) => { span.setAttributes({ 'tool.name': tool.name, 'tool.args': JSON.stringify(intent.params), }); const start = Date.now(); try { const result = await tool.execute(intent.params); span.setAttribute('tool.duration_ms', Date.now() - start); span.setAttribute('tool.success', true); return result; } catch (error) { span.setAttribute('tool.success', false); span.setAttribute('tool.error', (error as Error).message); throw error; } finally { span.end(); } }); }
|
追踪数据的可视化效果: 在 Jaeger 或 Grafana Tempo 中,你能看到一条完整的调用链:
1 2 3 4 5 6 7 8 9 10
| agent.execute (3.2s) ├── agent.understand_intent (450ms) │ └── llm.request → llm.response (430ms, 120 tokens) ├── agent.execute_tools (2.1s) │ ├── tool.search_documents (800ms) │ ├── tool.query_database (150ms) │ └── tool.create_summary (1.1s) │ └── llm.request → llm.response (1.0s, 850 tokens) └── agent.generate_response (650ms) └── llm.request → llm.response (630ms, 400 tokens)
|
哪个环节慢、哪个环节消耗 Token 最多,一目了然。
4.3 质量监控:不只是”有没有报错”
传统监控只关注技术指标(CPU、内存、延迟)。AI Agent 应用还需要质量指标——回答好不好、准不准、有没有幻觉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
interface QualityMetrics { responseLatencyMs: number; tokenEfficiency: number; toolCallSuccessRate: number; hallucinationScore?: number; userFeedbackScore?: number; userFeedbackText?: string; isMarkedHelpful?: boolean; }
export class QualityMonitor { private readonly sampleRate = 0.1; async evaluate( requestId: string, response: AgentResponse, userMessage: string, ): Promise<QualityMetrics | null> { if (Math.random() > this.sampleRate) { return null; } const evaluation = await this.callEvaluationModel({ userQuery: userMessage, agentResponse: response.content, toolsUsed: response.tools.map(t => t.name), context: response.contextSummary, }); const metrics: QualityMetrics = { responseLatencyMs: response.latencyMs, tokenEfficiency: this.calculateTokenEfficiency(response), toolCallSuccessRate: this.calculateToolSuccessRate(response), hallucinationScore: evaluation.hallucinationRisk, }; await this.metricsClient.write('agent_quality', metrics, { tags: { agentId: response.agentId, requestId }, }); return metrics; } detectHallucination(response: string, context: string[]): number { const claims = extractClaims(response); let unsupported = 0; for (const claim of claims) { const hasSupport = context.some(c => computeSimilarity(claim, c) > 0.7 ); if (!hasSupport) unsupported++; } return claims.length > 0 ? unsupported / claims.length : 0; } }
|
4.4 实战监控面板
将以上指标整合到 Grafana 面板中,你的运维团队就能像监控传统服务一样监控 AI Agent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ┌─────────────────────────────────────────────────────────────────┐ │ AI Agent 运维仪表盘 │ ├───────────────────┬───────────────────┬───────────────────────┤ │ 请求量 (RPM) │ 平均延迟 (P50) │ Token 消耗 (今天) │ │ ▲ 1,234 │ ▲ 2.3s │ ▲ 4.2M / 10M │ │ ─────── │ ─────── │ ─────── │ │ 周环比 +12% │ 周环比 +0.1s │ 预算使用 42% │ ├───────────────────┼───────────────────┼───────────────────────┤ │ 工具调用成功率 │ 质量评分 (平均) │ 安全事件 (24h) │ │ ▲ 98.5% │ ▲ 4.2 / 5 │ ▲ 3 (已拦截) │ │ ─────── │ ─────── │ ─────── │ │ ↓ 0.3% 需关注 │ 用户反馈 +15% │ 0 起成功注入 │ ├───────────────────┴───────────────────┴───────────────────────┤ │ 关键告警 │ │ ⚠ Token 消耗速率异常 - 当前消耗速度将在 3 天内耗尽月度预算 │ │ ⚠ LLM API 延迟 P99 超过 15s - 已自动切换到备用 Provider │ │ ⚠ 工具 "execute_code" 调用失败率 > 5% - 检查沙箱环境 │ └─────────────────────────────────────────────────────────────────┘
|
五、成本优化:让每一分钱花在刀刃上
5.1 Token 经济学
AI Agent 应用的运营成本与传统应用有本质区别:成本与使用量正相关,而非固定的。
一个简单的计算:假设使用 GPT-4o,输入 $2.5/M tokens,输出 $10/M tokens。一个日均 1000 次对话的应用,每次平均消耗 2000 输入 + 500 输出 Token:
1 2 3 4
| 日成本 = 1000 × (2000 × $2.5/1M + 500 × $10/1M) = 1000 × ($0.005 + $0.005) = $10/天 = $300/月
|
如果优化到 1500 输入 + 300 输出 Token(减少冗余上下文):
1 2 3 4
| 日成本 = 1000 × (1500 × $2.5/1M + 300 × $10/1M) = 1000 × ($0.00375 + $0.003) = $6.75/天 = $202.50/月
|
月度节省 33%。 这就是 Token 优化的价值。
5.2 语义缓存:最高 ROI 的优化
很多用户的提问是相似的。如果每次都调用 LLM,就是白花钱。语义缓存通过向量相似度匹配,让相似问题复用之前的回答。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| import { QdrantClient } from '@qdrant/js-client';
export class SemanticCache { private qdrant: QdrantClient; private readonly collection = 'agent_cache'; private readonly similarityThreshold = 0.95; constructor() { this.qdrant = new QdrantClient({ url: process.env.QDRANT_URL, apiKey: process.env.QDRANT_API_KEY, }); } async get(query: string, context: CacheContext): Promise<CacheHit | null> { const embedding = await this.embed(query); const results = await this.qdrant.search(this.collection, { vector: embedding, filter: { must: [ { key: 'agentId', match: { value: context.agentId } }, { key: 'modelVersion', match: { value: context.modelVersion } }, ], }, limit: 1, score_threshold: this.similarityThreshold, }); if (results.length === 0) return null; const cached = results[0].payload as CachedResponse; const age = Date.now() - cached.timestamp; if (age > this.getMaxAge(cached)) { return null; } await this.recordCacheHit(cached, results[0].score); return { response: cached.response, similarity: results[0].score, cachedAt: cached.timestamp, savedTokens: cached.tokenUsage, }; } async set( query: string, response: string, context: CacheContext, tokenUsage: TokenUsage, ): Promise<void> { const embedding = await this.embed(query); await this.qdrant.upsert(this.collection, { points: [{ id: crypto.randomUUID(), vector: embedding, payload: { query, response, agentId: context.agentId, modelVersion: context.modelVersion, tokenUsage, timestamp: Date.now(), }, }], }); } shouldCache(query: string, context: CacheContext): boolean { if (/今天|现在|刚刚|刚才/i.test(query)) return false; if (/\b(my|our|this)\s+\w+\s+(id|name|file|project)\b/i.test(query)) return false; if (/什么是|解释一下|how\s+to|what\s+is|explain/i.test(query)) return true; return false; } private async embed(text: string): Promise<number[]> { const response = await fetch('https://api.openai.com/v1/embeddings', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'text-embedding-3-small', input: text.substring(0, 8000), }), }); const data = await response.json(); return data.data[0].embedding; } }
|
实际效果: 在客服场景中,语义缓存通常能命中 20-40% 的请求,直接节省相应比例的 LLM 调用成本。Embedding 的成本极低(约为主模型成本的 1/100),ROI 非常高。
5.3 模型路由:用对的模型做对的事
不是所有任务都需要最贵的模型。模型路由策略根据任务复杂度动态选择模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
type TaskComplexity = 'trivial' | 'simple' | 'moderate' | 'complex';
const modelMapping: Record<TaskComplexity, { model: string; maxTokens: number }> = { trivial: { model: 'gpt-4o-mini', maxTokens: 500 }, simple: { model: 'gpt-4o-mini', maxTokens: 1000 }, moderate: { model: 'gpt-4o', maxTokens: 2000 }, complex: { model: 'gpt-4o', maxTokens: 4096 }, };
export class ModelRouter { assessComplexity(message: string, context: AgentContext): TaskComplexity { const len = message.length; const hasCode = /```|function|class|const|import/.test(message); const hasReasoning = /为什么|分析|比较|设计|架构|why|analyze|compare|design/i.test(message); const toolCount = context.availableTools.length; if (hasReasoning && hasCode && toolCount > 3) return 'complex'; if (hasReasoning || (hasCode && toolCount > 1)) return 'moderate'; if (len > 100 || toolCount > 0) return 'simple'; return 'trivial'; } route(message: string, context: AgentContext) { const complexity = this.assessComplexity(message, context); const config = modelMapping[complexity]; logger.info('Model routing decision', { complexity, model: config.model, messageLength: message.length, }); return config; } }
|
成本影响: 在实际项目中,约 60% 的请求可以用 mini 模型处理。综合下来,模型路由可以将平均成本降低 50-70%。
5.4 成本仪表盘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
export class CostTracker { private readonly pricing = { 'gpt-4o': { input: 2.5e-6, output: 10e-6 }, 'gpt-4o-mini': { input: 0.15e-6, output: 0.6e-6 }, 'text-embedding-3-small': { input: 0.02e-6, output: 0 }, }; async recordUsage(userId: string, model: string, usage: TokenUsage) { const cost = this.calculateCost(model, usage); const today = new Date().toISOString().split('T')[0]; const key = `cost:${userId}:${today}`; await redis.hincrbyfloat(key, 'total', cost); await redis.hincrbyfloat(key, `model:${model}`, cost); await redis.hincrby(key, 'requests', 1); await redis.expireat(key, nextMidnightTimestamp()); const dailyTotal = await redis.hget(key, 'total'); if (parseFloat(dailyTotal) > this.getAlertThreshold(userId)) { await this.triggerCostAlert(userId, parseFloat(dailyTotal)); } } private calculateCost(model: string, usage: TokenUsage): number { const price = this.pricing[model]; if (!price) return 0; return usage.promptTokens * price.input + usage.completionTokens * price.output; } }
|
六、弹性伸缩与容错
6.1 伸缩策略
AI Agent 应用的伸缩策略与传统应用不同——因为单个请求耗时长、资源消耗大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: agent-service-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: agent-service minReplicas: 3 maxReplicas: 30 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 70 - type: Pods pods: metric: name: active_agent_requests target: type: AverageValue averageValue: "10" behavior: scaleUp: stabilizationWindowSeconds: 30 policies: - type: Percent value: 100 periodSeconds: 30 scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 10 periodSeconds: 60
|
为什么”快扩慢缩”? AI 请求是长时任务——一个请求可能需要 30 秒。如果缩容太快,正在处理的请求会被中断。快速扩容应对突发流量,缓慢缩容等待正在进行的请求完成。
6.2 降级策略
当系统压力过大或 LLM 不可用时,需要有优雅的降级方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
export class DegradationManager { private currentLevel: 'normal' | 'degraded' | 'emergency' = 'normal'; async handleRequest( message: string, context: AgentContext, ): Promise<AgentResponse> { switch (this.currentLevel) { case 'normal': return this.fullAgentFlow(message, context); case 'degraded': return this.simplifiedFlow(message, context); case 'emergency': return this.emergencyResponse(message, context); } } async monitor() { setInterval(async () => { const metrics = await this.getSystemMetrics(); if (metrics.llmErrorRate > 0.3 || metrics.queueDepth > 1000) { this.currentLevel = 'emergency'; logger.error('System entered emergency mode', metrics); } else if (metrics.llmLatencyP99 > 30000 || metrics.cpuUsage > 0.85) { this.currentLevel = 'degraded'; logger.warn('System entered degraded mode', metrics); } else { this.currentLevel = 'normal'; } }, 10000); } private async simplifiedFlow(message: string, context: AgentContext) { const response = await this.llmProxy.chat([ { role: 'system', content: context.systemPrompt }, { role: 'user', content: message }, ], { maxTokens: 1000 }); return { content: response.content + '\n\n*(系统当前处于降级模式,部分功能可能不可用)*', degraded: true, toolsUsed: [], }; } private async emergencyResponse(message: string, context: AgentContext) { const cached = await this.cache.get(message, context); if (cached) { return { content: cached.response, cached: true, toolsUsed: [], }; } return { content: '抱歉,系统当前负载过高,暂时无法处理您的请求。请稍后再试。', emergency: true, toolsUsed: [], }; } }
|
6.3 异步任务队列
对于耗时特别长的 Agent 任务(如代码生成、深度分析),同步等待不现实。使用消息队列将任务异步化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| import { Queue, Worker } from 'bullmq';
export const agentTaskQueue = new Queue('agent-tasks', { connection: { host: 'redis', port: 6379 }, defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 5000, }, removeOnComplete: 100, removeOnFail: 50, timeout: 120000, }, });
app.post('/api/agent/async', authMiddleware, async (req, res) => { const { message } = req.body; const priority = req.user.plan === 'pro' ? 1 : 10; const job = await agentTaskQueue.add( 'process', { userId: req.user.id, sessionId: req.session.id, message, }, { priority }, ); res.status(202).json({ taskId: job.id, status: 'queued', estimatedWaitTime: await estimateWaitTime(job.id), resultUrl: `/api/agent/result/${job.id}`, }); });
const worker = new Worker('agent-tasks', async (job) => { const { userId, sessionId, message } = job.data; try { await job.updateProgress(10); const context = await buildAgentContext(userId, sessionId); await job.updateProgress(30); const response = await agent.generate(message, { context }); await job.updateProgress(80); await storeResult(job.id, response); await job.updateProgress(100); await notifyClient(userId, { taskId: job.id, status: 'completed' }); return response; } catch (error) { logger.error('Agent task failed', { jobId: job.id, error }); throw error; } }, { connection: { host: 'redis', port: 6379 }, concurrency: 5, });
|
七、CI/CD:AI 应用的持续交付
7.1 测试策略
AI 应用的测试不能照搬传统方式。你需要三层测试体系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
|
describe('Tool: search_documents', () => { it('should return results for valid query', async () => { const result = await searchDocumentsTool.execute({ query: 'React hooks' }); expect(result.results).toBeDefined(); expect(result.results.length).toBeGreaterThan(0); }); it('should handle empty query gracefully', async () => { const result = await searchDocumentsTool.execute({ query: '' }); expect(result.results).toEqual([]); }); });
describe('Agent integration flow', () => { it('should call correct tools for "search and summarize" intent', async () => { const mockLLM = createMockLLM({ toolCalls: [ { name: 'search_documents', args: { query: 'React performance' } }, ], finalResponse: 'React performance can be improved by...', }); const agent = new Agent({ llm: mockLLM, tools: [searchDocumentsTool] }); const result = await agent.generate('搜索 React 性能优化相关文档并总结'); expect(mockLLM.callCount).toBe(2); expect(result.toolsUsed[0].name).toBe('search_documents'); }); });
describe('Agent quality assessment', () => { const testCases = [ { input: '帮我写一个 React useEffect 的使用示例', assertions: { containsCode: true, mentionsCleanup: true, maxLatency: 10000, maxTokens: 1000, }, }, { input: '什么是闭包?', assertions: { containsExplanation: true, hasExample: true, maxLatency: 5000, maxTokens: 800, }, }, ]; for (const testCase of testCases) { it(`quality: "${testCase.input.substring(0, 30)}..."`, async () => { const results = await Promise.all( Array.from({ length: 5 }, () => agent.generate(testCase.input)), ); const passCount = results.filter(r => evaluateAssertions(r, testCase.assertions) ).length; expect(passCount).toBeGreaterThanOrEqual(4); }); } });
|
7.2 评估流水线
在 CI/CD 中加入 AI 评估步骤,确保模型或 Prompt 变更不会降低质量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| name: AI Agent CI/CD
on: push: branches: [main, develop] pull_request: branches: [main]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - run: npm ci - run: npm run test:unit - run: npm run test:integration - run: npm run lint - run: npm run typecheck evaluate: needs: test runs-on: ubuntu-latest if: | contains(github.event.head_commit.message, '[evaluate]') || github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - run: npm ci - name: Run evaluation suite env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} EVAL_DATASET_PATH: ./eval/dataset.jsonl run: npx tsx scripts/run-evaluation.ts - name: Compare with baseline run: npx tsx scripts/compare-eval-results.ts \ --current ./eval/results/latest.json \ --baseline ./eval/results/baseline.json \ --threshold 0.05 - name: Upload evaluation report if: always() uses: actions/upload-artifact@v4 with: name: evaluation-report path: ./eval/results/ build: needs: evaluate runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build Docker image run: | docker build -t registry.example.com/agent-service:${{ github.sha }} . docker tag registry.example.com/agent-service:${{ github.sha }} \ registry.example.com/agent-service:latest - name: Push to registry run: docker push registry.example.com/agent-service:${{ github.sha }} deploy: needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: Deploy to staging (canary) run: | # 灰度部署:先部署 10% 流量到新版本 kubectl set image deployment/agent-service \ agent-service=registry.example.com/agent-service:${{ github.sha }} \ -n staging sleep 300 ERROR_RATE=$(curl -s prometheus/api/v1/query \ 'rate(http_requests_total{status=~"5.."}[5m])' | jq '.data.result[0].value[1]') if (( $(echo "$ERROR_RATE > 0.05" | bc -l) )); then echo "Error rate too high, rolling back" kubectl rollout undo deployment/agent-service -n staging exit 1 fi kubectl set image deployment/agent-service \ agent-service=registry.example.com/agent-service:${{ github.sha }} \ -n production
|
7.3 评估脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| import { readFileSync, writeFileSync } from 'fs';
interface EvalCase { id: string; input: string; expected: { containsKeywords?: string[]; hasCode?: boolean; minHelpfulScore?: number; maxToxicityScore?: number; }; }
interface EvalResult { caseId: string; input: string; output: string; passed: boolean; scores: { helpfulness: number; correctness: number; safety: number; }; metrics: { latencyMs: number; tokenUsage: { prompt: number; completion: number }; cost: number; }; }
async function runEvaluation(): Promise<void> { const dataset: EvalCase[] = readFileSync(process.env.EVAL_DATASET_PATH!, 'utf8') .split('\n') .filter(Boolean) .map(JSON.parse); const results: EvalResult[] = []; for (const testCase of dataset) { const start = Date.now(); const response = await agent.generate(testCase.input); const latency = Date.now() - start; const scores = await evaluateResponse( testCase.input, response.content, testCase.expected, ); const passed = checkAssertions(scores, testCase.expected); results.push({ caseId: testCase.id, input: testCase.input, output: response.content, passed, scores, metrics: { latencyMs: latency, tokenUsage: response.usage, cost: calculateCost(response), }, }); process.stdout.write(` [${results.length}/${dataset.length}] ${testCase.id}: ${passed ? '✓' : '✗'}\n`); } const summary = { total: results.length, passed: results.filter(r => r.passed).length, passRate: results.filter(r => r.passed).length / results.length, avgLatencyMs: avg(results.map(r => r.metrics.latencyMs)), avgCost: avg(results.map(r => r.metrics.cost)), avgHelpfulness: avg(results.map(r => r.scores.helpfulness)), timestamp: new Date().toISOString(), gitCommit: process.env.GITHUB_SHA, }; writeFileSync('./eval/results/latest.json', JSON.stringify({ summary, results }, null, 2)); console.log('\n=== Evaluation Summary ==='); console.log(`Pass rate: ${(summary.passRate * 100).toFixed(1)}%`); console.log(`Avg latency: ${summary.avgLatencyMs.toFixed(0)}ms`); console.log(`Avg cost: $${summary.avgCost.toFixed(4)}`); console.log(`Avg helpfulness: ${summary.avgHelpfulness.toFixed(2)}/1.0`); if (summary.passRate < 0.85) { console.error('Evaluation pass rate below threshold (85%)'); process.exit(1); } }
runEvaluation().catch(console.error);
|
八、运维实战清单
8.1 上线前检查清单
将以下清单作为每次发布前的硬性检查项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| ## AI Agent 应用上线检查清单
### 基础设施 - [ ] Docker 镜像已通过安全扫描(Trivy / Snyk) - [ ] Kubernetes 副本数 ≥ 3(跨可用区分布) - [ ] 资源 Request/Limit 已合理配置 - [ ] HPA 配置已验证(压测确认伸缩正常) - [ ] 数据库连接池大小已调优 - [ ] Redis 内存限制和淘汰策略已配置
### 安全 - [ ] API Key 认证已启用 - [ ] 多维度限流已配置(QPS + 用户 + Token) - [ ] Prompt 注入检测已启用 - [ ] 工具权限按最小化原则配置 - [ ] 敏感数据已加密(API Key、用户数据) - [ ] 审计日志已开启(所有写操作记录) - [ ] CORS 配置正确(非 * 通配) - [ ] HTTPS 已强制启用
### 可观测性 - [ ] 结构化日志已配置(JSON 格式) - [ ] 链路追踪已接入(OpenTelemetry) - [ ] 关键指标已上报(QPS、延迟、错误率、Token 用量) - [ ] Grafana 面板已创建 - [ ] 告警规则已配置(延迟、错误率、Token 预算) - [ ] 错误聚合已接入(Sentry)
### LLM 相关 - [ ] 多 Provider 故障转移已配置 - [ ] 熔断器已启用 - [ ] 语义缓存已启用并验证命中率 - [ ] 模型路由策略已配置 - [ ] Token 预算告警已设置 - [ ] 降级策略已验证
### CI/CD - [ ] 单元测试通过率 100% - [ ] 集成测试通过 - [ ] 评估测试通过率 ≥ 85% - [ ] 灰度发布配置已准备 - [ ] 回滚脚本已验证
### 应急 - [ ] 降级开关已准备(feature flag) - [ ] 回滚流程已文档化 - [ ] 值班联系人和升级路径已明确 - [ ] 事故响应模板已准备
|
8.2 事故响应流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
|
export class IncidentResponder { private rules: IncidentRule[] = [ { name: 'LLM API 全线超时', detect: (m) => m.llmErrorRate > 0.5 && m.llmLatencyP99 > 60000, actions: [ { type: 'switch_provider', target: 'fallback' }, { type: 'enable_degradation', level: 'degraded' }, { type: 'notify', channel: 'pagerduty', severity: 'P1' }, ], }, { name: 'Token 消耗异常飙升', detect: (m) => m.tokenUsageRate > m.expectedRate * 3, actions: [ { type: 'enable_strict_rate_limit' }, { type: 'notify', channel: 'slack', severity: 'P2' }, { type: 'auto_block_abusive_users' }, ], }, { name: 'Prompt 注入攻击', detect: (m) => m.securityEvents > 10, actions: [ { type: 'block_attacking_ips' }, { type: 'notify', channel: 'pagerduty', severity: 'P1' }, { type: 'enable_strict_input_filter' }, ], }, ]; async evaluate(metrics: SystemMetrics) { for (const rule of this.rules) { if (rule.detect(metrics)) { await this.executeActions(rule); await this.createIncident(rule, metrics); } } } private async executeActions(rule: IncidentRule) { for (const action of rule.actions) { try { switch (action.type) { case 'switch_provider': await llmProxy.switchTo(action.target); break; case 'enable_degradation': await degradationManager.setLevel(action.level); break; case 'enable_strict_rate_limit': await rateLimitManager.switchToStrictMode(); break; case 'block_attacking_ips': await firewall.blockIPs(await getAttackSourceIPs()); break; case 'notify': await notifier.send(action.channel, { title: `Incident: ${rule.name}`, severity: action.severity, timestamp: new Date().toISOString(), }); break; } } catch (error) { logger.error(`Failed to execute action: ${action.type}`, error); } } } }
|
九、成本与性能的平衡术
在结束之前,让我们用一组真实的数据来总结生产化优化的综合效果。
假设一个 AI Agent 应用日均 10,000 次请求,以下是优化前后的对比:
1 2 3 4 5 6 7 8 9
| 优化前(裸奔) 优化后(生产级) ──────────── ────────────── 平均延迟 (P50) 8.2s 2.1s (-74%) 延迟 (P99) 45s 6.5s (-86%) 日均 Token 消耗 45M 18M (-60%) 日均成本 $180 $45 (-75%) 错误率 2.1% 0.3% (-86%) 缓存命中率 0% 32% (+32%) 安全事件 3-5 次/周 0 次 (全部拦截)
|
优化手段组合:
- 语义缓存 → 减少 32% 的 LLM 调用
- 模型路由 → 60% 请求使用 mini 模型
- Prompt 精简 → 平均输入 Token 减少 25%
- 流式输出 → 用户感知延迟降低 50%
- 连接池复用 → 网络开销减少 30%
关键洞察: 最大的性能提升不是来自更强的硬件,而是来自减少不必要的 LLM 调用。缓存命中一次,就是省掉了 2-30 秒的延迟和对应的 Token 费用。
结语:五篇之后,回到起点
在这个系列的五篇文章中,我们走过了一条完整的路径:
第一篇谈的是认知——AI Agent 不是聊天机器人,是能自主规划、调用工具、迭代执行的智能体。前端开发者身处这场变革的最佳位置:Agent 的交互界面在浏览器中,而那是我们的主场。
第二篇谈的是工具——工具是 Agent 的手和脚。好的工具设计让 Agent 从”会说话”变成”能干活”。从 Function Calling 到 MCP 协议,工具生态正在标准化。
第三篇谈的是协作——单个 Agent 的能力是有限的。多 Agent 编排让 PM、前端、后端、QA 各司其职,用分工解决上下文污染和能力冲突。
第四篇谈的是记忆——没有记忆的 Agent 是一次性的。从短期上下文窗口到长期向量存储,记忆系统让 Agent 从”工具”进化为”伙伴”。
而这第五篇,谈的是生产化——以上所有能力,只有在真实流量、真实用户、真实预算下稳定运行,才算真正完成了交付。
这五步,构成了一个前端工程师向 AI Agent 工程师跃迁的完整地图:
1 2 3 4 5 6 7
| 认知 → 工具 → 协作 → 记忆 → 生产化 │ │ │ │ │ │ │ │ │ └── 部署、监控、成本、弹性 │ │ │ └── 跨会话状态持久化 │ │ └── 多 Agent 编排架构 │ └── Function Calling / MCP / 工具设计模式 └── Agent 范式、Vercel AI SDK、第一个 ToolLoopAgent
|
但请记住,技术栈会更新,框架会迭代,模型会更强大。今天写的代码,可能明年就需要重构。真正不变的是三个底层能力:
- 系统性思维——把 AI Agent 当作一个完整系统来设计,而不是一个聊天功能来开发
- 工程化能力——从原型到生产,每一步都需要扎实的工程实践
- 持续学习的习惯——这个领域每三个月就有一次范式迭代
AI 不会取代前端开发者。但会用 AI 构建完整应用的前端开发者,会取代不会的人。
系列到此结束,但实践才刚刚开始。去构建你的 Agent,去部署它,去在真实流量中打磨它。代码是写出来的,Agent 是运营出来的。
本文是《前端 + AI Agent 开发实战》系列第五篇(完结篇)。
系列文章: