LLM应用架构与可观测性
从架构认知到技术栈拆解,再到可观测性落地 · 测试人员的必备视角
这篇怎么学
本讲是全课程最"偏开发"的一讲,但目标不是让你写代码,而是让你看得懂架构、读得懂链路、找得到问题。建议按"认知 → 技术栈 → 可观测性 → 实操"四层递进学习,先建立全局视图,再深入每个技术栈的测试关注点。
第1章:为什么测试人员要懂应用架构
1.1 不懂架构只能做黑盒
传统软件测试里,不懂架构也能写用例、点页面、发请求。但在 LLM 应用中,一个用户问题的回答可能经过了检索、重排、Prompt 拼接、模型推理、后处理五个以上环节。如果你不知道这些环节的存在,当回答出错时,你只能说"回答不对",却说不清到底是哪一层出了问题。
不懂架构的测试人员:不懂架构的测试人员 只能报告"回答不准确" 无法判断是检索问题还是生成问题 写的 Bug 描述让开发无法定位 测试策略只有"问一遍看结果" 懂架构的测试人员:懂架构的测试人员 能定位到"检索层返回了无关文档" 能区分 Prompt 模板问题和模型能力问题 能按分层设计针对性测试用例 能和开发用同一套语言沟通
1.2 懂架构能做分层定位
LLM 应用的问题定位本质上是一个链路分层排查的过程。以 RAG 应用为例:
| 错误现象 | 可能的问题层 | 定位方式 |
|---|---|---|
| 回答内容完全编造 | 检索层未命中 / Prompt 未注入上下文 | 查看检索结果是否为空 |
| 回答引用了错误文档 | 检索层 / Rerank 层 | 对比召回文档与预期文档 |
| 回答内容正确但格式混乱 | Prompt 模板 / 后处理层 | 检查 Prompt 中的格式指令 |
| 回答速度极慢 | 模型推理层 / 向量检索层 | 查看各环节延迟分布 |
| 回答中出现敏感内容 | 内容审核层 / 模型本身 | 检查是否有审核过滤器 |
1.3 测试人员需要懂到什么程度
测试人员的架构认知三层
- 知道有什么:知道链路上有哪些组件,各自负责什么
- 知道怎么连:知道数据从用户输入到最终输出流经了哪些节点
- 知道哪里可能出错:知道每个节点的典型失败模式和可测试点 你不需要能写 LangChain 代码,但你需要知道 Chain 的执行顺序;你不需要能部署 Milvus,但你需要知道向量检索的 Top-K 和阈值会影响回答质量。
第2章:LLM应用架构全景图
2.1 典型架构六层模型
一个完整的 LLM 应用从用户输入到最终输出,通常经过以下六层: 👤 用户 ↓ 前端 (Web / App / API) ↓ API 网关 (鉴权 / 限流 / 路由) ↓ 应用层 (LangChain / Dify / 自研) ↓ 大模型 API (GPT / Claude / 文心 / 通义) ↕ 应用层还会调用以下外部资源 🔧 工具 / 插件 📚 知识库 / 向量DB 🗄️ 业务数据库
2.2 每一层的测试关注点
| 层级 | 核心职责 | 测试关注点 |
|---|---|---|
| 前端层 | 用户交互、消息收发、流式渲染 | 输入长度限制、流式渲染中断、Markdown 渲染 |
| API 网关 | 鉴权、限流、路由转发 | Token 过期、并发限流、超时配置 |
| 应用层 | Prompt 编排、Chain 编排、RAG、Agent 调度 | Prompt 注入、Chain 中断、上下文截断 |
| 模型层 | 文本生成、推理 | 幻觉、一致性、安全过滤、Token 消耗 |
| 工具/插件 | 搜索、计算、API 调用 | 工具选择正确性、参数传递、超时 |
| 知识库 | 文档存储、向量检索 | 召回率、相关性、索引更新延迟 |
| 数据库 | 会话记录、用户数据 | 数据一致性、历史记录准确性 |
关键认知
LLM 应用的架构和传统 Web 应用最大的区别在于:模型层是非确定性的。同样的输入可能产生不同的输出,这意味着测试策略不能只靠"断言精确值",还需要引入语义评估和统计方法。
第3章:LangChain / LlamaIndex
3.1 是什么、解决什么问题
LangChain 和 LlamaIndex 是目前最流行的两个 LLM 应用开发框架。它们的核心价值是把"调用大模型"这件事从一次 API 调用,扩展为可编排、可扩展、可追溯的应用链路。
| 框架 | 定位 | 核心优势 | 适用场景 |
|---|---|---|---|
| LangChain | 通用 LLM 应用编排框架 | Chain/Agent/Tool 编排灵活 | 对话系统、Agent、复杂工作流 |
| LlamaIndex | 数据索引与检索增强框架 | 数据连接和检索能力强 | 知识库问答、文档检索、RAG |
3.2 LangChain 核心概念
| 概念 | 作用 | 类比 |
|---|---|---|
| Chain | 将多个步骤串联成一条执行链 | 流水线上的工序 |
| Agent | 让 LLM 自主决定调用哪些工具 | 自主决策的调度员 |
| Memory | 管理多轮对话的上下文记忆 | 对话的"短期记忆" |
| Retriever | 从知识库中检索相关文档 | 图书馆管理员 |
| Tool | Agent 可调用的外部能力 | 工具箱里的具体工具 |
| Prompt Template | 结构化管理提示词模板 | 填空题的模板 |
3.3 LangChain 模块关系图
LangChain 核心模块关系 用户输入 ↓ Prompt Template(模板渲染) ↓ Chain / Agent(编排 & 决策) ↓ LLM 调用 Retriever 检索 Tool 工具调用 ↓ Memory(上下文管理) ↓ 输出给用户
3.4 测试人员要关注什么
LangChain/LlamaIndex 的关键测试点
- Chain 的步骤执行顺序:是否按预期顺序执行,中间步骤是否可追溯
- Prompt 模板变量注入:变量是否正确填充,特殊字符是否转义
- Memory 的上下文管理:多轮对话是否正确保持上下文,上下文窗口截断策略
- Agent 的工具选择:是否选择了正确的工具,工具调用参数是否正确
- 异常处理:某个步骤失败后整条 Chain 的行为
第4章:Dify / Coze 等低代码平台
4.1 低代码平台的架构模式
Dify、Coze、FastGPT 等低代码平台的核心思路是:把 LangChain 那套代码逻辑,变成可视化的拖拽配置。开发者不需要写 Python 代码,就能搭建出一个完整的 LLM 应用。
可视化画布 → 工作流引擎 → 节点执行器 → LLM / 工具 / 知识库 → 结果输出
4.2 工作流/Agent 的运行机制
| 模式 | 运行方式 | 适用场景 |
|---|---|---|
| 工作流模式 | 按预定义节点顺序依次执行 | 流程固定的业务(客服、报告生成) |
| Agent 模式 | LLM 自主决策执行路径 | 需要灵活决策的场景(研究助手) |
| 混合模式 | 固定流程中嵌入 Agent 决策节点 | 兼顾可控性和灵活性 |
4.3 和代码开发的区别
代码开发(LangChain):代码开发(LangChain) 完全掌控每一行逻辑 调试靠日志和断点 版本管理靠 Git 测试用 pytest + 单元测试 灵活但门槛高 低代码平台(Dify/Coze):低代码平台(Dify/Coze) 通过可视化配置搭建流程 调试靠平台内置日志面板 版本管理靠平台快照 测试靠平台预览 + 手动验证 上手快但灵活度受限
4.4 测试策略差异
| 维度 | 代码开发 | 低代码平台 |
|---|---|---|
| 单元测试 | 可针对每个函数写测试 | 只能测试单个节点的输入输出 |
| 集成测试 | Mock 依赖服务 | 依赖平台的预览功能 |
| 回归测试 | CI/CD 自动化 | 需导出配置 + 用 API 回归 |
| 链路追踪 | 集成 LangFuse 等工具 | 使用平台内置日志 |
| 版本对比 | Git diff | 平台快照对比(能力有限) |
低代码平台测试的最大风险
低代码平台上线速度快,但测试手段少。最常见的问题是:配置改了一个小参数(比如 Temperature 从 0.3 改成 0.7),没有任何测试覆盖,直接上线后发现回答质量下降。建议对关键工作流建立 API 级别的自动化回归。
第5章:RAG 系统技术栈详解
5.1 完整技术栈
| 组件 | 职责 | 主流选型 | 测试关注 |
|---|---|---|---|
| 文档加载器 | 读取各种格式文档 | LangChain Loader、Unstructured | PDF/Word/Excel 解析准确性 |
| 文本切分器 | 把文档切成适合检索的片段 | RecursiveCharacterSplitter、TokenSplitter | 切分粒度、上下文丢失 |
| Embedding 模型 | 将文本转成向量表示 | OpenAI Ada、BGE、M3E、Cohere | 语义相似度、多语言支持 |
| 向量数据库 | 存储和检索向量 | Milvus、Pinecone、Qdrant、Weaviate | 查询延迟、召回率、索引更新 |
| Rerank 模型 | 对初步召回结果精排 | Cohere Rerank、BGE Reranker、JinaAI | 排序准确性、延迟开销 |
| Prompt 拼接 | 将检索结果注入提示词 | LangChain PromptTemplate | 上下文截断、格式正确性 |
| LLM 生成 | 基于上下文生成回答 | GPT-4o、Claude、文心、通义 | 幻觉、忠实度、安全性 |
5.2 向量数据库选型对比
| 数据库 | 部署方式 | 核心特点 | 适合场景 |
|---|---|---|---|
| Milvus | 自部署 / Zilliz Cloud | 高性能、分布式、生态好 | 大规模生产环境 |
| Pinecone | 全托管 SaaS | 零运维、开箱即用 | 快速上线、不想维护基础设施 |
| Qdrant | 自部署 / Cloud | Rust 编写、性能优异 | 中小规模高性能场景 |
| Weaviate | 自部署 / Cloud | 支持混合搜索(向量+关键词) | 需要混合检索策略 |
| Chroma | 嵌入式 / 自部署 | 轻量、开发友好 | 原型验证、小规模项目 |
5.3 数据入库流程
RAG 数据入库流程(Indexing Pipeline) 原始文档 PDF / Word / HTML ↓ 文档加载器 解析 & 提取文本 ↓ 文本切分器 按策略切分为 Chunk ↓ Embedding 模型 文本 → 向量 ↓ 向量数据库 存储向量 + 元数据
5.4 查询流程
用户提问 → Query Embedding → 向量检索 Top-K → Rerank 精排 → Prompt 拼接 → LLM 生成 → 输出回答
5.5 测试人员要关注哪些组件
RAG 系统测试的五个关键卡口
- 文档解析准确性:表格、图片、特殊格式是否被正确提取
- 切分质量:切分后的片段是否保持了语义完整性
- 检索召回率:相关文档是否被检索到,Top-K 设置是否合理
- Rerank 排序:最相关的文档是否排在最前面
- 生成忠实度:回答是否忠于检索到的上下文,而非编造
第6章:Agent 框架运行机制
6.1 主流 Agent 范式
| 范式 | 运行方式 | 优势 | 典型风险 |
|---|---|---|---|
| ReAct | 思考 → 行动 → 观察 → 循环 | 推理过程可追溯 | 循环次数过多、死循环 |
| Plan-and-Execute | 先制定计划,再逐步执行 | 适合复杂多步任务 | 计划不合理、执行偏离 |
| Multi-Agent | 多个 Agent 协作完成任务 | 可分工协作 | 协调开销大、职责模糊 |
6.2 ReAct 执行循环
ReAct 循环:Thought → Action → Observation
🤔 Thought LLM 推理思考 → 🔧 Action 调用工具/API → 👁️ Observation 获取执行结果 ↩
循环直到 LLM 认为可以给出最终回答
6.3 LangGraph 的状态图模型
LangGraph 是 LangChain 团队推出的 Agent 编排框架,核心思想是用**有限状态机(FSM)**来管理 Agent 的执行流程。每个节点是一个处理步骤,边上带有条件判断。
| 概念 | 含义 | 测试关注 |
|---|---|---|
| State | 图中流转的数据状态 | 状态字段是否正确更新 |
| Node | 一个处理步骤(LLM 调用/工具调用/逻辑判断) | 节点输入输出是否正确 |
| Edge | 节点之间的连接 | 条件分支是否走对 |
| Conditional Edge | 基于状态决定下一步走哪个节点 | 边界条件下路由是否正确 |
| Checkpoint | 状态快照,支持回溯和恢复 | 中断恢复后状态是否一致 |
6.4 工具注册与调用机制
Agent 的能力边界由它可以调用的工具决定。工具注册通常包含三个要素:
# LangChain 工具定义示例
from langchain.tools import tool
@tool
def search_knowledge_base(query: str) -> str:
"""在知识库中搜索与用户问题相关的文档。
Args:
query: 用户的搜索关键词
Returns:
与查询最相关的文档片段
"""
results = vector_store.similarity_search(query, k=5)
return "\n".join([doc.page_content for doc in results])Agent 测试的关键链路
- 工具选择:面对具体问题,Agent 是否选了正确的工具
- 参数构造:传给工具的参数是否合理、完整
- 结果解读:工具返回结果后,Agent 是否正确理解并继续
- 循环控制:是否存在无限循环,最大步数限制是否生效
- 错误恢复:工具调用失败后,Agent 是否能换一种策略
第7章:什么是 LLM 可观测性
7.1 传统 APM vs LLM 可观测性
传统 APM(Application Performance Monitoring):传统 APM(Application Performance Monitoring) 关注:延迟、错误率、吞吐量 输入/输出是确定性的 监控指标明确、阈值清晰 工具:Datadog、New Relic、Prometheus LLM 可观测性(LLM Observability):LLM 可观测性(LLM Observability) 关注:延迟 + Token 成本 + 回答质量 输入/输出是非确定性的 需要追踪 Prompt → 检索 → 生成全链路 工具:LangFuse、LangSmith、Phoenix
7.2 核心概念
| 概念 | 含义 | 类比 |
|---|---|---|
| Trace | 一次完整的请求处理过程 | 一个完整的订单从下单到完成 |
| Span | Trace 中的一个步骤 | 订单中的某个处理环节 |
| Generation | 一次 LLM 调用(特殊的 Span) | Chef 做菜的那个环节 |
示例
一次 RAG 请求在可观测性系统中的 Trace 结构:
Trace: "用户提问:公司的退款政策是什么?"
├── Span: Query 预处理 (12ms)
├── Span: Embedding 生成 (45ms)
├── Span: 向量检索 Top-5 (23ms)
├── Span: Rerank (67ms)
├── Generation: GPT-4o 调用 (1,340ms)
│ ├── Input tokens: 1,520
│ ├── Output tokens: 380
│ └── Cost: $0.034
└── Span: 后处理 & 返回 (5ms)
总耗时: 1,492ms | 总 Token: 1,900 | 总费用: $0.0347.3 为什么 LLM 应用更需要可观测性
| 特性 | 传统应用 | LLM 应用 | 可观测性需求 |
|---|---|---|---|
| 确定性 | 同输入同输出 | 同输入不同输出 | 极高 需要追踪每次输出 |
| 链路长度 | 通常 3-5 层 | 可能 10+ 步骤 | 高 需要细粒度 Span |
| 成本 | 边际成本低 | 每次调用都有 Token 费用 | 高 需要成本监控 |
| 质量评估 | 断言即可 | 需要语义级评估 | 中高 需要质量打分 |
| 调试难度 | 看日志定位 | Prompt + 模型行为交织 | 极高 需要完整上下文回放 |
第8章:可观测性要看什么
8.1 十个关键监控指标
| # | 指标 | 含义 | 告警阈值参考 |
|---|---|---|---|
| 1 | 请求量 | 每分钟/小时的 LLM 调用次数 | 突增 >200% 或骤降 >50% |
| 2 | 延迟分布 | P50/P90/P99 响应时间 | P99 > 10s |
| 3 | Token 消耗 | Input/Output Token 使用量 | 单次请求 > 8K tokens |
| 4 | 费用 | 按时间/用户/功能的费用统计 | 日费用超预算 150% |
| 5 | 错误率 | LLM 调用失败、超时、限流的比例 | > 5% |
| 6 | 幻觉率 | 回答中包含编造内容的比例 | > 10%(采样评估) |
| 7 | 用户满意度 | 点赞/点踩比例 | 点踩率 > 20% |
| 8 | 模型版本 | 当前使用的模型和版本 | 模型切换后质量下降 |
| 9 | Prompt 版本 | 当前使用的 Prompt 模板版本 | Prompt 变更后回归测试 |
| 10 | 检索命中率 | RAG 查询中检索到相关文档的比例 | 命中率 < 60% |
8.2 告警规则设计
| 告警级别 | 触发条件 | 处理方式 |
|---|---|---|
| P0 严重 | LLM API 全部超时或不可用 | 立即切换备用模型,通知 on-call |
| P0 严重 | 日费用超预算 300% | 限流或暂停服务,排查异常调用 |
| P1 重要 | 错误率 > 10% 持续 5 分钟 | 排查模型状态和网络问题 |
| P1 重要 | P99 延迟 > 15s 持续 10 分钟 | 检查模型负载和排队情况 |
| P2 一般 | 检索命中率下降 > 20% | 检查知识库索引状态 |
| P2 一般 | 用户点踩率上升 > 10% | 采样分析 Bad Case |
8.3 线上质量持续监控策略
三道防线
- 实时监控:请求量、延迟、错误率、Token 消耗 → Grafana / 云监控
- 每日采样评估:随机抽取 N 条对话,用评估模型或人工打分 → LangFuse Annotation
- 周度回归测试:用标准测试集跑一遍,检查质量是否有回退 → CI/CD Pipeline
第9章:用户反馈转化为测试用例
9.1 反馈收集机制
| 反馈方式 | 信息量 | 实现成本 | 处理方式 |
|---|---|---|---|
| 👍 / 👎 按钮 | 低(只知道好坏) | 极低 | 统计比例,筛选 Bad Case |
| 多维评分(准确性/有用性/流畅性) | 中 | 低 | 按维度分析薄弱环节 |
| 文字反馈 | 高 | 低 | NLP 分类 + 人工审核 |
| "正确答案"提交 | 极高 | 中 | 直接作为 Ground Truth |
9.2 从 Bad Case 到测试用例的转化流程
收集用户反馈 → 筛选 Bad Case → 分析根因 → 生成测试用例 → 加入回归集 → 持续监控
示例
转化案例: 用户反馈:"问'上季度销售额是多少',系统编造了一个数字。"
- 根因分析:知识库中没有最新季度的销售数据,RAG 未检索到相关文档,LLM 自行编造
- 测试用例:当知识库中不存在相关信息时,系统应回答"暂无该数据"而非编造
- 检查点:① 检索结果是否为空;② Prompt 中是否有"无信息时不要编造"的指令;③ 回答是否承认信息不足
9.3 持续改进闭环
反馈 → 测试 → 优化 闭环
用户反馈 → Bad Case 库 → 回归测试集 → 优化 Prompt/RAG → 上线验证
↻ 持续循环
关键原则
每一个线上 Bad Case 都应该变成一条回归测试用例。如果同类问题反复出现,说明根因没有被修复。回归测试集的增长速度反映了系统质量治理的力度。
第10章:LangFuse 实操
10.1 LangFuse 是什么
LangFuse 是目前最流行的开源 LLM 可观测性平台,功能覆盖 Trace 追踪、Prompt 管理、评估打分、成本统计。
| 能力 | 说明 |
|---|---|
| Trace 追踪 | 记录每次请求的完整链路(Span + Generation) |
| Prompt 管理 | 版本化管理 Prompt 模板,支持 A/B 测试 |
| 评估打分 | 支持人工标注和模型自动评分 |
| 成本统计 | 按模型/用户/时间维度统计 Token 和费用 |
| 数据集管理 | 管理测试用例集,支持批量评估 |
10.2 安装部署
# Docker 自部署(推荐用于内网环境)
git clone https://github.com/langfuse/langfuse.git
cd langfuse
# 启动 LangFuse(包含 PostgreSQL)
docker compose up -d
# 访问 http://localhost:3000
# 默认无预设账号,首次访问需注册10.3 Python 集成
# 安装 SDK
# pip install langfuse openai
from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context
from openai import OpenAI
langfuse = Langfuse(
public_key="pk-lf-xxx",
secret_key="sk-lf-xxx",
host="http://localhost:3000"
)
client = OpenAI()
@observe()
def retrieve_documents(query: str) -> list:
"""检索相关文档"""
langfuse_context.update_current_span(
metadata={"query": query, "top_k": 5}
)
results = vector_store.similarity_search(query, k=5)
return results
@observe()
def generate_answer(query: str, context: str) -> str:
"""调用 LLM 生成回答"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": f"基于以下上下文回答问题:\n{context}"},
{"role": "user", "content": query}
]
)
return response.choices[0].message.content
@observe()
def rag_pipeline(query: str) -> str:
"""完整的 RAG 管道"""
docs = retrieve_documents(query)
context = "\n".join([doc.page_content for doc in docs])
answer = generate_answer(query, context)
langfuse_context.update_current_trace(
user_id="user_123",
tags=["rag", "production"]
)
return answer10.4 追踪一次 RAG 请求的完整链路
集成完成后,每次调用 rag_pipeline() 都会在 LangFuse 中生成一条完整的 Trace。你可以在 LangFuse 界面中看到:
LangFuse 界面 - Trace 详情视图
左侧面板展示 Trace 的时间轴,可以看到每个 Span 的执行顺序和耗时:
rag_pipeline(总耗时 1,523ms)- ├──
retrieve_documents(68ms)— 输入:query,输出:5条文档 - └──
generate_answer(1,440ms)— Generation 类型 - ├── Model: gpt-4o
- ├── Input tokens: 1,520 | Output tokens: 380
- ├── Cost: $0.034
- └── 完整的 Prompt 和 Response 文本 右侧面板展示选中 Span 的详细信息,包括输入输出、元数据、耗时分布。
10.5 评估与标注
在 LangFuse 界面中,你可以对每条 Trace 进行人工打分:
- 准确性(Accuracy):回答内容是否正确
- 忠实度(Faithfulness):是否忠于检索到的上下文
- 有用性(Helpfulness):回答对用户是否有帮助
- 安全性(Safety):是否包含不当内容
第11章:成本监控实操
11.1 Token 消耗统计脚本
# 使用 LangFuse API 统计 Token 消耗
import requests
from datetime import datetime, timedelta
LANGFUSE_HOST = "http://localhost:3000"
LANGFUSE_PUBLIC_KEY = "pk-lf-xxx"
LANGFUSE_SECRET_KEY = "sk-lf-xxx"
def get_token_usage(days=7):
"""获取最近 N 天的 Token 消耗"""
end = datetime.utcnow()
start = end - timedelta(days=days)
response = requests.get(
f"{LANGFUSE_HOST}/api/public/metrics/daily",
auth=(LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY),
params={
"traceName": "rag_pipeline",
"fromTimestamp": start.isoformat(),
"toTimestamp": end.isoformat()
}
)
data = response.json()
total_input = sum(d.get("inputTokens", 0) for d in data)
total_output = sum(d.get("outputTokens", 0) for d in data)
total_cost = sum(d.get("totalCost", 0) for d in data)
print(f"时间范围: {start.date()} ~ {end.date()}")
print(f"Input Tokens: {total_input:,}")
print(f"Output Tokens: {total_output:,}")
print(f"总费用: ${total_cost:.2f}")
return data
get_token_usage(7)11.2 多维度成本统计
| 统计维度 | 价值 | 实现方式 |
|---|---|---|
| 按用户 | 找出异常高消耗用户 | Trace 中记录 user_id |
| 按功能 | 哪些功能最费钱 | Trace 中记录 tags (如 rag / chat / summary) |
| 按模型 | 不同模型的性价比 | Generation 中自动记录 model |
| 按时间 | 发现费用趋势和异常 | 按日/周/月聚合 |
11.3 异常消耗告警
# 异常消耗检测 & 告警
import statistics
def check_cost_anomaly(daily_costs: list, threshold_sigma=2):
"""基于统计方法检测费用异常"""
if len(daily_costs) < 7:
return False, "数据不足"
mean = statistics.mean(daily_costs)
stdev = statistics.stdev(daily_costs)
latest = daily_costs[-1]
upper_bound = mean + threshold_sigma * stdev
if latest > upper_bound:
alert_msg = (
f"⚠️ 费用异常告警\n"
f"今日费用: ${latest:.2f}\n"
f"历史均值: ${mean:.2f}\n"
f"告警阈值: ${upper_bound:.2f}\n"
f"偏离程度: {(latest - mean) / stdev:.1f} σ"
)
send_alert(alert_msg) # 发送到钉钉/Slack/飞书
return True, alert_msg
return False, "正常"第12章:案例 — 为一个 RAG 系统搭建可观测性
12.1 系统描述
案例背景
某公司内部知识库问答系统,基于 LangChain + Milvus + GPT-4o 构建。用户可以提问公司制度、产品手册、技术文档等内容。上线后收到大量"回答不准确"的反馈,但团队无法定位具体问题。
12.2 LangFuse 集成过程
| 步骤 | 操作 | 耗时 |
|---|---|---|
| 1 | Docker 部署 LangFuse | 30 分钟 |
| 2 | 在 RAG 管道中集成 @observe() 装饰器 | 2 小时 |
| 3 | 为每个 Trace 添加 user_id 和 tags | 30 分钟 |
| 4 | 配置 Grafana 看板(接 LangFuse 的 Prometheus Exporter) | 1 小时 |
| 5 | 建立每日采样评估流程 | 2 小时 |
12.3 上线后发现的问题
通过 LangFuse 的 Trace 追踪,团队在一周内定位了三个关键问题:
| 问题 | 发现方式 | 根因 | 修复方案 |
|---|---|---|---|
| 30% 的问题检索不到相关文档 | 检索 Span 返回结果为空 | 文档切分粒度太大(2000 字/块),语义匹配不上 | 调整切分策略为 500 字/块 + 50 字重叠 |
| 部分回答包含已过时的制度信息 | Trace 中检索到的文档是 2023 版 | 新版文档上传后未删除旧版向量 | 建立文档版本管理和向量更新流程 |
| 某个用户 Token 消耗是平均值的 50 倍 | 成本统计按用户维度排序 | 该用户在用脚本批量调用接口做数据抓取 | 增加用户级别的调用频率限制 |
12.4 优化效果
优化前:优化前 检索命中率:65% 用户满意度:58% 日均费用:$120 P99 延迟:8.5s 优化后(2 周):优化后(2 周) 检索命中率:89% +24% 用户满意度:82% +24% 日均费用:$75 -37% P99 延迟:4.2s -50%
案例总结
可观测性不仅仅是"看日志",它是**让问题从"感觉回答不好"变成"知道哪里出了问题、为什么出问题、怎么修"**的关键基础设施。没有可观测性的 LLM 应用,就像没有仪表盘的汽车——能开,但出问题时你不知道发动机温度是多少。
第13章:课堂练习
课堂练习
练习一:架构分层分析 你所在的团队正在开发一个"智能客服"系统,使用 Dify 搭建工作流,接入了 GPT-4o 和企业知识库。请回答:
- 画出这个系统的完整架构图(参考第2章的六层模型)
- 列出每一层至少 3 个测试关注点
- 如果用户反馈"客服回答了一个完全不相关的内容",你会按什么顺序排查哪些层?
课堂练习
练习二:可观测性指标设计 为上述智能客服系统设计可观测性方案:
- 从第8章的 10 个指标中,选出对这个系统最重要的 5 个,并说明理由
- 为每个指标设计告警阈值和告警级别
- 设计一个"每日质量报告"的模板,包含哪些内容
课堂练习
练习三:LangFuse 实操(需要环境)
- 用 Docker 部署一个 LangFuse 实例
- 编写一个简单的 RAG Pipeline(可以用 Mock 数据),集成 LangFuse 追踪
- 在 LangFuse 界面中查看 Trace,回答以下问题:
- 整条链路中最耗时的环节是哪个?
- 一次请求消耗了多少 Token?
- 如果检索结果为空,Trace 中能看出来吗?
补充:OpenTelemetry 与 GenAI 语义约定
为什么需要 OpenTelemetry
LangFuse 等专用工具解决了 LLM 层面的可观测性,但企业级系统还需要把 LLM 调用放到整个请求链路(Web → API → LLM → 数据库)中统一追踪。OpenTelemetry(OTel)是云原生可观测性的事实标准,2025 年开始正式定义了 GenAI 语义约定,让 LLM 调用的 Trace/Span 能和传统服务的 Span 统一串联。
GenAI 语义约定核心字段
| Span 属性 | 说明 | 示例值 |
|---|---|---|
| gen_ai.system | 模型提供方 | openai |
| gen_ai.request.model | 请求的模型名 | gpt-4o |
| gen_ai.response.model | 实际使用的模型 | gpt-4o-2024-08-06 |
| gen_ai.request.max_tokens | 最大生成 token 数 | 1000 |
| gen_ai.request.temperature | 温度参数 | 0.7 |
| gen_ai.usage.input_tokens | 输入 token 数 | 150 |
| gen_ai.usage.output_tokens | 输出 token 数 | 320 |
| gen_ai.response.finish_reasons | 结束原因 | ["stop"] |
Python 集成示例
# pip install opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-openai
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
# 自动 instrument OpenAI 客户端
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
OpenAIInstrumentor().instrument()
# 之后所有 OpenAI 调用自动生成带 gen_ai.* 属性的 Span
from openai import OpenAI
client = OpenAI()
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "你好"}]
)
# 控制台会输出包含 gen_ai.usage.input_tokens 等字段的 Span与 LangFuse 的关系
| 维度 | OpenTelemetry | LangFuse |
|---|---|---|
| 定位 | 通用可观测性标准,覆盖全链路 | LLM 专用追踪平台 |
| Span 类型 | HTTP、DB、消息队列、LLM 全覆盖 | 专注 Generation、Trace、Score |
| 生态 | Jaeger / Grafana Tempo / Datadog 等 | 自有 Dashboard + API |
| 适用场景 | 企业级全链路追踪 | LLM 应用开发和调试 |
| 建议 | 两者互补:OTel 做全链路,LangFuse 做 LLM 精细化分析 |
测试人员关注的 OTel 检查点
- Span 完整性:每次 LLM 调用是否生成了 Span,gen_ai.* 字段是否齐全
- 链路串联:LLM Span 是否正确挂在父 Span(如 HTTP 请求)下
- Token 准确性:OTel 记录的 token 数是否与 API 响应一致
- 错误标记:LLM 调用失败时 Span 的 status 是否设为 ERROR
- 敏感数据:Span 中是否无意间记录了用户 Prompt 或 API Key
课堂练习
- 用 OpenTelemetry 的 ConsoleExporter 跑一次 OpenAI 调用,观察输出的 Span 包含哪些 gen_ai.* 属性。
- 对比 OTel 记录的 token 数和 API 响应中 usage 的 token 数是否一致。
补充参考答案要点
- 练习一的架构分层至少要拆到输入层、编排层、模型层、工具/检索层和观测层,问题必须能落到具体一层。
- 练习二的指标设计不能只看最终分数,还要同时覆盖流量、延迟、错误类型、成本和人工反馈。
- 练习三做 LangFuse 或同类平台时,核心不是“把日志接进去”,而是保证 trace、prompt、模型输出和评价结果能串起来看。