公司动态
代码生成模型评测:SlopCodeBench横向测评Fable、Sol与Kimi K3
最近在整理代码生成模型的评测方案时我发现一个非常现实的问题模型能不能通过单元测试和模型生成的代码能不能直接进生产是完全两回事。过去我们关注“代码能不能跑”现在更值得关注的是“代码会不会让别人崩溃”。围绕这个痛点我基于 SlopCodeBench 做了一轮针对 Fable、Sol、Kimi K3 三个代码生成模型的横向评测并整理成一套可复现的评测流程包含评测集设计、Python 调用脚本、指标统计和结果分析方法适合对模型选型、代码质量评估和 LLM 应用落地感兴趣的开发者参考。1. 背景与核心概念1.1 什么是 Slop CodeSlop Code 这个词最早是从 AI 生成内容领域的 “AI Slop” 延伸过来的。它描述的是一种看起来很合理、但实际质量很低的代码风格。典型特征包括大量无意义的注释、过度嵌套的判空、重复的防御性分支、为了“健壮性”强行吞掉异常、以及生成了一堆与任务无关的工具函数。这类代码最麻烦的地方在于它不是不能运行而是会让项目维护成本持续上升。新人在这类代码上做二次开发往往要花大量时间去分辨哪些逻辑是必要的哪些只是模型“加戏”。代码评审时也需要反复追问“为什么这里要判空”“这个 try-except 到底在保护什么”。当这类代码大量涌入仓库后整个项目的可读性会快速下降。SlopCodeBench 就是针对这个问题设计的一套评测基准。它不追求“模型能不能写出正确算法”而是评估模型在写代码时是否克制、是否精准是否能用最小的代码量完成需求是否避免了无意义的堆砌。1.2 SlopCodeBench 的评测维度在实现评测脚本前需要先明确 SlopCodeBench 核心考察的几个维度。这里我把它拆解为以下六个方向评测维度说明示例表现代码膨胀率生成代码行数与最小实现行数之间的比例最小实现 10 行模型输出 40 行注释质量注释是否有价值还是单纯复述代码注释写出“将 i 加 1”异常处理是否过度捕获异常吞掉错误空 try-except 块无效防御是否有大量无意义的判空、类型检查对象刚创建又开始判空幻觉 API是否调用了不存在的库或函数使用不存在的 pandas 接口结构冗余是否存在重复代码、无关函数为 2 行逻辑单独封装工具类这些维度不能只看“对不对”还要看“有没有必要”。SlopCodeBench 的思路是从工程评审视角出发用规则统计加模型打分结合的方式量化模型的“克制程度”。1.3 为什么要在 SlopCodeBench 上做横向评测当前代码生成模型的评测大多集中在 HumanEval、MBPP 这类“能否通过测试用例”的基准上。这类基准的重要缺陷是只要模型见过相似题目就能通过记忆输出接近标准答案的代码但一旦遇到开放式工程任务模型就容易在正确的基础上疯狂堆砌。做 SlopCodeBench 横向评测的价值在于三个层面对模型选型如果模型正确率高但代码膨胀严重引入生产时会造成长期维护负担。对 Prompt 优化同一模型在不同指令风格下Slop Code 倾向差异明显评测可以帮助定位指令模板的改进方向。对代码评审策略通过评测结果可以知道需要重点检查哪些模型生成的哪些模式。因此下文将围绕 Fable、Sol、Kimi K3 三个模型从环境搭建、评测集构造、脚本编写到结果分析完整展示一次 SlopCodeBench 评测的执行过程。2. 参赛模型Fable、Sol、Kimi K32.1 Kimi K3通用对话类代码模型Kimi K3 是月之暗面发布的新一代 Kimi 大模型主要面向通用对话、长文本理解和复杂推理场景。在代码生成方面Kimi 系列目前的定位是“从需求到实现”的一站式助手因此它的输出通常结构完整、说明充分但也正因为如此如果 Prompt 没有做好约束它很容易生成带大量解释性注释和多余空行的“教学式代码”。Kimi K3 的接入比较方便可以通过 Moonshot 开放平台提供的 OpenAI 兼容接口直接调用。对于只需要做评测验证的场景直接使用 API 是最快的方式不需要准备本地推理资源。2.2 Fable轻量实验型代码模型Fable 是近期社区讨论度较高的开源代码模型之一主打轻量化和快速推理适合本地部署。与 Kimi K3 这类通用大模型不同Fable 的设计目标更偏向“快速补全”和“低延迟生成”因此它的评测价值在于观察轻量模型在 Slop Code 控制上的表现。由于开源模型迭代速度很快本文不指定具体的 checkpoint 版本。读者在实际执行评测时需要以自己本地拉取的权重版本为准并在记录评测结果时一并记录模型版本号这样后续对比才有意义。2.3 Sol专项代码推理模型Sol 从命名和模型定位上看更接近“面向代码任务的专项推理模型”强调在函数级补全、注释修正和代码解释等场景上的表现。这类模型的优势通常在于输出简洁因为训练数据更聚焦代码指令而不是通用对话数据。但专项模型也存在一个潜在问题由于训练数据过于聚焦遇到开放式的工程问题时可能缺少足够的世界知识来判断“什么代码是多余的”。所以 Sol 在 SlopCodeBench 上的表现需要结合具体任务类型来分析不能一概而论。2.4 版本与接入方式说明需要强调一点我撰写本文时这三个模型都处于快速迭代阶段API 地址、模型名称、参数配置都可能发生变化。为了确保评测可复现请务必在评测脚本中记录如下信息模型名称与版本号API 地址或本地权重路径采样参数temperature、top_p、max_tokens评测时间评测所用的基础镜像或 Python 环境版本版本信息不完整时任何评测结论都是脆弱的。下面进入环境准备阶段。3. 评测环境准备3.1 评测目标与评估口径在编写代码前先明确本次评测的目标。我假设的场景是给定一组中等难度的代码生成任务要求模型不能只输出正确结果还要输出简洁、克制、可维护的代码。评估口径分为两层客观规则指标代码行数、注释行数、空行比例、try-except 数量、import 数量、print 调用次数。主观质量指标用一组固定评分标准来评估可读性、健壮性、简洁性和可维护性。其中客观指标可以直接用 Python 脚本统计主观指标则建议采用“规则评分 独立 Judge 模型打分 人工抽检”的方案避免单一来源偏差。3.2 评测集合设计评测集合是整条评测链路的地基。这里我采用 JSON 文件来组织任务每个任务包含描述、语言、约束条件、输入输出示例以及最小实现参考行数。一个任务示例如下{ tasks: [ { id: task_001, title: 两数之和, description: 给定一个整数数组和一个目标值返回数组中两个数相加等于目标值的下标。, language: python, constraints: [ 不能使用内置的 combinations, 函数名必须为 two_sum, 不需要处理输入输出只需返回函数代码 ], input_example: nums [2, 7, 11, 15], target 9, expected_output: [0, 1], minimal_lines: 8 }, { id: task_002, title: 读取配置文件, description: 读取一个 JSON 配置文件返回其中 key 为 timeout 的整数值如果缺失则返回默认值 30。, language: python, constraints: [ 使用标准库实现, 不要打印日志, 函数签名必须为 get_timeout(path: str) - int ], input_example: config.json 内容为 {\timeout\: 10}, expected_output: 10, minimal_lines: 6 } ] }这里有几个关键点。第一constraints是抑制 Slop Code 的一个重要手段模型只有明确知道“不要日志、不要额外解释、不要处理输入输出”时才可能输出精简代码。第二minimal_lines用于后续计算代码膨胀率是 SlopCodeBench 的核心指标之一。第三问题要覆盖不同的代码形态既有算法题也有工程题这样评测结果才更有参考价值。3.3 模型接入方式三个模型我采用了两种接入方式Kimi K3使用 Moonshot 开放平台的 OpenAI 兼容 HTTP 接口通过 API Key 认证。Fable 和 Sol假设采用本地部署方式使用 vLLM 启动 OpenAI 兼容服务。vLLM 启动命令示例# Fable 服务 python -m vllm.entrypoints.openai.api_server \ --model /path/to/fable \ --port 8000 \ --served-model-name fable # Sol 服务 python -m vllm.entrypoints.openai.api_server \ --model /path/to/sol \ --port 9000 \ --served-model-name sol本地部署的好处是评测过程不依赖外部网络且可以在固定版本权重上重复实验。如果你的两个模型也通过 API 提供则只需要修改配置文件中的 base_url 和 model 字段即可。3.4 运行环境本次评测脚本基于 Python 3.10 编写核心依赖只有requests安装命令如下pip install requests如果你使用 vLLM 本地部署还需要保证 GPU 显存足够并提前安装好对应版本的 vLLM。这里不展开 vLLM 的安装细节重点放在评测脚本本身。4. 核心评测脚本实现4.1 模型配置文件为了避免在代码中硬编码 API 地址和模型名我单独维护一个配置文件config.json{ models: { kimi-k3: { api_key_env: KIMI_API_KEY, base_url: https://api.moonshot.cn/v1, model: kimi-k3 }, fable: { api_key_env: EMPTY, base_url: http://localhost:8000/v1, model: fable }, sol: { api_key_env: EMPTY, base_url: http://localhost:9000/v1, model: sol } }, sampling: { temperature: 0.2, max_tokens: 2048, top_p: 0.9 }, samples_per_task: 3 }这里采用环境变量KIMI_API_KEY保存密钥而不是直接写在代码中因为评测脚本可能提交到代码仓库硬编码密钥是安全隐患。本地部署的 vLLM 服务默认不校验 Key所以api_key_env可以写成EMPTY。4.2 模型调用模块模型调用模块统一封装为model_client.py核心逻辑如下# 文件路径bench/model_client.py import json import os import time import requests class ModelClient: def __init__(self, model_cfg: dict): self.model model_cfg[model] self.base_url model_cfg[base_url].rstrip(/) self.api_key os.getenv(model_cfg[api_key_env], EMPTY) def chat(self, messages, temperature0.2, max_tokens2048): url f{self.base_url}/chat/completions headers { Authorization: fBearer {self.api_key}, Content-Type: application/json, } payload { model: self.model, messages: messages, temperature: temperature, max_tokens: max_tokens, } resp requests.post(url, headersheaders, jsonpayload, timeout180) resp.raise_for_status() data resp.json() return data[choices][0][message][content]这里有三个设计要点。第一base_url统一追加/chat/completions兼容 Moonshot 平台和 vLLM 默认路由。第二超时时间设置为 180 秒因为部分代码任务会让模型生成较长内容默认的超时时间可能不够。第三把采样参数放在调用层便于按模型独立调整。调用示例# 文件路径bench/test_client.py from model_client import ModelClient client ModelClient({ model: kimi-k3, base_url: https://api.moonshot.cn/v1, api_key_env: KIMI_API_KEY }) resp client.chat( messages[ {role: user, content: 用 Python 实现冒泡排序只输出代码。} ], temperature0.2, max_tokens1024 ) print(resp)4.3 任务加载与 Prompt 构造任务加载模块负责读取评测集 JSON 文件并且把评测任务拼装成适合代码生成的 Prompt。# 文件路径bench/task_loader.py import json def load_tasks(path: str) - list: with open(path, r, encodingutf-8) as f: data json.load(f) return data[tasks] def build_prompt(task: dict) - str: constraints \n.join(f- {c} for c in task.get(constraints, [])) prompt f请完成下面的编程任务只输出最终代码块不要输出任何解释。 任务标题{task[title]} 任务描述{task[description]} 编程语言{task[language]} 约束条件 {constraints} 输入示例{task.get(input_example, 无)} 期望输出{task.get(expected_output, 无)} 输出格式 {task[language]} 你的代码 return promptPrompt 中明确要求“只输出最终代码块”非常关键。如果不加这个约束很多模型会给出“好的下面是实现代码”这类前置说明这些内容虽然不影响代码运行但会显著干扰代码块抽取和指标统计。 ### 4.4 代码抽取与指标统计 模型返回的内容通常包含 Markdown 代码块标记需要先抽取纯代码部分。同时还要统计客观指标。 python # 文件路径bench/code_metrics.py import re def extract_code_block(text: str) - str: text text.strip() pattern r[a-zA-Z]*\n(.*?) matches re.findall(pattern, text, re.DOTALL) if matches: return max(matches, keylen).strip() return text def compute_metrics(code: str) - dict: lines [line for line in code.splitlines()] non_empty_lines [line for line in lines if line.strip()] comment_lines 0 try_cnt 0 print_cnt 0 import_cnt 0 for line in non_empty_lines: stripped line.strip() if stripped.startswith(#) or stripped.startswith(//): comment_lines 1 if re.search(r\btry\s*:, stripped): try_cnt 1 if re.search(r\bprint\s*\(, stripped): print_cnt 1 if re.search(r^\s*(import|from)\s, stripped): import_cnt 1 return { total_lines: len(lines), non_empty_lines: len(non_empty_lines), comment_lines: comment_lines, try_count: try_cnt, print_count: print_cnt, import_count: import_cnt, }这段代码不需要过于精细因为 SlopCodeBench 的客观指标只用于横向对比。重要的是统一口径所有模型都走同一个抽取和统计流程指标才可比。4.5 综合评测主流程最后把以上模块组合成主脚本run_bench.py。这里采用多次采样取平均值的方式降低单次生成的随机性。# 文件路径bench/run_bench.py import json import os import csv from collections import defaultdict from model_client import ModelClient from task_loader import load_tasks, build_prompt from code_metrics import extract_code_block, compute_metrics def load_config(pathconfig.json): with open(path, r, encodingutf-8) as f: return json.load(f) def run(): config load_config() tasks load_tasks(tasks.json) sampling config[sampling] samples_per_task config[samples_per_task] results [] for model_name, model_cfg in config[models].items(): client ModelClient(model_cfg) for task in tasks: prompt build_prompt(task) for sample_idx in range(samples_per_task): record { model: model_name, task_id: task[id], sample_idx: sample_idx, raw_output: , code: , metrics: {}, minimal_lines: task.get(minimal_lines, 0), } try: raw client.chat( messages[ {role: system, content: 你是一名严谨的软件工程师请用最直接的方式完成任务。}, {role: user, content: prompt}, ], temperaturesampling[temperature], max_tokenssampling[max_tokens], ) record[raw_output] raw code extract_code_block(raw) record[code] code record[metrics] compute_metrics(code) except Exception as e: record[raw_output] fERROR: {e} results.append(record) with open(results.jsonl, w, encodingutf-8) as f: for record in results: f.write(json.dumps(record, ensure_asciiFalse) \n) print(f评测完成共 {len(results)} 条记录已写入 results.jsonl) if __name__ __main__: run()脚本输出results.jsonl每一行是一次模型采样的完整记录包含原始输出、抽取后的代码、指标信息等。保存原始输出非常重要因为后续分析过程中可能需要回看某个模型的真实回复内容。5. 运行与结果分析5.1 运行方式在bench目录下按下面的顺序执行# 1. 安装依赖 pip install requests # 2. 设置密钥 export KIMI_API_KEY你的 Moonshot API Key # 3. 填写评测集将评测任务写入 tasks.json # 4. 启动本地模型服务Fable 和 Sol 需要 # 5. 运行评测 python run_bench.py正常情况下脚本会在结束后输出一行 “评测完成”并生成results.jsonl文件。如果某个模型调用失败记录中会出现ERROR前缀不影响其他记录生成。5.2 输出说明results.jsonl中的一条记录类似于{ model: kimi-k3, task_id: task_001, sample_idx: 0, raw_output: python\ndef two_sum(nums, target):\n ...\n, code: def two_sum(nums, target):\n ..., metrics: { total_lines: 12, non_empty_lines: 10, comment_lines: 2, try_count: 0, print_count: 0, import_count: 1 }, minimal_lines: 8 }基于这份 JSONL可以计算每个模型的平均代码膨胀率等集中趋势指标。5.3 结果分析范式由于模型版本变化很快这里不给出具体的最终跑分而是提供一个可复用的分析范式。核心的分析视角包括代码膨胀率非空代码行数 / minimal_lines比值越接近 1说明模型越克制。注释密度comment_lines / non_empty_lines比值过高说明模型倾向于“解释代码”而不是“写代码”。防御性代码频率try_count和print_count的均值高数值往往对应着无效防御和调试残留。稳定性同一任务多次采样的代码差异程度差异越大模型输出的可控性越差。建议使用如下格式汇总对比模型平均膨胀率平均注释密度平均 try 数量平均 print 数量可用率Kimi K3待实测待实测待实测待实测待实测Fable待实测待实测待实测待实测待实测Sol待实测待实测待实测待实测待实测表中的“可用率”是指代码能被人工评审直接接受的采样比例需要人工或者独立 Judge 模型标注。5.4 三个模型的差异观察从评测流程本身来看三个模型在 SlopCodeBench 上的表现会有三类典型差异。第一类是“正确但话多”常见于 Kimi K3 这类通用模型只要约束到位它通常能生成正确代码但会带有较多注释和空行。第二类是“简洁但脆弱”常见于 Fable 这类轻量模型输出往往很短但有时会忽略边界条件。第三类是“稳定但模板化”常见于 Sol 这类专项模型它倾向于复用训练数据中的固定套路代码结构相似度高但遇到训练分布之外的任务时能力会下降。这些差异也提示我们SlopCodeBench 评测不应该只看单个指标而要从正确率、膨胀率、稳定性和可维护性四个角度综合决策。模型选型时如果团队代码评审力量充足可以宽容膨胀率偏高的模型如果自动化合并流程占主导则更应关注代码膨胀率和注释密度。6. 常见问题与排查思路6.1 常见问题速查表问题现象常见原因解决思路模型调用超时任务过长或 max_tokens 设置偏小增大 timeout 与 max_tokens返回 429 错误并发请求过多或触发限流增加重试机制降低并发抽取代码为空模型未按指定格式输出在 Prompt 中加强格式约束多次采样结果差异大temperature 设置过高将 temperature 降至 0.1 到 0.3指标统计不稳定抽取逻辑对代码块匹配不一致统一使用正则抽取纯代码评分存在主观偏差使用参赛模型自身作为 Judge改用独立 Judge 或人工抽检6.2 重点排查流程如果评测过程中结果异常建议按以下顺序排查。首先检查模型服务的健康状态直接请求一下接口确认可以正常返回。其次核对配置中的 base_url 和 model 名称是否正确尤其是 vLLM 启动时如果指定了--served-model-name请求中的 model 字段必须保持一致。然后检查评测集 JSON 是否合法常见的错误是约束条件里写了中文标点导致 Prompt 拼接后出现异常字符。最后检查代码抽取逻辑可以在单条样本上打印 extract_code_block 的结果确认代码没有被错误截断。这些排查步骤适用于大多数调用类评测任务不仅限于 SlopCodeBench。按照“服务层 - 配置层 - 数据层 - 逻辑层”的顺序排查效率最高。7. 最佳实践与工程建议7.1 评测集维护评测集是评测的核心资产建议按版本管理。每个任务都要有清晰的中文描述、约束条件、最小实现参考行数并且要标注任务难度和考察点。评测集不应频繁变更否则历史结果无法对比。如果确实要增加任务建议以追加的方式保留旧任务或者在评测报告中明确标注新旧任务的差异。7.2 采样策略代码生成具有随机性单次采样结果不能代表模型真实水平。建议每个任务至少采样 3 到 5 次并计算均值和中位数。对于波动较大的任务可以进一步增加采样次数。在记录结果时需要连同采样参数一起保存否则复现评测时无法判断差异是模型变化还是采样参数变化导致的。7.3 Judge 与人工复核使用模型打分会存在系统性偏差。比如让 Kimi K3 去评判 Fable 的代码模型可能会因为训练数据的偏好而给出不公正的评分。更好的做法是使用独立的强模型作为 Judge同时抽取 10% 到 20% 的样本进行人工评审。人工评审的重点不是给分数而是确认规则指标的解释是否合理。7.4 评测可复现性评测可复现性决定了评测结果的长期价值。每次运行评测前建议生成一份包含以下内容的 meta 信息评测脚本 commit ID评测集版本号模型版本与 API 地址采样参数运行时间与环境版本将这些信息写入评测结果文件的第一行或单独生成一个meta.json。这样即使几个月后回看数据也能快速还原当时的评测条件。7.5 安全与权限提示如果评测环境涉及公司内部代码、私有数据集或生产环境接口请注意权限控制。评测脚本中不要写入任何生产环境的密钥在测试环境验证通过之前不要对线上服务发起批量请求涉及敏感数据时确保所用模型和 API 服务符合公司数据安全要求。8. 总结与后续方向本文从 Slop Code 对工程维护的负面影响出发梳理了 SlopCodeBench 的核心评测维度并完整实现了针对 Fable、Sol、Kimi K3 三个模型的评测流程。通过这套流程可以量化每个模型在代码膨胀率、注释密度、防御性代码数量等方面的表现为模型选型和 Prompt 优化提供数据支撑。后续可以在几个方向上继续深入。一是根据评测结果反向优化 Prompt 模板比如在出现“过度注释”问题时在约束条件中加入“禁止注释每一行代码”等指令。二是扩展评测集规模把算法题、工程题、SQL 脚本和配置文件模板分开评测形成更细粒度的能力画像。三是接入自动化流水线让每次模型发版后自动触发 SlopCodeBench 评测把结果作为版本发布的质量门禁之一。如果你也在做代码生成模型的选型评估建议先跑通这套最小实现再根据团队的实际代码风格补充评测任务效果会更贴近生产环境。