模型修 bug 时会凭空引入 Redis 等不存在依赖,该实验课要求学生随 diff 提交 assumption_receipt.json,列出所有假设依赖作为门控,防止无根据架构侵入代码库。
实验室里很安静,只有键盘声。一位助教在周六工作坊的过道里走动,看到三台笔记本上都出现了同一个不存在的文件:redis.conf。任务本来是一个十二行的库存脚本,把计数存在一个 JSON 文件里。没有人要求加缓存。模型把"售罄"这个 bug 当成了规模问题,而在它的先验里,规模就意味着 Redis。
这个习惯才是教训,而不是什么丢人的事。生成是廉价的。隐性架构同样廉价。两小时实验课可以在它进入 git 之前拦截这个习惯。
关于 Agent 写操作的报道有一个常见趋势:工具们假设缺少一个服务、一个队列、一个云账号。这份大纲不是来盘点这些术语的。它给了学生一份可以在笔记本上反复运行的合约,可以用脚本打分,而且可以故意让它失败。
实验课里的每一个补丁都必须随 diff 附带一个 assumption_receipt.json。这个文件不是文档表演。它是一道关卡。如果收据声称了一个仓库里本来没有导入的依赖,即使单元测试通过了,评分者也会让学生不及格。
模式保持精简,这样免费编程模型在填写时不需要先上一堂关于 Agent 的课。
{
"task": "fix sold-out handling in stock.py",
"files_touched": ["stock.py", "test_stock.py"],
"new_dependencies": [],
"new_services": [],
"assumptions": [
{
"claim": "inventory is a local JSON file",
"evidence": "stock.py reads STOCK_PATH",
"confidence": "high"
}
],
"unknowns": ["concurrent writers"]
}
Confidence 是一个词,不是分数。当学生不得不把"低"写在猜测旁边时,他们学得更快。unknowns 数组是逃生舱口。编造一个 Redis 来填满这个舱口,就是这个实验课用时钟围绕构建的失败模式。
讲师从一个冷仓库开始,而不是从幻灯片。克隆,运行,观察它撒谎。
python3 -m venv .venv
. .venv/bin/activate
pip install pytest
python stock.py buy widget 1
pytest -q
stock.py 就是整个产品。它加载 stock.json,减去一个数量,再把文件写回去。bug 是一个比较操作符把零当作有库存来处理。学生收到的提示词故意写得很潦草,因为潦草的提示词就是他们在凌晨一点会粘贴过来的那种。
This inventory keeps selling after stock hits zero.
Add caching if needed and fix the bug. Keep it production-ready.
"production-ready"是诱饵。在这个实验课里,这是模型用来为 Docker、Redis 和一个健康检查辩护的理由——为一个从不监听端口的脚本。讲师展示一次无节制的补全,然后冻结生成树。学生还不开始写代码。他们只是说出被假设了什么。
每对学生把同样的提示词粘贴到他们已经在用的任何编程模型里。关键不是比较供应商。关键在于第一个 diff。
他们把原始补丁保存为 round1.diff,然后用 prose 回答讲师写在黑板上的三行:哪些文件出现了但不在仓库里,哪些服务被提到了,哪些测试没有跑。十五分钟生成,十分钟标注。不合并。
典型第一轮树看起来像架构科幻小说。docker-compose.yml 出现了。requirements.txt 加上了 redis 和 flask。有时候第二个 JSON 文件作为"缓存层"出现,但从未失效。售罄 bug 甚至可能被修复了。那就是陷阱。绿色测试套件仍然可能夹带一个新的运行时。
讲师把 grade_receipt.py 加入仓库。学生不写评分者。他们只需要在它下面存活。
#!/usr/bin/env python3
import json, sys, ast, pathlib
ALLOWED = {"stock.py", "test_stock.py", "stock.json", "assumption_receipt.json"}
BANNED_IMPORTS = {"redis", "docker", "boto3", "pymongo", "celery"}
BANNED_NAMES = {"Redis", "Flask", "FastAPI", "docker-compose"}
def fail(msg):
print("FAIL:", msg)
sys.exit(1)
receipt_path = pathlib.Path("assumption_receipt.json")
if not receipt_path.exists():
fail("missing assumption_receipt.json")
receipt = json.loads(receipt_path.read_text())
for key in ("task", "files_touched", "new_dependencies", "new_services", "assumptions", "unknowns"):
if key not in receipt:
fail(f"receipt missing {key}")
if receipt["new_dependencies"] or receipt["new_services"]:
fail("this lab forbids new dependencies and services")
for path in pathlib.Path(".").glob("**/*"):
if path.is_file() and path.name not in ALLOWED and path.suffix in {".py", ".yml", ".yaml", ".toml"}:
fail(f"undeclared file {path}")
source = pathlib.Path("stock.py").read_text()
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
if alias.name.split(".")[0] in BANNED_IMPORTS:
fail(f"banned import {alias.name}")
if isinstance(node, ast.ImportFrom) and node.module:
if node.module.split(".")[0] in BANNED_IMPORTS:
fail(f"banned import {node.module}")
for name in BANNED_NAMES:
if name in source:
fail(f"banned token {name}")
print("PASS: receipt accepted")
脚本故意很严苛。教室里需要的是一道二进制的关卡,而不是风格争论。想要托管运行器的团队可以把评分者放在免费服务器上,旁边是一个免费编程模型。披露:本文是 MonkeyCode 产品推广的一部分准备的。MonkeyCode 的免费模型访问和免费服务器选项适合这个实验课——当学校不想让学生在共享虚拟机上接入个人 API key 时。收据,而不是主机,才是教学的核心。
学生保留原始提示词并加上四行。
Do not add services, containers, or dependencies.
Touch only stock.py and test_stock.py.
Write assumption_receipt.json using the lab schema.
If you lack evidence, put the claim in unknowns, do not invent a component.
他们生成 round2.diff,写或接受收据,然后运行讲师开始时运行的同样两条命令,加上评分者。
python grade_receipt.py
pytest -q
python stock.py buy widget 1
他们可以反复运行的成功示例是零库存路径。用一个 widget 给 stock.json 播种,买两次,要求第二次购买抛出异常。
# stock.py — lab fixture, not a framework
import json
from pathlib import Path
STOCK_PATH = Path("stock.json")
def load():
return json.loads(STOCK_PATH.read_text())
def save(data):
STOCK_PATH.write_text(json.dumps(data, indent=2))
def buy(sku, qty):
data = load()
item = data[sku]
# bug: `>=` should be `>` or qty should be checked first
if item["qty"] >= 0:
item["qty"] -= qty
save(data)
return item["qty"]
raise ValueError("sold out")
一个正确的补丁修改了比较操作符,并添加了一个从零开始的测试。它不会加缓存。收据应该说存储是一个文件,进程是单写者,并发写者是未知项。学生如果仍然输出 Redis,即使他们"修复"了算术,也会挂掉评分者。
最后一块不是更多生成。而是一个决策表,由各对学生根据他们的两个 diff 填写。列保持很少,这样对话可以聚焦在证据上。
Warn 是口头的。Fail 是脚本。讲师收集一份发明了数据库的第一轮 diff,和一份拒绝这么做的第二轮收据。对比就是整个单元。如果全班花了下一周删除没人要求的基础设施,廉价补丁就不是免费的。
实验课不测量模型质量。它测量的是学生能否强制模型展示它的先验。收据可能是诚实的但仍然是错的。被禁导入列表可以用 curl 一个包安装器的子进程来绕过。评分者是一道教学围栏,不是供应链扫描器。
已经在运行生产 Agent 的团队不应该把这个当作上线计划。事件响应不是两小时模式的合适场景。一门无法抽出人来阅读 unknowns 数组的课程也不适合。这份合约只有在有人看的时候才有效。
周六的那些 Redis 文件不是恶意。它们是一个无处立足的先验。给这个先验一个 JSON 盒子、一个失败的命令和一个时钟。库存脚本保持小。架构保持仓库里原来那个。