35 日志与成本:看清每一次 RAG 调用
日志与成本:看清每一次 RAG 调用
RAG 项目要上线,必须能看清一次请求经历了什么、慢在哪里、花了多少、为什么失败。
[TOC]
1. 这一节到底解决什么问题
前面我们已经把文档上传、解析、切片、Embedding、向量入库、向量检索和基础问答串起来了。到这里,RAG 已经不再只是一个“能回答”的 demo。
从 Day30 开始,重点要慢慢转向工程质量:
答案为什么可信
错误为什么能定位
效果为什么能比较
成本为什么能估算
部署为什么能复现
项目为什么能讲清楚
用户说“刚才很慢”,你不能只猜是模型慢。你要能看到 Embedding、检索、Rerank、模型首 Token 和完整生成各自耗时。
2. 先看一个业务场景
一次问答总耗时 6 秒。拆开后发现 Embedding 200ms,向量检索 80ms,Rerank 900ms,模型首 Token 4 秒。这个时候优化数据库没有意义,重点应该看模型调用和上下文长度。
这个场景里,最容易出错的地方通常不是某一行代码,而是链路边界没有想清楚。
输入是什么
依赖哪份资料
中间结果怎么保存
失败时返回什么
如何证明改动有效
带着这些问题往下看,后面的设计就不会变成空泛理论。
这一节不是为了“多打点日志”,而是为了让一次问答能被复盘。
判断顺序可以这样写:
1. 先列出一次 RAG 请求经过哪些阶段:Embedding、检索、Rerank、Prompt、LLM、返回
2. 每个阶段记录耗时、输入规模、输出数量和错误信息
3. 成本要拆开算:Embedding token、Rerank 次数、LLM prompt token、completion token
4. 日志不能泄露敏感正文,必要时只记录 chunkId、traceId 和摘要
5. 最后用一条 traceId 能还原完整链路,知道慢在哪里、贵在哪里、错在哪里
它解决的是线上排查难题:没有追踪和成本记录时,系统慢、贵、错都只能猜。
3. 核心概念
1. TraceId
每次请求生成唯一 traceId,贯穿所有日志、模型调用、反馈和错误。
可以把它记成一句话:没有 traceId,就没有调用链。
2. 阶段耗时
总耗时要拆成 Embedding、检索、重排、生成等阶段。
可以把它记成一句话:慢在哪里要能看见。
3. Token 统计
输入、输出、上下文和总 Token 都要记录。RAG 成本常被过长上下文拉高。
可以把它记成一句话:Token 是成本的影子。
4. 成本估算
按模型、Token 和调用时价格快照估算成本,并按用户、知识库、功能聚合。
可以把它记成一句话:成本要能分摊。
4. 整体流程
graph TD;
A["入口生成 traceId"] --> B["记录 Embedding span"]; B["记录 Embedding span"] --> C["记录检索 span"]; C["记录检索 span"] --> D["记录 Rerank span"]; D["记录 Rerank span"] --> E["记录模型 span"]; E["记录模型 span"] --> F["聚合 Token 成本"];
F["聚合 Token 成本"] --> G["输出监控报表"];
图里每个节点都应该能单独打日志、单独统计耗时、单独写测试。
这就是我一直强调的:不要把 RAG 写成一个黑盒方法。黑盒方法演示时很爽,排查时很痛苦。
5. 落地步骤
1. 统一日志结构
使用 JSON 字段记录 traceId、stage、latencyMs、status、model、tokens。不要只写一行自然语言日志。
落地时要留下可检查的产物:结构化日志字段。
2. 正文最小化
用户问题和文档内容可能包含隐私。日志可以存摘要、hash 或脱敏片段,不要默认存全文。
落地时要留下可检查的产物:脱敏策略。
3. 记录价格快照
模型价格可能变化。历史成本要按当时价格计算,不要用当前价格倒推。
落地时要留下可检查的产物:priceSnapshot 字段。
4. 建立看板
至少看请求量、成功率、P95、Token、成本、错误码和模型调用次数。
落地时要留下可检查的产物:监控看板。
6. 数据结构或配置示例
| 项目 | 说明 |
|---|---|
| traceId | 全链路关联 ID |
| stage | 当前阶段,例如 embedding/search/chat |
| latencyMs | 阶段耗时 |
| inputTokens | 输入 Token 数 |
| outputTokens | 输出 Token 数 |
| estimatedCost | 估算成本 |
如果项目里已经有自己的字段命名,可以不照抄这张表。但这些信息最好都能找到对应位置,否则排查问题时会缺证据。
7. 示例
{
"traceId": "01HX...", "stage": "chat_generation", "model": "qwen3.5-flash", "promptVersion": "rag-v3", "inputTokens": 4200, "outputTokens": 380, "latencyMs": 1820, "estimatedCost": 0.0031, "status": "success"}
示例的重点不是格式,而是思路:把规则、参数、输入、输出和失败状态显式写出来。
8. 为什么不能简单粗暴地做
不能只记录总耗时和最终答案。总耗时无法指导优化,最终答案也无法解释为什么贵。RAG 链路越长,越需要分阶段记录。
很多初学者会希望有一个“最简单固定答案”。但 RAG 项目里,简单粗暴通常会把问题藏起来:短期好像能跑,长期很难维护。
9. 调试和排查路径
当结果不符合预期时,建议按这个顺序排查:
1. 输入问题是否正确
2. 权限范围是否正确
3. 检索候选是否包含正确证据
4. 排序或过滤是否把正确证据丢掉
5. Prompt 是否把证据清楚交给模型
6. 模型是否按证据回答
7. 引用是否能支持结论
8. 日志、成本、耗时是否异常
不要一看到答案不对就去改 Prompt。很多时候问题根本不在 Prompt,而是在解析、切片、过滤、召回或数据版本。
10. 工程取舍
- 日志越详细,隐私风险越高,需要脱敏。
- 成本估算可以先粗略,但字段要提前设计。
- P95 和 P99 比平均值更能反映用户体验。
取舍要写在文档里。因为过几周再回来看,你很可能只记得“当时这么做了”,却忘了“为什么这么做”。
11. 常见坑
- 日志保存完整 Prompt 和密钥。
- 只记录错误请求,正常请求没有指标。
- 重试产生的 Token 没有计入成本。
- 各服务 traceId 不一致。
这些坑不是为了吓人,而是为了让你知道项目坏掉时从哪里下手。
12. 验收清单
- ☐ 每次请求有 traceId
- ☐ 各阶段耗时可见
- ☐ Token 统计进入日志
- ☐ 成本可按模型聚合
- ☐ 敏感正文不默认落库
验收清单是博客里很有价值的部分。它能把一篇文章从“我讲了一个概念”变成“你可以跟着做出一个结果”。
13. 落地清单
- ☐ 设计一次请求的日志字段
- ☐ 统计 10 次请求的平均 Token
- ☐ 计算一次问答估算成本
- ☐ 画一个简单成本看板字段
落地后,建议把结果截图或记录到项目文档里。后面写 README、博客复盘或项目说明时,这些都是现成素材。
14. 小结
日志与成本把 RAG 从“能跑”变成“能运营”。看不见的系统很难优化,也很难上线。
到这里,你应该能把今天的主题讲成一个完整故事:为什么需要它、它解决哪一层问题、怎么落地、怎么验证、有什么代价。