news 2026/10/7 8:34:32

30天从零开始学AI应用开发(Day 19):项目二完结:RAG 知识库问答系统,把自己攒的资料变成私人顾问

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
30天从零开始学AI应用开发(Day 19):项目二完结:RAG 知识库问答系统,把自己攒的资料变成私人顾问

这是系列的第 19 篇。整个系列写给零基础、想入行 AI 的朋友,每天一篇,30 天后你会做出 3 个能写进简历的项目。

这篇解决什么问题

Day 1 的时候我埋了一个包袱,说 Day 19 会做 RAG 知识库问答系统,让大家在评论区说说想用 AI 解决的具体问题,说不定就是我的素材。今天来结账。

四天的铺垫到这里收口:Day 15 讲清了为什么需要 RAG,Day 16 学会把文字变向量,Day 17 用 ChromaDB 建库,Day 18 解决了切分和检索。今天把它们组装成一个完整系统。

和昨天的区别在哪?昨天是个验证性的小脚本,今天是一个有人愿意用的东西:能导入一整批文档、能追问、能标出答案来自哪份文件的哪一页、资料里没有就老实说不知道。这就是你简历上的第二个项目,也是当前招聘市场上最吃香的技能方向。

一、先看系统长什么样

先把结构画清楚,写代码时就不会乱。我们分三层:

  • 数据层:文档进来,切分,向量化,存进 ChromaDB
  • 检索层:用户提问,找出相关的片段
  • 回答层:把片段和问题交给大模型,生成带来源的回答

对应的文件结构建议这样分,别全堆在一个文件里:

rag-demo/ ├── config.py # 密钥和参数配置 ├── ingest.py # 文档导入(读文件、切分、入库) ├── rag.py # 检索和回答的核心逻辑 └── app.py # 跑起来的入口

分文件是专业习惯,也是简历上的加分点,面试官看到目录结构就知道你不是随手写的。

二、第一步:导入文档

先写 config.py,把配置集中管理:

# config.pyAPI_KEY="你的密钥"# 正式项目放 .env,用 os.getenv 读BASE_URL="https://api.deepseek.com"CHAT_MODEL="deepseek-chat"EMBED_MODEL="embedding-model-name"# 换成你平台的 embedding 模型名DB_PATH="./chroma_db"CHUNK_SIZE=500CHUNK_OVERLAP=50

再写 ingest.py,负责把文档变成库里的片段:

# ingest.pyimportosimportchromadbfromopenaiimportOpenAIfromlangchain_text_splittersimportRecursiveCharacterTextSplitterfromconfigimportAPI_KEY,BASE_URL,EMBED_MODEL,DB_PATH oai=OpenAI(api_key=API_KEY,base_url=BASE_URL)defread_doc(path):"""读文档,先支持 txt 和 md,够用了"""withopen(path,"r",encoding="utf-8")asf:returnf.read()defingest_file(path,collection):"""把一份文档切分入库,带来源信息"""text=read_doc(path)filename=os.path.basename(path)# 记下文件名,后面要标来源splitter=RecursiveCharacterTextSplitter(chunk_size=500,chunk_overlap=50,separators=["\n\n","\n","。","!","?"," ",""])chunks=splitter.split_text(text)print(f"{filename}:切成{len(chunks)}段")fori,chunkinenumerate(chunks):vec=oai.embeddings.create(model=EMBED_MODEL,input=chunk).data[0].embedding collection.add(documents=[chunk],embeddings=[vec],ids=[f"{filename}_{i}"],# 唯一编号metadatas=[{"source":filename,"chunk":i}]# 元数据:来源)

关键在最后那行 metadatas。它把“这段内容来自哪个文件”一起存进了库,后面回答时才能标来源。Day 18 讲切分时提过的“给片段贴身份证”,就是用在这里。

批量导入一个文件夹:

if__name__=="__main__":client=chromadb.PersistentClient(path=DB_PATH)collection=client.get_or_create_collection(name="knowledge")folder="./docs"# 把你的资料放这个文件夹fornameinos.listdir(folder):ifname.endswith((".txt",".md")):ingest_file(os.path.join(folder,name),collection)print("全部导入完成,共",collection.count(),"个片段")

三、第二步:检索加回答

核心逻辑写在 rag.py:

# rag.pyimportchromadbfromopenaiimportOpenAIfromconfigimportAPI_KEY,BASE_URL,CHAT_MODEL,EMBED_MODEL,DB_PATH oai=OpenAI(api_key=API_KEY,base_url=BASE_URL)client=chromadb.PersistentClient(path=DB_PATH)collection=client.get_or_create_collection(name="knowledge")defretrieve(question,n=3):"""检索最相关的 n 个片段,返回内容加来源"""vec=oai.embeddings.create(model=EMBED_MODEL,input=question).data[0].embedding result=collection.query(query_embeddings=[vec],n_results=n)hits=[]fordoc,metainzip(result["documents"][0],result["metadatas"][0]):hits.append({"text":doc,"source":meta["source"]})returnhitsdefanswer(question):"""检索 + 生成回答"""hits=retrieve(question)# 把资料拼起来,标上编号,方便让 AI 引用context="\n\n".join(f"[{i+1}] (来自{h['source']})\n{h['text']}"fori,hinenumerate(hits))prompt=f"""你是一个严谨的知识库助手。请只根据下面的资料回答问题。 要求: 1. 资料里没有提到的内容,直接回答"资料中没有相关内容",不要编造。 2. 回答时在句末标注依据来源,格式如 [1]。 3. 回答用中文,条理清晰,不超过 300 字。 资料:{context}问题:{question}"""response=oai.chat.completions.create(model=CHAT_MODEL,messages=[{"role":"user","content":prompt}])returnresponse.choices[0].message.content,hits

四、第三步:跑起来

app.py 做个简单的命令行交互:

# app.pyfromragimportanswerprint("知识库助手已就绪,输入问题开始(输入 退出 结束)")whileTrue:question=input("\n你:").strip()ifquestion=="退出":breakifnotquestion:continueresult,hits=answer(question)print("\nAI:",result)# 把用到的资料来源列出来,方便用户核对print("\n--- 依据来源 ---")fori,hinenumerate(hits,1):print(f"[{i}]{h['source']}")

运行起来,问几个问题试试。重点验证三件事:

  1. 资料里有答案的问题,答得准不准
  2. 资料里没有答案的问题,它有没有老实说不知道
  3. 回答末尾有没有标出来源

第三点特别重要。能标出来源的 AI,用户才敢信。这是 RAG 相比普通聊天机器人的核心优势,面试时也值得强调。

五、这个项目强在哪

对比一下你的项目一,你会发现项目二的价值跨度:

项目一是“用 AI 处理文件”,项目二是“让 AI 掌握你的私有知识”。后者能解决的问题更值钱:企业知识库、客服机器人、产品文档助手、法律条文问答,技术底座全是它。

写在简历上大概是这个形态:

RAG 知识库问答系统(个人项目)

  • 背景:个人积累的文档资料分散,检索靠关键词经常搜不到,需要语义级问答
  • 实现:基于 LangChain 文本切分 + ChromaDB 向量库 + 大模型构建 RAG 流程;设计 chunk_size 与 overlap 双参数切分策略,并通过问题改写提升召回质量
  • 增强:回答强制标注来源文件,提示词层面约束"资料外不编造",有效抑制大模型幻觉
  • 技术栈:Python、LangChain、ChromaDB、向量检索、大模型 API

明天还有接口化的内容,把项目二的完成度再提一档,到时候用更完整的话术写进简历。

常见报错排查

报错一:检索出来的片段和问题完全不相关。

先检查 embedding 是否用了同一个模型(入库和查询必须一致,Day 16 讲过的坑)。再检查切分是否合理,用 Day 18 的自测方法看片段是否完整。

报错二:AI 明明资料里有答案,却说“资料中没有相关内容”。

两种情况:一是召回没命中,加大 n_results 试试;二是提示词约束过严,AI 太保守了。可以改成“优先根据资料回答,资料中确实没有的信息再说明没有”。

报错三:老是返回同样几条片段。

检查 ids 是不是重复了,导致重复内容覆盖。ids 里带上文件名和序号就不会出这个问题。

报错四:导入大文件时很慢。

每条片段单独请求 embedding 确实慢。改成批量请求(embedding 接口一般支持一次传多条),速度能快好几倍。这个优化留给你自己动手,做出来可以直接写进简历。

报错五:中文文件名入库后乱码。

确认读取文件时用了 encoding=“utf-8”,写入时也一样。

今天的作业

用你自己的真实资料(笔记、手册、行业文档都可以)跑通整个系统,然后做三个测试:一个资料里有答案的问题、一个需要综合两段内容的问题、一个资料里完全没有的问题。把三个回答和来源截图贴到评论区。

我最想知道的是:资料里没有的那个问题,它有没有老实交代。

明天预告

Day 20:《用 FastAPI 给你的 RAG 穿上接口,别人也能访问了》。现在你的系统只能自己在本机跑,明天把它变成一个能被别人调用的服务。做完之后,面试官可以打开链接直接问你的知识库,这个体验完全不一样。而且 FastAPI 是后端开发的标配,学一次受益很久。

————————————————
*系列目录:30天从零开始学AI应用 开发

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

你的 AI 对话记录,存在谁家硬盘上?——聊聊 AI 编程的「上下文归属」

写代码的你,大概都遇到过这一幕。 你和 AI 编程助手磨合了三个月:它帮你理清了代码库架构、记住了你的命名习惯、踩过的坑、定过的规矩。然后你换了一把新工具——这一切,归零。 不是你的记忆出了问题。是这些上下文,从来就不在…

作者头像 李华
网站建设 2026/10/7 8:31:50

整装行业细分场景关键词布局方法:从奶油风到环保整装的内容组织

摘要整装行业的内容竞争,正在从“通用词争夺”转向“细分场景争夺”。用户不再只搜索“整装公司”,而是搜索“奶油风整装”“软装一体化装修”“老房改造整装”“环保整装”等带有明确场景指向的关键词。这些细分场景关键词,搜索量虽不及通用…

作者头像 李华
网站建设 2026/10/7 8:31:07

caveman:AI编码代理的极简主义实践与token优化指南

1. 从“caveman”说起:一个AI编码代理的极简主义实践第一次看到“caveman”这个词被用作项目名,我脑子里浮现的画面是原始人拿着石斧敲代码。但真正上手之后才发现,这个命名其实精准得可怕——它要解决的核心问题,就是让AI编码代理…

作者头像 李华