工程化应用基础设施:可观测性要覆盖 提示词、检索和执行
一、AI 链路的黑盒不能只靠日志猜
传统后端排障时,我们看接口状态码、数据库慢查询、服务日志和链路追踪。AI 应用多了几层:Prompt 组装、向量检索、重排、模型推理、内容安全、流式输出。这些步骤任何一个出问题,用户看到的都可能只是“回答变差了”。
如果可观测性只停留在 HTTP 200 和 500,就无法解释质量问题。模型没有报错,但检索召回错了;推理没有超时,但 Prompt 被模板拼坏了;接口成功返回,但答案没有引用来源。这些都需要专门的观测字段。
二、观测链路:从请求到质量反馈
flowchart LR A[用户请求] --> B[Prompt 模板] B --> C[向量检索] C --> D[重排与截断] D --> E[模型推理] E --> F[内容安全] F --> G[用户反馈] G --> H[质量分析]这条链路里,质量反馈也应进入观测系统。用户点踩、重新生成、复制结果、追问纠错,都可能说明输出质量不稳定。技术指标和行为指标结合,才能更接近真实问题。
三、埋点结构:既能排障,又不泄露隐私
下面是一个简化的事件结构。注意不要直接记录完整用户输入和完整输出,生产中应做脱敏、摘要或采样。
{ "request_id": "req_20260701_001", "template_version": "qa_v7", "model": "llm-prod-a", "retrieval_top_k": 8, "retrieval_latency_ms": 42, "prompt_tokens": 1380, "completion_tokens": 420, "infer_latency_ms": 2100, "safety_action": "pass", "feedback": "regenerate" }这些字段能回答很多问题:某个模板版本是否导致 token 暴涨,检索延迟是否突然升高,某个模型是否更容易被重新生成,安全策略是否误拦截。没有结构化事件,团队只能翻日志和猜用户体验。
四、工程边界:质量指标不能滥用
AI 质量很难用一个分数概括。回答长度、用户停留、点赞率、重试次数都只是侧面信号。若把某个指标当唯一目标,模型输出很容易被优化歪。比如只追求点赞,可能生成更讨好的内容;只追求低延迟,可能牺牲检索深度;只追求少重试,可能让用户不愿继续使用。
取舍在于隐私和可排障性。记录越多,排障越容易,但隐私风险越大。我的建议是默认记录结构化元数据,正文只做严格采样和脱敏,并为敏感业务提供关闭开关。对内部调试样本要有访问审计,不要让“排障需要”成为无限读取用户内容的理由。
还要把观测和发布关联。每次模型、Prompt、检索索引、重排策略变更,都要带版本号。出现质量波动时,能迅速定位是哪次变更影响了哪类请求。AI 应用的回归并不总是崩溃,更多时候是答案悄悄变差。版本化观测,是发现这种问题的基础。
实践中还要区分“系统错误”和“质量错误”。系统错误通常表现为超时、异常码、依赖不可用;质量错误则可能表现为引用缺失、答非所问、事实冲突或用户连续追问。两类错误的处理方式不同,前者偏工程恢复,后者偏样本分析和策略回归。如果把它们混在一个错误率里,团队会误判问题性质。
比较稳妥的做法是建立少量高价值看板:请求量、延迟、成本、检索命中率、生成重试率、用户负反馈率、版本维度对比。看板不需要一开始很复杂,但字段要稳定。可观测性不是把所有东西都打出来,而是让值班同学在半夜也能判断:是系统坏了,还是质量变差了,下一步该找谁处理。
生产落地补充:从能跑到可维护
从生产落地角度看,这类方案不能只停留在主流程。更关键的是把输入校验、失败分支、资源上限和回滚路径提前写清楚。主流程通常容易在演示环境里跑通,真正暴露问题的是异常输入、依赖抖动、并发放大和权限边界。一篇技术方案如果没有解释这些约束,读者很难判断它能否放进真实系统。
异常路径补充:把失败当成接口契约
下面的补充片段强调一个原则:调用方必须得到稳定、可解释的错误,而不是在超时、空输入或依赖失败时收到模糊结果。代码不追求覆盖所有业务细节,而是展示输入校验、超时控制和错误封装这三个生产系统最容易遗漏的环节。
from __future__ import annotations import asyncio from dataclasses import dataclass @dataclass class GuardedResult: ok: bool value: str = "" error: str = "" async def run_with_guard(input_text: str, timeout: float = 3.0) -> GuardedResult: if not input_text.strip(): return GuardedResult(ok=False, error="input cannot be empty") try: async with asyncio.timeout(timeout): # 真实项目中这里放模型调用、数据库查询或外部服务请求。 await asyncio.sleep(0.01) return GuardedResult(ok=True, value=f"accepted: {input_text}") except TimeoutError: return GuardedResult(ok=False, error="operation timeout") except Exception as exc: return GuardedResult(ok=False, error=f"operation failed: {exc}")五、总结
AI 应用基础设施的可观测性,必须覆盖 Prompt、检索、推理、安全和反馈。既要能排障,也要控制隐私边界。把链路看清楚,模型质量问题才不会变成一句模糊的“感觉不对”。