刷题 Agent 的工具体系:LLM 推理、沙箱执行与评测的三位一体
刷题 Agent 的工具体系:LLM 推理、沙箱执行与评测的三位一体
一、单靠 LLM 的输出,离可靠题解还有多远
用 LLM 生成算法题解,最常见的做法是:把题目丢给模型,等它输出代码。然后你复制粘贴到 LeetCode,提交,等待判题结果。如果通过了,题解可用;如果没通过,你可能需要再试几次,或者去看报错信息手动调整。
这个流程的问题在于:LLM 的输出与最终验证之间是断裂的。模型不知道自己生成的代码对不对,你也不知道——直到手动提交后。这种断裂导致两个后果:一是反馈循环长,二是纯粹依赖 LLM 的生成质量,没有第二道防线。
这篇文章讨论的不是 LLM 本身,而是围绕 LLM 构建的一套工具体系——让 Agent 不仅能生成代码,还能自主执行、验证、修正。
二、三位一体的工具架构
flowchart TD
subgraph LLM推理层
A[题目理解与算法选择]
B[代码生成]
C[复杂度分析]
end
subgraph 沙箱执行层
D[代码编译]
E[样例测试]
F[边界用例测试]
G[性能压测]
end
subgraph 评测反馈层
H[正确性评分]
I[性能评分]
J[代码质量评分]
K[综合报告]
end
LLM推理层 -->|生成代码| 沙箱执行层
沙箱执行层 -->|执行结果| 评测反馈层
评测反馈层 -->|失败反馈| LLM推理层
评测反馈层 -->|通过| L[输出最终题解]
这套体系的核心逻辑是「生成-执行-反馈-修正」的闭环。LLM 负责生成,沙箱负责验证,评测层负责判断和反馈。三个模块独立分工,通过消息队列连接。
三、Agent 工具注册与调度
from typing import Any, Callable
from dataclasses import dataclass
from enum import Enum
class ToolCategory(Enum):
"""工具分类"""
LLM = "llm" # 推理类工具
SANDBOX = "sandbox" # 沙箱执行类工具
EVALUATOR = "evaluator" # 评测类工具
@dataclass
class ToolDef:
"""工具定义
每个工具包含:名称、描述、输入参数 schema、执行函数。
Agent 框架通过函数描述决定何时调用哪个工具。
"""
name: str
description: str # 描述给 LLM 阅读,帮助它选择合适的工具
category: ToolCategory
parameters: dict # JSON Schema 格式的参数定义
handler: Callable # 实际执行函数
class AgentToolRegistry:
"""Agent 工具注册中心
管理所有可用工具,提供查询和调用能力。
设计思想类似于 LangChain 的 Tool 体系,但更轻量,
只关注刷题场景需要的最小工具集。
"""
def __init__(self):
self._tools: dict[str, ToolDef] = {}
def register(self, tool: ToolDef) -> None:
"""注册一个工具"""
self._tools[tool.name] = tool
def get_tool_descriptions(self) -> str:
"""生成工具描述文本,供 LLM 做工具选择决策"""
lines = []
for tool in self._tools.values():
lines.append(
f"- {tool.name}: {tool.description}"
f" 参数: {tool.parameters}"
)
return "\n".join(lines)
def invoke(self, name: str, **kwargs) -> Any:
"""调用指定工具"""
tool = self._tools.get(name)
if not tool:
raise ValueError(f"未知工具: {name}")
return tool.handler(**kwargs)
# ---- 工具实现 ----
def llm_generate_solution(problem_desc: str, language: str) -> str:
"""LLM 工具:生成题解代码"""
# 实际调用 LLM API
# 此处用伪代码示意
return "# 生成的代码"
def sandbox_run_test(code: str, test_input: str, language: str) -> dict:
"""沙箱工具:在隔离环境中执行代码并返回结果"""
# 实际调用沙箱执行器
return {"passed": True, "output": "...", "time_ms": 42}
def evaluator_score(result: dict) -> dict:
"""评测工具:根据执行结果生成质量评分"""
score = 100 if result["passed"] else 0
return {
"correctness": score,
"performance": 100 if result.get("time_ms", 0) < 1000 else 70,
"suggestions": [] if result["passed"] else ["检查边界条件"],
}
class ProblemSolvingAgent:
"""刷题 Agent:编排 LLM 推理、沙箱执行与评测
工作流程:
1. 接收题目,调用 LLM 生成初始代码
2. 在沙箱中执行测试用例
3. 评测结果,如果通过则输出,否则反馈给 LLM 重新生成
4. 最多重试 3 次
"""
MAX_RETRIES = 3
def __init__(self):
self.registry = AgentToolRegistry()
# 注册 LLM 工具
self.registry.register(ToolDef(
name="generate_solution",
description="根据题目描述和目标语言生成算法题解代码",
category=ToolCategory.LLM,
parameters={
"problem_desc": {"type": "string", "description": "题目描述"},
"language": {"type": "string", "enum": ["python", "java", "cpp"]},
},
handler=llm_generate_solution,
))
# 注册沙箱工具
self.registry.register(ToolDef(
name="run_test",
description="在沙箱环境中执行代码并返回测试结果",
category=ToolCategory.SANDBOX,
parameters={
"code": {"type": "string"},
"test_input": {"type": "string"},
"language": {"type": "string"},
},
handler=sandbox_run_test,
))
# 注册评测工具
self.registry.register(ToolDef(
name="evaluate",
description="根据执行结果评估代码质量并给出改进建议",
category=ToolCategory.EVALUATOR,
parameters={"result": {"type": "object"}},
handler=evaluator_score,
))
def solve(
self,
problem_desc: str,
test_cases: list[dict],
language: str = "python",
) -> dict:
"""解决一道题目:生成-评测-修正 的完整流程"""
feedback_history = [] # 记录每次失败的反馈,供 LLM 参考
for attempt in range(self.MAX_RETRIES):
# 第 1 步:LLM 生成代码(带上之前的失败反馈)
prompt = problem_desc
if feedback_history:
prompt += f"\n\n之前的错误反馈:{feedback_history}"
code = self.registry.invoke(
"generate_solution",
problem_desc=prompt,
language=language,
)
# 第 2 步:沙箱执行所有测试用例
all_passed = True
test_results = []
for tc in test_cases:
result = self.registry.invoke(
"run_test",
code=code,
test_input=tc["input"],
language=language,
)
test_results.append(result)
if not result["passed"]:
all_passed = False
# 第 3 步:评测
eval_result = self.registry.invoke(
"evaluate", result={"passed": all_passed, "details": test_results}
)
if all_passed:
return {
"code": code,
"passed": True,
"attempts": attempt + 1,
"eval": eval_result,
}
# 收集失败信息,供下一轮反馈
failures = [
f"用例{i+1}: {t.get('error', '失败')}"
for i, t in enumerate(test_results) if not t["passed"]
]
feedback_history.append(f"第{attempt+1}轮失败:{'; '.join(failures)}")
return {
"code": None,
"passed": False,
"attempts": self.MAX_RETRIES,
"eval": {"correctness": 0, "suggestions": ["超过最大重试次数"]},
}
四、边界与权衡
4.1 重试的上限与策略
当前实现重试 3 次,每次都完整重跑 LLM 推理。更好的策略是根据失败类型选择不同的修正路径——语法错误可以直接让 LLM 修正,逻辑错误需要重新分析题目。不同错误类型的修正成本和策略不同。
4.2 工具调用的成本
每次 LLM 调用都有延迟和费用。如果沙箱执行只需要 50ms,而 LLM 推理需要 2s,那么"先执行再反馈"的延迟主要来自 LLM。优化的方向是尽量减少 LLM 的无效调用——比如在沙箱执行前做一次静态分析,排除明显的语法错误。
4.3 评测的维度拓展
当前评测只关注正确性和性能。完整的评测体系还应该包含代码风格、可读性、边界覆盖度等维度。这些需要通过静态分析工具(如 pylint、SonarQube)来辅助,而非仅依赖沙箱执行结果。
4.4 Agent 与流水线的协作
Agent 是异步的、面向单题的,而流水线是批量的、面向多题的。两者需要一个调度层来协调——流水线批量分配题目给 Agent,Agent 完成单题后回传结果,流水线汇总生成报告。
五、总结
刷题 Agent 的工具体系不是让 LLM 更强——LLM 的能力边界是固定的。它的价值在于:给 LLM 配上了"手"和"眼睛"。有了沙箱,LLM 能知道自己的代码能不能跑;有了评测,LLM 能知道哪里需要改。这种「感知-行动」闭环,是让 Agent 真正能在工程场景中落地的关键。
更多推荐
所有评论(0)