Commit 396de3e
authored
feat: 完成 Study Companion Phase 4 知识追踪与质量候选机制 (#1326)
* 新增了完整的知识追踪链路:
- 掌握度追踪
- 每次答题后根据正确率、稳定性、难度、置信度计算 topic mastery。
- 支持识别“假掌握”:看似正确率高,但表现波动大。
- 数据写入 mastery_snapshots,可用于后续 UI 展示薄弱知识点。
- 知识图谱
- 新增 topics 正式知识点表。
- 加载 knowledge_graph_seed.json,当前有 127 个中学数学 topic。
- 支持前置依赖检查,出题前能识别 blocker。
- LLM 发现的新知识点不会直接写入正式图谱,而是进入候选区。
- 错题系统
- 判题为 wrong / partial / dont_know 时写入错题。
- 支持错题变体生成。
- 连续多次答对后可自动标记为 resolved。
- 错题的 error_type 会转化为候选 misconception evidence。
- FSRS-like 复习调度
- 新增本地 fsrs_bridge.py,支持 Again / Hard / Good / Easy 评分。
- 维护复习卡片的 due、stability、difficulty、retrievability。
- get_review_queue() 可返回当前需要复习的 topic。
- 当前未接 fsrs-rs Python 绑定,采用本地调度实现。
- 候选知识质量机制
- 新增 KnowledgeQualityStore。
- 支持候选类型:
- topic
- edge
- misconception
- question_type
- 支持 evidence:
- mentioned
- used_in_prompt
- user_confirmed
- answer_improved
- user_rejected
- conflict_detected
- duplicate_detected
- review_retained
- 候选项会根据 evidence 得分,生命周期为:
- candidate
- active
- trusted
- deprecated
- 本地匿名知识贡献摘要
- 新增 PublicGraphContributionBuilder。
- 从候选知识和 evidence 生成本地匿名统计。
- 默认不上传,knowledge_contribution_opt_in = false。
- 摘要只保留结构化 ID/key、计数、score bucket 和 outcome。
- 会拒绝 OCR 原文、题目全文、用户答案、expected answer、feedback、LLM reply 等敏感内容。
- 插件 API 扩展
- study_status 新增:
- knowledge_quality_summary
- anonymous_knowledge_stats_summary
- 新增 entry:
- study_knowledge_quality_status
- study_anonymous_knowledge_preview
- study_clear_knowledge_contribution_queue
* 1. Prompt / fallback 集中
- 新增 N.E.K.O/config/prompts/prompts_study_companion.py
- N.E.K.O/plugin/plugins/study_companion/llm_prompts.py 和 N.E.K.O/plugin/plugins/study_companion/tutor_llm_agent.py 不再内联 Study Companion 的 LLM prompt / fallback 模板。
2. 覆盖率补齐并验证
- 补了 FSRSBridge、KnowledgeQuality、KnowledgeContribution、prompt 压缩、OCR、screen classifier、LLM 参数约束等测试。
- 覆盖率命令通过:uv run --with coverage python -m coverage report --fail-under=80 plugin/plugins/study_companion/*.py
- 结果:TOTAL 80%,满足 80% gate。
3. 测试全部改用 uv run
- 已用 uv run python -m pytest ... 跑过。
- 说明:直接 uv run pytest 在本机报 uv trampoline failed to canonicalize script path,所以使用了等价的 uv run python -m pytest。
4. async lock 跨 await 修复
- N.E.K.O/plugin/plugins/study_companion__init__.py 中 Tesseract / RapidOCR 下载不再持有 threading.Lock 跨 await,改为在 RLock 内设置 in-progress flag,await 前释放锁。
5. LLM temperature / max tokens 修复
- _call_model() 不再向 create_chat_llm() 显式传 temperature / max_completion_tokens。
- 清掉了 study_companion 自己的 llm temperature/max_tokens 配置和 llm_limits_for_operation() 死代码,避免和备忘录冲突。
验证结果:
- uv run python -m pytest ...:study_companion 相关 69 个测试通过。
- coverage gate:80% 通过。
- 静态搜索 study_companion 源码:无 temperature / max_tokens / max_completion_tokens / llm_limits_for_operation 残留。
* 已修复
- knowledge_quality.py
- TRUSTED 不再无条件保持。
- 新增可配置项:
- evidence_recompute_limit
- trusted_negative_threshold
- TRUSTED 候选在 user_rejected 或负向 evidence 达到阈值时可降级为 DEPRECATED。
- recompute_score() 不再硬编码 limit=5000,改用可配置上限。
- store.py
- 新增 safe_int()。
- load_knowledge_seed()、upsert_topic()、_topic_from_row() 的 depth / difficulty 转换改为容错转换,坏 seed 不会打断启动。
跳过 / 最小化处理
- 没有新增 evidence 分页 API 或归档/删除机制:当前 store 没有现成分页接口,这条是 nitpick,最小修复采用“可配置 recompute limit”,避免扩大改动面。
补充测试
- trusted 候选在配置阈值下可被强负向 evidence 降级。
- trusted 候选被 user_rejected 后可降级。
- 坏 seed / 坏 topic 数值字段不会导致启动或 upsert 崩溃。
- 现有 trusted conflict 测试改为显式使用 trusted_negative_threshold=1。
验证
uv run python -m pytest plugin/tests/unit/plugins/test_study_companion_knowledge_quality.py plugin/tests/unit/plugins/test_study_companion_knowledge_tracker.py -q
# 11 passed
uv run python -m pytest @files -q
# 73 passed
@files 为 plugin/tests/unit/plugins/test_study_companion*.py。
* fix: 修复边类型知识点过滤及未知主题答题处理
- 重构知识点过滤逻辑,正确匹配 EDGE 类型候选的 from/to_topic_id
- 未录入主题答题时使用空 topic_id,避免外键约束失败
- 启用 SQLite 外键约束并规范化 topic_key 存储
- 新增相关单元测试
* fix: 修复难度整数未归一化及错题匹配逻辑
- 将1-5的整数难度值正确缩放至0.1-1.0范围
- 优先匹配重试中的错题,且通用正确回答仅推进一条错题
* 主要改动
- N.E.K.O/plugin/plugins/study_companion/store.py:16
- 新增 append-only 表的滚动修剪逻辑,默认每组保留最新 5000 条。
- 对 mastery_snapshots、qa_records、review_log、knowledge_evidence 写入后按 topic_id 或 item_id 修剪。
- 新增 count_tracked_mastery_topics(),用于统计所有已有 mastery 记录的 topic 数。
- list_candidate_items() 支持按 topic_id 过滤候选知识项。
- N.E.K.O/plugin/plugins/study_companion/knowledge_quality.py:166
- list_candidates() 增加 topic_id 参数。
- prompt_evidence_summary() 现在先按 topic 过滤,再应用 limit。
- _candidate_matches_topic() 不再把没有 topic 的候选当作匹配当前 topic。
- N.E.K.O/plugin/plugins/study_companion/knowledge_tracker.py:457
- tracked_topic_count 改为用 count_tracked_mastery_topics(),避免被 overview 的 limit 截断。
测试改动
- N.E.K.O/plugin/tests/unit/plugins/test_study_companion_knowledge_tracker.py:89
- 新增 append-only 表按 key 修剪的回归测试。
- test_knowledge_seed_loads_idempotently 去掉脆弱的 120 <= count <= 150,改为 first_count > 0 加幂等性断言。
- N.E.K.O/plugin/tests/unit/plugins/test_study_companion.py:252
- 新增测试确认 tracked_topic_count 不受 list_mastery_overview(limit=...) 限制。
- N.E.K.O/plugin/tests/unit/plugins/test_study_companion_knowledge_quality.py:176
- 新增测试确认 prompt evidence summary 会先按 topic 过滤再 limit。
已验证
- .venv\Scripts\python.exe -m pytest plugin/tests/unit/plugins/test_study_companion_knowledge_tracker.py
- 结果:9 passed
- git diff --check 通过。
* - 原来外溢在 config/prompts/prompts_study_companion.py 的 Study Companion prompt 模板,已移动到插件内:
- plugin/plugins/study_companion/prompt_templates.py
- 两个引用点已改为插件内相对导入:
- plugin/plugins/study_companion/llm_prompts.py
- plugin/plugins/study_companion/tutor_llm_agent.py
- 全局文件现在是删除状态:
- config/prompts/prompts_study_companion.py
验证也过了:
- compileall plugin/plugins/study_companion
- test_study_companion.py:50 passed
* - get_status_summary() 的 average_mastery 改为调用 store 的全量最新掌握度平均值,而不是只按当前 overview limit 计算。
- store.py
- 新增 average_latest_mastery()。
- 它按每个 topic 最新一条 mastery_snapshots 计算全量平均掌握度。
- test_study_companion.py
- 扩展状态汇总测试:构造 12 个 topic,验证 tracked_topic_count 不受 limit 影响,同时验证 average_mastery == 0.6667,确认平均值来自全量 topic 而不是 limit=8 的截断结果。
- test_study_companion_knowledge_tracker.py
- 补充 1.0、"1.0"、3.0、5.0 的难度归一化断言。
- 将错题难度证据测试从 difficulty=1 改成 difficulty=1.0,覆盖实际 JSON float 场景。
已验证过:
- python -m compileall plugin\plugins\study_companion\knowledge_tracker.py
- uv run python -m pytest plugin/tests/unit/plugins/test_study_companion_knowledge_tracker.py -q
- 全量 study companion 单测:81 passed, 1 warning
* - N.E.K.O/plugin/plugins/study_companion/knowledge_tracker.py
- 新增 count_weak_topics():按全量 mastery overview 统计弱项数量。
- 新增 count_due_reviews():按全量 FSRS cards 统计 due review 数量。
- get_status_summary() 改为使用这两个真实计数。
- get_weak_topics(limit=...) 和 get_review_queue(limit=...) 仍保持展示列表限制,不影响 UI 列表大小。
- 测试
- N.E.K.O/plugin/tests/unit/plugins/test_study_companion.py:验证 weak_topic_count 不被展示 limit 截断。
- N.E.K.O/plugin/tests/unit/plugins/test_study_companion_knowledge_tracker.py:新增 due review 计数测试,12 个 due cards 时 get_review_queue(limit=8) 返回 8,但 due_review_count 返回 12。
验证通过:
- python -m compileall plugin\plugins\study_companion\knowledge_tracker.py
- 定向测试:62 passed, 1 warning
- 全量 study companion 单测:84 passed, 1 warning
* - plugin/plugins/study_companion/store.py
- 修复 topic 数值字段读取/写入时的默认值逻辑:
- 原来是 safe_int(value or 1, 1) / safe_float(value or 0.5, 0.5)
- 现在是 safe_int(value, 1) / safe_float(value, 0.5)
- 影响位置:
- _topic_from_row()
- load_knowledge_seed()
- upsert_topic()
- 目的:保留合法的 0 / 0.0,不再因为 falsy 被替换成默认值。
- 修复 record_wrong_question_correct():
- 新增 processed_error_types
- 同一次正确答题只推进每个 error_type 的一条 wrong question
- 保留原有 generic ""/"none" 只推进一条的行为。
- plugin/tests/unit/plugins/test_study_companion_knowledge_tracker.py
- 新增测试:同一 error_type 的两条 sibling wrong questions,只推进一条。
- 扩展数值容错测试:depth=0、difficulty=0.0 会被保留。
已验证过:
- python -m compileall plugin\plugins\study_companion\store.py
- 定向测试:13 passed, 1 warning
- 全量 study companion 单测:85 passed, 1 warning
* - knowledge_contribution.py
- 新增 _anonymous_topic_key()。
- 匿名知识贡献 payload 中的 topic 引用现在统一转成 topic:<hash>。
- 覆盖:
- topic candidate 的 topic_id
- edge 的 from_topic_id / to_topic_id
- misconception 的 topic_id
- question type 的 topic_id
- 目的:避免从用户文本 slug 生成的 topic_id 泄露到 anonymous stats / queue。
- test_study_companion_knowledge_contribution.py
- 新增测试:构造包含 alice_private_calculus_goal、bob_secret_prerequisite 的候选项,确认匿名统计里不出现原始 topic 文本,只出现 topic: hash key。
- store.py
- 增强 export_json() 的导出完整性:
- 新增导出 sessions
- 新增导出 qa_records
- 新增导出 review_log
- 新增导出 knowledge_evidence
- 新增对应 list/helper:
- list_sessions()
- list_qa_records()
- _qa_record_from_row()
- list_review_log()
- list_knowledge_evidence() 支持不传 item_id 时列出全部 evidence
- 这部分看起来是另一个 review 修复:让 store 的 JSON 导出包含 Phase 4 新增的学习记录/证据表,不只导出 summary 表。
- test_study_companion.py
- 扩展 test_study_store_round_trip_and_export:
- 写入 session、QA record、review log、knowledge evidence
- 验证 export_json() 包含这些新增表数据。
* 1. 清理误入 PR 的 pytest 临时库
这 3 个是删除状态,用来把已跟踪的测试产物从 PR 里移除:
D .pytest-run-qa-trim-codex/test_add_qa_record_trims_unkno0/study.db
D .pytest-run-qa-trim-codex/test_append_only_knowledge_tab0/study.db
D .pytest-run-qa-trim-codex/test_unknown_topic_answer_reco0/study.db
2. study_companion 运行时代码修改
N.E.K.O/plugin/plugins/study_companion/knowledge_tracker.py: 新增 _difficulty_to_level(),把难度统一归一到 1-5 档;错题纠正和 difficulty key 都改用这个函数。
N.E.K.O/plugin/plugins/study_companion/store.py: 调整 append-only 查询为“取最新 N 条再按旧到新返回”;seed topic 批量导入时减少重复 commit;knowledge_contribution_queue 增加按 status 裁剪历史的逻辑。
3. 测试修改
N.E.K.O/plugin/tests/unit/plugins/test_study_companion.py: 补了 QA、review log、knowledge evidence 最新记录返回顺序的断言。
N.E.K.O/plugin/tests/unit/plugins/test_study_companion_knowledge_contribution.py: 新增 contribution queue 按 status 裁剪的测试。
N.E.K.O/plugin/tests/unit/plugins/test_study_companion_knowledge_tracker.py: 新增 difficulty level 转换测试,以及默认中等难度也能正确解析错题的测试。
* fix: 修复自定义问题复用旧题目数据及复习队列截断问题
- 自定义题目评估时不再错误继承当前题目的主题等字段
- 移除 FSRS 卡片和到期复习的数量上限,避免大量卡片时遗漏
- 调整错题重试排序优先级,确保重试中的题目优先被选中
- 修复空 topic_id 查询 QA 记录的条件匹配
* feat(study_companion): propagate session_id and run_id through evaluation pipeline
- Extract session_id from context with fallback chain for knowledge tracking
- Pass run_id and session_id to learning context in evaluate_answer
- Update _resolve_current_run_id to check extra_args first
- Preserve seed topic fields on conflict in store upsert1 parent 1e5e238 commit 396de3e
18 files changed
Lines changed: 8772 additions & 326 deletions
File tree
- plugin
- plugins/study_companion
- static
- tests/unit/plugins
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| 41 | + | |
| 42 | + | |
40 | 43 | | |
41 | 44 | | |
42 | 45 | | |
| |||
52 | 55 | | |
53 | 56 | | |
54 | 57 | | |
55 | | - | |
56 | | - | |
| 58 | + | |
| 59 | + | |
57 | 60 | | |
58 | 61 | | |
59 | 62 | | |
60 | 63 | | |
61 | 64 | | |
62 | 65 | | |
| 66 | + | |
63 | 67 | | |
64 | 68 | | |
65 | 69 | | |
66 | 70 | | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
67 | 76 | | |
68 | 77 | | |
69 | 78 | | |
| |||
72 | 81 | | |
73 | 82 | | |
74 | 83 | | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
75 | 89 | | |
76 | 90 | | |
77 | 91 | | |
| |||
201 | 215 | | |
202 | 216 | | |
203 | 217 | | |
204 | | - | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
205 | 227 | | |
206 | 228 | | |
207 | 229 | | |
| |||
299 | 321 | | |
300 | 322 | | |
301 | 323 | | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
302 | 341 | | |
303 | 342 | | |
304 | 343 | | |
| |||
404 | 443 | | |
405 | 444 | | |
406 | 445 | | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
407 | 496 | | |
408 | 497 | | |
409 | 498 | | |
| |||
416 | 505 | | |
417 | 506 | | |
418 | 507 | | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
419 | 512 | | |
420 | 513 | | |
421 | 514 | | |
| |||
470 | 563 | | |
471 | 564 | | |
472 | 565 | | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
473 | 601 | | |
474 | 602 | | |
475 | 603 | | |
| |||
690 | 818 | | |
691 | 819 | | |
692 | 820 | | |
693 | | - | |
| 821 | + | |
694 | 822 | | |
695 | 823 | | |
696 | 824 | | |
| |||
707 | 835 | | |
708 | 836 | | |
709 | 837 | | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
710 | 848 | | |
711 | 849 | | |
712 | 850 | | |
713 | 851 | | |
714 | 852 | | |
715 | 853 | | |
716 | 854 | | |
717 | | - | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
718 | 860 | | |
719 | 861 | | |
720 | 862 | | |
| |||
790 | 932 | | |
791 | 933 | | |
792 | 934 | | |
793 | | - | |
794 | | - | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
795 | 939 | | |
796 | 940 | | |
797 | 941 | | |
| |||
814 | 958 | | |
815 | 959 | | |
816 | 960 | | |
817 | | - | |
| 961 | + | |
| 962 | + | |
818 | 963 | | |
819 | 964 | | |
820 | 965 | | |
| |||
825 | 970 | | |
826 | 971 | | |
827 | 972 | | |
828 | | - | |
829 | | - | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
830 | 977 | | |
831 | 978 | | |
832 | 979 | | |
| |||
859 | 1006 | | |
860 | 1007 | | |
861 | 1008 | | |
862 | | - | |
| 1009 | + | |
| 1010 | + | |
863 | 1011 | | |
864 | 1012 | | |
865 | 1013 | | |
0 commit comments