☰
数学场景下LLM为何不靠谱?Agent+工具闭环才是解法
2026/10/20 14:11:09 网站建设 项目流程

如果你也试过让大语言模型帮忙解数学题,多半遇到过这种场面:它先是给出一段看起来极其专业的解答步骤,你信心满满地往下读,结果最后一步的答案和标准答案差了十万八千里——再让它检查一遍,它甚至还能信誓旦旦地告诉你“重新检查后确认无误”。这种“流利地胡说八道”在数学场景里格外致命,因为数学只认结果,不认语气。这期AI for Math速成进入第四篇,我想认真聊一件事:为什么数学场景下LLM单打独斗不够用,以及为什么Agent结构是更靠谱的解法。如果你正在做AI教育产品、数学解题工具,或者单纯想搞明白“LLM加工具”到底怎么落地,这篇就是为你准备的。

1. 面对数学,LLM的"会说话"为什么变成"不会做"

1.1 语言模型的本质是"接话",不是"推导"

先说一个被很多人忽略的事实:大语言模型的训练目标是预测下一个Token,它最擅长的其实是把一段话接得“像那么回事”。你在对话框里输入一道题,它生成的下一个词,根本不是在执行数学规则,而是在做一个概率选择——在所有可能出现的文本里挑一个最“顺”的片段继续写下去。这个过程和人类做推导是两码事:人类从公理出发,按逻辑规则一步步变形;LLM只是让接出来的句子在语法上、语义上看起来不违和。

用一个不太严谨但很形象的类比:一个口才极好的导游,可以对着地图说得头头是道,你让他估算两座山之间的实际距离,他报出的数字大概率是靠“听起来差不多”蒙的。数学恰恰是所有知识里最不买“说话好听”账的领域。题目答案唯一,判分机制非黑即白,哪怕论证过程再漂亮,只要一个符号错了,整道题就是错的。这种“硬正确性”放大了LLM的弱点:它输出的流畅性和正确性之间,几乎没有任何保证关系。

更麻烦的是,LLM在生成错误步骤时,自己往往毫无感知。这不是态度问题,而是机制问题:模型内部没有“算一算再回答”的模块,它的每一步选择都在模仿训练数据里的模式,而不是真的在解方程。你在数学题上追问它“你确定吗”,它甚至可能基于同样的概率机制,给出另一个同样流利的错误答案。

1.2 一道积分题如何让我认清现实

我最初做AI数学工具时,也天真地以为“让模型多看看例题,多给点Few-shot提示,它就能学会”。直到我拿一道很基础的积分题反复测试,才彻底改变想法。

题目是求不定积分:∫ (2x+3)/(x²+3x+1) dx。

这类题的标准做法是换元。观察分子2x+3,恰好是分母x²+3x+1的导函数,所以令u=x²+3x+1,du=(2x+3)dx,原积分直接变成∫1/u du = ln|u|+C = ln|x²+3x+1|+C。

听着不难对吧?我连续让同一个模型试了几轮,它的表现让我大跌眼镜:有时它能说“令u等于分母”,但接下来会写成ln|分母/2|,凭空多个1/2;有时它干脆跳过换元,直接用分部积分硬算,最后得出一堆乱七八糟的分式;还有一次,它第一行写对了,第三行却把u代回去的时候少写了平方项。整个过程像极了一个学生在考场上“假装会做”,每一步看起来都有模有样,但合在一起根本经不起推敲。

我后来又试了纯数值计算的题目,比如两阶矩阵乘法。2x2的矩阵,公式清清楚楚:C11=A11B11+A12B21,就这么几个乘法和加法,模型偶尔还是会把某一项漏掉,或者把行和列对应错。真正让我死心的是:错误不是随机的,每次错的地方还不太一样,根本没有一个稳定的纠错规律可循。

那次测试之后我得出一个结论:数学场景里,LLM不适合当“生产最终答案的人”。它更适合当“调度员”——负责把问题拆开、决定该调用什么东西、判断下一步动作,而真正的计算和变形,必须交给不会“胡说”的工具去做。

2. LLM在数学上的三块硬伤,以及为什么工具是必然解药

2.1 硬伤一:数值计算本质是"猜"

第一块硬伤最直观:让LLM做精确数值计算,它不是在算,而是在猜一个“看起来像答案的数字”。比如20位以内的整数乘法、带小数的浮点运算、多步除法,模型给的答案经常会在某一位莫名其妙地错掉。这背后同样是Token预测的机制——数字在它眼里只是文本片段,它知道1和2“长得像”,但并不知道1加2等于3这件事是确定性的。

处理办法其实很朴素:把计算任务从模型身上摘走。最简单的方法是让它生成Python表达式,然后用解释器去执行;稍复杂一点可以用任意精度计算库。我现在的做法是给Agent配一个数值计算工具,规定任何多步数值计算必须走工具,模型不直接输出数字。这样“猜”的问题就彻底消失了:工具的答案永远精确,唯一的风险只在于模型把输入表达式传错。

2.2 硬伤二:符号计算环节容易"变形失忆"

第二块硬伤更隐蔽,也更容易误导人。符号计算——比如多项式展开、因式分解、求导、化简——表面上是“写式子”,看上去LLM应该擅长,但实际上它对符号变形的处理极不稳定。

举个例子,因式分解x²-5x+6,正确答案是(x-2)(x-3)。模型可能给出正确答案,也可能给成(x-2)(x+3)或(x-6)(x+1),中间项的符号稍微一变,整道题就毁了。再比如把(x+1)^5展开成多项式,二项式系数1、5、10、10、5、1它一般能记住,但展开到第三项、第四项时,指数或系数偶尔会写串。问题在于,符号变形本质上要求每一步都严格保持等价关系,任何一步失忆都会让整个结果废掉。而LLM恰恰没有“验算上一步”的固有能力。

这个场景的解药是计算机代数系统,比如开源的SymPy。我提到的那个换元积分题,交给SymPy就是一条命令的事情,它直接返回ln(x²+3x+1),精确且可复现。Agent在这里的角色就变成了:识别用户输入属于“求积分”任务,然后调用符号积分工具,把工具返回的结果整理成答题步骤。模型负责“知道该求积分”,工具负责“真的求出积分”。

2.3 硬伤三:多步推导长距离"漂移"

第三块硬伤是长链推理漂移。一道证明题或者复合函数求导,第一行还对,第三行开始偏,最后一步彻底飞走。我观察到的规律是:中间步骤一旦出现一个微小错误,后面的所有步骤都会基于这个错误继续推,而且模型完全不会回头发现。这很像人类学生做题时“一步错步步错”,但人类至少还有可能在某一步觉得“不对劲”,LLM连这种直觉都没有。

应对漂移的思路有一条我很推荐:不要依赖模型内部一步步推导,把推理过程“外化”。什么叫外化?就是让模型把中间步骤写成代码、写成工具调用,每算一步都让工具返回确定的结果。比如让模型完成“先求导,再令导数等于零,再解方程”的流程,就不要让它一口气输出整个解答,而是规定它第一步调用求导工具,拿到结果后第二步调用求根工具,每一步工具都返回确定值,模型只负责解读这些值并决定下一步。这么一来,长链推理被拆成了短链,漂移的生存空间被压缩得极小。

顺带提一句,自洽性验证也有用——让模型对同一个问题生成多个解答,然后投票选出出现最多的答案。这在选择题、判断题上能提升正确率,但我觉得它只是“用重复对抗随机错误”,治标不治本。真正治本的办法,还是把推理拆开、交给工具逐步确认。到这里你应该也感觉到,单靠模型内部的能力天花板是有限的,想突破这个天花板,就必须把计算、变形、验证分包给外部工具——而这正是Agent的起点。

3. Agent闭环:让LLM从"写答案"变成"做实验"

3.1 为什么数学是Agent最自然的应用场景

先想想数学高手解题时在做什么。拿到一个问题,他不会直接就写答案,而是先读题、想思路、列式子、在草稿纸上算几步、验算一下,发现不行就换个方向再来。整个过程其实是一个不断“尝试-观察-调整”的实验闭环:脑子负责出思路,手负责算和写,眼睛负责看结果对不对。

LLM现在缺的就是这个闭环中的“手”和“眼睛”。它只有一张嘴,输出一段文本之后,既看不到自己写了什么符号,也无法确认结果是否真的成立。Agent结构正好补上这一环:LLM依然是“大脑”,负责生成策略和判断;外部工具是“手”,负责精确计算、符号变形、画图、验证;工具返回的结果则是“眼睛”,让模型看到行动输出,再决定下一步。

用一个场景类比:LLM单独做数学题,就像一个学生叫助手“你直接告诉我答案”,助手只能凭记忆空说;Agent则是一个研究助理工作台,助手可以翻参考书、按计算器、写程序和验算。你觉得哪种方式更靠谱?答案不言而喻。

3.2 最小闭环:规划→行动→观察→再规划

一个数学Agent的最小闭环其实只需要四步:

  1. 规划:LLM读问题,决定下一步要做“什么动作”,比如先简化解、求导、求根,或者查一个公式。
  2. 行动:把“动作”翻译成一次具体的工具调用,例如调用符号积分工具,传入被积函数表达式。
  3. 观察:工具执行后返回结果,这个结果会作为新消息放回对话历史里。
  4. 再规划:LLM基于观察结果,判断任务是否完成,如果没完成则继续走规划步骤。

这里的关键点是:循环不是预先把步骤写死的,而是由模型逐轮决定的。模型可以根据上一步的实际输出灵活调整。比如它第一步让符号计算工具化简一个表达式,发现化简结果仍然很复杂,它可以决定改用数值方法来求近似解;如果化简结果很简单,它可能直接决定输出答案。这个灵活性正是“Agent”和“脚本”的区别——脚本是固定动作序列,Agent是由决策驱动的动态流程。

3.3 三件套:工具、记忆、验证器

把Agent用于数学,我建议最少配齐三样东西:工具层、记忆层和验证层。

工具层是Agent的外部能力集合。按用途可以分成几类:数值计算类,负责精确算术和浮点运算;符号操作类,负责积分、求导、化简、因式分解;方程求根类,负责找数值解或符号解;可视化类,负责画函数图像,帮助模型“看一眼”函数形态;还有知识检索类,负责查公式和定理。

记忆层解决的是“做过什么”的问题。Agent在循环中会产生一系列中间步骤,如果不做记忆管理,要么上下文很快被塞满,要么模型开始重复之前做过的失败尝试。我的做法是维护一个简短工作记忆:当前目标、已尝试的路径、最近一次工具结果。每轮结束后把关键信息压缩成一两行摘要,防止模型在长流程里“失忆”。

验证层是数学Agent里被我放在最高优先级的部分。别的领域的Agent可以容忍“差不多”,数学不行。验证器最好用可执行断言,而不是让模型自己判断。比如得到积分结果后,验证器可以对结果求导,再看导数和被积函数是否等价;解方程之后,把根代回原方程看误差是否小于阈值。只有这种“工具级的裁判”才能真正判对错。

反思机制可以作为第四件套加上:当验证器判错时,把错误信息反馈给模型,让模型基于错误原因重新规划。这相当于给学生一本错题本——不是让他重新做一遍,而是让他看着错误反思自己的解题路径哪里出了问题。我后面实测中发现,反思是提升正确率非常有效的一招,但要注意别让模型陷入反复反思的怪圈,否则会浪费大量Token。

4. 从零搭一个数学Agent的最小骨架

4.1 先想清楚边界:不是要证明世界难题,而是要解题闭环

在动手之前必须明确一个目标:我们不是要造一个能攻克数学前沿问题的系统,而是要造一个“在可离散化为工具调用的问题上稳定不翻车”的解题器。这个边界很重要,它决定了系统设计的重点。

基于这个边界,我的核心原则是三句话:LLM做路线规划,工具做精确计算,验证器做最终裁判。模型不直接输出答案,而是输出“下一步该调用哪个工具、传入什么参数”;工具返回的确定结果再一步步被模型汇总结论。这样即便模型某个判断失误,工具层和验证层仍然能把错误拦截在出口处。

4.2 工具层的实际选择

我用Python写了一个最小的工具注册表。每个工具都定义三样信息:名字、用途描述、参数说明。为什么描述这么重要?因为模型全靠描述来决定调不调用,描述写得含糊,它就会乱传参数。

TOOLS = { "eval_numeric": { "desc": "计算某个数学表达式的数值。expression为合法Python表达式,例如'3**2 + 4'。", "params": {"expression": "str"} }, "sympy_calculate": { "desc": "对表达式执行符号运算,支持integrate积分、diff求导、simplify化简、factor因式分解。mode指定运算类型。", "params": {"mode": "str", "expression": "str"} }, "find_root": { "desc": "在区间[a, b]上寻找函数f(x)的数值根。f使用Python表达式表示,变量为x,例如'x**3 + x - 1'。", "params": {"f": "str", "a": "float", "b": "float"} }, }

我把这套注册表放进系统提示词里,然后告诉模型:遇到数学计算,先看工具注册表,能交给工具的任务绝对不要自己算。这个设定乍一听很简单,但它带来的效果非常明显——模型直接从“答题者”变成了“工具调用者”。

4.3 规划层的提示词与结构化输出

有了工具,下一步就是约束模型的输出格式。经验之谈:千万不要让模型自由发挥写大段话,一定要规定结构化输出。我用的格式是JSON,包含thought、action、action_input三个字段。

{ "thought": "这个表达式需要先化简,判断是否可以直接求积分", "action": "sympy_calculate", "action_input": { "mode": "integrate", "expression": "(2*x + 3)/(x**2 + 3*x + 1)" } }

主循环的伪代码也非常直白:

for step in range(MAX_STEPS): response = llm_chat(messages) cmd = parse_json(response) if cmd["action"] == "finish": return cmd["result"] result = execute_tool(cmd["action"], cmd["action_input"]) messages.append({ "role": "observation", "content": f"工具返回: {result}" })

这段代码里有两个细节我后来非常受益:一是把工具返回结果用“observation”角色注入,模型天然把“观察结果”当成外部信息来处理;二是设定MAX_STEPS上限,防止Agent在复杂问题上无限循环。我一般设8到10步,超过就直接让模型基于已有信息输出“最优猜测”,保证系统总能在有限时间内结束。

4.4 一个完整例子跑通:求x³+x=1在[0,1]上的根

为了让骨架更直观,我拿一个很具代表性的题目走一遍完整流程。题目是:求方程x³+x=1在区间[0,1]内的根。

第一轮,LLM读完题,输出thought:“这个方程不能直接手算,需要找数值根,区间是[0,1]。”然后action调用find_root,传入f="x**3 + x - 1",a=0,b=1。

工具返回0.6823278038281921。到这里,如果是一个“直接答题”的模型,它可能已经直接输出答案了。但我这个Agent没那么简单:LLM收到结果后,第二轮的thought是“我需要验证一下这个根是否正确”,于是它再次调用eval_numeric,计算f(0.6823278038281921)的值。

工具返回约-1.9e-16。这个结果非常接近0,LLM第三轮才判断“误差在可接受范围,任务完成”,然后输出结论:x约等于0.6823,且误差已经过数值验证。

不知道你注意到没有,整个过程最值钱的不是那一步求根——计算器也能做——而是Agent自己决定“求完根还要验算”。这就是从LLM到Agent的质变:它不再给一个孤立的答案,而是给出一个有验证链路、有置信度的结论。用户拿着这个结果可以放心引用。

5. 实测里踩过的坑,和现在的处理办法

5.1 当LLM生成工具参数时,参数也会传错

理想很丰满,现实很骨感。第一次跑完整闭环时,我发现在“模型做决策”这一步会出现新的错法:模型不是自己算错,而是把工具参数传错。最典型的是调用sympy_calculate时,把mode写成"integrate"却把表达式写成含有多个变量的,工具报错说变量不明确;或者求根时该传a=0、b=1,却传成了a=1、b=0,区间方向反了。

我的解决办法是双管齐下。第一,把工具描述写得更“啰嗦”,明确写出参数类型、边界含义、常见错误示例,甚至直接写“a必须小于b”;第二,工具执行出错时,把错误信息原封不动放回observation里,让模型基于错误信息重新生成调用。由于很多错误是参数层面的,模型看完错误信息后,大概率能自己修正。我加了“同一工具连续失败3次就换策略”的兜底规则,防止一直撞墙。

5.2 工具调用失败后,Agent会反复撞同一堵墙

这个坑更隐蔽:某个工具调用连续失败,模型却固执地用完全一样的参数重试。我第一次遇到是求根工具因为区间端点处函数值同号而报错,LLM收到错误后,第二轮的输出和第一轮完全一致——同一个工具、同一组参数,仿佛错误信息没被读到。这个问题如果不管,Agent会白白烧掉好几轮Token。

对策有两层。第一层是在系统提示词里规定:“如果某个工具连续两次返回错误,说明当前思路无效,必须换一种方法。”第二层是在代码里硬编码限制:对同一工具的连续失败次数计数,达到2次后,注入一条人工提醒“你正在重复失败操作,请换策略”。顺带提一嘴,模型对“错误信息”的敏感度其实没有我们想象中那么高,尤其当错误信息很长时,它容易忽略关键句。所以我每次只把工具返回的错误摘要放进observation,不塞完整堆栈。

5.3 上下文被"过程"撑爆,早期步骤被忘光

Agent一轮轮跑下来,observations越堆越多,很快把上下文窗口占满。更要命的是,模型在后期会遗忘早期步骤的关键信息——比如它一开始想好的边界条件,到了第五轮已经完全想不起来了,这等于长链漂移在Agent形态里又复活了。

我现在的做法是引入轻量级摘要机制。每一轮结束后,把当前状态压缩成一行摘要,比如“已在[0,1]找到候选根0.6823,误差1.9e-16,待最终确认”,然后把历史消息里的早期内容删掉,只保留摘要和最近两三条observation。这样既控制长度,又保证关键状态始终在上下文里。要注意的是,摘要生成本身也靠LLM,这就等于让模型“自己记笔记”,比堆原始历史可靠得多。

5.4 让LLM验证对错,等于没有验证

最后一个坑可能最有普遍性:我以为让Agent“自我检查”能提升准确率,结果发现LLM检查完还会说“结果正确”。有一次求积分,工具返回结果是正确的,但模型在检查环节说“分子分母有公因子,简化后应该再加一项”,硬生生把一个正确答案改成了错误答案。这说明让模型验证,等于让一个不可靠的裁判去判题。

我现在的原则是:所有验证必须由工具执行,模型只能解读工具的验证结果。比如验证积分,验证器调用sympy对结果求导,然后化简并与被积函数做差,差为0就通过;验证方程根,就是把根代回表达式看绝对值是否小于阈值。模型要做的只是看验证器返回的“PASS”或“FAIL”,而不是自己重新推一遍。这条原则立下来之后,系统的稳定性上了一个档次,错误不会再在验证环节被“重新引入”。

最后说点我个人体会。这套最小骨架跑通之后,我对“AI做数学”的预期发生了根本变化:不再奢望模型直接给出可靠答案,而是把它当成一个能自我安排验证流程的调度员。数学Agent做得好不好,往往不取决于模型本身有多强,而取决于工具层描述是否清晰、验证器是否严格、记忆管理是否到位。如果你也想上手试试,我建议先把工具注册表和验证脚本写扎实,再让LLM只负责聪明地编排。从LLM到Agent,本质上就是把推理从模型内部搬到外部世界——模型说方法,工具做计算,验证器给结论,三方配合,才能让数学题真正解明白。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询