当前位置: 首页 > news >正文

山东大学软件学院项目实训-创新实训-计科智伴(四)—— 后端第四周:智能互动 + 练习模块

前三周做完认证、画像、学习计划后,这周开始做智能互动和练习模块。这个模块要对接AI、要做题目推荐和自动批改,还要和画像联动更新掌握度,比前几周的纯CRUD要稍复杂一些。今天把这周的工作和代码细节一起记下来。

一、这周干了什么?

在前三周的基础上,这周主要完成了智能互动与练习模块。按之前的接口设计,这个模块包含四个核心功能:发起智能问答、获取练习题、提交练习答案、获取专项练习。此外还顺便把聊天记录持久化做完了,这样用户的历史问答可以追溯。

这次设计的几个关键点:

  • 复用项目原有的Question实体,题干从关联的LearningResource联查获取,不破坏原有表结构
  • 按知识点和难度从题库拉取题目,过滤掉已经做过的题
  • 客观题直接比对答案,主观题调用AI生成解析
  • 练习提交后自动更新画像中的知识点掌握度和薄弱点列表

接口和表结构这边,本周新增了chat_historyuser_question_record两张表,以及AiChatRequest/ResponseExerciseDTOSubmitAnswerResponse等一批DTO。

二、大模型选型与配置

智能互动模块离不开大语言模型。我们选择阿里云通义千问作为底层模型,通过Spring AI框架集成。配置文件如下:

spring: ai: openai: base-url: https://dashscope.aliyuncs.com/compatible-mode api-key: ${OPENAI_API_KEY} chat: options: model: qwen-max-latest embedding: options: model: text-embedding-v3 dimensions: 1024
  • qwen-max-latest:通义千问最新最强版本,适合复杂问答、结构化输出、答案解析生成。
  • text-embedding-v3:用于后续语义检索(RAG),1024维向量,能较好地支撑知识库问答。

使用DashScope兼容模式的好处是:OpenAI的API调用方式几乎不变,只用改base-urlmodel,代码层面无侵入。

三、核心接口设计

练习模块共开发了以下接口(代码主要在ExerciseController里):

接口方法功能
/ai/chatPOST智能问答(支持文本+pdf文件,流式返回)
/exercise/nextPOST获取下一道/一组练习题
/exercise/{exerciseId}/submitPOST提交答案并接收批改结果
/exercise/targetedPOST

根据薄弱点获取专项练习

所有接口都加了登录校验,userIdUserHolder的ThreadLocal里拿。

(多模态暂仅实现pdf)

四、获取练习题 —— 按画像推荐

4.1 核心流程

获取练习题入口是getNextExercises方法,用户传入想练习的知识点和难度,后端根据画像和未做题目前按需返回:

  • 如果前端指定了knowledgePoint,就按指定知识点拉取
  • 如果没指定,用画像中的weakPoints(薄弱知识点)优先推荐
  • 难度动态调整:根据画像中用户整体准确率算难度——准确率低于60%用基础难度,60%-80%用中等难度,高于80%用高级难度
  • 查询Question表时,关联user_question_record表,过滤掉用户已经做过的题目,避免重复
4.2 题干的特殊设计

项目原有的Question实体只存了kpId(知识点ID)、qTypedifficultyansweranalysis,题干实际存在LearningResource表里,通过resourceId关联。所以DTO组装时得联查两张表:

private ExerciseDTO convertToExerciseDTO(Question question) { LearningResource resource = learningResourceService.getById(question.getResourceId()); return ExerciseDTO.builder() .exerciseId(question.getQId()) .questionText(resource != null ? resource.getContent() : "") .resourceUrl(resource != null ? resource.getFileUrl() : "") .knowledgePoint(getKnowledgePointName(question.getKpId())) // ... .build(); }

五、提交答案 —— 批改 + 画像联动

5.1 批改逻辑

提交答案的入口是submitAnswer方法,流程如下:

  1. 根据exerciseId拿到题目信息,联查LearningResource拿到题干原文
  2. 比对用户答案和标准答案(当前是字符串全等匹配,后续可以扩展为AI辅助评分)
  3. 记录答题记录到user_question_record
  4. 如果答错了,调用通义千问qwen-max-latest生成解析
  5. 返回批改结果
// 简化的答案比对 private boolean checkAnswer(String userAnswer, String correctAnswer) { return userAnswer.trim().equalsIgnoreCase(correctAnswer.trim()); } // 答错时用AI生成解析(使用配置好的ChatClient,实际调用qwen-max-latest) private String generateExplanation(...) { String prompt = String.format( "请为以下题目生成解析:\n题目:%s\n用户答案:%s\n正确答案:%s...", questionText, userAnswer, correctAnswer ); return chatClient.prompt().user(prompt).call().content(); }
5.2 画像联动:掌握度更新

这是本周的一个核心联动点。练习模块不能只返回对错,还得反映到学情画像上。我在UserProfileServiceImpl里补充了两个方法:

  • updateKnowledgeMastery(Long userId, String knowledgePoint, double delta)—— 增量更新某个知识点的掌握度
  • updateWeakPoints(Long userId, String knowledgePoint)—— 将某个知识点加入薄弱点列表

调用时机:用户提交答案后,如果答对了就小幅提升掌握度;答错了就显著降低掌握度,并确保该知识点在weakPoints中。

// 在提交答案后调用 if (isCorrect) { userProfileService.updateKnowledgeMastery(userId, knowledgePoint, 0.05); } else { userProfileService.updateKnowledgeMastery(userId, knowledgePoint, -0.1); userProfileService.updateWeakPoints(userId, knowledgePoint); }

这样画像里的知识掌握度会随着用户的练习动态变化,后续的计划生成、题目推荐都会更准确。

六、智能问答 —— 对接通义千问,支持流式+多模态

这周也把智能问答的基础架子搭了。因为项目已经集成了Spring AI和之前的通义千问配置,实现起来不算太复杂。现在文本问答走ChatClient调用qwen-max-latest模型,返回结构化回答,同时存入chat_history表方便以后做RAG检索。

// 调用通义千问 qwen-max-latest String prompt = "你是一个学习助手。请回答以下问题,并以JSON格式输出,包含字段:answer(简短答案),explanation(详细解释),knowledgePoint(所属知识点),relatedQuestions(最多3个),suggestion(学习建议)。问题:" + question; String response = chatClient.prompt().user(prompt).call().content(); // 解析JSON,存入ChatHistory

之所以选择qwen-max-latest,是因为它对中文理科题目的理解能力强,且支持结构化输出。后续也可以根据成本调整模型为qwen-plusqwen-turbo

最终实现的效果:

  • 支持纯文本问答
  • 支持上传pdf文件
  • 支持流式输出(打字机效果)
  • 自动管理会话记忆(每个chatId独立上下文)
  • 消息自动持久化到数据库
4.1 核心代码解读

几个关键点

  • Converse 记忆CHAT_MEMORY_CONVERSATION_ID_KEY是 Spring AI 内置的会话记忆 ID,只要同一个chatId多次调用,模型会自动带上历史对话,不需要手动拼接上下文。
  • 多模态:Spring AI 的Media对象封装了文件和 MIME 类型,传给spec.user()后,通义千问会自动识别图片或音频内容。
  • 流式返回Flux<String>配合前端 EventSource 或 fetch,就能实现类似 ChatGPT 的打字机效果。
  • 会话与消息存储sessionService只保存会话元信息(会话 ID、类型、创建时间),实际的消息内容由 Spring AI 的PersistentChatMemory自动保存。在这个版本里我没有手动保存消息,因为使用了CHAT_MEMORY_CONVERSATION_ID_KEY,Spring AI 的内存管理会负责存储历史。但为了后续检索和展示聊天记录,我单独建了message表,在需要时手动存入(可以另加一个异步方法)。你当前的代码中其实没有保存消息,不过这不是大问题,因为内存已经能维持上下文。后面如果需要做历史记录翻页,再补充即可。
4.2 多模态文件处理(暂未完整实现)

上传的图片会被暂存在内存中,Spring AI 自动转换成 base64 或临时 URL 发给模型。为了长期存储用户的图片/音频文件,我调用了minioService.uploadFile将文件保存到 MinIO,并可以关联到消息记录。

// 在另一个方法中保存用户消息时,上传文件到 MinIO String fileUrl = minioService.uploadFile(file, CURRENT_USER_ID, chatId);

这样用户上传的习题照片、手写笔记都能永久保存,后续可以用于错题本或复习。

4.3 会话记忆是如何工作的?

Spring AI 内置了ChatMemory接口,我们依赖注入时没有显式配置,框架默认使用了InMemoryChatMemory,它把每个chatId的对话存在本地内存。生产环境可以考虑替换为RedisChatMemory,支持多实例共享。

只要在每次请求时传入同样的chatId,模型就能记住之前的对话。例如:

用户:什么是死锁? AI:死锁是...(回答) 用户:那如何避免? ← 不需要重复说“死锁”,模型知道在问什么

这个能力对连续问答非常有用。

七、专项练习 —— 薄弱点突破

专项练习接口getTargetedExercises的逻辑比普通练习更聚焦:直接取前端传入的薄弱点列表(或画像中的weakPoints),按知识点拉题,每个薄弱点抽1-2道,组一套练习给用户集中突破。这个功能是错题本和前面积累的薄弱点数据打通的,用户在首页点击“薄弱点专项训练”就能直接进入。

八、踩坑与心得

这周也遇到几个值得记下来的问题:

1. 题干到底存哪的坑

项目原来Question里没有题干字段,题干在LearningResource里。刚开始写DTO转换时没注意,直接question.getQuestionText()拿空值,查了半天才发现要联查。解决方案是写了一个convertToExerciseDTO方法,每次取题都自动把LearningResource的内容带出来。

2. 题库重复推荐

如果用户做过的题不记录下来,/exercise/next会反复推荐同一道题。解决方案是在selectExercisesSQL里加了一个条件:uqr.question_id IS NULL,过滤掉已做过的,并且在每次提交后往user_question_record表记录。

3. JSONB字段解析报错

UserProfileServiceImpl里解析knowledgeMasteryweakPoints时,入参类型是Object(因为数据库存的JSONB字段映射到Java是Object),但解析方法之前写的是接收String。改成了兼容处理:如果是String就正常解析;如果是Map/List就直接转换;其他情况尝试转成JSON再解析。

if (knowledgeMasteryObj instanceof String) { return objectMapper.readValue((String) knowledgeMasteryObj, typeRef); } else if (knowledgeMasteryObj instanceof Map) { return (Map<String, Double>) knowledgeMasteryObj; } else { String json = objectMapper.writeValueAsString(knowledgeMasteryObj); return objectMapper.readValue(json, typeRef); }

这样不管MyBatis-Plus返回什么类型,都能正确解析画像里的掌握度数据。

4. 大模型API限流与错误处理

阿里云DashScope有并发限制,如果调用频繁会返回限流错误。后续需要在调用处增加重试机制和降级规则(例如限流时返回预设的默认回答)。当前版本只做了简单的异常捕获,这个问题会在下周优化。

5. 流式响应下如何存储完整消息

Flux<String>是边生成边发送给前端,后端无法轻易拿到完整的 AI 回复。如果想存消息记录,可以使用collectList()或者另起一个异步监听。目前版本没有强制存储,因为 Spring AI 的内存记忆已经能满足多轮对话。后续如果需要查看历史聊天记录,再考虑实现消息持久化。

九、总结

一周时间,从两张新表、一批DTO到整套接口跑通。模块四的交互比前三个模块更复杂,因为它涉及到通义千问模型调用、画像联动、题库查重等多个环节。目前文本问答和普通练习已经可以正常使用了,专项练习接口也已经打通。下一步计划补充上传手写题图片的多模态问答(基于qwen-vl模型)及AI对编程题、主观题的智能评分。

http://www.jsqmd.com/news/771348/

相关文章:

  • 2026上海普拉提机构排行榜:高性价比机构推荐 - 品牌2025
  • 2026年太原短视频代运营与企业全网营销深度指南 - 优质企业观察收录
  • 查看 Taotoken 账单明细了解各模型与项目的详细资源消耗
  • 2026通风设备厂家推荐及行业应用解析 - 品牌排行榜
  • 2026年熟地黄深度测评:如何为你的养生需求匹配最佳方案? - 速递信息
  • 3分钟掌握Blender到Unity FBX转换:解决坐标错乱的终极方案
  • linux的文件目录C语言数据结构
  • 深耕不动产资产管理系统企业,2026年国资私有化部署平台全面盘点 - 品牌2026
  • 2026年做临床前CRO服务的公司选择参考 - 品牌排行榜
  • ESP32无人机远程识别技术挑战与开源解决方案深度解析
  • 2026年光伏项目总包服务公司助力新能源项目高效推进 - 品牌排行榜
  • 在自动化脚本中使用Taotoken API实现批量文本处理与摘要生成
  • DevOps工程师转型AI架构师:18个月实战路线图与MCP智能体构建指南
  • AI建站工具怎么选?5大维度对比与选型指南
  • Path of Building:流放之路Build规划工具如何帮你打造完美角色?
  • eSIM SGP32 EuiccPackage包eimSignature和euiccSignEPR生成及校验
  • 2026昆山最靠谱的律师推荐及选择参考 - 品牌排行榜
  • 如何快速掌握UndertaleModTool:5个实用技巧提升你的游戏修改效率 [特殊字符]
  • 泰安松泽复合材料:泰安短切毡 表面毡出售选哪个公司 - LYL仔仔
  • 2026年乌鲁木齐黄金回收新态势:瑞盛黄金推全城上门与现场结算服务 - 博客万
  • UndertaleModTool终极指南:快速解锁GameMaker游戏的无限可能
  • 创业团队如何利用Taotoken统一管理多模型API成本
  • 阜阳人都在悄悄卖金?本地上门回收TOP6真实排行榜 - 福正美黄金回收
  • 如何启动MQTT服务器
  • 2026年武汉正规名表保养门店有哪些:本地专业服务机构选型参考指南 - 速递信息
  • CVE-2026-31431 Copy Fail:Linux 内核本地提权漏洞技术分析、复现验证与修复建议
  • 2026年苏州财税公司如何助力小微企业税收筹划? - 速递信息
  • 期货下单指南:市价、限价、止损……别再下错单了
  • AI工程化实战:基于模块化工具集快速构建生产级AI服务
  • taotoken 的审计日志功能如何满足企业级安全与合规需求