三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

AI 工作流失败复盘:先定位输入、模型还是工具调用

AI 工作流失败复盘:先定位输入、模型还是工具调用

AI 工作流失败复盘:先定位输入、模型还是工具调用

AI 工作流失败时,先判断输入、模型输出还是工具执行最早偏离预期。为每个节点保存脱敏摘要、状态码与版本信息,便于构造相同条件并重放状态机路径。

1. 问题现象与排查入口

排查时可抽取脱敏样本,用抓包与日志重放还原调用顺序:

# 过滤排查业务网关日志中的异常请求序列 grep -E "ERROR|ExtractFailed" /var/log/workflow/app.log | tail -n 50 # 查找匹配 TraceID 的原始日志(发现日志中缺失了发给大模型的完整 Prompt) jq '. | select(.trace_id=="tr-9023812")' /var/log/workflow/trace.json

可以从三个容易缺记录的节点开始检查:

  1. Prompt 模版未版本化:代码里用的 Prompt 字符串是动态拼接的,线上跑的到底是哪个模版版本无法回溯。
  2. 入参与出参快照缺失:出于隐私限制,上游过滤掉了原始文本,导致只留下了最终报错,中间 LLM 返回的原始字符串(Raw Output)直接丢弃了。
  3. 降级保护缺失:当 LLM 没有按 JSON Schema 返回时,系统没有自动修复(Auto-Repair)和硬编码兜底机制,直接抛出未捕获异常。

2. 具备完整证据链捕获的工程实现

为了让每一次失败实验和线上故障都能“留有痕迹”,可以在 Node.js/TypeScript 服务端重构工单处理流程。

核心要求有两个:一是在请求全生命周期埋入不可变 Trace Snapshot;二是引入确定性的 Schema 校验与兜底降级。

import { createHash } from 'crypto'; interface AuditSnapshot { traceId: string; templateVersion: string; rawInput: string; renderedPrompt: string; rawOutput?: string; parsedResult?: Record<string, any>; status: 'SUCCESS' | 'FORMAT_ERROR' | 'TIMEOUT' | 'FALLBACK'; errorDetail?: string; durationMs: number; } export class TicketProcessor { private templateVersion = "v2.1.4"; constructor( private llmClient: { complete: (prompt: string) => Promise<string> }, private auditStore: { save: (snapshot: AuditSnapshot) => Promise<void> } ) {} private renderPrompt(ticketContent: string): string { return `You are a professional customer support classifier. Analyze the following ticket and return STRICT JSON with format: {"category": string, "priority": "LOW"|"HIGH"}. Do NOT add markdown block quotes or explanations. Ticket Content: ${ticketContent}`; } private tryParseJson(text: string): Record<string, any> | null { try { // 清楚可能存在的 markdown 标记 (如 ```json ... ```) const cleaned = text.replace(/```json\s*|\s*```/g, '').trim(); return JSON.parse(cleaned); } catch { return null; } } public async processTicket(traceId: string, ticketContent: string): Promise<{ category: string; priority: string; isFallback: boolean }> { const startTime = Date.now(); const renderedPrompt = this.renderPrompt(ticketContent); const snapshot: AuditSnapshot = { traceId, templateVersion: this.templateVersion, rawInput: ticketContent, renderedPrompt, status: 'SUCCESS', durationMs: 0 }; try { // 调用模型服务 const rawOutput = await this.llmClient.complete(renderedPrompt); snapshot.rawOutput = rawOutput; // 强校验 JSON 解析 const parsed = this.tryParseJson(rawOutput); if (!parsed || !parsed.category || !parsed.priority) { snapshot.status = 'FORMAT_ERROR'; snapshot.errorDetail = "Model output failed schema validation"; // 触发降级逻辑,写入人工审核队列 await this.auditStore.save({ ...snapshot, durationMs: Date.now() - startTime }); return { category: "UNCATEGORIZED", priority: "HIGH", isFallback: true }; } snapshot.parsedResult = parsed; snapshot.durationMs = Date.now() - startTime; await this.auditStore.save(snapshot); return { category: parsed.category, priority: parsed.priority, isFallback: false }; } catch (err: any) { snapshot.status = err.name === 'TimeoutError' ? 'TIMEOUT' : 'FALLBACK'; snapshot.errorDetail = err.message || String(err); snapshot.durationMs = Date.now() - startTime; await this.auditStore.save(snapshot); // 线上保底:出错绝不能卡死业务流程 return { category: "SYSTEM_FALLBACK", priority: "HIGH", isFallback: true }; } } }

3. 故障快照复盘与对比分析

这套证据链埋点上线后,当再次遇到类似故障时,可以直接根据trace_id抽取完整快照。

通过数据库查询或日志检索,直接对比正常与异常请求的入参特征:

# 从证据链快照中按状态检索异常记录 sqlite3 /data/audit/snapshots.db \ "SELECT trace_id, template_version, error_detail, duration_ms FROM snapshots WHERE status='FORMAT_ERROR' LIMIT 10;"

应加入一类边界用例:工单正文包含大括号、引号和日志片段。若输入没有分隔与转义,模型可能把用户内容误当成指令;测试应验证模板边界和结构化字段不会被覆盖。

修复后应按同一任务集复测,并记录稳定性与自动流转率:

指标维度证据链建立前证据链建立后
故障定位平均时长 (MTTR)记录证据链建立前记录证据链建立后
未捕获异常比例记录证据链建立前记录证据链建立后
Prompt 模版追踪粒度无记录精确到 Git Commit & 版本号
工单流转兜底成功率记录证据链建立前记录证据链建立后

4. 经验总结

在传统业务中落地 AI 功能,最要紧的不是追求模型有多聪明,而是保证系统足够健壮。

Trace ID 应贯穿 Prompt 渲染、模型响应摘要、Schema 校验和降级分流。日志先脱敏,并设置留存期限;复盘时从第一次状态偏移开始检查。

← 返回列表