头图

大模型应用可观测性实践:从 Prompt 日志到 Trace 评估闭环

很多团队在做大模型应用时,前期最关注的是“功能能不能跑通”:

  • 能不能接入模型?
  • 能不能调用知识库?
  • 能不能完成多轮对话?
  • 能不能调用工具?
  • 能不能输出结构化结果?

但当应用真正上线后,问题会迅速变复杂。

用户反馈“回答不准”,到底是哪里出了问题?

  • 是用户问题没理解对?
  • 是检索结果不相关?
  • 是 Prompt 设计有问题?
  • 是模型幻觉?
  • 是工具调用失败?
  • 是上下文太长导致关键信息丢失?
  • 是模型版本变更后效果下降?
  • 是某个租户的数据源质量差?

传统 Web 应用可以通过日志、指标和链路追踪排查问题。
大模型应用同样需要可观测性,只不过观测对象从接口耗时、错误码,扩展到了 Prompt、上下文、检索结果、模型输出、工具调用和人工反馈。

本文从工程实践角度聊聊:如何为大模型应用搭建一套可用的可观测体系。


一、为什么大模型应用更需要可观测性?

传统系统的问题通常比较确定。

例如接口报错:

HTTP 500
数据库连接超时
Redis key 不存在
接口响应超过 3 秒

这类问题可以通过日志和监控快速定位。

但大模型应用的问题往往是“非确定性”的:

用户:这个答案不对
系统:请求成功,模型也正常返回了

从系统层面看,一切都正常:

  • HTTP 状态码是 200
  • 模型接口没有报错
  • 向量库也返回了结果
  • 工具调用也成功了
  • 页面也展示了答案

但业务结果就是不对。

这就是大模型应用可观测性的难点:
系统成功不代表语义正确。

所以我们需要观测的不只是“有没有报错”,还要观测:

  • 模型为什么这么回答?
  • 它看到了哪些上下文?
  • 检索召回了哪些文档?
  • Prompt 最终拼接成什么样?
  • 工具调用输入输出是什么?
  • 用户是否满意?
  • 哪些问题高频失败?
  • 哪次版本发布导致效果下降?

二、大模型应用的典型调用链路

以一个 RAG 问答系统为例,一次请求可能经历如下流程:

用户问题
  ↓
意图识别 / 问题改写
  ↓
向量检索
  ↓
关键词检索
  ↓
Rerank 重排序
  ↓
Prompt 拼接
  ↓
大模型生成
  ↓
答案后处理
  ↓
返回用户
  ↓
用户反馈

如果系统支持 Agent,还可能增加:

工具选择
  ↓
参数生成
  ↓
权限校验
  ↓
工具执行
  ↓
结果总结

这条链路中任何一个环节出问题,最终答案都可能失败。

因此,大模型应用的可观测性至少要覆盖三类数据:

  1. Trace 链路数据
  2. Prompt 与上下文数据
  3. 评估与反馈数据

三、需要记录哪些日志?

很多团队最开始只记录用户输入和模型输出:

{
  "question": "如何申请报销?",
  "answer": "您可以登录 OA 系统申请报销。"
}

这远远不够。

一个更完整的请求日志应该包含:

{
  "request_id": "req_202506090001",
  "user_id": "u_10086",
  "tenant_id": "t_001",
  "session_id": "s_abc",
  "timestamp": "2026-06-09T10:30:00",
  "query": "差旅报销需要哪些材料?",
  "rewritten_query": "员工申请差旅报销需要提交哪些材料?",
  "retrieval": {
    "top_k": 5,
    "documents": [
      {
        "doc_id": "doc_001",
        "title": "差旅报销制度",
        "chunk_id": "chunk_12",
        "score": 0.86,
        "content_preview": "员工申请差旅报销需提供发票、行程单、审批单..."
      }
    ]
  },
  "prompt": {
    "template_version": "rag_qa_v3",
    "input_tokens": 2480
  },
  "model": {
    "provider": "xxx",
    "model_name": "Qwen2.5-14B-Instruct",
    "temperature": 0.2,
    "max_tokens": 1024,
    "output_tokens": 286,
    "latency_ms": 1850
  },
  "answer": "申请差旅报销通常需要提供发票、行程单、出差审批单等材料。",
  "cost": {
    "prompt_tokens": 2480,
    "completion_tokens": 286
  }
}

这些信息可以帮助我们回答几个关键问题:

  • 这个答案是基于哪些文档生成的?
  • 检索结果是否相关?
  • Prompt 模板版本是什么?
  • 模型版本是什么?
  • 输入输出 token 消耗是多少?
  • 延迟主要来自哪里?
  • 是否某个租户频繁出现低质量回答?

四、Trace:把一次大模型请求拆开看

仅有一条完整日志还不够。
如果请求链路复杂,最好用 Trace 方式记录每一步耗时和输入输出。

例如:

request_id: req_001
├── query_rewrite        120ms
├── vector_search         80ms
├── keyword_search        65ms
├── rerank               210ms
├── prompt_build          15ms
├── llm_generate        1850ms
└── post_process          20ms

如果是 Agent:

request_id: req_002
├── planner              450ms
├── tool_call:get_user   180ms
├── tool_call:get_order  260ms
├── tool_call:refund_policy 90ms
└── final_answer         900ms

这样就能快速定位:

  • 是检索慢?
  • 是 Rerank 慢?
  • 是模型生成慢?
  • 是工具调用慢?
  • 是某个外部 API 不稳定?

在工程实现上,可以为每个步骤封装统一的 Span。

示例:

import time
import uuid
from contextlib import contextmanager

@contextmanager
def trace_span(trace_id, name, metadata=None):
    start = time.time()
    span_id = str(uuid.uuid4())
    error = None

    try:
        yield {
            "trace_id": trace_id,
            "span_id": span_id,
            "name": name,
            "metadata": metadata or {}
        }
    except Exception as e:
        error = str(e)
        raise
    finally:
        end = time.time()
        log = {
            "trace_id": trace_id,
            "span_id": span_id,
            "name": name,
            "duration_ms": int((end - start) * 1000),
            "error": error
        }
        print(log)

使用方式:

trace_id = "req_001"

with trace_span(trace_id, "vector_search", {"top_k": 5}):
    docs = vector_search("差旅报销需要哪些材料?")

with trace_span(trace_id, "llm_generate", {"model": "Qwen2.5-14B"}):
    answer = call_llm(prompt)

实际生产环境中,可以接入 OpenTelemetry、Jaeger、Prometheus、Grafana,也可以使用 Langfuse、Phoenix、Helicone 等 LLM 可观测工具。


五、Prompt 版本管理很重要

大模型应用中,Prompt 就像代码。

但很多团队在管理 Prompt 时非常随意:

prompt = f"""
你是一个智能助手,请回答用户问题:
{question}
"""

随着业务迭代,Prompt 会不断变化:

  • 增加角色设定
  • 增加输出格式要求
  • 增加安全限制
  • 增加引用来源
  • 增加拒答策略
  • 增加 few-shot 示例

如果没有版本管理,就会出现一个问题:

用户昨天反馈某个答案不对,但今天 Prompt 已经改了,无法复现当时结果。

因此建议为 Prompt 增加版本号:

rag_qa_v1
rag_qa_v2
rag_qa_v3
contract_extract_v1
customer_service_v4

每次请求日志中记录:

{
  "prompt_template": "rag_qa_v3",
  "prompt_hash": "8d7f3a9c",
  "variables": {
    "question": "差旅报销需要哪些材料?",
    "context_count": 5
  }
}

如果涉及敏感数据,完整 Prompt 可以脱敏后保存,或者只保存 hash 与变量摘要。


六、RAG 场景要重点观测检索质量

很多 RAG 系统回答不准,并不是模型不行,而是检索没召回正确内容。

常见问题包括:

  • 文档切分不合理
  • 向量模型效果差
  • 关键词召回缺失
  • TopK 太小
  • Rerank 排序错误
  • 文档内容过期
  • 权限过滤导致无结果
  • OCR 解析质量差

因此,RAG 观测不能只看最终答案,要看检索中间结果。

建议记录:

{
  "query": "差旅报销需要哪些材料?",
  "retrieval": {
    "strategy": "hybrid_search",
    "vector_top_k": 20,
    "keyword_top_k": 20,
    "rerank_top_k": 5,
    "final_chunks": [
      {
        "doc_id": "doc_001",
        "chunk_id": "c_12",
        "score": 0.91,
        "title": "差旅报销制度",
        "page": 3
      }
    ]
  }
}

同时,可以做一些检索指标:

  • 命中率
  • 无结果率
  • Top1 命中率
  • TopK 命中率
  • 平均召回分数
  • 用户点击引用比例
  • 文档过期命中比例

如果有标注数据,还可以计算:

Recall@K
MRR
NDCG
Hit Rate

这些指标能帮助我们判断:
问题到底在检索,还是在生成。


七、Agent 场景要记录工具调用过程

Agent 应用的问题更复杂,因为它不只是回答,还会执行动作。

一次工具调用至少需要记录:

{
  "tool_call_id": "tool_001",
  "tool_name": "query_order",
  "arguments": {
    "order_id": "A1001"
  },
  "status": "success",
  "latency_ms": 230,
  "result_preview": {
    "status": "已发货",
    "express": "顺丰"
  }
}

如果是高风险工具,还要记录:

{
  "tool_name": "refund_order",
  "risk_level": "high",
  "need_confirm": true,
  "confirmed_by_user": true,
  "operator": "u_10086"
}

Agent 可观测性重点关注:

  • 工具是否选对
  • 参数是否生成正确
  • 工具是否执行成功
  • 是否出现重复调用
  • 是否出现无意义循环
  • 是否越权调用
  • 是否触发人工确认
  • 最终结果是否符合用户目标

对于多步 Agent,还建议设置最大执行步数:

MAX_STEPS = 8

for step in range(MAX_STEPS):
    action = agent.plan()
    if action.type == "final":
        break
    result = execute_tool(action)
else:
    raise RuntimeError("Agent exceeded max steps")

否则可能出现模型一直调用工具、不停循环的问题。


八、评估数据:让反馈进入闭环

可观测性不仅是为了排查问题,更重要的是形成优化闭环。

用户反馈可以分为显式反馈和隐式反馈。

1. 显式反馈

例如:

  • 点赞 / 点踩
  • 是否解决问题
  • 用户选择错误原因
  • 人工标注正确答案
  • 客服质检结果

可以记录:

{
  "request_id": "req_001",
  "feedback": {
    "rating": "bad",
    "reason": "答案不完整",
    "correct_answer": "差旅报销还需要出差审批单和行程单。"
  }
}

2. 隐式反馈

例如:

  • 用户是否追问
  • 用户是否复制答案
  • 用户是否点击引用文档
  • 用户是否转人工
  • 用户是否重新提问
  • 用户停留时长

这些行为虽然不一定准确,但可以作为辅助信号。


九、常见线上问题如何定位?

下面列几个典型问题。

问题 1:用户说答案胡编

排查顺序:

1. 查看最终 Prompt
2. 查看检索结果是否包含答案依据
3. 查看模型是否引用了不存在的内容
4. 查看 temperature 是否过高
5. 查看系统提示词是否要求“不知道就说不知道”
6. 查看是否缺少引用约束

优化方式:

  • 降低 temperature
  • 增加基于上下文回答约束
  • 增加引用来源
  • 检索不到时拒答
  • 对答案做事实一致性检查

问题 2:RAG 召回不到正确文档

排查顺序:

1. 文档是否入库
2. 文档解析是否正确
3. chunk 是否切得太碎或太大
4. embedding 模型是否适合业务语料
5. query rewrite 是否改坏了问题
6. 权限过滤是否误过滤
7. TopK 是否太小

优化方式:

  • 调整切分策略
  • 引入混合检索
  • 增加 Rerank
  • 使用领域 embedding
  • 建立黄金测试集评估 Recall@K

问题 3:响应突然变慢

排查顺序:

1. Trace 看耗时集中在哪个 Span
2. 检查 LLM 推理耗时
3. 检查向量库耗时
4. 检查 Rerank 服务耗时
5. 检查工具 API 是否超时
6. 检查输入 token 是否变长

优化方式:

  • 缓存检索结果
  • 控制上下文长度
  • 降低 rerank top_k
  • 增加超时降级
  • 使用流式输出改善首响体验

问题 4:模型升级后效果下降

排查顺序:

1. 对比模型版本
2. 对比相同 Prompt 下输出差异
3. 跑离线评估集
4. 分析失败样本类型
5. 检查输出格式是否不稳定

优化方式:

  • 建立模型版本灰度机制
  • 上线前跑回归测试
  • 保留旧模型回滚能力
  • 对结构化输出增加校验和修复

十、指标体系怎么设计?

大模型应用的指标可以分为四类。

1. 性能指标

平均响应时间
P95 / P99 延迟
TTFT
输出 token 速度
请求超时率
工具调用耗时
检索耗时

2. 成本指标

输入 token 数
输出 token 数
单次请求成本
不同租户成本
不同模型成本
缓存命中率

3. 质量指标

用户满意度
点赞率
点踩率
人工通过率
答案准确率
引用命中率
幻觉率
结构化输出成功率

4. 稳定性指标

请求失败率
模型接口错误率
向量库错误率
工具调用失败率
Agent 超步数比例
JSON 解析失败率
拒答比例

比较推荐的方式是将指标按业务场景拆开看。
例如客服机器人、合同审查、代码助手、数据分析 Agent 的质量指标并不完全相同。


十一、一个简单的请求记录封装

下面给一个简化示例,用于记录 LLM 调用信息。

import time
import uuid

def call_llm_with_log(client, model, messages, temperature=0.2):
    request_id = str(uuid.uuid4())
    start = time.time()

    try:
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature
        )

        latency_ms = int((time.time() - start) * 1000)
        answer = response.choices[0].message.content

        log = {
            "request_id": request_id,
            "model": model,
            "temperature": temperature,
            "messages_count": len(messages),
            "latency_ms": latency_ms,
            "answer_preview": answer[:200],
            "status": "success"
        }

        print(log)
        return answer

    except Exception as e:
        latency_ms = int((time.time() - start) * 1000)

        log = {
            "request_id": request_id,
            "model": model,
            "latency_ms": latency_ms,
            "status": "error",
            "error": str(e)
        }

        print(log)
        raise

真实项目中不建议只用 print,可以写入:

  • Elasticsearch
  • ClickHouse
  • PostgreSQL
  • Kafka
  • OpenTelemetry Collector
  • 云日志服务

十二、隐私与安全问题

记录 Prompt 和模型输出时,一定要注意数据安全。

大模型日志中可能包含:

  • 用户手机号
  • 身份证号
  • 邮箱
  • 地址
  • 合同金额
  • 客户名称
  • 内部文档
  • 业务系统数据
  • API 返回结果

因此需要做:

1. 脱敏

例如:

手机号:138****1234
身份证:320***********0012
邮箱:zh***@example.com

2. 权限控制

不是所有开发人员都应该看到完整请求内容。
可以按角色区分:

研发:看 Trace 和错误信息
算法:看脱敏后的 Prompt 和输出
业务:看标注样本和用户反馈
管理员:看完整审计日志

3. 日志保留周期

不要无限期保存所有原始数据。
可以按照数据敏感级别设置保留周期:

原始 Prompt:7 天
脱敏日志:90 天
聚合指标:长期保存
评估样本:人工确认后保存

十三、推荐的落地路径

如果从 0 开始建设大模型可观测性,不建议一上来就做得特别复杂。

可以按三个阶段推进。

第一阶段:基础日志

先记录:

  • 用户问题
  • 模型回答
  • 模型名称
  • Prompt 版本
  • token 消耗
  • 请求耗时
  • 错误信息

目标:能复现单次问题。


第二阶段:链路追踪

增加:

  • 检索结果
  • Rerank 分数
  • 工具调用记录
  • 每个步骤耗时
  • Trace ID
  • Prompt hash

目标:能定位问题发生在哪个环节。


第三阶段:评估闭环

继续增加:

  • 用户反馈
  • 人工标注
  • 离线评估集
  • A/B 测试
  • 模型版本对比
  • Prompt 回归测试

目标:能持续优化效果,而不是靠感觉调 Prompt。


总结

大模型应用上线后,真正的挑战不是“让模型回答一次”,而是让系统在真实业务中持续稳定地回答、可解释地回答、可优化地回答。

大模型可观测性需要覆盖:

  • Prompt 版本
  • 检索过程
  • 上下文内容
  • 模型输入输出
  • 工具调用链路
  • 性能与成本
  • 用户反馈
  • 离线评估

它的价值不只是排查 bug,更重要的是建立从线上问题到离线优化的闭环。

对于企业级 AI 应用来说,如果没有可观测性,就很难判断效果问题到底来自模型、数据、检索、Prompt 还是工具链路。
而当日志、Trace、指标和评估体系逐渐完善后,大模型应用才真正具备可运维、可迭代、可规模化落地的基础。


空虚的硬币
1 声望0 粉丝