将课程教材接入 RAG 知识库,三种模式闭环(学习→练习→考试),多 Agent 协同 + MCP 工具支撑,让大学生更快掌握课程内容。
- 🎯 三种模式:学习讲解 / 智能出题+评分 / 模拟考试
- 📚 RAG:PDF/TXT/MD/DOCX/PPTX/PPT 解析,Hybrid 检索(FAISS + BM25),附页码引用
- 🛠️ 工具:计算器、网页搜索、文件写入、记忆检索、思维导图、日期时间查询(共 6 个 MCP 工具)
- 🧠 记忆系统:SQLite 跨会话追踪薄弱知识点,自动强化
- ⚡ 体验:SSE 流式输出 + 执行进度提示,Mermaid 思维导图渲染与 PNG 导出
- 🔒 安全:路径穿越防护、并发 chdir 加锁、编码回退、分块死循环保护
| 模式 | 适用场景 | 执行 Agent | 工具可用性(代码层) | 关键约束 | 自动记录 |
|---|---|---|---|---|---|
| 学习 (Learn) | 概念讲解、知识梳理 | TutorAgent | ToolPolicy 放行全部 6 个工具(运行时按 Agent 规则约束) |
优先 RAG 引用,按需 ReAct 调用工具 | 问答写入 memory.db(qa episode) |
| 练习 (Practice) | 出题、提交答案、评分讲评 | QuizMasterAgent(出题)+ GraderAgent(评分讲解) | 出题阶段:按需最小工具(主要 websearch/get_datetime);评卷阶段:仅 calculator |
_is_answer_submission() 命中后强制切到 Grader 评卷链路 |
practices/ Markdown 记录 + memory.db |
| 考试 (Exam) | 模拟考试、自测报告 | QuizMasterAgent(出卷)+ GraderAgent(批改讲解) | 出卷阶段:按需最小工具(主要 websearch/get_datetime);批改阶段:仅 calculator |
_is_exam_answer_submission() 命中后进入 Grader 评卷链路 |
exams/ Markdown 记录 + memory.db |
- 学习模式:基于上传教材 RAG 检索,支持引用来源展示、网页补充检索、思维导图生成与笔记落盘。
- 练习模式:QuizMaster 负责出题(Plan-Solve + 按需外部信息),提交答案后 Runner 自动路由至 Grader,按“逐题对照 → calculator 汇总 → 讲评”流程输出并落盘。
- 考试模式:QuizMaster 负责出卷(返回试卷正文 + 内部答案元数据),交卷后 Grader 负责批改与讲解;结束后自动保存考试记录并同步记忆。
- 实现说明:当前
core/orchestration/policies.py中learn/practice/exam的allowed_tools均为ALL_TOOLS。模式差异主要来自 Runner 路由和 Agent 内部实现约束,不是白名单本身。 - 方法论标签:Router=
Plan+Replan,Tutor=ReAct,QuizMaster=Plan-Solve,Grader=Plan + ReAct-Solve(calculator only)。
- 支持 PDF / TXT / MD / DOCX / PPTX / PPT 六种格式
- 文本分块 + FAISS 向量索引(嵌入模型:
BAAI/bge-base-zh-v1.5,专为中文优化) - 检索模式支持
dense / bm25 / hybrid,默认hybrid(RRF 融合) - GPU 自动加速:有 NVIDIA GPU 时自动使用 CUDA,batch_size 256;无 GPU 退回 CPU
- TXT/MD 文件自动检测编码(UTF-8 → GBK → Latin-1 回退)
- 检索结果携带文档名、页码、相关度分数
TOP_K_RESULTS控制默认返回数量;考试模式内部固定top_k=12
用户请求
↓
OrchestrationRunner(Python 调度器)
├─ [LLM] Router Agent ← 制定 Plan(need_rag / style)并在失败时 Replan 一次
├─ [工具] RAG Retriever ← Hybrid 检索(FAISS + BM25)
├─ [工具] memory_search ← 预取历史错题上下文
│
├─ 学习模式
│ ↓
│ Tutor Agent(ReAct)
│ ├─ 可调用:calculator · websearch · filewriter
│ │ memory_search · mindmap_generator · get_datetime
│ └─ 流式输出教学回答
│
├─ 练习/考试出题
│ ↓
│ QuizMaster Agent(Plan-Solve)
│ ├─ 默认不走工具循环,仅按需调用 MCP(websearch/get_datetime)
│ └─ 输出题目/试卷 + 内部元数据(quiz_meta / exam_meta)
│
└─ 练习/考试评卷(检测到答案提交)
↓
Grader Agent(Plan + ReAct-Solve)
├─ 先做内部评卷计划(不展示)
├─ 仅调用 calculator 汇总得分
└─ 流式输出评分结果 + 讲评
| 工具 | 功能 |
|---|---|
calculator |
数学表达式计算(支持 math/statistics/组合数学/双曲函数/单位换算,Python 受限 eval) |
websearch |
SerpAPI 网页搜索(用于补充教材外信息;实际是否调用由当轮模式提示词与任务阶段决定) |
filewriter |
将笔记写入课程 notes/ 目录(.md 格式) |
memory_search |
检索历史练习/错题记忆,自动强化薄弱知识点 |
mindmap_generator |
生成 Mermaid 思维导图,支持导出 SVG / 3× 高清 PNG / 源码 |
get_datetime |
返回当前精确日期、时间、星期,避免 LLM 凭训练数据回答时效性问题 |
说明:工具调用路径统一为 OpenAI tool call -> MCPTools.call_tool -> stdio MCP -> server_stdio.py -> 本地工具实现,不做本地直调 fallback。
1) 传输与协议
- 传输层:本地
stdio(Content-Length帧); - 协议子集:
initialize / notifications/initialized / tools/list / tools/call; - 协议版本:
2024-11-05。
2) Client 侧(mcp_tools/client.py)
_StdioMCPClient作为单例客户端,按需懒启动python -m mcp_tools.server_stdio;- 启动后先握手:
initialize,再发送notifications/initialized; - 每次
tools/call使用 JSON-RPC 请求 ID 做请求/响应匹配; - 进程异常时自动重连 1 次;进程退出时通过
atexit清理子进程。
3) Server 侧(mcp_tools/server_stdio.py)
stdout仅输出协议帧,业务print重定向到stderr,避免污染协议通道;tools/list返回由_to_mcp_tools()转换后的 MCP 工具定义(name/description/inputSchema);tools/call调用MCPTools._call_tool_local()执行真实工具逻辑并返回结构化结果。
4) 严格仅 MCP 语义
MCPTools.call_tool()统一走mcp_stdio,不再回退本地直调;- 返回体附加
via: "mcp_stdio"便于观测; - 通道异常时返回标准错误结构(
success=false+error),由上层 Agent 决定如何继续回答。
5) 上下文透传
filewriter的notes_dir由 Runner 注入后,通过 MCP 参数透传给子进程执行,保证写入当前课程目录。
后端通过 Server-Sent Events (SSE) 逐 token 推送,前端 Streamlit 实时渲染,减少等待感。
- 前端会展示当前执行阶段(如“模型分析中 / 检索中 / 工具调用中 / 整理答案中”),避免“卡死感”。
- 每轮回答只展示当前轮检索到的引用来源;历史回答保留各自引用,不会混入当前轮引用。
- 为降低引用编号串扰,前端传历史给后端前会清理 assistant 历史中的
[来源N]标记。
Browser (Streamlit :8501)
│ HTTP / SSE
▼
FastAPI (:8000)
│
├─ OrchestrationRunner(Python 调度器,非 LLM)
│ ├─ Router Agent → Plan + Replan(need_rag / style)
│ ├─ Tutor Agent → 学习讲解(ReAct)
│ ├─ QuizMaster Agent → 练习出题 / 考试出卷(Plan-Solve)
│ └─ Grader Agent → 练习/考试评卷讲解(Plan + ReAct-Solve)
│
├─ RAG Pipeline
│ ├─ DocumentParser (ingest.py)
│ ├─ Chunker (chunk.py)
│ ├─ EmbeddingModel (embed.py)
│ └─ FAISSStore (store_faiss.py)
│
└─ MCP Tools
├─ calculator
├─ websearch
├─ filewriter
├─ memory_search
├─ mindmap_generator
└─ get_datetime
| Agent | 调用时机 | 输入 | 输出 |
|---|---|---|---|
| Router | 每次请求首先调用;必要时触发一次重规划 | 用户消息 + 模式 + 失败原因(重规划时) | Plan(need_rag、style、output_format) |
| Tutor | 学习模式主执行 | 问题 + RAG 上下文 + 历史 | 教学内容(可含引用/工具结果) |
| QuizMaster | 练习出题 / 考试出卷 | 请求 + RAG 上下文 + 历史错题上下文 | 题目/试卷正文 + 内部元数据 |
| Grader | 练习/考试检测到答案提交 | 题目或试卷原文 + 学生答案 + 历史错题上下文 | 逐题对照 + 得分 + 讲评 |
├── README.md ← 本文档
├── docs/
│ ├── USAGE.md ← 使用手册(面向用户)
│ ├── ARCHITECTURE.md ← 架构设计与数据流
│ ├── debug.md ← 调试记录
│ ├── CONTRIBUTING.md ← 贡献指南
│ └── SECURITY.md ← 安全说明
├── requirements.txt
├── pyproject.toml
├── .env ← 本地环境变量(不入库)
├── rebuild_indexes.py ← 批量重建全部课程索引
│
├── frontend/
│ └── streamlit_app.py ← Streamlit 前端
│
├── backend/
│ ├── api.py ← FastAPI 路由、上传、SSE 端点
│ └── schemas.py ← Pydantic 数据模型
│
├── core/
│ ├── llm/
│ │ └── openai_compat.py ← LLM 客户端(兼容 DeepSeek / OpenAI)
│ ├── orchestration/
│ │ ├── runner.py ← 主编排器 + 记录保存
│ │ ├── prompts.py ← 提示词模板
│ │ └── policies.py ← 工具策略(模式 → 允许工具)
│ └── agents/
│ ├── router.py
│ ├── tutor.py
│ ├── quizmaster.py
│ └── grader.py
│
├── rag/
│ ├── ingest.py ← PDF/TXT/MD/DOCX/PPTX/PPT 解析
│ ├── chunk.py ← 文本分块(含重叠保护)
│ ├── embed.py ← sentence-transformers 嵌入
│ ├── lexical.py ← BM25 词法检索
│ ├── store_faiss.py ← FAISS 向量索引(线程安全)
│ └── retrieve.py ← dense/bm25/hybrid 检索 + 引用格式化
│
├── mcp_tools/
│ ├── client.py ← 工具实现 + schema + 调用路由
│ └── server_stdio.py ← stdio MCP Server(最小协议实现)
│
├── memory/
│ ├── manager.py ← 记忆检索/写入编排
│ └── store.py ← SQLite 存储访问
│
├── tests/
│ ├── test_basic.py
│ └── sample_textbook.txt
│
└── data/
├── memory/
│ └── memory.db ← SQLite 记忆库(episodes/user_profiles)
└── workspaces/
└── <course_name>/
├── uploads/ ← 上传的原始文件
├── index/ ← FAISS 索引文件
├── notes/ ← FileWriter 保存的笔记
├── mistakes/ ← 错题本(mistakes.jsonl)
├── practices/ ← 练习记录(自动保存,Markdown)
└── exams/ ← 考试记录(自动保存,Markdown)
- 环境准备
conda create -n study_agent python=3.11 -y
conda activate study_agent
git clone https://github.com/Eric-he-cn/your_AI_study_agent.git
cd your_AI_study_agent
pip install -r requirements.txt- 配置环境变量(项目根目录
.env)
# LLM(必填)
OPENAI_API_KEY=sk-xxxxxxxx
OPENAI_BASE_URL=https://api.deepseek.com # 或 https://api.openai.com/v1
DEFAULT_MODEL=deepseek-chat # 或 gpt-4o 等
# RAG(可选,均有默认值)
EMBEDDING_MODEL=BAAI/bge-base-zh-v1.5 # 中文优化嵌入模型
EMBEDDING_DEVICE=auto # auto/cuda/cpu
EMBEDDING_BATCH_SIZE=256 # GPU 推荐 128-512;CPU 推荐 32
CHUNK_SIZE=600
CHUNK_OVERLAP=120
TOP_K_RESULTS=6
RETRIEVAL_MODE=hybrid # dense / bm25 / hybrid
BM25_K1=1.5
BM25_B=0.75
HYBRID_RRF_K=60
HYBRID_DENSE_WEIGHT=1.0
HYBRID_BM25_WEIGHT=1.0
HYBRID_DENSE_CANDIDATES_MULTIPLIER=3
HYBRID_BM25_CANDIDATES_MULTIPLIER=3
# MCP(可选)
SERPAPI_API_KEY=your_serpapi_key- 启动服务
# 终端1:后端
python -m backend.api # 端口 8000,Swagger: http://localhost:8000/docs
# 终端2:前端
streamlit run frontend/streamlit_app.py # 端口 8501- 首次使用流程
① 侧边栏创建课程(课程名 + 学科标签)
② 选择课程 → 上传教材(PDF/TXT/MD/DOCX/PPTX/PPT)
③ 点击「构建索引」→ 等待完成(显示块数)
④ 选择模式(学习/练习/考试)开始对话
| 变量 | 必填 | 默认值 | 说明 |
|---|---|---|---|
OPENAI_API_KEY |
✅ | — | LLM API 密钥 |
OPENAI_BASE_URL |
✅ | https://api.openai.com/v1 |
API 基础 URL |
DEFAULT_MODEL |
— | gpt-3.5-turbo |
对话模型名称 |
EMBEDDING_MODEL |
— | BAAI/bge-base-zh-v1.5 |
嵌入模型(HuggingFace Hub ID) |
EMBEDDING_DEVICE |
— | auto |
计算设备:auto / cuda / cpu |
EMBEDDING_BATCH_SIZE |
— | 256(GPU)/ 32(CPU) |
encode batch 大小 |
CHUNK_SIZE |
— | 600 |
文本分块大小(字符数) |
CHUNK_OVERLAP |
— | 120 |
分块重叠大小(需 < CHUNK_SIZE,建议 20%) |
TOP_K_RESULTS |
— | 6 |
默认检索返回块数(考试模式内部固定 top_k=12) |
RETRIEVAL_MODE |
— | hybrid |
检索模式:dense / bm25 / hybrid |
BM25_K1 |
— | 1.5 |
BM25 参数 k1 |
BM25_B |
— | 0.75 |
BM25 参数 b |
HYBRID_RRF_K |
— | 60 |
RRF 融合常数 |
HYBRID_DENSE_WEIGHT |
— | 1.0 |
dense 分支融合权重 |
HYBRID_BM25_WEIGHT |
— | 1.0 |
bm25 分支融合权重 |
HYBRID_DENSE_CANDIDATES_MULTIPLIER |
— | 3 |
dense 候选池倍数(top_k * 倍数) |
HYBRID_BM25_CANDIDATES_MULTIPLIER |
— | 3 |
bm25 候选池倍数(top_k * 倍数) |
SERPAPI_API_KEY |
— | — | SerpAPI 密钥(websearch 工具依赖,未配置时该工具返回失败并跳过) |
DATA_DIR |
— | data/workspaces |
课程数据根目录 |
完整接口见 http://localhost:8000/docs。
GET /workspaces # 列表
POST /workspaces # 创建 Body: {course_name, subject}
DELETE /workspaces/{course_name} # 删除POST /workspaces/{course_name}/upload # multipart/form-data 上传
POST /workspaces/{course_name}/build-index # 构建 FAISS 索引
GET /workspaces/{course_name}/files # 文件列表 + 索引状态
DELETE /workspaces/{course_name}/files/{filename} # 删除单个文件
DELETE /workspaces/{course_name}/index # 删除向量索引POST /chat # 同步对话
POST /chat/stream # SSE 流式对话请求体示例:
{
"course_name": "线性代数",
"mode": "learn",
"message": "什么是矩阵的秩?",
"history": [
{"role": "user", "content": "上一条消息"},
{"role": "assistant", "content": "上一条回复"}
]
}SSE 每帧格式:data: <JSON字符串>\n\n,需 json.loads() 解码。
流式中可能出现以下元事件:
{"__citations__": [...]}:当前轮引用元数据(文档名/页码/分数/原文片段){"__status__": "..."}:当前执行状态提示(检索/工具调用/答案整理)
| 层次 | 技术 |
|---|---|
| 前端 | Streamlit 1.31 |
| 后端 | FastAPI 0.109 + Uvicorn |
| LLM | OpenAI SDK 兼容(DeepSeek / OpenAI / 本地 Ollama) |
| 嵌入 | sentence-transformers BAAI/bge-base-zh-v1.5(中文,768 维) |
| 嵌入加速 | PyTorch CUDA 12.8(CPU fallback) |
| 向量库 | FAISS |
| 文档解析 | PyMuPDF(PDF)、python-docx(DOCX)、python-pptx(PPTX)、pywin32+PowerPoint(PPT) |
| 数据校验 | Pydantic v2 |
| 工具搜索 | SerpAPI |
| 异步 | Python asyncio + SSE |
- 文件上传:
basename净化 + 扩展名白名单(.pdf .txt .md .docx .pptx .ppt),阻断路径穿越与非法格式。 - 课程名称:
get_workspace_path()强制basename,拒绝../等非法输入。 - FAISS 并发安全:全局锁包裹
os.chdir()+ FAISS 读写,避免并发修改工作目录。 - 分块安全:
chunk.py自动收敛overlap >= chunk_size,杜绝死循环。 - 编码回退:TXT 解析按
utf-8-sig → utf-8 → gbk → latin-1尝试,不再静默丢失内容。 - 流式稳健性:SSE chunk 统一 JSON 编码,前端逐帧
json.loads(),防止换行截断。
- 扫描版 PDF(图片)需先 OCR,当前不支持直接提取文字。
.ppt解析依赖本机安装 Microsoft PowerPoint(通过 COM 转换到.pptx)。- 嵌入模型默认
BAAI/bge-base-zh-v1.5(中文优化,768 维);中英混排教材可换BAAI/bge-m3(多语言,更慢)。 - 更换嵌入模型后需运行
python rebuild_indexes.py重建所有课程索引(维度变化时旧索引不兼容)。 - FAISS 在 >100 万向量时性能下降,需考虑分片或 HNSW 方案。
- 设计为单机部署,多实例需额外处理索引共享与并发写。
- 网页搜索依赖 SerpAPI,未配置
SERPAPI_API_KEY时工具静默跳过。 - Mermaid 思维导图 PNG 导出在极复杂图表(>50节点)时可能超出浏览器渲染限制。
- 欢迎提交 Issue / PR,一起完善功能与安全性。
- 许可证:MIT License
- 作者:Eric He · 更新日期:2026-03-11