Skip to content
cjxxx222Public

About

题库 PDF 进 · 随机抽题出 —— 非结构化题库 PDF 的结构化抽取 + 智能刷题系统(规则优先 + LLM 兜底 / 三池加权抽题 / Leitner 间隔重复 / 带引用讲解)

Topics

Resources

Stars

51 stars

Watchers

0 watching

Forks

Repository files navigation

QuizForge:PDF 题库抽取与智能刷题系统

把一个大型题库 PDF 导入系统,自动抽取成结构化题库(题干 / 选项 / 答案 / 解析 / 题型),随后以随机抽题 + 错题优先 + 间隔重复的策略驱动刷题,即时判分、即时看解析、沉淀错题本与统计。

技术核心是半结构化题库的高质量结构化抽取管道:规则解析优先,DeepSeek LLM 兜底不规则块,全量入库前自动校验。零 GPU 依赖、无向量数据库、LLM 月成本预算 ¥50 以内。

完整技术方案(架构 / 里程碑 / 评测体系)见仓库根目录《QuizForge智能刷题系统-技术方案V2.docx》。

为什么是它(30 秒版)

备考刷题的题库是 PDF,格式粗糙:答案内嵌题干括号((C ))、判断题行尾打勾(√)、选项分隔符三种混用(A./A./A、)、题号跳号、题干折行跨页。手工整理一份几十页的题库要几小时。QuizForge 把这个过程做成可度量、可回归的管道——本 README 中的所有数字都来自真实样本实测(一份高校期末题库:14 页 / 136 个题号块,已脱敏)。

快速开始

# 环境:Python 3.12+,依赖 pdfplumber / pydantic / openai(界面需 streamlit)
pip install -e ".[ui,dev]"

# 1. 导入题库 PDF(规则优先;无 API key 时自动跳过 LLM 兜底)
cp .env.example .env   # 填入 DEEPSEEK_API_KEY(可选)
python -m quizforge import /path/to/题库.pdf

# 2. 终端练习(即时判分 + 解析 + 错题本 + 间隔重复)
python -m quizforge banks
python -m quizforge practice <bank_id 或名称> -n 10

# 3. 图形界面(M2):题库管理 / 练习 / 错题本 / 学习统计
streamlit run app.py   # 浏览器打开 http://localhost:8501

# 4. 测试与评测
pytest -q
python eval/run_eval.py import /path/to/题库.pdf

样本实测基线(2026-10-13)

对一份高校期末题库(14 页 / 136 个题号块,已脱敏)实测:

指标 纯规则(--no-llm) 规则 + LLM 兜底 目标值(方案 7.1)
切分完整率 95.6%(130/136) 97.1%(132/136) ≥ 98%
规则直出率 100% 99.2%(LLM 仅兜底 1 题) ≥ 80%
隔离区 6 条 4 条,全部可归因 ≤ 5% 且可归因
题号缺口检测 正确识别「单选:缺 7」 同左 —
入库题型分布 — 单选 92 / 判断 25 / 填空 15 —

LLM 只兜底了 1 题:有一道古诗默写题,源文件里两个选项都标成了 B.(源文件笔误),规则无法确定选项边界,DeepSeek 修复标号后通过校验——这正是"规则管格式、LLM 管长尾"分工的理想形态。

隔离区 4 条(每条带 raw_block + fail_reason + page_ref,是规则库扩充的燃料):

页码 原因 处置
p3 与第 6 题重复 去重拦截,符合预期
p7 题干为空答案括号 ( ) 源文件缺答案;不送 LLM(猜的答案不可信),转人工
p14 ×2 判断题残片(无 √/×) 源文件残缺,转人工

一条重要的诚实性设计:源文件本身没有答案线索的题块,不允许 LLM 兜底(_has_answer_signal 闸门)。兜底能修"格式坏但信息在"的题,不能猜"答案缺失"的题——否则会把不可信的猜测教给用户。这一条把 3 个「无答案线索」的题块挡在 LLM 之外(p7 空答案括号、p14 判断题残片 ×2),使完整率停在 97.1%,而不是靠模型猜测把数字刷高——这是刻意的取舍。

M2 刷题界面(Streamlit)

五个页面,全部复用 CLI 用的同一套引擎(抽题 / 判分 / 调度),界面层不含业务逻辑:

页面 能力
今日 落地页:今日待复习 / 错题待攻克 / 随机练习 三个快捷入口 + 今日已作答 / 连续学习 / 完成率 / 重做率 + 近 14 天曲线
练习 三种模式(随机 / 今日待复习 / 错题重练)→ 抽题 → 作答(选项即按钮,点选直接判分;多选与填空保留提交)→ 解析 → 答错一键生成带引用讲解 → 本轮小结
错题本 未攻克优先排序,页头「一键重练」直接开一轮只含错题的练习
学习统计 产品度量看板(见下)+ 近 14 天练习曲线 + 题型分布
题库管理 题库列表、上传 PDF 导入并展示导入报告、隔离区复核
streamlit run app.py    # http://localhost:8501

三个入口怎么来的:抽题引擎 pools.choose_question() 增加 restrict_ids 参数(只在给定题集内抽题,传空集合即"无题可抽",不会被当成"不限定"而抽全库)。 "今日待复习"限定 schedule.due_date <= 今天 的到期题,"错题重练"限定错题本题集——入口是数据视图,不是第二套抽题逻辑。

视觉:.streamlit/config.toml 定主题(靛蓝主色 + 浅灰分层),ui/app.py 注入自定义 CSS(卡片化题目区、圆角按钮与指标卡、紧凑排版)。

工程约束:数据库连接短开短关(Streamlit 每次交互重跑脚本,长连接会把未提交事务丢掉);练习状态存 st.session_state,跨 rerun 保持;快捷入口用 goto 中转页面跳转。

产品度量:从「系统质量」到「产品是否有用」

技术方案里的指标(切分完整率 / 规则直出率 / grounded_rate / whitelist_hit_rate)衡量的是系统质量; metrics.py 衡量的则是产品是否真的被用起来 —— 两者不能互相替代。

指标 定义 为什么看它
练习完成率 答完的轮次 / 开始的轮次 判断练习流程是否劝退;中途离开也计入未完成
错题重做率 错题本里被再次作答过的题 / 错题总数 判断错题本是否真的被用起来(学习闭环是否成立)
错题攻克率 已标记 mastered 的错题 / 错题总数 区分「回头看了」和「真的学会了」
总体正确率 答对次数 / 客观题作答次数 自评填空不计入分母,避免主观数据污染
题库覆盖率 作答过的题 / 题库总题数 衡量题库被消化到什么程度
连续学习天数 连续有作答记录的天数 学习习惯是否形成(今天没学不算断)

这些数字不依赖任何模型调用,纯 SQL 聚合,可离线复现(口径由 tests/test_metrics.py 的 12 条单测锁死)。

为此在存储层新增 practice_sessions 表(记录每轮练习的计划题数 / 实际作答数 / 正确数 / 状态)。完成率的分母就是它——没有这张表,「完成率」只能靠猜。

M3 讲解增强(开放域 RAG)

答错后自动生成带引用的知识点讲解:题干 → 查询构造 → 博查搜索 → 来源白名单重排 → 上下文组装 → DeepSeek 生成(强制引用编号)→ 讲解与网页快照落库。

python -m quizforge explain <题库> --seq 62      # 为指定题生成讲解
python -m quizforge practice <题库> -n 5 --explain   # 练习时答错自动讲解
python eval/run_eval.py explain-eval <题库> -n 5     # 讲解质量评测

实测指标(5 道题):

指标 值 说明
grounded_rate 1.0 全部讲解有检索支撑(无降级)
citation_coverage 1.0 全部讲解标注了引用编号
whitelist_hit_rate 55.6% tier1+tier2 来源占比(加查询引导前为 23.5%)

两个诚实的设计取舍:

  1. 开放域的来源质量是硬约束:白名单命中率 55.6% 意味着近一半来源是问答站/小百科。这是"开放域 vs 闭域(课件)"的真实代价——闭域没有这个问题,但需要课件语料。数字如实记录,不做粉饰。
  2. 查询引导是有效的工程手段:query 追加"知识点 讲解 教材"后,白名单命中率 23.5% → 55.6%(对照实验)。这类"检索侧微调 + 指标验证"正是 RAG 工程的核心工作,已设为默认配置。

降级路径:搜索 API 不可用 / 无结果 / 生成失败 → 降级为 LLM 直讲,输出开头强制声明"以下内容来自模型知识,非教材原文",绝不假装有出处。网页快照落库保证引用可验证(不怕原页失效)。

架构

六层,主链路不含任何检索组件:

层 职责 实现
接入层 练习 UI / 题库管理 / 统计 Streamlit(ui/,M2 已实现)
应用层 练习流程 / 判分 / 错题本 / 调度 quiz/ engine + metrics.py
抽取层 文本抽取 → 题型分节 → 切分 → 字段识别 → 校验隔离 parsing/ + validate.py
策略层 三池加权抽题 / Leitner 调度 / 防剧透 strategy/
存储层 SQLite 六表 + 题库 JSON 快照 storage/
模型层 LLM 兜底(DeepSeek,OpenAI 兼容)+ 讲解生成 llm/
讲解层(M3) 检索适配器 / 白名单 / 快照 / 带引用生成 explain/

判分零 LLM、抽题不吐答案(防剧透契约)、LLM 输出必须过 Schema 校验——三条铁律见 docs/adr/。

为什么不用混合检索 / RAG / LangChain

这个产品的核心链路(结构化抽取 + 抽题 + 判分)不存在语义检索需求:抽题是结构化取样,判分是答案匹配。检索增强只在一个场景有明确职责——答错后从课件检索该知识点的讲解(M3 可选增强,届时引入 Chroma + BGE-M3 + 本地 CUDA)。具体论证见方案 V2「〇、版本说明」与 docs/adr/0007。

目录

src/quizforge/
├── parsing/     # 抽取管道:pdf_extract → clean → sectioning → splitting → fields → pipeline
├── llm/         # DeepSeek 兜底客户端(Schema 校验 + 重试一次 + 隔离)
├── validate.py  # 入库门禁七条校验
├── storage/     # SQLite 六表 + JSON 快照
├── strategy/    # Leitner 调度 + 三池抽题
├── explain/     # M3 讲解增强:搜索适配器 / 白名单 / 快照 / 带引用生成
├── quiz/        # 判分 + 练习引擎
├── metrics.py   # M2 产品度量:完成率 / 错题重做率 / 攻克率 / 覆盖率 / 连续天数
├── ui/          # M2 Streamlit 界面(题库管理 / 练习 / 错题本 / 学习统计)
└── cli.py       # import / banks / practice
app.py           # Streamlit 启动入口(streamlit run app.py)
tests/           # 50 个单测(合成题库复刻真实版式特征;M3 全离线假源;UI 用 AppTest 冒烟)
eval/            # 评测入口 + 黄金标注集模板 + 基线报告
docs/adr/        # 9 条架构决策记录

合规与隐私

仅处理个人合法持有的学习资料;data/(SQLite、快照、原始 PDF 产物)已 gitignore;公开仓库前对题库内容、文件名、截图统一脱敏。

About

题库 PDF 进 · 随机抽题出 —— 非结构化题库 PDF 的结构化抽取 + 智能刷题系统(规则优先 + LLM 兜底 / 三池加权抽题 / Leitner 间隔重复 / 带引用讲解)

Topics

Resources

Stars

51 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages