《当代码可以被无限生成… 我病了吗?》
Prompt → Context → Rule → Harness,AI Coding 的工程化之路
我一直想写一篇关于 AI Coding 的文章。
去年做相关项目时,写过怎么写提示词,写了一半就搁置了。后来转向上下文工程,又开始研究上下文组织方式对模型生成效果的影响……再后来,就没有后来了。
大模型时代的技术迭代太快了。Prompt、Agent、Context、Spec、Skills、Harness……一个个新词不断冒出来,它们都在做同一件事:把我们正在做的事情,定义清楚,让它变得标准、可复用。
但我用 AI Coding 越深,一种奇怪的感觉就越强烈:
敲代码确实越来越轻松了,因为不用怎么敲了。但检查代码的负担,却越来越重了。
以前写代码是有节奏的:先把需求想清楚,理解上下文,在脑子里搭好方案,再一行行敲、查文档、等编译、做验证、转测。一套流程走完,其实是很放心的,可以非常自信的说一定没问题,就算有问题也不会怀疑是自己的问题。
但现在的工作流变成了这样:
可这个验证,成本可能很高。Agent 的改动范围可能很大,逻辑散、边界模糊,有时候很难一眼看出改了啥。虽然有理有据,但不是自己写的代码,我相信现阶段绝大多数人还是会再验证一遍。
于是我最清晰的感受是:
我对业务需求的理解,变弱了。
更准确地说,我对「我提交的代码」的理解程度变弱了。因为把更多时间花在了和 Agent「辩论」以及验证上:不断完善上下文边界、修正提示、重新生成,直到所有验证通过……整个过程里,越来越只关注「能不能跑通」这个结果,却慢慢忽略了业务需求本身的逻辑和边界。
模型写的代码确实比自己写的更快、更好、更标准。但有时候会问自己:
我真的读懂了这段牛逼代码了吗?
我能说清楚这里的业务逻辑吗?
往后系统升级的时候,我还记得这段代码为什么这么实现吗?
之前在前团队做 AI Coding 时有个经历:当时要写一个很繁琐的工程化校验逻辑,大多是正则表达式和类型判断。于是定好大体的框架后,就交给模型补全完整代码了。说实话,绝大多数场景下都没有问题,但不巧后台协议某些字段类型需要前端来兼容一下,恰巧我当时又在休假,同事排查起来就说非常困难,尤其是那些晦涩的正则和类型判断。
最后,他只能重构了一版。
而我细想,其实也不知道模型在那里面到底做了多少边界处理。毕竟,我只定了架构,细节实现,都是模型生成的。
那么当代码可以被无限生成、无限重构,我们,又该站在哪里?
这篇文章想通过这一年的技术演变,结合自己之前的工作经验,来写写自己的理解与看法。
Prompt:把话说清楚
去年做 Vibe Coding 相关工作,就是通过自然语言描述需求,调用大模型自动生成可落地的应用。
当时遇到最苦恼的问题:同一个模型,换一种说法,结果差很多。
Prompt 的质量直接决定了生成结果的质量。
而又因为这个原因,我们越发想往 Prompt 里塞更多内容,希望模型记住需求、记住约束、记住规范,让输出更稳定、更可控、更符合预期。
我们可能会这么做:
- 把模型历史的 badcase 写进 prompt,告诉模型不要这么干
- 列出几十项约束、限制,告诉模型不许这么写(组件命名需蛇形……)
- 希望模型能「记住」所有需求和规则,幻想它像程序员一样理解上下文
于是 Prompt 越写越长,越来越像一份前端百科全书……又或者是一份疑难杂症的病历,对症下药。
问题接踵而至:
| 问题 | 表现 |
|---|---|
| 遗忘 | 模型在多轮交互中丢失先前的约束条件 |
| 质量 | 生成代码存在命名不规范、逻辑缺失等 badcase 反复出现 |
| 失智 | prompt 中明确写了不要 xxx,反而出现的概率更大了 |
| 脆弱 | 改一个措辞就能导致整个系统崩溃 |
这些问题的根源,基于一个看似合理实则脆弱的假设:只要提供足够的上下文,AI 就能完美工作。
但事实并非如此。完美提示词是一种幻觉,工程问题只有最佳实践,没有最优解。
Prompt Engineering 迭代下来的最佳实践技巧包括:调整温度、结构化 Prompt(XML)、System Prompt、任务分解、提供 few-shot、订目标再探索结果。
这些东西有用,但需要理解本质:大模型是模式匹配,本质上是概率问题。
模型在生成每个 token 时,都在做概率选择,在当前上下文下预测下一个 token。提示词工程做的事,是在概率空间里提供方向,把模型输出的概率分布往特定方向推。
日常 Chat 中,很少需要考虑这些,让模型优化一下,它就能生成一段不错的提示词。
但提示词工程的边界非常清晰:它解决的是表达问题,而不是信息问题。
举个例子:让模型生成一个表单页面,表单组件用我自己写的私有组件库。提示词写得再漂亮,它也不会知道怎么用,只会去它训练数据里找一个类似的出来,然后编造一个结果给你。
graph LR
A[用户意图] --> B[Prompt Engineering]
B --> C{模型能理解吗?}
C -->|训练数据里有| D[正确输出]
C -->|训练数据里没有| E[幻觉/编造]
style E fill:#f9d0d0
style D fill:#d0f9d0
Prompt Engineering 的信息边界
Prompt 只能调整「怎么说」,解决不了「模型不知道」的问题。这就引出了下一层。
Context:把书准备好
上下文工程的出现,恰好解决了提示词工程信息缺失的问题。
核心逻辑很简单:在合适的时机、以合适的方式,给模型注入它不知道的信息,让模型能理解新事物、新规则。
在 Agent 处理任务的流程中,上下文的范围远比提示词更宽:历史对话记录、工具调用结果、私有文档、组件库说明、接口规范,这些都是模型训练数据里没有的东西;还有代码上下文、业务需求文档,甚至实时的系统状态。所有能影响模型决策的信息,都是上下文的一部分。
最早做上下文工程时,踩过很多坑。比如把所有文档一股脑塞进模型的上下文窗口,结果要么超出窗口限制,要么信息混乱,模型抓不到重点。
后来对上下文内容做了拆分:在每次 Agent Loop 流程中,动态构建上下文,基于 Query 的内容筛选关联上下文。单次模型调用中,只提供 Query 涉及的最大相关内容。
效果还挺好,上下文大小减少了很多,模型输出的内容也更稳定了。
之前看过一篇文章说,单次模型调用 prompt 长度超过 3000 tokens 后,模型降智的曲线会变得更加陡峭。所以不必各种吹嘘无限上下文、超长上下文——如果 1M 的上下文都能超,多多少少在 Agent 设计上有些问题。
RAG:检索增强生成
因为上下文窗口大小的限制,RAG in Context 成了不少工程实践的选择:在构建上下文窗口时,通过 RAG 检索外部知识库内容,再塞入上下文。
但 RAG 的召回稳定性是个老问题。之前做特定领域场景的 RAG 解决方案时,遇到过很多问题:文档怎么切分、切分策略怎么定,检索用什么方式,向量数据库里应该包含哪些内容……
分享几个实践经验,如果你的 RAG 是应用在某个特定领域(不是泛化的),可以试试:
- 利用大模型,针对知识库内容和特定领域的 Query,对知识库内容进行切分,避免文档被切得太碎、不连贯
- 关联 chunk 与 doc id,在后续增强步骤中召回
- 基于特定领域,预先生成批量的 Query-Answer 对,提高 Query 命中率
- 对检索结果 Rerank
flowchart LR
A[用户 Query] --> B[向量检索]
B --> C[召回 Top-K Chunks]
C --> D[Rerank 重排序]
D --> E[筛选最相关内容]
E --> F[注入 Context Window]
F --> G[模型生成]
H[知识库] --> I[文档切分]
I --> J[向量化入库]
J --> B
style D fill:#fff3cd
style E fill:#d4edda
RAG 检索增强生成流程
RAG 解决的是上下文超长、内容无法有效获取的问题。在这个检索增强生成的流程中,当一个 Query 需要依赖外部知识库时,我们只把相关内容放进上下文窗口。
Skills:渐进式披露
这和 Skills 的渐进式披露本质是一样的:不是把所有信息一开始就全给模型,而是按需、分层地给,做好精确的上下文管理。
Skills 的信息分为两类:
- 静态上下文:只给模型看最少的元信息,比如技能名称、描述、触发条件
- 动态上下文:模型自主决策后,按需加载相关的完整内容
这样做有两个好处:一是节省 context window,把宝贵的空间留给真正有用的信息;二是减少噪音,信息越少越精准,模型做决策的干扰越少。
一句话总结:给地图,不给百科全书。
flowchart TB
subgraph 静态上下文
A1[技能名称]
A2[简要描述]
A3[触发条件]
end
subgraph 动态上下文
B1[完整指令]
B2[示例代码]
B3[工具定义]
end
A1 & A2 & A3 --> C{模型判断
是否需要?}
C -->|是| B1 & B2 & B3
C -->|否| D[跳过,节省窗口]
style 静态上下文 fill:#e8f4fd
style 动态上下文 fill:#fff3cd
Skills 渐进式披露机制
但即便做好了上下文工程,还是会陷入新的问题:信息给对了,Prompt 也写得很好,模型生成的代码还是会出错。
因为上下文解决的是「输入问题」,没解决「执行问题」。模型在连续生成、多步操作的过程中,依然会跑偏、会出错,甚至悄悄修改不该改的逻辑,而我们往往要到最后验证时才能发现。
之前做 AI Coding 项目时,我们在 Agent 最后做了很多 Babel 检查来保证输出结果稳定,这和现在 Harness 中的结果控制不谋而合。但这还远远不够。
Rule:把流程把控好
这里本来想写 Agent 的,但 Agent 本质就是个 Loop,没什么太多可说的。这里主要讲 Spec 和 Rule 这两个在 Harness 前夜出现的词。
我们用 AI 写代码时,很容易陷入一个误区:只关心 AI 能不能把代码写出来,却很少想——AI 写出来的东西,怎么在我们的工程体系里管得住。
这也是之前做 AI Coding 反复踩过的坑:Agent 乱改接口、代码风格不统一、明明只写一个交互它非要顺手多做一堆,最后全是隐患。
问题不在模型强不强,在于没有给它明确的约束。Rule 和 Spec 就是用来解决这件事的,也是整个 Harness 体系的地基。
Rule:先学会不越界
Rule 的核心就一件事:把「不能干什么」写成机器能看懂、能校验的边界。
不同工具做法其实差不多:Codex 会读 AGENTS.md 做长期指导,Claude 用 CLAUDE.md 做默认约束,本质都是 Rule。现在很多项目也都会在
rules 目录下加上规范约束,Agent 启动就自动加载。
再配上 Linter、Babel 这类工具,把规则变成硬性检查。Agent 生成代码后,工具自动扫一遍:命名不规范、动了不该动的目录、语法有问题,直接打回去重改,直到合规为止。
这种方式比靠 Prompt 约束靠谱得多。
flowchart LR
A[Agent 生成代码] --> B[Linter/Babel 检查]
B --> C{是否合规?}
C -->|否| D[打回重改]
D --> A
C -->|是| E[通过,进入下一步]
F[Rule 定义] --> G[AGENTS.md / CLAUDE.md]
G --> H[Agent 启动自动加载]
H --> A
style D fill:#f9d0d0
style E fill:#d0f9d0
Rule + Linter 闭环检查流程
举个实际的例子。我在项目的 rules 里定义了一条约束:源文件名必须用 kebab-case(全小写,单词用 - 连接),禁止 PascalCase 或 camelCase。
// 错误
ModulePanelHeader.vue
UserProfile.ts
// 正确
module-panel-header.vue
user-profile.ts
Agent 启动时自动加载这条规则,生成文件时就会遵守。这比在 Prompt 里写「请用 kebab-case 命名文件」靠谱得多——Prompt 里的约束模型随时可能忽略,但 Rule 是每次任务都会带上来的硬上下文。
Spec:别让 Agent 跑偏
比较久之前用的了,现在我自己也不怎么用这个了,但在写 prompt 的时候会下意识地按照这种方式去提问,效果会好很多。
有了 Rule 守住底线,还需要 Spec 定清楚目标。
有一点很重要:Spec 不是更长的 Prompt,而是变更范围的纪律。
一份能用的 Spec,必须说清五件事:
- 这次要解决什么
- 这次不解决什么
- 允许改哪些文件、哪些逻辑
- 哪些接口、契约绝对不能动
- 做到什么样才算完成
给 Agent 派任务前,先写一段极简 Spec,不用写长,把范围和验收条件卡死。
比如:
只改登录页的提示文案,不动登录接口,不改其他页面样式,改完能正常渲染就算过。
边界一清楚,Agent 就不会自作主张「多做事」,更聚焦。
graph TB
subgraph Spec 五要素
A[要解决什么]
B[不解决什么]
C[允许改什么]
D[绝对不能动什么]
E[完成标准]
end
A --> F[聚焦目标]
B --> F
C --> G[控制范围]
D --> G
E --> H[明确验收]
F & G & H --> I[Agent 不跑偏]
style A fill:#d4edda
style B fill:#f9d0d0
style D fill:#f9d0d0
style E fill:#fff3cd
Spec 五要素结构
简单说:Rule 管边界,Spec 管目标。
但只靠这两个还不够。要让整个过程稳定可控,还需要 Loop 把流程跑完。
Loop:闭环执行
有了 Rule 和 Spec,Agent 才能在操场内有计划地干活。而 Loop 就是让 Agent 能稳定可控地干活,控制 Agent 执行任务的节奏。现在基本上所有的 Coding Agent 背后跑的都是 ReAct 模式:Reasoning + Acting,想一步,做一步。
流程是这样的:模型先看当前的上下文,想一下该做什么(Reasoning),然后调一个工具去执行(Acting),拿到工具返回的结果后再想下一步该做什么。如此循环,直到任务完成。
flowchart LR
A[观察上下文] --> B[思考:下一步该做什么]
B --> C[行动:调用工具]
C --> D[观察:拿到结果]
D --> E{任务完成?}
E -->|否| B
E -->|是| F[输出结果]
style B fill:#d4edda
style C fill:#fff3cd
ReAct 模式:Reasoning + Acting
举个例子:你让 Agent「修一下登录页的报错」。它不会一口气生成一大段代码,而是先读一下项目结构,发现登录页在
src/pages/login.tsx,再读这个文件,发现报错来自一个接口调用,然后去看接口定义,最后才动手改代码。每一步都是「看到什么,想一下,做一步」。
这和早期那种「一次性把需求丢给模型,然后开始许愿」完全不同。ReAct 模式让 Agent 有了纠偏的机会:每一步都能根据最新的信息调整方向,而不是一把梭。
当然,光有 Loop 不够。每一步之间还需要外部校验(Linter、测试)和状态记录,这些就是前面 Rule 和后面 Harness 负责的事情了。
对日常用 Claude Code 或 Codebuddy IDE 这些工具来说,我们不需要太关注 Loop 的内部实现,Agent 会自己跑这套流程。但理解它的运作方式,能帮我们写出更好的 Spec:把任务拆得足够小、边界足够清晰,Agent 在每一步 Reasoning 时才不容易跑偏。而对于 Rule 来说,每个项目会有自己的一些 rule 约束,这些 rule 约束会被带到 Agent 调用模型的提示词内,也就是会做出 Prompt 的约束。
Harness:模型之外的一切
前面聊了 Rule、Spec、Loop,分别解决边界、目标、执行流程的问题。放在一起,基本能让 Agent 少出错。
但少出错和稳定交付之间,还隔着一整套工程系统。
前面提到过,之前在 Agent 末尾加的那些 Babel 检查,现在回头看,只是 Harness 的冰山一角。检查只管最后一步的输出,真正要管住的,是 AI 从接到任务到交出结果的每一步。
这就是 Harness Engineering 要做的事。
先说清楚几个概念
LLM、推理模型、Agent、Harness,这几个词经常混着用,但它们处在不同层次:
graph TB
subgraph 层次关系
A[LLM
预测下一个 token] --> B[推理模型
多想几步,多校验几次]
B --> C[Agent
控制循环:看什么、做什么、何时停]
C --> D[Harness
工程系统:上下文、工具、状态、权限]
end
style A fill:#e8f4fd
style B fill:#d4edda
style C fill:#fff3cd
style D fill:#f9d0d0
LLM → 推理模型 → Agent → Harness 层次关系
| 概念 | 本质 | 类比 |
|---|---|---|
| LLM | 预测下一个 token 的引擎 | 会写代码的工程师 |
| 推理模型 | 多花算力、多想几步的 LLM | 做事前先想清楚的习惯 |
| Agent | 围绕模型的控制循环 | 处理任务的节奏和步骤 |
| Harness | 模型之外的一切工程系统 | 工位、工具链、规范、权限和验收机制 |
用一个类比把层次串起来:LLM 是一个会写代码的工程师,推理模型是他做事前先想清楚的习惯,Agent Loop 是他处理任务的节奏,Harness 就是团队给他配的工位、工具链、代码规范、仓库权限和验收流程。
一个再厉害的工程师,扔到陌生项目里,不给仓库权限、不告诉团队规范、不接 CI/CD,他也写不出能上线的代码。AI 也一样。
Mitchell Hashimoto 说过一句话:「你不是模型,那你就是 Harness。」对于我们做应用的工程师来说,模型层面能做的事越来越少,能拉开差距的,就是外面这层系统。
同一个模型,为什么效果差这么多
这是我一直在想的问题。
同样说一句「修一下这个问题,顺便看看代码为什么报红了」,有的 Agent 噼里啪啦回一大段解释,看起来很努力,结果改错了文件。有的 Agent 会先默默扫一遍仓库结构,读 README 和配置文件,跑一遍测试命令定位报错,最后给一个能直接合并的 diff。
模型是同一个,差别在哪?在 Harness。
自己也有过类似体验。同样的模型,在普通聊天界面里让它改 bug,它给我解释了一大堆原理,代码贴出来还跑不通。但在 IDE Agent 里,同样的指令,它会自己去读文件、跑测试、定位报错,最后给出能直接用的 diff。模型没变,变的是它跑在什么样的系统上。
所以 Harness 不是一个架子,也不是把 Rule、Spec、Loop 捆在一起就行了。它是一套运行时系统,负责上下文组装、工具调用、权限校验、状态持久化、出错恢复。简单说,模型之外跟执行有关的事,都归它管。
引导与控制
Harness 对模型的管理分两个阶段:生成之前引导,生成之后控制。
生成之前,给模型看该看的东西。架构文档(AGENTS.md、CLAUDE.md)、编码规范、类型定义,这些都属于引导。给模型看的是地图,不需要百科全书:告诉它项目长什么样、规矩是什么、哪些路不能走。
但文档不是越长越好。60 行以内的 AGENTS.md 比几百行的长文档有用得多。信息越精准,模型做决策时的噪音越少。
生成之后,用工具去校验结果。单元测试、类型检查、Linter、Hooks,都属于控制。这些工具的输出需要针对模型优化。比如 Linter 报错不要只说「line 42: error」,而要说「line 42: 变量名应该用 camelCase,当前是 snake_case,建议改为 userName」。模型拿到后者,一次就能改对。
一前一后配合:引导减少犯错概率,控制兜住漏网之鱼。
这个结构乍一看和模型训练里的前向传播、反向传播有点像,都是「一前一后形成回路」。但本质不同:反向传播真的在「学习」,梯度回传会改变模型权重;Harness 的控制只是「纠错重试」,模型还是那个模型,只是拿到了新的上下文信息再来一次。
flowchart LR
A[架构文档 / 编码规范 / 类型定义] -->|引导| B[模型生成代码]
B -->|校验| C[测试 / Linter / Hooks]
C -->|不通过| B
C -->|通过| D[合并]
style A fill:#d4edda
style C fill:#fff3cd
引导 + 控制
零信任
看到这里,你大概已经感觉到了:Harness 的任务,从来不是让 AI 更自由,而是让人更放心。
对我们来说,AI 一直都是一个黑盒,一个抽奖游戏。AI 写的每一行代码都需要被检查,不然不敢动。
这一小节标题叫零信任,说的是:在做 Agent 的时候,如果想保证结果稳定,不能只靠上下文。想做到 100% 正确,单纯靠模型能力,完全不可能。
那就工程化。在全链路内增加工程化校验,校验输出结果。但这也只能保证格式正确,不能保证逻辑正确。
比如之前做 Vibe Coding 时遇到的问题:我们的渲染引擎只支持两层数组,但模型生成页面配置 JSON 时不知道这个限制,偶尔会输出三层嵌套,直接解析失败。光在 Prompt 里写「不要超过两层」没用,模型过几轮就忘了。
如果任何环节都没有 AI 的参与,这个约束约定一下就好了。但现在大多数代码都是 AI 生成的,比如写个生成 Schema 的 Skills,让模型生成表单
Schema,它可能就不知道嵌套层数的限制。因此必须在工程环境上做好这个校验:定义一份 JSON Schema,直接把 items.type 不允许为 array
写死。模型输出之后跑一次校验,发现嵌套了就打回,把校验错误信息连同原始输出一起喂回去,让模型重新生成。这一步不需要人参与,Harness 自动完成。
flowchart LR
A[模型输出] --> B[JSON Schema 校验]
B -->|格式错误| C[错误信息 + 原始输出]
C --> D[模型重新生成]
D --> B
B -->|校验通过| E[进入下一步]
style B fill:#fff3cd
style C fill:#f9d0d0
格式校验与自动重试
同样的思路可以推广到更多场景:
- 文件操作前校验路径合法性,阻止越权访问
- Bash 命令执行前做语法级拦截,阻止
rm -rf /这类危险模式 - 代码生成后跑 Linter 和类型检查,不合格直接打回
- 极端情况下开 bare 模式,整个工具池只留文件读写和执行三个功能,其他全关
这些校验保证的都是「格式层面」的正确。逻辑对不对,还得靠测试用例和人工 review。但至少,格式层面的问题不需要人操心了,Harness 帮你挡掉。
光有拦截还不够,全程都得留痕。每一步的状态都被完整记录,形成可追溯的会话转录本。哪怕会话突然崩了,下次打开也能从断点接着干。
出了问题能追根溯源,好的实践也能沉淀成新的规则。用得越久,规则越准。
AI 越能干,缰绳越得勒紧
这是我对 Harness 最大的感受。
很多人觉得模型越强,需要的约束应该越少。恰恰相反。模型越强,它能做的事越多,失控的后果也越严重。一个只能写 hello world 的模型,放不放约束差别不大。但一个能重构整个项目、能执行任意命令的模型,如果不加约束,一次失控就可能造成不可逆的损害。
我们在业务层做的事情,本质是在产品自带的 Harness 之上再加一层约束。团队的代码规范、项目的目录约定、业务逻辑的边界,这些都是我们自己的 Harness。AGENTS.md
里的每一行,对应的可能就是一次历史踩坑。
说到底:模型决定你能做到多好,Harness 决定你最差不会差到哪去。
回到工程落地
聊了这么多,最后说说在实际工作中怎么看待这件事。
真实的软件开发,只有一小部分是在「生成下一段代码」。更多精力花在这些地方:
- 仓库导航,搞清楚项目结构
- 搜索文档,找到相关的接口和规范
- 查找函数和文件,理解调用链
- 应用 diff,确认改动范围
- 运行测试,验证没有破坏已有功能
- 检查报错,定位问题根因
- 维持上下文连续性,让下一步操作不丢失前面的信息
这些事,人类工程师再熟悉不过了。我们常觉得「写代码很累」,大部分累不是在想算法,而是这些碎的、脏的、但绕不过去的脑力劳动。回想一下自己一天的工作,真正在「写逻辑」的时间可能不到两成,剩下全是这些活。
一个好的 Coding Harness,就是把这些活接过去。它替你导航仓库、组织上下文、编排工具调用、管理状态恢复,你专注在业务判断上。
我自己现在接到需求的时候,因为在新团队,对业务理解和代码仓库都还在熟悉阶段,都会直接让模型先帮我找到这个需求要改动的地方在哪里,以及关联的是哪些东西,这样能快速有一个需求的全貌。
所以同一个模型,放在普通聊天界面里只能做简单的问答,放在一个做过上下文管理、工具编排、状态恢复的系统里,能完成复杂的多步骤工程任务。差距不在模型,在 Harness。
这也是为什么越来越多的 AI Coding 产品在 CLI 化。CLI 天然离终端近、离文件系统近、离开发者的真实工作流近。模型很重要,但产品形态决定了它能做什么事情。任务一旦进入真实仓库,结果差距就逐渐从模型能力转移到模型外面的那套系统工程上。
工程师的角色正在从「写代码」转向「设计写代码的系统」。我们写的 AGENTS.md、配置的 Hooks、定义的 Rules,做的就是 Harness Engineering。
说到这里,想聊聊之前做的 D2C(Design to Code)。
当时做的是基于 Figma 生成落地应用。流程是这样的:Figma 设计稿进来,模型先打标(识别组件类型、层级关系),然后工程化转标(把标注结果转成中间结构),基于 HTML 工程化出 React 代码框架,再让模型填充业务逻辑,最后跑一轮工具校验。整套流水线跑下来,还需要一份庞大的规范文档告诉模型该怎么写、不该怎么写。
这套方案能跑通,但中间环节很重。模型打标、工程化转标、基于 HTML 再出 React 框架,每一步都有信息损耗,每一步都可能出错。而且整条流水线是独立的,和业务工程是割裂的。模型不知道你项目里用的是什么组件库、什么目录结构、什么命名规范,它只看到一张设计稿和一份通用规范文档。生成出来的代码,往好了说能跑,往差了说和项目风格格格不入,还是要人花时间改。
即便是一次性的页面,这条链路我认为也不轻松,中间步骤的维护成本不低,一旦设计稿的结构稍有变化,流水线就容易断。
而我看来,D2C 应该回到工程环境本身。把 D2C 的能力沉淀成 Skills,这些 Skills 跑在 IDE Agent 里,天然就能利用 Harness 的能力——它知道你的项目结构、你的组件库、你的代码规范,生成出来的代码本来就是贴合项目的。中间不需要打标、转标、再转框架,省掉了那些信息损耗的环节。
flowchart LR
A[传统 D2C] --> B[打标 → 转标 → 出框架 → 填逻辑]
B --> C[通用规范文档]
C --> D[生成通用代码]
E[D2C as Skills] --> F[IDE Agent + Harness]
F --> G[项目上下文 + 组件库 + 规范]
G --> H[直接生成贴合项目的代码]
style A fill:#f9d0d0
style E fill:#d4edda
传统 D2C vs D2C as Skills
说白了,传统 D2C 是给模型一份说明书让它闭卷考试,D2C as Skills 是让模型坐在你的工位上开卷答题。同一个模型,后者的输出质量会好得多,因为 Harness 帮它补齐了所有它不知道的东西。
我自己最近在试的一个思路叫 D2A(Design to Anything)。做法很简单:通过 Figma API 拿到设计稿的 Design Token(颜色、字号、间距、组件结构),做一层压缩把冗余信息去掉,然后直接丢给 IDE Agent。不打标、不转标、不出中间框架,就一层。模型拿到的是精简过的设计意图,再结合当前项目的上下文(组件库、目录结构、代码规范),直接生成高可用的代码。
flowchart LR
A[Figma API] -->|获取| B[Design Token]
B -->|一层压缩| C[精简设计意图]
C --> D[IDE Agent]
D -->|结合项目上下文| E[生成代码]
E --> F[Playwright 视觉校验]
F -->|通过| G[交付]
F -->|不通过| H[截图差异 + 错误信息]
H --> D
style B fill:#e8f4fd
style D fill:#d4edda
style F fill:#fff3cd
D2A:一层压缩 + Playwright 视觉校验
整条链路只有一次信息转换,损耗降到最低。模型不需要理解 HTML 再翻译成 React,它直接从设计意图出发,在你的项目环境里写代码。
生成之后还有一步:启动 Playwright 做视觉校验。代码跑起来之后,Playwright 打开页面截图,和设计稿做像素级对比(或者和基线截图对比)。如果布局、间距、颜色有差异,把截图和差异信息回传给 Agent,让它基于视觉反馈修正代码。这一步不需要人参与,整个链路闭环。
这个 Skill 现在还在打磨,但方向上我觉得是对的:把中间环节砍到最少,让 Harness 做它擅长的事。Playwright 的加入,等于给 Agent 加了一双「眼睛」,它不再只能靠静态规则校验,还能看到真实的渲染结果。
但其实我认为,AI 时代下,Figma 不会是最终形态,未来的设计产品会有很大的变革。当然,AI 时代下的设计师也不会再追着那 1px 的 100% 还原了吧。
但写到这里,回头看看自己这一年的状态,又会冒出另一个问题。
向前看
前面聊的都是现状和经验。接下来想说一件正在酝酿的事:AI 时代的组件库建设。
传统组件库的边界
先说个观察。我们现在的组件库,不管是内部的还是开源的,设计目标都很明确:给开发者用。开发者看文档、查 API、引入组件、传 props、调事件。整条链路都是「人对组件」。
这没问题。但放到 AI Coding 的场景里,这条链路有个隐性的断裂:AI 写代码时,它怎么知道该用哪个组件?怎么知道这个组件的 props 约束?怎么知道组件之间的组合规则?
答案是:大多数时候,它不知道。
在之前做 AI Coding 出码开发的时候,就做过组件库的接入,当时遇到的最大问题就是:
- 历史组件逻辑耦合,难以抽象出标准组件
- props 非常多,props 之间有关联,传入 a === 1 则 b 一定要传入的隐性逻辑
- 没有规范的文档
- 没有系统的组件管理
模型能猜,能从训练数据里扒拉出一份类似的用法。但它不知道你项目里私有的 ProTable 和 Ant Design 的 Table 有什么区别,不知道
BizForm 的 onSubmit 回调里要做哪些校验,不知道 AuthButton 在没有权限时应该渲染成什么。拿 Login
组件来说,它不知道登录态需要什么参数,只会按照自己的想法来,userName 和 password。
于是只能把这些组件信息塞进上下文:组件文档、API 说明、使用示例。但这些东西都没有现成的,都需要重新生成。
而站在现在这个点,我们设计的组件库本身就可以自带一份 AI 可读的完整描述。
组件 Schema:一份结构化的组件百科
这个想法的起点很直接:能不能让每个组件在构建阶段就自动生成一份结构化的描述,这份描述足够完整,既能给 Agent 用来理解和组装组件,也能直接渲染成给人看的组件库文档。
关键点在「自动生成」这四个字上。我们不需要手动维护一份额外的 schema,而是定义好 schema 的结构规范,在组件库构建时,让模型自动从源码、类型定义、使用示例中提取信息,生成这份描述。当然,这是理想状态。也可以通过项目结构、项目描述来辅助生成更加精确的内容。
这是一个未来要定义的标准 schema 结构,还没想好。但设想一下,一个组件的 schema 大概长这样:
{
"name": "ProTable",
"category": "数据展示",
"description": "带分页、排序、筛选的企业级表格,支持远程数据源",
"importPath": "@biz/pro-table",
"props": {
"columns": {
"type": "Column[]",
"required": true,
"description": "列配置,定义表格的每一列"
},
"dataSource": {
"type": "Record[]",
"required": true,
"description": "表格数据源"
},
"pagination": {
"type": "PaginationConfig | false",
"default": "{ pageSize: 20 }",
"description": "分页配置,设为 false 关闭分页"
}
},
"constraints": {
"dataSource 超过 100 条时必须启用 remote 模式",
"columns 不支持嵌套超过 3 层"
},
"composableWith": {
"FilterPanel": "筛选面板,需要和 ProTable 的 filters 联动",
"SearchBar": "搜索栏,需要通过 onSearch 回调触发表格查询"
},
"antiPatterns": [
"不要在 columns 里写业务逻辑,应该用 render 函数",
"不要直接修改 dataSource 引用,应该通过 setState 更新"
],
"scenarios": [
"商品管理列表:支持搜索、筛选、批量操作",
"订单详情表格:展示固定数据,支持展开查看子订单"
]
}
这份 schema 覆盖了一个组件的全部维度:props 定义、插槽、事件、用法场景、约束条件、组合规则、反面模式。它既是一份完整的组件文档的数据源,也是 Agent 理解和组装组件的全部输入。
换句话说,只要有了这份 schema,就能:
- 渲染出完整的组件库文档站(给人看)
- 作为 Agent 的上下文(给 AI 用)
- 驱动组件间的组合校验(给工程系统用)
同一份数据,三个消费方。这就是 schema 的价值:一份投入,多处产出。
构建:模型生成,人审校
刚才说了,schema 是构建时模型自动生成的,不是手写的。整个链路大概是这样的:
flowchart LR
A[组件源码] --> D[模型提取]
B[类型定义 / TypeScript] --> D
C[JSDoc / 注释] --> D
E[已有使用示例] --> D
F[人工补充的业务上下文] --> D
D --> G[生成 Schema]
G --> H{人工审校}
H -->|通过| I[入库]
H -->|不通过| J[修改 + 标记问题]
J --> G
style D fill:#d4edda
style H fill:#fff3cd
style I fill:#e8f4fd
Schema 生成链路
模型从四个来源提取信息:
- 组件源码:提取导出接口、默认值、内部逻辑分支
- 类型定义:提取 props 的类型签名、泛型约束
- JSDoc 注释:提取已有的描述信息
- 使用示例:从项目中搜索该组件的实际用法,提取真实场景
此外,还需要人补充一些模型提取不到的业务上下文:比如「这个组件在哪个业务线用得最多」「和哪个后端接口强绑定」「有什么历史遗留的限制」。
生成之后,走一轮人工审校。审校通过就入库,不通过就标记问题点,修改后重新生成。
这个流程的好处是:schema 的生成成本被摊到了构建环节,组件每次发版,schema 自动更新。不需要专门维护一份文档,也不需要担心文档和代码不同步。
消费:一份 schema,处处可用
schema 入库之后,它就不再属于某个特定工具,而是一份通用的组件知识资产。任何需要理解组件的场景,都能消费这份 schema。
IDE Agent 场景:Agent 接到页面需求时,从 schema 注册表中检索相关组件,按约束生成代码。Agent 不需要猜这个组件怎么用,schema 里有完整的 props 定义、使用场景和组合规则。
CLI 场景:在终端里敲一行命令,就能查到某个组件的用法、示例、注意事项。不需要打开浏览器翻文档,schema 就是文档的数据库。
D2C 场景:设计稿进来,Agent 从 schema 中匹配组件,按组合规则组装。不需要维护一份额外的「设计组件到代码组件」的映射表,schema 里的 category 和 composableWith 字段天然承担了这个职责。
新成员 onboarding:新人加入团队,不需要先啃几百页的文档站。把 schema 注入到 AI 助手里,直接问「我要做一个商品列表页该用哪些组件」,AI 基于真实的 schema 数据回答,比看文档高效得多。
graph TB
A[组件 Schema 注册表] --> B[IDE Agent
自动生成组件代码]
A --> C[CLI 工具
终端查询组件用法]
A --> D[D2C
设计稿自动映射组件]
A --> E[AI 助手
新人问答 / 方案推荐]
A --> F[文档站
自动渲染组件文档]
style A fill:#d4edda
一份 schema,处处可用
核心思路是:schema 是唯一的真实来源。文档站、Agent 上下文、CLI 工具、D2C 映射,都从同一份 schema 派生出来。不会出现「文档上写的和代码里实现的不一样」这种老问题,因为文档就是从代码生成的。
Agent First:不只是组件库
组件 Schema 只是其中一个切面。更宏观地看,AI 时代下,工程里的一切都应该变得更加 Agentic。
这也是我到新部门工作感受最深的一点:所有的工程设计都要 Agentic。
这不是一句口号,而是一个设计原则:在写代码、搭架构、做工程决策的时候,多想一步——Agent 能不能理解?AI 能不能利用?
几个具体的例子。
项目结构设计。传统的项目结构是给人导航的:src/pages、src/components、src/utils,开发者靠经验和约定找到想改的文件。但
Agent 导航项目的方式和人不一样,它更依赖结构化的描述文件。所以新项目里,除了目录本身,还会维护一份结构描述:每个目录干什么、每个模块的职责边界、模块之间的依赖关系。这份描述写在
AGENTS.md 里,Agent 启动时自动加载,不需要自己猜。
接口契约前置。以前前后端协作是「人对人」:前端看接口文档,后端写接口文档,中间经常对不上。新项目里,接口契约不再只是一份给人看的文档,而是一份机器可读的 JSON Schema 或 TypeScript 类型定义。Agent 读到这份类型定义,就知道请求长什么样、返回什么结构、哪些字段必填。前后端的 AI 都在同一份契约上工作,对不上的概率大幅降低。
错误处理标准化。以前每个模块自己定义错误码和错误信息,格式五花八门。Agent 处理报错时,需要理解「这个报错是什么意思、该从哪里排查」。新项目里,错误处理统一收敛:错误码、错误信息、排查建议、关联文档链接,全部结构化。Agent 拿到报错信息,不需要猜,直接知道该干什么。
构建产物多态。前面提到的组件 Schema 就是典型例子。构建时不止产出 JS bundle,还产出 AI 可消费的 schema、人可读的文档、校验可用的类型定义。同一份源码,一次构建,多种产物。Agent 用 schema,开发者用文档,工程系统用类型定义,各取所需。
flowchart LR
A[组件源码] --> B[构建流程]
B --> C[JS Bundle
给运行时]
B --> D[组件 Schema
给 Agent]
B --> E[类型定义
给工程系统]
B --> F[文档站
给开发者]
style B fill:#d4edda
style D fill:#fff3cd
一次构建,多种产物
配置即约束。传统项目里,很多工程约束是靠人遵守的:命名规范、目录约定、提交格式。新项目里,这些约束全部写成可执行的规则:ESLint 配置、Husky hooks、CI 检查。Agent 不需要「记住」这些规则,工具会自动替它检查。这其实就是前面聊的 Harness 思路在项目维度的落地。
回头看我正在做的新项目,整个架构设计就是按这个思路来的。从项目初始化的第一天起,就不只是「给人用」,而是同时考虑「给 Agent 用」。目录结构、类型定义、错误处理、构建流程,每一层都多想了一步:AI 能不能从这些信息里理解项目的全貌?
以前我们写代码,心里只有一个读者:下一个看这段代码的人。现在多了一个读者:AI。而且这个读者可能比下一个同事来得更早、看得更多。
代码写得好不好,以前看人好不好懂。以后还要看 AI 好不好懂。
我病了吗
回到开头那个问题。
当 AI 席卷各行各业,焦虑和亢奋让人矛盾激昂。
我病了吗?
没有。只是刚好站在一个时代切换的路口:从人写代码,到 HITL in AI(Human-in-the-Loop)。
之前面试时面试官说了一句话,我记了很久:「AI 时代不分什么前端、后端……不要给自己加前缀,我们都是工程师」(大概这个意思)。
技术边界正在被打破,Agent 在重塑我们的角色。但工程师的核心在于解决问题的能力,而非某种编码能力。
工具在变,瓶颈在变,价值在变。但真正不变的是:人对业务的理解、对逻辑的判断力。这些是 AI 永远无法生成的东西。
对个人来说,在 AI 时代站稳脚跟的关键,需要更加专注于文档的沉淀,或者说数字资产的积累。不管大模型、Agent 未来怎么迭代,只要我们有自己的文档沉淀、经验积累和业务认知,这些独一无二的数字资产,就是我们始终坚挺的底气。
加油!
graph LR
A[代码 / 文档 / 测试用例] -->|AI 能生成| C[需要人验证]
B[业务理解 / 逻辑判断力 / 经验沉淀] -->|AI 无法生成| D[验证的底气]
style A fill:#e8f4fd
style B fill:#d4edda
AI 能生成 vs AI 无法生成