首页
/ Langchain-Chatchat 基于 ChromaDB 的知识库向量服务:ChromaKBService 实现解析与接入指南

Langchain-Chatchat 基于 ChromaDB 的知识库向量服务:ChromaKBService 实现解析与接入指南

2026-09-08 13:42:25作者:廉皓灿Ida

导读

本文面向在 Langchain-Chatchat 项目中需要选用 ChromaDB 作为本地知识库向量存储的开发者,系统梳理 ChromaKBService 的完整实现:包括本地持久化路径的组织规则、与 LangChain Chroma 封装与 Embedding 模型的对接方式、文档增删查清(CRUD)与相似度检索的底层调用链,以及如何通过配置和工厂机制把默认向量库切换到 ChromaDB。读完本文,你将掌握 ChromaKBService 每个关键方法的作用与数据流转,能够在自己的知识库工程中直接复用这套基于 ChromaDB 的服务抽象,并具备阅读其余向量库实现(FAISS、Milvus、PG 等)的迁移能力。


1. ChromaKBService 在项目中的定位

在 Langchain-Chatchat 中,知识库功能被抽象为 KBService 基类(见 kb_service/base.py),不同后端通过子类实现统一接口。其中面向 ChromaDB 的实现即为 ChromaKBService,源码位于 chromadb_kb_service.py

该服务类通过 SupportedVSType.CHROMADB(值为字符串 "chromadb")声明自己的后端类型,SupportedVSType 中还定义了 FAISS、MILVUS、ZILLIZ、PG、RELYT、ES、DEFAULT 等其他可选后端。KBServiceFactory.get_service(见 base.py)根据传入的 vector_store_type 动态实例化对应子类——当类型为 chromadb 时即返回 ChromaKBService 实例,因此上层代码(知识库 API、WebUI、初始化迁移脚本)无需关心底层存储细节。

从类结构看,ChromaKBService 声明了以下实例属性:

属性 含义
vs_path 向量库持久化目录路径
kb_path 知识库根目录路径
chroma LangChain 的 langchain_chroma.Chroma 封装对象(负责 embedding 计算与检索 API 封装)
client 原生 chromadb.PersistentClient 客户端实例(负责本地持久化与集合管理)
collection 当前知识库对应的 Chroma 集合(部分方法直接使用 chroma._collection

kb_nameembed_modelkb_info 等则继承自 KBService.__init__base.py),其中 kb_name 为知识库名称,embed_model 为构建该知识库时使用的向量化模型名。


2. 路径规划:知识库根目录、文档目录与向量目录

ChromaKBService 的两个路径方法直接委托给知识库模块的公共工具函数(见 knowledge_base/utils.py):

  • get_kb_path() 返回 KB_ROOT_PATH/<kb_name>,即该知识库的根目录;
  • get_vs_path() 返回 get_vs_path(self.kb_name, self.embed_model),解析后为 KB_ROOT_PATH/<kb_name>/vector_store/<embed_model>

两个关键点值得注意:

  1. 同一个知识库可存在多份向量索引:向量目录中嵌入了 embed_model 名,因此切换/升级 Embedding 模型时,不同模型的向量空间会落在不同子目录,互不覆盖。这一点与 FAISS 实现一致——FaissKBService.get_vs_path 同样以 self.vector_name(默认是替换 :_ 后的模型名)组织子目录,见 faiss_kb_service.py
  2. 知识库内容目录另有分工:原始文档(content)目录由 get_doc_path 返回(<kb_path>/content),Chroma 持久化目录则承载切分后文档的向量数据,二者分离,便于文件管理层面的独立操作。

文中部分描述方法原文以 /path/to/... 示意路径,实际取值取决于运行时配置的 KB_ROOT_PATH(属于 basic_settings),不同部署环境结果不同。


3. 初始化:do_init 与 Chroma 本地持久化

do_init 是知识库服务启动时由 KBService.__init__ 自动调用的钩子(base.py),ChromaKBService 的实现流程如下(chromadb_kb_service.py):

  1. 通过 get_kb_path() / get_vs_path() 计算并缓存 self.kb_pathself.vs_path
  2. chromadb.PersistentClient(path=self.vs_path) 创建本地持久化客户端——ChromaDB 无需独立部署服务端,数据落盘在指定目录中,这是它相比 Milvus/Zilliz/PG 方案部署成本低的核心原因;
  3. 调用 get_or_create_collection(self.kb_name) 按知识库名获取或创建集合;
  4. 调用私有方法 _load_chroma(),用 Chroma(client=self.client, collection_name=self.kb_name, embedding_function=get_Embeddings(self.embed_model)) 构造 LangChain 层封装。其中 get_Embeddings 依据 self.embed_model 返回对应的 Embedding 模型适配器,真正在写入与查询时把文本转成向量。

需要强调的是,初始化期间会校验 kb_nameembed_model 两个属性:前者决定集合名与路径,后者决定向量维度与 embedding 来源。若二者与知识库建库时不一致,会得到空结果或维度不匹配的异常。

3.1 do_create_kb:空实现背后的语义

创建 Chroma 集合的操作实际发生在 do_initget_or_create_collection 中,因此 ChromaKBService.do_create_kb 直接 passchromadb_kb_service.py)。这与基类的调用约定并不冲突:基类 create_kb() 会先在文件系统创建 content 文档目录、把知识库记录写入数据库(add_kb_to_db),再回调子类 do_create_kb()base.py);Chroma 场景下"建库"已被初始化集合的动作覆盖,故无需额外逻辑。对照 FAISS 实现(需要 os.makedirs(vs_path) 并加载向量池)可以看出不同后端的建库动作差异。


4. 文档写入:do_add_doc 的向量化与落库

do_add_doc(docs, **kwargs) 接收 List[Document],返回每个入库文档的 {"id", "metadata"} 列表。其内部实现(chromadb_kb_service.py)共四步:

  1. 通过 get_Embeddings(self.embed_model) 获得 embedding 函数;
  2. docs 中拆出 textspage_content)与 metadatas,并调用 embed_func.embed_documents(texts=texts) 批量生成向量;
  3. uuid.uuid1() 为每篇文档生成基于时间戳的唯一 ID(保证分布式并发下基本不冲突);
  4. 逐个调用 self.chroma._collection.add(ids=..., embeddings=..., metadatas=..., documents=...) 写入集合,并把 idmetadata 收集进 doc_infos 返回。

4.1 上层封装:从文件到向量的完整链路

do_add_doc 只是"向量化 + 写库"的最底层一环。真正从知识库文件入口走起的是基类的 add_doc(kb_file, docs, **kwargs)base.py):

  • 先校验 Embedding 模型可用性(check_embed_model);
  • 若未显式传入 docs,则调用 kb_file.file2text() 完成文档加载与文本切分(此过程还会给每篇文档的 metadata["source"] 打上相对路径的文件名标记);
  • delete_doc(kb_file) 清掉该文件旧版本数据,再调用 do_add_doc 入库,最后通过 add_file_to_db 把文件记录、文档数与 doc_infos 写入数据库元信息表。

由此形成 "KBService.add_doc -> do_add_doc -> Chroma collection.add" 的调用链;知识库 API 层(如 kb_doc_api.py)与初始化脚本只需面对 add_doc 一个入口。


5. 相似度检索:do_search、阈值过滤与辅助转换函数

5.1 do_search 的检索参数

do_search(query, top_k, score_threshold) 是知识库检索的核心方法,签名(chromadb_kb_service.py)三个参数分别为:

  • query:查询文本(字符串);
  • top_k:最多返回的文档条数(整数);
  • score_threshold:相关度阈值(浮点数),默认取 Settings.kb_settings.SCORE_THRESHOLD

KBSettings 中,SCORE_THRESHOLD 默认值为 2.0,注释明确说明:"取值范围在 0-2 之间,SCORE 越小相关度越高,取到 2 相当于不筛选,建议设置在 0.5 左右";同时 VECTOR_SEARCH_TOP_K 默认 3,即默认返回 3 条结果。这些默认值决定了不传参时知识库问答检索的命中数量与严格程度。

5.2 检索实现:LangChain 层封装而非裸 query

需要说明:早期/文档注释中所描绘的实现是用 Embedding 适配器先把 query 编码,再直接调用 collection.query(n_results=top_k) 获得 QueryResult,最后由结果转换函数打包成 (Document, score) 元组列表。当前仓库代码则统一改走 file_rag.utils.get_Retriever("vectorstore") 通道:

retriever = get_Retriever("vectorstore").from_vectorstore(
    self.chroma,
    top_k=top_k,
    score_threshold=score_threshold,
)
docs = retriever.get_relevant_documents(query)
return docs

即先通过 Chroma 封装建立 retriever,将 top_kscore_threshold 注入检索器(BaseRetrieverService 抽象定义见 file_rag/retrievers/base.py,其 from_vectorstore 统一接收 vectorstore/top_k/score_threshold 三个参数),再一次性完成 "query 编码 -> 向量相似度检索 -> 距离阈值过滤 -> 结果裁剪"。score_threshold 的过滤语义沿用 score_threshold_processbase.py)中对 "越小越相关、score <= threshold 保留" 的约定:对 Chroma 返回的 L2 距离类分数而言,阈值越小保留越严格。这也解释了为什么在 Knowledge Base 对话/检索 API 中"相关度阈值调低更精准"。

5.3 结果转换辅助函数

ChromaKBService 模块级还保留了两个纯函数,用于把 Chroma 原生结果结构解包为 LangChain 对象:

_get_result_to_documents(get_result: GetResult) -> List[Document]chromadb_kb_service.py):

  • get_result["documents"] 为空则直接返回 []
  • metadatas 缺失或为空,则以 [{}] * len(documents) 补齐同长度的空元数据,保证"一文一 metadata"一一对应;
  • 遍历 zip(documents, metadatas),以 page_contentmetadata 关键字构造 Document 列表。

典型输出形态:

[
    Document(page_content="文档内容1", metadata={"作者": "张三"}),
    Document(page_content="文档内容2", metadata={"作者": "李四"})
]

该函数当前被 get_doc_by_ids 调用,用于把 Chroma collection.get(ids=...) 的原始 GetResult 还原成业务可读的 Document 列表。

_results_to_docs_and_scores(results) -> List[Tuple[Document, float]]chromadb_kb_service.py):

  • 函数注释标明其逻辑参考自 langchain_community.vectorstores.chroma.Chroma
  • 入参结构包含 documents[0]metadatas[0]distances[0] 三层(批查询结果的第一个 batch),通过 zip 并行解包,构造 (Document, distance) 元组列表,metadata 缺失时兜底为空字典。

典型输出形态:

[
    (Document(page_content="文档内容1", metadata={"作者": "张三"}), 0.95),
    (Document(page_content="文档内容2", metadata={"作者": "李四"}), 0.89)
]

从源码结构看,_results_to_docs_and_scores 当前已不被 do_search 直接调用(检索改走 retriever 通道),但在依赖原始 collection.query 的扩展或调试场景中,它依然是把 QueryResult 快速转成带分文档列表的实用工具。


6. 文档按 ID 查询与删除

知识库中每篇文档都有一个 Chroma 集合内的稳定 ID(入库时由 do_add_doc 生成)。针对 ID 的两个方法构成配套:

get_doc_by_ids(ids: List[str]) -> List[Document]chromadb_kb_service.py):调用 self.chroma._collection.get(ids=ids) 获得 GetResult,再交给 _get_result_to_documents 转换后返回。基类 list_docsbase.py)正是借助 get_doc_by_ids 实现按文件名/元数据批量回捞全文内容的,因此它是"数据库记录 -> 文档实体"的桥接点。

del_doc_by_ids(ids: List[str]) -> boolchromadb_kb_service.py):调用 _collection.delete(ids=ids) 并固定返回 True。注意该方法不校验 ID 是否存在,对不存在的 ID 亦不报错,文档注释提醒调用方不要依赖返回布尔值来判断实际删除数量。此外 Chroma 只按 ID 批量删除,KBService.update_doc_by_idsbase.py)即通过 "先按 ID 删除、再对非空文档重新 do_add_doc" 的方式实现单篇更新。


7. 整库级操作:删库与清空向量空间

do_drop_kbchromadb_kb_service.py):Chroma 场景下"删除知识库"等价于删除对应集合,即 self.client.delete_collection(self.kb_name)。代码捕获 ValueError,且仅在异常信息不是 Collection {kb_name} does not exist. 时才重新抛出——也就是说当集合本就不存在时静默放行,避免误报,这与 Milvus 等后端在 drop 阶段的"存在性容错"思路一致。基类 drop_kb 会随后删除数据库中的知识库元信息记录(base.py),保证文件系统与元数据库同步。

do_clear_vschromadb_kb_service.py):清空向量空间被实现为直接调用 do_drop_kb(),即"删集合"而非逐条删除向量。相比 collection.delete 全量扫描,drop 集合在 Chroma 的持久化语义下是更高效且幂等的做法。需要注意的是该操作不可逆,业务层调用 clear_vs 前应确认确实需要重建(例如 migrate.pyrecreate_vs 模式正是在入库前调用 clear_vs + create_kb,见 knowledge_base/migrate.py)。

do_delete_doc(kb_file: KnowledgeFile, **kwargs)chromadb_kb_service.py):按"文件"粒度删除,条件是 where={"source": kb_file.filepath},即通过 metadata 中的 source 字段精确定位该文件全部切分文档后删除。这里的 source 在被基类 add_doc 处理时会统一改写为相对 content 目录的相对路径(见 base.py),确保入库与按文件删除时匹配的是同一取值。KnowledgeFile 对象封装文件路径与加载器信息(knowledge_base/utils.py),上层 delete_doc 还负责同步删除数据库中的文件记录。


8. 从服务方法到对外 API:一次知识库检索的完整链路

把上文各方法串起来,Langchain-Chatchat 中一次"知识库对话检索"的调用路径大致如下:

  1. WebUI/API 层发起 kb_chat / 知识库检索请求;
  2. KBServiceFactory.get_service_by_name(kb_name) 依据数据库记录还原出正确的 vs_type(如 chromadb)与 embed_model,并构造对应服务实例(构造过程中触发 do_init,连接 Chroma 持久化目录);
  3. 上层调用 KBService.search_docs(query, top_k, score_threshold),其先校验 embedding 模型可用性,再转发到子类 do_searchbase.py);
  4. do_searchget_Retriever("vectorstore") 构建检索器并返回命中文档,作为 LLM 的上下文素材进入对话拼接。

类似的,KBService.add_doc / update_doc / delete_doc / clear_vs / drop_kb 分别被知识库文档管理 API(上传、删除、重建向量库等)与 migrate.py 初始化脚本复用。因此,掌握 ChromaKBService 一个类,即掌握了该后端在"建库 -> 入库 -> 检索 -> 维护"四个阶段的所有行为。


9. 如何让 Langchain-Chatchat 使用 ChromaDB

9.1 后端能力与配置声明

ChromaDB 方案的优势是无需单独起服务、无外部依赖,向量数据以文件形式保存在 vs_path。在配置上,Chroma 无需连接参数,kbs_config"chromadb": {} 即为空配置(settings.py);与 Milvus(需要 host/port)、PG(需要 connection_uri)、ES(需要 host/index)形成鲜明对比。

要使知识库默认走 ChromaDB,需将默认向量库类型切换为 chromadb

  • KBSettings.DEFAULT_VS_TYPE 的取值域为 "faiss" | "milvus" | "zilliz" | "pg" | "es" | "relyt" | "chromadb",默认 "faiss"settings.py)。运行时该配置由 kb_settings 配置文件覆盖;
  • 知识库的 vs_type 一经入库,后续通过 get_service_by_name 恢复服务时会沿用建库时的类型与模型,因此"建库时用什么类型,之后访问就应保持什么类型"。

9.2 命令行/脚本视角的接入点

从代码结构看,接入 ChromaDB 的入口有两处:

  • 初始化迁移folder2db 接受 vs_type 参数(类型约束中显式包含 "chromadb"),对知识库目录中已有文件执行 recreate_vs / update_in_db / increment 三种模式之一的向量化(knowledge_base/migrate.py),并在脚本内通过 KBServiceFactory.get_service(kb_name, vs_type, embed_model) 组装出 Chroma 服务后调用 kb.add_doc 逐文件入库;
  • 业务 API:知识库管理 API 在创建知识库时接受向量库类型参数,类型为 chromadb 时即持久化为 Chroma 后端。

因此,把 DEFAULT_VS_TYPE 指到 chromadb 并重新执行知识库初始化(重建向量空间),即可在 Langchain-Chatchat 中完整切换到本地持久化的 ChromaDB 检索方案。

9.3 仓库内的测试参考

仓库的向量库测试集中在 tests/kb_vector_db 目录(含 test_faiss_kb.pytest_milvus_db.pytest_pg_db.pytest_relyt_db.py),尽管当前仓库未看到 chromadb 专项测试文件,但这些测试共用同一套 KBService 接口约定。若需为 Chroma 后端编写冒烟测试,可以参照上述测试的组织方式,覆盖 create_kb / add_doc / search_docs / get_doc_by_ids / delete_doc / drop_kb 六个核心动作的往返一致性。


10. 使用注意事项与易错点汇总

结合 ChromaKBService 与基类的实现,实际接入时有以下注意事项值得牢记:

  1. 删除语义偏"乐观"del_doc_by_ids 恒返回 True,不会因 ID 不存在而失败;do_drop_kb 对"集合不存在"的 ValueError 静默吞掉。调用方不应以返回值断言数据量变化,必要时自行用 get_doc_by_ids 复查。
  2. 不可逆操作需谨慎do_drop_kbdo_clear_vsdo_delete_doc 均直接作用于 Chroma 数据,删除后无法从向量库恢复;业务层应保留 content 源文件并善用 recreate_vs 重建。
  3. embed_model 一致性vs_path 目录与集合内向量都依赖 embed_model,更换 Embedding 模型后旧向量不可直接复用,必须重建向量空间,否则会出现维度不匹配或空命中。
  4. 阈值方向:Chroma 场景沿用 score_threshold_processscore <= threshold 语义(分数为距离,越小越相似),SCORE_THRESHOLD 默认 2.0 等效于不过滤,需要更严格召回时建议调到 0.5 量级。
  5. 入参格式do_add_doc 依赖 get_Embeddings(embed_model) 与有效 Document_get_result_to_documents_results_to_docs_and_scores 对 Chroma 原生结果的 documents/metadatas/distances 结构有强依赖,手工构造结果时需保证三键齐全。
  6. 并发与 ID:入库 ID 使用 uuid.uuid1()(含时间戳),多进程并发写入时仍需在业务层保证集合访问安全,避免同一 ID 重复。

结语

ChromaKBService 是 Langchain-Chatchat 多向量库抽象体系中最"轻量"的实现之一:它用 PersistentClient 换掉了独立向量数据库的部署负担,用 get_or_create_collection 让"建库即初始化"天然幂等,又通过 get_Retriever 统一到项目共用的阈值过滤检索链路上。把握住它继承自 KBService 的 "do_ 方法 + 业务封装方法"双层设计,再结合 KBServiceFactory 的装配逻辑,你就能在 FAISS、Milvus、PG、ES 等后端之间快速迁移同样的知识库运维经验。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391