刷题 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 真正能在工程场景中落地的关键。

更多推荐