
实用技巧 — 知道这些就能指挥AI
第3课学了用Hurl测试和Git防止漂移。50个端点以内足够了。但规模变大后出现新问题。你只是说了句"整理一下",AI就把你定的决策当成细节给覆盖了。
核心原理:把决策从代码中提取出来。 代码里混着你的决策(“这个字段是整数类型”)和细节(变量名、错误处理)。AI分不清这两者。把决策声明在单独的规范中,AI就无法覆盖。
安装yongol:
对智能体说:“npx skills add park-jun-woo/yongol 安装”
用声明式方式做一个Login功能:
对智能体说:“用SSOT声明Login功能”
AI会自动生成API规范、DB模式、服务流程、授权策略、测试场景。
对智能体说:“运行yongol validate,让错误数归零”
有错误AI会自己修。287条规则交叉验证10个规范之间的矛盾。所有勾号都是绿色就可以开始代码生成了。
yongol目前仅支持Go项目。但"把决策移出代码、用交叉验证捕获矛盾"的原理与语言无关。而且第3课学的Hurl测试本来就不受语言限制。
快速体验
让智能体安装yongol skill:
对智能体说:“npx skills add park-jun-woo/yongol 安装”
对智能体说:“用SSOT声明Login功能”
AI会生成5个文件。specs/api/openapi.yaml、specs/db/users.sql、specs/service/auth/login.ssac、specs/policy/authz.rego、specs/tests/scenario-login.hurl。
对智能体说:“运行yongol validate specs/”
0 errors就成功了。
故意制造一个矛盾:
对智能体说:“把OpenAPI中的email字段改成mail”
对智能体说:“运行yongol validate specs/”
应该会出现"OpenAPI里是mail但DDL里是email"这样的错误。只看一层没问题。交叉两层就能看到矛盾。
对智能体说:“修复validate错误”
AI会统一字段名。再validate。0 errors。
为什么要这样指挥
上节课回顾
第3课学了三样东西。
- 用Hurl声明和验证API行为
- 用Git创建存档点
- 用CI/CD自动化验证
仅这三样就能防止氛围编程最大的敌人——漂移。“添加新功能。但所有现有Hurl测试必须通过。“这一句话就是防线。
但超过50个端点后,新问题出现了。
超过50个会怎样
假设用氛围编程做一个SaaS。一开始很快。
“做个注册” — 2分钟。 “做个登录” — 1分钟。 “做个资料编辑” — 1分钟。
12个端点、5个表。20分钟就能跑起来。
超过50个就开始出问题。AI昨天做的模式今天不一样了。超过100个,现有功能悄悄坏了。超过200个,添加一个新功能比最初做10个还慢10倍。
为什么?
不是因为AI笨。
代码中混杂的三样东西
打开源代码会看到三样东西混在一起。
用户决策 — “这个字段是整数类型。““这个API只有所有者能访问。““分页用游标方式。”
业务逻辑 — 定价策略、工作流、生命周期规则。
实现细节 — 变量名、库调用顺序、错误处理代码。
AI读这些代码时,分不清哪行是你的决策、哪行是细节。所以你说"重构一下”,它就把你的决策当成细节悄悄覆盖了。
打个比方。你盖房子时决定了"大门必须朝南”。然后让装修工"把房子整理一下”,结果他改了大门方向。“这样动线更好。“从装修工的角度是优化。从你的角度是灾难。
AI干的就是这个。用更大的模型也解决不了。因为介质(源代码)本身就不保护决策。
把决策移出代码
解决方案很简单。把决策从代码中分离出来。
以前你指挥AI的方式:
"做登录API" → AI写代码 → 决策和细节混在一起
yongol提出的方式:
你声明决策 → AI编辑声明 → yongol生成代码
决策住在声明式规范中,代码是一次性的投影。决策变了就改声明、重新生成代码。细节变了就只重新生成代码。互不混淆。
SSOT十类 — 各管一个关注点
yongol把构成软件的决策分离为10个声明式规范(SSOT: Single Source of Truth)。每个规范只管一个关注点。
你不需要记住这些名称。按角色分组很直观:
定义数据的:
| SSOT | 管什么决策 | 通俗地说 |
|---|---|---|
| features.yaml | 功能目录 | “要做什么” |
| manifest.yaml | 项目配置 | “认证用JWT,数据库用PostgreSQL” |
| SQL DDL | 数据模型 | “在这个表里存这些字段” |
| sqlc | DB查询 | “用这个SQL查询数据” |
定义行为的:
| SSOT | 管什么决策 | 通俗地说 |
|---|---|---|
| OpenAPI | API契约 | “往这个地址发这些数据,收到这个响应” |
| SSaC | 服务流程 | “按查询→验证→创建→响应的顺序处理” |
| Mermaid stateDiagram | 状态转移 | “订单按待处理→已确认→已完成→已取消的顺序变化” |
验证的:
| SSOT | 管什么决策 | 通俗地说 |
|---|---|---|
| OPA Rego | 授权策略 | “只有管理员能删除” |
| Hurl | 测试场景 | “这样调用必须这样响应” |
定义界面的:
| SSOT | 管什么决策 | 通俗地说 |
|---|---|---|
| STML(Service Template Markup Language) | 前端 | “在界面上这样显示这些数据” |
10种看起来很多?不用怕。知道三个事实就行。
第一,10种中8种是行业标准。 OpenAPI、SQL、sqlc、Rego、Mermaid、Hurl、YAML——都是专业开发者用的行业标准工具,但你不需要直接用。AI知道。yongol新创建的只有SSaC(服务流程)和STML(前端)两种。
第二,你不需要学这些。 AI知道。你说"做个注册功能”,AI就编辑10个规范。你看到的只是结果。
第三,yongol目前仅支持Go项目。 还不能用于React+FastAPI或Next.js这样的技术栈。但第4课学的原理——把决策移出代码、用交叉验证捕获矛盾——与语言无关。理解原理后工具扩展时可以立刻应用。而且第3课学的Hurl测试已经不受语言限制——没有yongol也能现在就做API契约验证。
10万行 vs 1.25万行
为什么非要把决策单独提出来?看数字就明白了。
| 规模 | 示例 | SSOT(仅决策) | 实现代码 |
|---|---|---|---|
| 小型 | 美发店预约 | ~1,500行 | ~1万行 |
| 中型 | Jira、Notion级 | ~12,500行 | ~10万行 |
| 大型 | Shopify级 | ~30,000行 | ~30万行 |
以中型SaaS为例,10万行代码中决策只有12,500行。其余87,500行是布线——错误处理、库调用、样板代码。
可以让AI读10万行。1M token上下文物理上做得到。但能读和能准确处理是两回事。上下文越长,中间信息越容易遗漏,无关token会干扰判断。
只分离决策就是12,500行。在没有噪音、只剩核心的上下文中,AI准确率会提高。同一个AI做同样的工作,读取量减少到八分之一,准确率就上升。约10倍的上下文压缩效果。
operationId — 贯穿全部层的钥匙
10个规范各自为政就是混乱。需要连接。怎么连?
用一个名字。
按一个按钮,一个请求就发到服务器。每一个这样的请求叫端点。
在全栈应用中,功能的单位是API端点。用户按按钮,API被调用,API执行服务逻辑,读DB,检查权限,转移状态。这个流程的起点就是operationId。
一目了然地展示一个功能横跨哪些文件。
输入ExecuteWorkflow这个operationId看看会发生什么:
── Feature Chain: ExecuteWorkflow ──
OpenAPI api/openapi.yaml POST /workflows/{id}/execute
SSaC service/workflow/execute_workflow.ssac @get @empty @auth @state @call @publish @response
DDL db/workflows.sql CREATE TABLE workflows
DDL db/execution_logs.sql CREATE TABLE execution_logs
Rego policy/authz.rego resource: workflow
StateDiag states/workflow.md diagram: workflow → ExecuteWorkflow
FuncSpec func/billing/check_credits.go @func billing.CheckCredits
FuncSpec func/billing/deduct_credit.go @func billing.DeductCredit
FuncSpec func/worker/process_actions.go @func worker.ProcessActions
FuncSpec func/webhook/deliver.go @func webhook.Deliver
Hurl tests/scenario-happy-path.hurl scenario: scenario-happy-path.hurl
读不懂这个输出也没关系。重要的是一个operationId就能追踪全部。
从API规范到DB模式、授权策略、状态转移、函数实现、测试场景——一个功能的全部地形一屏可见。
这就是yongol把operationId称为**拱心石(keystone)**的原因。就像建筑中拱顶最后嵌入的那块楔石支撑整个拱一样,一个PascalCase标识符物理性地绑定了10个层。
代码评审时——“改了这个功能,那个文件是不是也该变?“把这个疑问和chain对照就能立刻确认。新人入职问"ExecuteWorkflow怎么运行的?",展示一个Feature Chain就行。几十次grep被一条命令替代了。
SSaC — 捕获函数内部的决策
10个SSOT中最独特的是SSaC(Service Sequences as Code)。
看看现有SSOT,OpenAPI声明"接收什么请求、给什么响应”。SQL DDL声明"存什么”。但函数内部——“查询→验证→创建→响应"这样的业务流程——没有地方可以声明。只有读实现代码才能知道。
SSaC填补了这个空白。
来看一个实际例子。“接受提案(AcceptProposal)“这个功能。
先用中文读是这样的:
- 查询提案
- 提案不存在就报"未找到"错误
- 查询提案关联的项目
- 项目不存在就报"未找到"错误
- 检查请求者是否为项目所有者
- 检查提案状态是否允许接受
- 检查项目状态是否允许接受
- 将提案状态改为"已接受”
- 给项目分配自由职业者,状态改为"进行中”
- 将付款资金托管
- 发布"提案已接受"事件
- 重新查询更新后的提案并返回
用SSaC写出来是这样的:
下面的代码只是把上面的中文按格式写出来罢了。不需要读。
// @get Proposal p = Proposal.FindByID({ID: request.id})
// @empty p "Proposal not found" 404
// @get Gig gig = Gig.FindByID({ID: p.GigID})
// @empty gig "Gig not found" 404
// @auth "AcceptProposal" "gig" {ResourceID: request.id} "Forbidden" 403
// @state proposal {status: p.Status} "AcceptProposal" "Cannot accept" 409
// @state gig {status: gig.Status} "AcceptProposal" "Cannot accept on gig" 409
// @put Proposal.UpdateStatus({ID: p.ID, Status: "accepted"})
// @put Gig.AssignFreelancer({ID: gig.ID, FreelancerID: p.FreelancerID, Status: "in_progress"})
// @call billing.HoldEscrowResponse escrow = billing.HoldEscrow({GigID: gig.ID, Amount: gig.Budget})
// @publish "proposal.accepted" {GigID: gig.ID, FreelancerID: p.FreelancerID}
// @get Proposal updated = Proposal.FindByID({ID: p.ID})
// @response { proposal: updated }
func AcceptProposal() {}
你不需要读这段代码。AI来写。你只需确认上面的中文描述是否正确。
16行。10个注解。里面有什么:
- 两次资源查询(
@get) - 存在性检查(
@empty) - 权限检查(
@auth) - 两次状态机验证(
@state) - 两次更新(
@put) - 托管处理(
@call) - 事件发布(
@publish) - 最终响应(
@response)
从这16行生成实现代码会超过100行。错误处理、事务管理、类型转换、响应格式化——全由代码生成来填充。你只需关心"按什么顺序处理"这个决策。
SSaC的全部注解不超过20个。一页纸就能学会。而且再说一次,你不需要自己学。AI来写。
yongol validate — 287条规则捕获矛盾
决策分散在10个文件中,文件之间可能产生矛盾。
- DDL里是
BIGINT但OpenAPI里是string怎么办? - SSaC声明了
@auth但Rego里没有对应规则怎么办? - 状态图里有转移但SSaC里没有对应函数怎么办?
- Hurl有测试但引用了features里不存在的端点怎么办?
矛盾的决策产生矛盾的代码。代码再干净,决策对不上行为就会出错。
yongol validate捕获这些。检查10个规范之间所有连接的结果:
不需要完全理解下面的输出。所有勾号都是绿色表示没有矛盾。
✓ manifest ✓ openapi_ddl ✓ ssac_rego
✓ openapi ✓ openapi_ssac ✓ ssac_authz
✓ ddl ✓ hurl_openapi ✓ ssac_sqlc
✓ query ✓ hurl_statemachine ✓ ddl_statemachine
✓ ssac ✓ hurl_manifest ✓ ddl_rego
✓ statemachine ✓ openapi_manifest ✓ rego_manifest
✓ rego ✓ ssac_ddl ✓ stml_openapi
✓ hurl ✓ ssac_statemachine
✓ funcspec ✓ ssac_func
0 errors, 0 warnings
先单独验证每个SSOT,然后执行跨层交叉验证。约287条规则检查10个SSOT之间的所有符号引用。有一个矛盾就拒绝代码生成。
这里要抓住要点。现有工具只看自己那一层。 OpenAPI验证器检查OpenAPI规范是否有效。SQL验证器检查DDL是否有效。但"OpenAPI里user_id是string,DDL里是BIGINT”——这种跨层矛盾没有工具抓。yongol validate的独特价值在于这个交叉验证。
出错时会显示这样的消息:
✗ SSaC CancelReservation
@model Reservation.SoftDelete — method not found in sqlc queries
✗ Cross 1 mismatch
FAILED: Fix errors before codegen.
“CancelReservation函数调用了Reservation.SoftDelete,但sqlc查询中没有SoftDelete方法。“毫不含糊。精确告诉你哪里对不上。
AI自由地写。跑出轨道validate立刻捕获。轨道上的自由。
yongol agent — 4.5B模型也能收敛到0错误
validate能捕获——很好。但修错误也要人来做吗?
不用。AI来做。
yongol agent specs/ --model ollama:gemma4:e4b --max-rounds 20
这一条命令让AI重复validate→确认错误→修复→再validate→再修复。直到0错误。
有实验结果。对一个Login端点,让不同模型编写9个SSOT文件:
| 模型 | 大小 | 环境 | 结果 |
|---|---|---|---|
| Grok 4.3 | 大型 | API | 首次尝试0错误 |
| Gemini 2.5 Flash | 中型 | API(免费) | 1次反馈后0错误 |
| Gemma4 | 4.5B | 本地(16GB VRAM) | 1次反馈后0错误 |
| Qwen3 | 8B | 本地 | 1次反馈后0错误 |
4.5B本地模型也行。零成本。离线。不需要联网。
为什么小模型也能行?因为validate给出的反馈是确定性事实。“line 41: field name mismatch, expected ‘user_id’, got ‘userId’"——这不是意见。是事实。面对事实AI没有谄媚的余地。“好的,我来修”——接受并修正。
不是模型的IQ,而是反馈的精确度决定结果。
基准测试:ZenFlow — 69分钟32个端点
不是理论。是实测。
ZenFlow——多租户工作流自动化SaaS。从头到尾用yongol做的。
| 阶段 | 内容 | 时间 | 累计 |
|---|---|---|---|
| 初始构建 | 10个端点、6个表、认证、状态机 | 13分钟 | 13分钟 |
| + 版本管理 | 工作流克隆、版本列表 | 6分钟 | 19分钟 |
| + Webhook | Webhook CRUD、队列后端 | 6分钟 | 25分钟 |
| + 模板市场 | 游标分页、跨组织克隆 | 3分钟 | 28分钟 |
| + 文件附件 | 执行报告、文件后端 | 4分钟 | 32分钟 |
| + 调度 | 定时调度、会话后端 | 6分钟 | 38分钟 |
| + 审计日志 | 偏移分页、缓存后端 | 3分钟 | 41分钟 |
| + 仪表板 | 关联连接、func响应类型 | 7分钟 | 48分钟 |
| + 批量操作 | jsonb批量插入 | 14分钟 | 62分钟 |
| + 外部API | 地理编码func、添加字段 | 3分钟 | 65分钟 |
| + 条件更新 | 哨兵模式、自动分配 | 4分钟 | 69分钟 |
最终:32个端点、14个表、47个Hurl请求。11/11阶段通过。
这些数字中最重要的不是"69分钟”。而是功能增加时速度没有下降。
第一个功能(初始构建)花了13分钟。第十一个功能(条件更新)花了4分钟。氛围编程中所谓的"200端点之墙”——功能增加时追加成本呈指数增长的现象——并不存在。
现有测试也没坏。47个Hurl请求在每个阶段全部通过。
生成的代码能编辑吗
“代码自动生成的话,我手动改的部分不会丢掉吗?”
可以。yongol generate再次运行时会保留用户编辑。
- 所有生成文件都有
//yg:checked llm=yongol-gen hash=<8hex>注解。 - 你修改代码后hash会变。
- hash变了的文件标记为已保留(preserved),下次generate时跳过。
yongol status可以查看已保留文件和契约漂移。
SSOT是真理,生成代码是投影,但你在投影上画的画会被保留。
为什么更大的模型不是答案
“GPT-6出来就解决了。”
解决不了。问题不在模型智能,在介质。
代码这种介质不区分决策和实现。什么模型来读代码,看到的都是决策和细节混在一起的文本。模型再聪明,介质不提供区分就无法区分。
yongol改变了介质。把AI编辑的对象从代码转移到声明式规范。规范中只有决策没有实现细节,所以AI不会把决策当成细节。
小LLM只编辑SSOT,validate在每次失误时给出精确反馈,就能维持跟大得多的模型编辑原始代码同等水平的决策完整性。
不是更大的模型,而是更精确的结构才是答案。
智能体工作流 — 你看到的只有结果
实际使用yongol的流程是这样的:
1. 说"做预约功能"
2. AI编辑specs/里的SSOT
3. yongol validate specs/ — 检查一致性
4. 有错误 → AI修复对应的SSOT → 回到第3步
5. 0错误 → yongol generate — 生成代码
6. Hurl测试自动运行
7. 通过 → 提交。下一个功能。
你不需要读代码。甚至不需要读SSOT。“做” → “好了吗?” → “好了” — 只转这个循环就行。不同的是背后不会坏。
氛围编程的体验不变。只是200端点之墙消失了。
总结 — 本课要记住的
代码中混着决策和细节。 AI分不清这两者。这是漂移的根本原因。
把决策移出代码。 10个声明式规范(SSOT)各管一个关注点。10种中8种是行业标准。
operationId是拱心石。 一个名字贯穿10个层。一个Feature Chain就能看到功能的全部地形。
287条规则捕获跨层矛盾。 现有工具只看自己那层。yongol validate抓的是层与层之间的裂缝。
4.5B模型也能收敛到0错误。 不是模型的IQ,而是反馈的精确度决定结果。
实操:声明Login端点为SSOT并捕获矛盾
目标: 亲身体验yongol validate的交叉验证。
步骤1:环境准备
把以下命令复制到终端执行,安装yongol功能。
npx skills add park-jun-woo/yongol
在AI智能体中安装yongol skill。
步骤2:声明Login端点
让AI做:“用SSOT声明Login功能。”
AI会生成5个文件:
specs/api/openapi.yaml— POST /auth/loginspecs/db/users.sql— CREATE TABLE usersspecs/service/auth/login.ssac— @get → @empty → @call → @responsespecs/policy/authz.rego— 授权策略specs/tests/scenario-login.hurl— 登录测试
步骤3:验证
yongol validate specs/
0 errors就成功了。
步骤4:故意制造矛盾
让AI"把OpenAPI中的email字段改成mail”。DDL和SSaC保持不变。
yongol validate specs/
应该会出错。“OpenAPI里是mail但DDL里是email。”
这就是交叉验证。只看一层没有错。交叉两层矛盾就暴露了。
步骤5:解决矛盾
让AI:“修复validate错误。” AI会统一字段名。再validate。0 errors。
步骤6:代码生成
yongol generate specs/ artifacts/
Login功能的完整代码被生成。从SSaC的16行产出100行以上的实现代码。
本次实操应该体会到的:
- 决策(SSOT)和实现(生成代码)分离的感觉
- 交叉验证捕获跨层矛盾的瞬间
- 说"修一下"后AI跟着validate反馈收敛的过程
相关文章
- yongol — AI编程SaaS的龙骨 — yongol架构、10个SSOT、287条交叉验证规则的技术详情
- Feature Chain — 用一个operationId贯穿10个层的追踪体系
- SSaC — Service Sequences as Code。声明式捕获函数内部业务流程的方法
Reins Engineering 全部课程
| 课程 | 标题 |
|---|---|
| 第1课 | 如何指挥AI |
| 第2课 | 如何不信任AI |
| 第3课 | 不会崩溃的应用 |
| 第4课 | 将决策移出代码 |
| 第5课 | 有缰绳的AI |
| 第6课 | 通过就锁定 |
| 第7课 | 翻转谄媚 |
| 第8课 | 智能体的工厂 |
| 第9课 | 代码之外的自动化 |
| 第10课 | 数据的法则 |
参考资料
- ZenFlow基准测试 — 69分钟完成32个端点、14个表、47个Hurl请求。11/11阶段通过。添加功能时无速度下降
- yongol agent模型实验 — Grok 4.3(首次0错误)、Gemini 2.5 Flash(1次反馈0错误)、Gemma4 4.5B(1次反馈0错误)、Qwen3 8B(1次反馈0错误)
- yongol validate — 287条规则,10个SSOT间交叉验证
- 中型SaaS代码规模对比 — SSOT 12,500行 vs 实现代码10万行(上下文约10倍压缩)