news 2026/10/6 8:06:52

30天从零开始学AI应用开发(Day 17):ChromaDB 上手:给本地文档建一个“外挂大脑”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
30天从零开始学AI应用开发(Day 17):ChromaDB 上手:给本地文档建一个“外挂大脑”

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

这篇解决什么问题

昨天你把文字变成了坐标,也感受到了“意思相近的两句话相似度更高”。今天要解决一个新问题:这些坐标存哪儿?

总不能每问一次就重新算一遍全部资料的坐标吧,那也太慢了。我们需要一个能存向量、能快速找出最近邻居的地方。这个东西叫向量数据库。

今天要用的 ChromaDB 是最好上手的向量数据库,不用装服务器、不用配环境,pip 装完就能用。今天结束的时候,你会拥有一个真的能“提问就返回相关段落”的外挂大脑。

一、向量数据库,就是给坐标建的索引

普通数据库擅长的是“精确匹配”,查 ID 为 1001 的用户、查名字叫张三的记录。但我们要的是“找离这个坐标最近的五个点”,这是完全不同的一类查询。

向量数据库就是为这类查询设计的。打个比方:普通数据库像字典,按字查;向量数据库像图书馆的书架索引,你说“我想找讲历史的”,它能把相关的几排书指给你。

名字听起来高深,你别被它唬住。今天你会看到,用起来只有三个动作:建库、塞数据、查数据。

二、装好,建个库

先装包:

pipinstallchromadb

Chroma 的一大好处是默认在本地存文件,不需要启动任何服务。建库的代码就三行:

importchromadb# 数据存在当前目录的 chroma_db 文件夹里,重启也不会丢client=chromadb.PersistentClient(path="./chroma_db")# 建一个集合(collection),可以理解成一张表collection=client.get_or_create_collection(name="my_docs")

注意 get_or_create 这个名字:有就打开,没有就建。所以这段代码重复运行也没问题,第二次会直接打开已有的库。

三、塞数据:三步一份

往库里存东西,要准备三样:

  • documents:原文(存进去方便后面拿出来给 AI 用)
  • embeddings:向量(Chroma 可以自己算,但为了统一,建议我们显式传)
  • ids:每条的唯一编号(必须,用来自查和更新)
fromopenaiimportOpenAI oai=OpenAI(api_key="你的密钥",base_url="https://api.deepseek.com")defembed(text):"""复用昨天的函数,把文字变成向量"""r=oai.embeddings.create(model="embedding-model-name",input=text)returnr.data[0].embedding# 假设这是你切好的资料片段chunks=["公司差旅费报销流程:先提交申请单,出差结束后五个工作日内提交发票。","差旅标准:市内交通每天不超过 100 元,住宿费按城市级别报销。","员工请假流程:提前一天在系统提交申请,由直属主管审批。",]# 批量入库:一个文件对应一份资料collection.add(documents=chunks,embeddings=[embed(c)forcinchunks],# 逐条算向量ids=[f"doc_{i}"foriinrange(len(chunks))]# 生成唯一编号)print("入库完成,共",collection.count(),"条")

关键在 ids 这一步。它就相当于每份资料的门牌号,重复的 id 会被当成同一条覆盖掉,所以生成时要保证唯一。

四、查数据:问一句,拿回相关段落

这一步是见证时刻。你直接用大白话提问,看它能不能把对的段落捞出来:

question="出差住宿怎么报销?"result=collection.query(query_embeddings=[embed(question)],# 把问题也变成向量n_results=2# 只要最相近的 2 条)print("查到的资料:")fordocinresult["documents"][0]:print("-",doc)

跑一下,你会看到它返回的是差旅报销和差旅标准那两条,请假那条没有被选上。注意,提问里用的是“住宿报销”,原文里写的是“住宿费按城市级别报销”,字面上并不完全一致,但依然被准确找到了。这就是昨天讲的向量检索的威力。

五、把检索和回答接起来

现在把两头连上,就有了一个最小的 RAG:

defask(question):# 第一步:检索result=collection.query(query_embeddings=[embed(question)],n_results=2)context="\n".join(result["documents"][0])# 把资料拼成一段# 第二步:让 AI 基于资料回答prompt=f"""请只根据下面的资料回答问题,资料里没有就说“资料中没有提到”。 不要使用资料之外的知识。 资料:{context}问题:{question}"""response=oai.chat.completions.create(model="deepseek-chat",messages=[{"role":"user","content":prompt}])returnresponse.choices[0].message.contentprint(ask("出差住宿怎么报销?"))

提示词里那两句约束是灵魂所在:只根据资料回答、资料里没有就说不知道。这是 RAG 治幻觉的关键,也是 Day 8 讲的“把隐性要求变成显性要求”的实战。

试着问一个资料里没有的问题,比如“年假有多少天”,它应该老实告诉你资料里没写,而不是编一个数出来。

常见报错排查

报错一:chromadb 装不上,卡在编译依赖。

优先升级 pip 再装。如果还是不行,指定清华源:pip install chromadb -i https://pypi.tuna.tsinghua.edu.cn/simple。

报错二:入库时提示 embeddings 维度不一致。

说明不同批次用了不同的 Embedding 模型。同一个集合里所有向量必须来自同一个模型,检查一下调用代码。

报错三:query 返回的结果不太相关。

三个可能:一是切分太粗(一段太长,混了多个主题);二是检索条数太少,试试加大 n_results;三是文档本身不含答案。切分的问题 Day 18 专门讲。

报错四:ids 重复导致数据被覆盖。

生成 id 时带上序号或者文件名,别只用内容做 id。

报错五:数据没保存住,重启后没了。

检查是不是用了临时客户端。想要持久化,必须用 PersistentClient 并指定 path。

今天的作业

把你的一份真实资料(笔记、手册、常见问题都可以)切分成几段,入库,然后问它三个问题:两个资料里有的,一个资料里没有的。把结果贴到评论区,尤其是最后那个问题的回答,看看它有没有老实说“资料里没有”。

明天预告

Day 18:《文档切分与检索优化:RAG 效果好坏的分水岭》。今天库建起来了,但效果只能说能用。为什么有时候它答得准,有时候答非所问?问题多半出在切分上。明天讲清楚切分的原则、怎么定每段长度、怎么提升召回率。这一篇的内容,直接决定你的项目能不能拿出手。

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

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

视频会议系统设计技术指南:从H.323协议到H.264编解码与部署实践

简介:面向智能视频会议系统规划与实施人员,这是一份可直接参考的技术解决方案文档,适用于政企、教育、医疗等行业的远程会议、应急指挥、远程培训场景,可辅助需求梳理、方案设计与项目汇报。文档以 doc 格式提供,压缩包…

作者头像 李华
网站建设 2026/10/6 8:06:21

一篇文章,吃透数据在内存中的存储

文章目录 整数在内存中的存储◦ 大小端字节序和字节序判断◦ 为什么会有大小端之分◦ 经典练习题 浮点数在内存中的存储浮点数存的过程浮点数取的过程 整数在内存中的存储 前面我们了解到整数的二进制表示方法有三种:原码,反码,补码。 对于有…

作者头像 李华
网站建设 2026/10/6 7:59:41

第二篇:keepalived VRRP 原理篇:协议与 VIP 漂移机制深入解析

Keepalived 原理篇:VRRP 协议与 VIP 漂移机制深入解析开篇很多运维朋友都用过 Keepalived,知道它能做高可用、能漂移 VIP,但对它"为什么能漂移"、VRRP 报文里到底传了什么、主备是怎么选举的,往往一知半解。本文作为 Ke…

作者头像 李华