青戈AI小栈
AI Agent + RAG + MCP 从零开发校园课程智能助手

01. 校园课程智能助手项目介绍与成品演示

6,305 字约 16 分钟读完1,136 次阅读更新于 2026/10/23

登录后收藏

阅读设置

字号
行距
代码

1. 项目简介

做一个能查真实数据、能按文档作答、能帮你存计划的 AI 课程助手。

学生登录后,可以让助手查询自己的课表、根据已上传的课程资料回答问题,并生成学习计划保存下来。

本节不写代码,先理清技术栈、看成品效果、搞懂 MCP / Agent / RAG 各自的职责,后续各节再逐块实现。

2. 学完你能得到什么

  • 一个可运行、可演示的 AI Agent 全栈项目(Vue 3 + FastAPI + MCP + LangChain Agent + RAG)
  • 搞懂 MCP、ReAct Agent、RAG 在真实业务系统中如何分工、如何串起来
  • 掌握面试常被问到的几个工程问题:
    • 多用户场景下,Agent 如何做到不越权查询他人数据
    • 如何约束模型不编造知识库中没有的内容
    • 如何用 SSE 把工具调用过程实时推送到前端

3. 适合谁

适合 不太适合
刚学完 Python 基础,想做第一个 AI 实战项目 完全没学过 Python
了解大模型、RAG、Agent 等概念,但还没动手做过 想系统学习深度学习、模型训练等理论
想要一个能跑起来、能演示、能写进简历的完整项目 已有 Agent 项目实战经验,想看进阶内容

没有 FastAPI、Vue 基础也可以跟学,用到的地方会逐步讲解。

教学方式:

  • 手把手带敲,关键代码逐行讲解
  • 后端代码全部手写,会重点讲解核心部分
  • 前端代码不手敲,提供前端工程文件并简单讲解
  • 先讲业务和效果,再讲概念,最后写代码,避免一上来被框架名词淹没
  • 专有名词都会配通俗说明

4. 技术栈

4.1 技术选型

下表用来建立「名称 → 职责」的对应关系。第一次看到这么多名词不用慌,不用一次记牢,后面每节只用到其中一两个,用到时都会详细讲。

层级 技术 职责
前端 Vue 3 + Element Plus + Vite 页面、表格、对话窗口;marked + DOMPurify 安全渲染 Markdown 回答
后端业务 FastAPI + SQLAlchemy + MySQL 登录、课表、文档、计划等接口
鉴权 JWT 标识当前用户,助手不能越权查询他人数据
模型调用 LangChain 统一封装大模型调用与消息格式;走 OpenAI 兼容接口,默认对接 DeepSeek,也可换成其他兼容模型
工具协议 FastMCP(独立进程) 把「查课表 / 检索资料 / 存计划」暴露为标准工具
智能编排 LangChain create_agent(底层基于 LangGraph) 按 ReAct 模式决定是否调用工具、调用哪个、调用几次
MCP 客户端 langchain-mcp-adapters 让 Agent 连接 MCP 服务,把 MCP 工具加载成可调用的工具
流式推送 SSE 把处理状态、工具调用过程和回答文字推送到前端
知识库 Chroma + Embedding(RAG) 文档向量化存储,为回答提供可检索的原文片段;Embedding 默认使用硅基流动的 bge-m3

关于 Chroma:本课选用 Chroma 是因为它轻量、零运维,适合教学和原型验证。生产环境中常用 pgvector、Milvus 等方案,RAG 的核心流程是一样的。

4.2 运行时服务

运行时需要同时启动三个服务:

flowchart LR
    STU["学生<br/>提问 · 查看"]
    ADM["管理员<br/>维护 · 入库"]

    subgraph SYS[系统边界 · 校园课程智能助手]
        FE["前端 Vue 3<br/>对话页 · SSE"]
        API["FastAPI 服务<br/>业务 API · Agent"]
        MCP["MCP 服务<br/>课表 · 检索 · 计划"]
        DB[("MySQL<br/>课表 · 计划")]
        VDB[("Chroma<br/>向量知识库")]
    end

    LLM["大模型 API<br/>第三方"]
    EMB["Embedding API<br/>第三方"]

    STU --> FE
    ADM --> FE
    FE --> API --> MCP
    MCP --> DB
    MCP --> VDB
    API --> LLM
    MCP --> EMB

    classDef base fill:#F1EFE8,stroke:#5F5E5A,color:#444441
    classDef biz fill:#E1F5EE,stroke:#0F6E56,color:#085041
    classDef ext fill:#EEEDFE,stroke:#534AB7,color:#3C3489
    class STU,ADM base
    class FE,API,MCP,DB,VDB biz
    class LLM,EMB ext
    style SYS fill:none,stroke:#888780,stroke-dasharray:6 4

虚线框内是本项目自己开发和部署的服务,框外右侧是需要申请 Key 的第三方 API。

进程 默认地址 职责
前端 http://127.0.0.1:5173 页面交互与对话 UI
FastAPI http://127.0.0.1:8000 业务 API,课程助手的对话入口
FastMCP http://127.0.0.1:8001/mcp 执行查课表、检索资料、存计划

MCP 服务没启动时,助手无法调用任何工具。演示前请确认三个进程都在运行。

5. 成品演示

演示前,管理员先上传课程文档并完成入库。然后学生 aaa 登录,在助手里依次操作:

flowchart LR
    A["管理员 admin<br/>上传文档并入库"] --> B["学生 aaa<br/>登录"]
    B --> C["问:明天有什么课?<br/>调用 查询我的课表"]
    C --> D["问:实验要预习吗?<br/>调用 检索课程资料"]
    D --> E["说:生成计划并保存<br/>调用 保存学习计划"]
    E --> F["切换学生 bbb<br/>验证数据隔离"]

    classDef base fill:#F1EFE8,stroke:#5F5E5A,color:#444441
    classDef ai fill:#EEEDFE,stroke:#534AB7,color:#3C3489
    class A,B,F base
    class C,D,E ai
操作 你会看到什么
问「我明天有什么课?」 过程区出现「查询我的课表」,回答与课表页完全一致
问「实验需要预习吗?」 过程区出现「检索课程资料」,回答依据已上传的文档;文档里没有的规定,助手不会编造
说「帮我生成一份学习计划并保存」 过程区出现「保存学习计划」,学习计划页新增一条记录
切换到学生 bbb 再问课表 看不到 aaa 的课表和计划,数据按用户严格隔离

从过程区可以直接看出:这不是一个只会聊天的机器人,而是在调用真实的业务接口。

6. 业务说明

6.1 角色与权限

角色 演示账号 主要能力
学生 aaa / 123(有课表) bbb / 123(无课表,用于验证数据隔离) 课表、学习计划、课程助手
管理员 admin / admin 课程管理、文档管理(上传知识库)、用户管理;也可以用助手验证检索效果

数据隔离原则:

  • 课表、学习计划:只属于当前登录用户
  • 知识库文档:全站共享,由管理员维护,学生通过助手检索(不是每人一份私有库)

6.2 学生侧能力

flowchart LR
    STU["学生"] --> S1["我的课表<br/>只读列表"]
    STU --> S2["问资料<br/>助手检索知识库"]
    STU --> S3["学习计划<br/>手动填写或助手生成"]
    S1 --> DB[("MySQL<br/>课表 · 学习计划")]
    S3 --> DB
    S2 --> VDB[("Chroma<br/>向量知识库")]

    classDef base fill:#F1EFE8,stroke:#5F5E5A,color:#444441
    classDef biz fill:#E1F5EE,stroke:#0F6E56,color:#085041
    classDef ai fill:#EEEDFE,stroke:#534AB7,color:#3C3489
    class STU base
    class S1,S3,DB biz
    class S2,VDB ai
  1. 我的课表:只读列表。演示数据由脚本按「今天 / 明天 / 后天」写入,通过 course_id 与课程表关联。
  2. 问资料:管理员先在「文档管理」中上传并入库,学生再在助手中提问,由检索工具查询知识库。
  3. 学习计划:可以手动填写保存,也可以由助手生成;只有用户明确要求保存时才写入数据库。

6.3 管理员侧能力

  • 课程管理:维护课程名称、代码等元数据(与向量库解耦)。
  • 文档管理:上传
输出
.txt / .md / .pdf / .docx

→ 保存文件 → 立即向量化入库;支持预览抽取出的纯文本、重新入库,删除文档时同步清理向量。

输出
- 支持批量上传;其中任意一个文件失败,整批回滚,不会出现「一半入库、一半没入库」。
- 扫描版 PDF 没有文字层,抽不出文本,无法入库(本课不涉及 OCR)。

演示顺序:先入库文档,再提问。 没有入库时,资料类问题会得到「检索为空」。

7. 核心概念:MCP、Agent、RAG

7.1 MCP:把业务能力封装成标准工具

业务层本来就有「查课表、检索、存计划」这些函数。MCP 把它们包装成工具,运行在独立进程中,Agent(或调试用的 MCP Inspector)通过统一协议调用。

工具名 过程区显示 作用
get_my_schedule 查询我的课表 查询当前登录用户的课表
search_course_docs 检索课程资料 只读检索知识库
save_study_plan 保存学习计划 写入当前用户的学习计划
flowchart LR
    AG["ReAct Agent"] --> CLI["FastAPI<br/>MCP 客户端 · 携带 JWT"]
    CLI -->|MCP 协议| SRV["FastMCP 服务<br/>解析 JWT 得到 user_id"]
    SRV --> T1["get_my_schedule"]
    SRV --> T2["search_course_docs"]
    SRV --> T3["save_study_plan"]
    T1 --> DB[("MySQL")]
    T3 --> DB
    T2 --> VDB[("Chroma")]

    classDef biz fill:#E1F5EE,stroke:#0F6E56,color:#085041
    classDef ai fill:#EEEDFE,stroke:#534AB7,color:#3C3489
    class CLI,DB biz
    class AG,SRV,T1,T2,T3,VDB ai

本课不在进程内手写 Tool Calling 清单,而是把工具部署在独立的 MCP 进程中(Streamable HTTP 协议),FastAPI 通过 langchain-mcp-adapters 作为客户端连接,拉取工具后交给 Agent 使用。

身份传递(重点): 前端登录拿到的 JWT 随对话请求传给 FastAPI,FastAPI 再原样转发给 MCP;工具侧自己解析 JWT 得到 user_id,并再查一次数据库确认该用户仍处于启用状态。用户 id 绝不由模型填写,这样即使模型被诱导,也查不到别人的数据。

7.2 Agent:决定是否以及如何使用工具

在系统提示词的约束下,大模型除了生成文字,还要决定:

  • 是否需要调用工具
  • 调用哪一个工具
  • 工具返回后,如何组织成面向用户的回答

本课采用 ReAct(Reason + Act)模式:边推理边行动,工具执行完后把结果交回模型,直到可以给出最终回答。为防止模型陷入死循环,循环轮数有上限(默认 6 轮,可在配置中调整)。

flowchart TB
    Q["用户提问"] --> R["模型推理<br/>结合系统提示词"]
    R --> J{"需要调用工具?"}
    J -->|是| T["通过 MCP 调用工具"]
    T --> O["工具结果返回模型"]
    O --> R
    J -->|否| A["生成最终回答<br/>SSE 逐字推送"]

    classDef base fill:#F1EFE8,stroke:#5F5E5A,color:#444441
    classDef ai fill:#EEEDFE,stroke:#534AB7,color:#3C3489
    class Q,A base
    class R,J,T,O ai

前端通过 SSE 实时接收处理过程,例如:

  • 正在思考…
  • 正在查询我的课表…
  • 查询我的课表完成
  • 随后逐字显示回答正文

后端推送的事件分为六种:status(状态)、tool_start(工具开始)、tool_end(工具结束)、token(回答文字)、done(完成)、error(出错)。前端根据事件类型更新过程区和对话气泡。

多轮对话: 服务端不保存会话,每次提问时由前端把之前的对话记录(只含问答文字,不含过程区内容)一起发给后端,模型据此理解上下文。

关于「明确要求时才保存」: 分两层保证。

  1. 提示词约束:系统提示词规定,只有用户明确说「保存」「记录」「存下来」时才能调用保存工具;只说「帮我规划一下」时只返回计划文本。这一层依赖模型遵守规则,偶尔仍可能误判。
  2. 代码兜底:同一轮对话里,保存工具最多成功执行一次,防止模型重复保存。

7.3 RAG:文档可检索,回答有据可依

RAG(Retrieval-Augmented Generation,检索增强生成)分为写入和读取两段:

  1. 写入(入库):上传文档 → 抽取纯文本 → 分块 → Embedding 向量化 → 存入 Chroma
  2. 读取(检索):用户提问 → 问题向量化 → 检索相似的原文片段 → 交给模型组织回答

本课的具体参数:每块约 500 字、相邻块重叠 50 字;每次检索取最相似的 3 个片段,并带上来源文件名。

flowchart LR
    subgraph W[写入:上传文档时执行]
        W1["上传文档"] --> W2["抽取纯文本"] --> W3["分块"] --> W4["Embedding<br/>向量化"]
    end
    subgraph R[读取:用户提问时执行]
        R1["用户提问"] --> R2["问题向量化"] --> R3["检索相似片段"] --> R4["模型组织回答"]
    end
    W4 --> VDB[("Chroma<br/>向量库")]
    VDB --> R3

    classDef biz fill:#E1F5EE,stroke:#0F6E56,color:#085041
    classDef ai fill:#EEEDFE,stroke:#534AB7,color:#3C3489
    class W1,W2,W3,W4 biz
    class R1,R2,R3,R4,VDB ai
    style W fill:none,stroke:#B4B2A9,stroke-dasharray:4 4
    style R fill:none,stroke:#B4B2A9,stroke-dasharray:4 4

两条关键约定:

  1. 向量化发生在上传(或重新入库)时,而不是每次对话时。
  2. 区分「检索失败」和「没检索到」:
    • 未配置 Embedding Key 或检索服务异常,属于检索失败,助手要如实说明「当前无法查询资料」,不能说成「资料里没有」;
    • 检索正常但没有相关片段,才说「知识库中没有检索到相关内容」。
    • 两种情况都禁止模型假装已查阅资料、编造内容。

7.4 三者如何串起来

flowchart LR
    FE["Vue 3<br/>SSE 流式"] --> API["FastAPI<br/>转发 JWT"] --> AG["ReAct Agent<br/>create_agent"]
    AG <--> LLM["大模型<br/>推理 · 决策"]
    AG --> MCP["MCP 服务<br/>3 个工具"]
    MCP --> DB[("MySQL<br/>课表 · 计划")]
    MCP --> VDB[("Chroma<br/>向量库")]
    ADM["管理员<br/>上传文档"] --> ING["文档入库<br/>分块 · 向量化"] --> VDB

    classDef base fill:#F1EFE8,stroke:#5F5E5A,color:#444441
    classDef biz fill:#E1F5EE,stroke:#0F6E56,color:#085041
    classDef ai fill:#EEEDFE,stroke:#534AB7,color:#3C3489
    class FE,ADM base
    class API,DB,ING biz
    class AG,LLM,MCP,VDB ai

上面一行是一次对话请求的链路,下面一行是管理员的文档入库链路。

能力 在链路中的位置 如果没有它
Agent 决策层:决定调用哪些工具 系统只是普通的 CRUD 页面
MCP 执行层:真正完成业务操作 模型只能空口回答
RAG 资料层:提供可检索的文档依据 规定类问题只能靠模型记忆,容易编造

8. 小结

  1. 本课 = 校园业务 + MCP 工具 + ReAct Agent + RAG 知识库。
  2. 学生侧三项能力:查课表、问资料、存计划;管理员负责课程元数据与文档入库。
  3. MCP提供工具,Agent决定如何使用工具,RAG 提供可检索的文档依据。
  4. 两条贯穿全课的工程原则:用户身份由系统传递,不由模型填写;检索不到就如实说,不许编造。
有帮助
登录后提问 / 纠错

提问与反馈

仅你和管理员可见。我的反馈

设置你的昵称

在英雄榜和讨论中,使用你自己的名字。

2–12 个字符0 / 12