尧图精选

Langchain-Chatchat 的 Elasticsearch 向量知识库服务:ESKBService 源码解析与配置实战

🕒 发布时间:2026/9/10 13:53:03 📁 来源:尧图网络
Langchain-Chatchat 的 Elasticsearch 向量知识库服务ESKBService 源码解析与配置实战【免费下载链接】Langchain-ChatchatLangchain-Chatchat原Langchain-ChatGLM基于 Langchain 与 ChatGLM, Qwen 与 Llama 等语言模型的 RAG 与 Agent 应用 | Langchain-Chatchat (formerly langchain-ChatGLM), local knowledge based LLM (like ChatGLM, Qwen and Llama) RAG and Agent app with langchain项目地址: https://gitcode.com/GitHub_Trending/la/Langchain-Chatchat导读本文以 Langchain-Chatchat 仓库中 Elasticsearch 向量知识库服务的核心实现为线索系统讲解ESKBService类的初始化、索引构建、文档增删与相似度检索全链路。你将掌握如何在kbs_config中配置 ES 连接、理解ESKBService各方法与其父类KBService的调用关系并能在自己的 RAG 应用中直接用 Elasticsearch 作为规模化向量存储。一、ESKBService 在知识库体系中的位置在 Langchain-Chatchat 的 RAG 架构中知识库由“文档目录content 向量存储vectorstore 元数据库SQLite/MySQL 等”三部分构成其中向量存储被抽象为统一的KBService基类。查看 KBService 基类定义SupportedVSType枚举了当前支持的全部向量库类型class SupportedVSType: FAISS faiss MILVUS milvus DEFAULT default ZILLIZ zilliz PG pg RELYT relyt ES es CHROMADB chromadb每个具体向量库都实现为KBService的一个子类并覆盖do_init、do_search、do_add_doc、do_delete_doc、do_clear_vs、do_drop_kb、do_create_kb、vs_type这些do_*抽象方法。Elasticsearch 对应的实现就是ESKBService源码位于 es_kb_service.py。选择哪个向量库实例由工厂类KBServiceFactory.get_service统一裁决在 base.py 中可见 ES 的分支注册逻辑当请求的vector_store_type SupportedVSType.ES时惰性导入并返回ESKBService(**params)。因此上层 WebUI、API 与命令行在使用 ES 知识库时并不直接触碰ESKBService而是通过get_service(kb_name, es, embed_model)拿到实例——这也是理解下文“方法级 API”前需要建立的整体认知。从类属性与职责看ESKBService维护了以下核心状态属性含义初始化位置kb_name知识库名称也决定 ES 索引名KBService.__init__kb_path知识库在KB_ROOT_PATH下的本地目录do_initdoc_path知识库文档content目录KBService.__init__index_nameES 索引名称取自kb_path的末级目录名do_initscheme / IP / PORT / user / passwordES 连接参数do_initverify_certs / ca_certs / client_key / client_certHTTPS 双向认证参数do_initdims_length向量维度do_initembeddings_model本地加载的嵌入模型do_inites_client_python原生 elasticsearch-py 客户端Elasticsearchdo_initdbLangChainElasticsearchStore执行索引写入与近似检索do_init二、初始化链路do_init 如何打通 ES 与 LangChaindo_init在实例构造时被自动调用父类KBService.__init__的最后一行是整条调用链的“发动机”。其完整流程对应 es_kb_service.py第一步确定本地路径并推导索引名self.kb_path self.get_kb_path(self.kb_name) self.index_name os.path.split(self.kb_path)[-1]self.kb_path由静态方法get_kb_path基于Settings.basic_settings.KB_ROOT_PATH拼接得到ES 索引名index_name直接取知识库目录的末级名称即默认约定“一个知识库 一个 ES 索引 索引名取知识库名”。需要特别留意的是尽管 settings.py 中es配置项里含有一个index_name: test_index但do_init实际并未读取该键而是用kb_name覆盖。如果希望自定义索引前缀需在构造前调整知识库命名或在子类中改写该赋值逻辑。第二步读取 kbs_config 中的 es 连接配置kb_config Settings.kb_settings.kbs_config[self.vs_type()] self.scheme kb_config.get(scheme, http) self.IP kb_config[host] self.PORT kb_config[port] self.user kb_config.get(user, ) self.password kb_config.get(password, ) self.verify_certs kb_config.get(verify_certs, True) self.ca_certs kb_config.get(ca_certs, None) self.client_key kb_config.get(client_key, None) self.client_cert kb_config.get(client_cert, None) self.dims_length kb_config.get(dims_length, None)其中vs_type()返回字符串es因此它实际读取的是配置字典中kbs_config[es]这一子配置。仓库默认值为见 settings.pyes: { scheme: http, host: 127.0.0.1, port: 9200, index_name: test_index, # 注意实际索引名由知识库名推导 user: , password: , verify_certs: True, ca_certs: None, client_cert: None, client_key: None, },参数语义如下schemehttp或https决定连接协议与后续 TLS 校验分支host、portES 服务地址默认指向本机9200本地 docker 启动 ES 的默认对外端口user、password可选认证。两者同时为空时代码会打出logger.warning(ES未配置用户名和密码)并走无认证连接适合本地开发环境verify_certs、ca_certs、client_key、client_cert仅当scheme https时才生效。verify_certs控制是否校验服务端证书ca_certs提供自定义 CAclient_key/client_cert用于 mTLS 双向认证dims_length向量维度必须在创建索引前与所用 embedding 模型的输出维度一致如text-embedding-3-small为 1536。该值既会被用于写死原生索引 mapping也是 LangChain 侧建索引的依据配置错误会直接导致写入或检索失败。第三步加载嵌入模型self.embeddings_model get_Embeddings(self.embed_model)。embed_model来自父类构造参数缺省时由get_default_embedding()解析模型配置中第一个可用的 embedding 模型。其实现位于 server/utils.py会根据模型配置选择OpenAIEmbeddings、OllamaEmbeddings或LocalAIEmbeddings等封装并注入api_base等参数。第四步创建原生 elasticsearch-py 客户端connection_info dict(hostf{self.scheme}://{self.IP}:{self.PORT}) if self.user ! and self.password ! : connection_info.update(basic_auth(self.user, self.password)) if self.scheme https: connection_info.update(verify_certsself.verify_certs) if self.ca_certs: connection_info.update(ca_certsself.ca_certs) if self.client_key and self.client_cert: connection_info.update(client_keyself.client_key, client_certself.client_cert) self.es_client_python Elasticsearch(**connection_info)这一段只会建立 TCP/HTTP 连接探测并不会真正做业务调用任何ConnectionError会被捕获、记录日志后重新抛出由上层感知“ES 不可用”。注意 ES 客户端通常为惰性连接这里与其说是“连通性验证”不如说是配置对象的构造——真正的失败点通常落在后续首次请求时。第五步用原生客户端预建索引 mappingmappings { properties: { dense_vector: { type: dense_vector, dims: self.dims_length, index: True, } } } self.es_client_python.indices.create(indexself.index_name, mappingsmappings)该步以显式 mapping 声明dense_vector字段的类型、维度和可索引性确保后续向量检索字段类型正确。若索引已存在ES 会抛出BadRequestError索引已存在属正常幂等情况此处被捕获后仅记录logger.error(创建索引失败,重新)不中断流程。第六步构造 LangChain ElasticsearchStoreparams dict( es_urlf{self.scheme}://{self.IP}:{self.PORT}, index_nameself.index_name, query_fieldcontext, vector_query_fielddense_vector, embeddingself.embeddings_model, strategyApproxRetrievalStrategy(), es_params{timeout: 60}, ) if self.user ! and self.password ! : params.update(es_userself.user, es_passwordself.password) if self.scheme https: params[es_params].update(verify_certsself.verify_certs) # ca_certs / client_key / client_cert 同理注入 self.db ElasticsearchStore(**params)这里直接导入了langchain_community.vectorstores.elasticsearch.ElasticsearchStore与ApproxRetrievalStrategy。关键字段约定如下query_fieldcontextES 文档中存放文本正文的字段名vector_query_fielddense_vector存放嵌入向量的字段名strategyApproxRetrievalStrategy()采用 HNSW 近邻的近似检索策略而非暴力全量比对这是大规模知识库能维持低延迟的基础es_params[timeout] 60请求超时上限大索引构建或慢查询时可依实际情况放宽认证与 TLS 参数与原生客户端一致地透传。初始化收尾处代码还会调用self.db._create_index_if_not_exists(index_nameself.index_name, dims_lengthself.dims_length)做一次补建异常仅记日志。ESKBService 不依赖本地vector_store目录落盘——这与 FAISS 等文件型向量库有本质差异ES 的数据与索引全部保存在远端集群中。三、路径工具与知识库生命周期方法3.1 get_kb_path / get_vs_path两者都是静态方法es_kb_service.pystaticmethod def get_kb_path(knowledge_base_name: str): return os.path.join(Settings.basic_settings.KB_ROOT_PATH, knowledge_base_name) staticmethod def get_vs_path(knowledge_base_name: str): return os.path.join(ESKBService.get_kb_path(knowledge_base_name), vector_store)get_kb_path(my_kb)在KB_ROOT_PATH /data/knowledge_bases时返回/data/knowledge_bases/my_kbget_vs_path进一步追加vector_store子目录。需要说明对于 ES 服务该目录当前并不承载向量数据更多是为保持各向量库统一的知识库目录结构而存在。3.2 do_create_kb / vs_type / do_drop_kb当前源码中do_create_kb的实现为空...。这是因为 ES 知识库“创建”的实质动作——os.makedirs(doc_path)与 ES 索引创建——分别由父类的KBService.create_kb()见 base.py负责本地文档目录与数据库记录和do_init完成子类无需再重复建目录。对比早期的 API 参考文档可发现历史上该方法确实负责在kb_path下创建vector_store目录重构后该职责已被移除——这提醒读者查阅该服务时应以当前源码为准。vs_type()返回SupportedVSType.ES即字符串es它同时被配置读取取kbs_config[es]、数据库记录和工厂分发三处使用。do_drop_kb()用于删除知识库本地目录先判断self.kb_path是否存在再shutil.rmtree递归删除整棵目录树见 es_kb_service.py。配合父类drop_kb()会先删目录再从元数据库清除该知识库记录。该操作不可逆且不会删除远端 ES 索引见下文do_clear_vs删除知识库前务必评估数据保留策略。四、文档写入add_doc → do_add_doc 的完整调用链向 ES 知识库灌入文件时调用方统一走父类的KBService.add_doc(kb_file, docs)base.py其内部编排如下校验 embedding 模型可用若未显式传入docs调用kb_file.file2text()完成文件解析与切分得到List[Document]将每个doc.metadata[source]规范化为相对doc_path的相对路径保证 ES 内 source 值一致便于按文件删除先self.delete_doc(kb_file)清掉该文件旧的切片避免重复插入调用本服务实现的self.do_add_doc(docs, **kwargs)真正写入向量库成功后add_file_to_db(...)将文件、切片数量与返回的 doc 索引信息登记进元数据库。ESKBService.do_add_doc的当前实现es_kb_service.py如下def do_add_doc(self, docs: List[Document], **kwargs): self.db.add_documents(documentsdocs) if self.es_client_python.indices.exists(indexself.index_name): file_path docs[0].metadata.get(source) query { query: { term: {metadata.source.keyword: file_path}, term: {_index: self.index_name}, } } search_results self.es_client_python.search(bodyquery, size50) if len(search_results[hits][hits]) 0: raise ValueError(召回元素个数为0) info_docs [ {id: hit[_id], metadata: hit[_source][metadata]} for hit in search_results[hits][hits] ] return info_docs值得向读者澄清两件事早期实现中的_load_es私有方法参考 API 文档中描述的_load_es(docs, embed_model)负责按是否有认证选择ElasticsearchStore并处理ConnectionError在新版本中已内联进do_add_doc即直接调用self.db.add_documents(...)由 LangChain store 内部完成向量化复用self.embeddings_model与批量写入。若你在旧版分支或历史文档中看到_load_es应理解其语义已合并至此处。写入后的“自检”写入完成后代码用 ES term 查询按metadata.source.keyword回捞该文件切片最多取 50 条。若一条都查不到立即抛出ValueError(召回元素个数为0)——这是防止“写入静默失败”的防御性校验。方法返回值为[{id: es文档id, metadata: {...}}, ...]该列表随后被父类写入元数据库成为后续list_docs按 id 取原文的基础。对应的“单测式”最小验证用法源码__main__演示块es_kb_service.pyesKBService ESKBService(test) esKBService.add_doc(KnowledgeFile(filenameREADME.md, knowledge_base_nametest)) print(esKBService.search_docs(如何启动api服务))五、相似度检索do_search 与参数语义do_search(query, top_k, score_threshold)的当前实现es_kb_service.py不再直接调用similarity_search_with_score而是先经过一层 Retriever 服务封装def do_search(self, query: str, top_k: int, score_threshold: float): retriever get_Retriever(vectorstore).from_vectorstore( self.db, top_ktop_k, score_thresholdscore_threshold, ) docs retriever.get_relevant_documents(query) return docs其底层机理可沿两处源码继续追溯get_Retriever(vectorstore)返回的是VectorstoreRetrieverService注册关系见 file_rag/utils.pyfrom_vectorstore在 retrievers/vectorstore.py 中被实现为retriever vectorstore.as_retriever( search_typesimilarity_score_threshold, search_kwargs{score_threshold: score_threshold, k: top_k}, ) return VectorstoreRetrieverService(retrieverretriever, top_ktop_k)由此可以明确三个参数的精确语义query检索语句将先被self.embeddings_model编码成查询向量再在dense_vector字段上做 HNSW 近似近邻搜索top_k期望返回的候选文档条数。search_typesimilarity_score_threshold下它作为k注入最终get_relevant_documents返回结果还会再截断到top_kscore_threshold相似度分数阈值。在similarity_score_threshold检索类型中低于该分数的结果会被过滤。调用父类search_docsbase.py时若不显式传值会取Settings.kb_settings.VECTOR_SEARCH_TOP_K与Settings.kb_settings.SCORE_THRESHOLD作为默认值。另见基类提供的score_threshold_process(score_threshold, k, docs)base.py该工具可对“带相似度得分的文档列表”做统一的阈值过滤与截断供其他检索分支复用。返回对象为List[Document]每份文档的page_content即索引字段context的原文metadata携带source等原始元数据。六、按 id 读取与删除get_doc_by_ids / del_doc_by_ids这两个方法直接走原生es_client_python的文档级 API。get_doc_by_ids(ids)es_kb_service.py遍历 id 列表调用self.es_client_python.get(indexself.index_name, iddoc_id)从响应的_source中读取context字段作为page_content、metadata字段作为元数据组装成 LangChainDocument。任一 id 检索失败仅记录logger.error不会中断整体循环——因此结果列表长度可能小于传入 ids 长度调用方需自行容错。该方法是父类list_docs依据元数据库中的 id 回捞完整文档的核心支撑。del_doc_by_ids(ids)es_kb_service.py对每个 id 执行es_client_python.delete(indexself.index_name, iddoc_id, refreshTrue)。refreshTrue意味着删除后立即刷新分片保证后续查询立即可见——写入频繁的大批量删除场景下该参数会带来额外开销可结合业务权衡是否改为手动 refresh。七、按文件删除do_delete_doc 的实现细节do_delete_doc(kb_file, **kwargs)解决“删除一个知识库文件的所有切片”问题es_kb_service.py。由于一个文件经切分后会产生多个 ES 文档删除必须以元数据匹配而非单 id 进行if self.es_client_python.indices.exists(indexself.index_name): query { query: { term: {metadata.source.keyword: self.get_relative_source_path(kb_file.filepath)} }, track_total_hits: True, } size self.es_client_python.search(bodyquery)[hits][total][value] search_results self.es_client_python.search(bodyquery, sizesize) delete_list [hit[_id] for hit in search_results[hits][hits]] if len(delete_list) 0: return None for doc_id in delete_list: self.es_client_python.delete(indexself.index_name, iddoc_id, refreshTrue)其中有两处容易踩坑的细节相对路径一致性删除时用self.get_relative_source_path(kb_file.filepath)将绝对路径转为相对路径后再做 term 匹配与写入阶段把metadata.source规范为相对路径的逻辑保持一致。该工具方法定义在父类 base.py转换失败时会打印提示并退化为原路径。size 与 track_total_hitsES 的 search 默认只返回 10 条命中源码先以track_total_hits: True的查询拿到真实命中总数作为size再执行第二次完整查询从而避免“大文件切片数超过 10 条导致漏删”。开发者若自行改写此类逻辑务必保留这一两步查询策略。八、清理与删除的区别do_clear_vs / do_drop_kb两个“删除”方法作用域完全不同使用前务必辨析do_clear_vs()es_kb_service.py作用于远端 ES 集群。它检查indices.exists(indexself.kb_name)若索引存在则indices.delete删除整个索引及其全部数据实现向量库“一键清空”。父类clear_vs()随后会同步清空元数据库中该知识库的所有文件记录。do_drop_kb()作用于本地文件系统shutil.rmtree(self.kb_path)删除知识库本地目录含 content 源文件目录。如上文所述它不会删除 ES 中的索引。一个常见误区是“删除了知识库drop_kb后 ES 索引也消失了”——事实恰好相反。若要让 ES 索引与本地知识库同时消失需要先clear_vs()再drop_kb()或直接调用索引删除接口。由于删除索引不可恢复代码层面未做二次确认生产环境建议在操作前自行增加备份与确认机制。九、运行前提与常见问题排查启动前检查清单确保 ES 集群已启动且网络可达默认配置为http://127.0.0.1:9200在Settings.kb_settings.kbs_config[es]中正确填写scheme/host/port若开启安全认证同时填写user/password二者都为空时服务会以无认证模式运行并打印告警dims_length必须与所选 embedding 模型输出维度一致选用 HTTPS 时需要一并核对verify_certs、ca_certs乃至client_key/client_cert证书链配置缺少 CA 或客户端证书都会在初始化阶段抛异常通过KBServiceFactory.get_service(kb_name, SupportedVSType.ES, embed_model)或直接ESKBService(kb_name, embed_model...)构造实例时构造过程即触发do_init任何连接失败都会以ConnectionError形式向上抛出。常见异常速查现象可能原因处理建议do_init抛ConnectionError日志“连接到 Elasticsearch 失败”host/port 错误、ES 未启动或网络隔离核对kbs_config[es]连接串用curl http://ip:port探测建索引阶段持续打印“创建索引失败”已有同名索引且 mapping 冲突或dims_length与已建索引不一致清理旧索引或调整维度后重建向量维度一旦建索引即不可变do_add_doc抛ValueError(召回元素个数为0)写入后按metadata.source.keyword检索不到多为 source 相对路径不一致或写入延迟检查docs[0].metadata[source]是否被规范为相对路径必要时去掉refresh相关顾虑并重试检索结果为空或数量偏少score_threshold设置过高、top_k 过小、查询词与库内文本语义差异大调低阈值或参考SCORE_THRESHOLD默认值改用混合检索ensemble增强召回删除文件后旧切片仍被检索到metadata.source路径风格绝对/相对不一致导致 term 未命中确认写入、删除两侧都经get_relative_source_path统一处理十、总结在 Langchain-Chatchat 的多向量库架构中ESKBService提供了一条“以 Elasticsearch 作为远端向量库”的完整通路通过do_init把kbs_config[es]的运维配置、原生 ES 客户端与 LangChainElasticsearchStore串联起来通过重写父类的do_*钩子方法让上层完全透明的完成建库、灌入、检索、单文件删除与整库清理。它与 FAISS 等本地文件型服务最大的不同在于索引与向量数据始终存活在 ES 集群中天然具备横向扩展与持久化能力适合文档规模大、需要多副本高可用的 RAG 场景。若希望在更大的工程上下文里继续深化建议顺藤摸瓜阅读以下仓库文件KBService 抽象基类与工厂理解do_*钩子与公共编排逻辑ES 向量库服务实现本文核心对象的最新源码向量库类型与全局配置kbs_config、DEFAULT_VS_TYPE、检索默认参数定义VectorstoreRetrieverServicedo_search依赖的 Retriever 封装知识库迁移脚本folder2db中recreate_vs等模式如何批量驱动各 KBService 实例完成索引重建。【免费下载链接】Langchain-ChatchatLangchain-Chatchat原Langchain-ChatGLM基于 Langchain 与 ChatGLM, Qwen 与 Llama 等语言模型的 RAG 与 Agent 应用 | Langchain-Chatchat (formerly langchain-ChatGLM), local knowledge based LLM (like ChatGLM, Qwen and Llama) RAG and Agent app with langchain项目地址: https://gitcode.com/GitHub_Trending/la/Langchain-Chatchat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →