最近在关注大模型应用层的动态时,一个方向引起了我的注意:让编码 AI 通过自我改进的方式,用远小于 GPT-5 的参数量,在编程任务上达到接近甚至超越的效果。这类项目标题往往很吸引眼球,比如“仅用 117 倍更少参数实现 93.3% 正确率”,但真正落实到工程上,它背后的技术思路、训练策略和评估方式才是我们需要理解的重点。
这篇博文我会拆解一下“自我改进编码 AI”的核心逻辑,聊一聊它和传统大模型参数竞赛的差异,并给出一个可运行的简化实验思路,帮助你把“自我改进”这个概念真正落到代码层面。
1. 背景:编码 AI 的“参数军备竞赛”与效率拐点
1.1 大模型做编码任务的现状
当前的编码 AI 赛道,基本被两类方案主导:
- 通用大模型 + 代码能力微调:比如 GPT-4、GPT-5、Claude 系列,它们通过海量文本和代码训练,具备强大的生成能力。
- 专门化的代码模型:比如 CodeLlama、DeepSeek-Coder、StarCoder,它们在通用模型基础上,使用代码语料做继续预训练和监督微调。
这套范式的一个显著问题是:模型越大,效果越好,但成本也越高。动辄上千亿参数的大模型,无论是训练、微调还是推理,都需要昂贵的 GPU 资源。很多开发者和中小团队根本没有能力私有化部署这类模型,只能依赖 API 调用,这就带来了数据安全、成本控制、响应延迟等一系列问题。
1.2 什么是“自我改进 AI”
“自我改进 AI”并不是一个新概念,但随着大模型能力的提升,它有了更实际的落地方式。
简单说,自我改进 AI 是指模型能够利用自身生成的输出、反馈信号甚至执行结果,自动优化自身行为的一类方法。它不依赖大量人工标注数据,而是通过自动化评估、搜索、重试和再训练,形成“生成 -> 评估 -> 学习”的闭环。
在编码任务中,这种闭环尤其有价值,因为代码的“正确与否”是可以被自动验证的:单元测试通过了就是通过,没通过就是没通过。这种确定性的反馈信号,正好可以作为自我改进的“老师”。
1.3 为什么“更少参数”能实现高正确率
标题中的“93.3% 正确率”和“117 倍更少参数”听起来很夸张,但它的底层逻辑并不神秘:
- 任务窄化:如果只专注于特定编码任务(比如代码补全、Bug 修复、测试生成),而不是追求通用的对话能力,那么较小的模型也能做得很好。
- 数据质量优先:通过自我改进循环产生的数据,往往比全网爬取的语料更具针对性,模型在特定分布上的表现可以大幅提升。
- 高效的蒸馏与微调:小模型可以从大模型的输出中学习,也可以从测试反馈中直接学习,从而逼近大模型的效果。
所以,这篇文章并不是要说服大家弃用 GPT-5,而是想展示一个工程方向:在特定垂直场景下,如何利用自我改进机制,把模型体积、成本和效果调整到一个更优的平衡点。
2. 核心概念拆解:自我改进编码 AI 的三大件
要理解这类系统的实现,需要先掌握三个核心组件:生成器(Generator)、评估器(Evaluator)和优化器(Optimizer)。
2.1 生成器:代码生成的起点
生成器就是一个代码语言模型,它接收自然语言描述或部分代码上下文,输出代码片段。在自我改进系统中,生成器不一定是当前最强的模型,它可以是一个参数量较小的模型。
关键点在于:生成器需要具备一定的“多样性”输出能力。如果同一个问题每次都只生成同一个答案,那自我改进就没有探索空间。所以,在采样环节我们通常会设置较高的温度参数(temperature),让模型生成多个候选方案。
# 使用 transformers 库调用生成器模型 from transformers import AutoModelForCausalLM, AutoTokenizer import torch model_name = "your-code-model" # 替换成实际模型路径 tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16, device_map="auto") def generate_candidates(prompt: str, num_return_sequences: int = 5, max_new_tokens: int = 200): inputs = tokenizer(prompt, return_tensors="pt").to(model.device) outputs = model.generate( **inputs, max_new_tokens=max_new_tokens, num_return_sequences=num_return_sequences, do_sample=True, temperature=0.8, top_p=0.95, ) candidates = [tokenizer.decode(output, skip_special_tokens=True) for output in outputs] return candidates这段代码的目的是让模型对一个 prompt 输出 5 个不同的候选代码。这里的核心配置是do_sample=True和temperature=0.8,它们控制着生成结果的随机性和多样性。
2.2 评估器:如何判断代码“好”与“坏”
评估器是自我改进循环中最关键的一环。它的职责是给生成器产出的每一个候选代码打分。打分方式有很多种:
- 单元测试通过率:最直接的评估。如果代码能通过全部预置单测,得满分;通过的越多,分越高。
- 静态检查:使用 pylint、flake8、mypy 等工具检测代码质量和类型错误。
- 对拍测试:将生成结果和标准答案比较,但这种情况较少见。
实际工程中,我们通常优先使用单元测试通过率作为主要信号,因为它最接近用户真实需求。
下面是一个简化的评估器实现:
import subprocess import tempfile import os def evaluate_with_tests(code: str, test_code: str) -> dict: with tempfile.TemporaryDirectory() as tmp_dir: solution_path = os.path.join(tmp_dir, "solution.py") test_path = os.path.join(tmp_dir, "test_solution.py") with open(solution_path, "w", encoding="utf-8") as f: f.write(code) with open(test_path, "w", encoding="utf-8") as f: f.write(test_code) result = subprocess.run( ["python", "-m", "pytest", test_path, "--tb=short", "-q"], capture_output=True, text=True, timeout=30, ) passed = "passed" in result.stdout # 这里简单的解析,实际工程中用 pytest 的 JSON 报告会更准 return {"passed": passed, "output": result.stdout, "stderr": result.stderr}这个函数的核心思路是:把生成器的输出和测试代码写入临时目录,然后通过 pytest 子进程执行,最终返回是否通过。要注意的是,subprocess.run中的timeout参数很重要,它可以防止模型生成的死循环代码卡住整个评估流程。
2.3 优化器:从反馈中学习
优化器负责根据评估器返回的分数,更新生成器的参数,使其下一次生成更优秀的代码。常见方法有:
- 强化学习(RLHF / RLAIF):将评估器的得分作为奖励信号,使用 PPO 等算法更新模型。这个方法效果最好,但工程实现复杂。
- 拒绝采样微调(RFT):只保留高分输出,丢弃低分输出,在这些高质数据上做监督微调。这个方法实现简单,且效果稳定。
- 直接偏好优化(DPO):类似 RFT,但用的是偏好对(比如高分代码 vs 低分代码),不需要单独训练奖励模型。
对于大多数工程团队,拒绝采样微调是最容易上手的方案。它的流程非常清晰:先生成一批候选,过滤掉低分样本,然后用过滤后的数据继续训练模型。
3. 环境准备与版本说明
在开始实战之前,我们需要一个可复现的实验环境。这里以 Python 3.10 + PyTorch 2.x 为例,核心依赖如下:
| 依赖库 | 作用 | 版本建议 |
|---|---|---|
| torch | 深度学习框架 | 2.1.0 及以上 |
| transformers | 加载和调用语言模型 | 4.36.0 及以上 |
| datasets | 数据集处理 | 2.16.0 及以上 |
| pytest | 运行单元测试 | 7.4.0 及以上 |
| peft | 参数高效微调 | 0.7.0 及以上 |
安装命令如下:
conda create -n code-self-improve python=3.10 -y conda activate code-self-improve pip install torch==2.1.0 transformers==4.36.0 datasets==2.16.0 pytest==7.4.0 peft==0.7.0需要注意的是,如果你使用的是最新版本的 transformers,某些 API 可能有变化。文中代码以 4.36.0 版本为例验证过,如果你用的是更新的版本,遇到报错可以查看官方文档调整参数。
为了让大多数读者能够实际运行,这里不要求你本地部署一个巨大的模型。我们可以使用参数量在 1B 左右的代码模型(比如 DeepSeek-Coder-1.3B 或 CodeLlama-7B 的量化版),如果你的 GPU 显存有限,也可以使用transformers的 8bit 加载模式。
4. 完整实战:构建一个简化版自我改进编码系统
接下来,我们一起动手实现一个简化但完整的自我改进编码 AI 循环。任务设定为:给定一个函数描述,模型生成代码实现,通过单元测试进行验证,最后用筛选后的数据微调模型。
4.1 创建项目结构
首先组织好项目目录:
code-self-improve/ ├── data/ │ ├── train_seed.jsonl # 初始种子任务(问题 + 单元测试) │ └── generated_pairs.jsonl # 自我改进产生的训练数据 ├── src/ │ ├── generator.py # 生成器封装 │ ├── evaluator.py # 评估器封装 │ ├── self_improve.py # 主循环 │ └── finetune.py # 微调脚本 ├── output/ │ ├── model/ # 保存微调后的模型 │ └── logs/ # 训练日志 ├── requirements.txt └── README.md初始种子任务是一个 JSONL 文件,每一行是一个训练样本,包含两个字段:prompt和test_code。下面是一个示例:
{"prompt": "编写一个 Python 函数,输入两个整数,返回它们的和。函数名:add", "test_code": "from solution import add\n\ndef test_add():\n assert add(2, 3) == 5\n assert add(0, 0) == 0\n assert add(-1, 1) == 0\n"}这里的设计思路是:prompt给模型下达任务描述,test_code提供了验证标准。模型生成的代码会与test_code结合,用 pytest 自动验证。
4.2 实现生成器模块
生成器模块的职责非常明确:加载基础代码模型,为每个 prompt 生成多个候选代码。
# 文件路径:src/generator.py from transformers import AutoModelForCausalLM, AutoTokenizer import torch import json from tqdm import tqdm class CodeGenerator: def __init__(self, model_name: str, device: str = "auto"): self.tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) self.model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.float16, device_map=device, trust_remote_code=True, ) self.model.eval() def generate_candidates(self, prompt: str, num_seq: int = 5) -> list[str]: """ 为单个 prompt 生成多个候选代码。 这里我们使用了 do_sample=True 来增加生成多样性, 实际使用中可以根据任务难度调整 temperature 和 top_p。 """ messages = [ {"role": "user", "content": prompt} ] input_text = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) inputs = self.tokenizer(input_text, return_tensors="pt").to(self.model.device) with torch.no_grad(): outputs = self.model.generate( **inputs, max_new_tokens=256, do_sample=True, temperature=0.8, top_p=0.9, num_return_sequences=num_seq, pad_token_id=self.tokenizer.eos_token_id, ) candidates = [] for output in outputs: decoded = self.tokenizer.decode(output, skip_special_tokens=True) # 剥离掉指令部分,只保留模型生成的代码 response = decoded.split("<|assistant|>")[-1].strip() candidates.append(response) return candidates def batch_generate(self, tasks: list[dict], num_seq: int = 5) -> list[dict]: """ 批量生成候选代码,并把结果以列表形式保存。 返回格式:[{"prompt": ..., "candidates": [...], "test_code": ...}, ...] """ results = [] for task in tqdm(tasks, desc="Generating candidates"): candidates = self.generate_candidates(task["prompt"], num_seq=num_seq) results.append({ "prompt": task["prompt"], "test_code": task["test_code"], "candidates": candidates, }) return results这里有两个值得注意的细节:
- 使用了
apply_chat_template来构造输入,这样可以让模型更好地理解“用户要求它写代码”的任务语义。 - 生成的代码使用
split("<|assistant|>")切分,这是因为 Chat 模型会在输出中加入角色标记,需要去掉。
4.3 实现评估器模块
评估器的目标是把生成结果中可通过测试的代码筛选出来。
# 文件路径:src/evaluator.py import subprocess import tempfile import os import json from typing import Optional class CodeEvaluator: def __init__(self, timeout: int = 30): self.timeout = timeout def evaluate(self, code: str, test_code: str) -> float: """ 返回 0 或 1 的得分。0 表示测试未通过,1 表示通过。 在真实场景中,可以根据多个测试用例的通过比例返回连续分数。 """ code = self._extract_code(code) with tempfile.TemporaryDirectory() as tmp_dir: solution_path = os.path.join(tmp_dir, "solution.py") test_path = os.path.join(tmp_dir, "test_solution.py") with open(solution_path, "w", encoding="utf-8") as f: f.write(code) with open(test_path, "w", encoding="utf-8") as f: f.write(test_code) try: result = subprocess.run( ["python", "-m", "pytest", test_path, "--tb=short", "-q"], capture_output=True, text=True, timeout=self.timeout, ) return 1.0 if "passed" in result.stdout else 0.0 except subprocess.TimeoutExpired: return 0.0 def _extract_code(self, raw_code: str) -> str: """ 从模型原始输出中提取可执行代码。 有些模型的输出包含 Markdown 代码块标记,需要清理。 """ raw_code = raw_code.strip() if raw_code.startswith("```python"): raw_code = raw_code.replace("```python", "").replace("```", "", 1) elif raw_code.startswith("```"): raw_code = raw_code.replace("```", "", 1) return raw_code.strip() def filter_best_samples(self, generated_data: list[dict], top_k: int = 1) -> list[dict]: """ 从生成的多个候选中,选出每个问题下得分最高的样本。 返回的列表可以直接用于后续微调。 """ best_samples = [] for item in generated_data: best_score = -1.0 best_code = None for candidate in item["candidates"]: score = self.evaluate(candidate, item["test_code"]) if score > best_score: best_score = score best_code = candidate if best_code is not None: best_samples.append({ "prompt": item["prompt"], "chosen": best_code, "score": best_score, }) return best_samples_extract_code这个辅助函数非常重要。因为语言模型经常会把生成的代码包装在 Markdown 代码块里,如果直接写入 pytest 文件会语法报错,通过这段简单清理可以极大提升评估成功率。
4.4 实现自我改进主循环
主循环将生成器和评估器串联起来,形成一个完整的迭代流程。
# 文件路径:src/self_improve.py import json import random from generator import CodeGenerator from evaluator import CodeEvaluator def load_seed_data(path: str) -> list[dict]: with open(path, "r", encoding="utf-8") as f: return [json.loads(line) for line in f] def save_generated_data(data: list[dict], path: str): with open(path, "w", encoding="utf-8") as f: for item in data: f.write(json.dumps(item, ensure_ascii=False) + "\n") def run_self_improve_loop( seed_data_path: str, output_path: str, model_name: str, num_iterations: int = 3, num_candidates: int = 8, ): generator = CodeGenerator(model_name) evaluator = CodeEvaluator(timeout=30) current_data = load_seed_data(seed_data_path) for iteration in range(num_iterations): print(f"Iteration {iteration + 1}/{num_iterations}") # 1. 生成候选代码 generated = generator.batch_generate(current_data, num_seq=num_candidates) # 2. 评估候选代码,筛选最优样本 best_samples = evaluator.filter_best_samples(generated, top_k=1) # 3. 保存本轮数据 save_generated_data(best_samples, output_path + f"_iter{iteration + 1}.jsonl") # 4. 下一轮使用通过率更高的样本(含 prompt)作为新的种子输入 # 注意:当前简化版本不直接做模型微调,而是将数据累积用于后面的 finetune 脚本 current_data = best_samples print(f" Best samples count: {len(best_samples)}") if __name__ == "__main__": run_self_improve_loop( seed_data_path="data/train_seed.jsonl", output_path="data/generated_pairs", model_name="deepseek-ai/deepseek-coder-1.3b-instruct", num_iterations=3, num_candidates=8, )这个主循环是最小可执行版本。它的每一轮都会:
- 用当前模型为每个任务生成 8 个候选代码。
- 让 pytest 评估每段代码是否通过。
- 保留每个任务下的最佳代码。
- 将最佳样本作为下一轮的输入任务(这里的复用一个比较简单的策略,实际项目中通常会在这些样本上微调一次模型)。
4.5 实现微调脚本
微调是“自我改进”中真正让模型发生变化的一步。这里使用 PEFT 的 LoRA 方法,在消费级显卡上也能跑得动。
# 文件路径:src/finetune.py import json import torch from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForSeq2Seq, ) from peft import LoraConfig, get_peft_model, TaskType from datasets import Dataset def load_training_data(file_path: str): samples = [] with open(file_path, "r", encoding="utf-8") as f: for line in f: item = json.loads(line) if item.get("chosen") and item.get("prompt"): samples.append({ "prompt": item["prompt"], "code": item["chosen"], }) return samples def format_example(prompt: str, code: str) -> str: messages = [ {"role": "user", "content": prompt}, {"role": "assistant", "content": code}, ] return messages def finetune_with_lora( base_model_name: str, train_data_path: str, output_dir: str = "output/model" ): tokenizer = AutoTokenizer.from_pretrained(base_model_name, trust_remote_code=True) if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token model = AutoModelForCausalLM.from_pretrained( base_model_name, torch_dtype=torch.float16, device_map="auto", trust_remote_code=True, ) lora_config = LoraConfig( task_type=TaskType.CAUSAL_LM, r=8, lora_alpha=16, lora_dropout=0.1, target_modules=["q_proj", "v_proj"], ) model = get_peft_model(model, lora_config) samples = load_training_data(train_data_path) texts = [] for sample in samples: messages = format_example(sample["prompt"], sample["code"]) text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False) texts.append(text) # 这里只用最简单的文本映射,实际工程中可以加入截断和分批处理 dataset = Dataset.from_dict({"text": texts}) def tokenize_function(examples): return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=512) tokenized_dataset = dataset.map(tokenize_function, batched=True, remove_columns=["text"]) training_args = TrainingArguments( output_dir=output_dir, per_device_train_batch_size=1, gradient_accumulation_steps=8, num_train_epochs=3, learning_rate=2e-5, fp16=True, save_total_limit=2, logging_steps=10, report_to="none", ) data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, padding=True, label_pad_token_id=-100) trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_dataset, data_collator=data_collator, ) trainer.train() model.save_pretrained(output_dir) tokenizer.save_pretrained(output_dir) if __name__ == "__main__": finetune_with_lora( base_model_name="deepseek-ai/deepseek-coder-1.3b-instruct", train_data_path="data/generated_pairs_iter1.jsonl", output_dir="output/model", )这段代码的核心思路是:从之前保存的高分样本中构造训练数据,让模型学会“看到用户需求,直接输出能通过测试的代码”。
4.6 运行与预期效果
在命令行中依次执行:
# 1. 运行自我改进循环 cd src python self_improve.py # 2. 用第一轮筛选出的数据微调模型 python finetune.py预期你会看到类似下面的日志输出:
Iteration 1/3 Best samples count: 12 Iteration 2/3 Best samples count: 11 Iteration 3/3 Best samples count: 13实际效果因人而异,取决于基础模型和任务复杂度。如果你把生成候选数量调大(比如 16 个),通过率通常会更高,因为搜索空间变大,模型更有可能找到正确答案。
5. 设计评估指标:如何衡量“超越 GPT-5”
“超越 GPT-5”这种表述,在工程上不能只看单一任务的准确率。我们需要一套多维度评估体系。
5.1 准确率(Pass@K)
最核心的指标是 Pass@K,它表示:对于一个问题,模型生成 K 个答案,如果至少有 1 个通过测试,就算成功。
计算公式为:
Pass@K = 1 - (C(n - c, k) / C(n, k))其中:
n是生成的候选总数。c是其中通过的候选数。k是允许的尝试次数。
这个公式的含义是:考虑所有错误的候选组合中,没有正确结果的概率。实际计算时,为了数值稳定性,通常使用循环累加而不是直接计算组合数。
def pass_at_k(n: int, c: int, k: int) -> float: """ 计算 Pass@K 指标 n: 生成的候选总数 c: 其中通过的候选数 k: 允许尝试的次数 """ if n - c < k: return 1.0 return 1.0 - (lambda x: x)( # 占位,下面用循环实现更数值稳定 __import__("math").comb(n - c, k) / __import__("math").comb(n, k) ) def stable_pass_at_k(n: int, c: int, k: int) -> float: if n - c < k: return 1.0 import math total = 0.0 # 遍历所有错误样本数 i for i in range(c + 1): total += math.comb(c, i) * math.comb(n - c, k - i) / math.comb(n, k) # 这个版本表示至少一个正确,简化可使用 1 - comb(n-c,k)/comb(n,k) return 1.0 - math.comb(n - c, k) / math.comb(n, k)Pass@K 的核心价值在于:它衡量的是候选代码中包含正确解集的概率,更贴近实际开发中“模型生成多个方案,开发者挑一个用”的使用方式。
5.2 与 GPT-5 比较的注意点
如果我们拿到一个项目的信息,说“93.3% 的正确率”,第一步不应该兴奋,而应该确认:
- 测试集有多少道题?是小规模(100 题内)还是大规模(1000 题以上)?
- 题目的难度分布如何?是基础语法题还是复杂算法题?
- 是否限制了模型访问外部库和网络?
- 有没有做过污染检测(即训练数据是否包含测试题)?
如果测试集规模很小,或者题目偏简单,准确率很容易虚高。这也是为什么大模型标准评测集(如 HumanEval、MBPP)的意义在于提供可横向比较的基准。
5.3 成本指标
除了准确率,我们应该重点评估“单位成本的正确率”。
可以设计这样的指标:
性价比分数 = 准确率 / (模型参数量 × 单次推理成本)这个指标虽然不严谨,但能反映出小模型在垂直任务上的价值:用更少的 GPU 资源、更低的延迟,达到可用的正确率。对于企业来说,这往往比“极限准确率”更有意义。
6. 常见问题与排查思路
在实际运行自我改进流程时,大家容易遇到下面几类问题。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
pytest 报错ModuleNotFoundError | 生成的代码缺失 import 语句 | 在 prompt 中加入示例代码片段,约束模型输出完整可执行代码 |
| 所有候选都得了 0 分 | 模型生成的不是纯代码,而是包含解释文字 | 调整_extract_code解析逻辑,或者在后处理时只保留首个代码块 |
| 微调后模型效果没有提升 | 训练数据太少或重复度过高 | 增加种子任务数量,并尝试每轮从更多候选中收集正样本 |
| 显存不足 | 模型过大或序列过长 | 使用 8bit 量化加载、减小max_length、降低per_device_train_batch_size |
| 生成向量慢 | 候选数过多、模型推理效率低 | 使用 vLLM 等推理加速框架,或者并行生成 |
| 部分测试超时 | 模型生成了死循环代码 | 在评估器中增加超时限制,并记录失败原因,后续在 prompt 中强调效率要求 |
排查顺序一般遵循:先看代码是否纯代码、再看单测是否可运行、后看模型生成的是不是内容截断,最后再考虑训练策略。
6.1 示例:如何处理“模型生成了解释文字”
这是最常见的问题。假设模型的输出是:
这是一个简单的加法函数,我们只需要使用 + 运算符即可: def add(a, b): return a + b我们的_extract_code目前只能去掉 Markdown 标记,没法去掉前面的自然语言。一个更稳健的方法是使用正则表达式提取代码块:
import re def extract_python_code(raw_text: str) -> str: # 匹配 ```python ... ``` 代码块 match = re.search(r"```python\n(.*?)```", raw_text, re.DOTALL) if match: return match.group(1).strip() # 如果找不到代码块,尝试提取第一个 def 或 class 定义 lines = raw_text.split("\n") code_lines = [] in_code = False for line in lines: if line.strip().startswith("def ") or line.strip().startswith("class ") or line.strip().startswith("import "): in_code = True if in_code: code_lines.append(line) return "\n".join(code_lines).strip()把这段函数替换到_extract_code中,就可以过滤掉大部分自然语言噪声。
6.2 示例:处理“生成代码不完整”
有时模型生成的代码在中间突然截断:
def add(a, b): return a +这种代码即使是最简单的测试也无法通过。解决办法有三个方向:
- 增加
max_new_tokens,让模型有空间生成完整代码。 - 在 prompt 中强调“输出完整代码,不要省略”。
- 评估时增加语法检查——先用
ast.parse()检查代码是否能被 Python 解析,如果不能就直接打 0 分,避免浪费 pytest 的时间。
import ast def quick_syntax_check(code: str) -> bool: try: ast.parse(code) return True except SyntaxError: return False这样在正式跑用例前先淘汰掉语法错误的样本,可以显著加速筛选过程。
7. 最佳实践与工程建议
7.1 从最小用例集开始
不要把初始数据集一次搞到几千条。先准备 20~50 个典型的、覆盖不同难度的编程题。这样做的好处是:
- 快速验证整个自我改进闭环是否通畅。
- 方便人工检查模型生成质量。
- 训练一轮周期短,便于迭代调参。
等流程稳定后,再逐步扩充到数百、数千题。
7.2 对测试代码进行严格校验
测试代码本身也可能有 bug。如果一个题目描述和测试代码不一致,那么模型再努力也过不了测试。在把测试代码放入种子集之前,建议:
- 先用标准答案跑一遍测试,确保测试代码能通过。
- 用错误的答案测试一次,确保测试代码能“有效失败”。
- 确认测试代码不依赖特定环境变量和文件路径。
7.3 关注 prompt 设计
prompt 设计对生成质量影响巨大。推荐在 prompt 中明确说明:
- 函数签名。
- 输入输出约束。
- 需要的 import 语句。
- 禁止使用的外部模块。
一个更精确的 prompt 示例:
请实现一个 Python 函数 even_or_odd(num)。 - 输入:整数 num - 输出:字符串 "even" 或 "odd" - 如果 num 是偶数,返回 "even",否则返回 "odd" - 不需要额外 import,函数定义必须完整 - 只需要输出函数代码,不要输出解释这种结构化 prompt 能显著降低模型生成无关内容的概率。
7.4 记录每一轮数据状态
在自我改进实验中,数据和训练日志都要有版本记录。推荐使用简单的命名规范:
data/generated_iter{迭代次数}_{模型名}_cand{候选数}.jsonl output/model_{迭代次数}_{日期}/这样可以随时回溯,定位“效果提升到底来自哪一轮的哪些数据”。
7.5 是否真的需要动模型参数
在工程落地时,要评估“微调模型”是否必要。很多时候,通过优化 prompt、调整采样参数、增加候选数量,就能将 Pass@K 提升到可接受水平。只有在这些手段用尽后,才去投入算力做微调。
这是一个成本和收益的平衡决策,不是所有场景都必须“让模型自我进化”。
8. 总结与学习路线
这篇文章从一个宏观的研究方向开始,介绍了“自我改进编码 AI”的核心概念:生成器、评估器、优化器三件套,并给出了一个完整的、可运行的简化实验代码。你可以在自己的机器上复现这套流程,也可以在此基础上扩展:
- 如果你对数据感兴趣,可以尝试使用 MBPP、HumanEval 或其他公开代码数据集做种子任务。
- 如果你对训练感兴趣,可以把拒绝采样微调替换成 DPO 或 PPO,进一步提升模型上限。
- 如果你对工程部署感兴趣,可以将微调后的 LoRA 模型导出为 ONNX 或使用 vLLM 进行高效推理。
关于“超越 GPT-5”的说法,我的看法是:在通用编码能力上超越前沿大模型并不容易,但在特定领域、特定任务、特定约束下,通过自我改进机制,完全可以用小得多的模型成本逼近甚至超过通用大模型的平均表现。这条路对资源有限的团队来说更有现实意义。
下一步建议你动手做这样两件事:
- 准备 20 道简单的编程题,运行上面的完整流程,体验一次自我改进的闭环。
- 观察第一轮生成样本里,有哪类代码是模型“原先生成不出来、但通过筛选后成功保留”的,这会帮你理解自我改进的数据价值。
如果你在复现过程中遇到问题,欢迎在评论区留言交流。编码 AI 的方向还远未定型,自我改进可能是让它真正走向工程化的重要一环。