news 2026/9/3 10:14:03

基于RAG的课程资料问答助手:从零搭建智能体应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于RAG的课程资料问答助手:从零搭建智能体应用

课程资料问答助手是AI编程与智能体开发课程中非常典型的一个综合案例。学生在学习过程中经常需要快速查找教材、课件和实验指导书中的知识点,但课程资料以PDF、Word、Markdown、PPT等多种格式分散存放,人工检索效率低,直接问通用大模型又往往得不到课程专属的准确回答。这个案例的核心目标是让一个智能体基于课程资料回答学生提问:系统先检索相关文档片段,再结合大语言模型生成回答,同时展示答案对应的资料出处。

本案例适合刚学完Python基础、对RAG(检索增强生成)有一定了解,但还没有完整做过智能体项目的读者。通过本案例,你可以掌握:用AI编程工具(如Cursor)快速生成项目骨架,把多种格式课程资料转成可检索的向量库,用Streamlit搭建一个能实际使用的问答界面,以及面对报错时如何按数据流排查问题。文章会先讲清楚RAG为什么是这类助手的合理方案,然后给出完整可运行的实现,最后补充排错清单和生产化改造思路。

1. 课程资料问答助手到底要解决什么问题

1.1 课程资料的整理和检索成本被低估了

一门课程的资料往往散落在多个地方:教材PDF、课件PPT、实验指导书Word、讲义Markdown、补充阅读材料TXT。学生想查一个概念时,第一反应是打开搜索引擎或者直接问大模型,但这样做有几个明显问题。

搜索引擎返回的是通用网页,不一定是老师课件里的表述方式。通用大模型虽然知识面广,但并不知道你这门课的教学重点、实验要求、考核范围,更可能在不熟悉的细节上给出看似合理、实际错误的回答。对课程场景来说,答案是否有据可查比答案是否流畅更重要。

课程资料问答助手要解决的就是这件事:把课程资料变成可检索的知识库,让大模型只能依据资料内容回答,而不是凭记忆编造。学生在输入框里问“什么是生成器”“列表和元组有什么区别”,系统先找到资料里相关段落,再组织成回答,并标注来源。这样整个回答链路是可追溯的。

1.2 为什么不直接让大模型记忆课程内容

一种简单思路是把所有课程资料塞进大模型的上下文窗口,让模型基于这些内容回答。这个思路在小规模演示时可行,但会很快碰到上限。

大模型的上下文窗口再大,也装不下一门课程的全部PDF和PPT。一次提问塞入几十万字,响应速度和成本都不可控。而且课程资料是不断更新的,每次更新都重新构造Prompt会非常浪费。

RAG的做法是把“知识的存储”和“知识的生成”分离:向量库负责存储和检索,大模型负责根据检索结果生成回答。提问时只把最相关的几个片段取出来拼进Prompt,模型看到的是聚焦后的内容,回答质量更高,来源也更清楚。

RAG的核心流程可以用一句话概括:先把待检索的课程资料切成小块并向量化,再在提问时用向量相似度找到最相关的几个资料块,最后将资料块和问题一起交给大模型生成答案。

1.3 这个案例在课程中的定位

在一个完整的AI编程与智能体开发课程里,8.10案例属于“把大模型能力落地成具体应用”的阶段。前面通常已经讲过提示词工程、大模型API调用、LangChain基础组件等内容,到了这个案例,才把文档加载、文本切片、向量检索、大模型调用、Web界面串成一条完整链路。

这个案例也适合用AI编程的方式来做。使用Cursor、Continue等AI辅助编程工具时,开发者可以先描述需求,让AI生成项目骨架,再人工审查和调整关键部分。这里和低代码智能体开发平台的区别在于:低代码平台把RAG流程封装成黑盒,操作简单但出了问题难以排查;代码实现则能清楚看到每一步数据流,更适合教学和二次开发。

2. 环境准备与项目骨架生成

2.1 技术选型与版本约束

本案例的课程教学版本建议采用Python 3.10或3.11,框架组合为Streamlit + LangChain + Chroma + OpenAI兼容接口。

组件作用注意事项
Python 3.10+运行环境建议使用虚拟环境,避免污染系统Python
Streamlit快速搭建问答Web界面对RAG场景足够,不需要额外写前端
LangChain封装文档加载、切片、Prompt组装版本差异较大,导入路径要以安装版本为准
Chroma本地向量数据库适合课程演示,单机即可运行
OpenAI兼容接口提供向量化和对话能力可以接OpenAI,也可以接DeepSeek、通义、智谱等国内模型

选择Chroma而不是Elasticsearch或Milvus,原因很简单:课程资料量一般在几十到几百个文档量级,Chroma本地运行、配置简单,足够支撑教学演示。生产环境中需要处理大量并发和水平扩展时,再迁移到pgvector、Milvus等更完整的向量数据库。

2.2 用Cursor等AI编程工具生成项目骨架

这个案例如果用AI编程工具来做,第一步不是手写目录,而是把需求描述清楚后让工具生成骨架。推荐在Cursor中新建文件夹并打开,然后输入类似下面的提示词:

请帮我生成一个课程资料问答助手的Python项目,使用Streamlit作为前端,LangChain作为RAG框架,Chroma作为向量数据库,支持读取pdf、txt、md、docx格式的课程资料。 项目结构如下: - requirements.txt - build_knowledge_base.py 用于加载资料、切片、向量化并写入Chroma - app.py 用于启动Streamlit问答界面 - data/ 存放课程资料 - knowledge_base/ 存放向量库 要求: 1. build_knowledge_base.py 支持通过命令行参数指定资料目录和向量库目录。 2. app.py 使用st.chat_input实现问答,回答后展示检索到的来源片段。 3. 向量化模型和大模型都使用环境变量配置。 4. 代码要有清晰注释,方便教学演示。

AI生成代码后不要直接运行,先做两件事:检查依赖版本,检查导入路径。LangChain从0.1到0.3的导入路径变化较大,例如OpenAIEmbeddingslangchain_openai里,Chromalangchain_community.vectorstores里。生成代码后最好逐个import验证一遍,否则容易在启动阶段报模块不存在。

2.3 安装依赖与目录准备

创建虚拟环境并安装依赖:

python -m venv venv source venv/bin/activate

在Windows下激活命令为venv\Scripts\activate。然后在项目根目录创建requirements.txt,内容如下:

streamlit>=1.30 langchain>=0.2 langchain-community>=0.2 langchain-openai>=0.1 chromadb>=0.4 pypdf>=4.0 python-docx>=1.1

安装:

pip install -r requirements.txt

这里要提醒一个坑:不要把版本号写成latest。LangChain和Chroma的升级速度很快,不同小版本之间可能存在兼容性问题。教学演示时建议锁定一个已经验证过的版本组合,例如把langchain==0.2.16写死。如果安装后出现ModuleNotFoundError,优先检查是不是某个包没有随依赖一起装上,比如langchain-community经常被遗漏。

3. 核心实现:让问答助手真正基于课程资料回答问题

3.1 课程资料加载与文本抽取

build_knowledge_base.py的第一步是遍历资料目录,根据文件后缀选择不同的加载器。

from pathlib import Path from langchain_community.document_loaders import PyPDFLoader, TextLoader, Docx2txtLoader def load_documents(data_dir: str): data_path = Path(data_dir) docs = [] for file_path in data_path.iterdir(): if not file_path.is_file(): continue suffix = file_path.suffix.lower() try: if suffix == ".pdf": loader = PyPDFLoader(str(file_path)) docs.extend(loader.load()) elif suffix in (".txt", ".md"): loader = TextLoader(str(file_path), encoding="utf-8") docs.extend(loader.load()) elif suffix == ".docx": loader = Docx2txtLoader(str(file_path)) docs.extend(loader.load()) else: print(f"跳过不支持的文件格式: {file_path.name}") except Exception as e: print(f"加载失败: {file_path.name}, 错误: {e}") return docs

为什么要单独处理每种格式?因为PDF、Word、纯文本的解析机制完全不同。PyPDFLoader处理PDF中的文本层,Docx2txtLoader处理Word文档,TextLoader处理纯文本。这里最常见的坑是PDF扫描版,也就是里面的文字其实是一张图片,用PyPDFLoader读出来会是空白。遇到这种情况,需要先用OCR工具把PDF转成可复制文字的版本,否则后续检索必然失败。

3.2 文本切片:直接决定检索质量的一个环节

加载出来的文档可能很长,如果整篇作为一条记录写入向量库,检索精度会非常差。所以要先把文档切成小块,每一块包含一个相对完整的语义单元。

from langchain.text_splitter import RecursiveCharacterTextSplitter def split_documents(docs): splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ".", "!", "?", " ", ""] ) return splitter.split_documents(docs)

chunk_size=500表示每个切块尽量控制在500个字符左右,chunk_overlap=50表示相邻切块之间保留50个字符的重叠,避免一个知识点的句子被切断后信息丢失。

切块大小没有绝对标准,需要根据资料特征调整。如果课程资料以概念定义为主,500字符左右是一个合适的起点。如果资料包含大量代码,建议把chunk_size调大到800,并且把换行符\n作为主要分隔符,避免把一段完整代码切碎。

3.3 向量化与向量库构建

文本变成向量后,才能通过余弦相似度或欧氏距离寻找语义相近的片段。这一步需要调用外部Embedding模型。

from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma def build_vectorstore(docs, persist_dir: str): embeddings = OpenAIEmbeddings() vectorstore = Chroma.from_documents( documents=docs, embedding=embeddings, persist_directory=persist_dir ) return vectorstore

OpenAIEmbeddings()默认会读取环境变量OPENAI_API_KEY。如果使用国内支持OpenAI协议的服务,还需要配置OPENAI_BASE_URL。比如在启动脚本前执行:

export OPENAI_API_KEY="你的API Key" export OPENAI_BASE_URL="模型服务商提供的Base URL"

第一次运行会请求Embedding模型,把切好的文本块全部向量化并写入knowledge_base目录。向量库构建完成后,后续启动问答程序不需要重新构建,只需要读取已有的向量库。

3.4 检索问答主流程

问答程序的核心逻辑可以拆成四步:读取向量库、根据问题检索相关片段、组装Prompt、调用大模型生成回答。

from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain_community.vectorstores import Chroma from langchain.prompts import ChatPromptTemplate def create_qa_chain(persist_dir): embeddings = OpenAIEmbeddings() vectorstore = Chroma( persist_directory=persist_dir, embedding_function=embeddings ) retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个课程资料问答助手。请只根据提供的课程资料片段回答问题,不要编造资料中没有的内容。如果资料中没有相关信息,请明确说明。"), ("human", "课程资料片段如下:\n\n{context}\n\n学生的问题是:{question}") ]) return retriever, llm, prompt

检索参数k表示每次取回几个片段。k=4在多数课程场景下够用,既能保证信息充分,又不会让Prompt过长。如果问题涉及的知识点跨多份资料,可以适当调大。模型参数temperature=0是为了让回答尽量稳定、保守,减少自由发挥。

回答时手动组装数据流的版本更直观:

def answer_question(retriever, llm, prompt, question): docs = retriever.invoke(question) context = "\n\n".join([doc.page_content for doc in docs]) messages = prompt.invoke({"context": context, "question": question}) response = llm.invoke(messages) return response.content, docs

这一步展示了RAG和普通大模型调用的本质区别:大模型不是直接接收问题,而是先通过检索拿到“和这个问题相关的资料”,再基于这些资料回答。如果检索不到任何相关内容,应该让模型明确说不知道,而不是生成一个猜测性的答案。

3.5 Streamlit问答界面

最后编写app.py,把上面的逻辑接入Web界面。

import streamlit as st from build_knowledge_base import load_documents, split_documents from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma st.set_page_config(page_title="课程资料问答助手", layout="wide") st.title("课程资料问答助手") @st.cache_resource def init_qa(): embeddings = OpenAIEmbeddings() vectorstore = Chroma( persist_directory="knowledge_base", embedding_function=embeddings ) retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) return retriever retriever = init_qa() if "messages" not in st.session_state: st.session_state.messages = [] for msg in st.session_state.messages: with st.chat_message(msg["role"]): st.markdown(msg["content"]) question = st.chat_input("请输入你的课程问题,例如:什么是生成器?") if question: st.chat_message("user").markdown(question) st.session_state.messages.append({"role": "user", "content": question}) docs = retriever.invoke(question) context = "\n\n".join([doc.page_content for doc in docs]) from langchain_openai import ChatOpenAI from langchain.prompts import ChatPromptTemplate llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个课程资料问答助手。请只根据提供的课程资料片段回答问题,不要编造。资料中没有的信息请明确说明。"), ("human", "课程资料片段如下:\n\n{context}\n\n学生的问题是:{question}") ]) messages_input = prompt.invoke({"context": context, "question": question}) response = llm.invoke(messages_input) answer = response.content with st.chat_message("assistant"): st.markdown(answer) st.session_state.messages.append({"role": "assistant", "content": answer}) with st.expander("查看检索到的资料片段"): for i, doc in enumerate(docs, start=1): st.markdown(f"片段 {i}:") st.text(doc.page_content[:300])

@st.cache_resource保证向量库只加载一次,避免每次点击都重新读取。st.session_state负责保存多轮对话记录。st.expander把检索到的原文折叠起来,既提供了答案溯源能力,又不会让界面显得杂乱。

4. 运行验证与问题排查

4.1 从构建向量库到启动页面

先确认资料已经放进data目录,然后执行向量库构建命令:

python build_knowledge_base.py --data_dir ./data --persist_dir ./knowledge_base

正常输出会显示每个文件的加载情况,最后提示切片数量和向量库写入位置。如果这一步出现网络报错,优先检查OPENAI_API_KEYOPENAI_BASE_URL是否配置正确。

启动Web界面:

streamlit run app.py

启动后浏览器会自动打开本地页面。如果使用远程服务器,可以在命令行参数中加--server.address 0.0.0.0

4.2 提问验证与输出样例

以Python课程资料为例,在输入框提问“Python中的生成器是什么”。期望的结果不是模型凭知识生成定义,而是基于课程讲义中的原文片段回答,并且展开“查看检索到的资料片段”后能看到与生成器相关的教材段落。

验证检索质量有一个简单方法:在回答前打印检索到的文档片段。如果问题和片段完全不相关,说明向量检索或文本切片出了问题。如果片段相关但答案不准确,重点检查Prompt是否约束了模型“只依据资料回答”。

4.3 常见报错与应对

问题现象常见原因检查方式处理建议
提问后报401或403API Key未设置或失效查看终端是否有AuthenticationError检查环境变量,重启服务
回答为空或超时模型接口响应慢,或上下文过长查看Streamlit日志调小k值,或换用更快的模型
检索结果与问题无关切片过大或资料质量差打印检索片段看内容调小chunk_size,精简资料文件
PDF资料检索不到PDF是扫描版,没有文本层用PDF阅读器尝试复制文字先OCR再导入
新建向量库后查不到新资料启动时读取的是旧持久化目录查看knowledge_base目录修改时间重新构建并重启服务
LangChain导入报错版本差异导致模块路径变化检查报错中的模块名调整import路径或锁定依赖版本

4.4 七步排查链路

当问答助手出现异常时,按照数据流顺序排查,比在Streamlit界面里反复点按钮更有效:

  1. 确认问题是否到达后端:在Streamlit回调中打印question
  2. 确认文档加载是否正常:检查构建向量库时的日志,是否有“加载失败”提示。
  3. 确认向量库是否构建成功:查看knowledge_base目录是否生成,文件大小是否正常。
  4. 确认检索结果是否命中:打印retriever.invoke(question)返回的片段数量和内容。
  5. 确认Prompt组装是否正确:打印最终发给模型的messages_input
  6. 确认大模型API调用是否成功:查看返回类型和异常。
  7. 确认页面展示逻辑是否有误:检查st.session_state中的消息列表。

这条链路本质上和RAG数据流一致。任何一环断开,都会表现为“最终回答不对”,但真正的根因可能在前面的某一层。

5. 从课程演示到生产环境还差哪些工作

5.1 本机跑通的教学版存在哪些边界

课程案例在本地跑通后,停留在“演示可用”的水平。它使用本地Chroma存储向量,API Key直接配置在环境变量中,没有并发控制,没有日志系统,也没有用户权限管理。同一个时间只有少数几个学生访问时没问题,但如果部署成全院公开服务,这些短板都会暴露。

另一个容易被忽略的问题是向量库更新策略。教学版每次都是全量重建,如果课程资料只有几十份,全量重建完全可行。但如果资料增长到几千份,全量重建的时间和API调用成本都会明显上升,需要设计增量更新方案。

5.2 从学习版到正式服务的能力对比

能力项课程演示版生产环境版本
向量库Chroma单机存储pgvector、Milvus或ES,支持动态扩容
文本切片固定500字符按文档结构动态切分,结合标题层级
检索策略向量相似度Top-K混合检索:向量+关键词+重排序
模型调用直接调用大模型API通过网关统一管理多模型和限流
用户权限校园统一认证、按课程授权
日志追踪Streamlit默认日志结构化日志、请求链路追踪
资料更新手动全量重建文件监听、定时增量更新
效果评估人工抽查自动化评测:相关性和引用命中率

5.3 回答质量和数据安全不能只靠模型自觉

生产环境还需要考虑两个容易被忽视的问题。一是回答质量评估,RAG系统并不是把资料接入大模型就万事大吉,资料更新、切片参数调整、模型更换都可能影响回答质量,需要建立一套测试问题集,每次变更后跑一遍回归。二是课程资料的版权和隐私,课件和讲义属于课程团队的内容资产,对外提供服务前要确认使用范围,同时要记录用户的提问日志,防止恶意爬取或提示注入。

6. 扩展方向与可复用开发清单

6.1 从单轮问答扩展到真正的智能体

当前实现是无状态的单轮问答,学生每次提问都不参考之前的对话。可以扩展的方向包括:

  • 多轮对话记忆:把历史问题保存到st.session_state,构造Prompt时追加最近几轮对话,让追问“那它和迭代器有什么区别”这样的问题能被理解。
  • 支持多知识库:按课程、学期、老师隔离知识库,学生进入页面时选择对应的课程,而不是所有课程混在一起检索。
  • 增加工具调用:当检索不到答案时,允许智能体调用在线搜索、课程实验环境或考试系统等外部工具,拓展能力边界。
  • 反馈收集:在回答末尾加上“答案是否有帮助”的反馈按钮,把用户反馈写入日志,用于后续评估和调优。

6.2 RAG效果调优的优先级

如果回答效果不理想,不要一开始就换大模型,先按以下优先级排查:

  1. 原始文档是否干净,扫描版PDF是否已OCR。
  2. 切片大小和重叠是否匹配资料结构。
  3. 检索返回的Top-K片段是否真的相关。
  4. Prompt是否明确要求只依据资料回答。
  5. 模型能力是否足够,是否换更强的模型。
  6. 是否需要对检索结果做重排序。

前两项直接决定输入质量,输入质量差,后面再怎么优化Prompt也有限。

6.3 从课程案例迁移到真实项目前的检查清单

下面的清单可以直接用于发布前自查:

  • [ ] 所有课程资料是否已转换成可提取文本的格式,PDF扫描版是否处理完成。
  • [ ] 是否验证过切片参数在当前资料集上的效果。
  • [ ] 是否检查了向量库构建日志,确认没有文件加载失败。
  • [ ] 是否配置了环境变量,并且测试过Embedding和对话两条API链路。
  • [ ] 是否处理了“检索不到相关内容”的情况,模型是否会主动承认不知道。
  • [ ] 是否展示答案来源,让学生可以回到原文核对。
  • [ ] 是否记录用户提问日志,便于发现高频问题和回答质量短板。
  • [ ] 是否明确了课程资料的版权和使用范围。
  • [ ] 是否设置了并发限制,防止单机服务被打满。
  • [ ] 是否建立了一组测试问题集,在每次变更后运行回归验证。

6.4 这个案例最有价值的练习点

对学习AI编程与智能体开发的人来说,这个案例最重要的收获不是会写一个Streamlit页面,而是理解了一条完整、可验证的智能体数据流:从原始资料出发,经过加载、切片、向量化、检索、Prompt组装、模型生成,最后回到用户界面。每一步都可观察、可测试、可调优。这个能力可以迁移到文档问答、客服机器人、知识库搜索引擎等更多场景。如果刚开始接触AI编程,建议先把这个最小闭环完整跑通,再逐步加入多轮对话、多知识库和外部工具,避免一上来就搭建过大的架构。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 10:13:18

Excel运营数据分析实战:从数据清洗到可视化报告的完整指南

1. 先搞清楚运营数据分析到底要解决什么问题 很多运营新人拿到数据表格,第一反应是“我要做分析”,然后就开始在Excel里一通操作,最后可能只是把数据换了个样子重新贴出来。这其实没解决任何问题。运营数据分析的核心,不是把数据变…

作者头像 李华
网站建设 2026/9/3 10:13:48

TDOA三站定位与Chan算法:原理、代码实现与工程实践

简介:围绕TDOA三站时差定位技术,这份资源提供了基于Chan算法的球面定位Python实现。它面向无线通信、雷达定位、物联网设备追踪等方向的算法学习者,重点解决无GPS环境下仅依赖信号到达时间差推算信号源位置的问题。压缩包共计7个文件&#xf…

作者头像 李华
网站建设 2026/9/1 10:05:33

掌讯SD8227车机刷机教程:新UI升级包安装与排障指南

简介:掌讯SD8227新UI-800x480-5.1.zip是一套面向中控大屏设备的完整系统固件包,适用于车机维修、系统升级与定制开发场景。包内18个文件以bin引导程序、ext4系统镜像为主,另含gz压缩包、uImage内核、tar归档及配置xml等,整体约443…

作者头像 李华
网站建设 2026/9/1 10:03:11

基于神经网络的中文虚假评论识别系统设计与实现

简介:本资源是一套面向本科计算机/人工智能方向学生的毕业设计实战项目,聚焦电商与社交平台中虚假评论识别这一典型NLP应用场景,采用卷积神经网络(CNN)与LSTM混合架构实现高精度判别。压缩包共23个文件,包含…

作者头像 李华
网站建设 2026/9/3 7:06:01

宠物救助领养平台开发实战:从零搭建救助系统的完整指南

近年来,宠物领养需求持续增长,但信息分散、流程不规范、审核缺失等问题仍然突出。从技术角度看,构建一套宠物救助领养平台,核心在于将“发布—审核—申请—回访”这一完整链路线上化。本文基于实际开发经验,从系统架构…

作者头像 李华