本节摘要:代码生成任务是 DSPy 家族里最"幸运"的场景——它拥有确定性裁判:代码要么跑得过测试要么跑不过。本节讲如何把这份红利兑换成编译优势:以执行通过率为指标的自举闭环、ProgramOfThought 模块的适用边界、数据合成任务的实操案例,以及把生成代码当不可信输入处理的安全纪律。
难点有两重。其一,容错率低:自然语言答案差一个措辞还算对,代码差一个字符就是异常。其二,正确性难以静态判断:代码"看起来对"毫无意义,只有执行结果作数。而红利恰恰长在难点上——因为必须执行,所以有确定性裁判;因为有确定性裁判,指标的模糊地带归零:exact_match 在问答任务里被同义表述困扰的问题,在代码任务里被测试用例彻底解决。这让代码生成成为自举机制效果最好的任务类型:程序自己生成的代码,测试跑过了就是合格的示范,没有任何争议空间。
用 ProgramOfThought 或普通 Predict 生成代码,外部执行器跑测试,结果回馈给指标——闭环的骨架如下:
import dspy class WriteFunction(dspy.Signature): """根据需求描述写出 Python 函数,只输出函数定义。""" requirement = dspy.InputField(desc="功能需求与边界条件说明") function_code = dspy.OutputField(desc="完整的 Python 函数定义") class CodeSolver(dspy.Module): def __init__(self, runner): super().__init__() self.write = dspy.ChainOfThought(WriteFunction) self.runner = runner # 沙箱执行器:跑测试用例 def forward(self, requirement, test_cases): pred = self.write(requirement=requirement) passed = self.runner.run(pred.function_code, test_cases) if not passed: # 带失败信息重写一次:把报错与用例输出回喂给模型 pred = self.write( requirement=requirement + "\n上次尝试未通过测试,失败信息:" + passed.failure_log ) return dspy.Prediction(code=pred.function_code, passed=passed.ok)
指标直接消费执行结果:
def code_metric(pred, example, trace=None): # 执行器在外部已跑完;这里只比对通过状态 return bool(pred.passed) == bool(example.should_pass)
编译时这个闭环威力翻倍:自举过程中,每个训练样本都是"需求加测试用例",程序生成的代码跑过测试的轨迹回收为示范——模型见到的每条范例都是"真实通过测试的代码",这种示范质量是人工标注无法企及的(人标注答案是快的,人写带测试的完整代码示范反而慢)。实测经验上,五十条带测试的需求样本,一轮自举后通过率常能从小模型的四成上下拉到七成以上;这是所有任务类型里自举收益最陡峭的曲线。
顺带把模块的适用边界讲清。ProgramOfThought 让模型生成代码、由运行时执行取结果,它的正确定位是"确定性计算的外包":日期推算、单位换算、集合运算、统计汇总——凡是模型"心算"容易错而代码"实算"必然对的场景。它的边界同样明确:需要常识判断与语义理解的部分不该塞给代码("这段评论的情感倾向"不是可执行的算式);执行环境必须受控(沙箱、超时、资源限额),因为生成的代码是不可信输入——这一点无论用于推理还是交付都成立。
数据合成是代码生成的一个高价值变体:让模型生成"产生指定特征数据的程序"而不是数据本身。比如需要一万条不同形态的测试数据,与其让模型直接吐数据(重复率高、可控性差),不如让它写生成函数(参数可控、可复现、可审计)。DSPy 化的写法与上面同构:需求签名描述数据特征,执行器跑函数抽样,指标校验样本特征分布。这种"要鱼不如要渔"的思路在测试构造、仿真数据、脱敏样本等场景都有直接应用。
本节的安全纪律值得单独成段。模型生成的代码必须当作不可信输入处理,三条底线:执行环境隔离(容器或进程级沙箱,禁文件系统与网络访问,除非任务明确需要且另行管控);资源限额(超时与内存上限必须设置,防止生成代码里的意外死循环拖垮服务);输出审查(交付给下游的代码过一遍静态检查——禁用危险调用、强制类型注解,这类检查用规则实现,别用模型自查自)。数据合成场景的额外一条:合成样本使用前抽样人工复核,模型生成函数的"特征理解偏差"会批量复制到每个样本,人审是唯一的止损点。
这三条底线与 5.3 节的断言机制衔接自然:把"静态检查通过"做成出口断言,把"执行超时"做成回溯触发,安全约束就融入了程序结构而非依赖人的自觉。还有一个工程细节值得记录:执行器要把测试失败信息做成结构化的回喂材料——报错类型、失败用例、期望与实际的差值,按固定格式拼进重写提示。失败的回喂质量直接决定重写的成功率,模糊的"没通过"三个字与详细的失败报告,在重写通过率上能差出数倍;这一条与 4.4 节的自举逻辑同源——模型进步的原料永远是高质量的反馈信号。