VibeMathed:用自然语言+符号计算构建AI数学解题应用
你在平时做题、备课或者写教学材料时可能遇到过这样的尴尬局面拿一道数学题去问通用 AI它能把题干分析得头头是道但一到复杂计算环节就开始“一本正经地胡说八道”而传统计算工具虽然算得准却不会给你讲步骤更看不懂你描述的“人话”。VibeMathed 这个项目思路就是想用“自然语言描述数学问题 AI 理解并生成解题步骤 符号计算引擎兜底验证”的方式把上面两端的能力拼在一起。本文将围绕这条主线完整拆解一个可运行的 AI 数学解题应用从设计到落地的全过程。1. 项目背景VibeMathed 到底在解决什么问题1.1 VibeMathed 是什么VibeMathed 的名字可以拆开看Vibe 来自 Vibe Coding 的概念指的是“用自然语言描述意图让 AI 自动完成代码实现”Mathed 则是 Math 过去式的组合。串起来理解就是让用户用自然语言、口语化地描述一道数学题AI 负责理解题意、规划解法、生成推导步骤并最终输出答案。你可以直接把它看作一个“数学题解答引擎”。用户输入解方程x^2 3x - 4 0或者更口语化一些一个矩形的长比宽多 3 厘米面积是 40 平方厘米长和宽各是多少VibeMathed 都能把它转化成结构化的解题流程输出分步解答和最终结论。1.2 它解决的痛点现有工具在“数学解题”这件事上往往各有短板工具类型优点缺点通用大语言模型理解自然语言能力好能生成解题步骤复杂计算容易算错存在幻觉Symbolab、Wolfram Alpha 等计算平台计算准确能出步骤部分功能付费对口语化题目的理解不够灵活人工解题准确、有教学思路成本高、耗时无法批量处理VibeMathed 的核心思路是把语言理解能力交给大模型把计算准确性交给符号计算引擎。两者结合之后既保留了“自然语言提问”的友好交互又用代码验证兜住了大模型“算错数”的风险。1.3 典型使用场景学生做作业时遇到不会的题想看到分步推导过程。老师批量准备习题讲解文案需要快速生成规范的解答步骤。家长辅导孩子时对某些知识点记忆模糊需要 AI 先给出过程再自己复述。在线教育平台接入自动答疑能力作为教学辅助。这也是为什么把这个项目定义为“AI 工程实践”而非单纯的“大模型 Demo”它包含了提示词设计、结构化解题输出、计算结果验证、交互界面封装等一整套可落地的流程。2. 整体技术方案与系统设计2.1 总体架构VibeMathed 采用分层设计共四层交互层 - 解析层 - 推理层 - 验证层 Streamlit 提示词解析 LLM 解题 SymPy 校验交互层用户输入题目系统展示解题步骤和答案。解析层通过提示词让大模型把自然语言数学题解析成结构化任务。推理层大模型根据结构化任务生成分步解题过程和一段可执行验证代码。验证层用 SymPy 在本地执行验证代码判断最终答案是否成立。每层之间用标准 JSON 格式传递数据这样大模型输出的结果可以直接被下一层消费也方便后续扩展新的前端界面或者 API 服务。2.2 为什么不用纯 LLM也不只用 SymPy如果只用大模型交互最自然但计算不稳定。如果只用 SymPy计算绝对可靠但它只能处理已经“符号化”的题目无法理解“长比宽多 3 厘米”这种口语描述。VibeMathed 把两者结合起来大模型负责“读题”和“讲题”SymPy 负责“算题”和“验题”。大模型输出的验证代码即使第一次写错系统也可以把错误信息回传给大模型进行二次修正逐步逼近正确结果。2.3 模块职责划分模块文件职责配置管理config.py读取环境变量管理 API Key 和模型参数LLM 客户端llm_client.py封装大模型 API 调用统一出入参提示词管理prompts.py维护解题和解析的提示词模板解题服务solver.py组装完整链路处理 JSON 解析与重试验证服务verifier.py执行 SymPy 验证代码返回校验结果入口服务app.pyStreamlit 界面接收输入并展示结果3. 环境准备与项目结构3.1 环境依赖本文示例在 Python 3.10 环境下开发。需要安装以下依赖pip install openai sympy streamlit python-dotenv版本说明本文不绑定某个具体的大模型版本示例中以openai兼容接口为例。如果你使用的是其他厂商的模型服务只要提供兼容 OpenAI 格式的接口或者将llm_client.py中的调用方式替换为对应的 SDK 即可。依赖版本需要根据你的实际环境调整。3.2 项目目录结构vibemathed/ ├── app.py # Streamlit 入口 ├── config.py # 配置项 ├── llm_client.py # 大模型客户端 ├── prompts.py # 提示词模板 ├── solver.py # 核心解题服务 ├── verifier.py # SymPy 验证 ├── requirements.txt # 依赖清单 └── .env # 密钥配置不要提交到仓库3.3 环境变量配置在.env文件中配置大模型 API 信息# 复制后按实际情况填写 LLM_API_KEYyour_api_key_here LLM_BASE_URL LLM_MODELgpt-4o-mini如果使用官方接口LLM_BASE_URL留空即可如果使用国内云厂商的兼容接口则填对应的网关地址。生产环境下API Key 应通过环境变量或密钥管理系统注入不要写死在代码里。4. 核心功能模块实现4.1 配置模块 config.pyimport os from dotenv import load_dotenv load_dotenv() LLM_API_KEY os.getenv(LLM_API_KEY, ) LLM_BASE_URL os.getenv(LLM_BASE_URL, ) LLM_MODEL os.getenv(LLM_MODEL, gpt-4o-mini)4.2 LLM 客户端封装 llm_client.py统一封装大模型调用方便后续替换模型服务from openai import OpenAI from config import LLM_API_KEY, LLM_BASE_URL, LLM_MODEL client_kwargs {api_key: LLM_API_KEY} if LLM_BASE_URL: client_kwargs[base_url] LLM_BASE_URL client OpenAI(**client_kwargs) def chat(messages, temperature0.2, max_tokens3000): 调用大模型返回文本内容 resp client.chat.completions.create( modelLLM_MODEL, messagesmessages, temperaturetemperature, max_tokensmax_tokens, ) return resp.choices[0].message.content把 LLM 调用单独封装一层是为了让核心业务代码只依赖chat()函数。即使后续更换服务商也只需要改这个文件。4.3 提示词模板 prompts.py提示词是整个系统效果的关键。我们需要让大模型输出两样东西一是分步解题过程二是可执行的 SymPy 验证代码。为了让结果稳定要求大模型必须输出 JSON。SYSTEM_PROMPT 你是一位严谨的数学老师同时也是一名熟练的 Python 代码工程师。 你的任务是根据用户给定的数学题完成以下工作 1. 理解题目提取数学表达式或方程。 2. 给出清晰的分步解答步骤要适合学生学习。 3. 编写一段可执行的 SymPy 验证代码用来校验最终答案。 你必须严格按照下面的 JSON 格式返回不要输出任何多余内容 { 解析: 对题目的理解以及符号定义, 步骤: [第一步..., 第二步..., 第三步...], 最终答案: x ..., 验证代码: import sympy as sp\n...可执行代码 } 注意事项 - 验证代码必须是完整可运行的 Python 代码。 - 如果题目不能计算或无解请说明原因。 - 如果题目不是数学题请明确拒绝回答。 USER_PROMPT_TEMPLATE 题目{question} 请生成结构化 JSON 输出。这里强调 Json 输出的原因是LLM 天然输出非结构化文本如果不用格式约束后面 JSON 解析会非常痛苦。把格式要求写进 System Prompt并说明“不要输出任何多余内容”能显著提高解析成功率。4.4 核心解题服务 solver.pysolver.py 负责把整个流程串起来。由于大模型偶尔会输出非标准 JSON或者首轮验证代码有问题这里做了两件事JSON 容错提取和一次失败重试。import json import re from llm_client import chat from prompts import SYSTEM_PROMPT, USER_PROMPT_TEMPLATE def extract_json(text: str) - dict: 兼容 LLM 将 JSON 包裹在代码块中的情况 text text.strip() # 去掉 json 和 标记 if text.startswith(): text re.sub(r^(?:json)?\s*, , text) text re.sub(r\s*$, , text) return json.loads(text) def solve_math_problem(question: str): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: USER_PROMPT_TEMPLATE.format(questionquestion)}, ] for attempt in range(2): raw chat(messages, temperature0.2) try: result extract_json(raw) if not result.get(验证代码): raise ValueError(缺少验证代码) return result except (json.JSONDecodeError, ValueError) as e: if attempt 1: # 第二次失败时把错误信息反馈给大模型要求重新生成 messages.append({role: user, content: f你上次的输出无法解析{e}请重新输出合法 JSON。}) raw chat(messages, temperature0.1) return extract_json(raw) raise RuntimeError(大模型输出解析失败)这里的容错思路值得展开说一下。第一轮失败时不要直接放弃第二轮把错误信息拼进上下文让大模型“看到自己的问题”并修正这是目前 AI 工程中非常实用的交互策略。4.5 SymPy 验证模块 verifier.py验证模块是整个系统准确性的最后防线。我们使用exec在受限环境下执行大模型生成的代码并捕获异常。import sympy as sp import traceback def run_verification(check_code: str, timeout: int 10) - dict: 执行 SymPy 验证代码。 返回 {ok: bool, output: str} # 允许访问的全局对象 allowed_globals { sympy: sp, sp: sp, pprint: sp.pprint, solve: sp.solve, symbols: sp.symbols, Eq: sp.Eq, simplify: sp.simplify, } try: # 注意timeout 是 Python 层面的大致限制 # 生产环境建议使用 subprocess 或沙箱隔离 exec exec(check_code, allowed_globals) return {ok: True, output: 验证代码执行完成未发现明显错误。} except Exception: return { ok: False, output: traceback.format_exc(), }需要特别说明使用exec执行大模型生成的代码属于一个安全敏感操作。原因在于如果你没有对输出做严格约束模型可能在极端情况下生成访问文件系统、网络或其他危险操作的代码。本文示例中的白名单全局变量只是最基础的限制。生产环境建议用 AST 静态分析过滤掉 import、open、exec 等敏感节点。用沙箱容器或 subprocess 隔离执行设置 CPU 和内存限制。只允许 SymPy 相关的白名单 API。4.6 主流程串联 pipeline我们把“解题 验证”合并成一个入口便于前端调用def full_pipeline(question: str) - dict: result solve_math_problem(question) verify_result run_verification(result.get(验证代码, )) return { question: question, analysis: result.get(解析, ), steps: result.get(步骤, []), final_answer: result.get(最终答案, ), verify_ok: verify_result[ok], verify_output: verify_result[output], raw_check_code: result.get(验证代码, ), }5. 基于 Streamlit 构建交互界面5.1 页面设计为了让项目不只是命令行工具我们用 Streamlit 写一个简单的交互页面。用户输入题目后点击按钮触发上面那条完整链路页面按三个区块展示题目解析、分步步骤、验证结果。import streamlit as st from solver import solve_math_problem from verifier import run_verification st.set_page_config(page_titleVibeMathed, page_icon) st.title(VibeMathed - AI 数学解题助手) question st.text_area( 请输入你的数学题, placeholder例如解方程 x^2 3x - 4 0, height100, ) if st.button(开始解题, typeprimary): if not question.strip(): st.warning(请先输入题目) else: with st.spinner(AI 正在解题并验证...): try: result solve_math_problem(question) verify run_verification(result.get(验证代码, )) st.subheader(题目解析) st.write(result.get(解析, )) st.subheader(解题步骤) for i, step in enumerate(result.get(步骤, []), 1): st.markdown(f{i}. {step}) st.subheader(最终答案) st.success(result.get(最终答案, )) st.subheader(SymPy 验证) if verify[ok]: st.success(验证通过代码执行未报错) else: st.error(验证异常请参考下方日志) st.code(verify[output], languagetext) with st.expander(查看验证代码): st.code(result.get(验证代码, ), languagepython) except Exception as e: st.error(f解题失败{e}) st.markdown(---) st.caption(VibeMathed 使用大模型生成解题思路并用 SymPy 进行校验。结果仅供参考复杂题目请人工复核。)5.2 运行项目启动命令streamlit run app.py页面默认端口是 8501。浏览器访问后输入题目点击“开始解题”就能看到完整输出链路。6. 运行演示与结果说明6.1 示例一一元二次方程输入解方程x^2 3x - 4 0预期流程解析层输出识别为一元二次方程目标为求 x 的解。推理层生成步骤写出判别式Delta 3^2 - 4 * 1 * (-4) 25因为 Delta 0方程有两个不相等的实根。代入求根公式得 x 1 或 x -4。验证层生成 SymPy 代码使用sp.solve求解并对比。这一步能直观看到模型给出了教学式讲解而验证代码保证了“最终答案确实是方程的解”。6.2 示例二应用题输入一个矩形的长比宽多 3 厘米面积是 40 平方厘米求长和宽。预期输出步骤设宽为 w长为 w 3。根据面积公式列方程w * (w 3) 40。展开并求解w^2 3w - 40 0解得 w 5 或 w -8。舍去负值得到宽为 5 厘米长为 8 厘米。这里的价值在于从自然语言到代数方程这一步是传统计算工具很难自动完成的而大模型可以很轻松地完成语义转换。6.3 示例三非法输入输入今天天气怎么样合规的处理方式应当是系统提示“这似乎不是一道数学题请重新输入”。这也是在提示词中明确要求“非数学题拒绝回答”的意义所在避免把闲聊内容硬塞进数学解题链路。7. 常见问题与排查思路7.1 高频问题排查表问题现象常见原因解决思路调用大模型接口报错API Key 未配置或额度不足检查 .env 和账号余额输出不是合法 JSON模型输出被截断或格式漂移加入容错解析把错误信息回传重试验证代码执行失败模型生成的 SymPy 语法不正确捕获 traceback 并回传给模型修正页面无法启动Streamlit 或依赖未装齐重新执行 pip install -r requirements.txt答案与验证结果不一致模型计算幻觉以 SymPy 验证结果为准提示模型重新推导接口响应慢模型上下文过长或网络波动裁剪上下文、设置超时、增加缓存7.2 大模型输出不稳定怎么办这是做 LLM 应用最常遇到的问题。我的建议是提示词中给出明确的 JSON 示例最好给一个完整例子。设置较低 temperature例如 0.1 ~ 0.2降低随机性。解析失败时把错误信息拼回去重新请求而不是直接放弃。如果场景固定只允许有限题型可以先用分类模型做题型识别再走不同分支提示词。7.3 LLM 幻觉问题如何兜底数学场景的幻觉主要体现在“编造一个看似合理的错误答案”。VibeMathed 的兜底方案就是让 SymPy 去验证。如果验证代码本身没有报错但算出结果和模型给出的“最终答案”不同我们应该以验证结果为准并在界面中提示“验证结果与模型生成结论不一致请以验证结果为准”。更进一步可以把验证结果反馈给模型让它重新解释差异形成第二次推导。8. 工程化建议与安全边界8.1 提示词设计的最佳实践提示词不要写得过长但关键约束必须明确。我习惯把提示词拆成三层角色定义让模型知道自己是“数学老师 Python 工程师”。输出约束规定 JSON 字段、代码格式、拒绝规则。示例引导如果预算允许在提示词中加一个小例子效果会明显提升。8.2 密钥与敏感信息管理禁止把 API Key 提交到 Git 仓库。.env文件要加入.gitignore。生产环境建议使用云厂商的密钥管理服务或者通过 CI/CD 环境变量注入。若涉及用户数据还需遵守数据合规要求必须获得用户合法授权后才能把题目内容发送给模型服务商。8.3 代码执行安全前面已经提到exec执行大模型代码存在风险。生产环境绝对不要直接裸奔需要用 AST 过滤危险节点例如import os、open、eval、exec等。使用受限子进程执行并设置资源限制。对执行结果做超时处理避免无限循环。# 示例用 AST 做简单的危险操作检查 import ast DANGER_NODES (ast.Import, ast.ImportFrom, ast.Call) def check_code_safe(code: str) - bool: tree ast.parse(code) for node in ast.walk(tree): if isinstance(node, ast.Call): func node.func if isinstance(func, ast.Name) and func.id in {open, eval, exec, __import__}: return False if isinstance(node, DANGER_NODES): return False return True这段代码只是演示思路。实际工程中建议使用正规的沙箱方案甚至直接把验证代码放到独立容器中运行。8.4 成本与性能优化大模型调用并不是免费的可以从几个方面优化设置合理的缓存层相同题目直接返回历史结果。上下文裁剪只保留必要的题目内容不要把历史聊天记录全塞进请求。失败重试次数控制在 1 到 2 次避免恶意输入造成大量 token 消耗。对输入长度做限制防止超大文本被一次性发给模型。8.5 可扩展性思考当前架构中LLM 的调用、SymPy 验证、前端交互是解耦的。这意味着你可以很容易地把前端换成 API 接口或者把验证引擎从 SymPy 换成其他符号计算库。如果题目涉及图像也可以加入 OCR 识别模块把图片转为文本再走现有链路。9. 总结与下一步扩展方向VibeMathed 的核心价值是提供了一套“自然语言理解 符号计算验证”的混合解题方案。通过提示词让大模型输出结构化 JSON再通过 SymPy 执行验证既解决了自然语言到数学表达式的转换问题也抑制了模型在数值计算上的幻觉。整个系统从代码结构上划分了配置、客户端、提示词、服务、验证和界面六个模块每个模块都可以独立替换和升级。如果后续想继续扩展这个项目有以下几个方向可以尝试接入 OCR 模型支持拍照上传数学题。增加题型分类器针对不同题型优化提示词分支。把验证代码的运存放进 Docker 沙箱提升安全性。增加“重新推导”按钮当验证失败时自动把错误日志回传给模型进行二次修正。增加历史记录面板方便学生回顾之前的解题过程。数学问题自动求解是一个很有代表性的 AI 工程场景它既考验语言理解又要求结果严谨。VibeMathed 的混合架构思路同样可以平移到其他需要“理解 计算”的领域例如物理题、金融计算、代码错误修复等。你可以先按本文的代码跑通一个最小版本再根据自己的需求逐步扩展亲自感受一下“AI 负责思考、代码负责验证”这种组合带来的稳定性提升。如果卡在环境配置或结果解析上欢迎在评论区留言交流。

相关新闻