基于知识图谱与Qwen-Plus大模型的智能出题系统设计
2026/8/31 14:50:44 网站建设 项目流程

每年到毕业设计答辩季,都有不少同学被“智能出题”“在线考试系统”这类题目困住。大部分人的第一版方案,不过是把题目提前存进数据库,再按随机数抽取几道题拼成一张试卷。这种方案虽然能跑通演示,但很难讲出技术深度,答辩时也容易被老师追问到答不上来。

这篇文章我会完整拆解一个更符合当前技术趋势的毕设思路:把知识图谱和 Qwen-Plus 大模型接进智能出题系统,让系统能够根据知识点之间的关联关系自动选点,再调用大模型生成新题目,并结合错题记录做薄弱知识点分析。整体方案包含系统架构、知识图谱设计、Qwen-Plus 接入代码、错题分析逻辑、答辩准备建议,源码思路可以直接复用到你的毕业设计项目里。

文章适合正在做考试系统、作业系统、智能学习平台类毕设的同学。如果你已经在传统选题上投入了不少代码,也可以只把“大模型出题”和“知识图谱选题”两个模块抽出来,作为系统的亮点功能。

1. 项目背景与核心概念

1.1 为什么选“智能出题系统”作为毕设

毕设选题最怕两件事:一是题目太大做不完,二是题目太小没东西可写。在线考试系统、题库管理系统属于经典业务系统,功能无非是用户管理、题目管理、考试记录、成绩统计,做完之后技术点平平无奇。而“智能出题系统”在这个基础之上,增加了两个有讨论价值的核心问题:

  • 题目从哪里来?传统做法是人工录入,系统只是“取题”;更好的做法是让系统按知识点和难度自动生成题目。
  • 题目怎么选?传统做法是纯随机;更好的做法是结合知识图谱,先定位本次考试要覆盖的知识点范围,再根据知识点之间的依赖关系出题。

把这两点做进去,项目的创新性就有了。再加上“错题分析”模块,就能形成“知识点建模 -> 智能出题 -> 答题 -> 错题诊断 -> 再次出题”的闭环。这在教育领域的个性化学习场景里,是非常经典的产品思路。

1.2 Qwen-Plus 是什么,能做什么

Qwen-Plus 是阿里云百炼平台提供的大语言模型服务,属于通义千问系列。它能完成文本生成、代码生成、JSON 结构化输出、数学题解答等任务。对毕设项目来说,最关键的一点是它提供了 OpenAI 兼容的 API 访问方式,简单来说就是你用很熟悉的接口写法,就能让系统自动生成一道带答案、带解析、带知识点标签的题目。

相比自己维护几万道题的题库,调用大模型生成题目的优势非常明显:

  • 题目数量不受人工录入限制。
  • 可以指定考察范围、难度、题型。
  • 生成结果包含详细解析,适合直接展示给学生。
  • 题目可以做到“每次生成都不一样”,体现系统的智能感。

当然,大模型不是万能的。它可能生成重复题、超纲题,甚至出现答案错误。所以在系统设计里,需要加一层“题目校验与人工审核”机制,这也是答辩时值得展开讲的内容。

1.3 知识图谱在出题系统里的作用

知识图谱本质上是一种用图结构描述实体及其关系的技术。在题库场景下,实体是“知识点”,实体之间的关系可以是:

  • 前置关系:学“一元二次方程求根公式”之前,必须先掌握“一元一次方程”。
  • 父子关系:“函数”是“一次函数”的父知识点。
  • 相似关系:“等差数列”和“等比数列”在题型上有相似性。

有了这些关系,系统在出题时就不是盲目选题,而是能像老师一样思考:这次考试范围是“函数”,其中“二次函数图像”是核心知识点,而且学生之前做错过“顶点坐标”相关题目,那就优先围绕这个薄弱点出题。

把知识图谱和大模型结合起来,是当前很多智能教育产品的技术方向。毕设里能把这个结合讲清楚,整个项目的层次会明显不一样。

2. 系统总体设计

2.1 技术选型

毕设项目一般不建议使用过于冷门的技术栈,因为老师不一定熟悉,答辩时反而解释成本高。推荐以下组合:

模块技术选型说明
前端Vue 3 + Element Plus后台管理界面、考试页面、错题展示
后端Spring Boot 2.7 或 3.x提供 RESTful API,处理业务逻辑
数据库MySQL 8.0存储用户、题目、考试记录、错题记录
知识图谱Neo4j 5.x存储知识点实体及关系
大模型Qwen-Plus智能生成题目、生成知识点解释
数据处理Python 3.10批量导入知识点数据、离线分析错题

如果不想写两套后端,也可以只用 Spring Boot 完成全部功能,Python 脚本只负责数据预处理和知识图谱批量导入。这样项目结构更简单,部署时也少踩坑。

2.2 功能模块划分

整个项目按功能可以拆成四个核心模块:

  • 用户与权限模块:学生、教师、管理员三种角色;教师可以管理题库和知识点,学生可以参加考试和查看错题。
  • 知识图谱模块:维护知识点节点与关系,支持可视化展示。
  • 智能出题模块:结合知识图谱选中知识点范围,调用 Qwen-Plus 生成题目,并对生成题目做校验。
  • 错题分析模块:记录学生错题,将错题映射到知识点,计算薄弱度,输出学情报告。

2.3 核心业务流程图

整个系统的核心流程可以描述为:

教师设置考试范围(章节/知识点) -> 系统从知识图谱中选出相关知识点 -> 按一定规则过滤(难度、上次出题时间) -> 构建提示词调用 Qwen-Plus -> 解析返回的 JSON 题目 -> 教师审核/一键入库 -> 学生答题 -> 系统记录错题 -> 错题关联知识点 -> 分析薄弱知识点 -> 下次出题优先选择薄弱知识点

这个流程每一步都可以在答辩 PPT 里单独画一页,讲清楚“数据怎么流动”,比单纯堆积截图更有说服力。

3. 环境准备与项目初始化

3.1 本地开发环境

以下是本文示例使用的开发环境。版本并不唯一,你的电脑上如果已经装好其他版本,只要能正常运行即可,重点是理解配置思路。

工具版本建议用途
JDK1.8 或 11 或 17Spring Boot 运行依赖
Maven3.6+后端依赖管理
Node.js16+前端项目运行
MySQL8.0业务数据存储
Neo4j4.4 或 5.x知识图谱存储
Python3.8+数据处理与图谱导入脚本
IDEA2023+后端开发 IDE
VSCode任意版本前端开发 IDE

如果你使用的是 Spring Boot 2.7,JDK 1.8 到 17 都可以。如果使用 Spring Boot 3.x,JDK 需要 17 以上。建议不要在这个环节浪费太多时间,用你自己最熟悉的版本组合即可。

3.2 申请 Qwen-Plus API Key

这是整个项目里唯一需要“外部依赖”的环节。操作路径如下:

登录阿里云百炼平台,进入控制台,开通大模型服务。然后在 API-KEY 管理页面创建新的 API Key。创建后把 Key 复制出来,保存到自己项目的配置文件中,注意不要提交到 Git 仓库。

调用 Qwen-Plus 会产生少量费用。毕设演示阶段的调用量一般不会太大,费用通常可以控制在很低的范围。如果完全不想花钱,也可以找一些有免费额度的国内大模型平台替换,但代码结构基本不变,只需要改接口地址和模型名称。

3.3 后端项目初始化

使用 Spring Initializr 创建项目,选择 Web、MySQL Driver、Spring Data JPA(或 MyBatis-Plus)、Validation 这几个依赖。项目名称可以是ai-exam-system

这里需要额外引入两个依赖:一个是spring-boot-starter-web用于写 REST 接口,另一个是spring-boot-starter-data-neo4j用于操作 Neo4j。在pom.xml中加入:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-neo4j</artifactId> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency>

我这里使用了 OkHttp 作为 HTTP 客户端来调用大模型 API。你也可以用 Spring 自带的RestTemplate,或者更现代的WebClient,看个人习惯。

3.4 配置文件

application.yml中,至少需要配置 MySQL、Neo4j 和自定义的大模型参数:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/ai_exam?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_mysql_password driver-class-name: com.mysql.cj.jdbc.Driver data: neo4j: uri: bolt://localhost:7687 username: neo4j password: your_neo4j_password database: neo4j # 自定义大模型配置 ai: qwen: api-key: sk-xxxxxxxxxxxxx base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 model: qwen-plus max-tokens: 2048 temperature: 0.6

base-url这里使用的是阿里云百炼的 OpenAI 兼容地址。不同平台的地址不一样,如果你的项目后续换了平台,只需要修改这个配置项。

4. 知识图谱设计与构建

4.1 实体与关系设计

在出题系统场景下,知识图谱不需要做得太复杂,设计三个实体就够了:

  • 课程(Course):比如“初中数学”。
  • 章节(Chapter):比如“一元二次方程”。
  • 知识点(KnowledgePoint):比如“根的判别式”。

实体关系可以定义为:

(:Course)-[:HAS_CHAPTER]->(:Chapter) (:Chapter)-[:HAS_POINT]->(:KnowledgePoint) (:KnowledgePoint)-[:PREREQUISITE_OF]->(:KnowledgePoint) (:KnowledgePoint)-[:PARENT_OF]->(:KnowledgePoint)

最后两种关系比较关键。PREREQUISITE_OF表示前置依赖,比如“求根公式”是“韦达定理”的前置知识。PARENT_OF表示父子关系,比如“二次函数”是“二次函数顶点坐标”的父知识点。

设计好关系后,系统在出题时就能做到:

  • 教师选择“二次函数”作为考点,系统自动关联出子知识点。
  • 学生错了一道“韦达定理”的题,系统分析后发现是“求根公式”没掌握,于是推荐前置知识点的练习题。

4.2 使用 Cypher 创建知识点

Neo4j 支持通过 Cypher 语句操作数据。下面是最基础的知识点创建语句:

CREATE (p:KnowledgePoint {name: '一元二次方程求解', difficulty: 2, type: 'COMPUTE', description: '使用公式法解一元二次方程'})

创建两个知识点之间的关系:

MATCH (a:KnowledgePoint {name: '一元一次方程'}) MATCH (b:KnowledgePoint {name: '一元二次方程求解'}) CREATE (a)-[:PREREQUISITE_OF]->(b)

这种语句适合少量数据测试。如果知识点有几百上千条,建议用 Python 脚本读取 Excel 或 CSV 文件批量导入,效率更高,也方便后续维护。

4.3 Python 批量导入脚本

/scripts/import_knowledge.py中,可以使用neo4j驱动批量创建节点和关系。示例代码:

from neo4j import GraphDatabase URI = "bolt://localhost:7687" AUTH = ("neo4j", "your_password") class KnowledgeGraphImporter: def __init__(self): self.driver = GraphDatabase.driver(URI, auth=AUTH) def close(self): self.driver.close() def create_point(self, tx, name, difficulty, description): query = ( "MERGE (p:KnowledgePoint {name: $name}) " "SET p.difficulty = $difficulty, p.description = $description " "RETURN p" ) tx.run(query, name=name, difficulty=difficulty, description=description) def create_relation(self, tx, source, target, rel_type): query = ( "MATCH (a:KnowledgePoint {name: $source}) " "MATCH (b:KnowledgePoint {name: $target}) " "MERGE (a)-[r:" + rel_type + "]->(b)" ) tx.run(query, source=source, target=target) def import_data(self, points, relations): with self.driver.session() as session: for point in points: session.execute_write(self.create_point, point["name"], point["difficulty"], point["description"]) for rel in relations: session.execute_write(self.create_relation, rel["source"], rel["target"], rel["type"]) print(f"导入完成:{len(points)} 个知识点,{len(relations)} 条关系") if __name__ == "__main__": points_data = [ {"name": "一元一次方程", "difficulty": 1, "description": "最基础的线性方程"}, {"name": "一元二次方程求解", "difficulty": 3, "description": "使用求根公式解方程"}, {"name": "根的判别式", "difficulty": 3, "description": "判断方程根的情况"}, {"name": "韦达定理", "difficulty": 4, "description": "掌握根与系数的关系"}, ] relations_data = [ {"source": "一元一次方程", "target": "一元二次方程求解", "type": "PREREQUISITE_OF"}, {"source": "一元二次方程求解", "target": "根的判别式", "type": "PREREQUISITE_OF"}, {"source": "一元二次方程求解", "target": "韦达定理", "type": "PREREQUISITE_OF"}, ] importer = KnowledgeGraphImporter() importer.import_data(points_data, relations_data) importer.close()

这里使用MERGE而不是CREATE,好处是重复执行不会产生重复节点,适合开发阶段反复调试数据。

4.4 Spring Boot 中查询知识图谱

在 Spring Boot 中,可以直接使用 Neo4j 官方提供的Neo4jTemplate来执行 Cypher 查询。下面是一个根据知识点名称查询其前置知识的示例:

// 文件路径:src/main/java/com/example/aiexam/service/KnowledgeGraphService.java import org.springframework.data.neo4j.core.Neo4jTemplate; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Property; import org.springframework.stereotype.Service; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.GeneratedValue; import java.util.List; import java.util.Map; @Node("KnowledgePoint") class KnowledgePointNode { @Id @GeneratedValue private Long id; @Property("name") private String name; @Property("difficulty") private Integer difficulty; @Property("description") private String description; // 省略 getter/setter } @Service public class KnowledgeGraphService { private final Neo4jTemplate neo4jTemplate; public KnowledgeGraphService(Neo4jTemplate neo4jTemplate) { this.neo4jTemplate = neo4jTemplate; } public List<KnowledgePointNode> findPrerequisitePoints(String pointName) { String cypher = """ MATCH (p:KnowledgePoint {name: $name})<-[:PREREQUISITE_OF]-(pre:KnowledgePoint) RETURN pre """; return neo4jTemplate.query(cypher, Map.of("name", pointName), KnowledgePointNode.class); } public List<KnowledgePointNode> findChildPoints(String parentName) { String cypher = """ MATCH (p:KnowledgePoint {name: $name})<-[:PARENT_OF]-(child:KnowledgePoint) RETURN child """; return neo4jTemplate.query(cypher, Map.of("name", parentName), KnowledgePointNode.class); } }

这里使用@Node注解将实体类和图数据中的知识点节点映射起来。Neo4jTemplate的使用方式和 JdbcTemplate 类似,容易上手。

5. 智能出题模块:调用 Qwen-Plus

5.1 提示词设计

大模型生成题目的质量,很大程度上取决于提示词写得是否清晰。下面是本项目出题场景的提示词模板。

你是一位经验丰富的中考数学命题老师。请根据以下要求生成题目: 1. 考察知识点:{{knowledgePoint}} 2. 题目难度:{{difficulty}}(1-5,数字越大越难) 3. 题型:{{questionType}} 4. 题目数量:1 5. 考察维度:理解、计算、应用 要求: - 题目必须贴合指定知识点,不得超纲。 - 难度必须与指定难度匹配。 - 必须包含答案和详细解析。 - 解析中需要说明解答思路。 - 按 JSON 格式输出,结构如下: { "question": "题目内容", "options": ["A. xxx", "B. xxx", "C. xxx", "D. xxx"], "answer": "正确答案选项", "analysis": "详细解析", "knowledgePoint": "关联知识点", "difficulty": 3 } - 如果题型是填空题或解答题,options 字段使用空数组。

在生产环境中,提示词建议维护在数据库或配置中心,方便调整。毕设中可以直接把模板写在代码里或资源文件中。

5.2 后端调用 Qwen-Plus 的完整代码

使用 OkHttp 发送 HTTP 请求,请求体使用 OpenAI 兼容格式。这里给出一个QuestionGeneratorService的核心实现:

// 文件路径:src/main/java/com/example/aiexam/service/QuestionGeneratorService.java import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import okhttp3.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.concurrent.TimeUnit; @Service public class QuestionGeneratorService { private final OkHttpClient httpClient = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .build(); @Value("${ai.qwen.api-key}") private String apiKey; @Value("${ai.qwen.base-url}") private String baseUrl; @Value("${ai.qwen.model}") private String model; @Value("${ai.qwen.max-tokens}") private Integer maxTokens; @Value("${ai.qwen.temperature}") private Double temperature; public JSONObject generateQuestion(String knowledgePoint, Integer difficulty, String questionType) throws IOException { // 构建提示词 String prompt = buildPrompt(knowledgePoint, difficulty, questionType); // 构造 OpenAI 兼容请求体 JSONObject requestBody = new JSONObject(); requestBody.put("model", model); requestBody.put("max_tokens", maxTokens); requestBody.put("temperature", temperature); JSONObject message = new JSONObject(); message.put("role", "user"); message.put("content", prompt); JSONArray messages = new JSONArray(); messages.add(message); requestBody.put("messages", messages); Request request = new Request.Builder() .url(baseUrl + "/chat/completions") .addHeader("Authorization", "Bearer " + apiKey) .addHeader("Content-Type", "application/json") .post(RequestBody.create(JSON.toJSONString(requestBody), MediaType.parse("application/json; charset=utf-8"))) .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("调用大模型失败,HTTP 状态码: " + response.code() + ", 响应: " + response.body().string()); } String responseBody = response.body().string(); JSONObject jsonObject = JSON.parseObject(responseBody); // 解析 OpenAI 兼容接口返回结构 String content = jsonObject.getJSONArray("choices") .getJSONObject(0) .getJSONObject("message") .getString("content"); // 大模型可能返回纯 JSON,也可能包括 ```json 代码块 String cleanedContent = content.replace("```json", "").replace("```", "").trim(); return JSON.parseObject(cleanedContent); } } private String buildPrompt(String knowledgePoint, Integer difficulty, String questionType) { return """ 你是一位经验丰富的中考数学命题老师。请根据以下要求生成题目: 1. 考察知识点:%s 2. 题目难度:%d(1-5,数字越大越难) 3. 题型:%s 要求: - 题目必须贴合指定知识点,不得超纲。 - 难度必须与指定难度匹配。 - 必须包含答案和详细解析。 - 解析中需要说明解答思路。 - 按 JSON 格式输出。 输出格式: { "question": "题目内容", "options": ["A. xxx", "B. xxx", "C. xxx", "D. xxx"], "answer": "正确答案选项", "analysis": "详细解析", "knowledgePoint": "关联知识点", "difficulty": 3 } 如果题型是填空题或解答题,options 字段使用空数组。 """.formatted(knowledgePoint, difficulty, questionType); } }

这段代码的核心逻辑并不复杂:拼接提示词,发送 HTTP 请求,解析返回的 JSON。但有几个容易踩坑的地方需要特别注意:

  • 大模型返回的内容里有时会包含 Markdown 代码块标记 ```json,解析前必须先剔除。
  • 超时时间不能设置太短,大模型生成一篇带解析的题目可能需要 5 到 15 秒。
  • 不要每次请求都创建 OkHttpClient 实例,复用同一个实例可以显著提升性能。

5.3 题目校验与入库

大模型生成的题目不能直接发给学生。这里至少要经过一道程序化校验:

// 文件路径:src/main/java/com/example/aiexam/service/QuestionValidateService.java @Service public class QuestionValidateService { public boolean validateGeneratedQuestion(JSONObject questionJson) { if (questionJson == null) { return false; } String question = questionJson.getString("question"); String answer = questionJson.getString("answer"); String analysis = questionJson.getString("analysis"); if (question == null || question.length() < 10) { return false; } if (answer == null || answer.isBlank()) { return false; } if (analysis == null || analysis.length() < 10) { return false; } // 如果题目中有选项,则必须包含选项 JSONArray options = questionJson.getJSONArray("options"); if (options != null && !options.isEmpty()) { return options.size() >= 2; } return true; } }

校验通过后,将题目标记为“待审核”状态保存到 MySQL。教师可以在后台看到所有由 AI 生成的题目,选择发布或删除。这个“人工审核”环节,是保证系统安全性和内容质量的关键设计,在答辩时也可以作为体现工程思维的加分点。

5.4 组合出题接口

出题接口接收前端传过来的考试范围参数,先查询知识图谱获取相关知识点,再循环调用大模型接口生成多道题目。这里使用简单的 for 循环,控制并发为 1,避免触发平台限流:

// 文件路径:src/main/java/com/example/aiexam/controller/ExamController.java @RestController @RequestMapping("/api/exam") public class ExamController { private final QuestionGeneratorService questionGeneratorService; private final KnowledgeGraphService knowledgeGraphService; public ExamController(QuestionGeneratorService questionGeneratorService, KnowledgeGraphService knowledgeGraphService) { this.questionGeneratorService = questionGeneratorService; this.knowledgeGraphService = knowledgeGraphService; } @PostMapping("/generate") public Result generateExamPaper(@RequestBody GenerateExamRequest request) throws IOException { // request 中包含 examRange(如“二次函数”)、questionCount、difficulty List<String> knowledgePoints = knowledgeGraphService.findChildPoints(request.getExamRange()) .stream() .map(KnowledgePointNode::getName) .collect(Collectors.toList()); if (knowledgePoints.isEmpty()) { knowledgePoints = List.of(request.getExamRange()); } List<JSONObject> questions = new ArrayList<>(); for (int i = 0; i < request.getQuestionCount() && i < knowledgePoints.size(); i++) { JSONObject question = questionGeneratorService.generateQuestion( knowledgePoints.get(i), request.getDifficulty(), "选择题" ); questions.add(question); } return Result.success(questions); } }

考虑到 API 调用延迟,前端需要做 Loading 状态,给用户明确的等待反馈。也可以在后端使用异步任务,生成完题目后通过 WebSocket 或轮询通知前端,但毕设为了简化,直接同步返回即可。

6. 错题分析与薄弱知识点推荐

6.1 错题数据模型

学生每次答题后,系统会判断对错。错题记录表建议包含以下字段:

字段类型说明
idbigint主键
student_idbigint学生 ID
question_idbigint题目 ID
knowledge_pointvarchar题目关联的知识点
is_correcttinyint是否正确
answer_timedatetime答题时间
wrong_timesint该题错误次数

仅记录“哪道题错了”是不够的。真正有用的是“错题关联到哪个知识点”,这样我们才能统计出学生的薄弱点分布。

6.2 薄弱度计算

这里的核心是一个简单的权重公式:

薄弱度 = 错误次数 * 0.6 + 最近错误权重 * 0.4

其中“最近错误权重”可以通过时间衰减实现:越近的错误,权重越高。例如:

// 文件路径:src/main/java/com/example/aiexam/service/WrongQuestionAnalysisService.java @Service public class WrongQuestionAnalysisService { public Map<String, Double> calculateWeakPoints(List<WrongRecord> wrongRecords) { Map<String, Double> weakScoreMap = new HashMap<>(); for (WrongRecord record : wrongRecords) { String point = record.getKnowledgePoint(); double timeWeight = calculateTimeWeight(record.getAnswerTime()); double score = record.getWrongTimes() * 0.6 + timeWeight * 0.4; weakScoreMap.merge(point, score, Double::sum); } // 按薄弱度降序排列 return weakScoreMap.entrySet().stream() .sorted(Map.Entry.<String, Double>comparingByValue().reversed()) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new )); } private double calculateTimeWeight(LocalDateTime answerTime) { long days = ChronoUnit.DAYS.between(answerTime, LocalDateTime.now()); if (days <= 1) { return 1.0; } else if (days <= 3) { return 0.8; } else if (days <= 7) { return 0.5; } return 0.2; } }

6.3 结合知识图谱的推题策略

有了薄弱知识点清单后,系统在下次出题时,会优先从薄弱点出发,并通过知识图谱查找该知识点的前置知识点。如果学生连“一元一次方程”的相关题目都频繁出错,那么推送“一元二次方程”的难题就没有意义,应该先巩固前置知识。

出题推荐逻辑可以这样描述:

1. 从错题表中统计数据,找出薄弱知识点集合,按薄弱度排序。 2. 取出前 N 个知识点。 3. 对每个知识点,用知识图谱查询: - 是否已经掌握其前置知识点(根据前置知识点近 7 天错误率判断)。 - 如果前置知识也不牢固,则推前置知识的简单题。 - 如果前置知识掌握良好,则推当前薄弱点的中等难度题。 4. 组装推荐题目列表返回前端。

这种“先补基础、再练薄弱”策略,在个性化学习系统中是常见且有效的产品逻辑,写进论文里也是一段不错的内容。

7. 前端页面与接口对接

7.1 前端技术选型

前端使用 Vue 3 + Element Plus 是最稳妥的选择。Element Plus 提供了表格、表单、对话框、消息提示等现成组件,能让开发速度提升不少。如果时间紧张,甚至可以直接使用若依等开源脚手架,然后在此基础上二次开发。

7.2 创建考试页面

在 Vue 组件中,调用后端生成试卷接口,代码示例如下:

// 文件路径:src/views/exam/GenerateExam.vue <template> <el-card> <el-form :model="examForm" label-width="100px"> <el-form-item label="考试范围"> <el-select v-model="examForm.examRange" placeholder="请选择知识点"> <el-option label="二次函数" value="二次函数"></el-option> <el-option label="一元二次方程" value="一元二次方程"></el-option> <el-option label="全等三角形" value="全等三角形"></el-option> </el-select> </el-form-item> <el-form-item label="题目难度"> <el-rate v-model="examForm.difficulty" :max="5"></el-rate> </el-form-item> <el-form-item label="题目数量"> <el-input-number v-model="examForm.questionCount" :min="1" :max="10"></el-input-number> </el-form-item> <el-form-item> <el-button type="primary" :loading="loading" @click="generateExam"> {{ loading ? 'AI 出题中...' : '智能生成试卷' }} </el-button> </el-form-item> </el-form> <el-card v-if="questions.length > 0" style="margin-top: 20px"> <div v-for="(item, index) in questions" :key="index" class="question-item"> <h4>第 {{ index + 1 }} 题</h4> <p>{{ item.question }}</p> <div v-for="opt in item.options" :key="opt" class="option-item"> {{ opt }} </div> <el-alert title="答案" :description="'正确答案:' + item.answer" type="success" show-icon :closable="false" style="margin-top: 10px" > </el-alert> <el-alert title="解析" :description="item.analysis" type="info" show-icon :closable="false" style="margin-top: 10px" > </el-alert> </div> </el-card> </el-card> </template> <script> import axios from 'axios' export default { data() { return { loading: false, examForm: { examRange: '二次函数', difficulty: 3, questionCount: 5 }, questions: [] } }, methods: { async generateExam() { this.loading = true try { const res = await axios.post('/api/exam/generate', this.examForm) this.questions = res.data.data } catch (error) { this.$message.error('AI 出题失败,请检查后端服务或 API Key 配置') } finally { this.loading = false } } } } </script>

这里需要注意,后端返回的题目可能是 JSON 字符串,也可能是对象。建议在后端统一返回解析后的JSONObject,前端直接res.data.data取值即可。如果发现答案是对象而页面显示[object Object],通常就是类型没处理好。

7.3 知识图谱可视化

Neo4j 自带的 Browser 界面已经能展示图谱,但那是开发调试工具,不适合直接嵌入到毕设系统里。如果前端要展示知识图谱,推荐使用vue3-force-graphECharts的 graph 类型。

这里提供一个 ECharts 的知识图谱展示思路:

// 文件路径:src/views/knowledge/KnowledgeGraph.vue import * as echarts from 'echarts' // 从后端获取知识图谱节点和关系数据 const response = await axios.get('/api/knowledge/graph') const nodes = response.data.data.nodes const links = response.data.data.links // 将数据转换为 ECharts 需要的格式 const chartData = { nodes: nodes.map(node => ({ name: node.name, value: node.difficulty })), links: links.map(link => ({ source: link.source, target: link.target })) } const chart = echarts.init(document.getElementById('graphChart')) chart.setOption({ tooltip: {}, series: [{ type: 'graph', layout: 'force', roam: true, label: { show: true }, force: { repulsion: 300 }, data: chartData.nodes, links: chartData.links }] })

可视化不是核心功能,但放在演示环节非常加分。只要把图谱展示出来,答辩现场的效果立竿见影。

8. 部署与演示注意事项

8.1 本地打包启动

后端项目打包:

mvn clean package -DskipTests java -jar target/ai-exam-system-0.0.1-SNAPSHOT.jar

前端项目安装依赖并启动:

npm install npm run serve

Neo4j 和 MySQL 需要提前启动。Neo4j 的默认地址是bolt://localhost:7687,HTTP 管理页面是http://localhost:7474,如果端口被占用,可以在neo4j.conf中调整。

8.2 答辩演示建议

答辩时最容易翻车的地方是现场调用大模型接口失败。因为实验室网络、API 余额、平台临时限流等问题不可控。建议准备两个方案:

  • 方案 A:现场直接调用 Qwen-Plus,展示真实 AI 出题效果。
  • 方案 B:在本地题库中预先存储 10 到 20 道由大模型生成的题目,如果现场 API 调用失败,直接走题库兜底。

代码层面可以通过开关控制:

ai: qwen: enabled: true # 设置为 false 时,出题模块走本地题库

这个“降级方案”在答辩时反而可以作为一个亮点:说明你考虑了系统可用性,不是把所有鸡蛋放在一个篮子里。

8.3 录制演示视频备用

不管现场演示是否顺利,都建议提前录一段完整的演示视频,时长控制在 5 分钟以内。内容包括:

  • 登录系统。
  • 选择知识点生成试卷。
  • 展示大模型请求日志(可以看到提示词和返回内容)。
  • 学生答题,故意答错几道题。
  • 进入错题分析页面,展示薄弱知识点和推荐题目。
  • 展示知识图谱页面。

万一现场电脑或网络出问题,放视频也是一种有效的应急方案。

9. 常见问题与排查思路

9.1 调用 Qwen-Plus 返回 401 或 InvalidApiKey

问题现象常见原因解决思路
HTTP 401 UnauthorizedAPI Key 配置错误或已失效检查application.ymlapi-key是否填对
InvalidApiKeyKey 前后有空格或引号使用配置管理工具,避免误输入空格

建议在测试阶段写一个简单的单元测试,直接调用接口,快速验证 Key 是否可用。

9.2 大模型返回内容解析失败

问题现象常见原因解决思路
JSON parse error大模型返回了```json代码块调用前先清理 markdown 标记
返回内容包含多余文字提示词未明确“只输出 JSON”在提示词末尾强调“不要输出任何额外内容”
返回结果少字段模型截断或提示词约束不足增大max_tokens或优化提示词

建议在生成接口外面包一层 try-catch,解析失败时记录原始返回内容到日志,方便排查。

9.3 Neo4j 连接失败

问题现象常见原因解决思路
Connection refusedNeo4j 服务未启动确认 Neo4j Desktop 或服务已启动
Auth error默认账号密码未修改检查database名称是否为neo4j
Bolt protocol host unreachable使用了错误的 URI本地使用bolt://localhost:7687

在 Spring Boot 启动后,可以通过访问一个测试接口确认 Neo4j 连接是否正常,避免所有查询接口都报错时难以定位。

9.4 出题速度慢,前端等待时间过长

大模型生成题目平均需要 3 到 8 秒,一次生成 5 题可能需要 20 秒以上。这里有几个优化方向:

  • 一次提示词中要求生成多道题目,减少请求次数。
  • 后端使用线程池并行调用,但要注意平台 QPS 限制。
  • 前端提示“AI 出题中,预计需要 10-20 秒”,降低用户焦虑。

对于毕设场景,建议将questionCount默认设置为 5,并采用串行生成,避免接口限流。

10. 毕业论文与答辩准备建议

10.1 论文框架参考

如果你的毕设论文还没有开始写,可以参考以下结构:

第一章 绪论 1.1 研究背景与意义 1.2 国内外研究现状 1.3 论文主要工作 第二章 相关技术介绍 2.1 大语言模型技术 2.2 知识图谱技术 2.3 Spring Boot 与 Vue 框架 第三章 系统需求分析 3.1 功能需求分析 3.2 非功能需求分析 第四章 系统设计 4.1 总体架构设计 4.2 知识图谱设计 4.3 数据库设计 4.4 智能出题模块设计 4.5 错题分析模块设计 第五章 系统实现 5.1 知识图谱构建实现 5.2 大模型出题实现 5.3 错题分析实现 5.4 页面展示 第六章 系统测试 6.1 功能测试 6.2 出题质量分析 6.3 系统性能测试 第七章 总结与展望

10.2 答辩可能被问到的问题

提前准备以下问题的回答,答辩会从容很多:

  • 为什么选择 Qwen-Plus,而不是其他大模型? 可以回答:Qwen-Plus 调用成本低、响应快,且提供的 OpenAI 兼容接口容易集成;项目代码里做了模型可配置,未来可以切换到其他模型。
  • 知识图谱在系统中到底解决了什么问题? 重点回答:知识点关联关系,前置知识诊断,薄弱点推题。
  • 大模型生成的题目如果错了怎么办? 重点回答:程序化校验 + 教师审核机制 + 后台修改功能。
  • 系统如何保证同一学生不同次考试难度一致? 可以回答:难度参数固定传入大模型,并按知识点难度系数做过滤。

10.3 创新点表达方式

答辩 PPT 上不要只写“系统使用了 AI 技术”这种空话,要把创新点具体化:

  • 基于知识图谱的知识点前置依赖关系,实现薄弱点定位与补强学习。
  • 基于 Qwen-Plus 大模型的动态题目生成,减少人工录入成本。
  • 错题数据与知识图谱双向联动,形成个性化出题闭环。
  • 提示词模板可配置,可快速适配不同学科。

这种表达方式能有效提升项目的技术含金量。即便系统本身并不复杂,只要逻辑讲透、闭环完整,就能在答辩中拿到不错的成绩。

最后说句实在话:毕设项目真正的难点从来不是技术本身,而是如何把零散功能串成一个有逻辑、有亮点的完整故事。知识图谱负责“选什么”,大模型负责“怎么出”,错题分析负责“为什么出”,三个模块互相支撑,整个项目就能站得住。如果你正在为选题发愁,或者项目做到一半不知道怎么增加技术亮点,可以直接参考这套方案动手改造自己的系统。代码结构按照文中的分层方式去写,后续扩展题目类型、增加学科支持、接入新的模型,都会非常方便。

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

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

立即咨询