第4课

实用技巧 — 知道这些就能指挥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数据模型“在这个表里存这些字段”
sqlcDB查询“用这个SQL查询数据”

定义行为的:

SSOT管什么决策通俗地说
OpenAPIAPI契约“往这个地址发这些数据,收到这个响应”
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)“这个功能。

先用中文读是这样的:

  1. 查询提案
  2. 提案不存在就报"未找到"错误
  3. 查询提案关联的项目
  4. 项目不存在就报"未找到"错误
  5. 检查请求者是否为项目所有者
  6. 检查提案状态是否允许接受
  7. 检查项目状态是否允许接受
  8. 将提案状态改为"已接受”
  9. 给项目分配自由职业者,状态改为"进行中”
  10. 将付款资金托管
  11. 发布"提案已接受"事件
  12. 重新查询更新后的提案并返回

用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错误
Gemma44.5B本地(16GB VRAM)1次反馈后0错误
Qwen38B本地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分钟
+ WebhookWebhook 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端点之墙消失了。

总结 — 本课要记住的

  1. 代码中混着决策和细节。 AI分不清这两者。这是漂移的根本原因。

  2. 把决策移出代码。 10个声明式规范(SSOT)各管一个关注点。10种中8种是行业标准。

  3. operationId是拱心石。 一个名字贯穿10个层。一个Feature Chain就能看到功能的全部地形。

  4. 287条规则捕获跨层矛盾。 现有工具只看自己那层。yongol validate抓的是层与层之间的裂缝。

  5. 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/login
  • specs/db/users.sql — CREATE TABLE users
  • specs/service/auth/login.ssac — @get → @empty → @call → @response
  • specs/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倍压缩)