这次我们聊的主题不是某个具体模型,而是一个正在把 AI 编程工具用户变成真正 AI 工程师的方法论:Observability(可观测性)如何把 Vibe Coding 变成 AI Engineering。
Vibe Coding 是依赖 AI 生成代码的开发方式,常见于 Cursor、Trae、Copilot 这类工具。你给出意图,AI 写代码,你凭感觉看结果是否合理。这种节奏很快,但问题也很明显:项目变大之后,回归靠猜、定位靠翻日志、上线不放心。可观测性要解决的正是这件事:让 AI 应用和 AI 生成代码的运行过程可回放、可度量、可评测。
这篇文章会先讲清楚可观测性为什么是 Vibe Coding 工程化的关键,再给一套最小可落地框架,包含日志、指标、链路追踪(Trace)、效果评测四个维度。之后会演示如何用日志记录一次 LLM 调用、如何给批量任务打 Trace、如何观察资源占用,最后给一张常见问题排查表。适合正在使用 Cursor、Trae、Copilot 生成代码,或者正在开发 RAG、Agent、LLM API 网关的开发者。
1. 可观测性核心能力速览
先说结论:可观测性不是锦上添花的监控,而是把 Vibe Coding 从“生成代码”推进到“维护系统”的工程基础设施。
| 能力项 | 说明 |
|---|---|
| 主题类型 | AI 应用可观测性与工程化方法论 |
| 落地形态 | 日志系统、指标采集、链路追踪、效果评测、反馈回收 |
| 核心能力 | 记录一次 LLM 调用的输入、输出、模型、上下文、Token 数、耗时,支持回归评估和批量监控 |
| 硬件门槛 | 可观测平台本身可在 CPU 机器上运行;GPU 只看你是否同时跑本地 LLM 推理 |
| 显存占用 | 可观测组件通常不依赖 GPU;本地推理的显存占用以推理服务实际值为准 |
| 支持平台 | Linux、macOS、Windows,Windows 建议使用 WSL2 |
| 启动方式 | Docker Compose、云服务、SDK 埋点 |
| 是否支持 API | 支持,通常通过 SDK 或 HTTP 上报 Trace 和指标 |
| 是否支持批量任务 | 支持,批量任务可按 Trace ID 或任务 ID 聚合 |
| 适合场景 | AI 编程、RAG 应用、Agent 系统、LLM API 网关、批量推理任务 |
这里有一个关键点要分清:可观测层并不依赖 GPU。很多人以为做 AI 工程就必须先买一张大显存显卡,实际上日志、Trace、指标采集这些组件都是轻量服务,CPU 机器就能跑。只有当你用本地模型做推理时,显存才变成瓶颈。
所以,这个主题的硬件门槛取决于两部分:
- 可观测平台本身:1 到 2 台 CPU 机器,8G 以上内存,磁盘要预留足够日志和 Trace 的存储空间。
- 本地推理服务:如果有,才需要考虑 GPU 显存。
2. 适用场景与使用边界
可观测性不是所有团队都需要马上全面铺开,但它有非常明确的使用边界。
适合的场景包括:
- 团队在用 AI 编程工具生成代码,但代码合入后经常出现“不知道哪里出了问题”。
- 自建了 RAG 问答系统,用户反馈答案不稳定,想定位是检索失败、上下文截断还是模型生成问题。
- 开发了 Agent 应用,一次任务会多次调用 LLM,中间还穿插工具调用,需要完整的调用链路。
- 做批量推理任务,比如批量摘要、批量翻译、批量图片描述,想统计成本、延迟和失败率。
- 正在微调或自定义提示词,需要对比不同版本的效果。
不适合的场景也很清晰:
- 只有一个临时脚本,跑完就删,不需要长期维护。
- 没有任何用户和业务指标接入,只是为了“看起来工程化”而堆监控。
- 团队连基础日志都没有统一格式,直接上复杂 Trace 平台会适得其反。
使用边界方面,合规和安全必须重点说:
- 原始 Prompt 和模型输出往往包含用户数据,写入日志和 Trace 时必须脱敏。
- 不要在生产日志里记录 API Key、Token、会话 Cookie、身份证号、手机号等敏感信息。
- 如果接入第三方 LLM API,要注意数据出境和隐私合规要求,最好在自建服务内部做脱敏和审计。
- 涉及人脸、声音、版权素材的生成任务,必须确认授权范围。
可观测性最大的副作用是“记录得太全”。记录全意味着可以定位问题,也意味着一旦存储被攻破,风险更大。所以,能采样就采样,能脱敏就脱敏,不要天真地把所有原始数据都堆进一个看板。
3. 环境准备与前置条件
在开始搭建之前,先明确一个原则:不要一上来就搭一套完整分布式追踪系统。多数团队连“记录一次 LLM 调用的结构化日志”都没做好,直接上复杂平台很容易被成本和无尽维护拖垮。
建议的最小前置条件如下:
- 操作系统:Linux、macOS、Windows 均可,Windows 建议用 WSL2。
- 语言环境:Python 3.9+ 或 Node.js 18+,取决于你用什么 SDK 接入。
- 容器环境:如果使用 Docker Compose 部署可观测平台,需要提前安装 Docker。
- 数据存储:日志和 Trace 会持续增长,提前规划磁盘空间,建议至少预留 20G 以上。
- LLM SDK:例如 OpenAI、Anthropic、LiteLLM、LangChain、LlamaIndex 等,具体取决于你的应用。
命令检查示例:
# 检查 Docker 是否可用 docker --version # 检查 Python 版本 python --version # 检查磁盘空间 df -h如果本地已有模型推理服务,比如 vLLM、Ollama、LLaMA.cpp,还需要观察 GPU 状态:
nvidia-smi --query-gpu=name,memory.total,memory.used,utilization.gpu --format=csv可观测平台的部署不需要 GPU,但需要足够的 CPU 和内存来处理日志解析、指标抓取和 Trace 存储。如果数据量大,建议单独分配一台 4 核 8G 以上的机器。
另外一个容易忽略的问题是端口占用。常用端口包括 4317、4318(OTLP)、9090(Prometheus)、3000(Grafana)、8000 或 8080(业务服务)。启动前先检查:
# 检查端口是否被占用 lsof -i :4317 lsof -i :9090如果端口被占用,换端口即可,不要硬启动。
4. 最小可观测平台怎么部署
这里给出一套通用部署方案:OpenTelemetry Collector 负责接收 Trace 和指标,Prometheus 负责存储指标,Grafana 负责展示。这个组合是目前最通用的自建可观测基础栈。
不要把它当成唯一标准,而是当成一个起点。你也可以换成托管服务,或者直接用 Langfuse、LangSmith、Phoenix 这类 LLM 可观测平台,但部署思路是一致的。
先创建一个docker-compose.yml:
services: otel-collector: image: otel/opentelemetry-collector-contrib:latest container_name: otel-collector ports: - "4317:4317" - "4318:4318" volumes: - ./otel-config.yml:/etc/otelcol-contrib/config.yaml restart: unless-stopped prometheus: image: prom/prometheus:latest container_name: prometheus ports: - "9090:9090" restart: unless-stopped grafana: image: grafana/grafana:latest container_name: grafana ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin restart: unless-stopped注意:实际使用时不要用latest标签,应该固定到你验证过的具体版本。这里只是为了演示结构。
OpenTelemetry Collector 需要一个最小配置otel-config.yml:
receivers: otlp: protocols: grpc: http: processors: batch: exporters: debug: verbosity: detailed service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [debug] metrics: receivers: [otlp] processors: [batch] exporters: [debug]这个配置的作用是把应用上报的 Trace 和指标打印到 Collector 日志中。先跑通链路,再接入真正的存储和看板。
启动命令:
docker compose up -d启动后观察:
docker logs -f otel-collector如果你看到类似收到 Trace 的日志,说明上报链路已经通了。
这个阶段最容易出问题的地方有三个:
- 镜像拉取慢,建议配置容器镜像加速。
- 端口冲突,尤其是 4317 和 4318。
- Collector 配置格式错误,YAML 缩进写错会导致启动失败。
5. 四个核心维度:日志、指标、追踪、评测
可观测性在 AI 工程里的落地可以拆成四个维度,每一个都有明确作用。
5.1 日志
日志是最容易启动的维度。统一格式是最关键的一步,千万不要再用纯文本随意打印:
user ask something, success这种日志没法解析,也没法检索。建议使用 JSON 结构化日志,至少包含时间、Trace ID、模型名、Prompt、输出、状态、耗时。这里有一个最小结构示例:
{ "ts": "2025-05-01T12:00:00.000Z", "trace_id": "req_123456", "model": "gpt-4o-mini", "prompt": "请把下面这段话翻译成中文", "output": "请把下面这段话翻译成中文", "status": "ok", "latency_ms": 320, "prompt_tokens": 12, "completion_tokens": 15 }注意:这个示例只是演示字段,不一定需要直接暴露原始 Prompt。生产环境建议只记录 Prompt 的哈希值或者脱敏后的摘要。
5.2 指标
指标用来回答“整体健康度怎么样”。常见指标包括:
- 请求总数,按模型、按用户、按接口维度拆分。
- 平均耗时和 P95 耗时。
- 错误率。
- Token 消耗总量和预估成本。
- 缓存命中率。
- 批量任务成功率。
这些指标可以上报到 Prometheus,也可以直接输出成结构化日志后聚合。初期不需要做太细,先算清三个数:请求量、错误率、Token 成本。
5.3 链路追踪
链路追踪是可观测性的核心。一次用户请求可能经过多个环节:
用户输入 -> 检索召回 -> 拼装 Prompt -> 调用模型 -> 工具调用 -> 生成输出如果你只记录应用日志,很难把一个请求的完整过程串起来。Trace 就是解决这个问题的:每次请求生成一个 Trace ID,每个环节是一个 Span,Span 之间通过 Parent ID 关联。这样排查问题时,可以直接还原一次完整调用。
最小实现思路:
import uuid import time import json def start_trace(): trace_id = uuid.uuid4().hex root_span_id = uuid.uuid4().hex[:8] return { "trace_id": trace_id, "root_span_id": root_span_id, "start_time": time.time() }实际接入时,建议使用 OpenTelemetry SDK,而不是自己造一套 Span 管理逻辑。自己维护 Trace 结构很容易出错,长期成本高。
5.4 效果评测
日志、指标、追踪解决的是“系统有没有问题”,评测解决的是“AI 回答得好不好”。后者是 AI 工程区别于传统后端工程的关键。
评测可以从两个层面做:
- 离线评测:准备一组测试集,跑不同模型版本或提示词版本,比较输出质量。
- 在线评测:在真实用户请求上采集点赞、点踩、复制、重试等信号。
在线评测的信号可以当成反馈数据回填到 Trace 中,形成闭环:
{ "trace_id": "req_123456", "feedback": { "thumbs_up": false, "user_comment": "回答没有命中问题" } }有了反馈,下一步才能优化提示词、调整模型、改检索策略。没有反馈,所有优化都是自说自话。
6. 功能测试与效果验证
可观测性不是部署完就结束了,你需要验证它真的能帮你发现问题。下面是一组通用验证用例,可以直接拿来测。
| 测试项 | 输入示例 | 观测点 | 判断标准 |
|---|---|---|---|
| 基础生成 | 一个简单问答 Prompt | 是否生成 Trace,日志是否记录模型名和耗时 | 有 Trace 和日志 |
| 回归测试 | 同一组测试集跑两个模型 | 输出内容差异、Token 数差异 | 能对比出质量变化 |
| 批量任务 | 10 条文本摘要任务 | 每条任务是否有 Trace ID,成功率和耗时 | 所有任务都有记录 |
| 长上下文 | 输入超过模型上下文一半长度的文本 | 是否截断,是否超时,Token 数是否符合预期 | 能定位到截断位置 |
| Agent 工具调用 | 让 Agent 查询天气并执行计算 | 工具调用是否生成独立 Span | 能看到工具调用的入参和出参 |
| 成本追踪 | 连续调用 100 次 | 按模型聚合 Token 成本 | 能算出每次请求平均成本 |
测试时注意一个原则:不要只看“成功”或“失败”两个状态。AI 应用的失败有很多种:
- 请求成功,但输出是幻觉。
- 请求成功,但检索为空,模型只能硬编。
- 请求成功,但耗时太长,用户体验差。
- 请求成功,但 Token 消耗异常,成本失控。
所以,可观测性要同时记录状态、耗时、Token 和输出内容。只有这样,后续优化才有依据。
7. 接口 API 与批量任务接入
如果你开发的是一个 API 服务,建议在接口层把 Trace ID 带进上下文,并且响应里返回 Trace ID。这样用户报问题的时候,你可以直接按 Trace ID 查链路。
一个通用批量调用示例:
import json import time import uuid def run_batch_with_observability(items, call_llm): results = [] for item in items: trace_id = uuid.uuid4().hex start = time.perf_counter() try: output = call_llm(item["prompt"]) status = "ok" except Exception as exc: output = str(exc) status = "error" latency_ms = round((time.perf_counter() - start) * 1000, 2) record = { "trace_id": trace_id, "task_id": item.get("task_id"), "prompt": item["prompt"], "output": output, "status": status, "latency_ms": latency_ms, "ts": time.time() } results.append(record) print(json.dumps(record, ensure_ascii=False)) return results这个示例的核心思路是:批量任务里的每一条数据都带上独立 Trace ID,最后汇总成 JSONL 文件。如果你使用的可观测平台有自己的 SDK,就用 SDK 上报,而不是只打印日志。
假设你要把 Trace 上报到自建 Collector 的 HTTP 端点,上报逻辑可以写成这样:
import requests def report_trace(trace_payload, endpoint="http://127.0.0.1:4318/v1/traces"): response = requests.post(endpoint, json=trace_payload, timeout=5) response.raise_for_status()注意:这个代码里的 endpoint 是示例,实际接入时要以 OpenTelemetry Collector 实际暴露的地址和协议为准。如果先用自定义 HTTP 上报,建议在内部定义一个统一结构,避免不同模块上报格式不一致。
批量任务最容易被忽略的是“失败重试”。建议在批量脚本里加两个字段:
attempt:当前第几次尝试。error_type:错误类型,比如超时、限流、上下文过长、模型不可用。
这样后续统计失败原因时,可以直接按error_type分组,而不是只看到一堆 error。
8. 资源占用与性能观察
可观测性本身会增加少量资源开销,但只要控制好采样率,影响通常可以忽略。
重点观察这几个指标:
| 观察对象 | 命令或工具 | 关注点 |
|---|---|---|
| 容器资源 | docker stats | CPU、内存、网络流量 |
| GPU 显存 | nvidia-smi | 显存占用和 GPU 利用率 |
| Collector 日志 | docker logs -f otel-collector | 是否有 Trace 上报错误 |
| 日志增长 | du -sh /var/log/your_app | 磁盘占用是否过快 |
| Trace 存储 | 数据库表大小 | 是否需要采样和清理 |
资源占用和几个因素直接相关:
- 上报频率。
- 是否记录完整输入输出。
- 是否保留原始 Prompt。
- 采样率设置。
如果觉得开销大,可以按下面顺序降低:
- 降低采样率,比如只采样 10% 的请求。
- 去掉低价值日志的完整输出,只保留截断版本。
- 批量上报 Trace,而不是每一条独立发一次请求。
- 对静态资源和健康检查请求不打 Trace。
本机部署时的实际操作建议:
# 观察容器资源占用 docker stats # 观察 GPU 显存 nvidia-smi -l 2在批量任务场景下,重点观察批量脚本进程的内存。如果一个批次加载了过多长文本,进程内存会快速增长,卡住时通常不是 CPU 满,而是内存不足。
9. 常见问题与排查方法
下面是一张通用问题排查表,适合应用通过可观测性链路定位问题时参考。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 日志里没有 Trace ID | 请求入口没有初始化 Trace | 检查入口中间件或装饰器 | 在入口统一生成 Trace ID |
| Trace 有 Span 但无法串联 | Parent Span ID 未传递 | 检查异步任务是否传递上下文 | 异步任务显式传递 Trace 上下文 |
| 模型调用超时 | 网络慢、模型排队、上下文过长 | 看耗时分布和 Token 数 | 设置超时重试,压缩上下文 |
| 输出质量突然下降 | Prompt 版本变更或模型版本切换 | 对比不同 Trace 的 model 和 prompt | 固定模型版本,配置回滚机制 |
| Token 成本异常 | 上下文重复拼接或 Agent 死循环 | 按 trace_id 查 Token 消耗 | 加缓存,限制最大轮数 |
| 批量任务卡住 | 某条任务触发重试死循环 | 看 attempt 和 error_type | 设置最大重试次数和熔断 |
| 日志磁盘写满 | 记录了完整输入输出 | 查看日志文件大小 | 截断输出,提升采样率 |
| 隐私数据泄漏 | 原始 Prompt 直接入库 | 检查日志字段 | 脱敏后再写入 |
| API 上报失败 | Collector 地址错误或端口不通 | 检查容器日志 | 确认端口和地址 |
所有这些排查的前提都是:你有日志和 Trace。如果什么都没有,遇到问题就只能重新跑一遍,或者靠用户复现,这是 Vibe Coding 项目最容易失控的地方。
10. 最佳实践:从 Vibe Coding 走向 AI Engineering 的落地清单
最后给一份可操作的落地清单,不需要一次全部做完,按顺序逐步推进。
第一步,统一结构日志。所有 LLM 调用都输出 JSON 日志,包含 Trace ID、模型、状态、耗时、Token 数。这一步可以直接替换掉项目里随意的 print 日志,收益最明显。
第二步,给关键业务请求打 Trace。从一个入口请求开始,记录从用户输入到模型输出的完整链路。异步任务要特别小心上下文传递,最好在任务开始时重新创建 Trace。
第三步,建立在线反馈通道。在生成结果的 UI 上增加有用、无用的反馈按钮,反馈数据关联到 Trace。这是后续优化提示词和模型的重要依据。
第四步,做批量任务的成本和质量统计。批量任务必须记录成功率、平均耗时、Token 消耗和失败原因,否则跑一次长任务你都说不清楚它到底做完了没有。
第五步,设置采样和脱敏策略。默认不记录敏感字段,线上日志默认脱敏。所有原始 Prompt 和输出在展示前都过一遍脱敏规则。
第六步,把可观测性和代码审查、测试流程结合起来。AI 生成的代码合入前,除了看功能,还要看是否新增了日志、是否传播了 Trace ID、是否引入了未被观测的第三方调用。
最后一件事:不要迷信“上了一个平台就等于工程化”。可观测性平台的搭建只占三成工作量,剩下七成在数据规范、SLA 定义、反馈闭环和日常排障习惯。一个团队只要能在一小时内定位一次坏请求的完整链路,就已经比大多数只靠 Vibe Coding 写代码的团队强很多了。
如果你正在用 Cursor、Trae、Copilot 这类工具大规模生成代码,建议先做一件事:跑一个批量任务,把每个请求的耗时、状态、Token 数、错误类型都记录下来。看到数据的瞬间,你会重新理解什么叫“可观测性把 Vibe Coding 变成 AI Engineering”。
建议收藏备用,下次用 AI 编程到一半却没有日志能查的时候,回来按这份清单补数据链路。