传统软件的监控很成熟——接口延迟、错误率、吞吐量,一层层监控系统早就完善了。但 LLM 应用不一样。HTTP 200 不代表回答正确,延迟正常不代表输出了无毒内容。
LLM 应用的监控需要全新的维度。这篇文章讲讲怎么做好 LLM 应用的观测和评估。
为什么传统监控不够
传统 API 的监控:
请求 → 处理 → 响应 → 记录状态码和延迟
LLM 调用的监控:
用户输入 → LLM 调用 → 工具调用 → 再次 LLM 调用 → ...
→ 最终输出
↓
我们需要知道:
- 输出有没有幻觉?
- 工具调用正确吗?
- 多轮之后有没有偏离主题?
- 成本花了多少?
- 延迟在哪里花了最多时间?
传统的 HTTP 状态码和时间戳解决不了这些问题。
三大观测维度
LLM 应用的可观测性分三个维度:
1. 性能观测(Performance)
延迟、吞吐量、Token 用量、成本
→ 回答"系统跑得怎么样"
2. 质量观测(Quality)
输出准确率、忠实度、相关性
→ 回答"输出质量怎么样"
3. 安全观测(Safety)
有害内容、越狱、数据泄露
→ 回答"系统安全吗"
1. 性能观测
基本的指标:
延迟:
TTFT(Time to First Token):首 token 延迟
TPOT(Time per Output Token):每 token 生成时间
总延迟
用量:
输入/输出 token 数
模型调用次数(包括工具调用的多轮)
KV Cache 命中率
成本:
每次请求的总 cost
按用户、按功能拆分的成本
成本趋势(是否有突发增长)
并发:
最大并发数
排队长度
限流触发次数
Trace 是必须的。没有 trace,你无法知道一个"慢请求"到底慢在哪里——是 LLM 调用慢、工具调用慢、还是网络传输慢?
每个 LLM 调用都需要记录完整的 trace:
Request ID: req_abc123
Step 1: LLM call (claude-sonnet-4)
- Input tokens: 1520
- Output tokens: 35 (含 tool call)
- Duration: 1.2s
Step 2: Tool call (search_knowledge_base)
- Query: "分布式锁 Redisson 配置"
- Result: 3 documents found
- Duration: 0.3s
Step 3: LLM call (claude-sonnet-4)
- Input tokens: 2100 (含检索结果)
- Output tokens: 256
- Duration: 2.1s
Total: 3.6s | Cost: $0.008
2. 质量观测
质量观测是 LLM 应用的独有维度——传统软件不需要问"这个响应正确吗",但 LLM 应用必须问。
离线评估(Eval)
离线评估是系统上线前做的评估。用标注好的测试集(Golden Dataset)评估模型输出质量。
关键指标:
Faithfulness(忠实度):
输出是否基于提供的上下文?
最常见的失败模式:模型在检索结果之外自行编造信息
Answer Relevance(回答相关性):
回答是否针对用户问题?
失败模式:答非所问、泛泛而谈
Context Precision(上下文精确度):
有多少检索到的上下文真正被使用了?
失败模式:检索到了但没用上
Hallucination Rate(幻觉率):
模型输出了不在上下文中的信息?
这是最重要的指标之一
在线评估(Monitoring)
离线评估不能覆盖所有线上场景。在线监控需要自动化的评估器(LLM-as-Judge)。
def evaluate_response(question: str, answer: str, context: list[str]) -> dict:
"""用另一个 LLM 评估回答质量"""
prompt = f"""你是一个评估助手。评估以下回答的质量。
用户问题:{question}
参考上下文:{context}
模型回答:{answer}
评估维度(1-5分):
1. 忠实度:回答是否有事实基础?
2. 相关性:回答是否针对问题?
3. 完整性:回答是否覆盖了问题的所有方面?
请输出 JSON:
{{"faithfulness": 4, "relevance": 5, "completeness": 3, "explanation": "..."}}
"""
return json.loads(llm.generate(prompt))
LLM-as-Judge 的陷阱:
- 位置偏差:Judge LLM 偏好列表中的第一个选项
- 自我强化:Judge 偏好与自己风格相似的输出
- 长度偏差:Judge 认为"越长的越好"
- 谨慎偏差:Judge 倾向于给中等分数
缓解方法:使用不同的模型做 Judge(不要用输出模型本身),多次评估取平均,在 prompt 中明确要求忽略长度和格式。
评估频率:
上线前:完整 Golden Set 评估(100-500 个样本)
每周:抽样评估(50-100 个最新对话)
每次迭代:Diff 评估(新旧版本在相同测试集上的对比)
持续监控:关键指标自动化(忠实度、拒绝率、用户反馈)
3. 安全观测
LLM 应用的安全观测关注三个方面:
有害内容检测:
用户输入 → 检测有害内容(PII、仇恨言论、攻击)
模型输出 → 检测有害内容(越狱、敏感话题)
"检测"可以用内容安全 API(如 OpenAI 的 Moderation API),也可以用小模型做分类。
Prompt Injection 检测:
目标:检测用户是否在尝试劫持模型的指令
方式:
- 检测用户输入中的"忽略之前指令"
- 检测用户输入中的角色扮演请求
- 检测输入格式的异常变化
数据泄露监控:
目标:确保模型输出不包含敏感信息
方式:
- 输出中是否包含 API Key、密码、个人隐私信息
- 输出中是否包含内部文档的原始文本
- 跨会话的相关信息泄露
工具链
工具 做什么 什么时候用
────────────────────────────────────────────────────────────────
Langfuse LLM trace + 成本跟踪 + 评估 日常开发 + 生产
MLflow Eval + Prompt 版本管理 + 实验跟踪 模型实验 + 对比测试
LangSmith Trace + 调试 + 测试集管理 LangChain 生态
Phoenix (Arize) 在线监控 + 漂移检测 生产环境监控
WhyLabs LLM 安全 + 异常检测 安全合规场景
Guardrails 输出校验 + 格式保证 生产环境输出稳定
我个人的推荐:从小开始,Langfuse 起步。它的开源版免费,部署简单,trace + eval + cost tracking 都有。等需要更专业的功能再换。
配置一个最基础的 trace:
# 只需要在初始化 LLM 客户端时加一个 wrapper
from langfuse import Langfuse
from langfuse.openai import OpenAI
# 自动 trace 所有 OpenAI 调用
client = OpenAI()
# Agent 调用时自动记录
response = client.chat.completions.create(
model="gpt-4o",
messages=[...],
tools=[...],
# 自动生成 trace
)
Eval 设计的最佳实践
构建 Golden Dataset
一个好的 Golden Dataset 应该包含:
正常用例(60%):
常见问题、典型场景
验证常规功能是否正常
边界用例(20%):
极短输入("?"、"好")
极长输入(超过 4K tokens)
多意图问题
含糊不清的问题
失败用例(10%):
已知的 Bad Case(之前答错的问题)
常见的用户错误
安全用例(10%):
Prompt Injection 尝试
敏感信息查询
有害内容请求
评估的自动化
一次好的评估流程应该是自动化的、可重复的:
修改 prompt / 切换模型 → 自动跑 Golden Set
→ 计算各维度分数 → 对比基线版本
→ 生成报告 → 判定通过/不通过
每次上线前应该对比新旧版本的分数差异。如果新版在某些维度上有下降,在做决定前需要明确知道下降的原因。
Eval 的陷阱
陷阱 1:用同一个 LLM 做生成和评估
如果 Agent 用 GPT-4o,Judge 也是 GPT-4o,那评估结果会偏高(自我强化偏差)。如果你发现评估分数一直很高但用户反馈不好,先检查是不是在"自己评自己"。
陷阱 2:只看分数不看 Bad Case
分数从 85 升到 90 不值得高兴——真正重要的是那 10% 的失败案例是什么。每次评估都应该去读几个失败的案例,理解失败模式。
陷阱 3:没有版本管理
Prompt 改了,效果好了还是差了?如果每次改 prompt 不做测评、不做版本对比,你永远不知道哪个版本更好。Prompt 要像代码一样版本化,每版都要有对应的评估结果。
生产级监控体系
┌─────────────────────────────────────────────────────────┐
│ 监控体系总览 │
│ │
│ 实时告警 每日报告 │
│ ├─ 错误率 > 5% → 钉钉 ├─ 用户反馈统计 │
│ ├─ 延迟 P99 > 10s → 邮件 ├─ Bad Case 列表 │
│ └─ 成本 > 阈值 → 短信 └─ 质量趋势 │
│ │
│ 系统层 应用层 业务层 │
│ ├─ GPU 利用率 ├─ Call trace ├─ 忠实度 │
│ ├─ KV Cache 命中 ├─ Tool 成功率 ├─ 用户满意度 │
│ ├─ 模型延迟 ├─ Token 用量 └─ 拒绝率 │
│ └─ 队列深度 └─ Cost 追踪 │
└─────────────────────────────────────────────────────────┘
总结
LLM 应用的可观测性是一个新领域,很多工具和实践还在快速演进中。但有几件事是确定的:
- Trace 是一切的基础——没有 trace,你无法理解 LLM 在对话中做了什么
- Eval 是持续的过程——不是上线前做一次就完事,而是随着数据积累持续改进
- Bad Case 驱动迭代——与其追求全面覆盖,不如从真实用户的 Bad Case 做起
- 安全观测不能省——LLM 应用的攻击面比传统 API 大得多
随着 LLM 应用越来越普及,我相信可观测性会成为 LLM 工程的标配,就像今天的 APM 一样。
评论