news 2026/10/2 6:07:21

AI Agent 30天速成|Day7 笔记:用 Chroma 与 FAISS 给 RAG 搭一套可复现的向量检索

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent 30天速成|Day7 笔记:用 Chroma 与 FAISS 给 RAG 搭一套可复现的向量检索

1. Day7 的坑:FAISS 重启后向量全丢,RAG 检索层得换思路

AI Agent 30天速成走到第 7 天,前面几天我们一直在用 FAISS 做向量检索。FAISS 是什么?一句话说清:它是 Meta 开源的高性能向量相似度搜索库,能做什么?在百万级向量里毫秒级找最近邻。适合谁?适合做本地 RAG 检索层、推荐召回、去重匹配的开发者。但 Day3 那套 FAISS 内存索引有个致命问题——程序一重启,向量全没了,而且它没有元数据过滤、没有内置去重,检索出来的片段经常高度重复。

今天要解决的就是这个:给 RAG 搭一套可复现的向量检索层,用 Chroma 做持久化本地库,用 FAISS 做对照,配合 SigLIP 生成图文统一嵌入,最后对比两种索引在召回质量和速度上的差异。整个链路里,嵌入模型怎么调、Key 怎么管,我用 TaoToken 统一走一个 API 通道,省得每个模型单独配一套环境变量。

先说清楚今天要交付什么:一份可复制的依赖清单、一个 Chroma 持久化建库脚本、一个 FAISS 对照脚本、一组检索验证命令,以及一组测试问题来验证召回是否稳定。你跟着敲完,本地会多出一个./chroma_mm_db目录,重启 Python 进程后向量还在,这是 FAISS 内存索引做不到的。

为什么第 7 天才换 Chroma?因为前 6 天我们在打基础:Day1 环境、Day2 文本嵌入、Day3 FAISS 内存检索、Day4 分块、Day5 工具网关、Day6 ReAct 循环。到了今天,检索层要能落地、能过滤、能去重,才撑得起后面多模态 Agent 的完整链路。Chroma 就是为 LLM RAG 设计的,开箱即用,不用单独部署服务。

我试过把 FAISS 和 Chroma 放在同一个测试集上跑,最直观的差别不是速度,而是「重启后还能不能查」。FAISS 每次启动都要重新add一遍向量,Chroma 直接get_or_create_collection就能接着用。这个差别在学习和调试阶段特别省时间。

2. TaoToken 前置:统一 Key 与 API 通道,嵌入模型不再各配一套

在动手写建库脚本之前,先把调用通道理顺。RAG 检索层要调嵌入模型,多模态还要调 SigLIP 这类图文编码模型,如果每个模型都去单独申请 Key、单独配 Base URL,环境变量会乱成一团。TaoToken 在这里的作用是:提供一个统一的 API 通道,把 Key 和 Base URL 收敛成一套配置,嵌入模型、对话模型都从这里走。

官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数,配置的时候别把查询串带进去。

你需要先拿到 Key。进入控制台创建 API Key,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,创建完复制那串sk-开头的字符串,后面所有脚本都读同一个环境变量。如果你还没决定用哪个模型,可以先去模型对话页面试一下嵌入模型和对话模型的返回,地址是 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite ,确认通道通了再写代码。

这里有个关键点:Chroma 本身不绑定嵌入模型,它允许你传入自定义的 embedding function。所以我们的策略是——文本嵌入走 TaoToken 的 API,SigLIP 图文嵌入走本地模型(因为 SigLIP 权重可以离线缓存,不依赖网络)。两条路并行,互不干扰。

配置环境变量,Linux/macOS 用:

export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"

Windows PowerShell 用:

$env:TAOTOKEN_API_KEY="sk-你的Key" $env:TAOTOKEN_BASE_URL="https://taotoken.net/api"

如果你用 Claude Code 或者 Cline 这类工具做辅助编码,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有 Base URL、Key、Model ID 三件套的填法。Coding Plan 适合长期写 Agent 代码的场景,入口是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,如果你打算把 30 天速成坚持到底,可以了解一下。

注意:Key 只放在环境变量里,不要硬编码进脚本,更不要提交到 Git。后面所有代码都通过os.environ读取。

3. 可复制配置:依赖清单 + Chroma 持久化建库脚本

先把依赖装齐。新建一个requirements-day7.txt:

chromadb==0.4.24 faiss-cpu==1.7.4 torch==2.2.0 transformers==4.38.0 pillow==10.2.0 numpy==1.26.4 openai==1.14.0 scikit-learn==1.4.0

安装命令:

pip install -r requirements-day7.txt

openai这个包是用来走 TaoToken 的 OpenAI 兼容接口调嵌入模型的,faiss-cpu是 CPU 版,够学习用。装完先验证一下 Chroma 能不能 import:

python -c "import chromadb; print(chromadb.__version__)"

接下来是核心脚本chroma_store.py。它做三件事:用 TaoToken 的 API 生成文本嵌入、用 Chroma 持久化到本地磁盘、支持元数据过滤和 MMR 去重检索。

import os import chromadb from openai import OpenAI from typing import List, Dict # 走 TaoToken 统一通道 client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], ) EMBED_MODEL = "text-embedding-3-small" def text_to_vector(text: str) -> List[float]: resp = client.embeddings.create(model=EMBED_MODEL, input=text) return resp.data[0].embedding class ChromaStore: def __init__(self, persist_path="./chroma_mm_db", coll_name="mm_kb"): # 本地持久化客户端,重启不丢 self.client = chromadb.PersistentClient(path=persist_path) self.collection = self.client.get_or_create_collection( name=coll_name, metadata={"hnsw:space": "cosine"}, ) def add_text(self, text_list: List[str], source: str = "文档"): ids = [f"txt_{source}_{i}" for i in range(len(text_list))] vecs = [text_to_vector(t) for t in text_list] metas = [{"type": "text", "source": source} for _ in text_list] self.collection.add( embeddings=vecs, documents=text_list, metadatas=metas, ids=ids, ) def search(self, query: str, search_type: str = "all", top_k: int = 3, use_mmr: bool = False) -> List[Dict]: query_vec = text_to_vector(query) where_filter = {} if search_type == "text": where_filter = {"type": "text"} elif search_type == "image": where_filter = {"type": "image"} if use_mmr: res = self.collection.max_marginal_relevance_search( query_embeddings=[query_vec], n_results=top_k, where=where_filter or None, ) docs = res[0] if isinstance(res, list) else res["documents"][0] metas = res[1] if isinstance(res, list) else res["metadatas"][0] return [{"content": d, "meta": m} for d, m in zip(docs, metas)] res = self.collection.query( query_embeddings=[query_vec], n_results=top_k, where=where_filter or None, ) output = [] for doc, meta, dist in zip( res["documents"][0], res["metadatas"][0], res["distances"][0] ): output.append({ "content": doc, "meta": meta, "distance": round(float(dist), 4), }) return output

注意where_filter or None这个写法:Chroma 在where={}空字典时会报错,传None才是「不过滤」。这是踩过的坑之一,后面排障章节会再提。

如果你用 Cline 的 MCP 模式或者 Codex 的auth.json来辅助写代码,记得把三件套填全:Base URL 填https://taotoken.net/api,Key 填你的sk-串,Model ID 填你实际用的嵌入或对话模型名。缺一个都会连不上。

4. 验证请求:建库、检索、对比 FAISS 速度

先跑一个最小验证,确认 TaoToken 通道和 Chroma 持久化都正常。新建test_chroma.py:

from chroma_store import ChromaStore store = ChromaStore() store.add_text([ "多模态RAG使用SigLIP实现图文统一向量检索", "Chroma支持本地持久化,重启不丢失向量数据", "ReAct Agent可以自主调用图文检索工具", "FAISS是内存索引,适合高性能最近邻搜索", ], source="Day7学习文档") ret = store.search("什么是多模态RAG", search_type="text", top_k=2) for item in ret: print(item["distance"], item["content"])

运行:

python test_chroma.py

预期输出类似:

0.2134 多模态RAG使用SigLIP实现图文统一向量检索 0.3871 ReAct Agent可以自主调用图文检索工具

距离越小越相关,0.21说明第一条命中很准。然后关键一步——重启验证。再开一个 Python 进程,直接查,不重新 add:

python -c " from chroma_store import ChromaStore s = ChromaStore() print(s.collection.count()) print(s.search('Chroma 持久化', top_k=1)) "

如果count()返回 4,说明向量已经落在./chroma_mm_db目录里,重启不丢。这就是 Chroma 相对 FAISS 内存索引最实在的优势。

接着做 FAISS 对照。新建faiss_compare.py:

import time import faiss import numpy as np from chroma_store import text_to_vector texts = [ "多模态RAG使用SigLIP实现图文统一向量检索", "Chroma支持本地持久化,重启不丢失向量数据", "ReAct Agent可以自主调用图文检索工具", "FAISS是内存索引,适合高性能最近邻搜索", ] * 25 # 扩到100条做速度对比 vecs = np.array([text_to_vector(t) for t in texts], dtype="float32") dim = vecs.shape[1] index = faiss.IndexFlatIP(dim) faiss.normalize_L2(vecs) index.add(vecs) q = np.array([text_to_vector("什么是多模态RAG")], dtype="float32") faiss.normalize_L2(q) t0 = time.time() D, I = index.search(q, 3) t1 = time.time() print(f"FAISS 检索耗时: {(t1-t0)*1000:.2f} ms") print("命中索引:", I[0])

跑下来你会发现,100 条向量时 FAISS 检索在 0.1ms 级别,Chroma 因为要读磁盘、走 HNSW 索引,大概在几毫秒到十几毫秒。但注意——这个对比不公平的地方在于:FAISS 的向量是刚add进去的内存态,Chroma 是持久化后从磁盘加载的。真实场景里,FAISS 每次启动都要重新算一遍嵌入,那部分耗时才是大头。

用一组测试问题验证召回稳定性,我准备了 5 个:

questions = [ "Chroma 怎么持久化", "FAISS 和 Chroma 区别", "SigLIP 是做什么的", "ReAct 怎么调用工具", "多模态检索怎么过滤图片", ] for q in questions: r = store.search(q, top_k=1) print(q, "->", r[0]["content"][:20], r[0]["distance"])

如果每个问题的 top1 距离都稳定在 0.4 以下,说明召回是稳的。如果某个问题距离突然飙到 0.8 以上,多半是分块或嵌入模型的问题,不是 Chroma 的锅。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

这一节按真实报错来对。你在跑上面脚本时,大概率会撞上下面几个。

报错一:401 Unauthorized。完整信息通常是openai.AuthenticationError: Error code: 401 - {'error': {'message': 'Invalid API key'}}。原因就两个:Key 没读到,或者 Key 复制时带了空格。先确认环境变量:

echo $TAOTOKEN_API_KEY

如果输出为空,说明当前 shell 没加载。Windows 下用echo $env:TAOTOKEN_API_KEY。如果输出有值但还报 401,检查是不是把https://taotoken.net/api写成了带 UTM 的完整链接——Base URL 只填到/api,不要带查询串。

报错二:local proxy failed / connection error。完整信息类似openai.APIConnectionError: Connection error.或local proxy failed。这类多半是本地网络环境或代理配置干扰了请求。检查你的HTTP_PROXY/HTTPS_PROXY环境变量,如果设了但指向一个不可用的地址,请求会直接失败。清掉再试:

unset HTTP_PROXY HTTPS_PROXY

报错三:reading choices / KeyError 'choices'。完整信息是KeyError: 'choices'或reading 'choices'。这通常发生在你把对话模型的返回结构套用到嵌入接口上。嵌入接口返回的是resp.data[0].embedding,没有choices字段。检查你的text_to_vector是不是误用了chat.completions.create。嵌入必须用client.embeddings.create。

报错四:OAuth / 认证流程卡住。如果你用 Claude Code 或类似工具接入,报OAuth相关错误,说明工具在走它自己的登录流程,而不是读你的 API Key。这时候要检查工具的配置文件,把认证方式切成 API Key 模式,Base URL 填https://taotoken.net/api,Key 填sk-串,Model ID 填实际模型名。三件套缺一不可。Claude Code 的接入细节在 https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 有说明。

报错五:Chroma where 过滤报错。完整信息类似Expected where to have exactly one operator。原因就是前面说的空字典问题。把where={}改成where=None,或者只在有过滤条件时才传where参数。

报错六:SigLIP 模型下载慢。如果你加了本地 SigLIP 做图文嵌入,AutoProcessor.from_pretrained会去拉权重,网络不好会卡住。解决办法是提前把权重缓存到本地,用cache_dir指定路径,或者先在有网环境下载好再离线加载。学习阶段也可以先用纯文本嵌入跑通链路,图文部分后面再补。

排查顺序建议:先echo环境变量确认 Key 和 Base URL,再单独跑一个最小embeddings.create请求确认通道,最后才跑完整建库脚本。这样能把问题定位在「通道」还是「代码」上。

6. 语义一致 CTA:把检索层接进你的 Agent 链路

检索层跑通之后,下一步就是把它接进 Day6 的 ReAct 工具网关。你可以在网关里注册一个multimodal_search工具,参数包括query、search_type(all/text/image)、top_k、use_mmr,底层直接调ChromaStore.search。这样 Agent 在 Thought 阶段判断需要查知识库时,Action 就调这个工具,Observation 拿到带距离分数的片段,再决定是否继续检索。

如果你在接入过程中卡在 Key 或通道配置上,直接去 API Keys 页面重新生成一个,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite ,配合接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 对照着填。想先验证模型返回是否符合预期,去模型对话页面发一条测试请求最快,地址是 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。如果你打算把 30 天速成里的 Agent 代码长期维护下去,Coding Plan 的入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。

最后留一个可复现的检查点:重启进程后collection.count()不变、5 个测试问题的 top1 距离稳定、FAISS 对照脚本能跑出毫秒级耗时。这三条都过了,Day7 的检索层就算落地了。明天可以把 SigLIP 图文嵌入补上,让图片也能进同一个 Chroma 集合,用type=image元数据过滤,实现以文搜图。

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

PDF转PPT免费工具推荐!在线+离线实用方案整理

日常办公、学生做汇报、职场做述职,经常会遇到一个难题:拿到一份PDF资料,内容完整、排版规整,却没法直接编辑,想要做成演示用的PPT,只能逐页复制粘贴,费时又费力,还容易错乱排版。很…

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

AI搜索重构内容入口:企业GEO落地的技术路径

一、从搜索算法演进看企业线上的四个常见问题当用户提问方式从关键词检索转向自然语言对话,企业线上运营的底层逻辑正在经历一次结构性调整。过去堆砌关键词、铺量发稿的做法,在生成式引擎面前逐渐失效。企业普遍面临几个真实困境:一是内容生…

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

【Python 系统入门付费专栏】第 21 讲 自动化办公:OpenPyXL 与 Python-docx 实战,批量处理 Excel/Word 解放双手

专栏导读:本专栏为 Python 从入门到算法落地系统付费专栏,共 5 大阶段 25 讲。本文为第四阶段第 5 讲,承接上一讲的 Web 开发能力,进入办公自动化实战领域。日常办公中大量重复的 Excel 数据处理、Word 文档生成、报表统计等工作,占据了职场人大量的时间。Python 可以通过…

作者头像 李华
网站建设 2026/10/2 6:03:45

Claude Skills 实战指南:从 SKILL.md 编写到技能体系搭建

1. 从“skills”这个热词说起:它到底是什么,为什么突然火了如果你最近在技术社区、AI 工具群或者开发者论坛里频繁看到“skills”这个词,不用怀疑,它说的不是传统意义上的“技能”泛称,而是特指围绕 Claude 生态、尤其…

作者头像 李华
网站建设 2026/10/2 6:02:36

2026商业航天产业链全景拆解:从卫星制造到可回收火箭的卡位思路

去年做商业航天产业链梳理时,评论区问得最多的一个问题不是“谁最正宗”,而是“产业链到底怎么拆,为什么有的票反复活跃、有的票一买就套”。当时我给的答案比较直接:商业航天这个赛道,表面上炒的是星辰大海&#xff0…

作者头像 李华