news 2026/10/16 10:42:29

LLM+工具构建数学Agent:让数学解题不再靠“硬算”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLM+工具构建数学Agent:让数学解题不再靠“硬算”

这个系列写到第四篇了。前几篇我们把注意力放在怎么跟大模型对话、怎么把模型本身调得更懂数学,今天换个视角:别逼模型硬想,而是让它像我们做题时一样,拿草稿纸、按计算器、一步步把结果“做”出来。对,说的就是 Agent——准确讲,是 LLM 和外部工具组合起来的数学智能体。

如果你是做数学 AI 应用、科研辅助工具,或者打算在教育场景里落地 AI 解题功能,这篇文章应该能给你一条比较完整的路。我会把“为什么单纯靠 LLM 做数学不够用”“Agent 架构怎么补短板”“手把手搭一个能解微积分题的 Agent”“实战中踩过的坑”这四块讲透。有基础的朋友可以直接跳到第三部分抄代码,新手建议从头看,概念都不难。

1. 先搞清楚问题:为什么 LLM 在数学题上会“一本正经地胡说八道”

1.1 数学任务对推理的要求跟聊天不是一回事

大模型本质是一个概率生成系统。你说一句话,它预测下一个词、再下一个词,直到把整段内容生成完。这种机制在开放聊天、文本总结、内容创作上非常合适,但放到数学场景里,问题立刻暴露:数学要的是“确定性”,而不是“看起来合理”。

什么叫确定性?一个不定积分要么算对,要么算错;一个定理的证明每一步都必须严格推出;一道应用题的答案必须符合题目给出的条件。这些要求天然排斥概率式的文本生成。我见过不少让模型“发挥”出来的解题过程,中间那几步写得特别像教科书,但仔细一验算,符号错、正负号错、条件漏用,问题五花八门。最坑的是,它错得越自然,用户越难察觉。

数学推理还有一个特点就是要“可回溯”。你做一道证明题,每一步都能拿出来对一对、验一验,错了能指出具体是哪一步。但 LLM 每次生成是一整套 token 流,你很难说清楚“它刚才脑子里哪一步想错了”。这道题做错了,你能定位到它是第一步对题目理解错了,还是中间某次计算精度丢了?多数时候定位不了,只能重新生成,碰运气。这种不可控,在严肃场景里几乎没法用。

1.2 三个典型翻车场景

我把实际观察到的失败模式归纳成三类,基本覆盖了日常用量。

场景 A:计算过程错得悄无声息。让模型算 \frac{x^2+1}{x-1} \cdot \frac{x-1}{x^2+1},它可能会非常流利地写“上下约分得 1”,但等一下,条件是 x \neq 1 且 x^2 + 1 \neq 0,后者永远成立,前者要求 x 不等于 1。不把定义域写全这个细节,模型经常漏。一道题如果本身步骤不多、符号不复杂,模型靠训练记忆往往能蒙对,但只要稍微加点陷阱,漏定义域、漏讨论,问题立刻出现。

场景 B:对题目条件的理解偏差。给一道应用题,模型把“至少多少个”理解成“恰好多少个”,后面所有推导都基于错误前提,过程看起来超级严谨,最后答案自然不对。这种情况特别难防,因为从局部看每一步都合理,全局前提错了。人也常犯这个错,但人做完会回头读题核对条件,模型很少主动回头检查。

场景 C:伪证明。这是最严重的。遇到难一点的数论题或者不等式证明,模型可能输出一整套“证明”,里面用了某条不成立的引理,或者把两个充分条件偷换成了充要条件。对数学专业的人来说一眼能看出问题,但对普通学习者来说,这种“伪证明”比直接给错误答案更危险,因为它看起来完全合理。

1.3 为什么加大模型规模、堆提示词解决不了根本问题

很多人第一反应是换更大的模型、写更好的 prompt。这两招能提升一些下限,但治标不治本。

模型规模变大,确实能减少一部分低级计算错误,但不可能消除。因为数学推导本质不是“背下更多题目”,而是“每一步都要在当前状态下做正确变换”。只要生成过程还是概率式的,就会有随机出错的可能——哪怕概率是万分之一,只要跑大量题目就会遇到。而且数学题有个特点:越到后面步骤,错误被前提吸收得越多,你光看最后答案可能感觉“有点对”,但要人肉逐行验算才知道哪一步错了。

提示词工程也一样。你可以告诉模型“请一步一步思考”“请检查你的答案”,这在简单题上偶尔有效,但没法从机制上保证正确。模型没有真正“检查”的能力,所谓检查只是在另一套概率分布里再生成一遍。两遍都错到同一个地方的时候,它就自信地交卷了。实际上,我试过在 prompt 里加了“如果发现错误请及时修正”,模型该错还错,只是错得更理直气壮。

根上的矛盾就一句话:LLM 是概率系统,数学要的是确定性。你想让概率系统直接输出确定结果,路径必然不稳定。那怎么办?答案就是不要让它一个人“硬扛”,给它工具,让它把那些需要精确计算、严谨验算的环节外包出去。

2. Agent 化:让模型学会“用工具”而不是“硬算”

2.1 一句话理解数学 Agent:大脑加手脚

人做数学题从来不会完全靠心算。碰到复杂式子,你会拿草稿纸一步步展开;碰到积分,你会查积分表,甚至用计算器验算;碰到需要精确数值的,你会按计算器或者写个脚本跑一下。Agent 的思路完全一样:LLM 负责做“大脑”,理解题目、制定方案、解读工具返回的结果;外部工具负责做“手脚”,执行精确的计算、符号推导、逻辑验证。

所以一个数学 Agent 本质不是一个“更大的模型”,而是一个系统:由 LLM 做决策调度,由外部工具提供的确定性能力来保证每一个计算环节的准确。模型不必亲自做复杂的多项式展开,它只需要会调用工具,再把工具结果读明白,写进最终答案。

这个思路的好处是显而易见的:模型能在自己擅长的模式识别、解题策略选择上发挥优势,而把不擅长的精确运算交给专门工具。两者互补后的系统,才真正有希望接近“可靠”的数学解题能力。

2.2 数学 Agent 的四个核心组件

我实际搭下来的经验,一个能用的数学 Agent 至少要有四块。

第一块是规划器。这个人就是 LLM 主循环。它的职责是:读题、拆分问题、决定下一步要调用什么工具、解读工具输出,然后继续规划。它不负责最终计算的准确性,但要负责整体解题路线不跑偏。

第二块是工具集。这是 Agent 的“手”。常见工具有四类:数值计算器(精确浮点运算)、符号计算引擎(SymPy 这类,能做求导、积分、化简、解方程)、代码执行环境(Python 解释器,能跑任意计算脚本)、以及外部知识库/参考库(比如查公式、查定理)。对数学 Agent 来说,符号计算引擎和代码执行环境几乎是标配。

第三块是记忆模块。Agent 需要记住题目条件、已经得出的中间结果、已经试错过的路径。这里又有两层:短程记忆是在当前解题过程中维护状态变量,长程记忆是留推理历史,方便回溯或者避免反复走同一条死路。不设计好记忆,Agent 在多步任务里容易“失忆”,前面算完的东西后面忘掉,尤其上下文窗口吃紧的时候。

第四块是验证模块。这个很多人一开始会忽略,但恰恰是它把 Agent 和单纯的“模型加工具”区分开。验证器的职责是:对 Agent 最终得到的答案做独立校验。比如算一个定积分,验证器可以拿数值积分核对结果;解一个方程,验证器把解代回去也许算;做证明题,验证器至少检查关键步骤有没有逻辑跳变。验证器不需要做完整证明检查(那个非常难),但它能兜住一大类低级错误。

2.3 为什么是 Agent 而不是端到端模型

这里有个很自然的疑问:能不能直接训练一个端到端的模型,输入题目输出答案,然后靠强监督把正确率刷上去?针对特定题型、封闭题库,这条路是可行的,比如有些人专门微调模型应对某种竞赛题,刷出一个高分效果完全没问题。

但一旦题型变异、题目变难、需要跨领域知识组合,端到端模型就开始露怯。原因在于它的能力边界被训练数据锁死了,你很难让一个纯模型“学会一个新的定理”或者“突然会使用一种新的计算方法”。Agent 不一样。需要一种新能力,就加一个工具;工具能力不足,就升级工具。模型本身不需要再训练,只要在 prompt 里告诉它新工具怎么用,系统就立刻获得新能力。这种可扩展性,在实际项目里价值极高。

另外就是可调试性。端到端模型给个错误答案,你只能去猜它哪里想错了。Agent 不一样,整个推理步骤、每次工具调用、每个工具返回值都明明白白地摆在那里。数学 Agent 一旦做错,我可以打开日志,看到它在哪一步选择出了问题、哪个工具返回了异常、它在哪一次“思考”偏离了条件。定位问题、修 prompt、改工具返回格式,迭代效率高得不是一点半点。有工程经验的人都知道,系统能做对还不够,出了问题能快速定位原因才是长期可维护性的关键。

3. 实操:搭一个能解微积分题的数学 Agent

3.1 技术选型:直接照抄这份清单

先说选型,这是我自己跑通并稳定用了一段时间的组合,成本低、效果好。

推理核心:我用的是本地或 API 可调用的开源 LLM。选型的核心标准不是跑分最高,而是指令跟随能力好、能稳定输出结构化 JSON。因为 Agent 的通信格式全靠结构化 JSON 来传“思考内容”和“工具调用指令”,如果模型每次都把 JSON 格式输出弄乱,整个流程会异常痛苦。

代码执行:本地 Python 运行时。为了安全我用了 subprocess 隔离执行,超时设置在 10 到 15 秒。数学 Agent 里代码执行器的核心价值是处理数值计算和复杂表达式验证,模型把公式转换成 Python 代码,代码执行器负责算出来。

符号计算:SymPy。这个库处理求导、积分、化简、解方程、极限都非常成熟,而且可以精确地处理符号表达式,避免浮点误差。别自己写符号运算,数学符号逻辑太复杂,直接用成熟的库。

通信协议:JSON。固定几个字段:thought 存当前模型的推理内容,action 存要调用的工具名,action_input 存工具参数。模型每次输出一个 JSON,解析之后执行工具,再把观察结果拼回上下文,喂给模型继续下一步。

3.2 核心循环:ReAct 范式的落地

整个数学 Agent 的核心循环,就是 ReAct——交替执行“思考”和“行动”。

流程简单说:LLM 读题,先在 thought 里分析当前状态,然后决定调用哪个工具,比如调用 sympy_integrate 对表达式求不定积分,工具返回结果,系统把工具观察结果拼回去,让 LLM 分析结果,决定下一步是继续计算还是准备收尾写答案。这样循环,直到模型认为问题解决,或者达到最大步数限制。

我强烈建议别跳过 ReAct 直接写“心算”式 prompt 让模型一句一句推理。因为 ReAct 的思考步骤天然把计算任务剥离出来,安排给工具,模型的思考只需要做策略判断。这样的长链推理即使模型策略判断错了,我们事后也能清楚看到是哪一步选错了工具。

针对数学任务,我在 ReAct 基础上做了一个小改造:在思考阶段加了一个强制子步骤——先复述当前解题目标和已知条件。这个设计能显著减少模型“做着做着把题目条件丢了”的频率。所谓复述,就是让它每次行动前先写两行当前状态摘要:已知什么、要求什么、下一步打算做什么。看起来啰嗦,但在多步数学任务里非常有效,模型常常在复述过程中自己发现前面的错误。

3.3 可复现的代码骨架

下面这段代码是我的骨架,去掉了工程外壳,只留核心逻辑。你可以直接改成自己的实现,或者照着接一个 Agent 框架。

import json import subprocess import sympy as sp from openai import OpenAI # 工具一:符号计算(SymPy 积分) def sympy_integrate(expr_str, var_str): x = sp.symbols(var_str) expr = sp.sympify(expr_str) result = sp.integrate(expr, x) return str(result) # 工具二:任意 Python 代码执行(数值计算、验算都可) def run_python(code_str): try: result = subprocess.run( ["python", "-c", code_str], capture_output=True, text=True, timeout=15 ) return result.stdout.strip() or result.stderr.strip() except subprocess.TimeoutExpired: return "TIMEOUT" TOOLS = { "sympy_integrate": sympy_integrate, "run_python": run_python, } SYSTEM_PROMPT = """ 你是一个数学解题 Agent。你可以调用以下工具: - sympy_integrate(expr_str, var_str):对表达式关于变量求不定积分 - run_python(code_str):执行 Python 代码,用于数值计算与验算 你的每次回复必须是一个 JSON 对象,格式: {"thought": "当前思考", "action": "工具名或 finish", "action_input": "参数或最终答案"} 如果问题已解决,action 必须是 finish,action_input 是最终答案。 """ def run_agent(question): messages = [{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": question}] client = OpenAI() # 换成你的 API 配置 for step in range(10): resp = client.chat.completions.create( model="你的模型", messages=messages, temperature=0.0, response_format={"type": "json_object"} ) raw = resp.choices[0].message.content parsed = json.loads(raw) if parsed["action"] == "finish": return parsed["action_input"] # 执行工具 tool = TOOLS[parsed["action"]] obs = tool(**json.loads(parsed["action_input"])) # 将思考、行动、观察结果全部拼回上下文 messages.append({"role": "assistant", "content": raw}) messages.append({"role": "user", "content": f"Observation: {obs}"}) return "NO_ANSWER"

注意这里 temperature 一定要设成 0。数学 Agent 里不需要任何“创造性”随机性,每次生成应该尽可能确定。虽然实际也不能完全保证,但至少把随机性降到最低。

3.4 一个完整用例:跑一遍你立刻明白

给 Agent 一个题:求不定积分 \int (x^2+1)^2 dx。

实际运行日志大致是这样的:

第一步:模型思考后决定调用 sympy_integrate,参数是表达式 (x^2+1)^2,变量 x。此时模型在 prompt 里要做两步判断:识别出这是一个多项式积分,最适合直接用符号计算工具。

工具返回:x^5/5 + 2*x^3/3 + x。

第二步:模型思考,认为直接拿到结果但最好加一个验算步骤,于是调用 run_python,传入代码验证导数。这一步的价值在于:让模型自己养成“算完验算”的习惯,而不是全寄希望于工具不会错。

工具返回:导数与原表达式一致,验算通过。

第三步:模型输出 finish,最终答案是 x^5/5 + 2*x^3/3 + x + C。

看到区别了吗?模型全程没有自己动手展开多项式、没有心算积分。它只做了三件事:判断该用什么工具、调用工具、解读并确认结果。这里面最容易被忽略的是“解读并确认”这一步,很多人搭完 Agent 让工具算完就直接输出,那其实只是个脚本。真正的 Agent 应该是模型要能理解工具返回的符号表达式,并且有能力做必要的验证,这才能应对工具也出错的复杂情况。

如果我把这道题换成更复杂的:如果被积函数里有参数、或者被积函数不是初等函数,工具可能无法积分。这时候模型需要在思考阶段判断:SymPy 算不出来,那是不是应该尝试数值积分或者用级数展开?这类策略判断,才是 Agent 和普通“调函数”之间真正的分水岭。

4. 实战中踩过的坑与排查技巧:一张速查表收好

4.1 最常见的四类故障

我整理了一张表,对应我实际踩过最多次的坑,以及对应的排查思路。

现象可能原因排查与解决办法
工具代码执行报错模型生成的 Python 代码里有语法错误或未定义变量在 prompt 里给一段“工具调用示例”,强制要求先写计划再生成代码;错误信息要完整回传给模型,让它自己修复
SymPy 长时间算不出 / 卡死表达式太复杂,或者模型生成了错误表达式给工具加超时时间,超时后返回 TIMEOUT 并让模型换思路;检查是否是参数类型问题(比如字符串没转 symbol)
上下文窗口被撑爆每一步把完整思考过程、工具长输出都塞回去了对工具返回结果做截断,比如只保留前 500 字符;限制对话历史条数,必要时人工摘要之前的推理步骤
答案错但工具没报错模型把工具结果解读错了,或者模型在中间步骤做了不该做的“心算”在 prompt 里强约束“所有计算必须调用工具,不许自行计算”;增加验证器,让模型把最终答案代入原题核对

我特别想强调上下文管理这个坑。数学 Agent 天然是多步任务,每一步的思考、工具调用、工具输出全部堆进 messages,用不了几步上下文就会顶到窗口上限。我的做法是:每轮只保留最近 N 轮的完整信息,更早的内容压缩成一行摘要。

举个例子:如果前五步在尝试一种错误的解法,继续保留只会白白吃上下文。更好的方案是让模型每一步都输出一个简短的 progress_summary 字段,表示当前进度。之后只要把近三轮完整数据和之前所有 progress_summary 拼起来,模型也能维持全局感,不容易绕乱。

4.2 三招明显提升稳定性的小技巧

第一招,prompt 里强制“先计划、后行动”。我试过不加任何控制、让模型自由选择,结果它频繁在解题中途突然“灵机一动”去调用无关工具,或者干脆忘记目标。后来我在系统提示里加了固定输出顺序:第一步必须写当前状态与目标;第二步才能写计划;第三步才写出要调用的工具。模型一旦遵守这个节奏,出错的概率直线下降。

第二招,工具返回要精简,别让模型去垃圾堆里找重点。比如 run_python 的返回值,默认会包含不少无用的提示信息,最好自己格式化之后返回干净结果。繁杂的输出会让模型的下一步思考糊里糊涂,本来它是决策者,你给它塞一堆噪声,它决策质量自然下降。

第三招,一定要留“后路”:给 Agent 设置最大步数限制和放弃条件。数学问题有时候确实超出能力范围,死磕只会白白浪费 token。实际中我给 Agent 加了两种退出条件:最大步数到了;或者它连续两次对同一个工具返回结果给出同样的错误解读。此时让它直接输出一个“当前无法确定答案,建议手工检查”的说明,比硬给一个可能错的答案要体面得多。

4.3 定位问题:先分清楚是模型的错还是工具的错

这是排查数学 Agent 最重要的思维习惯。看到错误结果,第一步不是改 prompt,而是打开日志看关键路径:模型有没有明确决定调用正确工具?工具执行的输入参数正确吗?工具返回的结果是否被模型正确解读?

比如有一次 Agent 解一个方程,最后答案错了。我查日志发现模型其实调了方程求解工具,但传入的字符串里漏了一个括号,工具把表达式解析成了完全不同的东西。这明显不是工具的问题,也不是模型策略的问题,而是模型在“参数构造”上出了差错。解决办法不是换工具,而是在 prompt 里增加“参数构造前先检查括号是否匹配”的约束。

还有一次模型把工具返回的符号解直接当作最终答案输出,但题目要求的是数值解,而且要求精确到四位小数。这个问题的根源在于模型没有充分理解题目需求,工具其实给出了正确的通用符号解。此时要修的是系统提示词,把“拿到解后要检查与题目要求的匹配度”写成强约束。

先分清责任归属,再动手修。这个习惯能省下大把调试时间。

5. 还能往哪个方向走:进阶玩法与我的个人体会

搭好一个基础数学 Agent 之后,可以升级的方向其实非常多,我列几个我自己觉得特别值得探索的。

一个是“多工具协同”的纵深优化。现在很多题目不是为了能算一个积分而算,而是一整套建模流程:先读题、建立方程、用符号计算解出解析解、再用数值计算检验边界条件、最后用代码画图示意。这个流程如果由 Agent 串起来,它就是一个“小型数学工作台”。我试过让 Agent 完成一道应用题:识别问题类型、列方程、符号求解、数值验证、最后给出文字解释。这个过程虽然比简单积分复杂不少,但因为每步都是明确的工具调用,总体上还是可控的。

另一个方向是引入形式化验证工具。现在一些开源证明助手,比如 Lean 之类,已经能对数学命题做形式化验证。如果把 Lean 也接成一个工具,Agent 解题之后让证明验证器检查证明步骤的严谨性,那离“机器确认的数学解题”就更近一步了。目前这个方向还在快速发展,但潜力非常大。不过我也提醒一句:符号求导相对简单,形式化证明的复杂度高了一个维度,别一开始就把目标定得太高,建议目前先把米链式流程做好,再谈形式化验证。

还有一个值得玩的是“偏好策略记忆”。Agent 每次做错题,我们很容易判断错误发生在哪个环节。把这些失败案例记录成一个经验库,下次同样题型的任务出现时,把这些经验注入到 prompt 里。这个做法本质上是一种“在上下文中更新知识”的手段,成本低、见效快。我碰到不少重复踩坑的例子,比如模型总在参数构造时漏括号,我直接把这条写成一条经验规则放进 prompt,问题基本就消失了。

最后说一点个人体会。数学 Agent 最迷人的地方在于,它改变了我们思考“AI 做数学”的方式:不是让模型假装会数学,而是让模型像人一样学会借助工具完成数学。一个人计算能力再强,也要靠草稿纸理清思路;模型推理能力再强,也应该学会把计算、验证这些确定性的活路由工具承担。这种“分工”思想,我认为会贯穿未来好几年的 AI for Math 应用方向。

我自己实际用下来,数学 Agent 真正稳定的形态,并不是一个“全能解题器”,而是一个“可交互的数学伴侣”:模型负责引导思路、做策略规划、解释推导;工具负责算、负责验算、负责兜底。这种系统能真正帮到人,至少比直接甩给一个大模型问数学题要靠谱太多。如果你也在折腾这个方向,建议从最简单的积分题搭起,你已经有了骨架,剩下的就是不断喂题、记坑、迭代。

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

零基础AI编程:从需求描述到上手实操的完整指南

去年有位完全没写过一行代码的朋友问我,说自己攒了很多家庭菜谱,想整理成一个能按关键词搜索的网页,但他不想学编程,问有没有可能。我的回答是:现在完全可以,而且这正是你入局的最好时机。零基础AI编程已经…

作者头像 李华
网站建设 2026/10/16 10:40:47

用尼尔森十大易用性原则驱动测试设计:一份系统化交互体检清单

全栈测试这么多年,我最大的一个感触是:功能测试大家都会做,缺的是对“体验”进行系统化体检的方法。很多项目提测后,测试报告写得密密麻麻,全是按钮失灵、数据不对、接口报错,真正卡住用户的交互问题反而没…

作者头像 李华
网站建设 2026/10/16 10:40:12

RLinf+SGLang:打造大模型微调评测高效闭环

训练一个模型到能上线,真正的瓶颈往往不在训练本身,而在"训练完后怎么快速知道它有没有变好"。我们做 Cosmos3 时就被这个问题卡了很久,最后用 RLinf 加 SGLang 把微调、生成、评测揉成一条流水线,才总算把这个闭环跑顺…

作者头像 李华
网站建设 2026/10/16 10:39:24

CMake find_package 深度解析:Module与Config模式、搜索路径与实战避坑

1. 为什么 find_package 是 CMake 里最容易被低估的命令 如果你写过一段时间的 CMake,大概率经历过这样的场景:项目在自己机器上编译得好好的,换一台机器就报 Could NOT find XXX ,然后开始满世界找库的安装路径,手动…

作者头像 李华
网站建设 2026/10/16 10:39:14

RK3328+SPR5801边缘视频分析流水线实践:解码与推理解耦

最近在折腾一个边缘视频分析盒子的预研项目,需求看起来挺朴素:一路4K30fps的RTSP监控流进来,本机要完成硬件解码、目标检测,再把结果叠加到画面上送出去,整机功耗尽量压在15W以内。一开始我也想偷懒,想着问…

作者头像 李华
网站建设 2026/10/16 10:38:02

Greenplum 6.8数据存储区创建:从环境准备到gpinitsystem完整实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华