LLM 应用的故障排查难度远高于传统 API 服务:用户看到的是"回答质量下降"或"响应超时",背后可能涉及提示词构造、上下文窗口溢出、供应商限流、流式传输中断等多层问题。没有结构化的日志体系,团队往往只能靠用户截图和工程师猜测来复现问题。
日志设计的三个核心层次
请求追踪层
每个用户请求必须分配全局唯一的 trace_id,并在所有相关日志中携带。假设一个多轮对话场景:用户发起三轮提问,每轮可能触发知识库检索、提示词拼接、LLM 调用、结果后处理等步骤。如果没有 trace_id 串联,工程师面对数万条日志时无法快速筛选出某次对话的完整链路。
建议在请求入口(如 API Gateway 或应用层路由)生成 trace_id,并通过上下文对象(Context)传递到所有子模块。对于流式响应,需要在首个 chunk 返回时记录开始时间,在流结束或中断时记录结束状态,避免"用户说没收到完整回答,但日志里找不到异常"的盲区。
上下文记录层
LLM 应用的输入输出都是非结构化文本,必须记录足够的上下文才能复现问题。关键字段包括:
- 提示词快照:最终发送给模型的完整 prompt,包括系统指令、用户输入、检索到的知识片段。不要只记录模板名称,因为模板可能迭代,三个月后你无法还原当时的版本
- 模型参数:temperature、max_tokens、top_p 等配置,以及使用的模型档位(旗舰档/中间档/轻量档)
- 检索结果:如果涉及 RAG,记录命中的文档 ID、相似度分数、实际注入到 prompt 的文本片段长度
- 用户元数据:用户 ID、会话 ID、客户端版本,用于关联用户反馈
假设用户投诉"AI 给出了过时的产品信息",工程师需要通过 trace_id 找到当次请求,查看检索模块返回了哪些文档,prompt 中是否包含时间限定词,模型参数是否过于保守导致倾向历史数据。
异常分类层
将异常分为供应商侧、应用侧、数据侧三类,每类记录不同的诊断信息:
- 供应商侧:HTTP 状态码、错误代码(如 rate_limit_exceeded、context_length_exceeded)、重试次数、降级策略是否触发。建议解析供应商返回的错误结构体,提取
error.type和error.message,而不是只记录原始响应 - 应用侧:超时位置(连接超时/读取超时/总耗时超限)、熔断器状态、队列积压长度。对于流式响应,记录在第几个 chunk 时中断
- 数据侧:输入文本长度、token 估算值、检索召回数量为零、prompt 拼接后超出窗口限制。这类问题往往不会抛出异常,但会导致静默失败或质量劣化
问题排查的标准路径
当收到"LLM 没有正常回答"的报障时,按以下顺序收敛问题范围:
- 定位请求:通过
trace_id或时间范围 + 用户 ID 筛选日志 - 检查供应商响应:是否有 4xx/5xx 错误?错误类型是配额、超时还是内容审核拦截?
- 验证输入有效性:prompt 长度是否异常?检索结果是否为空?用户输入是否包含特殊字符导致模板渲染失败?
- 分析输出完整性:流式响应是否提前终止?finish_reason 是 stop、length 还是 content_filter?
- 对比历史基线:该用户的平均响应时长、token 消耗是否突变?同一提示词模板在其他用户处是否正常?
假设排查一个"回答不完整"的问题:日志显示 finish_reason: length,说明触发了 max_tokens 限制;进一步查看 prompt 快照,发现检索模块注入了三篇长文档,压缩了模型的输出空间。解决方案可能是调整检索结果的截断策略或增大 max_tokens 上限。
工具选型与实施建议
日志存储建议选择支持全文检索和结构化查询的方案(如 Elasticsearch、Loki + Grafana),避免依赖 grep 在纯文本文件中查找。对于高频应用,可以将关键指标(请求量、P99 延迟、错误率)导出到时序数据库,配置告警规则。
敏感信息脱敏是必选项:用户输入和 LLM 输出可能包含个人信息,生产环境日志应对手机号、身份证号等字段做哈希或掩码处理,同时保留原文的字符长度和类型特征(如"手机号 11 位数字"),以便排查格式校验问题。
Taylent Labs 在 AI 应用开发与架构咨询中积累了可观测性体系的实践经验,如果你的团队正在构建 LLM 应用并希望建立系统化的日志与监控方案,欢迎联系我们探讨具体需求。