diff --git a/技术设计/去掉自动生成知识点并改为基于KnowledgeSource出题-修订计划.md b/技术设计/去掉自动生成知识点并改为基于KnowledgeSource出题-修订计划.md new file mode 100644 index 0000000..d71c795 --- /dev/null +++ b/技术设计/去掉自动生成知识点并改为基于KnowledgeSource出题-修订计划.md @@ -0,0 +1,468 @@ +# 去掉自动生成知识点并改为基于 KnowledgeSource 出题 - 修订计划 + +> 修订日期:2026-07-05 +> 对照仓库:`rag-worker` / `api-server` / `ios-projects/AIStudyApp` + +--- + +## 1. 目标确认 + +本次改造的目标不是“优化自动生成知识点”,而是把系统职责重新切开: + +1. 上传资料后的 RAG 导入流程只负责解析、分块、向量化、索引,不再自动产出候选知识点。 +2. 知识点生成改为用户手动触发,继续沿用现有 `generate-knowledge` 流程。 +3. Quiz 生成不再依赖 `KnowledgeItem`,改为直接基于 `KnowledgeSource + KnowledgeChunk` 原文出题。 +4. iOS 侧同步移除“候选审批”相关死页面/死路由/死 API,并补齐“按资料出题”的客户端能力。 + +--- + +## 2. 现状核对 + +### 2.1 已确认符合目标的部分 + +1. 手动 AI 解析知识点链路已经存在: + - `api-server/src/modules/knowledge-source/knowledge-source.service.ts` + - `api-server/src/modules/ai/workflows/knowledge-import.workflow.ts` + - `ios-projects/AIStudyApp/AIStudyApp/Core/Services/API/KnowledgeSourceAPI.swift` + - `ios-projects/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift` + +2. Quiz 的 prompt / schema 基础设施已经存在: + - `api-server/src/modules/ai/prompts/quiz-generation.prompt.ts` + - `api-server/src/modules/ai/prompts/schemas/quiz-generation.schema.ts` + +3. Prisma 已支持 Quiz 挂载资料来源与题目来源块: + - `api-server/prisma/schema.prisma` + - `Quiz.sourceType` + - `Quiz.sourceId` + - `QuizQuestion.sourceBlockIds` + +### 2.2 与目标冲突的现状 + +1. Python RAG Worker 仍然会自动生成候选知识点: + - `rag-worker/pipelines/import_pipeline.py` + - `rag-worker/candidate_generator.py` + - `rag-worker/api_client.py` + +2. API Server 仍保留完整的 ImportCandidate 审批链路: + - `api-server/src/modules/import-candidate/*` + - `api-server/src/modules/rag/internal-rag.controller.ts` + - `api-server/src/modules/rag/rag.module.ts` + - `api-server/src/app.module.ts` + +3. iOS 仍保留候选审批页面与 API: + - `ios-projects/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift` + - `ios-projects/AIStudyApp/AIStudyApp/Core/Navigation/Route.swift` + - `ios-projects/AIStudyApp/AIStudyApp/Core/Services/API/ImportAPI.swift` + +4. 当前 Quiz 仍然直接从 `KnowledgeItem` 拼题: + - `api-server/src/modules/quiz/quiz.service.ts` + +5. `ai-job` 的 quiz snapshot 体系仍然以 `KnowledgeItem` 为输入,不符合“从资料原文出题”: + - `api-server/src/modules/ai-job/quiz-generation-snapshot-builder.ts` + - `api-server/src/modules/ai-job/quiz-generation-registration.service.ts` + - `api-server/src/modules/ai-job/quiz-generation-job-definition.ts` + - 对应 spec 文件 + +6. iOS 当前 Quiz API 只支持按知识库生成,不支持按资料生成: + - `ios-projects/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift` + - `ios-projects/AIStudyApp/AIStudyApp/Core/Services/API/QuizAPI.swift` + - `ios-projects/AIStudyApp/AIStudyApp/Features/Quiz/QuizViews.swift` + +--- + +## 3. 修订后的执行范围 + +本次改造不应只做你原计划里的 3 个点,而应拆成 5 个批次。 + +### 批次 1:去掉 Worker 自动候选知识点生成 + +#### 1.1 Python 主流程 + +文件: + +- `rag-worker/pipelines/import_pipeline.py` + +修改: + +1. 删除 `generate_candidates` import。 +2. 删除 `save_candidates` import。 +3. 删除 `GENERATING_CANDIDATES` 状态更新与整段候选生成逻辑。 +4. 更新文件头部注释,导入流程从“下载→解析→清洗→切片→embedding→Qdrant→AI 候选”改为“下载→解析→清洗→切片→embedding→Qdrant”。 + +#### 1.2 Python 死代码清理 + +文件: + +- `rag-worker/candidate_generator.py` +- `rag-worker/api_client.py` + +修改建议: + +1. 若确认后续完全不再保留候选知识点机制,删除 `candidate_generator.py`。 +2. 删除 `api_client.py` 中 `save_candidates()`。 +3. 若短期内想保留历史兼容,可先不删文件,但必须确保运行时不再被引用。 + +#### 1.3 API 端导入状态枚举同步 + +文件: + +- `api-server/src/modules/document-import/document-import.repository.ts` + +修改: + +1. `findStaleJobs()` 中移除 `GENERATING_CANDIDATES`,避免恢复逻辑继续认为该状态合法。 + +#### 1.4 Internal RAG API 同步清理 + +文件: + +- `api-server/src/modules/rag/internal-rag.controller.ts` + +修改: + +1. 删除 `/internal/rag/candidates` 保存入口。 +2. 删除 `ImportCandidateRepository` 注入。 +3. 删除“候选创建成功后将 `learningStatus` 置为 completed”的逻辑。 + +注意: + +`learningStatus` 的完成语义需要改成“手动 AI 解析成功后完成”,不能再绑定到上传流程。 + +--- + +### 批次 2:删除 ImportCandidate 审批体系 + +这是原计划漏掉的最大后端范围。 + +#### 2.1 API 模块清理 + +文件: + +- `api-server/src/modules/import-candidate/import-candidate.controller.ts` +- `api-server/src/modules/import-candidate/import-candidate.service.ts` +- `api-server/src/modules/import-candidate/import-candidate.repository.ts` +- `api-server/src/modules/import-candidate/import-candidate.module.ts` + +修改策略二选一: + +1. 如果决定彻底废弃候选知识点体系:删除整个模块,并移出所有引用。 +2. 如果担心一次删太多:先停用对外入口与 worker 写入口,但保留模块代码一版作为过渡。 + +建议选项: + +优先选 `彻底废弃`。因为当前产品目标已经变为“知识点只能手动生成”,保留审批模块只会制造错误认知和死维护成本。 + +#### 2.2 模块依赖清理 + +文件: + +- `api-server/src/modules/rag/rag.module.ts` +- `api-server/src/app.module.ts` + +修改: + +1. 移除 `ImportCandidateModule` import。 +2. 清理随之失效的 provider / constructor 依赖。 + +#### 2.3 数据模型处理 + +文件: + +- `api-server/prisma/schema.prisma` + +建议分两步: + +1. 第一阶段先保留 `ImportCandidate` 表,不再写入、不再读取,避免一次触发大迁移。 +2. 第二阶段若确认无回滚需求,再执行 Prisma migration 删除 `ImportCandidate` 模型及关联字段。 + +这样更稳,先完成行为切换,再做数据库瘦身。 + +--- + +### 批次 3:改造 Quiz 生成链路为基于资料原文 + +#### 3.1 新增 Workflow + +新文件: + +- `api-server/src/modules/ai/workflows/quiz-generation.workflow.ts` + +实现建议: + +1. 注入 `AiGatewayService` 与 `PrismaService`。 +2. 根据 `sourceIds` 查询 `KnowledgeChunk`。 +3. 对原文做长度控制,避免 prompt 失控。 +4. 把 `chunk.id` 一并带入 prompt 上下文,便于返回 `sourceBlockIds`。 +5. 调用 `promptKey = quiz-generation`,按 schema 解析。 + +建议输入结构: + +```ts +{ + userId: string; + knowledgeBaseId: string; + sourceIds: string[]; + questionCount: number; +} +``` + +#### 3.2 Prompt 改写 + +文件: + +- `api-server/src/modules/ai/prompts/quiz-generation.prompt.ts` + +当前问题: + +系统提示明确写的是“根据知识点内容创建题目”,这与新目标冲突。 + +修改方向: + +1. 明确输入是“学习资料原文/资料分块”。 +2. 要求题目依据资料事实,不依赖知识点摘要。 +3. 要求输出尽量回填 `sourceBlockIds`。 +4. 继续保留 `choice/fill/judge` 三种题型,兼容现有 iOS 作答页。 + +#### 3.3 Schema 核对 + +文件: + +- `api-server/src/modules/ai/prompts/schemas/quiz-generation.schema.ts` + +当前 schema 基本够用,但要确认: + +1. `answer` 的编码规则继续兼容现有判分逻辑: + - `choice` 用选项索引字符串 + - `judge` 用 `"true"` / `"false"` + - `fill` 用标准答案文本 +2. `sourceBlockIds` 可选且允许返回多个 chunk id。 + +#### 3.4 注册到 AiModule + +文件: + +- `api-server/src/modules/ai/ai.module.ts` + +修改: + +1. 将 `QuizGenerationWorkflow` 加入 providers。 +2. 将 `QuizGenerationWorkflow` 加入 exports。 + +#### 3.5 改写 QuizService + +文件: + +- `api-server/src/modules/quiz/quiz.service.ts` +- `api-server/src/modules/quiz/quiz.module.ts` + +修改: + +1. `create()` 不再查询 `knowledgeItem.findMany()`。 +2. 改为根据 `dto.sourceType / dto.sourceId / dto.knowledgeBaseId` 决定出题范围。 +3. 查询 `KnowledgeSource`,然后将 `sourceIds` 传给 `QuizGenerationWorkflow`。 +4. AI 返回结果后落库到 `Quiz` 与 `QuizQuestion`。 +5. `quiz.title` 可优先使用 `dto.title`,其次 `result.quizTitle`,最后兜底“自测”。 +6. `quiz.description` 建议写入 `result.quizDescription`,避免浪费模型输出。 + +#### 3.6 DTO / Controller 收口 + +文件: + +- `api-server/src/modules/quiz/quiz.controller.ts` + +修改建议: + +1. 不要继续使用 `dto: any`。 +2. 显式定义 create dto,至少包含: + - `knowledgeBaseId: string` + - `title?: string` + - `sourceType?: 'kb' | 'source'` + - `sourceId?: string` + - `questionCount?: number` + +这样才能和 iOS 的“按整库出题 / 按单资料出题”契约对齐。 + +--- + +### 批次 4:处理 ai-job 的 quiz snapshot 旧设计 + +这是原计划第二个漏项。 + +#### 4.1 必须先决策 ai-job 是否仍承载 Quiz 生成 + +当前仓库同时存在两套 Quiz 生成路径: + +1. `quiz.service.ts` 里的同步生成逻辑。 +2. `ai-job/quiz-generation-*` 里的统一 Job Engine 迁移体系。 + +如果本次不处理这个分叉,后面一定会出现“一个入口按原文出题,一个入口按知识点出题”的双标行为。 + +#### 4.2 建议方案 + +建议分两阶段: + +1. 本次先让线上主链路 `QuizService.create()` 切到 `KnowledgeSource + KnowledgeChunk`。 +2. 同步把 `ai-job/quiz-generation-snapshot-builder.ts` 改成同样基于 source/chunk 构建 snapshot,至少保证未来迁移方向一致。 + +#### 4.3 需要修改的点 + +文件: + +- `api-server/src/modules/ai-job/quiz-generation-snapshot-builder.ts` +- `api-server/src/modules/ai-job/quiz-generation-registration.service.spec.ts` +- `api-server/src/modules/ai-job/quiz-generation-snapshot-builder.spec.ts`(如存在) +- `api-server/docs/architecture/m-ai-07-quiz-generation-migration-contract.md` + +修改: + +1. snapshot 输入不能再以 `knowledgeItems` 为核心。 +2. 应改为 `knowledgeSources + chunks` 的截断快照。 +3. `knowledgePointIds` 这类字段若无业务价值,应移除或重新定义。 +4. 契约文档需要同步说明“资料原文出题”而非“知识点出题”。 + +如果这批不做,至少要在计划里标成 `必须跟进`,不能留空。 + +--- + +### 批次 5:iOS 清理候选审批,并补齐按资料出题 + +#### 5.1 删除候选审批页面与 API + +文件: + +- `ios-projects/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift` +- `ios-projects/AIStudyApp/AIStudyApp/Core/Navigation/Route.swift` +- `ios-projects/AIStudyApp/AIStudyApp/Core/Services/API/ImportAPI.swift` +- `ios-projects/AIStudyApp/AIStudyApp/Core/Services/APIService.swift` + +修改: + +1. 删除 `ImportReviewPage`。 +2. 删除 `Route.importReview(sourceId:)` 及对应 label / destination。 +3. 删除 `ImportCandidate` model。 +4. 删除 `ImportCandidateService`。 +5. 同步清理注释与文档中“ImportAPI = DocumentImport + ImportCandidate”的描述。 + +#### 5.2 扩展 Quiz 创建请求 + +文件: + +- `ios-projects/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift` +- `ios-projects/AIStudyApp/AIStudyApp/Core/Services/API/QuizAPI.swift` + +修改: + +1. `QuizGenerateRequest` 需要扩展为: + +```swift +struct QuizGenerateRequest: Codable { + let knowledgeBaseId: String + let sourceType: String? + let sourceId: String? + let questionCount: Int +} +``` + +2. `QuizService.generate(...)` 需要支持: + - 按知识库生成 + - 按单资料生成 + +#### 5.3 Quiz UI 文案与交互修正 + +文件: + +- `ios-projects/AIStudyApp/AIStudyApp/Features/Quiz/QuizViews.swift` +- `ios-projects/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift` +- `ios-projects/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialDetailView.swift` + +修改方向: + +1. `QuizViews.swift` 中“基于知识库内容自动生成测验题目”文案应改为“基于资料内容 AI 生成测验题目”。 +2. 知识库详情页仍可保留“按整库生成测验”入口。 +3. 资料详情页或资料阅读页建议新增“按当前资料生成测验”入口。 +4. 如果资料维度新增入口,返回列表/详情时可考虑展示题目来源范围。 + +#### 5.4 iOS 解码模型补齐 + +文件: + +- `ios-projects/AIStudyApp/AIStudyApp/Core/Models/Quiz.swift` + +建议: + +1. 为 `Quiz` 增加 `sourceType`、`sourceId`、`description` 字段,便于后续 UI 展示。 +2. 如果后端继续返回 `type` 而不是 `questionType`,要确认当前 `QuizQuestion` 的解码是否正确,否则会出现题型丢失。 + +--- + +## 4. 推荐执行顺序 + +### 第一阶段:先切行为,不动大表 + +1. 批次 1:停掉 Worker 自动候选生成。 +2. 批次 3:让 Quiz 改为基于资料原文生成。 +3. 批次 5:iOS 侧删除候选审批死代码,并补齐按资料出题请求参数。 + +这三步做完,核心业务目标已经成立。 + +### 第二阶段:再收废弃系统 + +1. 批次 2:下线 ImportCandidate 模块。 +2. 批次 4:统一 ai-job quiz snapshot 设计。 +3. 最后再决定是否通过 Prisma migration 删除 `ImportCandidate` 表及关联字段。 + +--- + +## 5. 验证清单 + +### 后端验证 + +1. `rag-worker/pipelines/import_pipeline.py` 运行后不再发出 candidates 请求。 +2. `api-server` 中不再出现 `GENERATING_CANDIDATES` 相关运行状态依赖。 +3. 调用 `POST /quizzes`: + - 传 `knowledgeBaseId` 能成功出题 + - 传 `sourceType=source + sourceId` 能成功按单资料出题 +4. 当资料存在但还没有 `KnowledgeItem` 时,Quiz 仍能正常生成。 +5. 当资料没有 chunk 时,接口返回明确错误,而不是空题或 500。 + +### iOS 验证 + +1. 编译通过,无 `ImportReviewPage` / `ImportCandidateService` 残余引用。 +2. 资料阅读页手动 AI 解析仍能正常生成 `KnowledgeItem`。 +3. 知识库页生成测验正常。 +4. 单资料页生成测验正常。 +5. Quiz 作答、提交、结果页不受影响。 + +### 命令建议 + +```bash +python3 -m py_compile /Users/Admin1/Documents/Project/rag-worker/pipelines/import_pipeline.py +cd /Users/Admin1/Documents/Project/api-server && npx tsc --noEmit +``` + +iOS 仍需在 Xcode 中完整编译验证。 + +--- + +## 6. 风险与注意事项 + +1. `KnowledgeSource.learningStatus` 目前与候选生成存在隐式绑定,移除候选后要重新定义其状态流转。 +2. `DocumentImportWorker` 与 Python RAG Worker 是两条不同导入链路: + - 纯文本导入:NestJS Worker 直接 AI 提取知识点 + - 文件导入:Python Worker 只做解析索引 + 这两条链路的产品语义要在文档里写清楚。 +3. `ai-job` 的 Quiz 迁移体系如果暂时不改,后续会再次引入“按知识点出题”的旧逻辑。 +4. 若后续决定删除 `ImportCandidate` 表,需要先确认生产上没有后台/运营侧依赖该数据。 + +--- + +## 7. 结论 + +原计划方向基本正确,但范围明显偏小。 +最关键的补充有三项: + +1. 不只是删 Worker 候选生成,还要同步处理 `api-server` 的 `import-candidate` 体系。 +2. 不只是改 `quiz.service.ts`,还要同步处理 `ai-job/quiz-generation-*` 的旧快照设计。 +3. iOS 不只是删审批页,还要补上“按资料出题”的请求模型、API 调用和入口。 + +按本修订版执行,才是一次完整的业务语义切换。