AI 题解准确性评估框架:正确性、完整性、可读性的三维打分
一、AI 生成的题解,真的"对"吗?
用 AI 生成算法题解已经成了很普遍的做法。把题目粘贴到对话框,几秒钟就能得到一份代码和说明。但你有没有想过:这份题解到底有多可靠?
我自己做过一个小实验:用同一个模型对 LeetCode 热门 100 题生成题解,然后逐一验证。结果是:约 75% 的题解代码能通过所有测试用例,约 15% 在边界条件上有问题,约 10% 的思路完全不对或者漏掉了关键约束。
这引出一个问题:我们如何系统性地评估一份 AI 生成的题解?仅仅"代码能跑"就够了吗?显然不够。一份好的题解不仅要正确,还要完整,更要让读者看得懂。
我设计了一个三维评估框架,从正确性、完整性、可读性三个维度对 AI 题解打分。这个框架不仅用于评估,也可以反过来指导 prompt 的优化。
flowchart TB A[AI 生成题解] --> B{三维评估} B --> C[正确性 Correctness] B --> D[完整性 Completeness] B --> E[可读性 Readability] C --> C1[编译/语法正确] C --> C2[通过公开测试用例] C --> C3[通过隐藏边界用例] C --> C4[复杂度分析准确] D --> D1[包含思路推导] D --> D2[包含状态转移说明] D --> D3[包含边界处理] D --> D4[包含时空复杂度分析] E --> E1[代码注释充分] E --> E2[变量命名有意义] E --> E3[解法逻辑线性展开] E --> E4[图/表辅助说明] C --> F[加权总分] D --> F E --> F F --> G{分数 >= 阈值?} G -->|是| H[通过审核] G -->|否| I[标记需人工复核]二、为什么这三个维度缺一不可
正确性是最基础的维度,但它不仅仅是"代码能通过测试"。真正的正确性包括四层:语法正确 → 公开用例通过 → 隐藏边界用例通过 → 复杂度分析无误。
很多 AI 题解的问题是:它在公开用例上表现完美,但在 n=0、n=1、或数据量为极限值时出错。这些边界条件往往不在题目描述中显式提及,而是需要解题者自行推导。
完整性衡量题解是否覆盖了解题的完整推理过程。一份只有代码的题解价值有限,读者最多只能"看懂答案",无法"学会方法"。完整的题解应该包括:为什么选择这个算法、状态/变量的含义、关键步骤的推导过程、以及时空复杂度的分析依据。
可读性往往被忽视,但它直接影响题解的传播价值。变量名是left和right还是l和r?有没有关键位置的中文注释?解法的逻辑是按照直觉顺序展开,还是跳跃式地罗列?这些细节决定了读者是能跟着走完还是中途放弃。
这三个维度形成一个递进关系:不正确的题解没有讨论另外两个维度的必要;不完整的题解即使代码正确,也无法帮助读者建立解题思维;不可读的题解等于浪费读者时间。
三、自动化评估工具的实现
""" AI 题解三维评估器 设计思路: 1. 正确性:在沙箱中执行代码,对多组测试用例验证 2. 完整性:用规则和 LLM 双重检查结构完整性 3. 可读性:基于代码风格和注释密度打分 """ import subprocess import json from dataclasses import dataclass from typing import List, Tuple, Dict @dataclass class SolutionEvaluation: """题解评估结果""" correctness: float # 0-10 分 completeness: float # 0-10 分 readability: float # 0-10 分 total_score: float # 加权总分 details: Dict[str, str] # 各维度详细说明 passed: bool # 是否通过阈值 class SolutionEvaluator: """ AI 题解评估器 正确性占 50%、完整性占 30%、可读性占 20% 为什么这样分配权重:正确性是必要条件, 但如果只有正确性,题解等同于一段裸代码, 因此完整性也占较高权重。 """ def __init__(self, timeout: int = 5): self.timeout = timeout # 沙箱执行超时,防止死循环 self.weights = { "correctness": 0.5, "completeness": 0.3, "readability": 0.2 } self.pass_threshold = 7.0 # 7 分及以上通过 def evaluate(self, code: str, test_cases: List[Tuple], language: str = "python") -> SolutionEvaluation: """对一份题解进行完整评估""" correctness = self._check_correctness(code, test_cases, language) completeness = self._check_completeness(code) readability = self._check_readability(code) total = ( correctness * self.weights["correctness"] + completeness * self.weights["completeness"] + readability * self.weights["readability"] ) return SolutionEvaluation( correctness=correctness, completeness=completeness, readability=readability, total_score=round(total, 1), details={ "correctness": f"通过 {self._count_passed(code, test_cases)}/{len(test_cases)} 组测试", "completeness": f"结构完整度: {completeness}/10", "readability": f"可读性: {readability}/10" }, passed=total >= self.pass_threshold ) def _check_correctness(self, code: str, test_cases: List[Tuple], language: str) -> float: """ 正确性检查 为什么用多组测试用例:单组用例无法覆盖边界条件。 公开用例检查思路正确性, 边界用例检查代码鲁棒性, 随机用例检查意外行为。 """ passed = 0 total = len(test_cases) for i, test_case in enumerate(test_cases): try: # 在安全沙箱中执行(简化版:直接 subprocess) # 实际应使用 Docker 或 seccomp 隔离 result = subprocess.run( ["python3", "-c", self._wrap_code(code, test_case)], capture_output=True, timeout=self.timeout, text=True ) if result.returncode == 0 and self._check_output(result.stdout, test_case): passed += 1 except (subprocess.TimeoutExpired, Exception): # 超时或异常:该用例失败 continue # 分数映射:全部通过 = 10 分,每失败一个扣 2 分,最低 0 return max(0, 10 - 2 * (total - passed)) def _check_completeness(self, code: str) -> float: """ 完整性检查 基于以下指标: - 是否有注释说明算法选择 - 是否包含关键变量含义 - 是否包含边界处理逻辑 - 是否包含复杂度注释 为什么用规则检查而非 LLM: 结构完整性有明确的模式可匹配, 规则检查比 LLM 更稳定、更快。 """ score = 10.0 # 检查 1:算法选择说明 (-2) algo_keywords = ["动态规划", "贪心", "回溯", "DFS", "BFS", "二分", "双指针", "滑动窗口", "递归"] if not any(kw in code for kw in algo_keywords): score -= 2 # 检查 2:关键变量含义 (-2) if "//" not in code and "#" not in code: score -= 2 # 检查 3:边界处理 (-3) edge_keywords = ["if not", "if len", "if n", "边界", "空"] if not any(kw in code for kw in edge_keywords): score -= 3 # 检查 4:复杂度分析 (-3) complexity_keywords = ["O(", "时间复杂度", "空间复杂度"] if not any(kw in code for kw in complexity_keywords): score -= 3 return max(0, score) def _check_readability(self, code: str) -> float: """ 可读性检查 为什么用这些指标: - 变量名长度反映了命名质量 - 注释密度反映了代码可理解性 - 行内注释比例反映了关键逻辑的说明程度 """ lines = code.split("\n") if len(lines) == 0: return 0 score = 10.0 # 检查 1:变量名长度 (-3) # 单字母变量名(除了 i,j,k 等约定俗成的循环变量)扣分 # 这里做简化判断 has_meaningful_names = any(len(line.split("=")[0].strip()) > 2 for line in lines if "=" in line and "==" not in line) if not has_meaningful_names: score -= 3 # 检查 2:注释密度 (-4) comment_lines = sum(1 for line in lines if line.strip().startswith("#") or line.strip().startswith("//")) if len(lines) > 20 and comment_lines / len(lines) < 0.1: score -= 4 # 检查 3:函数长度 (-3) func_lines = [line for line in lines if line.strip() and not line.strip().startswith("#")] if len(func_lines) > 40: score -= 3 return max(0, score) def _wrap_code(self, code: str, test_case: Tuple) -> str: """将代码包装为可执行的测试脚本""" input_data, expected = test_case return f""" {code} import json result = Solution().__getattribute__([m for m in dir(Solution()) if not m.startswith('_')][0])(*json.loads('{json.dumps(input_data)}')) print(json.dumps({{"result": result}})) """ def _check_output(self, output: str, test_case: Tuple) -> bool: """检查输出是否与期望一致""" try: result = json.loads(output.strip()).get("result") _, expected = test_case return result == expected except: return False def _count_passed(self, code: str, test_cases: List[Tuple]) -> int: """统计通过的测试用例数(用于报告)""" return sum(1 for _ in test_cases if self._check_correctness(code, [test_cases[_]], "python") > 0) # 使用示例 if __name__ == "__main__": evaluator = SolutionEvaluator() # 一份可能不完整的 AI 题解 ai_solution = ''' class Solution: def twoSum(self, nums, target): # 使用哈希表,时间复杂度 O(n),空间复杂度 O(n) seen = {} for i, num in enumerate(nums): complement = target - num if complement in seen: return [seen[complement], i] seen[num] = i return [] ''' # 评估 result = evaluator.evaluate( code=ai_solution, test_cases=[ (([2,7,11,15], 9), [0,1]), (([3,2,4], 6), [1,2]), (([3,3], 6), [0,1]), ] ) print(f"总分: {result.total_score}/10") print(f"正确性: {result.correctness}/10") print(f"完整性: {result.completeness}/10") print(f"可读性: {result.readability}/10") print(f"通过审核: {result.passed}")四、评估框架的边界与局限
测试用例的覆盖度问题。评估结果高度依赖测试用例的质量。如果提供的用例没有覆盖某个边界条件,正确性评分就会虚高。理想情况下需要结合模糊测试(fuzz testing)自动生成随机用例。
代码风格的跨语言差异。当前的完整性检查是基于 Python 的注释风格(# 和 """)。对于 Java(// 和 /** */)或 C++,需要调整关键词列表。好在正则匹配的框架是通用的,只需要维护不同语言的关键词表。
"好看但不对"的题解。有些 AI 生成的题解变量命名清晰、注释详细、结构完整,但核心逻辑有误。可读性高而正确性低,这种情况总分可能仍然不低,因为权重分配只是评估方法,不是衡量一切的绝对标准。在实践中,当正确性低于 6 分时应该直接判定不合格。
规则检查的灵活性不足。当前完整性检查是基于关键词匹配,可能漏掉一些隐式的完整性表达(比如通过代码结构隐式表达算法选择)。后续可以引入 LLM 做辅助判断,但需要权衡成本。
五、总结
AI 题解的评估不应该只靠"能不能 AC"。正确性、完整性、可读性三个维度共同构成了一份好题解的评价标准。
这个框架的价值不在于打分本身,而在于它把评估过程标准化了。当你在优化 prompt 时,可以用这个框架对比不同 prompt 的输出质量。当你在审核 AI 生成的题解时,可以用它快速筛选出需要人工复查的部分。
题解质量的上限,就藏在这三个维度里。