夜雨聆风学习资料网

ARTICLE · 1220406

开发了一个初中数学错题智能问答系统

开发了一个初中数学错题智能问答系统

哈喽大家好,我是盟主,今天咱们动手搭建一个初中数学错题智能问答 Agent。

平时我们做错题AI答疑,很容易遇到几个痛点: 普通RAG是固定流水线,只检索一次就生成,资料不足时容易编造内容,数学场景下经常出现推导跳步、定理误用的问题; 同样一道题目,学生是计算失误还是概念理解错误,对应的讲解重点完全不同,简单问答很难针对个人错因做个性化诊断; 大模型输出不稳定,每次讲解的详略、风格差异较大,缺少一套自动机制判断回答质量; 如果想要实现重新检索、反思校验、打分重试这类循环逻辑,靠普通链式调用编写会非常混乱。

Agentic RAG、LangGraph、自动化评测三者结合刚好可以解决这类问题:

  • Agentic RAG:智能体自主判断是否检索、检索内容,支持改写查询重复检索
  • LangGraph:基于状态图管理执行流程,天然支持分支、循环、回退逻辑
  • 自动化评测:独立Judge Agent对讲解内容打分,不达标自动重试生成

说明:本文为最小可用原型。生产环境可替换数学专用embedding、LaTeX解析、OCR识别、向量数据库以及学生学情库。 依赖安装命令:

pip install langgraph langchain langchain-openai chromadb python-dotenv

整体执行流程

接收错题(题干+学生作答) → 解析错题,定位错误点 → 规划检索query,查询知识库知识点、同类错题 → 检索获取文档信息 → 生成启发式讲解 → Judge自动化评测:正确性、步骤严谨度、学情适配度打分 → 分数不达标:回到查询规划节点重新检索生成;分数达标:输出最终讲解

LangGraph的核心是共享state,所有节点读取并更新这份状态,条件边控制流转方向。

完整可运行代码

新建 math_error_agent.py

from typing import TypedDict, Listfrom dotenv import load_dotenvimport osfrom langchain_openai import ChatOpenAI, OpenAIEmbeddingsfrom langchain_chroma import Chromafrom langgraph.graph import StateGraph, ENDfrom langchain_core.prompts import ChatPromptTemplateload_dotenv()# ===================== 1. 定义全局状态 State =====================classMathState(TypedDict):    raw_input: str          # 用户输入:题干 + 学生作答    problem_info: str       # 解析后的题目信息    student_error_point: str# 定位到的错误点、错因标签    sub_queries: List[str]  # 检索子query    retrieved_docs: str     # 检索拿到的知识库内容    draft_answer: str       # 初稿讲解    eval_score: float       # 评测分数 0-10    eval_comment: str       # 评测意见    final_answer: str       # 最终输出答案# ===================== 2. 初始化大模型、向量知识库(模拟错题库) =====================llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.2)embedding = OpenAIEmbeddings()# 构造一个极简初中数学错题知识库,真实场景换成大量结构化错题、定理math_knowledge = ["一元一次方程移项规则:移项要变号,不移不变号;常见错误:移项忘记变号。","三角形全等判定SSS:三边对应相等,两三角形全等;SSA不能判定全等。","不等式两边同时乘除负数,不等号方向必须反转,这是高频易错点。","去括号法则:括号前是负号,括号里面每一项都要变号。",]vectorstore = Chroma.from_texts(math_knowledge, embedding=embedding, persist_directory="./math_db")retriever = vectorstore.as_retriever(search_kwargs={"k":2})# ===================== 3. 各个节点Agent函数 =====================defparse_error_node(state: MathState) -> MathState:"""节点1:错题解析Agent,找出学生错在哪"""    prompt = ChatPromptTemplate.from_template("""你是初中数学错题分析师。用户输入包含【题目】和【学生写的答案】,帮我:1. 提炼题目信息2. 精准定位学生的错误点,标注错因(概念不清/计算失误/审题错误/定理误用)输出格式:题目信息:xxx学生错因:xxx输入:{raw_input}""")    chain = prompt | llm    res = chain.invoke({"raw_input": state["raw_input"]}).content# 简单分割    lines = res.split("\n")    problem_info = ""    error_point = ""for line in lines:if line.startswith("题目信息:"):            problem_info = line.replace("题目信息:","")if line.startswith("学生错因:"):            error_point = line.replace("学生错因:","")return {"problem_info": problem_info,"student_error_point": error_point    }defplan_query_node(state: MathState) -> MathState:"""节点2:查询规划Agent,根据错因生成检索query"""    prompt = ChatPromptTemplate.from_template("""根据下面题目信息和学生错因,生成2个检索关键词,用来在数学知识库查找相关知识点、易错点。只输出一行,用 || 分割两个query。题目:{problem_info}错因:{student_error_point}""")    chain = prompt | llm    qs = chain.invoke({"problem_info": state["problem_info"],"student_error_point": state["student_error_point"]    }).content    sub_queries = [q.strip() for q in qs.split("||")]return {"sub_queries": sub_queries}defretrieve_node(state: MathState) -> MathState:"""节点3:检索Agent,执行检索,合并文档"""    all_docs = []for q in state["sub_queries"]:        docs = retriever.invoke(q)        all_docs.extend([d.page_content for d in docs])    unique_docs = list(set(all_docs))return {"retrieved_docs": "\n".join(unique_docs)}defgenerate_answer_node(state: MathState) -> MathState:"""节点4:答疑生成Agent,启发式讲解,不直接喂答案"""    prompt = ChatPromptTemplate.from_template("""你是初中数学辅导老师,禁止直接把完整答案丢给学生,优先启发引导。结合知识库内容,针对学生的错误点讲解:指出错在哪、原理是什么、给引导提问。知识库参考:{retrieved_docs}题目:{problem_info}学生错因:{student_error_point}""")    chain = prompt | llm    ans = chain.invoke({"retrieved_docs": state["retrieved_docs"],"problem_info": state["problem_info"],"student_error_point": state["student_error_point"]    }).contentreturn {"draft_answer": ans}defevaluate_node(state: MathState) -> MathState:"""节点5:自动化评测Judge Agent!核心闭环"""    prompt = ChatPromptTemplate.from_template("""你是数学答疑评审Judge,按照下面3个维度打分,总分0-10分1. 正确性(4分):知识点、定理有没有错2. 步骤严谨性(3分):有没有跳步,逻辑通顺吗3. 学情适配度(3分):是不是启发式,有没有直接替学生做题输出严格格式:分数:数字评审意见:xxx待评审讲解内容:{draft_answer}题目和错因:{problem_info} | {student_error_point}""")    chain = prompt | llm    eval_res = chain.invoke({"draft_answer": state["draft_answer"],"problem_info": state["problem_info"],"student_error_point": state["student_error_point"]}).content    score = 0.0    comment = ""for line in eval_res.split("\n"):if line.startswith("分数:"):            score = float(line.replace("分数:",""))if line.startswith("评审意见:"):            comment = line.replace("评审意见:","")return {"eval_score": score, "eval_comment": comment}# ===================== 4. 路由判断函数:决定下一步怎么走 =====================defroute_after_eval(state: MathState):"""评测分支:≥7分合格输出;低于7分,重新走检索生成"""if state["eval_score"] >=7:return"pass"else:        print(f"⚠️ 评测不通过,分数:{state['eval_score']},意见:{state['eval_comment']},重新检索生成")return"retry"deffinal_node(state: MathState) -> MathState:return {"final_answer": state["draft_answer"]}# ===================== 5. 组装LangGraph图 =====================builder = StateGraph(MathState)builder.add_node("parse_error", parse_error_node)builder.add_node("plan_query", plan_query_node)builder.add_node("retrieve", retrieve_node)builder.add_node("generate", generate_answer_node)builder.add_node("evaluate", evaluate_node)builder.add_node("final_out", final_node)# 边连接builder.set_entry_point("parse_error")builder.add_edge("parse_error", "plan_query")builder.add_edge("plan_query", "retrieve")builder.add_edge("retrieve", "generate")builder.add_edge("generate", "evaluate")# 条件分支builder.add_conditional_edges("evaluate",    route_after_eval,    {"pass": "final_out","retry": "plan_query"    })builder.add_edge("final_out", END)graph = builder.compile()# ===================== 6. 测试入口 =====================if __name__ == "__main__":    user_input = """题目:解不等式 -2x + 1 > 5学生作答:-2x>4 → x > -2"""    result = graph.invoke({"raw_input": user_input})    print("===== 最终讲解 =====")    print(result["final_answer"])    print(f"\n【评测得分】{result['eval_score']}")    print(f"【评审意见】{result['eval_comment']}")

运行代码后,会识别出学生错误为不等式除以负数没有反转不等号。Judge自动打分,如果讲解质量不达标,系统自动回到查询规划节点,重新检索并生成讲解,形成闭环。

核心要点

1. 为什么这是 Agentic RAG,不是普通RAG?

普通RAG的查询固定,只检索一次就结束生成。 Agentic RAG将检索作为可调用工具,支持循环检索。在当前链路中,如果评测不合格,会重新规划query、再次检索,智能体具备反思能力。

2. LangGraph的价值

不使用LangGraph的情况下,循环重试逻辑需要手动编写while循环、维护大量变量、处理分支判断,代码维护成本高。 LangGraph把全部信息收敛到State中,每个节点只负责单一职责,条件边声明式定义流转分支,后续新增节点非常方便。

3. 自动化Judge是闭环核心

很多错题Agent只做到生成答案,缺少质量管控。教育场景对准确性要求很高,知识点讲解错误会误导学生。 Judge作为独立的LLM调用模块,有明确的打分规则,设置阈值7分,低于阈值自动重试。 注意:尽量不要让Judge和生成节点使用同一个弱模型,容易出现自校验失效的问题。

原型局限与进阶方向

当前版本属于最小Demo,适合理解整体架构,上线使用可以补充下面模块:

  1. 数学预处理模块:OCR识别手写错题、LaTeX公式解析;
  2. 向量库优化:批量导入教材、同步错题、变式题,使用数学领域Embedding;
  3. 增加符号计算工具:接入SymPy,自动验算方程、不等式,降低大模型主观打分带来的偏差;
  4. 学情模块:在State中增加学生标识、历史错题记录,实现讲解难度自适应;
  5. 人工审核节点:利用LangGraph的human-in-the-loop能力,多次评测低分的题目流转给教师复核;
  6. 增加缓存、限流、前端页面,封装为网页或者小程序端错题答疑服务。

相关学习资料