山东大学软件学院项目实训-创新实训-计科智伴 组周报(第五周)—— 错题诊断 Agent 落地、course-ai 接通大模型 + RAG + 多 Agent 调度、登录与对话全链路前端化
摘要
第四周末我们立下一句 flag——"下周开始让它真正'聪明'起来"。本周就来兑现。团队从"让系统稳定跑、能用"正式进入"让 AI 深度嵌入学习闭环"的阶段,目标是把"智能"这层从能调通推进到调得准、调得狠。三位组员分别围绕①错题本与智能诊断 Agent、②course-ai 后端线接通真实大模型 + RAG 检索 + 多 Agent 调度、③登录 / 对话 / 密码重置的全链路前端接通展开。本周把"用户做错题 → 诊断根因 → 更新画像 → 生成个性化计划 → 拆分任务"这条 AI 学习闭环彻底打通,同时让另一条后端线具备了真实大模型流式问答、知识库检索增强与意图调度的能力。
一、本周整体定位:从"能稳定跑"到"真正聪明"
第四周已经把代码闭环、四层测试体系、前端体验三件事做稳,"能跑、能稳定跑、能让人用"都已铺好。进入第五周,团队的任务有三条主线,三条线咬合在一起,构成"AI 真正参与学习"的完整证据。
第一,把"做错题"变成"学得到"。落地错题本 + 智能诊断 Agent,让每一次错误都被大模型分析根因、结构化回写画像,并驱动 AI 重新生成学习计划与任务。这是第四周「下周计划」里"诊断 Agent 与规划 Agent 正式接入"的兑现。
第二,把另一条后端线 course-ai 从"能编译、能跑"推进到"接真模型、能检索、会调度"。接通 DashScope Qwen 的 SSE 流式问答、搭起 pgvector + RAG 检索体系、再加一层多 Agent 意图调度,让后端不只是"会答",而是"先想清楚该让谁答、答的时候有没有据可依"。
第三,把"后端能力"翻译成"用户动作"。把登录流程、流式对话渲染、密码重置三件套在 uniapp 前端真正接通,让上面两条后端线的能力对用户可见、可用。
本周交付如下:
| 方向 | 负责人 | 本周交付 |
|---|---|---|
| 错题本 + 智能诊断 Agent + AI 生成计划 / 任务 | 本人 | 错题本 5 接口、诊断 Agent(异步 + 同步兜底 + 降级 + 重试)、AiStudyPlanGenerator、AiTaskGenerator、画像掌握度按置信度衰减联动 |
| course-ai 后端线:接通大模型 + RAG + 多 Agent 调度 | 组员一 | 195 个 Java 文件编译复活、SSE 真接 Qwen、14 个 endpoint 补齐、10 意图多 Agent 调度、pgvector + DashScope 直连 RAG 闭环 |
| 登录 / 对话 / 密码重置全链路前端接通 | 组员二 | 登录前置 + 画像感知跳转、fetch + ReadableStream 解 SSE 打字机、密码重置三件套 ActionSheet、9 个 notImplemented 占位转真实路径 |
下文按方向分述本周成果。
二、错题本 + 智能诊断 Agent + AI 生成计划 / 任务(本人负责)
这是本周花时间最多的一块。诊断 Agent 涉及的逻辑比预想复杂——要调大模型、要解析 JSON、要更新画像、还要异步处理避免阻塞。核心是把"用户做错题"这个动作,变成驱动整个自适应系统的数据源。
2.1 错题本基础接口
错题本模块对外提供 5 个 REST 接口(代码在 MistakeController):
| 接口 | 方法 | 功能 |
|---|---|---|
/api/mistakes |
POST | 添加错题(触发异步诊断) |
/api/mistakes |
GET | 错题列表(分页,可按知识点筛选) |
/api/mistakes/{mistakeId} |
GET | 错题详情(题目、正确答案、用户答案、诊断结果) |
/api/mistakes/{mistakeId}/diagnosis |
GET | 诊断报告(未诊断则同步触发一次) |
/api/mistakes/weak-points |
GET | 薄弱知识点列表(按错题数排序) |
添加错题的关键设计是"立即返回 + 后台异步诊断",前端先展示"诊断中",再轮询详情拿结果,不阻塞主流程:
@PostMapping
public Result addMistake(@RequestBody MistakeAddRequest request) {
Long userId = UserHolder.getUser().getUserId();
WrongQuestion wrongQuestion = new WrongQuestion();
wrongQuestion.setUserId(userId);
wrongQuestion.setQId(request.getQId());
wrongQuestion.setUserAnswer(request.getUserAnswer());
wrongQuestionService.save(wrongQuestion);
// 异步调用诊断 Agent,添加错题后立即返回
diagnosisAgent.diagnoseAsync(wrongQuestion.getWqId(), userId,
request.getQId(), request.getUserAnswer());
return Result.ok(Map.of("mistakeId", wrongQuestion.getWqId(), "status", "diagnosing"));
}
错题列表会从 Question 联查 LearningResource 取题干预览(截前 50 字),详情则返回完整题目、正确答案、解析、用户答案、知识点名称与诊断结果。
2.2 智能诊断 Agent —— 本周的核心
诊断 Agent 要做的事:拿到一道错题(用户答案、题目信息、知识点、用户画像),调用大模型分析错误根因,更新画像掌握度,并记录诊断结果供前端展示。整体设计四点:
- 异步为主:添加错题时走
@Async,不阻塞主流程。 - 同步兜底:用户查诊断报告时若还没诊断完,就同步触发一次
diagnoseSync。 - 降级机制:大模型调用失败 / 超时,自动降级为基于字符串相似度的规则诊断。
- 重试机制:大模型调用最多重试 3 次,指数退避。
提示词(Prompt)设计上,给模型喂的不只是题面,还包括:所属知识点及 ID、从 kp_relationship 查到的前置知识点与易混淆知识点、用户当前对该知识点的掌握度、以及一张知识点 ID 映射表(让模型返回具体 ID 而非名称)。要求模型返回结构化 JSON:error_type(knowledge_gap / concept_confusion / logic_error / calculation_error / syntax_error)、root_kp_id、explanation、confidence、suggested_actions。
大模型调用与重试:
private String callModelWithRetry(String prompt) {
for (int attempt = 1; attempt <= 3; attempt++) {
try {
return chatClient.prompt().user(prompt).call().content();
} catch (Exception e) {
if (attempt < 3) Thread.sleep((long) Math.pow(2, attempt - 1) * 1000);
}
}
throw new RuntimeException("大模型调用失败");
}
诊断结果回来后,updateUserProfileWithDiagnosis 会把 root_kp_id 加入 weakPoints,并按置信度衰减掌握度——置信度越高,扣得越狠,但有 0.1 的下限保护:
double decayRate = 0.05 * confidence;
double newMastery = Math.max(0.1, currentMastery * (1 - decayRate));
mastery.put(rootKpIdStr, newMastery);
这样每次错题诊断都会让画像更精准地反映知识短板,后续的计划与练习推荐也随之调整。若大模型不可用,则降级到 performRuleBasedDiagnosis,按用户答案与标准答案的编辑距离判型(相似度 > 0.8 → calculation_error,> 0.5 → concept_confusion,否则 knowledge_gap),保证 AI 挂了用户仍有基本反馈。
2.3 AI 生成学习计划(重构)
原来的计划生成是硬编码规则:按薄弱点或目标分数定类型,再固定分配 2 周 / 1 个月,计划项简单遍历知识点。本周改成调大模型生成,新增 AiStudyPlanGenerator:取用户画像(年级、专业、目标分数、掌握度、薄弱点、学习习惯)→ 构建提示词让模型分析每日可投入时长并产出 JSON 计划 → 解析后为每个计划项关联学习资源 → 按每日可用时长自动排日期 → 存 study_plan 表并失活旧计划。提示词里的硬约束包括:计划类型(WEAKNESS_FOCUS / TARGET_ORIENTED / COMPREHENSIVE)由模型自行判断、每项预计学习时间不超过每天总时长的 60%、优先安排薄弱点并考虑前置依赖。
2.4 AI 生成任务(重构)
有了计划项还得拆成可执行任务。原来 1 个计划项对应 1 个任务,太粗。本周写 AiTaskGenerator,让模型把每个计划项拆成 1~3 个具体任务(学习 / 练习 / 复习),每个任务含 title / description / knowledgePoint / estimatedMinutes / practiceCount,解析后补 resourceId 与 taskDate 再批量入库。于是"学习 Java 多线程 60 分钟"会被拆成"看基础视频 25min + 5 道选择题 20min + 写一个生产者消费者示例 15min",体验细腻很多。
2.5 本周后端踩坑
| 现象 | 根因 | 修复 |
|---|---|---|
大模型返回 JSON 不纯(带 json 代码块 / 注释) |
模型输出习惯 | 正则提取 ```json 包裹内容,再取第一个 { 到最后一个 } |
| 异步诊断里更新数据库不生效 | @Async 默认不在原事务中 |
更新画像方法标 @Transactional(propagation = REQUIRES_NEW) 走独立事务 |
| 高峰期大模型超时 | DashScope 并发限流 | 3 次指数退避重试 + 最终失败降级规则诊断 |
| AI 生成计划的日期解析失败 | 返回日期格式不一定是 yyyy-MM-dd |
parseDateSafely,失败用默认值 |
JSON 清洗的核心两步:
Matcher matcher = Pattern.compile("```(?:json)?\\s*([\\s\\S]*?)```").matcher(cleaned);
if (matcher.find()) cleaned = matcher.group(1).trim();
int start = cleaned.indexOf('{');
int end = cleaned.lastIndexOf('}');
jsonStr = cleaned.substring(start, end + 1);
三、course-ai 后端线:接通真实大模型 + RAG + 多 Agent 调度(组员一负责)
与上面的主学习闭环并行,组员一这周把另一条后端线 course-ai(Spring Boot 3.4 + MyBatis-Plus + Spring AI)从"刚接手时编译都过不去"推进到"接真模型、能检索、会调度"。
3.1 先让 195 个 Java 文件能编译
首次 mvn compile 报出 130+ 个错误,归类只有 5 个根因:Lombok 硬编码 1.18.22 与 JDK 17 + SB 3.4 不兼容(和第四周 L1 撞上的 JCTree$JCImport.qualid 同源)、BusinessException 包从未创建、SubmitAnswerRequest 整文件被注释、StudyPlanAIConfig 的 Bean 重复 + 构造参数漂移、MinioService.uploadBytes 未真迁入。
最值钱的一刀是把 Lombok 的硬编码版本拿掉、交给 spring-boot-parent 统一管理为 1.18.36,50+ 条"找不到符号"瞬间消失,剩下 5 处真错误再各个击破。最终 BUILD SUCCESS,195 文件全量编译通过。经验是:编译期问题先解决再谈别的——Lombok 失效让一大半错误都是假阳性。
3.2 智能对话真接通 Qwen(SSE 流式)
后端早用 Spring AI 接通了 DashScope,但前端还是 setTimeout 模拟。这周把后端 SSE 真正打通,踩到一个字节级的坑:
坑 · SSE 规范要剥前导空格,但 Spring 的输出不能剥
接通后第一版输出变成
HelloHello!Howcan...——词间空格全丢了。用od -c直查原始字节才确认:Spring 把 token 原样拼在data:后面,data: there里这个空格属于 token 本身。若按 SSE 规范"strip one leading SPACE"处理,就会把所有词间空格吃掉。
这和第四周排 SSE buffering 是一脉相承的经验:不要按规范读,要按字节读——Spring 这里就没按规范写。对策是不剥离、原样取 raw.substring(5)。
3.3 补齐 14 个后端 endpoint
前端一堆 notImplemented 占位对应的后端路径压根不存在,这周按模块一次性补齐 14 个:错题状态 / 统计、练习 next / subjects / records、课程列表、对话 recommend / cancel / history、首页 home / heatmap / growth、报告 report、通知(含新表 + NotificationService.push())。顺带修了两个老 bug:ORDER BY RAND()(MySQL 写法)在 PG 报错改 RANDOM();determineTargetDifficulty 返回字符串而 difficulty 是 Integer,永远查不到题——改成返回 "1"/"2"/"3"。第二条和第四周 L4 抓出的"kpName vs kpId"是同一类毛病:契约两端类型对不上,接口看着 200,数据却落不到点。
3.4 多 Agent 智能调度
项目里已有多个独立 agent / service(TeachingAgent、AiChatService、MultimodalChatService、MistakeDiagnosisAgent、AiStudyPlanGenerator),但全靠手动直接调用。这周在它们上面搭一层调度 Agent,对用户原始输入做意图判别再路由:
POST /api/agent/dispatch
→ DispatcherAgent.classify() // 1) 强信号短路 → 2) LLM 分类 → 3) 兜底
→ AgentOrchestrator.dispatch()
├── TeachingAgent.{generateExercises | gradeAnswer | ...}
├── AiChatService // CHAT
├── MultimodalChatService // MULTIMODAL_RECOGNITION
├── MistakeDiagnosisAgent // MISTAKE_DIAGNOSIS
└── AiStudyPlanGenerator // STUDY_PLAN_GENERATE
分类做成三段式以省钱省时:强信号(带 hintIntent / attachmentUrls / extras.mistakeId)直接短路命中、跳过 LLM(~0ms);否则把 10 种意图描述塞进 prompt 让 Qwen 输出 {intent, confidence, reasoning, extractedParams}(~800ms);解析失败兜底走 CHAT。实测 8 条中英文混合 case 全过,例如 "give me 3 medium dynamic programming questions" 命中 EXERCISE_GENERATE 并抽出 {count:3, difficulty:"2", knowledgePoint:"dynamic programming"}。
3.5 RAG 检索体系
数据源是 data_crawler/crawl/cleaned/ 下 13 个已切好 chunks 的 JSON,合计约 18,908 条(数据结构 6042、算法 2832、操作系统 2402、数据库 2147 等)。技术栈:PostgreSQL + pgvector,欧氏距离 <->,DashScope text-embedding-v3(1024 维),先取 topK × 3 召回再按 subject → course_id 过滤。三个非显然的坑:
坑一 · 维度错位:表原是
vector(768),v3 实际输出 1024 维。ALTER TYPE不支持,趁 0 行窗口期DROP + ADD COLUMN重建。坑二 · Spring AI 解不开 DashScope 响应:
OpenAiApi$EmbeddingList没标@JsonIgnoreProperties,而 DashScope 多返回一个顶层id字段,触发UnrecognizedPropertyException。干脆自写 100 行 HTTP 客户端DashScopeEmbeddingClient绕开——三方 SDK 兼容性问题,绕过比对抗便宜。坑三 · batch 上限是 10 不是 25:实测超过 10 条返回
batch size ... not be larger than 10,把BATCH_SIZE改 10 即稳。
导入器做了幂等(chunk_id 入 source_id + 唯一索引,重跑 skip)。验证从导入到回答:导入 github_database.json(150 chunks)后提问"数据库事务的 ACID 分别指什么?",SSE 里先收到 event:references(带召回片段和 score),再收到 event:delta 正文,且 AI 开头明说"结合了参考资料"——RAG 真闭环。
四、登录 / 对话 / 密码重置全链路前端接通(组员二负责)
后端两条线在变强,组员二这周把它们翻译成用户能直接操作的前端动作,并在联调中协同定位了两个隐藏 bug。
4.1 登录流程重构:先登录、再画像、再首页
把 App 启动落地页从 onboarding 问卷改成登录页,注册做成顶部 Tab,登录后调 getProfile 判断画像决定去向。判定逻辑放在前端,只看目标分数或在学课程,有任一即视为已建:
// login.vue
isProfileBuilt(profile) {
if (!profile) return false
const hasTarget = profile.targetScore != null
const hasCourses = Array.isArray(profile.currentCourses) && profile.currentCourses.length > 0
return hasTarget || hasCourses
}
联调中暴露并与后端协同定位了两个坑:其一,浏览器跨域带 authorization 头会先发 OPTIONS 预检,而 LoginInterceptor 没排除 OPTIONS,预检返回 401,浏览器视为失败,真正的 GET/PUT 根本不发出——后端日志一片空白,前端只见一个"提交失败",极难定位,后端在拦截器头部放过 OPTIONS 后解决。其二,/users/profile 直接返回 ThreadLocal 里的登录快照,导致 PUT 更新画像后再 GET 仍是旧值、isProfileBuilt 永远 false,改成按 userId 重查 DB 后正常。
4.2 流式对话前端渲染
把 chat.vue 里 setTimeout 模拟逐字的假渲染换成真 SSE:用 fetch + ReadableStream 拿流,按 \n\n 切事件、按 event: / data: 解析,逐 token 推 chatStore 形成打字机效果,并按后端约定的 session / references / delta / done / error 五类事件解耦"引用卡片"与"回答正文"的渲染。配合 3.2 的字节级坑修复,词间空格不再丢失。
4.3 密码重置三件套前端
原"忘记密码"是个 toast 占位,这周做成 ActionSheet 让用户在邮箱链接 / 短信验证码 / 管理员三条独立通路里三选一,分别对接后端 4 个 reset 端点。后端侧配套了安全约束(不暴露账号是否存在、验证成功即一次性消费、同手机号 60s 冷却、邮件 token 15min / 短信 code 5min)。E2E 11 个场景全过,含二次重放被拦、冷却生效等边界。
4.4 占位转真实
同步把 api/index.js 里 9 个 notImplemented 占位换成 3.3 补齐的真实路径,余 3 个合理保留:refreshToken(后端用长 token 无需刷新)、generateProfile(注册即建画像)、submitExercise(空数组守卫)。
五、阶段成果自检与数据汇总
| 指标 | 需求 | 第五周实际 |
|---|---|---|
| 错题本接口完整度 | 5 个 | 5 个全部可用 ✅ |
| 诊断 Agent 形态 | 异步 + 兜底 + 降级 | 异步为主 / 同步兜底 / 规则降级 / 3 次重试 ✅ |
| 诊断回写画像 | 自动衰减 + 加薄弱点 | newMastery = currentMastery × (1 − 0.05 × confidence),下限 0.1 ✅ |
| AI 生成计划 / 任务 | 取代规则 | AiStudyPlanGenerator + AiTaskGenerator 上线 ✅ |
| course-ai 编译通过 | 100% | 130+ → 0,195 文件 BUILD SUCCESS ✅ |
| 真接大模型 | SSE 流式 | DashScope Qwen 逐 token 下发 ✅ |
| 新增后端 endpoint | 补齐占位 | 14 个,废 9 个前端 stub ✅ |
| 多 Agent 意图分类 | 全过 | 8 / 8 case(中英混合)✅ |
| RAG 检索闭环 | 跑通 | references + delta 同流,已闭环 ✅ |
| RAG 已导入向量 | 试水 | 150 / 18,908(0.8%) |
| 密码重置 E2E | 全过 | 11 / 11 ✅ |
| 前端占位转真实 | — | 9 / 12(余 3 个合理保留)✅ |
| E2E 验证场景 | — | 40+ 条 curl 全 PASS ✅ |
六、共性问题与心得
如果说第四周的共性主题是"让内部约定显式化、契约优先",那本周三个方向的共同主题就是——把 AI 的不确定性关进确定性的笼子里。
大模型是这周所有功能的核心引擎,但它天生不可靠:返回的 JSON 可能裹着 Markdown 代码块、可能限流超时、可能把意图判错、可能在并发下崩。三个方向不约而同地用同一套手段去驯服这种不确定性——诊断 Agent 用「正则清洗 JSON + 3 次重试 + 规则降级」兜住模型的不稳定;调度 Agent 用「强信号短路」在能确定的场景下根本不调模型,把不确定性的暴露面缩到最小;RAG 导入器用「source_id + 唯一索引」让重跑变成无副作用操作;连 SSE 都得用 od -c 按字节核对、而不是信任规范。凡是依赖模型的地方,旁边必须有一条不依赖模型也能走通的退路,这是本周最实在的工程体会。
另一个延续第四周的体会是:先有测试与契约,再谈 AI 接入。第四周写好的四层测试和故障诊断手册,这周在诊断 Agent、多 Agent 调度接入时直接派上用场——改完 GradingService、TargetedPracticeService 的输出契约,跑一遍冒烟 + 闭环脚本就能知道有没有破坏既有链路,而不是上线后才发现 mastery key 又对不上。
七、下周计划
本周让系统"聪明"了起来,下周的重心是让这份聪明"沉淀成可持续的工程能力"——稳、准、可上线。
第一,CI/CD 收口。把第四周的 L1 + L2 接进 GitHub Actions 每次 PR 自动跑,L3 用 Newman 跑 Postman 集合纳入合并检查,让本周新增的诊断 / 调度 / RAG 链路也进回归网。
第二,RAG 从试水到全量。当前只导了 150 / 18,908(0.8%),下周分批导入,先覆盖最高频的 csdn_data_structure(4940)+ github_offer(195);召回质量上加入 BM25 关键词召回 + RRF 融合,解决纯向量召回对专有名词不敏感的问题。
第三,多模态从 PDF 扩展到图像 + 手写。接入 qwen-vl,覆盖印刷体公式、图表、手写题,把"上传习题照片 → 自动识别 → 诊断讲解"打通成完整产品路径。
第四,临时方案工程化。密码重置邮件 / 短信从打日志接真实 SMTP(QQ / Gmail)+ 阿里云 SMS;Agent cancel 从占位 ok 升级为维护 Map<sessionId, Disposable> 真断流;AdminController 加 @PreAuthorize("hasRole('ADMIN')") 收紧权限;Schema 变更整理成 Flyway 迁移脚本。
第五,知识图谱与可视化。在诊断 Agent 沉淀的错因数据上,做基于 Neo4j 的学习路径推荐,并把学习效果做成可视化报告,让用户直观看到自己的进步。
至此,"计科智伴"从"能稳定跑"走到了"AI 真正参与每一次学习"。下周开始,让它"聪明得稳、聪明得准"。
本周报聚焦各方向新增功能与关键技术决策,详细 commit / 修复列表可参见各仓库 git log。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)