先说说一个整体性的判断:如果你想为AI数学辅导系统装上真正的“大脑”,让它准确理解题目意图,而不是依赖关键词猜测,那么RAG(检索增强生成)技术就是实现这一目标的关键模块。从关键词匹配到语义向量检索,这一步看似只是技术细节的调整,实际上决定了AI解析数学问题时能达到的精准上限。
举个例子,当用户输入“解方程 3x + 5 = 11”,系统的处理流程绝不仅仅是“看到方程就输出解题步骤”那么简单。它需要先识别出这是一道一元一次方程,然后从知识库中调取相应的解法逻辑,最终生成逐步解析内容。在这个过程中,“题目识别”和“知识调取”正是RAG发挥作用的核心环节。
接下来,我们一步步拆解,如何将一个简单的AI数学解题系统,升级为具备语义理解能力的智能辅导工具。
第一步:准备知识库
在 backend 目录下创建一个 data/math_knowledge.json 文件,写入一些基础的数学知识点。内容结构包含知识点名称、描述、解法、公式等。这一步是基础,但关键在于——后续所有的检索能力,都依赖这个文件的丰富程度。
[{"name": "一元一次方程","description": "只含有一个未知数,且未知数最高次数为1的方程。","method": "移项、合并同类项、系数化为1","formula": "ax + b = 0"},{"name": "二元一次方程组","description": "含有两个未知数的一次方程组。","method": "代入法、加减消元法"},{"name": "勾股定理","description": "直角三角形三边关系","formula": "a² + b² = c²"},{"name": "一次函数","description": "函数形式 y = kx + b","method": "求斜率和截距"}]
第二步:新增RAG服务
新建 backend/app/rag_service.py,这是检索服务的核心。逻辑其实并不复杂:加载知识库文件,对每个知识点进行评分,基于关键词和文本重叠度进行匹配,然后返回最相关的前几条结果。
这里的 score_item 函数是关键——它通过比对问题中的关键词和知识库中条目里的关键词、标题、内容等字段,计算出一个匹配度分数。分数越高,代表知识库中的某条信息与当前问题的相关性越强。
代码实现上,retrieve_knowledge 负责排序和截取,build_context 则将检索到的内容组装成一段上下文文本,后续会直接注入到LLM的提问中。这种“先检索,再生成”的架构,比直接让模型作答要靠谱得多。
第三步:改造AI解析
打开 backend/app/llm_service.py,这里需要做两件事:一是导入刚才写好的 build_context,二是在调用大模型时,把检索到的知识库内容拼接进用户的提问里。
注意看代码中的 user_content 构建部分:如果context不为空,会在原始问题后面追加一段“以下是可参考的知识库内容,请优先基于这些内容解答”。这个设计很巧妙——它既保留了模型的自主推理能力,又通过外部知识库来约束和引导模型的输出方向,减少了“幻觉”风险。
测试一下,基本功能没问题。但问题在于,这只是关键词匹配版RAG——它只能识别硬性关键词命中,无法理解语义。比如“直角三角形斜边长度”和“勾股定理”,在关键词匹配版里,可能因为缺少“勾股”这个词而匹配失败。
更进一步:RAG向量数据库版
想要真正理解语义,就得用向量检索。这里选了一个本地可跑、无需单独起Docker的架构:Qdrant本地模式做向量库,bge-small-zh-v1.5做向量模型,知识库文件沿用现有的 backend/data/math_knowledge.json。
目标很明确:把“关键词匹配版RAG”升级成“语义检索版RAG”。
1)安装依赖
在 backend 目录执行:pip install qdrant-client sentence-transformers
2)重写 rag_service.py
这次改动比较大。核心思路是:将知识库中的每一条内容,用sentence-transformer编码成向量,存储到Qdrant中;当用户提问时,同样将问题编码成向量,通过余弦相似度找到最相近的几条知识条目。
这里有三个关键函数需要注意:
rebuild_index():全量重建索引。每次知识库内容更新后,需要调用这个函数重新编码和入库。ensure_index_ready():启动时自动检查索引是否存在,如果为空则重建。这个设计让系统启动更健壮。retrieve_knowledge():核心检索函数。将问题编码后,调用Qdrant的query_points接口,返回最相似的前top_k条记录。
值得注意的是,build_doc_text() 将知识条目的标题、关键词、内容拼接成一个长文本,作为向量化的输入。这个拼接方式会影响向量的语义质量,实际使用中可以根据效果不断调优。
3)修改 llm_service.py
改动不大,主要是确认import正确,以及将 get_knowledge_titles 的返回值注入到最终返回的数据结构中。这样前端可以知道当前题目命中了哪些知识点。
代码中有一个小细节:在JSON解析后,额外添加了 data["matched_knowledge"] = matched_knowledge。这使得前端能展示“本题关联知识点”,对教学场景特别有用。
4)修改 main.py
需要补充两个点:一是程序启动时调用 ensure_index_ready() 确保索引就绪;二是新增一个 /api/rag/rebuild 接口,方便运维或管理员手动重建索引。
为什么需要手动重建的接口?因为向量索引一旦建立,知识库内容更新后不会自动同步。如果数学公式库增加了新的知识点,必须调用重建接口才能让新内容被检索到。
5)知识库文件扩展
原来的 math_knowledge.json 内容太少,向量版本需要更丰富的条目才能体现语义匹配的优势。这里扩展了六个知识点:一元一次方程基础、二元一次方程组、分数加减法、一次函数基础、勾股定理、三角形内角和。每个条目都增加了更多的关键词和详细内容。
关键词的丰富度直接影响检索效果。比如“勾股定理”条目的关键词包含“勾股定理”、“直角三角形”、“平方和”、“斜边”,这样用户问“斜边怎么算”时也能被准确匹配。
6)前端添加“重建知识库索引”按钮
这一步虽然简单,但链条必须走通:
- 在
frontend/src/api/math.ts新增rebuildRagIndex函数 - 在
StudentBar.vue的模板和emits中新增按钮和事件 - 在
App.vue中引入函数并绑定事件处理函数
这个按钮的设计让系统运维变得灵活——非技术人员也能在UI上点击一下完成索引重建,而不需要登录服务器执行脚本。
7)启动与验证
回到 backend 目录,执行启动命令。然后可以用三个测试用例来验证:
- 测试1:
解方程 3x + 5 = 11—— 应该命中“一元一次方程基础” - 测试2:
在直角三角形中,两条直角边分别是3和4,斜边是多少?—— 应该命中“勾股定理” - 测试3:
已知一次函数 y = 2x + 1,求 x = 3 时 y 的值—— 应该命中“一次函数基础”
功能正常。对比之前的关键词匹配版本,语义向量检索的优势明显:即使用户的措辞不太精确,只要语义相近,系统也能找到最相关的知识点。
本质区别
之前是“猜词游戏”——系统只会匹配字面上重复的关键词,遇到同义词或不同表述就无能为力。现在是“理解式检索”——通过向量化将文本转化为数学空间中的坐标点,直接用语义距离来衡量相关性。
这个升级看似只是技术栈的替换,但对于教育类AI系统来说,意味着从“死板”到“灵活”的跨越。而且,整个架构可以在单机环境下完整运行,不需要连接外部向量数据库服务,这对个人开发者或小团队来说非常友好。
功能验证通过,系统已经稳定运行。
