构建卡顿先从依赖链查起
说明:本文以 AI 产品场景说明降级、版本测试和预算控制。日志、成本、时延与成功率均为示例,不代表实际运行结果。
三个月前,团队为了让旗下的 Markdown 笔记与文档协作工具“智能化”,发起了一项实验:引入基于 LLM Agent 的全自动编辑助手。最初的设想非常美好——用户只要选中一段文本,输入任何指令(如“重构这段逻辑”、“生成对应思维导图”、“补充示例代码”),LLM 就会自动解析意图并调用后端的函数链完成操作。
然而,这套系统上线测试仅两周,我们就被真实的运维数据浇了一头冷水:
- 延迟崩溃:由于将意图识别、上下文检索与代码生成全部交由 GPT-4 串行处理,单次操作的平均 P95 延迟高达 8.6 秒,用户吐槽“比自己写还慢”。
- 成本雪崩:高频小文本修改触发了完整的 Prompt 携带,API 费用在第 10 天就耗尽了整个季度的预算。
- 状态机失控:模型幻觉导致生成的 JSON 格式偶发损坏,直接把用户的本地文档给清空了一半。
这次失败的实验迫使我们重新思考:独立产品在引入 AI 驱动生产力工具时,究竟应该如何平衡智能化体验与确定的工程质量?
1. 失败架构 vs 二段式降级架构
最初导致惨败的核心原因,在于过分迷信 LLM 的通用能力,将本属于本地确定性算法的逻辑也全权托管给大模型。
2. 核心实现:本地快速意图路由与 AST 剪枝
为了将延迟从 8 秒压缩至 300ms 以内,我们实现了一套轻量级的本地意图路由分发器。简单的文本变换(如格式化、转换 Markdown 标题、去除空行)直接由本地正则与 AST 引擎处理,只有真正的创想类指令才会透传给远端 LLM。
// services/ai/intent-router.ts export type IntentType = 'LOCAL_FORMAT' | 'LOCAL_TABLE' | 'LLM_REWRITE' | 'LLM_GENERATE'; interface RouterResult { type: IntentType; handler?: (text: string) => string; payload?: Record<string, any>; } export class FastIntentRouter { // 本地意图正则表达式词表 private static localRules: Array<{ reg: RegExp; type: IntentType; transform: (t: string) => string }> = [ { reg: /^format:?\s*/i, type: 'LOCAL_FORMAT', transform: (text) => text.trim().replace(/\n{3,}/g, '\n\n'), }, { reg: /^to-table:?\s*/i, type: 'LOCAL_TABLE', transform: (text) => { const lines = text.trim().split('\n'); return lines.map((l) => `| ${l.split(/[,,\t]/).join(' | ')} |`).join('\n'); }, }, ]; public static route(inputPrompt: string, selectedText: string): RouterResult { // 1. 优先匹配本地硬核操作 for (const rule of this.localRules) { if (rule.reg.test(inputPrompt)) { return { type: rule.type, handler: () => rule.transform(selectedText), }; } } // 2. 判断是否满足 LLM 最简触发条件(控制 Token 携带) if (selectedText.length > 4000) { // 超长文本智能剪枝,提取关键句 selectedText = selectedText.slice(0, 2000) + '\n...[已自动剪枝]...\n' + selectedText.slice(-1500); } return { type: inputPrompt.includes('生成') ? 'LLM_GENERATE' : 'LLM_REWRITE', payload: { prompt: inputPrompt, context: selectedText }, }; } }3. 核心实现:可中断的 SSE 流式生成控制器
生产力工具应给用户绝对的控制权。如果用户发现 AI 生成方向偏差,应能按下Esc或点击“中断”按钮立即停止 Token 消费并回滚文档。
以下展示带安全熔断能力的 SSE(Server-Sent Events)消费封装:
// services/ai/stream-controller.ts export class StreamAIController { private abortController: AbortController | null = null; public async executeStreamTask( endpoint: string, payload: Record<string, any>, onDelta: (chunk: string) => void, onError: (err: Error) => void ): Promise<void> { this.abortController = new AbortController(); try { const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: this.abortController.signal, }); if (!response.ok || !response.body) { throw new Error(`HTTP Error: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let accumulatedText = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); accumulatedText += chunk; // 增量吐出给 UI Shadow Layer onDelta(chunk); } } catch (err: any) { if (err.name === 'AbortError') { console.warn('[AI Stream] 用户手动中断生成'); } else { onError(err); } } finally { this.abortController = null; } } // 用户按下 Esc 或切换视窗时触发 public cancel() { if (this.abortController) { this.abortController.abort(); } } }4. 成本与性能优化指标对照
经过二段式架构重构后,产品关键技术指标变化如下:
| 指标维度 | 失败实验阶段 (全 LLM 包办) | 改造升级后 (规则+LLM二段式) | 优化幅度 |
|---|---|---|---|
| 首字响应时间 (TTFB) | 3.2s | 180ms | ⬇️ 94.3% |
| P95 任务完成耗时 | 8.6s | 1.1s (包含流式展示) | ⬇️ 87.2% |
| 单用户日均 Token 消费 | 45,000 Tokens | 4,200 Tokens | ⬇️ 90.6% |
| JSON 格式解析崩溃率 | 4.8% | 0.01% (引入本地阴影层) | ⬇️ 99.7% |
5. 独立开发者打造 AI 工具的 4 条止损总结
- 别用 LLM 替换正则表达式:格式化、简单语法转换、字符统计等确定性逻辑,始终用本地代码实现,性能提升百倍且完全免费。
- 始终提供影子阴影层 (Shadow Buffer):AI 生成的内容在确认合入用户原文档前,应在独立的临时视窗或 Draft 状态中校验,绝不能直接修改用户主数据栈。
- 前端控制流中断:务必支持
AbortController传输中断,既帮用户节省等待时间,也帮开发者节省 API 账单成本。 - 渐进式智能化:不要试图一次性做“全能 AI 代理”。先从小而确定的 Prompt 场景切入,逐个打磨体验,才是独立产品存活的根本。