Vibe Coding 的工程化底线:可观测性如何升级为 AI Engineering
Vibe Coding 刚火起来时很多人的第一反应是“以后写代码只要会说话就行”。半年过去行业情绪明显分化有人用一个下午让 AI 写出了完整原型有人把 AI 生成的业务代码直接上线结果出事故时连问题都定位不到。同样是让 AI 写代码为什么体验差距如此之大我的判断是Vibe Coding 本身没有问题问题在于很多人把它当成“输出代码的工具”而不是“需要工程化保障的生产方式”。真正决定 AI 生成代码能不能上线、能不能长期维护的从来不是提示词写得多好而是——当这段代码运行起来之后你有没有能力看清它做了什么、为什么出错、下一次怎么改进。可观测性Observability就是那个分水岭。它能把 Vibe Coding 从“代码生成游戏”升级成真正的 AI Engineering可验证、可追踪、可回归、可持续。这篇文章会讲清楚三件事Vibe Coding 为什么需要工程化可观测性在 AI 应用里到底观测什么以及怎样用最小成本给 AI 生成的应用接入日志、指标、Trace 和评估体系。1. Vibe Coding 为什么会流行又为什么会被质疑1.1 什么是 Vibe CodingVibe Coding 这个词由 OpenAI 联合创始人 Andrej Karpathy 在 2025 年初提出用来描述一种新的编程方式开发者用自然语言描述需求AI 负责生成代码人只做方向上的判断而不是逐行检查实现细节。很多人把 Vibe Coding 理解成“随便敲几句提示词功能就写完了”。实际上它的本质是压缩了“想法 → 代码 → 验证”之间的反馈循环。过去一个 Java 开发者写 Spring Boot 的 CRUD要手动创建 Controller、Service、Mapper、实体类、分页封装、异常处理现在把需求告诉 Cursor、Trae 这类 AI IDE骨架和大部分实现可以自动生成开发者只需要做取舍和微调。这也是 Vibe Coding 在 Java 开发者中间快速流行的原因。Java 项目普遍存在大量样板代码AI 生成这些结构化的代码准确率相当高。相比从零手写用自然语言描述“给我加一个带缓存和限流的分页查询接口”通常十几秒就能得到可用代码。1.2 质疑声集中在哪Vibe Coding 流行得快质疑来得也快。归纳起来问题集中在四点。第一生成正确性得不到保证。AI 会“自信地编造”使用不存在的 API、引用过时的依赖、写出错误的并发假设。前端效果看着没问题后端在高并发下一测就崩溃。第二评审变成了走过场。AI 一次性生成几十个文件人不可能逐行看代码评审从“审查实现”退化成了“看个大概”。这种感觉上的“差不多”就是线上事故的来源。第三问题定位困难。AI 生成的代码很少自带规范日志和上下文信息。一旦线上接口超时日志里只有一行 NPE你甚至不知道这段代码是哪个版本生成的改过什么。第四回退和迭代困难。Vibe Coding 的天然习惯是“不行就重新生成”可没有版本化、没有开关控制你无法判断一个回归问题到底是人工改动引入的还是某次 AI 生成重写了逻辑。这些质疑的本质是“生成能力跑在了验证能力前面”。代码产出速度提升了几倍验证和运维能力还停在人工时代问题自然集中爆发。2. 从 Vibe Coding 到 AI Engineering缺少的不是规范而是观测2.1 AI Engineering 到底是什么AI Engineering 不是“把提示词调好”而是用工程方法管理 AI 应用的完整生命周期模型选型、数据准备、Prompt 设计、生成策略、评测、可观测、安全防护、成本控制。一句话概括传统开发关注代码对错AI Engineering 关注系统在真实环境里能不能持续正确。Vibe Coding 解决的是“代码怎么来”AI Engineering 解决的是“代码来了之后怎么保证它可靠”。这里做一个对比维度Vibe Coding传统编码AI Engineering代码产出方式自然语言 → AI 生成人逐行编写AI 生成 工程护栏验证方式看效果、凭感觉单元测试 人工评审评测集 可观测数据驱动运行可理解性弱接近黑盒中依赖个人经验强全链路 Trace迭代方式不满意就重写重构 回归测试评测失败 → 定向修改 → 回归风险控制弱中等可观测 可回滚 成本监控从这个表格能看出AI Engineering 和 Vibe Coding 最大的区别是验证与观测前置。传统编码也会出 bug但代码是人写的出了问题顺着调用栈还能推AI 生成的代码连作者自己都不知道模型基于什么训练数据、什么上下文写出来的如果不观测运行状态排查起来就是大海捞针。2.2 可观测性是那块关键拼图可观测性回答三个问题现在发生了什么、为什么发生、下一步怎么改。对 AI 生成的应用来说它的意义更特殊。没有可观测性你无法区分一次线上失败是模型幻觉、Prompt 设计不当、生成代码的 bug还是下游依赖变更。甚至连“要不要重新生成这段代码”这种最基本的决策都做不了。所以我说可观测性是把 Vibe Coding “变成” AI Engineering 的那个转折点。它提供的是数据基础有了运行数据才能做评测有了评测才能让 AI 针对失败做定向修改有了定向修改和回归才能谈可持续迭代。没有数据基础的 AI 代码修改本质上都是盲改。3. LLM 应用的可观测性看透每次模型决策3.1 到底观测什么LLM 应用的可观测性和传统后端应用有明显区别。传统后端观测的是请求、数据库、中间件LLM 应用还要观测模型调用这个特殊环节。具体包含这些维度输入数据Prompt 内容、上下文长度、System 指令版本模型参数模型版本、temperature、max_tokens、top_p输出数据回复文本、是否触发安全策略、是否出现截断性能指标总延迟、首 token 延迟成本指标输入 token 数、输出 token 数、单次调用成本质量信号用户反馈、下游任务成功率、评测分数LLM 调用有一个特点它不像普通 API 调用那样可以稳定复现。同一个 Prompt模型每次输出可能不同同一个问题换了模型版本结果可能完全不同。所以观测不仅要记录调用延迟和状态码还要把关键输入输出记录下来否则你永远不知道一次坏结果是在哪个环节产生的。3.2 LLM Trace 与传统 Trace 的差异传统 Trace 记录的是一次请求经过哪些服务、每个节点耗时多少。LLM Trace 在继承这些能力的基础上额外记录语义信息Prompt 是什么、模型返回了什么、用了多少 token、花费多少成本。这意味着 LLM Trace 的单个 Span 体积更大。一个 Prompt 可能包含几千 token不能全量存需要做截断或脱敏。OpenTelemetry 社区在推进 GenAI 语义约定推荐使用 gen_ai.operation.name、gen_ai.prompt、gen_ai.usage.completion_tokens 这类属性描述模型调用主流 LLM 观测平台都在逐步对齐这套标准。3.3 常见的 LLM 观测工具工具类型特点适用场景Langfuse开源 LLM 观测平台支持 Trace、评测、成本统计可自托管中小团队快速搭建 LLM 观测LangSmithLangChain 生态平台与 LangChain 深度集成评测能力强重度使用 LangChain 的项目Arize Phoenix开源 LLM Trace 与评测本地部署轻量适合 Notbook 调试研究原型和模型对比OpenTelemetry标准协议统一采集、统一上报后端自由选择需要和现有 APM 基础设施打通如果你在 Java 技术栈中使用 Spring AI还有个特殊优势Spring AI 进入 1.0 稳定版后已经将模型调用纳入 Micrometer Observation 体系。这意味着不需要单独引入一套 LLM 观测 SDK模型调用可以和普通 HTTP 调用一样走你现有的监控链路。4. AI 生成代码的运行时观测不能只盯模型很多人在讲 AI 应用可观测性时默认只讲 LLM 调用 Trace。实际上一套完整的 Vibe Coding 产物里模型调用可能只占很小一部分剩下的是大量普通代码Controller、Service、数据库访问、消息队列消费、缓存更新、文件处理。这些代码同样需要被观测。4.1 AI 生成代码最容易翻车的四个地方第一异常被吞掉。AI 生成的代码为了“看起来简洁”经常把异常 catch 住后只打印一行日志甚至直接忽略。这在开发环境没问题一上生产错误被静默吞掉业务数据错乱都发现不了。第二IO 操作没有超时和重试。AI 喜欢写“直来直去”的代码不会主动加超时、熔断、重试。一旦下游接口变慢整个链路跟着卡死。第三边界条件想当然。AI 生成的分页逻辑、空列表处理、并发计数器经常只覆盖“正常路径”。空值、边界值、并发写往往是线上事故的重灾区。第四没有上下文信息。AI 生成的代码默认不会打结构化日志不会传递 trace_id。出了问题只能看时间戳猜。4.2 结构化日志与全链路追踪是底线对 AI 生成的代码我的建议是定一条硬规则凡是 AI 生成的代码必须补上最小可观测性才能合入主干。最小可观测性包含三样东西一是 trace_id从请求入口开始贯穿到所有下游调用二是结构化日志关键分支必须记录入参、出参、耗时和错误三是核心指标接口请求量、错误率、耗时分布必须能通过监控平台查到。这套要求不复杂但它能把“不知道 AI 代码在干什么”变成“AI 代码的每一步运行都有据可查”。这也是可观测性在 AI 工程里最朴素也最核心的价值。5. 实操给 AI 生成的应用接入最小可观测性下面用一个最小示例演示从零给 AI 应用接入可观测性。示例分为三部分本地 Trace 后端、Python 模型的 LLM 调用埋点、Java 服务的指标与结构化日志。5.1 先启动一个本地 Trace 后端为了验证 Trace 是否上报先用 Docker 启动一个 Jaeger。新版 Jaeger all-in-one 镜像已经支持 OTLP 协议上报镜像 tag 请以 Docker Hub 实际版本为准。docker run -d --name jaeger \ -p 16686:16686 \ -p 4317:4317 \ -p 4318:4318 \ jaegertracing/all-in-one端口说明16686 是 Jaeger 的 Web UI4317 是 OTLP gRPC 接收端口4318 是 OTLP HTTP 接收端口。如果后续需要连接其他可观测性后端只需要把 OTLP endpoint 换掉即可。5.2 Python 示例给 LLM 调用增加 Trace假设你有一个 Python 服务内部调用模型生成回复。下面用 OpenTelemetry 给模型调用加 Trace记录 Prompt、回复、耗时和 token 数。依赖版本以 PyPI 最新版本为准。# observability/llm_trace_demo.py 最小示例用 OpenTelemetry 给 LLM 调用加 Trace。 依赖安装 pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-grpc import time from opentelemetry import trace from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor def setup_tracer(endpoint: str http://localhost:4317): provider TracerProvider() provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpointendpoint))) trace.set_tracer_provider(provider) return trace.get_tracer(vibe-coding-demo) def call_model(prompt: str) - str: # 伪代码替换为实际模型 SDK例如 OpenAI 客户端、vLLM、企业模型网关 time.sleep(0.5) return f模拟模型回复{prompt[:20]} def ai_chat(tracer, user_input: str) - str: with tracer.start_as_current_span(llm.chat) as span: start time.time() # 入参观测 span.set_attribute(gen_ai.operation.name, chat) span.set_attribute(gen_ai.request.model, demo-model) span.set_attribute(gen_ai.prompt, user_input) answer call_model(user_input) # 出参观测 span.set_attribute(gen_ai.response.text, answer) span.set_attribute(gen_ai.usage.completion_tokens, len(answer)) span.set_attribute(llm.latency_ms, int((time.time() - start) * 1000)) return answer if __name__ __main__: tracer setup_tracer() result ai_chat(tracer, 用 Java 实现一个线程安全的 LRU 缓存) print(result)这里使用了 OpenTelemetry 的 TracerProvider 和 BatchSpanProcessor。关键点有两个一是用 tracer.start_as_current_span 把模型调用包起来这样模型调用会成为一个独立 Span二是把 Prompt、模型名、回复、token 数等重要信息写进 Span 属性后续在 Jaeger 或 Langfuse 里可以直接查看。需要注意生产环境不要完整记录 Prompt 和回复涉及敏感信息时必须脱敏或截断。5.3 Java 示例Spring Boot 服务接入指标与结构化日志Vibe Coding 生成的 Java 服务最常见的接入方式是用 Spring Boot Actuator 暴露指标再配合结构化日志。先在 pom.xml 里加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency版本号不需要手写由 Spring Boot 的 BOM 统一管理。然后在配置文件里暴露 Prometheus 端点# 文件路径src/main/resources/application.properties management.endpoints.web.exposure.includehealth,prometheus,metrics management.metrics.export.prometheus.enabledtrue接下来写一个 AI 聊天接口在接口中记录成功/失败计数和耗时同时输出结构化错误日志// 文件路径src/main/java/com/example/aiboot/controller/AiChatController.java package com.example.aiboot.controller; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; RestController RequestMapping(/api/ai) public class AiChatController { private static final Logger log LoggerFactory.getLogger(AiChatController.class); private final MeterRegistry meterRegistry; public AiChatController(MeterRegistry meterRegistry) { this.meterRegistry meterRegistry; } PostMapping(/chat) public ChatResult chat(RequestBody ChatRequest request) { Timer.Sample sample Timer.start(meterRegistry); long start System.currentTimeMillis(); try { // 替换为真实的模型调用逻辑例如 Spring AI ChatClient String answer callLlm(request.question()); meterRegistry.counter(ai.chat.total, result, success).increment(); return new ChatResult(answer); } catch (Exception e) { meterRegistry.counter(ai.chat.total, result, error).increment(); log.error(ai_chat_failed question{} error{} elapsed_ms{}, request.question(), e.getMessage(), System.currentTimeMillis() - start); throw e; } finally { sample.stop(meterRegistry.timer(ai.chat.latency)); } } // 伪代码替换为 Spring AI ChatClient 或自研模型客户端 private String callLlm(String question) { return 模拟模型回复 question; } record ChatRequest(String question) {} record ChatResult(String answer) {} }这段代码的核心思想是成功失败分开计数耗时统一进 Timer异常发生时写入结构化错误日志。日志里不要直接拼接字符串而是用 keyvalue 的形式方便后续接入日志采集系统。5.4 建立最简单的评估脚本可观测性不只是记录运行状态还要把线上问题转化为评测用例让 AI 下次不再犯同样的错。下面是一个最小的评测脚本示例# eval/eval_demo.py EVAL_CASES [ {input: 用 Java 实现一个线程安全的 LRU 缓存, expect_keywords: [ReentrantLock, LinkedHashMap]}, {input: 写一个 SQL 查询找出每个部门工资最高的员工, expect_keywords: [ROW_NUMBER, PARTITION BY]}, ] def generate_answer(user_input: str) - str: # 伪代码替换为真实模型调用 return 这段是模型生成的回复需要被 judge 校验 def judge(answer: str, keywords) - bool: return all(k in answer for k in keywords) def run_eval() - dict: results [] for case in EVAL_CASES: answer generate_answer(case[input]) passed judge(answer, case[expect_keywords]) results.append({input: case[input], passed: passed}) return {total: len(results), passed: sum(r[passed] for r in results)} if __name__ __main__: print(run_eval())注意这里的“关键词命中”只是最基础的评估方式。真实项目里建议用更强的判断方法比如让另一个更强的模型按评分标准打分或调用单测/SQL 执行结果来验证。评估的价值在于线上 Trace 里发现的失败样例可以不断补充进 EVAL_CASES形成“线上问题 → 评测失败 → 修改 Prompt 或代码 → 回归通过”的反馈回路。6. 运行结果与效果验证6.1 验证 Python Trace运行 Python 示例python observability/llm_trace_demo.py脚本执行完后打开 http://localhost:16686在 Service 下拉框里选择 vibe-coding-demo应该能看到一条名为 llm.chat 的 Span。点击进去能看到我们写入的 gen_ai.prompt、gen_ai.response.text、gen_ai.usage.completion_tokens 等属性。如果看不到第一步检查 Jaeger 容器是否正常运行第二步检查 4317 端口是否被占用第三步检查 Python 程序是否在退出前有足够时间完成导出发送。BatchSpanProcessor 是异步批量上报脚本执行完立刻退出可能导致 Span 来不及发送调试时可以在结尾加个短暂 sleep。6.2 验证 Java 指标启动 Spring Boot 应用mvn spring-boot:run然后调用接口curl -X POST http://localhost:8080/api/ai/chat \ -H Content-Type: application/json \ -d {question:用 Java 实现线程安全的 LRU 缓存}预期返回{answer:模拟模型回复用 Java 实现线程安全的 LRU 缓存}再访问 Prometheus 指标端点curl http://localhost:8080/actuator/prometheus | grep ai_chat能看到类似 ai_chat_total 和 ai_chat_latency_seconds 的指标数据。判断成功的标准是指标中出现 ai_chat_total 且 result 标签同时有 success 和 error 两个维度说明计数埋点生效。6.3 判断接入成功的通用标准一个 AI 应用是否真正具备可观测性我建议按三个标准检查第一从一条请求进来开始能否得到一个贯穿全链路的 trace_id第二模型调用是否有独立的 Span并且能查到 Prompt、回复、token 和延迟第三线上出现的任何异常能否在日志里通过 trace_id 反查到完整的调用链。三个标准都满足再谈更大规模的可观测性建设。7. 常见问题与排查方法问题现象可能原因排查方式解决方案Trace 在 Jaeger 里看不到OTLP endpoint 配置错误BachSpanProcessor 没来得及导出检查容器端口和初始化日志看 Jaeger 服务列表修正 endpoint调试时降低批次间隔或在退出前等待LLM 调用没有生成独立 Span埋点位置没有包住模型 SDK异步线程上下文丢失检查代码中 span 作用域查看异步线程是否继承上下文用装饰器/拦截器包住模型调用在异步线程里显式传递 context指标出现重复计数Counter 写在循环或重试逻辑里检查计数代码是否在循环/重试内部把计数移到方法出口或用装饰器统一处理日志仍是单行字符串不是 JSON日志配置没有使用结构化 encoder查看日志输出格式添加 LogstashEncoder 或配置 logback 的 JSON patternAI 生成的代码测试全通过线上仍然出问题评测集覆盖不足缺少线上真实分布数据查看 Trace 中实际输入分布和失败分布把线上异常样本加入评测集扩展边界条件用例Prompt 里有敏感信息被记录没有做脱敏和截断检查 Span 属性内容接入前对 Prompt/Reply 做脱敏、截断或哈希处理这些坑在项目中几乎都会遇到。做可观测性不是一个“装上就能用”的动作它需要持续调整采样策略、脱敏规则和指标维度逐渐贴近真实业务。8. 最佳实践与工程建议8.1 从第一天接入不要等“稳定之后”很多团队的想法是“先用 Vibe Coding 快速做出功能稳定了再补监控”。这个顺序是反的。没有观测数据你根本不知道功能何时算稳定。AI 生成代码的迭代周期短问题复现成本高越晚补监控排错成本越高。建议新功能上线时可观测性和功能代码同步交付。8.2 先定三个必须回答的问题接入可观测性之前先明确三个问题这条请求从哪来、模型做了什么决定、花了多少钱。第一个问题靠 trace_id 贯穿第二个问题靠 LLM Span 记录输入输出第三个问题靠 token 和延迟指标。三个问题都回答了最小可观测栈就成立了。8.3 全链路 trace_id 贯穿AI 应用最容易出现的问题是前端 HTTP 请求、后端业务逻辑、模型调用三个环节各自有独立的追踪无法串联。建议在网关层生成 trace_id通过 HTTP Header 向后传递模型调用层再用 OpenTelemetry 的 Context Propagation 自动关联。这样用户反馈一个问题你从入口就能一路查到模型输出。8.4 敏感信息脱敏LLM 应用的日志比其他应用更危险因为 Prompt 和回复里经常包含用户隐私或业务敏感数据。建议默认策略是不完整记录 Prompt只记录截断后的前 N 个字符对身份证、手机号、地址等字段做正则脱敏涉及高敏感数据时只记录哈希值。8.5 成本和限流监控模型调用是按 token 计费的Vibe Coding 生成的代码如果不加控制很容易写出“每个请求都传超长上下文”的逻辑。建议在模型调用 Span 里记录 prompt_tokens 和 completion_tokens设置单请求成本上限告警并配合令牌桶限流防止异常代码把成本打爆。8.6 评测集要动态更新评测集不是一次建好就完事的。每次线上 Trace 里出现失败案例都应该评估是否值得加入评测集。加入之后让 AI 参考失败样本重新生成代码再跑一轮评估通过才能上线。这样线上的真实问题才能不断反哺生成质量。8.7 AI 生成代码的评审保护不要直接信任 AI 生成的代码。合入主干前至少要检查三处所有 catch 块是否正确记录日志所有 IO 操作是否有超时和重试所有关键接口是否补充了指标埋点。可以用一个可观测性检查清单作为评审规范AI 生成的代码不满足清单就不允许合入。9. 总结Vibe Coding 的终点是 AI Engineering回到开头的问题同样用 AI 写代码为什么有的团队越写越顺有的团队越写越慌答案不在模型能力强弱也不在提示词技巧而在你有没有给 AI 生成的代码配上一套可观测性基础设施。Vibe Coding 真正改变的是代码的产出方式从“人写”变成“人生成”。但生成只是起点运行、验证、定位、迭代才是工程。可观测性把这几件事串在一起Trace 告诉你发生了什么日志告诉你为什么发生指标告诉你影响范围评估集告诉你下次怎么改。四者组合起来AI 才能从“偶尔好用”变成“持续可靠”。如果你正准备在 Java 项目里尝试 Vibe Coding建议从最小可观测栈开始一个 OpenTelemetry SDK、一个本地 Trace 后端、一份结构化日志规范再配一个最简单的评估脚本。先把一条链路的观测跑通再逐步扩展。可观测性不是一步到位的重装备它更像一个随项目成长的必要习惯——早一天养成晚一天踩坑。