芥末
发布于 2025-10-15 / 0 阅读
0
0

Vibe Coding 的可靠性边界:从 LLM 概率本质到 AI 协同工程

Vibe Coding 最容易被误解成一句话:只要会描述需求,就能让 AI 自动写出完整软件。

这个说法很诱人,但也很危险。它把 AI 编程描述成一种“许愿机”:输入几句话,等待几分钟,拿到一个可上线的软件系统。真实的软件开发不是这样。一个稍微复杂的功能,就涉及需求边界、数据模型、接口契约、异常处理、并发控制、测试覆盖、部署策略、线上观测和后续维护。任何一个环节含糊,代码都可能在后面爆炸。

AI 确实改变了开发方式,但它没有取消软件工程。它把工程师的工作重心从“逐行写代码”推向了“定义目标、拆解任务、约束实现、验收结果、沉淀流程”。

更准确地说,Vibe Coding 不是让完全不懂开发的人变成工程师,而是让工程师逐渐从执行者变成一个更强的“甲方”:

  • 需求要能说清楚;
  • 技术方案要能判断;
  • 任务要能拆小;
  • 代码要能验收;
  • 风险要能兜住;
  • 可重复的流程要能固化成工具。

AI 可以承担大量脑力和体力劳动,但责任边界不能交给 AI。理解这一点,需要从 LLM(Large Language Model,大语言模型)的本质开始。

LLM 的本质:它在预测下一个 Token

LLM 经常表现得像一个无所不知的专家。它能写代码、解释论文、生成 SQL、分析日志,还能用非常确定的语气回答各种问题。问题在于,语气确定不等于结果正确。

从技术上看,LLM 的核心任务并不是“理解世界”,而是基于已有输入预测下一个 Token。Token 可以粗略理解为词、字、符号或词片段。一次生成过程大致是这样的:

flowchart LR
    A[用户输入 Prompt] --> B[切分为 Token 序列]
    B --> C[模型根据上下文计算下一个 Token 的概率分布]
    C --> D[采样或选择一个 Token]
    D --> E[追加到上下文]
    E --> C
    C -->|满足停止条件| F[输出完整结果]

例如输入:

def quicksort(

模型不会像人类工程师一样先在脑中设计完整算法,再把代码写出来。它会根据训练中学到的模式,计算后续 Token 的概率。它可能预测参数名、冒号、换行、递归逻辑、分区逻辑,因为这些模式在训练数据中经常和 quicksort 一起出现。

这也是为什么同一句问题,加上不同的前置信息,输出会完全不同:

你是一名资深后端工程师,请评审下面这段 Go 代码的并发安全问题。

和:

你是一名产品经理,请从用户体验角度评价下面这个功能设计。

这两段 Prompt 把模型推向了不同的概率空间。前者会让模型更关注锁、竞态、上下文取消、错误处理;后者会让模型更关注流程、转化、提示和操作成本。

所以,Prompt Engineering(提示词工程)和 Context Engineering(上下文工程)的本质不是“咒语技巧”,而是在有限上下文里提供尽可能多的有效信息,让模型的概率预测更接近目标结果。

有效上下文通常包括:

信息类型例子作用
任务目标“实现用户积分扣减接口”确定交付物
技术栈“Go + PostgreSQL + Redis”限定实现方式
项目结构“接口在 internal/api,业务逻辑在 internal/service”避免乱放代码
约束条件“积分扣减必须防止并发超扣”降低错误实现概率
输出格式“只输出迁移 SQL 和 service 层代码”减少无关内容
验收标准“并发 100 个请求时总扣减不能超过余额”让结果可检查

但这里有一个无法绕开的事实:只要是概率模型,就不可能保证每次都对。

p^n 困境:复杂任务会把成功率指数级拉低

假设 AI 完成一个小步骤的成功率是 p,一个任务需要连续完成 n 个步骤,并且任何一步错了都会影响整体结果,那么端到端成功率就是:

p^n

这个公式很简单,但它解释了很多 AI Agent(人工智能代理)在复杂任务上失败的原因。

单步成功率 p10 步任务20 步任务50 步任务
90%34.9%12.2%0.5%
95%59.9%35.8%7.7%
98%81.7%66.8%36.4%
99%90.4%81.8%60.5%

单步 95% 已经很高,但 50 步连续任务只剩 7.7% 的端到端成功率。软件开发里的真实任务往往远不止 50 步:理解需求、设计表结构、写接口、处理权限、处理异常、写测试、改配置、跑构建、修 lint、更新文档、兼容历史数据……每一步都可能出错。

很多人会把希望放在模型变强上。模型变强当然有用,但它解决不了数学结构本身。只要 p < 1,当 n 足够大,p^n 就会快速下降。

工程上更有效的办法不是只盯着提高 p,而是减少 AI 需要独立承担的 n

这会带来一个关键转变:不要把复杂任务端到端扔给 AI,而要让人、确定性程序和 AI 分工。

flowchart TB
    U[人: 明确需求、方案和验收标准] --> A[AI: 生成方案草稿或代码初稿]
    A --> T[确定性工具: 编译、测试、Lint、类型检查]
    T -->|失败| A
    T -->|通过| R[人: 关键评审和风险判断]
    R -->|需要修改| A
    R -->|通过| S[沉淀为文档、脚本、测试和规范]

这里的核心原则是:能用确定性工具解决的,不要交给 LLM 猜。

编译器、测试框架、静态扫描工具、格式化工具、CI/CD(Continuous Integration/Continuous Delivery,持续集成/持续交付)都不“聪明”,但它们有一个 LLM 没有的优点:确定性。输入相同,规则相同,结果就相同。

上下文舒适区:信息太少不行,太多也会坏

LLM 的输出质量和上下文质量高度相关,但上下文不是越多越好。有效信息长度和输出质量之间,更像一条先上升、再平稳、再下降的曲线。

有效信息长度与输出质量的关系

这条曲线可以分成三个区域:

区域上下文状态典型表现工程策略
上升区信息不足AI 在猜,输出发散补充需求、约束、项目背景
舒适区信息足够且聚焦输出稳定,成本可控保持任务边界清晰
衰退区信息过载遗忘需求、混入噪声、速度变慢压缩上下文、拆分任务、使用子 Agent

同一个模型处理不同任务时,舒适区的位置也不同。

任务复杂度对舒适区的影响

简单任务需要的上下文少,舒适区更容易到达;复杂任务需要更多背景、约束和验收标准,舒适区会右移,同时区间更窄。也就是说,复杂任务不仅需要更多信息,还更怕信息组织混乱。

信息不足:用问答式补全把需求拉进舒适区

很多失败的 AI 编程都从一个模糊请求开始:

帮我写一个在线五子棋小游戏。

这个请求缺少太多关键信息:

  • Web、桌面还是移动端?
  • 单机对战还是真人联网?
  • 是否需要账号体系?
  • 是否需要匹配大厅?
  • 是否需要聊天?
  • 前端技术栈是什么?
  • 后端技术栈是什么?
  • 数据是否需要持久化?
  • 预期并发是多少?

让 AI 在这么大的空间里自由发挥,得到的往往是 Demo,而不是可靠功能。

更好的方式是让 AI 先提问,把需求补齐:

我要做一个在线五子棋小游戏。不要直接写代码,先通过问题帮我细化需求。
每轮最多问 5 个问题,优先询问会影响架构设计的内容。
问题尽量使用单选或多选形式。

AI 可以输出:

1. 运行平台:
   A. Web 浏览器
   B. 桌面客户端
   C. iOS/Android
   D. 小程序

2. 对战模式:
   A. 本地双人
   B. 真人联网
   C. 人机对战
   D. 真人联网 + 人机兜底

3. 是否需要账号体系:
   A. 不需要,游客即可
   B. 需要手机号登录
   C. 需要微信/QQ 登录
   D. 需要自建账号系统

问答式补全的价值在于降低用户表达成本,同时快速增加有效上下文。很多 Spec-driven Development(规格驱动开发)工具和 AI 编程工具都在做类似事情:先把模糊想法变成结构化规格,再进入设计和编码。

信息过载:上下文压缩不是简单删字

当对话越来越长,模型会进入衰退区。上下文压缩可以缓解问题,但压缩不是把 8000 Token 机械缩成 1000 Token,而是提取对后续任务真正有用的信息。

例如原始对话:

用户:我需要实现购物车功能。
AI:需要确认技术栈、存储方式、商品结构……
用户:我们用 React,需要支持增删改查。
AI:可以使用 Context API 管理状态……
用户:要支持本地存储,不需要后端同步。

较好的压缩结果应该像这样:

需求:使用 React 实现购物车功能,支持商品增删改查。
状态管理:React Context API。
数据结构:{ id, name, price, quantity, image }。
持久化:使用 localStorage,本阶段不需要后端同步。
核心功能:添加商品、删除商品、修改数量、计算总价。

真正困难的是边界信息、指代关系和决策背景。

压缩难点风险
当前规模和未来规模同时出现只保留一个会导致过度设计或设计不足
“它”“这个按钮”“那个接口”等指代压缩后失去引用对象
早期约定在后续被反复使用只保留“需要认证”会丢失 JWT、刷新 Token、过期时间等细节
同一概念有多个名字“购物车”“cart”“ShoppingBasket”可能被误认为不同概念
决策原因被删掉只保留结果,后续无法解释为什么不用另一个方案

这也是很多长时间运行的 AI Agent 会逐渐变差的原因:压缩后的上下文看起来短了,但关键约束可能丢了。

任务外包:Multi-Agent 的本质是隔离上下文

Multi-Agent(多代理)不是魔法。它真正有价值的地方,是把一个大任务拆成多个边界清晰的小任务,让每个子 Agent 只拿到完成任务所需的最小上下文。

flowchart LR
    M[主 Agent: 负责规划和整合] --> D[设计 Agent: 生成方案]
    M --> T[测试 Agent: 生成测试用例]
    M --> C[编码 Agent: 实现单个任务]
    M --> R[审查 Agent: 检查规范和风险]
    D --> M
    T --> M
    C --> M
    R --> M

这和软件团队分工类似。前端工程师不需要知道数据库索引的每个细节,后端工程师也不需要知道按钮 hover 动画的每个参数。接口定义清楚,各自完成职责,再通过联调整合。

对 AI 来说,子任务边界越清晰,上下文越聚焦,成功率越高。

人和 AI 都会错,但错误类型不同

人类工程师也不可靠。会误解需求,会写 bug,会漏测边界,会因为疲劳犯低级错误。成熟的软件工程并不是假设人不会错,而是通过评审、测试、灰度、回滚和监控来控制错误。

但 AI 的不可靠和人的不可靠有本质差异。

人类能力通常具有层级性。一个能设计复杂分布式系统的高级工程师,通常不会突然不知道如何打开 IDE(Integrated Development Environment,集成开发环境),也不会忘记函数怎么定义。学生能做对高难压轴题,通常说明基础知识已经比较扎实,简单题最多可能粗心算错,而不是完全不会。

LLM 不遵循这种层级关系。它可能解出复杂数学题,也可能在简单小数比较上出错;它可能生成完整 React 应用,也可能数错某个英文单词里有几个字母。因为它不是从基础能力逐层构建高级能力,而是在海量数据中学习模式分布。

这就引出两个概念:

类型含义例子系统设计难度
Known Unknown知道哪里可能有未知风险新人容易漏边界条件,所以安排 Code Review可针对性防护
Unknown Unknown不知道风险会从哪里冒出来AI 在复杂推理后突然写错文件操作参数很难提前布防

人类团队的错误更接近 Known Unknown。我们知道需求会误解,所以做需求评审;知道代码会有 bug,所以写测试;知道发布会有风险,所以灰度和回滚。

LLM 的错误更接近 Unknown Unknown。它可能在任何环节翻车,而且错误常常带着自信语气。一个系统如果要依赖 AI,就不能只问“AI 强不强”,还要问“AI 的错误能不能被检测、隔离和恢复”。

LLM 不是 AGI:知识多不等于真正智能

AGI(Artificial General Intelligence,通用人工智能)通常意味着一个系统能在广泛环境中自主学习、迁移能力、自我纠错并持续提升。当前 LLM 虽然知识量巨大,但离这个定义还有关键差距。

两个能力尤其重要。

自我纠错能力

真正智能体应该能形成闭环:

flowchart LR
    A[设定目标] --> B[采取行动]
    B --> C[观察结果]
    C --> D[判断是否达成目标]
    D -->|未达成| E[调整策略]
    E --> B
    D -->|达成| F[结束]

婴儿想拿玩具,够不到会换姿势、爬椅子、继续尝试。它知道目标有没有达成,因为“玩具是否在手里”是内在可感知的标准。

LLM 生成代码后,并不知道代码是否正确。除非外部系统运行测试、编译器报错、用户指出问题,否则它只是完成了一次输出。即使它重新生成,也不代表它真正“知道”上一版为什么错。

自我提升能力

人学骑自行车,每次摔倒都会改变身体控制和神经连接。同一个人在不断升级。

LLM 推理时参数固定。今天写了一次排序算法,不会让明天的同一个模型天然更擅长排序算法。模型能力提升通常来自重新训练、微调、强化学习或外部记忆系统,而不是推理过程中的自发成长。

这也是为什么“让 AI 反思一下”有时有效、有时无效。所谓反思,更多是把新的文字上下文加入对话,让下一次概率预测更可能落在正确区域,而不是模型本身真的成长了。

责任心不能外包给 AI

软件工程中,可靠交付不只依赖能力,还依赖责任。

一个工程师写生产代码时会小心,是因为错误有后果:线上事故、绩效影响、团队信任下降、业务损失。责任心不是抽象美德,而是“我必须承担后果”带来的行为约束。

AI 没有这种约束。它没有工资、绩效、名誉、职业发展,也不会因为一次错误在下次任务中真正感到压力。它能输出“我会谨慎处理”,但这只是语言模式,不是内在责任。

RLHF(Reinforcement Learning from Human Feedback,基于人类反馈的强化学习)和 AI Alignment(AI 对齐)可以让模型更倾向于输出安全、礼貌、谨慎的回答,但它们训练的是行为模式,不是后果意识。模型可以学会在某些场景说“我不确定”,也可以学会拒绝危险请求,但这不等于它真的在乎结果。

因此,人机协作必须遵守一个边界:

AI 可以承担产出,但人必须承担责任。

这不是否定 AI 的价值,而是明确系统设计的责任闭环。工程师不需要手写每一行代码,但需要保证最终交付物可理解、可运行、可维护、可回滚。

用系统对抗 AI 的不可靠

航空、建筑、软件团队都已经证明了一件事:个体不可靠并不可怕,可怕的是系统没有容错机制。

成熟软件团队会把错误拦在不同阶段:

阶段可能错误系统机制
需求阶段理解偏差、遗漏边界需求评审、原型确认、验收标准
设计阶段架构不合理、数据模型错误技术方案评审、ADR、容量评估
编码阶段Bug、风格混乱、性能隐患Lint、类型检查、Code Review
测试阶段漏测、回归问题单元测试、集成测试、端到端测试
发布阶段环境差异、配置错误CI/CD、预发布、灰度、回滚
运行阶段线上异常、容量波动监控、告警、限流、熔断

AI 协同开发也应该这样设计。不要幻想一个万能 Prompt 让 AI 永远正确,而要建设一套流程,让 AI 即使犯错,也能尽早暴露、局部隔离、快速修正。

构建可靠 AI 协同系统的三条原则

原则一:确定性优先

可程序化的事情,尽量交给程序,而不是交给 LLM 临场发挥。

错误示例:

写完代码后,请根据项目情况自行构建、测试和修复问题。

这个指令把构建流程变成了概率任务。AI 可能忘记参数,可能用错环境,可能跳过某个检查。

更好的做法是把流程固化成脚本:

#!/usr/bin/env bash
set -euo pipefail

echo "1. 格式化代码"
gofmt -w .

echo "2. 静态检查"
golangci-lint run ./...

echo "3. 运行单元测试"
go test ./... -race -cover

echo "4. 构建二进制"
go build -o bin/app ./cmd/app

然后告诉 AI:

修改代码后必须执行 ./scripts/check.sh。
如果失败,只根据报错修复相关代码,不要改动无关模块。

这样,AI 仍然可以帮忙修复问题,但检查规则由确定性工具负责。

同样的思路也适用于:

可固化环节工具或产物
代码格式gofmt、prettier、black
静态检查golangci-lint、ESLint、Ruff
类型检查TypeScript、MyPy、Pyright
测试pytest、go test、JUnit
构建Makefile、npm scripts、Dockerfile
部署GitHub Actions、GitLab CI、Jenkins
API 文档OpenAPI、Swagger
数据库变更migration 脚本

每固化一个确定性环节,AI 要猜的步骤就少一个。n 变小,整体可靠性就会上升。

原则二:减少可能性空间

开放问题会让 AI 在巨大空间里随机选择。约束越清楚,输出越稳定。

模糊请求:

帮我优化这个接口性能。

这个请求没有说明瓶颈在哪里、约束是什么、能不能改接口、能不能加缓存、能不能改表结构。AI 可能随便给出 Redis、异步化、索引、批处理等方案,但未必适合实际系统。

约束后的请求:

这个接口每秒约 1000 次调用,性能瓶颈来自 PostgreSQL 查询。
已决定使用 Redis 缓存,不允许修改 API 入参和返回结构。

请完成三件事:
1. 设计缓存 key,要求能区分租户和用户;
2. 实现读取缓存、回源数据库、写入缓存的逻辑;
3. 设置过期时间,业务数据平均 30 分钟更新一次。

只修改 internal/service/user_score.go,并补充对应单元测试。

这段 Prompt 做了几件事:

  • 限定瓶颈:数据库查询;
  • 限定方案:Redis;
  • 限定兼容性:API 不变;
  • 限定改动范围:只改一个文件;
  • 限定验收:补测试。

可能性空间越小,AI 越不容易跑偏。

原则三:阶段性交付,可验收后再继续

不要让 AI 一次生成一个完整系统。复杂任务应该拆成可沉淀、可验收的阶段性成果。

以“用户积分系统”为例,一个可靠流程可以这样设计:

flowchart TB
    A[阶段 1: 需求细化] --> B{人工验收}
    B -->|通过| C[阶段 2: 技术方案]
    B -->|补充需求| A
    C --> D{人工验收}
    D -->|通过| E[阶段 3: 任务拆分和验收标准]
    D -->|调整方案| C
    E --> F{人工验收}
    F -->|通过| G[阶段 4: 单任务编码]
    F -->|重拆任务| E
    G --> H[确定性检查: 测试/Lint/构建]
    H -->|失败| G
    H -->|通过| I{人工评审}
    I -->|通过| J[合并并沉淀文档]
    I -->|修改| G

阶段 1:需求细化

请帮我细化用户积分系统需求,不要写代码。

需要覆盖:
- 积分获得规则:哪些行为获得积分,各自多少分;
- 积分消耗规则:能兑换什么,扣减顺序是什么;
- 积分过期规则:是否过期,如何处理历史积分;
- 并发规则:如何防止重复发放和超额扣减;
- 审计规则:是否需要积分流水,流水字段有哪些。

输出为需求文档,使用 Markdown 表格描述规则。

这一阶段产出的需求文档本身就是高质量上下文。即使后续换模型、换工程师,也能复用。

阶段 2:技术方案设计

基于已确认的需求文档,设计技术方案,不要写实现代码。

需要输出:
- 数据库表结构,包括字段、索引、唯一约束;
- 核心领域模型;
- 核心 API 定义;
- 积分发放流程;
- 积分扣减流程;
- 并发控制方案,并说明选择乐观锁或悲观锁的理由;
- 失败重试和幂等策略。

方案阶段要人工重点验收。因为这里的错误会传导到所有代码实现。

阶段 3:任务拆分和验收标准

请把积分系统拆分成可独立实现的任务。

每个任务必须包含:
- 任务名称;
- 功能描述;
- 依赖项;
- 涉及文件;
- 验收标准;
- 必须通过的测试用例。

任务粒度要求:单个任务可以在一次对话中完成,且能独立测试。

好的任务拆分可以缩小 AI 的爆炸半径。某个任务失败,只需要重做局部,不会推倒整个系统。

阶段 4:逐个任务编码

当前只实现 Task 1:积分流水表 migration 和数据模型。

约束:
- 严格遵守 agents.md 中的项目规范;
- 只修改 migration 目录和 internal/model/points.go;
- 不实现 service 逻辑;
- 必须补充模型字段注释;
- 完成后执行 ./scripts/check.sh。

验收标准:
- migration 可以正常 up/down;
- 字段、索引、约束符合技术方案;
- go test ./... 通过。

这种方式的价值不是“让 AI 慢一点”,而是让每一步都能沉淀资产。需求文档、设计方案、任务列表、测试用例、脚本和规范,都会让下一轮协作更可靠。

项目里的 agents.md 应该写什么

AI 编程工具通常支持项目级说明文件,例如 agents.mdCLAUDE.md.cursorrules 等。这个文件不是口号墙,而是给 AI 的项目操作手册。

一个实用的 agents.md 可以包含:

# 项目背景
这是一个多租户积分系统,后端使用 Go,数据库使用 PostgreSQL,缓存使用 Redis。

# 目录约定
- cmd/app:程序入口
- internal/api:HTTP handler
- internal/service:业务逻辑
- internal/repository:数据库访问
- internal/model:领域模型
- migrations:数据库迁移脚本

# 编码规范
- handler 不写业务逻辑,只做参数解析和响应组装
- service 必须接收 context.Context
- repository 只负责数据访问,不包含业务判断
- 所有外部错误必须包装上下文信息
- 所有新增逻辑必须包含单元测试

# 检查命令
修改代码后必须执行:
./scripts/check.sh

# 禁止事项
- 不要修改公共 API,除非任务明确要求
- 不要引入新的第三方库,除非先说明理由并获得确认
- 不要跳过测试

这类文件的价值在于把隐性团队经验变成显性上下文。它可以减少 AI 每次都重新猜项目习惯的概率。

工程师角色会变成什么

AI 时代的软件开发更像两级编译:

flowchart LR
    A[业务需求] --> B[工程师: 结构化规格和验收标准]
    B --> C[AI: 生成代码和文档草稿]
    C --> D[确定性工具: 编译/测试/扫描]
    D --> E[工程师: 评审和决策]
    E --> F[生产系统]

过去,工程师主要把需求翻译成代码。现在,工程师越来越多地把需求翻译成结构化规格、约束、测试和流程,再让 AI 生成代码。真正的编译器仍然负责把代码变成机器可执行的产物,而 AI 更像夹在需求和代码之间的新型编译层。

这会改变工程师的核心能力模型:

过去更重要现在越来越重要
熟悉语法和 API能把需求写清楚
手写实现速度任务拆解和边界定义
单点调试能力构建可验收流程
个人编码熟练度沉淀项目上下文和自动化工具
记住框架细节判断 AI 输出是否符合系统约束

程序员不会因为 AI 直接消失,但会出现明显分化。能把需求、架构、测试、自动化和 AI 协同流程组织起来的人,会借助 AI 放大产出;只会等待 AI 一次性吐出完整项目、又无法验收结果的人,会被复杂任务反复拖进调试泥潭。

可靠的 AI 协同不是“完全放手”,而是逐步建设一个系统:

  • 确定性的事情交给程序;
  • 模糊但可生成的事情交给 AI;
  • 关键判断和责任留给人;
  • 每次协作都沉淀文档、脚本、测试和规范。

当这些资产不断积累,AI 才会从一个不稳定的代码生成器,变成软件工程体系中的高效协作者。


评论