资讯详情

从零搭建向量检索:ChromaDB 与 LangChain 实战入门

📅 2026/10/14 15:11:01 | 华诺云谱 👁 阅读
从零搭建向量检索:ChromaDB 与 LangChain 实战入门
向量检索这个词这两年出现的频率越来越高但很多人第一次接触时都会卡在同一个地方Embedding 模型跑通了向量也生成了然后呢存哪里、怎么查、查出来的东西怎么用我当初学这块的时候光是在向量数据库这个环节就来回折腾了好几天。ChromaDB 是我最后选定并一直用到现在的方案配合 LangChain 做检索链路代码量少、心智负担低特别适合刚入门向量检索的人。这篇内容就把我从零搭起这套组合的完整过程拆开讲包括为什么选它、每一步在干什么、参数怎么调、以及我踩过的那些坑。不管你是刚听说向量检索这个概念还是已经用过其他向量库想换个轻量方案应该都能从里面找到对你有用的部分。1. 为什么向量检索需要 ChromaDB 和 LangChain 配合1.1 向量检索到底在解决什么问题传统的关键词搜索本质上是字符串匹配。你搜苹果手机电池续航它就去文档里找包含这些字眼的段落。问题是如果文档里写的是iPhone 电池能用多久字面上一个词都对不上传统搜索就废了。向量检索的思路完全不同它把文本转成一串数字也就是向量语义相近的文本在向量空间里的距离就近。这样你搜苹果手机电池续航系统能匹配到iPhone 电池能用多久因为这两句话的向量距离很近。这个能力在 RAG检索增强生成场景里特别关键。大语言模型本身不知道你私有的文档内容你得先把相关文档片段找出来塞进模型的上下文里它才能基于这些内容回答问题。而找出来这一步靠的就是向量检索。所以你可以理解为向量检索是让模型开卷考试的那个翻书动作。1.2 ChromaDB 在整条链路里扮演什么角色向量生成出来之后你得有个地方存还得能快速做相似度查询。这就是向量数据库的活。ChromaDB 是一个轻量级的向量数据库它的定位很像 SQLite 在关系型数据库里的位置——不需要单独部署服务pip 装完就能用数据可以存内存也可以落盘。我选它主要看中三点。第一是零运维不用像 Milvus 那样起一堆容器本地开发直接跑。第二是 API 设计得足够简单增删改查就那几个方法学习成本极低。第三是它和 LangChain 的集成非常顺滑LangChain 官方就提供了Chroma这个 VectorStore 封装几行代码就能接上。当然它也有局限比如超大规模数据千万级以上向量的性能不如专业分布式方案但对绝大多数个人项目、中小型应用来说完全够用。1.3 LangChain 在这里不是可有可无的有人会问我直接用 ChromaDB 的 Python SDK 不就行了为什么要套一层 LangChain这个问题我当初也纠结过。直接用的确可以但 LangChain 帮你省掉了很多胶水代码。比如文档加载、文本切分、Embedding 调用、向量入库、检索后处理这一整条链路 LangChain 都有现成的抽象。你换 Embedding 模型、换向量库、换检索策略改动量都很小。更实际的一点是LangChain 的VectorStore接口是统一的。今天你用 ChromaDB明天想换成 FAISS 或者别的只要改一行初始化代码上层检索逻辑基本不用动。这种可替换性在项目早期特别有价值因为你还不确定哪个方案最合适能低成本切换就是最大的优势。2. 环境搭建与依赖安装的实操细节2.1 依赖清单和版本选择的坑装依赖这一步看似简单但版本冲突是新手最容易翻车的地方。我建议用虚拟环境隔离别直接往全局 Python 里装。核心依赖其实就两个pip install chromadb langchain langchain-community但这里有个隐藏的坑LangChain 的包结构这两年拆得很细。早期langchain一个包什么都包含现在很多功能被拆到了langchain-community、langchain-core、langchain-openai等子包里。如果你只装langchain导入Chroma的时候可能会报ImportError。所以langchain-community一定要装上。另外 Embedding 模型这块如果你用 OpenAI 的接口还需要装langchain-openai如果用本地的开源模型那要装sentence-transformers。我下面演示会同时给出两种方案。# 用 OpenAI Embedding 的话 pip install langchain-openai # 用本地开源 Embedding 的话 pip install sentence-transformers提示ChromaDB 在 0.4.x 版本之后 API 有过一次比较大的调整网上很多老教程用的是 0.3.x 的写法直接照抄会报错。装的时候留意一下版本本文的写法基于较新的版本。2.2 验证安装是否成功装完之后别急着写业务代码先跑个最小验证确认各个包能正常导入、版本之间不打架import chromadb import langchain from langchain_community.vectorstores import Chroma print(chromadb version:, chromadb.__version__) print(langchain version:, langchain.__version__)如果这几行能顺利打印出版本号说明基础环境没问题。我见过不少人跳过这一步结果写到一半才发现是环境问题排查起来反而更费时间。先花三十秒验证能省掉后面半小时的困惑。2.3 关于 Embedding 模型的选择Embedding 模型决定了向量质量也就直接决定了检索效果。这里有两个方向调用云端 API比如 OpenAI 的text-embedding-3-small效果好、免维护但需要网络请求有成本数据也会离开本地。本地开源模型比如BAAI/bge-small-zh-v1.5中文效果不错完全本地运行免费首次下载模型后就不再依赖网络。我的建议是学习和中小项目优先用本地模型一来没有成本顾虑可以随便试二来数据不出本地更安心。等效果调优到瓶颈了再考虑换更强的云端模型对比。下面两种我都会给代码。3. 从零构建第一个向量检索 Demo3.1 准备 Embedding 和测试文本先定义好 Embedding 函数这是整条链路的起点。用本地模型的话from langchain_community.embeddings import HuggingFaceEmbeddings embedding HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, model_kwargs{device: cpu}, encode_kwargs{normalize_embeddings: True} )这里normalize_embeddingsTrue是个关键参数。它会把向量归一化到单位长度这样计算余弦相似度时就不用再做额外处理检索结果更稳定。很多人忽略这个参数导致相似度分数看起来怪怪的。如果用 OpenAI 的话更简单from langchain_openai import OpenAIEmbeddings embedding OpenAIEmbeddings(modeltext-embedding-3-small)然后准备几条测试文本模拟你要检索的知识库内容texts [ LangChain 是一个用于构建大模型应用的开发框架, ChromaDB 是一个轻量级的向量数据库支持本地持久化, 向量检索通过计算向量距离来找到语义相近的文本, RAG 是检索增强生成的缩写用于给大模型补充外部知识, Embedding 模型负责把文本转换成高维向量表示 ]3.2 把文本灌进 ChromaDB有了 Embedding 和文本入库就一行核心代码from langchain_community.vectorstores import Chroma vectorstore Chroma.from_texts( textstexts, embeddingembedding, persist_directory./chroma_db )from_texts这个方法内部做了三件事对每条文本调用 Embedding 生成向量、把向量和原文一起存进 ChromaDB、返回一个封装好的 VectorStore 对象。persist_directory指定落盘目录数据会持久化到本地下次启动直接加载就行不用重新生成向量。如果你已经有带 metadata 的文档可以用from_documents每条文档可以附带来源、页码等信息检索时能一起返回这在做引用溯源时很有用。3.3 执行第一次相似度查询入库之后查询同样简单query 怎么把文本变成向量 results vectorstore.similarity_search(query, k2) for i, doc in enumerate(results): print(f结果 {i1}: {doc.page_content})k2表示返回最相近的两条。跑一下你会发现虽然查询里没有Embedding这个词但返回的结果里包含了讲 Embedding 的那条这就是语义检索的威力。如果你想看相似度分数用similarity_search_with_scoreresults vectorstore.similarity_search_with_score(query, k3) for doc, score in results: print(f分数: {score:.4f} | 内容: {doc.page_content})注意不同向量库、不同距离度量方式下分数的含义不一样。ChromaDB 默认用的是平方欧氏距离分数越小越相似。别看到分数是 0.8 就以为是相似度 80%那是两码事。4. 检索质量调优那些文档不会告诉你的细节4.1 文本切分策略直接决定检索上限前面 Demo 里我直接拿短句入库但真实场景里你的文档可能是几万字的 PDF 或网页。这时候必须先切分而切分策略是影响检索质量最大的因素没有之一。切得太碎一个完整语义被拆散检索出来的片段缺上下文切得太粗一个片段里混了好几个主题向量表示被平均掉检索精度下降。我的经验是中文文本按 300 到 500 字一段比较合适同时设置一定的重叠overlap避免关键信息正好卡在切分边界上。from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap80, separators[\n\n, \n, 。, , , , ] ) chunks splitter.split_text(long_text)RecursiveCharacterTextSplitter的聪明之处在于它会按分隔符优先级递归切分优先在段落边界切实在不行才在句子边界切最后才硬切。中文场景下把句号、问号、感叹号加进分隔符列表很重要否则它可能按空格切而中文里空格很少效果会很差。4.2 chunk_overlap 到底设多少重叠长度这个参数很多人随便填。我的实测经验是overlap 设成 chunk_size 的 15% 到 20% 比较稳妥。太小了起不到衔接作用太大了会导致大量重复内容入库既浪费存储又可能让检索结果里出现一堆相似片段。举个例子chunk_size 是 400那 overlap 设 60 到 80 就合适。这个比例不是绝对的如果你的文档句子特别长可以适当调大如果是问答对那种短文本overlap 甚至可以设 0。4.3 metadata 过滤让检索更精准光靠向量相似度有时候不够。比如你有一个包含多个产品文档的库用户问的是 A 产品的问题但 B 产品有段文字语义很像就可能被误召回。这时候 metadata 过滤就派上用场了。入库时给每条文档打上标签from langchain_core.documents import Document docs [ Document(page_content..., metadata{product: A, source: manual.pdf}), Document(page_content..., metadata{product: B, source: faq.pdf}), ] vectorstore Chroma.from_documents(docs, embedding, persist_directory./chroma_db)检索时加过滤条件results vectorstore.similarity_search( query, k3, filter{product: A} )这样只在 A 产品的文档里做相似度检索精度会明显提升。这个技巧在多租户、多分类的知识库场景里几乎是必备的。4.4 相似度阈值过滤掉不相关结果有时候库里根本没有相关内容但向量检索还是会硬返回 k 条最相似的哪怕它们其实都不相关。这时候可以设一个分数阈值低于阈值的直接丢掉results vectorstore.similarity_search_with_score(query, k5) filtered [(doc, score) for doc, score in results if score 1.0]阈值设多少需要根据你的数据和 Embedding 模型实测。我的做法是拿一批已知相关和已知不相关的查询跑一遍观察分数分布找一个能分开两类的临界值。这个值不是固定的换模型、换数据都要重新校准。5. 把检索接进完整问答链路5.1 检索器Retriever的封装LangChain 里有个 Retriever 的概念它把 VectorStore 包装成一个统一的根据查询返回文档的接口。好处是后面接链Chain的时候检索这一步可以像积木一样替换。retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 3} )search_type除了similarity还有mmr最大边际相关性。MMR 会在相关性和多样性之间做平衡避免返回一堆内容几乎一样的片段。当你的文档里有大量重复表述时MMR 效果明显更好retriever vectorstore.as_retriever( search_typemmr, search_kwargs{k: 3, fetch_k: 10, lambda_mult: 0.5} )fetch_k是先取 10 条候选再从中挑 3 条最多样化的。lambda_mult控制相关性和多样性的权重0 到 1 之间越小越偏向多样性。5.2 拼一个最简 RAG 问答把检索和大模型接起来就是一个最简的 RAGfrom langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser llm ChatOpenAI(modelgpt-4o-mini) prompt ChatPromptTemplate.from_template( 根据以下资料回答问题如果资料中没有相关信息就说不知道。\n\n资料{context}\n\n问题{question} ) def format_docs(docs): return \n\n.join(doc.page_content for doc in docs) chain ( {context: retriever | format_docs, question: RunnablePassthrough()} | prompt | llm | StrOutputParser() ) answer chain.invoke(ChromaDB 是什么) print(answer)这段代码用 LCELLangChain 表达式语言把各环节串成管道。retriever | format_docs表示先检索再把文档拼成字符串RunnablePassthrough把原始问题透传下去。整个链路清晰、可读改哪一环都很直观。5.3 检索结果为空时的兜底实际用起来你会发现用户问的问题经常超出知识库范围。这时候如果硬让模型基于空上下文回答它要么胡编要么答非所问。加个兜底判断很有必要def retrieve_with_fallback(query): docs retriever.invoke(query) if not docs: return 抱歉知识库中没有找到相关内容。 return docs更稳妥的做法是结合前面说的分数阈值把低分结果也过滤掉只有真正相关的才喂给模型。这一步能显著降低幻觉率是我在实际项目里必加的环节。6. 持久化、增量更新与常见报错排查6.1 数据持久化与重新加载ChromaDB 的持久化很省心指定了persist_directory之后数据自动落盘。下次要用直接加载vectorstore Chroma( persist_directory./chroma_db, embedding_functionembedding )注意加载时一定要传入和入库时相同的 Embedding 函数。如果换了模型向量空间就对不上了检索结果会完全乱套。这是很多人换模型后检索突然不准了的根本原因。6.2 增量添加文档知识库不是一次建完就完事后续要不断加新内容。ChromaDB 支持增量添加vectorstore.add_texts( texts[新增的文档内容一, 新增的文档内容二], metadatas[{source: new1}, {source: new2}] )添加完记得确认持久化。较新版本的 ChromaDB 会自动持久化但如果你用的是老版本可能需要手动调用vectorstore.persist()。这个差异坑过不少人明明加了数据重启后却没了多半就是这里的问题。6.3 几个高频报错的定位思路我把踩过的几个典型问题整理成表方便对照排查报错现象可能原因解决方向ImportError: cannot import name Chroma没装 langchain-community补装对应子包检索结果完全不相关Embedding 模型和入库时不一致统一 Embedding 函数重启后数据丢失未指定 persist_directory 或未持久化检查目录配置和版本行为中文检索效果差用了英文 Embedding 模型换中文优化模型如 bge 系列内存占用飙升一次性加载过多数据到内存分批入库用持久化模式排查这类问题的通用思路是先确认环境包版本、导入是否正常再确认数据向量是否真的入库了、数量对不对最后确认逻辑Embedding 是否一致、查询参数是否合理。按这个顺序走基本都能定位到。6.4 关于性能的一点实测体会数据量在几万条以内ChromaDB 的查询响应基本在毫秒级完全无感。到了几十万条如果还用默认的暴力检索延迟会明显上升。这时候可以考虑建索引ChromaDB 支持 HNSW 索引能大幅加速近似最近邻查询。不过对大多数学习和小型项目来说这个阶段还远没到先把链路跑通、把检索质量调好性能优化是后面的事。我在实际使用中最大的体会是向量检索的效果七分靠数据切分和 Embedding 模型三分才靠向量库本身。很多人一上来就纠结选哪个向量数据库其实方向反了。把文本切分策略打磨好、选一个适合中文的 Embedding 模型比换任何向量库带来的提升都大。ChromaDB 加 LangChain 这套组合最大的价值就是让你能用最少的代码量把注意力集中在这些真正影响效果的地方而不是耗在环境搭建和接口适配上。等你把这套跑顺了再去尝试更复杂的方案心里就有底了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑