跳到主要内容

灌库管线与配置工厂

本章讲两件「支撑性」的事:文档怎么变成可检索的向量(离线管线),以及零件怎么按配置被装起来(工厂)。

6.1 离线管线:从文件到向量库

入口 load_from_local_files(offline_loading.py:11-69)。五步:

① 准备集合 vector_db.init_collection(dim=embedding.dimension, ...)
② 加载 file_loader.load_file / load_directory → [Document]
③ 切块+句窗 split_docs_to_chunks(...) → [Chunk]
④ 批量向量化 embedding_model.embed_chunks(chunks, batch_size)
⑤ 入库 vector_db.insert_data(collection, chunks)

一个小细节:集合名会被规整——空格和连字符替换成下划线(offline_loading.py:41),因为很多向量库不接受这些字符:

collection_name = collection_name.replace(" ", "_").replace("-", "_")

load_from_website(offline_loading.py:72-119)结构一样,只是把 file_loader 换成 web_crawler.crawl_urls

6.2 精华机制:Sentence Window(句窗切块)

这是灌库里最值得讲的设计,解决一个 RAG 经典矛盾:

小块利于检索(向量更聚焦、命中更准),但大块利于作答(LLM 需要足够上下文)。

DeepSearcher 的解法:检索用小块,作答用大块。具体在 _sentence_window_split(splitter.py:46-77):

# 先用 RecursiveCharacterTextSplitter 切成小块(chunk_size 默认 1500)
# 对每个小块,回到原文,向左右各扩 offset(=300)个字符
start_index = original_text.index(doc_text)
end_index = start_index + len(doc_text) - 1
wider_text = original_text[
max(0, start_index - offset) : min(len(original_text), end_index + offset)
]
doc.metadata["wider_text"] = wider_text # 宽上下文存进 metadata

两份文本各司其职:

字段内容用在哪
chunk.text小块(精确)算 embedding、做检索匹配、去重
metadata["wider_text"]小块 + 左右各 300 字符喂给 LLM 作答时用(更全的上下文)

所以前面各 agent 汇总时都有那段 if text_window_splitter and "wider_text" in metadata: 用 wider_text(如 deep_search.py:294-297chain_of_rag.py:321-324)——检索命中的是小块,但塞给 LLM 的是它的「加宽版」。

切块本身用 LangChain 的 RecursiveCharacterTextSplitter(splitter.py:97-99),chunk_overlap 默认 100 让相邻块有重叠,避免句子被硬切断。

6.3 配置工厂:按字符串名反射出零件

ModuleFactory(configuration.py:92-171)的核心是一个反射式装配(_create_module_instance,configuration.py:109-126):

# 配置里写 provider = "OpenAI",就动态 import 并 new 出 OpenAI 类
class_name = self.config.provide_settings[feature]["provider"] # 如 "OpenAI"
module = __import__(module_name, fromlist=[class_name]) # deepsearcher.llm
class_ = getattr(module, class_name)
return class_(**self.config.provide_settings[feature]["config"]) # 传入 config dict

妙在哪: 加一个新 LLM 厂商,只要在 deepsearcher/llm/ 加个类、在 __init__ 导出、在 yaml 里写名字——工厂代码一行不改。这就是为什么 README 能列出十几种 LLM/向量库:它们都是同一个反射模式下的可插拔实现。

配置来源是 config.yaml(Configuration.__init__,configuration.py:29-40),分三块:provide_settings(各零件用谁)、query_settings(如 max_iter)、load_settings。也可以用 set_provider_config(feature, provider, configs) 在代码里覆盖(configuration.py:55-71),就是 README 快速开始里那几行。

6.4 装配的时机:init_config 一次性 new 全部

init_config(configuration.py:186-241)被调用时,用工厂依次造出 llm / embedding / file_loader / web_crawler / vector_db,再把它们注入两个 agent,包成 RAGRouter 存进全局 default_searcher;同时单独造一个 naive_rag(NaiveRAG)供 naive_rag_query 用。注意 max_iterconfig.query_settings["max_iter"] 取(configuration.py:219),把「迭代多少轮」也做成了可配置项。