
如果你正在学《AI编程与智能体开发》这门课或者正在准备把一批课程讲义、PPT、实验文档变成一个可以随时提问的助手那么厦门大学林子雨老师在课程里安排的“8.10 案例课程资料问答助手”就是一个可以直接照着做的完整示例。这次我们来把这个案例拆开看它到底在做什么、依赖哪些技术组件、如何用 RAG 加智能体的方式把资料变成问答能力、以及如何把它暴露成 HTTP 接口供其他系统调用。课程资料问答助手的核心思路并不复杂先把课程资料PDF、Markdown、Word 等切分成小块然后做向量化并存入向量数据库用户提问时先从向量库检索最相关的资料片段再把这些片段和问题一起交给大语言模型生成答案。这一套流程就是目前最常见的“检索增强生成”模式也是智能体开发中“知识库型 Agent”的基础形态。相比直接让大模型凭记忆回答这种方式的引用来源更明确也更容易控制答案边界。这篇文章会按工程落地的顺序展开先是核心能力速览和适用边界然后给出环境准备、项目结构、资料入库、问答测试、接口封装、批量任务设计、性能观察与排错清单。你在自己的电脑上复现时不需要完全照抄代码重点是理解每个环节为什么这么设计。1. 核心能力速览能力项说明项目定位基于指定课程资料的问答助手属于 RAG 知识库型智能体来源厦门大学林子雨《AI编程与智能体开发》课程案例 8.10核心功能课程资料检索、知识点问答、答案引用来源、超范围问题拒答技术框架文档加载 文本切分 Embedding 向量检索 LLM 生成支持输入格式PDF、Markdown、txt 等常见文档按加载器支持范围而定启动方式命令行脚本 Web 服务 API是否支持 API支持可用 FastAPI 封装/qa接口是否支持批量任务支持批量导入资料、批量问题提问需自行实现任务脚本硬件要求CPU 可跑小模型GPU 可明显提升向量化和生成速度显存以实际模型为准适合人群学习 AI 编程与智能体开发的学生、需要搭建内部知识库的开发者这套案例最大价值不是发明了新算法而是把“资料问答”这个高频需求做成了可复用的技术路径。你把课程资料替换成产品文档、项目手册、实验报告就变成了另一个垂直场景的知识库助手。2. 适用场景与使用边界2.1 适合什么场景课程复习和答疑学生基于老师提供的讲义、PPT、实验指导书提问“什么是智能体”“RAG 的流程是什么”助手从课程资料中检索答案。教学辅助助教将课程资料配置成问答服务减少重复回答基础问题。内部知识库把产品文档、技术规范、项目总结变成可查询的智能体新成员入职培训时直接提问。学习实验作为 AI 编程与智能体开发的综合案例帮助学生掌握文档处理、向量检索和 LLM 调用。2.2 不建议用在哪里开放域问答资料库里没有的内容助手不应强行编造。如果被问到“天气怎么样”应明确表示不在课程资料范围内。高准确率要求的生产决策RAG 的答案仍可能受到检索噪声和模型生成偏差影响不能替代人工审核。涉及敏感或未授权数据不要把未公开的试卷、个人隐私、商业机密课程资料直接丢进向量库。2.3 合规与安全提醒使用课程资料前先确认你是否有权处理这些内容。如果是老师提供的公开课件可用于学习实验如果涉及尚未公开的论文、内部文档应获得授权后再做入库。涉及人脸、声音、版权素材的智能体项目同样要严格确认授权边界。生产环境部署时接口需要加访问控制和日志审计避免被任意调用。3. 环境准备与前置条件这个案例不依赖特定开发工具只要能运行 Python 即可。如果本机没有 GPUCPU 也能跑通小规模资料库只是响应时间会变长。以下是一份通用检查清单检查项建议操作系统Windows 10/11、Ubuntu 20.04、macOSPython 版本3.9 及以上建议 3.10 或 3.11包管理pip 或 conda建议使用虚拟环境CUDA可选使用 GPU 推理时需要具体版本以 PyTorch 要求为准本地推理工具可选Ollama / vLLM / llama.cpp 等用于加载本地大模型磁盘空间课程资料本身不大但虚拟环境和模型文件需要预留数 GB 到数十 GB网络需要下载 Python 依赖和 Embedding 模型国内环境建议配置镜像安装依赖时建议创建独立虚拟环境避免和系统 Python 环境冲突python -m venv course-qa-env source course-qa-env/bin/activate # Windows 使用 course-qa-env\Scripts\activate然后安装核心依赖。这里给的是通用组合实际版本依据个人环境选择pip install langchain langchain-community langchain-huggingface \ chromadb sentence-transformers pypdf fastapi uvicorn openai requests如果下载慢可以切换 pip 源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名Embedding 模型从 Hugging Face 下载时经常超时建议设置镜像环境变量export HF_ENDPOINThttps://hf-mirror.comWindows PowerShell 下使用$env:HF_ENDPOINT https://hf-mirror.com4. 系统架构与实现思路课程资料问答助手的整体结构可以分成四层层级组成职责数据层课程 PDF、Markdown、TXT 等原始资料统一放在data目录索引层文本切分器、Embedding 模型、向量库将资料转换成可检索的向量索引问答层检索器、Prompt 模板、LLM根据问题检索并生成答案交互层CLI 脚本、FastAPI 服务提供人工问答或 API 调用入口整个流程可以按六个阶段理解加载文档读取课程资料文件。文本切分按段落或固定大小拆成块保证检索精度。向量化用 Embedding 模型把文本块转为向量。存储索引将向量写入 Chroma 或 FAISS 等向量数据库。用户提问计算问题向量检索相似度最高的 top-k 个文档块。生成回答将检索结果和问题拼成 Prompt交给 LLM 生成答案并附上来源。这个流程的好处是课程资料可以随时更新重新构建索引即可问答时不需要把全部资料都塞给大模型节省上下文长度回答也更聚焦。5. 安装部署与项目结构建议先创建一个项目目录目录结构大致如下course-qa-agent/ ├── data/ # 存放课程资料 PDF、md ├── src/ │ ├── ingest.py # 资料入库脚本 │ ├── retrieve.py # 向量检索封装 │ ├── answer.py # 问答逻辑 │ └── api.py # FastAPI 接口 ├── requirements.txt └── README.md这是示例结构你完全可以根据实际代码调整。启动流程分两步先运行入库脚本构建索引再启动问答接口或 CLI。5.1 资料入库脚本示例src/ingest.py负责加载data目录下的 PDF 文档切分后生成向量库。以下代码使用 LangChain 和 Chromafrom langchain_community.document_loaders import DirectoryLoader, PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_huggingface import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 1. 加载 PDF loader DirectoryLoader( path./data, glob**/*.pdf, loader_clsPyPDFLoader, show_progressTrue ) docs loader.load() print(f加载文档数: {len(docs)}) # 2. 文本切分 splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , ., ] ) chunks splitter.split_documents(docs) print(f切分后块数: {len(chunks)}) # 3. 向量化并存储 embedding_model HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5 ) vectordb Chroma.from_documents( documentschunks, embeddingembedding_model, persist_directory./chroma_db ) print(索引构建完成)注意这里的模型名BAAI/bge-small-zh-v1.5只是一个示例你需要确认本机能够访问 Hugging Face或者已经配置镜像。如果资料只有几千页的小规模CPU 跑这个 Embedding 模型也够用。5.2 问答逻辑封装示例src/answer.py先加载向量库然后实现“检索 生成”from langchain_community.vectorstores import Chroma from langchain_huggingface import HuggingFaceEmbeddings from openai import OpenAI # 加载向量库 embedding HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5 ) vectordb Chroma( persist_directory./chroma_db, embedding_functionembedding ) # 本地或远程 LLM 的 OpenAI 兼容客户端 client OpenAI( base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY ) LLM_MODEL your-model-name # 按实际模型名替换 def get_answer(question: str, top_k: int 4): # 检索相关片段 docs_with_score vectordb.similarity_search_with_score(question, ktop_k) # 组装上下文 context_blocks [] for doc, score in docs_with_score: source doc.metadata.get(source, 未知来源) context_blocks.append(f[来源: {source}]\n{doc.page_content}) context \n\n.join(context_blocks) prompt f你是课程资料问答助手。请根据下面的课程资料回答问题。 如果资料中没有相关内容请直接说“资料中未找到相关内容”不要编造。 课程资料片段 {context} 问题 {question} 请给出详细答案并在答案末尾列出参考来源文件名。 response client.chat.completions.create( modelLLM_MODEL, messages[{role: user, content: prompt}], temperature0.2, max_tokens1024 ) answer response.choices[0].message.content return answer, docs_with_scoresimilarity_search_with_score返回的 score 在 Chroma 中通常越小表示距离越近也就是相关性越高。具体含义请以你使用的向量库文档为准。5.3 启动方式如果没有本地 LLM 服务这个案例是无法生成回答的。你可以选择使用远程大模型 API把上面的base_url和api_key换成实际服务地址LLM_MODEL换成服务商提供的模型名。使用本地推理框架先启动 Ollama再接入。如果使用 Ollama启动后在另一个终端加载模型ollama pull qwen2.5:7b ollama serve然后修改answer.py中的客户端配置client OpenAI( base_urlhttp://127.0.0.1:11434/v1, api_keyollama ) LLM_MODEL qwen2.5:7b这里不指定具体版本请按你本机已安装的模型名调整。6. 课程资料入库与索引构建资料入库是整个问答助手的地基。很多初学者在“文档切分”这一步偷懒导致后面的问答效果很差。这里我按工程习惯拆成三个注意点。6.1 原始资料整理把课程资料统一放在data目录下按类型或章节分子目录data/ ├── chapter01_intro.md ├── chapter02_agent.pdf ├── slides/ │ ├── week1.pdf │ └── week2.pdf └── exercises/ └── lab01.pdf目录结构越清晰后面定位来源就越方便。如果资料中有扫描版 PDF需要先做 OCR否则加载出来的内容是空文本。这个问题不算问答助手的 Bug而是数据质量问题。6.2 切分参数怎么调chunk_size和chunk_overlap没有绝对最优值需要按资料类型调整。参数作用经验参考chunk_size每个文本块的最大长度300-800 字chunk_overlap相邻块的重复长度50-100 字separators优先按什么符号切分先按段落再按句子如果块太大检索精度会下降上下文也可能超长如果块太小语义不完整模型更难理解。建议先构建一次索引用几个典型问题测试再回头调整切分参数。6.3 向量库选择本案例用 Chroma 的原因很简单轻量、本地文件存储、API 友好适合教学和小型资料库。如果资料规模到几十万级可以切换到 FAISS 或 Milvus。对课程资料问答助手来说Chroma 已经足够。向量库构建完成之后会在项目目录下生成chroma_db文件夹。以后重新入库时要么删除旧索引重新构建要么使用Chroma.add_documents增量追加。避免新旧索引混在一起出现重复检索结果。7. 功能测试与效果验证索引构建完成后先不要急着写接口先用命令行跑几个问题验证效果。7.1 基础问答测试写一个简单的测试脚本from src.answer import get_answer questions [ 什么是智能体, RAG 的完整流程是什么, 这门课程需要用到哪些编程工具, ] for q in questions: print(问题, q) answer, docs get_answer(q, top_k3) print(回答, answer) print(- * 50)判断成功标准回答内容确实来自课程资料而不是模型杜撰。回答末尾能列出资料中的相关片段来源。如果问题完全超出资料范围模型明确说“未找到相关内容”而不是强行回答。7.2 检索质量抽查问答效果不好时先检查检索环节而不是怪大模型。可以单独打印检索到的片段query 什么是智能体 docs_with_score vectordb.similarity_search_with_score(query, k3) for doc, score in docs_with_score: print(score, doc.page_content[:100]) print(---)如果检索到的片段和问题完全不相关说明切分粒度、Embedding 模型或资料格式有问题。如果检索片段相关但模型回答混乱则要优化 Prompt 或降低temperature。7.3 超范围问题拒答测试最好的拒答方式是“资料中未找到相关内容”。你需要测试几个明显超出课程范围的问题比如“今天股票行情如何”“帮我写一首诗”。如果模型在上下文里找不到依据就不应该顺着用户乱说。这个行为可以在 Prompt 里强调也可以用规则判断检索得分阈值。8. 接口 API 与批量任务设计命令行测试通过后就可以用 FastAPI 把问答能力封装成 HTTP 接口。8.1 FastAPI 服务示例# src/api.py from fastapi import FastAPI from pydantic import BaseModel from src.answer import get_answer app FastAPI(title课程资料问答助手 API) class QARequest(BaseModel): question: str top_k: int 4 class QAResponse(BaseModel): question: str answer: str sources: list app.post(/qa, response_modelQAResponse) def qa_endpoint(req: QARequest): answer_text, docs get_answer(req.question, req.top_k) sources [] for doc, score in docs: sources.append({ source: doc.metadata.get(source, unknown), score: float(score) }) return QAResponse(questionreq.question, answeranswer_text, sourcessources)启动服务uvicorn src.api:app --host 0.0.0.0 --port 8000这里0.0.0.0表示监听所有网卡生产环境建议改为127.0.0.1或放在内网并用反向代理管理。8.2 curl 调用示例curl -X POST http://127.0.0.1:8000/qa \ -H Content-Type: application/json \ -d {question: 什么是智能体, top_k: 3}8.3 Python 调用示例import requests url http://127.0.0.1:8000/qa payload { question: 课程资料问答助手使用了什么技术, top_k: 4 } response requests.post(url, jsonpayload, timeout120) print(response.json())8.4 批量提问任务批量任务适合课程复习题库、自动生成问答摘要等场景。可以准备一个questions.txt每行一个问题什么是智能体 智能体有哪些分类 RAG 和微调有什么区别然后写一个批处理脚本import json import time import requests API_URL http://127.0.0.1:8000/qa with open(questions.txt, encodingutf-8) as f: questions [line.strip() for line in f if line.strip()] results [] for q in questions: try: resp requests.post(API_URL, json{question: q}, timeout120) data resp.json() results.append({question: q, answer: data[answer]}) except Exception as e: results.append({question: q, answer: fERROR: {e}}) time.sleep(0.5) # 温和调用避免打满资源 with open(results.json, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2)批量任务里一定要加超时和失败重试机制否则一个超时问题会卡住整个队列。更健壮的做法是使用任务队列比如 Redis RQ 或 Celery但教学场景下先用同步脚本足够。9. 资源占用与性能观察课程资料问答助手的性能瓶颈通常不在向量库而在 Embedding 模型和 LLM 推理。9.1 显存和内存怎么看如果使用 GPU 推理可以用nvidia-smi实时查看显存占用nvidia-smi在 Linux 或 macOS 下可以用top或htop查看内存Windows 下打开任务管理器看 Python 进程的内存占用。9.2 影响响应速度的关键因素因素影响资料总量和 chunk 数量影响索引构建时间不影响检索速度太多top_k 大小搜索后需要重排的文本越多生成上下文越大响应越慢LLM 模型大小7B 模型比 70B 快很多但效果也差一些并发请求数多个请求同时打进来如果没有排队机制容易 OOM是否使用 GPUCPU 跑 Embedding 可以接受CPU 跑 7B 模型会明显慢9.3 如何降低资源占用向量索引构建时可以分批处理避免一次性把几千个 PDF 全部加载进内存。LLM 推理降低max_tokens不要让模型每次生成超长回答。选择量化模型比如 Q4_K_M 精度的 GGUF 版本。使用模型缓存把重复问题的答案缓存到内存或 Redis减少重复计算。限制 API 并发FastAPI 是异步框架但底层 LLM 服务如果没有并发能力队列会堆积。可以在 API 层加一个信号量限制并发。这里不写死具体显存数字因为不同模型差异太大。你只需要观察自己的进程占用测试时用一个小资料库先摸清基线。10. 常见问题与排查方法问题现象可能原因排查方式解决方案安装依赖时报错Python 版本过低或包冲突查看报错日志中的包名升级 Python 到 3.9用虚拟环境重装加载 PDF 后内容为空扫描版 PDF 或缺少解析库打印前 100 个字符对扫描版做 OCR安装 pypdf、pdfplumberHugging Face 模型下载失败网络无法访问看下载日志配置HF_ENDPOINThttps://hf-mirror.com向量库无法持久化persist_directory 被占用或权限不足查看文件夹是否有写入权限换目录或删除旧索引重新构建问答接口启动后 404uvicorn 监听端口和访问路径不一致检查访问 URL 是否包含/docs启动uvicorn src.api:app访问http://127.0.0.1:8000/docs回答内容完全不在资料里检索命中的 chunk 不相关或 LLM 没遵循 prompt单独打印检索结果调整切分参数、top_k强化 prompt 说明模型回答编造内容temperature 过高或资料里没有答案降低 temperature检查检索片段把 temperature 调到 0.1-0.3增加拒答提示本地 LLM 推理很慢模型太大或 CPU 推理看 CPU/内存占用换小模型或使用 GPU 推理批量任务跑到一半卡住没有设置超时或接口无响应查看日志和 Nginx/Apiserver 状态增加请求超时加失败重试逐条打印进度11. 最佳实践与使用建议第一次构建索引时先用一份 PDF 测试确认加载、切分、向量化全链路没问题再批量入库。这样定位问题会快很多。保持项目文件分目录data放原始资料chroma_db放索引outputs放问答结果logs放运行日志。不要所有文件堆在一起。课程资料更新后建议重新构建索引而不是新旧混用。如果必须在原索引上追加做好去重。Prompt 里明确“你是一个课程资料问答助手”并给出拒答规则能明显减少模型跑题。API 服务对外开放时至少加一个简单 token 鉴权避免被当作免费问答接口刷流量。涉及试卷、学生个人信息、内部未公开材料时先确认授权再决定是否入库。在展示实验效果时保留问题和答案的日志方便后续分析检索失败的原因。这个案例里的“问答助手”是智能体开发的基础形态。你可以继续扩展接入多轮记忆、增加工具调用、让助手具备操作数据库或执行脚本的能力、使用工作流平台做可视化编排。12. 总结与下一步课程资料问答助手这个案例把 AI 编程和智能体开发中几个关键点串在了一起文档加载与切分、Embedding 向量化、向量检索、Prompt 工程、LLM 调用、API 封装。你先把最小链路跑通再根据课程内容逐步优化就能从“能回答”走到“回答得好”。最值得先验证的功能是换一份你自己的课程资料重新入库后问几个资料里明确写过的知识点看回答是否准确、来源是否清晰。最容易踩的坑有两个一是扫描版 PDF 没有做 OCR导致切分出的内容是空二是检索命中不相关片段导致模型拿着不相关的上下文强行回答。先解决这两个问题后面的一切都会顺畅很多。如果下一步想把案例做得更完整可以尝试给助手加上多轮对话记忆、文件上传界面、更多向量库的适配或者把课程资料按章节做权限控制。这个案例真正练到的不是某个库的用法而是面对一个新场景时如何把“资料”变成可交互的“智能体服务”。