news 2026/7/22 14:05:15

AI 辅助题库建设:用 LLM 自动生成题目变体与测试用例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI 辅助题库建设:用 LLM 自动生成题目变体与测试用例

AI 辅助题库建设:用 LLM 自动生成题目变体与测试用例

一、深度引言与场景痛点:造题比刷题难十倍

刚开始做刷题系统时,我以为最难的部分是写判题引擎。后来才发现,最难的是持续产出高质量的题目和测试用例

手动造一道好题的过程是这样的:先想一个核心算法点,设计输入输出格式,编写题目描述,构造 10~20 组测试用例(包括边界条件),再用多种语言写一遍参考答案。这一套下来,一道中等难度的题至少要 4 个小时。如果题库目标是 500 道,一个人全职做也需要近一年。

但问题的另一面是:LLM 在"生成变体"这件事上表现出奇地好。给一个经典题目(如"两数之和"),它能生成多个变体(三数之和、最接近的三数之和、四数之和),并且保持核心算法思路的一致性。本文记录我在实践中使用 LLM 辅助题库建设的完整方案。

二、底层机制与原理深度剖析

LLM 自动生成题目的核心链路如下:

这条流水线的核心思想是:LLM 负责发散,自动化校验负责收敛。LLM 在发散阶段可以产生大量候选题目,校验层则像一道滤网,确保只有真正可用的题目才进入题库。

三、生产级代码实现与最佳实践

题目变体生成的提示词设计

# LLM 题目变体生成器 —— 核心在于提示词的约束设计 import json from openai import OpenAI class ProblemVariantGenerator: """基于种子题目,使用 LLM 生成多个变体""" SYSTEM_PROMPT = """你是一位资深算法竞赛出题人。你的任务是根据给定的种子题目,生成 {count} 个变体。 必须严格遵守以下规则: 1. 变体必须保留种子的核心算法思想,但可以改变场景、数据范围或约束条件 2. 每道变体必须有一个独特的角度,不能是简单的参数修改 3. 测试用例必须覆盖:正常情况、边界情况(空输入、极值、单元素)、性能极限情况 4. 输出必须是合法的 JSON,格式与输入一致 5. 如果生成过程中发现无法产生有意义的变体,在这个位置返回 null""" def __init__(self, api_key: str, model: str = "gpt-4"): self.client = OpenAI(api_key=api_key) self.model = model self.validator = ProblemSchemaValidator() def generate_variants( self, seed_problem: dict, count: int = 3, temperature: float = 0.8 ) -> list[dict]: """生成题目变体列表 Args: seed_problem: 种子题目的完整 JSON count: 期望生成的变体数量 temperature: 创造性参数,0.8 在多样性和可控性间取得平衡 Returns: 通过校验的变体列表,可能少于 count(部分被过滤) """ user_prompt = self._build_variant_prompt(seed_problem, count) response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": self.SYSTEM_PROMPT.format(count=count)}, {"role": "user", "content": user_prompt} ], temperature=temperature, # response_format 确保返回合法 JSON,减少格式解析失败的概率 response_format={"type": "json_object"} ) raw_output = json.loads(response.choices[0].message.content) variants = raw_output.get("variants", []) # 校验 + 过滤 valid_variants = [] for v in variants: if v is None: continue try: self.validator.validate(json.dumps(v)) valid_variants.append(v) except Exception as e: print(f"[变体被过滤] 原因: {e}") return valid_variants def _build_variant_prompt(self, seed: dict, count: int) -> str: return f"""种子题目: ```json {json.dumps(seed, ensure_ascii=False, indent=2)}

请为这道题生成 {count} 个变体。返回格式如下:

{{ "variants": [ {{...}}, // 变体 1 的完整题目 JSON {{...}}, // 变体 2 的完整题目 JSON null // 如果无法生成更多变体,用 null 占位 ] }}

变体设计要点:

  • 每个变体的 id 使用新的编号(P{seed['id'][1:]}X1 ~ P{seed['id'][1:]}X{count})
  • difficulty 可以与种子不同(如果变体引入了新的复杂度)
  • 测试用例至少 10 组,其中至少 3 组是边界条件
  • description 必须完整,不能引用"同种子题目"之类的表述"""
### 测试用例的交叉验证 ```python # 测试用例交叉验证器 —— 确保测试用例本身是正确的 class TestCaseValidator: """验证生成的测试用例是否自洽""" def validate_cross(self, problem: dict, reference_solution: str) -> dict: """交叉验证:用参考答案逐个运行测试用例 核心逻辑: 1. 用已知正确的参考答案执行每个测试用例 2. 如果参考答案的输出与 expectedOutput 不一致, 说明测试用例本身有误(输入或期望输出写错了) 3. 这可以过滤掉 LLM 幻觉产生的不一致数据 """ test_cases = problem.get("testCases", []) results = {"passed": [], "failed": [], "error": []} for i, tc in enumerate(test_cases): try: actual = run_code_with_input( reference_solution, tc["input"], timeout_ms=2000 ) expected = tc["expectedOutput"].strip() # 考虑到浮点数精度问题,使用模糊比较 if self._fuzzy_match(actual, expected): results["passed"].append(i) else: results["failed"].append({ "index": i, "expected": expected, "actual": actual }) except TimeoutError: results["error"].append({ "index": i, "reason": "参考答案执行超时,测试用例可能过大" }) except Exception as e: results["error"].append({ "index": i, "reason": str(e) }) return results def _fuzzy_match(self, actual: str, expected: str, eps: float = 1e-6) -> bool: """模糊匹配:处理浮点数精度问题""" actual = actual.strip() expected = expected.strip() if actual == expected: return True # 尝试按数值比较 try: actual_nums = [float(x) for x in actual.split()] expected_nums = [float(x) for x in expected.split()] if len(actual_nums) != len(expected_nums): return False for a, e in zip(actual_nums, expected_nums): if abs(a - e) > eps: return False return True except ValueError: return False

四、边界分析与架构权衡

LLM 生成的质量控制

实践中发现,LLM 生成的题目有三个常见问题:

  1. 测试用例逻辑错误:LLM 在生成测试用例时,有时会写一个输入,然后拍脑袋写一个输出——这个输出不一定是正确的。必须用参考答案交叉验证,否则这些错误用例会被用户当作"标准答案"。

  2. 题目描述的前后矛盾:比如"In the first line..."后面又说"Each line contains..."。这个问题在长文本生成中很常见,目前没有完美的自动检测方案,必须人工审核。

  3. 难度标注不准:LLM 倾向于把大多数题标为 MEDIUM。需要额外的难度评估模型(这个在下一篇会详细讲)。

成本分析

以 GPT-4 为例,每生成 10 个变体(含完整测试用例),Token 消耗约为:

  • 输入:~3000 tokens(种子题目 + 提示词)
  • 输出:~8000 tokens(10 道变体)

单次生成成本约 $0.30。当题库达到 500 道时,采用此方案比纯人工节省约 90% 的时间成本。

人工审核不能省略

自动化流水线的目标是减少人工工作量,而不是消除人工审核。每道 AI 生成的题目,在加入题库前仍然需要至少一个人过一遍。但审核 10 道 AI 生成的题目(每道约 2 分钟),远比手写 10 道题(每道约 4 小时)快。

五、总结

用 LLM 辅助题库建设的核心经验是:让 LLM 做它擅长的事(发散生成),用规则和代码做它不擅长的事(精确校验)

这个方案的三个关键环节:

  1. 精心设计的提示词(控制变体的多样性和质量)
  2. 多层自动校验(Schema 校验 + 交叉验证 + 难度评估)
  3. 必过人工审核(最后的兜底防线)

题库的质量永远是第一位的——宁可少 100 道题,也不要有 10 道错题混进去。AI 只是加速器,不是质量保证的替代品。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/22 14:04:21

今天谐振频率一直锁不住(一)

做一条220kV长电缆耐压,按资料上的电容量算好谐振频率大概在40Hz左右。接好线,开机,自动扫频。变频电源从20Hz开始往上扫,扫到35Hz左右的时候电压开始起来,但到了38Hz又掉下去了,再往上扫到42Hz又起来一点&…

作者头像 李华
网站建设 2026/7/22 14:03:32

java中result结果工具类

1.该类规范地规定了返回格式。2.工具类&#xff1a;import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data;/*** 全局统一返回结果类*/ Data Schema(description "全局统一返回结果") public class Result<T> {Schema(description "返…

作者头像 李华
网站建设 2026/7/22 14:03:29

感知AGI可信度:从对话连贯性到错误处理的维度完整性评估

1. 先理解“感知AGI”的核心&#xff1a;可信度来自维度完整&#xff0c;而非能力高低 这个标题的核心观点是&#xff1a;判断一个系统是否接近通用人工智能&#xff08;AGI&#xff09;&#xff0c;关键不是看它在单一任务上的能力有多强&#xff0c;而是看它在多个维度上的表…

作者头像 李华
网站建设 2026/7/22 14:03:14

技术博主如何用Coze和Python搭建自动化中台

1. 为什么技术博主需要一个自动化中台&#xff1f; 作为技术博主&#xff0c;每天要处理的内容生产流程远比普通创作者复杂。从选题收集、代码验证、文章撰写到多平台分发&#xff0c;每个环节都需要投入大量时间。我运营技术博客的前三个月&#xff0c;平均每天要花6小时在重复…

作者头像 李华
网站建设 2026/7/22 14:02:14

嵌入式外设识别与EPI接口:从GPIO身份验证到高速并行总线应用

1. 嵌入式外设识别的基石&#xff1a;为何需要“身份证”&#xff1f;在嵌入式开发的世界里&#xff0c;我们常常把微控制器&#xff08;MCU&#xff09;想象成一个五脏俱全的小型计算机。它内部集成了CPU、内存&#xff0c;以及各种各样的“器官”——也就是外设&#xff0c;比…

作者头像 李华