摘要
传统 Web 服务可以通过日志、指标和链路追踪定位问题,但大模型应用还需要回答更细的问题:一次回答使用了哪个 Prompt、调用了几次模型、检索了哪些文档、为什么触发工具、消耗了多少 Token、用户是否满意。
Langfuse 是一个开源的 LLM 可观测性平台,提供 Tracing、Prompt 管理、评估、成本统计和数据集能力。它可以通过 SDK、OpenTelemetry 或框架集成记录大模型应用中的 Trace、Generation、Span、Prompt、Token 和用户反馈。
本文以 RAG 客服应用为例,介绍:
- Langfuse 的 Trace、Span、Generation 和 Score;
- 如何通过 Docker Compose 自托管;
- 如何在 Java 或 OpenAI-compatible 应用中接入追踪;
- 如何记录 Prompt、检索、模型调用和工具调用;
- 如何统计 Token、延迟和成本;
- 如何构造数据集和评估集;
- 如何处理敏感 Prompt、租户隔离和数据保留。
一、背景与问题
1. 普通日志无法解释模型质量
一条普通日志可能只有:
text
request_id=req-001 answer=已为你查询
但排查回答质量时还需要知道:
- 用户原始问题;
- 最终 Prompt;
- Prompt 版本;
- 检索候选和分数;
- 实际模型;
- 输入和输出 Token;
- 首 Token 延迟;
- 工具调用;
- 错误和重试;
- 用户反馈。
2. AI 应用的调用链更长
text
用户请求
↓
问题改写
↓
向量召回
↓
重排序
↓
Prompt 组装
↓
模型调用
↓
工具调用
↓
最终回答
如果没有统一 Trace,这些记录会散落在多个服务和日志文件中。
3. Langfuse 的定位
Langfuse 将一次用户请求组织为 Trace,再把内部步骤记录为 Span、Generation、Event 和 Score:
text
Trace
├─ Span:检索
├─ Generation:模型调用
├─ Span:工具执行
├─ Generation:最终回答
└─ Score:相关性、满意度或人工评价
它关注的是 AI 应用的运行过程和质量反馈,不替代业务数据库、日志平台和权限系统。
二、核心概念
1. Trace
Trace 表示一次完整用户请求或 Agent Run,通常包含:
- traceId;
- userId;
- sessionId;
- input;
- output;
- metadata;
- tags;
- 开始和结束时间。
2. Span
Span 表示一个普通处理步骤:
- 查询会话;
- 文档解析;
- 向量召回;
- 重排序;
- 工具执行;
- 内容过滤。
3. Generation
Generation 表示一次 LLM 调用,应记录:
- 模型名称;
- Provider;
- Prompt;
- 输出;
- Token usage;
- 延迟;
- 参数;
- 成本。
4. Score
Score 表示对 Trace 或 Generation 的评价:
text
自动评分:答案是否包含引用
模型评分:相关性和忠实度
人工评分:用户是否满意
业务评分:是否解决工单
5. Prompt Management
Prompt 管理需要关注:
- Prompt Key;
- 版本;
- 标签;
- 变量;
- 发布状态;
- 回滚;
- 与 Trace 的关联。
6. Dataset 与 Evaluation
Dataset 是固定的输入、期望输出或参考答案集合。修改模型或 Prompt 后,可以重新运行同一批数据比较结果。
三、工作原理
1. RAG Trace
text
Trace: customer-question
├─ Span: load-conversation
├─ Span: retrieve-documents
├─ Span: rerank
├─ Generation: answer
└─ Score: answer-relevance
2. Agent Trace
text
Trace: ticket-agent-run
├─ Generation: planning
├─ Span: query-ticket
├─ Span: search-knowledge
├─ Generation: final-answer
└─ Score: task-completion
3. 成本计算
成本通常由以下字段决定:
text
输入 Token × 输入单价
+
输出 Token × 输出单价
+
缓存、批处理或工具相关费用
Provider 返回的 usage 可能不完整,因此成本系统要标识估算值和实际值。
4. OpenTelemetry
如果业务系统已经使用 OpenTelemetry,可以将模型调用的 Trace 与普通 HTTP、数据库和消息队列 Span 关联起来:
text
HTTP Trace
├─ SQL Span
├─ Retrieval Span
└─ LLM Generation
这样可以观察整个请求,而不是只在 AI 平台中看到模型部分。
四、实战示例
1. 使用 Docker Compose 自托管
Langfuse 的自托管部署通常需要数据库和应用服务。开发环境可以先参考官方 Compose:
powershell
git clone https://github.com/langfuse/langfuse.git
Set-Location langfuse
docker compose up -d
官方部署文档会随版本更新 Compose、环境变量和数据库依赖,生产环境应直接使用官方仓库当前版本的示例,不要长期复制旧文章中的配置。
2. 配置环境变量
dotenv
DATABASE_URL=postgresql://langfuse:change-me@postgres:5432/langfuse
NEXTAUTH_SECRET=replace-with-random-secret
SALT=replace-with-random-salt
ENCRYPTION_KEY=replace-with-32-byte-key
NEXTAUTH_URL=http://localhost:3000
生产环境还需要:
- 使用 Secret Manager;
- 启用 HTTPS;
- 配置外部访问 URL;
- 备份 PostgreSQL;
- 限制管理入口;
- 规划数据保留和删除。
3. Java 初始化 SDK
Java 应用可以使用 OpenTelemetry、HTTP API 或团队封装的 Langfuse Client。建议将追踪能力封装成自己的接口:
java
public interface AiObservability {
AiTrace startTrace(AiTraceRequest request);
AiSpan startSpan(AiTrace trace, String name);
AiGeneration recordGeneration(
AiTrace trace,
GenerationRequest request);
void score(AiTrace trace, String name, double value);
}
业务代码不应该到处直接拼 Langfuse HTTP 请求。
4. 追踪一次 RAG 请求
java
public KnowledgeAnswer answer(
CurrentUser user,
String question) {
AiTrace trace = observability.startTrace(
new AiTraceRequest(
"knowledge-answer",
user.userId().toString(),
question
)
);
try {
AiSpan retrieval = observability.startSpan(
trace,
"retrieve-documents"
);
List<Document> documents =
retrievalService.search(
user.tenantId(),
question
);
retrieval.end(Map.of(
"count", documents.size()
));
AiGeneration generation =
observability.startGeneration(
trace,
"answer",
promptVersion
);
String answer = chatClient.prompt()
.system(systemPrompt)
.user(question)
.call()
.content();
generation.end(answer, usage);
trace.end(answer);
return new KnowledgeAnswer(answer, documents);
} catch (Exception error) {
trace.fail(error);
throw error;
}
}
5. 记录模型调用
java
public record GenerationRequest(
String provider,
String model,
String promptVersion,
Object input,
Map<String, Object> parameters) {
}
public record TokenUsage(
Integer inputTokens,
Integer outputTokens,
Integer totalTokens) {
}
至少记录:
- Provider 和模型;
- Prompt 版本;
- 输入输出 Token;
- 延迟;
- 是否流式;
- 重试和 Fallback;
- 错误类型;
- requestId。
6. 记录工具调用
java
AiSpan toolSpan = observability.startSpan(
trace,
"query-ticket"
);
try {
TicketSummary result =
ticketTool.query(context, ticketId);
toolSpan.end(Map.of(
"status", "COMPLETED",
"ticketStatus", result.status()
));
} catch (Exception error) {
toolSpan.fail(Map.of(
"errorType", error.getClass().getSimpleName()
));
throw error;
}
不要把完整工具参数和结果无条件写入观测平台,尤其是包含密码、Token、个人信息和内部策略时。
7. 记录用户反馈
java
observability.score(
trace,
"user-feedback",
feedback == Feedback.POSITIVE ? 1.0 : 0.0
);
还可以记录:
- 是否点击引用;
- 是否重新提问;
- 是否人工转接;
- 是否采纳 Agent 建议;
- 工单是否最终关闭。
8. Prompt 版本关联
java
public record PromptRef(
String key,
String version,
String label) {
}
每次 Generation 记录 Prompt Key 和版本,质量下降时才能判断是模型、检索还是 Prompt 变化导致。
9. 流式调用观测
流式请求建议:
text
Generation start
↓
记录首 Token 时间
↓
记录片段数量或总字符数
↓
记录最终 usage
↓
Generation end
不要为每个 Token 创建一个独立 Trace 或 Span。可以记录首 Token 延迟、总延迟和片段统计。
10. 创建评估数据集
json
{
"name": "customer-service-rag-v1",
"items": [
{
"input": "差旅报销多久提交",
"expectedSources": ["travel-policy-v3"],
"referenceAnswer": "出差结束后十个工作日内提交"
}
]
}
评估结果可以包含:
text
context_recall
answer_relevance
faithfulness
citation_accuracy
tool_success_rate
五、常见问题与实践建议
1. 为什么 Trace 太多
常见原因:
- 每个 Token 都创建事件;
- 健康检查也被完整追踪;
- 重试没有关联同一个 Trace;
- 前端重复提交;
- 轮询接口没有采样。
设置采样和环境级别:
text
开发环境:完整 Trace
测试环境:完整 Trace + 脱敏
生产环境:按比例采样,错误请求全量
2. 是否应该保存完整 Prompt
默认不建议。可以保存:
- Prompt Hash;
- Prompt Key 和版本;
- 长度和 Token;
- 脱敏摘要;
- 受控调试样本。
如果合规要求必须保存完整内容,应加密、控制访问并设置保留期限。
3. 多租户怎么隔离
Trace Metadata 中保存 tenantId 只能帮助筛选,不等于权限隔离。观测平台仍需:
- 团队或项目分区;
- 用户访问控制;
- API Key 隔离;
- 数据保留策略;
- 管理员审计。
4. 成本为什么和供应商账单不一致
可能是价格配置、Token 统计、缓存、重试和 Fallback 口径不同。将 Langfuse 作为运行成本估算和异常监控,财务结算以供应商账单为准。
5. 观测平台故障是否应该阻塞业务
通常不应该。业务请求不能因为追踪写入失败而整体失败:
java
observability.tryRecord(
() -> trace.end(answer)
);
追踪上报可以异步、批量和降级,但错误请求和安全事件应有单独的可靠日志。
6. Prompt、Trace 和用户隐私
处理原则:
- 最小化采集;
- 字段脱敏;
- 访问分级;
- 设置删除接口;
- 限制跨区域传输;
- 不将生产数据复制到公共评估集。
六、进阶思考
1. Langfuse 与普通 APM 的关系
普通 APM 关注 HTTP、数据库、线程和基础设施;LLM Observability 还关注:
- Prompt;
- 模型;
- Token;
- 评估;
- 生成质量;
- 工具和检索;
- 用户反馈。
两者应该关联,而不是互相替代。
2. 质量指标和业务指标结合
技术指标:
- 延迟;
- 错误率;
- Token;
- 成本;
- 重试率。
业务指标:
- 工单解决率;
- 人工转接率;
- 用户满意度;
- 引用点击率;
- 任务完成率。
只优化模型延迟,不一定提升业务效果。
3. 评估集驱动 Prompt 和模型升级
升级流程:
text
固定 Dataset
↓
运行旧 Prompt / 旧模型
↓
保存基线
↓
运行新 Prompt / 新模型
↓
比较准确率、成本和延迟
↓
人工抽查差异
↓
灰度发布
4. 线上反馈回流
用户反馈可以回流到 Dataset:
text
线上低分 Trace
↓
人工标注
↓
加入评估集
↓
修正切片、Prompt 或模型
↓
回归测试
5. 观测平台的数据生命周期
设计:
- Trace 保留多久;
- Prompt 是否单独加密;
- 失败样本是否长期保留;
- 用户删除如何传播;
- Dataset 是否包含真实用户数据;
- 导出和备份如何控制。
观测数据也属于业务数据,不能无限保留。
结论
Langfuse 的价值在于把大模型应用从"能调用"推进到"能解释、能评估、能优化"。接入时应围绕一次完整请求建立 Trace,再把检索、模型、工具和用户反馈作为可关联的步骤记录。
核心实践包括:
- 为每次对话或 Agent Run 创建 Trace;
- 用 Span 记录检索和工具,用 Generation 记录模型调用;
- 记录 Prompt 版本、Token、延迟、成本和错误;
- 用 Score 和 Dataset 建立质量评估闭环;
- 对完整 Prompt、工具结果和用户数据做脱敏;
- 观测失败不应阻塞业务请求;
- 将 Langfuse 与普通 APM、日志和业务指标关联;
- 用线上失败样本持续完善评估集。
第一次实践可以先在测试环境部署 Langfuse,接入一个 RAG 请求,完整观察"问题---召回---模型---引用---反馈"的链路,再逐步扩展到 Agent、成本和自动评估。