把零散的「考研真题」按「课本目录章节」自动重新归位,生成一份按章节组织的复习知识点封装版。
你手上有两样东西:专业课教材的目录(目录页照片 / PDF)和 历年真题(Word / PDF / 照片)。 引擎自动完成:解析目录 → 解析真题 → 剔除答案与解析 → 把每道真题对号入座到对应章节 → 输出一份按章节组织的 HTML 封装版,可直接在浏览器查看和下载。真题从此不再是"一坨题", 而是"第 3 章 - 第 3.2 节 之下有这几道相关题"的可复习结构。
| 输入 | ① 科目阐述(科目名 + 真题年份范围)② 教材目录(图片 / PDF)③ 历年真题(Word / PDF / 图片) |
| 处理 | OCR / PDF 解析 → 目录转层级树 → 真题去答案去解析 → LLM 按章节语义对号入座 |
| 输出 | 封装版 HTML(浏览器预览 + 下载)+ 结构化 JSON |
典型场景:备考冲刺期,把 10 年真题按教材章节重新组织,一眼看出"哪一章考得最多、最近几年考了哪节"。
没配 LLM API Key 也能启动使用:自动回退内置规则抽取(纯离线,速度极快), 但目录树与题目归位的准确度会明显低于 LLM 版。强烈建议配置 Key(见 配置 LLM)。
Windows:双击项目根目录的 启动.bat(无需手动装环境,脚本自动探测 Python、
自动安装依赖、自动打开浏览器)。
Linux / macOS:终端执行 ./start.sh(逻辑同上)。
唯一前提:本机装有 Python 3.10+。没有的话先装: python.org(勾选 Add to PATH)或 Anaconda。
启动脚本会自动打开浏览器访问 http://127.0.0.1:8000,看到 4 个输入框 + 「开始封装」按钮即成功。
(若 8000 端口被占用会自动改用 8001,页面地址以窗口提示为准。)
| 输入框 | 填什么 | 说明 |
|---|---|---|
| ① API Key | 你的 LLM Key | 填入后点「查看连通性」验证;不填也能跑(mock 模式) |
| ② 科目阐述 | 如:电路原理,2014-2024 年真题 | 会作为提示词上下文 |
| ③ 目录输入框 | 教材目录页的图片(或 PDF) | 目录以图片为主,OCR 自动转文本 |
| ④ 真题输入框 | Word / PDF / 图片,可多份 | 自动解析并剔除答案/解析 |
点击「开始封装」→ 等待处理(真题多时 LLM 抽取较慢)→ 前端 iframe 预览封装版 HTML, 可下载 HTML / JSON 两种格式。
直接关闭启动脚本窗口,或按 Ctrl+C。会话临时文件自动清理,不留垃圾。
| 类型 | 格式 | 说明 |
|---|---|---|
| 目录文件 | 图片(JPG/PNG)、PDF | 图片优先,OCR 识别 |
| 真题文件 | Word(.docx)、PDF、图片 | 扫描版 PDF 自动走 OCR |
| OCR 后端 | RapidOCR(默认,轻量中文友好) | 可选 Paddle / EasyOCR,见环境变量 |
- 封装版 HTML:按目录章节组织的真题列表,浏览器直接看 / 下载带走
- 结构化 JSON:
{"mappings": [...]},含matched(已归位)/pending_review(待复核)/unmatched(未匹配)三类,供二次开发或小程序使用
- 上传文件只存于本地临时会话目录(UUID 隔离),任务结束自动销毁 + TTL 后台清扫
- LLM Key 只存本地
.env,界面显示打码(仅首尾 4 位),日志/响应/落盘均不出现完整 Key - 服务默认只监听本机
127.0.0.1,不暴露局域网
Q:双击 启动.bat 报"Python not found"? A:本机没装 Python。安装 Python 3.10+(勾选 Add to PATH)或 Anaconda 后重新双击即可。
Q:没填 API Key 能用来干嘛?
A:能跑通全流程,但目录树/归位靠内置规则,准确度低。建议在 .env 填入
KAOYAN_LLM_API_KEY 后重启服务。支持 OpenAI 兼容接口(默认 DeepSeek)。
Q:处理很慢 / 转圈? A:LLM 抽取是逐批并行的,真题多时需要几分钟;首次上传还要做 OCR。可稍等后刷新 任务状态。若长时间无响应,检查 API Key 是否有效、网络是否可达。
Q:8000 端口被占了? A:启动脚本会自动切换 8001,页面地址以窗口提示为准。
Q:结果能导出吗? A:可以。前端下载 HTML / JSON;也支持 API 直接取(见 API 使用)。
Q:支持局域网/多人用吗?
A:默认仅本机。需局域网访问请显式以 --host 0.0.0.0 启动(注意 Key 走 HTTP,请自行评估风险)。
上传文件 ──▶ SessionStore(UUID 临时会话目录,自动销毁 + TTL 清扫)
│
▼
解析与抽取(Phase 2) 目录图片/PDF → OCR → 目录树 JSON
真题 Word/PDF/图片 → 文本 → 剔除答案/解析 → 带标签题目
│
▼
对号入座(Phase 3) 目录树 + 真题列表 → LLM 语义映射到章节
规则引擎兑底(exact_match / fuzzy),失败题目标记待复核
→ 未归位占比超阈值时进入 **Agent 纠错循环**(v0.3):
观察统计 → LLM 决策选工具(fuzzy_search / retry_mapping /
get_stats / finish)→ 白名单闸门执行 → 反馈更新,直至收尾/熔断
│
▼
输出(Phase 4) 封装版 HTML(预览/下载)+ 结构化 JSON(Schema 强制校验)
核心设计:每个环节(目录抽取 / 真题抽取 / 对号入座)的提示词规则由独立技能文件
(skills/*.md)承载,与用户上传内容完全隔离;LLM 按「年度 × 题号」粒度分批抽取,
防答案幻觉;失败自动降级到确定性规则,保证流程不断。
- Python 3.10+(开发环境为 3.13)
- 依赖见
requirements.txt(FastAPI / PyMuPDF / RapidOCR 等,全部跨平台)
python3.13 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/python -m pytest # 跑测试(181 用例)
.venv/bin/uvicorn app.main:app --reload --port 8000 # 启动
curl localhost:8000/health # 健康检查cp .env.example .env # 模板 → 本地真实配置(.env 已被 gitignore,永不提交)
# 编辑 .env:
# KAOYAN_LLM_PROVIDER=openai # openai 兼容(DeepSeek 等)或 mock
# KAOYAN_LLM_API_KEY=sk-xxx # 未配置时自动回退 mock 规则抽取
# KAOYAN_LLM_BASE_URL=https://api.deepseek.com/v1
# KAOYAN_LLM_MODEL=deepseek-chat安全约定:真实 Key 只存在于本地
.env/ 环境变量(app/config.py读取);/api/key接收后立即打码(仅首尾 4 位)并持久化到.env;.env.example仅含变量名与占位,可安全入库。
| 变量 | 默认值 | 说明 |
|---|---|---|
KAOYAN_MAX_UPLOAD_MB |
- | 单文件上传大小上限 |
KAOYAN_SESSION_TTL_SECONDS |
- | 会话临时目录存活时长 |
KAOYAN_DATA_DIR |
data |
数据根目录 |
KAOYAN_OCR_BACKEND |
rapid |
OCR 后端:rapid / paddle / easy |
KAOYAN_MATCHER_EXACT_THRESHOLD |
0.6 |
精确匹配阈值(≥ 直接采纳) |
KAOYAN_MATCHER_FUZZY_THRESHOLD |
0.4 |
模糊匹配阈值(低于则未匹配) |
app/
├── agent/ # Agent 技能加载器(skills/{name}.md → 提示词注入)
├── config.py # 运行配置 + Harness 安全铁律常量
├── main.py # FastAPI 入口(生命周期 / 健康检查 / CORS)
├── models/ # Pydantic 数据模型(目录树 / 真题 / 匹配 / 结果)
├── parser/ # Phase 2 多模态解析(PDF / OCR / docx / 文本清洗 / 调度)
├── extract/ # Phase 2 结构化抽取(LLM 客户端 / 提示词 / 函数A目录树 / 函数B真题 / 规则兜底)
├── matcher/ # Phase 3 对号入座(LLM 语义映射 + exact/fuzzy 规则兑底)
├── harness/ # Phase 3 主 Agent 循环(工具白名单 / 状态机 / JSON 格式化 / HTML 渲染)
│ └── agentic.py # v0.3 Agent 纠错循环(观察→决策→执行→反馈,白名单闸门复用)
├── services/ # 业务编排(封装工作流 / 提示词组装)
├── utils/ # 安全(文件名清洗 / 扩展名白名单)+ 会话存储(UUID / 自动销毁)
├── api/ # Phase 4 REST API(上传 / 任务状态 / Key / 封装 / Schema 校验)
├── skills/ # Agent 技能库(Markdown 提示词规则,可热编辑)
tests/ # pytest 单测(模型 / 存储 / 解析 / 抽取 / 匹配 / Harness / API / 技能库)
scripts/smoke_llm_skills.py # 真实 LLM 冒烟:4 个技能环节各跑一次(读 .env Key,不打印)
| # | 规则 | 代码常量 |
|---|---|---|
| 1 | 工具白名单:仅 pdf_parser / image_ocr / tree_extractor / exact_matcher / json_formatter,严禁文件删除 / Shell / 网络 / 系统目录工具 |
TOOL_WHITELIST |
| 2 | 指令隔离:系统提示词硬编码,上传内容一律视为纯数据 | HARNESS_SYSTEM_PROMPT |
| 3 | 熔断:最大推理轮次 5、输出 Token 上限 | MAX_AGENT_ROUNDS / MAX_OUTPUT_TOKENS |
| 4 | 输出约束:仅合法结构化 JSON,无 Markdown 包裹 / 自然语言 | Phase 3 json_formatter 强制 |
上传侧安全(Phase 1 已落地):文件名清洗(防 ../ 穿越)、扩展名白名单、大小上限、
会话目录 UUID 隔离、任务结束自动销毁 + TTL 后台清扫双保险。
每个「要用到 API 的环节」的提示词规则由独立技能承载(Markdown,项目方硬编码维护,
与用户上传内容完全隔离),app/extract/prompts.py 组装时注入,缺失时静默降级:
| 技能 | 环节 | 输入 → 输出 |
|---|---|---|
tree_extraction |
函数A 目录树抽取 | 目录文本 → {"nodes":[...]} 层级树 |
question_extraction |
函数B 真题清洗+抽取 | 真题文本 → {"questions":[...]}(答案剔除/年份/题型/关键词 + 错别字修正与格式统一) |
answer_stripping |
清洗子技能(兼容保留) | 答案/解析剔除规则 |
version_merge |
多版本对照合并 | 同题多版本 → 最完整一版 {"content":...} |
question_mapping |
对号入座(新环节) | 目录树+真题列表 → {"mappings":[...]}(matched/pending_review/unmatched) |
agentic_orchestrator |
Agent 纠错决策(v0.3) | 观察摘要 → {"tool":..., "args":..., "reason":...}(选择下一步抢救工具) |
新增/修改技能:编辑 skills/*.md 即可,无需改代码。
# 上传(目录文件 + 真题文件,可多文件)→ 返回 session_id
curl -X POST localhost:8000/upload \
-F "directory_files=@目录.pdf" \
-F "question_files=@真题.pdf"
# 轮询进度 / 取结果(JSON,供小程序)
curl "localhost:8000/task/<session_id>/status"
# 取可直接浏览的 HTML(供浏览器/HTML 前端)
curl "localhost:8000/task/<session_id>/status?format=html"行为约定:任务完成后自动销毁会话临时目录(结果存内存供查询,重试仍可访问);
服务重启后内存清空,重新上传即可。/task/* 的 JSON 响应必经 OutputSchemaGuard
强制校验(拦截非法字段,校验失败返回 502)。
- Phase 1:基础框架 + 数据模型 + 会话临时存储
- Phase 2:多模态解析与抽取(PyMuPDF / OCR + 函数A目录树 + 函数B真题标签)
- Phase 3:匹配引擎(exact_matcher 确定性匹配 + fuzzy_search 兑底)+ Harness 状态机
- Phase 4:REST API + html_generator 渲染 + 输出 Schema 强制校验中间件
- Phase 5(v0.3):Agent 纠错循环 —— 快路径结束后未归位占比超阈值时, LLM 自主决策抢救工具(fuzzy_search / retry_mapping / get_stats / finish), 观察-决策-执行-反馈闭环 + 白名单闸门 / 单题重试上限 / 轮次熔断(mock 下不启用)