资讯动态

构建数学解题Agent:从LLM局限到工具调用与验证优先架构

发布时间:2026/10/14 4:48:05 来源:尧图企业网站定制
1. 从“让模型直接做数学”到“让模型组织数学”写这篇的时候我翻了下前面三篇的留言发现问得最多的不是某一个具体提示词怎么写而是一类带着困惑的问题为什么我让大模型算个积分、解个方程它经常一本正经地给我一个错误答案为什么我加了“请一步一步思考”之后确实变聪明了一点但面对更复杂的数学问题还是翻车这些问题的本质其实指向同一个方向——你在用LLM的方式去干一件不是LLM天生擅长的事。单个大语言模型是一个语言系统不是一个计算系统。它是一个根据上下文预测下一个token的机器数学是它“见过很多”但“从没有真正算过”的东西。这就好比一个读了大量菜谱的人你让他描述酸菜鱼的步骤他能说得头头是道但你真让他去片鱼、调芡、控制油温他的手跟不上嘴。LLM在数学上的表现本质就是“嘴上功夫”远超“手上功夫”。所以当你在“AI for Math”这条路上走到第四篇必须完成一次思维切换不再指望模型直接吐出正确答案而是让模型当一个调度者把计算外包给确定性的工具把验证交给可靠的裁判自己只负责规划路线和组织进程——这就是Agent也就是智能体。这篇文章会把“从LLM到Agent”这条演进路径讲透。适合谁看适合已经被纯提示词工程折磨过一阵子、对RAG有基本概念、准备真正动手搭一个数学解题Agent的人。我会从LLM做数学为什么不行开始讲到工具调用如何补上短板再给出一个Math Agent的完整分层架构最后提供一个可以直接跑通的最小实现并把你大概率会踩的坑提前踩一遍。2. 为什么裸奔的LLM做数学总在“临界点”翻车2.1 训练目标和数学目标根本是两码事你得先接受一个有点反直觉的事实大模型在数学题上的错误不是“还不够聪明”而是“训练时的目标和你要的目标不一致”。LLM的训练目标是最大化下一个token的预测概率它学的是语言分布也就是“在人类写过的文本里某个位置更可能接什么词”。数学推导文本当然也在训练数据里所以模型确实学到了一些数学表达的“语言形态”——比如它知道积分题后面一般跟着“”知道洛必达法则出现时大概率要写“求导”。但它并没有一个内部机制去保证这些token之间的逻辑关系是自洽的。我用个生活化的类比一个外国人看了大量中文小说他能模仿出中文的语感写出来的句子语法通顺、用词考究但你要他算清楚“三个人分五张饼每人能得多少”他未必比一个小学生算得快。因为他学的是“中文”不是“算术”。LLM学的是“文本”不是“数学”。这个错位决定了它的天花板。2.2 三种典型的“裸奔翻车”现场这几年我在实际测试里见过了各种翻车方式总结下来就三类第一类是“自信的胡扯”。你让模型解一个三元一次方程组它可能先正确地写出了两个方程的变形然后在第三步莫名其妙地丢掉一个负号最后给出一个漂亮但错误的答案。更麻烦的是你问它“确定吗”它会说“确定”如果你追问“那代入验算一下”它甚至可能自动“修正”出另一个错误答案。这类错误在小学应用题、初中代数题里尤其多因为训练数据里这类题多、文本套路强模型很容易“顺着话头编下去”。第二类是“知识的幻觉”。研究生级别的代数题、数论题数据量少模型没见过几次它只能从相近的模式里“猜”。我试过一个高数问题让模型求某类广义积分的收敛性它给出一个听起来无比专业的答案里面的定理名称、作者名字全是对的但核心推导步骤完全是编的。这是LLM做数学最危险的地方——答案的结构性可信和事实性错误可以同时并存。第三类是“精确计算的崩溃”。大数乘法、高精度浮点计算、长除法这种纯计算任务模型几乎必挂。因为自回归生成是逐token输出的早期产生的一个小误差会在后面被指数级放大而且模型本身没有“进位”这种东西的概念它只是在模仿“看起来像计算的过程”。2.3 核心结论LLM的价值在组织不在计算如果只看上面的翻车案例你可能会觉得LLM在数学上一无是处。那就错了。它的强项在于三个东西语义理解、方案生成、步骤组织。你给它一道题它能判断出这是代数、几何还是数论问题它能记得住你之前给过的约束和偏好它能把自己的推理过程拆成步骤并根据你反馈的错误定位到具体环节。这些能力恰恰是“一个解题管理器”的核心素质。所以正确的分工非常清晰LLM负责“做什么、按什么顺序做、每一步需要什么工具”而“计算”“推导”“验算”这些必须交给确定性工具。这就是Agent思路的基本盘——拿语言的强项去指挥精确工具的强项而不是让语言模型硬着头皮去干精确计算的活。3. 从RAG到工具调用Agent的第一块跳板3.1 RAG解的是“遗忘症”不是“计算残疾”在聊Agent之前必须先把上一阶段的技术铺垫起来。从LLM到Agent中间有一个绕不开的过渡形态——RAG检索增强生成。很多人在RAG里加了一堆数学公式、教科书片段希望模型“看完资料就能算对”结果发现效果还是不理想。这不奇怪因为RAG解决的是知识的时效性和覆盖度问题本质上是在给模型“递小抄”。模型看了小抄确实能复述定理、引用定义但计算精度和逻辑自洽的问题小抄帮不上忙。数学领域的RAG有个特殊的坑数学符号的嵌入质量差。你用一个通用embedding模型去向量化带希腊字母、上下标、根号的数学文本出来的向量几乎区分不了“x_i”和“x^2”这种关键差异。所以我在做数学RAG的时候强烈建议对公式做规范化处理比如把LaTeX转成带语义Token的文本形式再入库否则检索召回的质量会让你怀疑人生。3.2 关键一跳模型学会“调用工具”RAG之后的下一个里程碑是工具调用Function Calling / Tool Use。这一步的意义在于模型从“必须自己说出答案”变成了“可以请求外部程序代为计算”。你不再问它“384729乘以294821等于多少”而是让它识别出这是一道乘法题然后调用一个计算器工具去精确求值最后把返回值组织成人类可读的答案。这个演进看似简单但它把一个根本性问题解决了把确定性还给工具把灵活性留给模型。模型负责理解题目、决定调用哪个函数、解析返回结果计算器负责保证每一个数字绝对正确。这就是Agent的第一个形态脱离纯文本生成进入“感知-决策-执行”的控制循环。3.3 思维链如何与工具链打通市面上很多数学Agent的做法是在思维链Chain-of-Thought里面埋工具调用点。具体来说模型的推理过程不再是一串连续的文本而是一串交替的“思考片段”和“工具调用片段”。比如解一道关于函数单调性的题模型先思考“需要求导”然后调用导数计算工具工具返回导函数模型再思考“需要判断导函数符号”于是调用符号计算工具求解不等式如此往复。这种模式的关键创新在于工具输出被当作新的观察结果重新进入模型的上下文驱动下一轮推理决策。这已经不是“预测下一个token”了而是一个闭环控制系统。为了实现这种闭环模型在训练或微调时被注入了“何时调用什么工具”的行为模式推理时按这个模式执行。我们今天聊的Agent核心骨架就是这个控制循环。4. Math Agent的分层架构大脑、手指与裁判4.1 经典三层规划层、工具层、验证层如果只给一个架构图的话我认为Math Agent最合理的切法是分成三层规划层大脑、工具层手指、验证层裁判。很多人的误区在于把这三个层次混淆在一起让模型既当运动员又当裁判那最后一定翻车。每一层的目标、实现方式、失败处理逻辑都应该是独立的。规划层的职责是理解问题、生成解题方案、维护步骤状态。它不负责算——负责想。工具层的职责是提供确定性的计算、推导、绘图、检索能力。它不负责想——负责做。验证层的职责是在关键节点上校验中间结果和最终答案的合理性它既不负责想也不负责做——负责审。这个设计思想的核心在于把不确定性尽可能控制在规划层把确定性尽可能下沉到工具层和验证层。语言模型的不确定性是不可避免的但我们可以通过架构设计让这种不确定性只影响“决策方向”而影响不了“计算结果”和“最终判分”。4.2 规划层拆解从单次生成到循环控制规划层最典型的实现是ReAct模式Reason Act模型先输出一段推理Reason说明当前状态、下一步打算做什么然后输出一个动作Act告诉系统要调用哪个工具、参数是什么。系统执行工具后把结果作为Observation观察回到模型上下文模型继续下一轮Reason-Act循环直到它认为得到最终答案。如果把这种循环写成伪代码大概是while not done: thought llm.generate(current_context, prompt请分析当前状态并说明下一步意图) action llm.generate(current_context, prompt根据你的分析选择调用哪个工具并给出参数) observation execute_tool(action) current_context observation done llm.decide_finish(current_context)你可能会发现这个循环里调用了多次LLM这在真实场景中不算便宜但保证了每一步决策都有上下文依据。我做项目的时候常用一个更省token的变体让模型一次生成“思考动作”的组合但解析时严格区分两个字段比如用JSON格式而不是让思维链和动作夹杂在一起解析起来会稳定很多。4.3 工具层拆解四类工具的选型清单工具层是Agent能力的边界你给Agent配了哪些工具它就是什么级别的数学家。我按实用频率排序列出四类必备工具第一类是符号计算引擎SymPy是这个领域的首选。多项式展开、因式分解、求导、求积分、解方程、矩阵运算这些全部可以直接调。你完全不需要让模型去算这些你的Agent只需要把问题翻译成SymPy调用代码。第二类是数值计算工具比如NumPy/SciPy或者更专业的mpmath高精度库。凡是涉及近似解、数值积分、方程求根、线性方程组数值解的交给这类工具比让模型算靠谱一万倍。第三类是定理证明器或形式化验证工具比如Lean、Isabelle。这是目前Math Agent最硬核的工具适合做严谨的数学证明验证。当然上手门槛也高如果你只是做习题级别的数学前期可以不用但如果你要做研究级别的验证这是值得啃的方向。第四类是知识检索工具包括之前提到的数学RAG以及Wolfram Alpha之类的外部知识API。这类工具的价值在于让Agent能拿到自己训练数据里没有的信息比如某个定理的最新形式、某个特殊函数的展开式。注意检索本身不保证正确只能保证——有资料可以参考比凭空生成可靠。4.4 验证层拆解为什么“验证优先”能救回无数分数我最想强调的其实是验证层。因为95%的数学Agent Demo翻车都翻在没有验证环节。什么叫做验证就是你得到最终答案之后不要直接输出而是用一个独立于生成路径的方法去确认它的正确性。最简单的验证是代入验算解出方程根后把它代回原方程看是否成立。较高级的验证是双向计算比如求积分后用求导检验看导函数是否等于原被积函数。再高级的是用定理证明器做形式化检查。Verifier的设计原则是——它的行动路径必须和生成路径独立。如果你的模型是用求积分工具一步步推出答案的那验证就不该再用同一个求积分工具去“验证”而应选择求导确认这种对称操作。这里有个术语叫“验证优先”Verification-First与其费劲提升生成器的成功率不如先搭好验证器让生成器去自由探索最后统一验证。这个思想在代码生成领域已经被证明极其有效拿到数学领域同样成立——生成一百个候选答案用验证器筛掉九十九个错的比提高生成器单个准确率到百分之九十九实现成本低得多。5. 手把手搭建一个最小可用的数学解题Agent5.1 场景选取为什么用“多项式综合题”作为起步理论讲完来点实操。我选一个最经典的起步场景求解一道包含“展开、因式分解、求根、验证”的代数综合题。比如给一个多项式表达式要求(1)展开并合并同类项(2)因式分解(3)求根(4)验证。这个场景完美覆盖了Agent的基本循环——需要多轮工具调用、需要状态管理、需要结果反馈但难度又低到你可以在一个下午内调通。我见过太多人一上来就想做一个“解任意数学题”的通用Agent结果被工具调用格式问题、符号解析问题、错误反馈问题轮番折磨最后不了了之。正确路线是先用一个窄场景把循环跑通再逐步扩工具集和场景集。先把“形状”做对再谈“规模”。5.2 代码骨架与运行流程拆解下面是我实际在用的一个最小骨架基于Python核心依赖是openai或任意兼容API的SDK、sympy、json。我先定义工具函数然后写一个支持多轮调用的Agent循环。先看工具定义import sympy as sp x sp.symbols(x) TOOLS { expand: { description: 展开并合并多项式同类项参数expr字符串形式的代数表达式, function: lambda expr: sp.expand(sp.sympify(expr)) }, factor: { description: 对代数表达式进行因式分解参数expr字符串形式的代数表达式, function: lambda expr: sp.factor(sp.sympify(expr)) }, solve: { description: 求解方程或方程组的根参数expr等于0的方程左侧表达式, function: lambda expr: sp.solve(sp.Eq(sp.sympify(expr), 0), x) }, simplify: { description: 化简代数表达式参数expr字符串形式的代数表达式, function: lambda expr: sp.simplify(sp.sympify(expr)) } }这里的核心设计是工具层的每个函数都接收字符串、返回字符串。原因很简单——LLM与工具之间交换的数据格式越简单越不容易出错。SymPy的符号对象直接塞回给模型是灾难模型根本看不懂SymPy对象内部结构。全部转成字符串模型处理起来最顺手。再看Agent主循环def run_math_agent(question: str, max_steps: int 5): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: question} ] for step in range(max_steps): resp llm.chat(messages, toolsTOOL_SCHEMAS) msg resp.choices[0].message if msg.tool_calls: messages.append({ role: assistant, content: msg.content, tool_calls: msg.tool_calls }) for call in msg.tool_calls: tool_name call.function.name args json.loads(call.function.arguments) result TOOLS[tool_name][function](args[expr]) messages.append({ role: tool, tool_call_id: call.id, content: str(result) }) else: final_answer msg.content verification verify_answer(question, final_answer) if verification[is_correct]: return final_answer else: messages.append({ role: user, content: f你的答案是 {final_answer}但验证不通过{verification[detail]}。请重新求解。 }) return 达到最大步骤数未能获得通过验证的答案。注意到循环里的关键结构当模型没有发起工具调用而是给出了最终答案时并不是直接返回而是先进入验证环节。验证通过才输出不通过就把错误信息反馈回去让模型重新走一遍工具调用流程。这里的“反馈-重试”机制是Agent容错能力的核心。很多实现把验证环节省略了那Agent的容错也就形同虚设。5.3 验证函数怎么写一道题的三重检查继续看验证部分的实现思路。为了体现“验证优先”我写了一个三层递进的验证器def verify_answer(question: str, answer: str) - dict: checks [] # 第一重结构检查——答案是否包含树形推导痕迹 if 步骤 not in answer and not in answer: checks.append(答案缺少推导过程或步骤标记) # 第二重数值检查——提取答案中的最终结果回代原问题验证 # 假设答案是 x 2 或 x -1 roots_found re.findall(rx\s*\s*(-?\d), answer) if roots_found: for root in roots_found: substitution sp.sympify(ORIGINAL_EXPR).subs(x, int(root)) if substitution ! 0: checks.append(f根 {root} 代入原式结果不为0答案有误) # 第三重形式一致性检查——答案中的符号与题目中的符号集是否匹配 question_symbols set(re.findall(r[a-z], question.lower())) # 排除常见虚词字母后检查答案是否引入了题目不存在的符号 unexpected set(re.findall(r[a-z], answer.lower())) - question_symbols - {x, e, i} if unexpected: checks.append(f答案中出现了题目未定义的符号: {unexpected}) return {is_correct: len(checks) 0, detail: ; .join(checks)}这三重检查的意义在于用三种不同维度的信息交叉验证结构维度看“形式完整性”数值维度看“结果正确性”符号维度看“语义一致性”。任何一重不通过都必须返工重来。不会每次都抓到所有错误但能把大部分低级错误拦截在交付之前。5.4 口头推演一遍Agent是怎么思考的为了让你对运行流程有直观感知我用一道题目推演一遍。问题是“展开 (x2)^2(x-1)并因式分解结果然后求出所有实数根。”第一轮模型收到问题后在上下文中思考。它决定先展开于是发起工具调用expand参数是(x2)^2(x-1)。工具层返回x^3 3*x^2 - 4。第二轮模型看到展开结果继续思考。它决定因式分解于是发起工具调用factor参数是x^3 3*x^2 - 4。工具层返回(x-1)*(x2)^2。第三轮模型看到因式分解结果决定求根。它发起solve参数是x^3 3*x^2 - 4。工具层返回[1, -2]。第四轮模型组装最终答案展开结果是x^3 3*x^2 - 4因式分解结果是(x-1)*(x2)^2实数根是x1和x-2二重根。随后验证器提取根1和-2代入原式验算结果均为0验证通过答案输出。整条链路里模型只在“判断下一步做什么”上出智力在“计算”上一步都没自己做。这就是Agent的干活方式不是Model一个人扛而是Model带着一群精确工具协同作战。5.5 接入真实LLM时的三个落地提示第一个提示如果用OpenAI风格的工具调用API工具定义TOOL_SCHEMAS需要严格按照JSON Schema格式写。description字段一定要写清楚每个参数的语义和格式比如明确告诉模型“expr是一个字符串形式的数学表达式遵循SymPy语法”。描述写不清楚模型就会乱传参。第二个提示SymPy的sympify在解析字符串时会抛异常比如用户写了一个Python不支持的语法“2x”而不是“2*x”就会报错。这个报错会直接返回给Agent循环模型大概率会被报错信息搞晕。所以建议在工具函数里加一层try-except把SymPy解析异常翻译成人类可读的纠错提示比如“请检查表达式乘号不能省略”。第三个提示token预算问题。每一轮工具调用都会把工具返回结果拼接到上下文里如果表达式很长上下文会迅速膨胀。我的经验是限制工具返回结果的长度——比如超过500个字符就做截断或摘要。否则Agent在第4、5轮时上下文已经塞满了历史记录模型很容易忘记最初题目是什么开始跑偏。6. 实战中反复踩过的坑与排查思路6.1 模型“讲得像在计算实际上在背诵答案”一种特别隐蔽的翻车模式你给Agent一道题它没有调用任何工具直接像背书一样给出了完整推理过程最后答案还是对的。这种“背诵式作答”在常见题型上很容易蒙混过关但换一个数字、变一下结构模型就会露馅。排查方法很简单——在System Prompt里写死所有数学运算步骤必须通过工具调用完成禁止直接进行算术推导。如果模型背的答案工具调用日志里至少会暴露它一次工具都没调。我实际遇到过更狡猾的版本模型调用了工具但只在第一轮调用了后面全是自己推导。原因是它判断“后面的计算我自己来就行”。解决方法是调低模型的“自信阈值”在Prompt里明确指示任何涉及符号变换、数值计算的操作都必须调用工具即使你觉得很简单。6.2 工具调用格式的“死锁”工具调用最经典的错误是参数格式不匹配。你定义的工具要求JSON格式模型却传了YAML格式你要求expr字段是字符串模型却塞了一个对象结构你定义了两个参数模型直接漏掉一个。高级模型一般不会犯这类低级错误但稍老的模型会频繁踩坑。排查思路把所有工具调用的原始传输记录保存到日志文件里肉眼比对失败案例中模型传的参数结构与你们定义的工具格式之间的差异。如果发现模型频繁在某个工具上传错参数不要指望改Prompt能解决直接把参数校验逻辑前置工具入口处做一次严格的格式解析不符合要求的直接返回“参数解析失败请检查工具参数格式”而不是让它带着错误参数进入计算流程。6.3 验证器“叛变”验证逻辑本身有漏洞验证器不是万能的。我之前犯过一个经典错误用SymPy的sp.prove去验证一个不等式但实际这个不等式在SymPy的默认假设体系里是验证不了的结果验证器返回True因为测试跑通了但假设不成立导致Agent输出一个错误结论。后来我反省过来验证器的可靠性必须独立测试——拿一批已知正确和已知错误的案例去校准验证器确认它不会把错的全放过去。另外验证器不能和生成器的实现完全同构。如果你生成答案用的是SymPy的solve验证也用的solve那当SymPy的solve本身对这个方程处理有问题时你就等于“既当裁判又当运动员”。我后面做验证时基本上优先用独立的数值代入验证和形式化定理证明器复审双保险。6.4 环境依赖导致的“玄学”问题SymPy版本不同部分函数的返回结果会有差异。比如sp.solve在遇到某些高次方程时旧版本返回列表、新版本返回有限集Agent解析结果的逻辑就要兼容这两种情况。建议在项目启动时锁死依赖版本并且写一个回归测试集确保工具升级后所有接口行为不变。6.5 数学Agent的“能力边界”设定最后一条不能算坑但比坑更值得警惕。Agent工具越多、能力越强就越容易膨胀。我在实践中获得的经验是一定要在System Prompt里明确写下Agent的能力边界。比如“本Agent只处理高中及大学低年级代数与微积分问题超出此范围请明确告知用户能力不足”而不是放任它在能力边界上自由发挥。数学领域最怕半懂不懂——它知道自己不知道和它以为自己知道这是两个完全不同的行为表现。设置边界本质上是在保护Agent不进入幻觉区间。7. 一点个人体会把这套东西从理论落地成代码再反复跑通、翻车、修正我最大的感受是Agent不是把LLM变强了而是把LLM放到了合适的位置上。纯LLM做数学是让它一个人身兼数职Agent是让它在自己的岗位上做自己擅长的事——规划、组织、决策把不擅长的精确计算交给工具把终究判断交给验证器。如果你按这篇文章的骨架搭出了一个能跑的数学Agent我建议你做的第一件事不是增加工具、扩大题库而是试着在验证器上多花一倍时间。把验证器做得越严你的Agent在用户面前的可靠性就越高。工具可以慢慢加场景可以慢慢扩但一个每次都能在交付前拦截错误答案的验证层才是这个Agent真正的护城河。这个思路不仅适用于数学题在代码生成、数据分析、学术检索等所有LLM落地的场景里都是通用的。下一篇我会继续深入Agent的另一个核心能力——多工具并行调度与自主探索策略也就是当Agent面对一个没人告诉它“该用什么工具”的开放性问题时它该如何自主决策。如果你在搭建过程中遇到有意思的翻车案例欢迎带着日志来聊我觉得真实故障比任何原理说明都更能推进理解。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑