用Firestore做向量存储:轻量RAG实现与踩坑指南

发布时间:2026/10/20 17:14:17
用Firestore做向量存储:轻量RAG实现与踩坑指南 最近在折腾 RAG 项目被向量存储这一环折腾得不轻。市面上的向量数据库一大堆但真正塞进现有系统时往往要多养一套基础设施。我这次用了一个很多人想不到的方案直接拿 Firestore 当向量存储用。没错就是那个 NoSQL 文档数据库。LangChain 生态里有一个编号是 vector_stores038 的示例专门讲 FirestoreVectorStore 的用法我照着它跑通了完整的检索增强生成流程顺便把实现思路、选型权衡、踩坑记录都整理出来。如果你正在做 RAG又不想为了向量检索单独引入一套服务这篇文章应该能帮你省不少时间。我默认你已经对 RAG 的基本流程有概念如果没有也没关系下面会先补上必要的基础然后带你从零把示例跑起来。1. RAG 链路里的向量存储先搞清楚它解决什么问题1.1 这一环在 RAG 里到底干活的RAG 的链路基本是这样文档先被加载进来切成一个个小块再做向量化写入某个“向量存储”。等到用户提问时系统把问题也向量化然后去向量存储里找语义上最接近的内容把检索到的片段拼进提示词最后交给大模型生成答案。向量存储就是那个负责“记东西”和“找东西”的部件。没有它RAG 就退化成普通提示词拼接模型只能用自己训练时的记忆回答知识库更新了它也感知不到效果自然飘。为什么不能只用关键词搜索因为用户不会总是用文档里的原词提问。比如文档里写的是“这款设备噪音很大”用户问的反而是“它安静吗”。关键词搜索匹配不到但向量化之后“噪音大”和“是否安静”在语义空间里是很容易拉近的。这就是向量存储的核心价值基于语义相似度进行检索而不是基于字面匹配。这里要特别说清楚一点向量存储不仅要能存向量还要能快速做高维向量的最近邻搜索。传统数据库的 B-tree 索引对高维向量几乎没用一维排序和范围查找在几百维空间里会退化得很严重所以必须靠专门的向量索引结构。Firestore 从某个版本开始原生支持向量字段和 K 近邻索引才让“文档数据库兼任向量库”这件事变得可行。1.2 选存储前要回答的四个问题很多人一上来就选专用向量数据库其实在动手之前应该先问自己四个问题数据量到底多大一万条分片、十万条分片、百万条分片量级不同方案完全不同。一致性要求有多高数据更新之后多久必须能被检索到业务要求强一致还是最终一致运维成本能不能承受单独部署一套向量库意味着备份、监控、升级、权限都要自己管。现有技术栈能不能复用如果业务系统已经用了某个存储是不是可以少接一条数据管道我把这四个问题放在选型之前是因为很多人根本没有算清楚自己的量级就跟着社区推荐上了一个重型引擎。最后发现分片只有几千条还要维护一个独立的集群纯属自己给自己加戏。Firestore 能承担向量存储的角色就是因为它原生支持了向量近邻索引。在集合里放一个数组字段表示向量查询时按相似度排序返回。数据写入和数据查询都是同一个服务不需要额外的索引同步流程这在中轻量场景下非常舒服。2. Firestore 向量存储的定位与选型思考2.1 Firestore 做向量存储的资本Firestore 是文档型数据库数据以集合、文档、字段的形式组织。字段可以是字符串、数字、数组、对象也可以是一个表示向量的浮点数组。从平台支持向量索引之后你可以在一个文档里同时存业务字段和 embedding 字段查询时按向量距离排序返回最接近的文档。这意味着省掉了很多事。不需要单独部署向量引擎不需要做数据同步不需要维护两套数据库的一致性因为数据从头到尾就只有一个来源。对你正在读这篇文章的需求来说最能直观感受到的好处是如果你们团队已经在用 Firestore 存业务数据那么增加一个向量检索能力可能只需要一个新集合和一个索引配置。从能力边界上看它当然没法跟专业向量库比吞吐和召回率但几千到几十万条分片完全没问题。跑一个内部知识库、做一个 RAG demo、给 CMS 增加“更多相似文章”功能都在它的舒适区内。2.2 三种常见向量存储选型对比我把社区里最常见的几个选项拉了一个对比表。注意这个表不是告诉你哪个“最好”而是帮你在不同约束条件下做取舍。方案数据规模需要独立部署一致性适合场景FAISS百万级以上是内存/文件无强一致离线计算、非实时检索专用向量数据库海量、高并发是视产品而定生产级 RAG、大规模召回Firestore中小规模否强一致业务库复用、轻量 RAG如果你的文档分片只有几万条日查询量几千次Firestore 的成本和复杂度都远低于一套专用向量库。数据量小的时候你根本用不上 ANN 索引的各种高级参数最朴素的暴力扫描其实也很快。Firestore 的向量索引在这个量级下的召回质量足够好。2.3 什么时候不建议用 Firestore但我也得把丑话说在前面。如果你的分片超过几十万条或者对召回率有非常严格要求或者需要复杂过滤条件与向量检索在毫秒级同时完成又或者每秒要抗几百次并发查询这时候还是老老实实上专用引擎更稳。Firestore 的向量索引在维度、索引规模和扫描成本上都有它的上限硬撑会出问题账单也会很难看。还有一个容易被忽略的因素如果你已经有一个独立的向量数据库并且运维流程非常成熟那完全没必要因为“Firestore 能向量检索”就把架构改回去。技术选型最忌讳的是为了省事而重复造轮子Firestore 向量存储最大的价值是让原本没有向量能力的业务快速补上这块拼图。3. 核心实现跑通一个 Firestore 向量存储示例3.1 前置准备项目与依赖安装我建议你先准备一个服务账号的 JSON 密钥并且在本地配置好云环境认证。这样在代码里初始化 Firestore 客户端时可以省去很多麻烦。依赖方面我用到的主要是这几个langchain、langchain-google-firestore、google-cloud-firestore以及你选用的 embedding 模型和 LLM 接口。不同版本下类路径会有差别我这里用的是目前比较稳定的写法。pip install langchain langchain-google-firestore google-cloud-firestore初始化存储from google.cloud import firestore from langchain_google_firestore import FirestoreVectorStore db firestore.Client(projectyour-project-id, credentials: path/to/service_account.json) vector_store FirestoreVectorStore( collection_namerag_embeddings, embeddingembedding_function, distance_measurement_typeCOSINE_DISTANCE, )这里有一个很坑的细节langchain_google_firestore和langchain_google_community这两个包里都出现过 Firestore 向量存储相关类。如果你看的是旧文档很可能会 import 到一个已经被废弃的路径。我查当前源码时的做法是直接pip show装完的包再看看包里面的__init__.py导出了哪些类以实际装的版本为准。3.2 数据模型设计别把向量和业务数据混在一起我建议用单独一个集合来存放向量文档不要直接拿业务集合来加向量字段。虽然 Firestore 允许你在业务文档里加字段但一旦 embedding 模型升级或者向量维度变化你会面对一堆历史文档无法查询的尴尬。一个比较清晰的文档结构长这样集合rag_embeddings文档 IDdoc_xxxcontent原始文本块metadata{source, page, doc_id}embedding[-0.001, 0.002, ...]文档 ID 建议用业务 ID 或者内容哈希。这样当你更新某篇文档时可以基于这个 ID 做覆盖写入不会产生大量冗余历史版本。metadata 里保留doc_id和source用来追溯片段来自哪份原始文档能省掉很多排查问题的时间。不要把所有业务字段都塞进向量文档里。向量文档是给检索用的业务数据该留在原集合就在原集合通过 metadata 里的引用关联。这样导出、清理、批量删除时不会误伤业务数据也避免向量文档体积过大进一步拉高读操作成本。3.3 写入向量切分、嵌入、入库一条龙写入流程主要分三步加载文档、切分文本、向量化入库。from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.document_loaders import TextLoader loader TextLoader(docs/sample.txt) docs loader.load() splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, ) chunks splitter.split_documents(docs) vector_store.add_documents(chunks)add_documents会自动调用你在初始化时传入的 embedding 函数把每个 chunk 变成向量再写入 Firestore。看起来很简单但真正影响检索效果的反而是前面的切分参数。我个人的经验是chunk_size 从 500 字开始试chunk_overlap 设在 10% 左右。为什么要设 overlap因为文本切在句子的中间时前一块和后一块会各丢失部分上下文。重叠部分能让语义在相邻块之间平滑过渡。当然如果切得太大检索结果粒度过粗可能把一段几百字里只有一句相关的内容整块塞给模型稀释答案精度如果切得太小单块语义不完整又会让向量表达变弱。所以这个参数需要结合你实际文档和查询样例来回调。3.4 查询与 RAG 组装把上下文从向量库里捞出来写入之后就是查询。这里是完整闭环里最爽的一步因为 Firestore 向量检索的接口和普通向量库差别不大results vector_store.similarity_search_with_score( query设备安静吗, k4, filter{source: sample.txt}, )返回结果是一个个文档和对应分数组成的列表。拿到结果后把它们拼进提示词里交给 LLMfrom langchain.prompts import ChatPromptTemplate prompt ChatPromptTemplate.from_template( 基于以下资料回答问题 {context} 问题{query} 请给出准确、简洁的答案。 ) context_text \n\n.join([doc.page_content for doc, score in results]) messages prompt.format_messages(contextcontext_text, query设备安静吗) # 再把 messages 传给任意实现了 BaseChatModel 的模型对象这里的模型可以是本地部署的服务也可以是任何兼容 LangChain 接口的后端。关键是理解思路向量检索只是手段最终目的是给大模型一个“可参考的知识上下文”。4. 实操过程与关键参数解析4.1 Firestore 向量索引的配置忘记这一步必报错Firestore 要求向量字段必须先创建索引否则运行查询时会直接抛异常。我第一次跑的时候查询等了几秒然后报了一个FAILED_PRECONDITION错误提示找不到匹配的索引。这时候才想起自己只在 Console 里创建了集合没有创建任何索引。创建索引可以在控制台操作也可以用命令行工具。下面这个 JSON 是一个最小化的向量索引配置文件字段路径指向embedding维度填 768 是为了匹配 embedding 模型的输出维度{ index: { fields: [ { fieldPath: embedding, vectorConfig: { dimension: 768, flat: {} } }, { fieldPath: source, order: ASCENDING } ] } }然后用命令行创建gcloud firestore indexes create \ --collection-grouprag_embeddings \ --fileindex_config.json \ --projectyour-project-id关于这个索引有三个容易踩的坑索引的维度必须和 embedding 函数输出维度一致。如果你换了模型没换索引查询会直接报维度不匹配。filter 条件里用到的字段也要出现在索引定义里。上面示例中我加了source字段是因为我在查询时想按来源过滤。如果查询过滤字段不在索引里要么报错要么查询会特别慢。索引创建不是瞬时的。执行完命令后需要等几分钟状态从CREATING变成ENABLED。别像我一样反复重试同一个查询浪费时间。4.2 距离度量选型余弦、点积还是欧氏Firestore 支持多种距离度量包括欧氏距离、余弦距离、点积等。我一般默认用余弦距离因为它对向量的模长不敏感适合绝大多数文本 embedding。一个重要原因是很多 embedding 模型生成向量时并不做归一化向量长度可能跟文本长度和风格有隐含关系用点积就容易被模长干扰余弦距离则只看方向。不过如果你用 embedding 之前已经明确做了归一化那么点积和余弦在排序效果上是等价的这时点积计算更快。欧氏距离在部分场景更直观但文本语义相似度上不如余弦常用。我的建议是没有特殊理由就用COSINE_DISTANCE。这里还要注意初始化FirestoreVectorStore时写的distance_measurement_type要和底层find_nearest查询指定的类型一致否则分数含义会对不上。因为 LangChain 的相似度搜索会把底层返回的 distance 直接透传出来你看到 score 是 0.3 还是 -0.4完全没有可比性。我踩过一次这个坑后来统一成同一个枚举值才不再纠结。4.3 top_k 和相关性阈值怎么调top_k 控制取回片段数量但不是越大越好。给 LLM 塞太多片段会让答案变得又臭又乱还可能超上下文窗口。我的做法是先取 k4 看召回效果如果答案有遗漏再逐步加大到 6 或 8。如果你的业务像客服问答那样答案集中在少量片段上k 取 3 就够。相关性阈值方面不同库和不同距离度量下分数含义差异很大。Firestore 的向量距离如果设置成余弦距离它返回的是“距离”而不是“相似度”。也就是说数值越小文档与查询越接近。但 LangChain 有时会把 score 做一层包装有的版本直接透传 distance有的则反转成相似度。所以不要凭经验硬套一个“score 小于 0.3 才返回”之类的规则先打印几轮结果看看分布再定阈值最靠谱。4.4 成本与性能别让向量查询烧掉配额Firestore 是按文档读写次数计费的向量检索每一次查询至少会计费一次读操作。而且find_nearest的扫描范围和扫描量会直接影响实际消耗如果你不小心让查询跨了大量文档即便最终只返回 3 条账单上也可能很吓人。几个亲测有效的省钱技巧对高频问题做结果缓存缓存个 5 到 10 分钟完全足够绝大多数内部场景。查询时不要select *只取需要的字段。向量字段本身很大如果连续查询且每次都拉出来网络流量和解析开销都会上去。能用 collection group 索引就不要写全库遍历逻辑能把过滤范围压多小就压多小。如果是纯只读数据可以定期把向量拉到本地内存做一次近似计算这样线上请求根本不碰 Firestore成本几乎归零。性能方面如果查询变慢第一件事是确认索引状态第二件事是看数据分布是否均匀。如果某个集合下有几十万条文档且你的 filter 字段区分度不够高扫描范围会比想象中大得多查询自然就慢。5. 常见问题与排查技巧实录5.1 提示“The query requires an index”但控制台确实看不到这种情况多半是索引还没建完或者你配置的索引字段和查询条件不一致。我建议执行下面的命令去查实际状态gcloud firestore indexes list --projectyour-project-id看返回结果里的STATE字段只有ENABLED状态才能服务查询。索引从CREATING到ENABLED通常需要几分钟。与其反复试查询不如等状态稳定了再继续。如果你看到“requires an index”而控制台列表里根本没有大概率是你在查询里用了索引之外的过滤字段赶紧回去检查filter。5.2 向量检索返回结果明明存在但相关度很差先从查询条件、距离度量、文本切分三个方向排查。通用 embedding 对技术文档效果好对口语化问题可能一般文本切分如果太粗可能在匹配时把不相关内容混进来。我建议你建一个小规模的测试集每次改配置以后人工检查 top-5 的质量。别做大而全的代码评审直接看实际检索结果比什么都直观。5.3 数据结构不一致导致批量写入失败有人会先手动在 Console 建一批文档再用代码往里面补 embedding结果有的文档有字段有的没有查询时各种诡异。解决思路很简单统一用add_documents这样的接口写入让 embedding 字段由代码自动生成。如果你已经有手工造的数据残渣批量清理一下孤儿文档。还要注意字段类型必须一致embedding 字段必须是浮点数数组不能有的文档里存成字符串或整数列表。5.4 总是返回空结果先检查 filter 条件。如果 filter 字段没在索引里定义查询可能不报错但返回空。或者你的集合名写错文档存在但集合名多了个空格也会踩空。排错时先去掉 filter看看能不能返回结果。如果去掉后可以问题就在 filter如果去掉后仍空再查集合名和项目名。5.5 成本账单突然异常排查代码里是不是有循环调用相似度搜索。很多人会在构建上下文时对 prompt 里的每个问题分别跑一次向量检索看起来逻辑没问题但写成了嵌套循环调用量直接翻倍。我建议加一个日志点每次相似度搜索都打印调用时间、调用者、返回条数。这样账单异常时能快速定位到是哪个接口在烧钱。5.6 换了 embedding 模型后旧数据查询报维度错误非常常见。旧向量维度是 768新模型输出是 1024而索引还是按旧维度建的。要么重新跑一遍全量数据更新要么新建一个集合存新向量查询只用新集合。不要试图在同一索引下混存两种维度Firestore 不会容忍这种情况。6. 一点经验体会Firestore 向量存储的正确用法最后说点个人体会。Firestore 向量存储最大的价值不是性能多优秀而是帮你少维护一套系统。它特别适合“顺手用”的场景你已经在某个项目里使用了云端的 NoSQL 数据库现在业务需要增加语义检索直接加一个集合、建一个索引就能跑起来。很多内部工具、小规模知识库、个人项目真的不需要为了几万条数据去单独部署一个向量搜索引擎。我在实际测试中的一个经验是把向量结果做成内存缓存。每十分钟把 Firestore 里的向量读一次放在本地内存做一个简单的相似度排序。查询请求进来先查内存没有命中再回 Firestore 确认。这样既控制了账单又能享受向量检索带来的语义能力。等到哪一天数据量真的大到当前方案扛不住了再平滑迁移到专用向量库也不迟。如果你现在正卡在 RAG 的向量存储选型上我建议先冷静估算一下数据规模和查询频率。多数情况下Firestore 可能是比你想象中更合适的轻量答案。真到了需要重型引擎的时候你自然就会知道为什么需要。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询