首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >大模型应用开发中的可观测性实战:从日志到调用链,构建 LLM 应用的可靠性工程

大模型应用开发中的可观测性实战:从日志到调用链,构建 LLM 应用的可靠性工程

原创
作者头像
用户12566962
修改2026-08-05 15:49:43
修改2026-08-05 15:49:43
1500
举报

大模型应用开发中的可观测性实战:从日志到调用链,构建 LLM 应用的可靠性工程

当你的大模型应用从 Demo 走向生产,第一个让你夜不能寐的问题一定是:“它为什么又答错了?” ——没有异常堆栈,没有报错日志,只有一段看似合理却完全跑偏的文本。传统监控手段(日志级别、错误码)在大模型场景下几乎失效,因为 “错误”不再是程序崩溃,而是语义偏差、幻觉、检索遗漏

本文基于我们团队在金融 QA 机器人上的真实踩坑经历,分享一套面向 LLM 应用的可观测性体系设计方案,涵盖结构化日志、全链路追踪、语义指标监控三大支柱,并提供可落地的代码实践。


一、为什么传统监控不灵了?

传统的 APM(应用性能监控)针对确定性代码设计,你能预判每行代码的输入输出。而 LLM 应用具有三大不确定性:

  1. 输入无限:用户问题无法穷举,意图分布长尾。
  2. 中间过程黑盒:检索召回了哪些片段?重排序丢掉了什么?Prompt 被如何拼装?
  3. 输出“软错误”:语法正确、逻辑通顺,但事实错误(例如回答年假天数比制度多一倍)。

因此,我们需要一种可解释、可回溯、可量化的观测体系,让每一次请求都像一段可解剖的流水线。


二、三支柱落地实践

2.1 结构化日志:让每一帧都有迹可循

不要用 print(),不要用 logging.info() 随便拼字符串。采用 JSON 格式结构化日志,并强制包含以下字段:

  • trace_idspan_id(用于关联调用链)
  • session_iduser_id
  • step(检索、重排、生成、校验)
  • token_usagelatency_ms
  • retrieved_docs(只记录文档 ID 和 score)
  • prompt_preview(截断过长)

Python 实现:

代码语言:javascript
复制
import json
import logging
from pythonjsonlogger import jsonlogger

class LLMJsonFormatter(jsonlogger.JsonFormatter):
    def add_fields(self, log_record, record, message_dict):
        super().add_fields(log_record, record, message_dict)
        if not log_record.get('timestamp'):
            log_record['timestamp'] = datetime.utcnow().isoformat()
        # 强制注入全局 trace_id(需从 context 中取)
        log_record['trace_id'] = get_current_trace_id() or 'unknown'

logger = logging.getLogger('llm_app')
handler = logging.StreamHandler()
handler.setFormatter(LLMJsonFormatter())
logger.addHandler(handler)

# 使用示例
logger.info(
    "retrieval_completed",
    extra={
        "step": "retrieve",
        "query": user_query,
        "top_doc_ids": [d.metadata['id'] for d in docs],
        "top_scores": [s for _, s in docs_with_scores],
        "cost_ms": elapsed
    }
)

这些 JSON 日志直接灌入 ELK 或 Loki,你可以通过 trace_id 一键聚合所有步骤,快速定位“检索召回太靠后”还是“Prompt 被截断”。


2.2 全链路追踪(OpenTelemetry + Jaeger)

LLM 应用本质是一条流水线:意图分类 → 重写 → 检索 → 重排序 → 上下文压缩 → LLM 生成 → 事实校验。每一步都可能变慢或出错。用 OpenTelemetry 自动埋点并可视化在 Jaeger 中。

部署 OpenTelemetry Collector 并集成 FastAPI:

代码语言:javascript
复制
# docker-compose 部分
services:
  app:
    image: my-llm-app
    environment:
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
      - OTEL_SERVICE_NAME=llm-qa
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    volumes:
      - ./otel-config.yaml:/etc/otel/config.yaml
    command: ["--config=/etc/otel/config.yaml"]
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"

代码中手动创建子 Span:

代码语言:javascript
复制
from opentelemetry import trace
tracer = trace.get_tracer(__name__)

@app.post("/ask")
async def ask(query: str):
    with tracer.start_as_current_span("full_pipeline") as parent_span:
        parent_span.set_attribute("query", query)

        with tracer.start_as_current_span("retrieve") as span:
            docs = retriever.get_relevant_documents(query)
            span.set_attribute("doc_count", len(docs))

        with tracer.start_as_current_span("rerank") as span:
            top_docs = reranker.rerank(query, docs, top_n=3)
            span.set_attribute("top_scores", [d.score for d in top_docs])

        with tracer.start_as_current_span("llm_generate") as span:
            answer = llm.generate(query, top_docs)
            span.set_attribute("token_usage", answer.usage)

        return answer

在 Jaeger UI 中,你可以清晰看到每一步的耗时占比,发现“检索”阶段突然从 50ms 飙到 800ms,立即排查向量数据库连接池是否耗尽。


2.3 语义指标监控(RAGAS + 自定义 Metrics)

业务指标不是 QPS 和错误率,而是 检索命中率、答案忠实度、答案相关性。这些可通过离线评测框架(如 RAGAS)计算,但生产环境需要实时或准实时监控。

我们实现了一个异步评分器,将请求日志采样后发送至评估服务:

代码语言:javascript
复制
# evaluator.py
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_relevancy

def online_evaluate(sample: dict):
    # sample 包含 question, answer, contexts, ground_truth (若有)
    score = evaluate(
        dataset=[sample],
        metrics=[faithfulness, answer_relevancy, context_relevancy]
    )
    # 推送至 Prometheus
    for metric_name, value in score.items():
        llm_metric.labels(metric=metric_name).set(value)
    return score

Prometheus 指标暴露与告警规则:

代码语言:javascript
复制
from prometheus_client import Gauge, generate_latest

llm_metric = Gauge('llm_qa_ragas_score', 'RAGAS evaluation score', ['metric'])

@app.get("/metrics")
def metrics():
    return Response(generate_latest(), media_type="text/plain")

告警规则:如果 faithfulness 连续 3 个采样周期低于 0.7,触发告警,提示可能需要更新知识库或调整 Prompt。


三、缓存与降级:可观测性驱动的韧性设计

有了观测数据,我们能精准优化。例如,我们发现高频问题(“年假如何计算”)占请求量的 20%,完全可缓存。

Redis 缓存语义相似结果:

代码语言:javascript
复制
import hashlib
import redis
from sentence_transformers import SentenceTransformer

cache = redis.Redis(host='localhost', decode_responses=True)
encoder = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')

def get_cached_answer(query):
    # 对 query 做语义哈希(如用 embedding 的前 10 维作为 key)
    emb = encoder.encode(query)
    key = hashlib.md5(emb[:10].tobytes()).hexdigest()
    cached = cache.get(f"qa:{key}")
    if cached:
        logger.info("cache_hit", extra={"query": query})
        return json.loads(cached)
    return None

同时,当 LLM API 连续超时,我们利用观测数据触发降级——回退到只返回检索片段拼接的摘要,避免无响应。


四、落地效果与总结

我们这套体系上线后,问题定位时间从平均 2 小时缩短到 15 分钟。典型场景:某天 faithfulness 指标突降,Jaeger 显示 llm_generate 耗时正常但 retrieve 返回的文档平均分变低。进一步查结构化日志发现,某个向量库索引被意外重建,导致 embedding 分布偏移。半小时内回滚索引,指标恢复。

核心经验提炼:

  • 日志结构化是基础,没有它后续无从谈起。
  • 链路追踪让黑盒可视化,尤其适合多步骤流水线。
  • 语义指标才是 LLM 应用的“健康检查”,QPS 只能告诉你系统没崩,不能告诉你回答对不对。
  • 缓存与降级策略必须依赖观测数据驱动,否则无处下手。

最后,不要试图一次性做全。从结构化日志 + trace_id 关联开始,两周内你就能感受到差异。工具选型上,推荐 OpenTelemetry + Jaeger + Prometheus + Grafana 组合,全部开源且生态成熟。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 大模型应用开发中的可观测性实战:从日志到调用链,构建 LLM 应用的可靠性工程
  • 一、为什么传统监控不灵了?
  • 二、三支柱落地实践
    • 2.1 结构化日志:让每一帧都有迹可循
    • 2.2 全链路追踪(OpenTelemetry + Jaeger)
    • 2.3 语义指标监控(RAGAS + 自定义 Metrics)
  • 三、缓存与降级:可观测性驱动的韧性设计
  • 四、落地效果与总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档