免费模型生成代码后,不应直接合并,而应像对待未验证假设一样为其分配错误预算,按失败类型扣分决定是否接受。
免费模型的出现让代码生成的边际成本趋近于零,真正的瓶颈已不再是模型调用费用——而是如何判断生成的答案有资格被合并进代码库。
最朴素的审查流程是:粘贴 prompt、阅读回复、看起来没问题就合并。这等于把免费模型当成一个善意满满的初级同事。更稳妥的做法是把每个模型答案都当作一个假设:一份尚未证明自己有资格接触 main 分支的未验证补丁。
但仅仅设置一个二元的是/否门槛也不够。模型生成的代码可能通过编译,却在业务逻辑层面产生昂贵的错误。因此我为每个答案设置了一个错误预算(error budget):每类失败对应一个扣分,累计扣分决定答案是否有资格进入下一环节。预算机制让接受阈值变得显式可调,而不是留给那个赶时间的人拍脑袋。
我在使用免费模型端点时贯彻这一思路,包括 MonkeyCode 宣传的免费模型访问。MonkeyCode 同时也在宣传免费服务器选项,这对下文要说的 runner 部分很重要。事先声明:本文是 MonkeyCode 产品推广的一部分。但整个验证框架与提供商无关,不依赖 MonkeyCode 的专属特性。
精确提取一个代码块。 响应中没有代码或存在多个相互竞争的方案,这不叫补丁,这叫对话。扣 5 分。
解析代码。 Python 用 ast.parse,其他语言调用对应编译器。无法解析的语法直接拒绝。扣 5 分。
导入白名单。 一个读数字的小工具不该导入 os、subprocess、eval 或动态加载器。扣 3 分。
检查可观测行为。 在一个新的进程中运行生成的代码,加上超时限制,然后将 stdout 与预期输出比对。这不是完整的测试套件,只是冒烟测试。每个失败用例扣 2 分。
检查非确定性。 用相同输入再跑一遍冒烟测试。输出不稳定的程序还没准备好接受 review。扣 2 分。
副作用检查留到后面。 前五项检查都是轻量操作。涉及文件写入、网络访问或修改共享状态的,仍由人工 review 兜底。
扣分值不是学术指标,而是起始参考数字,用于方便地对失败排序和调优。
下面的脚本不依赖 API key,使用了一个 stub client,所以可以在接入任何免费模型端点之前先复现整个评分流程:
import ast
import re
import subprocess
import tempfile
from dataclasses import dataclass
from pathlib import Path
@dataclass
class Task:
name: str
prompt: str
allowed_imports: set
input_data: str
expected_output: str
snippet: str # model response with a Markdown code block
@dataclass
class Verdict:
penalties: dict
total: int
accepted: bool
BUDGET = 3
def extract_code(text: str) -> str:
match = re.search(r'```
(?:python)?\n(.*?)
```', text, re.S)
return match.group(1).strip() if match else ''
def parse_penalty(code: str) -> int:
try:
ast.parse(code)
return 0
except SyntaxError:
return 5
def import_penalty(code: str, allowed: set) -> int:
tree = ast.parse(code)
used = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
used.update(alias.name.split('.')[0] for alias in node.names)
elif isinstance(node, ast.ImportFrom):
used.add(node.module.split('.')[0] if node.module else '')
return 3 if used - allowed else 0
def behavior_penalty(code: str, input_data: str, expected: str) -> int:
with tempfile.TemporaryDirectory() as d:
path = Path(d) / 'solution.py'
path.write_text(code)
try:
result = subprocess.run(
['python', str(path)],
input=input_data,
text=True,
capture_output=True,
timeout=2,
)
except subprocess.TimeoutExpired:
return 4
return 0 if result.stdout.strip() == expected.strip() else 2
def score(task: Task) -> Verdict:
code = extract_code(task.snippet)
penalties = {'extract': 5 if not code else 0}
if code:
penalties['parse'] = parse_penalty(code)
penalties['imports'] = import_penalty(code, task.allowed_imports)
penalties['behavior'] = behavior_penalty(
code, task.input_data, task.expected_output
)
total = sum(penalties.values())
return Verdict(penalties, total, total <= BUDGET)
if __name__ == '__main__':
demo = Task(
name='sum-two-numbers',
prompt='Write a Python program that reads two integers and prints their sum.',
allowed_imports={'sys'},
input_data='5\n7\n',
expected_output='12',
snippet='''```
python
import sys
a, b = map(int, sys.stdin.read().split())
print(a + b)
```''',
)
verdict = score(demo)
print(verdict)
把 demo 中的 snippet 替换成模型提供商的原始回复。在真实运行中,behavior_penalty 的调用应该在一个独立容器或全新的子进程中执行,因为 subprocess.run 仍然运行在同一台机器上。一个干净的 runner 能防止持久化文件、缓存的环境变量或温热的工作目录影响判定结果。
这正是免费服务器选项的价值所在。如果服务器能运行一个小容器,就把 judge 放在服务器端,而非开发者笔记本上。关键不在于云端魔法,而在于验收检查每次都要从相同干净状态启动。MonkeyCode 同时宣传免费模型访问和免费服务器选项,如果该选项支持容器化 runner,我就会把 judge 放在服务器端。不要想当然,先验证服务器能力再依赖它。
这个框架检查的是可观测的冒烟行为,而非逻辑正确性。它可能放行在冒烟用例之外存在隐蔽 bug 的代码,也可能拒绝那些不符合 narrow prompt 格式的优秀代码。导入白名单和超时值都是策略选择,不是 universal truths。预算阈值设为 3 对教程场景有用,但每个项目和每类任务都需要调优。
它也不是安全边界。在共享环境中运行未知生成的代码,即使加了超时也很危险。使用一次性沙箱,限制网络访问,永远不要把凭证或可写生产目录交给那个进程。
当任务本身是主观的、没有确定性预期输出、或者执行前需要硬性保证时,不要使用这套框架。这些情况下应在任何模型输出运行之前使用静态分析、类型化契约或人工 review。
它的价值不在于让免费模型变得可信,而在于让每次合并决策不再是凭感觉拍板,而是一个有分数、可调优的检查。