文章定义了文档生成的「可起草」与「人类必须拥有」的边界,提出用分类规则替代风格偏好,并在合并环节强制执行验证门禁,防止模型幻觉内容进入代码库。
当生成变得免费时,文档的真正成本转移到了验证环节。模型可以在几秒钟内起草一页内容,但人类仍然必须判断每个声明是否可以安全发布。因此,有意义的问题不是你能够生成多少词,而是一份文档的哪些部分在发布前必须由人类负责。本文定义了这条所有权边界,然后给出一个强制执行它的合并门禁。
起草是一次通过;验证需要对每个声明进行判断
生成是一次性通过,而验证是对每个声明的判断,两者永远无法同比例扩展。一份零成本的草稿仍然需要被阅读、被对照代码检查、被判断准确性,这和耗时数小时的草稿毫无区别。生成的文本额外增加了一项任务:你必须验证验证者,因为模型无法将自己的陈述对照你的代码库来确认其正确性。保持审查理智的唯一方法是从一开始就限制模型被允许编写的内容。
下面的分类遵循一条结构规则而非风格偏好。当自动化检查能够确认其声明时,文档章节是可起草的;当正确性依赖于脚本无法评估的判断、政策或经验时,它就变成了人类拥有的。函数签名可以通过解析源码来验证,因此模型可以起草它。弃用承诺是关于你将支持什么的政策决定,因此模型可以收集相关事实,但不能撰写最终陈述。
可起草的和人类拥有的
将这张表读作一场协商,而非固定答案。没有专职安全审查员的团队应该将更多章节移入人类拥有的列。有黄金路径示例且没有公开 API 的团队可能完全放弃参考框架。永远不应改变的是规则本身:能廉价验证的内容交给模型起草,只能由人类判断的内容由人类拥有。
片段起草循环
只有边界在第一次提示之前就已设定,这条边界才能发挥作用,因此工作流从计划开始,而不是从请求开始。下面的每个步骤都假设你使用 MonkeyCode 的免费模型访问来生成片段草稿,使用 MonkeyCode 的免费服务器选项来执行编排步骤。
披露:本文是 MonkeyCode 产品推广的一部分。
无论工作流还是门禁都不依赖这些选择;它们只是让大量小型迭代变得可负担,而这恰恰是接下来五个步骤所需要的。
先写计划。列出目标文档的每个章节,并使用上面的表格将每个章节标记为模型或人类。在计划存在之前不要打开编辑器。
一次起草一个片段。每个片段发送一个小型提示,而不是一个请求生成整篇文档,这样可以使每个声明保持独立和可测试。
廉价地验证可起草的片段。在 CI 中运行示例,对生成的签名与源码进行 diff,并捕获命令输出,而不是信任叙述性文字。
将人类拥有的章节转换为问题存根。将每个人类拥有的章节转化为其开放问题,使所有权变成一个枚举列表而非模糊指令。
只有当门禁通过时才能合并。在 CI 中运行下面的检查器,并将任何未解决的人类拥有存根视为构建失败。
产物:计划契约和合并门禁
下面的脚本接受一个 JSON 计划,创建片段草稿和人类问题存根,然后阻止任何仍包含未解决的人类拥有章节的合并。它仅使用标准库,而 draft() 函数是你所配置端点的一个占位符,这使得代码对自身无法做到的事情保持诚实。
{
"sections": [
{
"id": "api_reference",
"owner": "model",
"prompt": "List every public function in src/ with its exact signature."
},
{
"id": "examples",
"owner": "model",
"prompt": "Write three usage examples for the public API."
},
{
"id": "security_notes",
"owner": "human",
"questions": [
"What credentials does this service hold?",
"What is the impact if a single token leaks?"
]
},
{
"id": "deprecation_policy",
"owner": "human",
"questions": [
"Which client versions stay supported this quarter?",
"What is the migration path for the older clients?"
]
}
]
}
#!/usr/bin/env python3
'''Enforce section ownership before a single token is generated.'''
import json
import pathlib
import sys
def load_plan(path: str) -> dict:
return json.loads(pathlib.Path(path).read_text())
def draft(section: dict) -> str:
# Replace with a call to the model endpoint you configured.
return '<!-- draft:' + section['id'] + ' -->\n\n(placeholder draft)\n'
def human_stub(section: dict) -> str:
questions = '\n'.join('- [ ] ' + q for q in section['questions'])
return '<!-- owner:' + section['id'] + ' -->\n\n' + questions + '\n'
def assemble(plan: dict) -> str:
parts = []
for section in plan['sections']:
if section['owner'] == 'model':
parts.append(draft(section))
elif section['owner'] == 'human':
parts.append(human_stub(section))
else:
raise SystemExit('unknown owner: ' + section['owner'])
return '\n\n'.join(parts)
def unresolved_owners(plan: dict, doc: str) -> list:
return [
s['id']
for s in plan['sections']
if s['owner'] == 'human' and 'owner:' + s['id'] in doc
]
def main() -> None:
plan = load_plan(sys.argv[1])
doc_path = pathlib.Path(sys.argv[2])
if '--draft' in sys.argv:
doc_path.write_text(assemble(plan))
print('drafted ' + str(doc_path) + '; human stubs are waiting')
return
doc = doc_path.read_text()
missing = unresolved_owners(plan, doc)
if missing:
raise SystemExit('merge blocked: human must own -> ' + ', '.join(missing))
print('ownership contract satisfied')
if __name__ == '__main__':
main()
在两个阶段使用该工具。第一个命令生成片段草稿加上问题存根,第二个命令在每个触及文档的 pull request 上作为 CI 门禁运行。
python doc_contract.py docs/plan.json docs/guide.md --draft
python doc_contract.py docs/plan.json docs/guide.md
失败的运行才是特性
假设示例通过了,签名 diff 通过了,但有人在前往 pull request 的路上仍然忘记了安全说明。门禁失败,并输出一条同时也是行动清单的消息。
$ python doc_contract.py docs/plan.json docs/guide.md
merge blocked: human must own -> security_notes
开发者打开生成的存根,回答两个问题,移除所有权标记,构建就变绿了。这种机制将一条审查评论变成了作者无法悄悄忽略的复选框,因为模型永远无法为人机协同的部分清除标记。这就是门禁的意义:免费草稿在边界处停止,而人类步骤在 diff 中变得可见。
这做不到什么
门禁证明人类编辑了该章节,而非该编辑是正确的,因此一位深思熟虑的审查者仍然很重要。表格中的验证列并未由此最小脚本强制执行,这意味着示例执行和签名 diff 应与它一起放在你现有的 CI 中。draft() 占位符需要一个真实的端点,因此将代码视为起点而非已完成的集成。
拥有纯叙述性文档且没有示例的团队会觉得这套流程很重,因为片段计划增加了第二个需要维护的真实来源。有一位专家作者拥有整篇文档的团队可能更偏好他们现有的模板。任何在无人审查的情况下发布生成文本的人都不应该添加这个,因为门禁分配了工作量但永远不会创造专业知识。
选择下一份触及安全说明或弃用承诺的文档,在任何提示之前写好计划,让门禁提醒每个人各自拥有什么。一份文档、一个计划、一次失败的构建就足以看到边界在起作用。边界为你的文档带来的东西比又一轮提示更多。