前八篇是在造船,这篇终于要出海了
如果你一路读过来,此刻脑子里应该存着一张清单:
- 第 03 篇:AST 函数级分割 + 向量检索,Recall@5 = 0.958,是文本方案的天花板
- 第 05 篇:图检索救出 Q8,但 BFS 噪声拖垮了 Q1------向量和图是"一换一"的买卖
- 第 06、07 篇:结构感知 embedding 和混合检索都没能真正弥合这道裂缝
- 第 08 篇:三路正交,向量管语义、图管结构、符号管精确------分开部署,按查询意图路由
这些都是在一个 30 个函数的"玩具"代码库上跑出来的。玩具很诚实,但它也是玩具。
今天换个场地。
LightRAG 是目前 GitHub Star 数最多的开源知识图谱 RAG 框架之一,代码库已经用 codebase-memory-mcp 完整索引:20,674 个节点、94,517 条边,覆盖 409 个 Python 文件外加 101 个 TypeScript 文件(Web UI)。这个规模放到真实工程场景里只能算中型项目,但已经足够让前几篇实验里的所有结论在真实战场上接受检验了。
这篇文章的任务很简单:拿三个真实问题,分别调用三条检索路径,把返回结果原样摊开,让你看清楚每路能给什么、给不了什么。
先把地图看一眼
在开始查询之前,先对着整体架构做一次定向。
LightRAG 这个名字很容易让人以为它只是一个 RAG 工具库,但实际的代码结构远比"一个检索工具"复杂。运行 get_architecture 后,最重要的信息不是文件列表,而是层次划分:
yaml
entry layer: kg/, chunker/ ← 入口:存储适配器、分块器
core layer: base, api, parser ← 核心:抽象基类、REST API、文档解析
internal: examples/, tests/ ← 内部:示例、测试(对外不暴露)
这张图立刻告诉你:如果你想理解"LightRAG 怎么处理一个文档",主战场在 lightrag/ 目录,入口在 lightrag.py,底层存储实现在 kg/ 下(支持 Neo4j、MongoDB、Qdrant、Milvus、PostgreSQL 等 10 余种后端)。
现在开始真正的检索。
第一路:向量路径------用自然语言问"怎么检索"
问题:LightRAG 支持哪些检索模式,每种模式分别适合什么场景?
这是一个典型的"功能查询"------提问者不知道函数叫什么,只知道自己想了解什么。这正是向量路径最擅长处理的类型。
工具:search_graph(BM25 + 向量双索引)
vbnet
query: "query search hybrid retrieval mode"
label: Method
limit: 5
返回结果(节选最关键的一条,其余是测试文件):
erlang
QueryParam (lightrag/base.py, line 83)
"local": Focuses on context-dependent information.
"global": Utilizes global knowledge.
"hybrid": Combines local and global retrieval methods.
"naive": Performs a basic search without advanced techniques.
"mix": Integrates knowledge graph and vector retrieval.
"bypass": ...
一条查询命中了这个:
python
class QueryParam:
"""Configuration parameters for query execution in LightRAG."""
mode: Literal["local", "global", "hybrid", "naive", "mix", "bypass"] = "mix"
6 种检索模式直接在 QueryParam 类里。 这个类的 docstring 把每种模式解释得很清楚:
| 模式 | 说明 |
|---|---|
local |
局部上下文检索,关注与查询直接相关的实体邻域 |
global |
全局知识检索,跨文档聚合关系 |
hybrid |
local + global 组合 |
naive |
朴素向量检索,不用知识图谱 |
mix |
知识图谱 + 向量检索融合(默认模式) |
bypass |
直接传给 LLM,跳过检索 |
注意这个搜索返回的是类定义,而不是某个具体的函数实现。这说明向量路径对"找概念入口"这类查询非常有效------你描述一个功能,它把最相关的抽象定义给你,然后你从这里深入。
另一个 search_graph 查询验证了这个规律:
php
query: "insert document knowledge graph extraction"
→ 命中: _find_related_text_unit_from_entities (operate.py:5260)
_find_related_text_unit_from_relations (operate.py:5511)
run_rebuild_entities_relations (tools/rebuild_vdb.py:900)
"插入文档、提取知识图谱"这个意图,向量索引准确定位到了 operate.py 里的相关提取逻辑,而不是返回一堆"insert"关键字的不相关代码。
第二路:图路径------追调用链,看一个函数如何触发整个管道
问题:ainsert() 调用了什么?文档插入的执行路径是什么?
这是一个典型的"结构查询"------提问者知道入口函数的名字,想理解整条执行路径。这正是图路径的主场。
工具:trace_path(调用图 BFS)
makefile
function_name: ainsert
mode: calls
direction: outbound
depth: 2
返回(hop=1 直接调用,hop=2 间接调用):
scss
hop=1 直接调用:
apipeline_enqueue_documents (pipeline.py)
apipeline_process_enqueue_documents (pipeline.py)
generate_track_id (utils.py)
resolve_chunk_options (parser/routing.py)
hop=2 间接调用(pipeline 内部):
_run_pipeline_batch
_validate_and_fix_document_consistency
_atomic_release_busy_or_consume_pending
compute_mdhash_id
sanitize_text_for_encoding
normalize_document_file_path
filter_keys (BaseKVStorage)
upsert (BaseVectorStorage)
get_by_id (BaseVectorStorage)
get_docs_by_statuses (DocStatusStorage)
get_namespace_data
get_namespace_lock
...(共 36 个节点)
这 36 个节点构成了完整的文档插入路径。但仅仅列出来还不够,源码才是真正的地图:
python
async def ainsert(
self,
input: str | list[str],
split_by_character: str | None = None,
split_by_character_only: bool = False,
ids: str | list[str] | None = None,
file_paths: str | list[str] | None = None,
track_id: str | None = None,
) -> str:
"""Async insert documents with checkpoint support (fixed-token chunking only).
SDK convenience entry point. It **always** chunks with the fixed-token
(F) strategy: ``process_options`` is intentionally not passed, so the
document runs the F chunker. ...
The LightRAG **server / REST API does not call this method** --- it
ingests via :meth:`apipeline_enqueue_documents` +
:meth:`apipeline_process_enqueue_documents` with a per-document
``process_options`` selector, which is how F/R/V/P are chosen there.
"""
chunk_opts = resolve_chunk_options(
self.addon_params,
split_by_character=split_by_character,
split_by_character_only=split_by_character_only,
)
await self.apipeline_enqueue_documents(input, ids, file_paths, track_id, chunk_options=chunk_opts)
await self.apipeline_process_enqueue_documents()
return track_id
这段源码里隐藏了一个非常关键的设计决策,光看函数名根本看不出来:
ainsert只支持 fixed-token 分块策略(F 策略)。如果你想用递归字符(R)、语义向量(V)或段落语义(P)分块策略,不能调ainsert,必须直接调底层的apipeline_enqueue_documents+apipeline_process_enqueue_documents,并显式传入process_options。
同样地,LightRAG 的 REST API 服务器也不走 ainsert,它直接调管道层------所以 SDK 用户和 REST API 用户实际上走的是不同的代码路径。
这就是图路径的价值所在:它不只返回"这里有个函数",它返回"这个函数在整个系统里扮演什么角色"。 如果只用向量搜索,你大概率会找到 ainsert 这个方法;但你不会知道它被故意设计成了一个 F-only 的简化入口,以及这个设计决策对你的实际用法意味着什么。
第三路:符号路径------精确命中,零歧义
问题:BaseVectorStorage.upsert 在整个代码库里被哪些地方调用?
这是一个"影响范围"查询------提问者想做重构或安全审计,需要知道某个接口的所有调用方。这类查询对语义理解没有任何要求,但要求精确匹配。
工具:search_code(图增强 grep)
vbnet
pattern: "BaseVectorStorage"
mode: compact
limit: 5
结果显示 upsert(BaseVectorStorage 的核心方法)在整个代码库里有 268 个入度(fan_in = 268),是整个项目最高频调用的方法之一。
换一个更精确的查询:
vbnet
query: "QueryParam"
立刻定位到:
bash
QueryParam lightrag/base.py:83 (Class, in_degree=19)
↑ 被 19 个地方使用
- lightrag/lightrag.py:2056 (query 方法)
- lightrag/lightrag.py:2091 (aquery 方法)
- lightrag/operate.py:4323 (_perform_kg_search)
- lightrag/lightrag.py:2344 (aquery_llm)
...
19 个调用方,精确定位,每个都带文件名和行号。如果你要修改 QueryParam 的接口(比如废弃某个 mode 或新增参数),这张清单就是你的影响面评估报告。
符号路径的本质是:图增强的 grep。 普通 grep 只会返回字符串匹配的位置;符号路径还告诉你每个命中位置在调用图里的层次------它是不是入口点、它的调用者是谁、它的 in_degree 有多高。这让你在几秒内就能判断"改这个地方改动大不大"。
三路联动:一个真实问题,三路分别打
前面三个例子是各路独立演示,现在做一个更真实的场景:
任务:理解 LightRAG 的文档入库完整流程,用于修改分块策略
这是一个工程师在准备做功能改造时会问的问题,答案不在单一函数里,需要多角度拼图。
第一步:向量路径定位概念入口
scss
search_graph("document chunking strategy pipeline insert")
→ ainsert (lightrag.py:1428)
→ ainsert_custom_chunks (lightrag.py --- deprecated)
→ resolve_chunk_options (parser/routing.py)
向量路径给出了"这个系统里和分块相关的入口在哪",并且顺手告诉你 ainsert_custom_chunks 已经被标记为 deprecated(从文档中读到的信息)。
第二步:图路径追执行链路
ini
trace_path("ainsert", mode=calls, depth=2)
→ hop=1: apipeline_enqueue_documents → resolve_chunk_options
→ hop=2: _run_pipeline_batch → [各种 Storage.upsert]
图路径告诉你:分块策略的选择发生在 resolve_chunk_options(入库前),真正的分块执行在 _run_pipeline_batch(管道批处理中),最终写入各种存储后端。如果你要修改分块策略,需要改 parser/routing.py,而不是改 lightrag.py 里的 ainsert。
第三步:符号路径找接口定义
bash
search_code("resolve_chunk_options")
→ lightrag/parser/routing.py:chunk_strategy_key
→ lightrag/parser/routing.py:slim_chunk_options
→ lightrag/parser/routing.py:default_chunker_config
符号路径直接定位到 routing.py 里的三个相关函数,以及它们在 ainsert 调用链里被引用的确切位置。
三步之后你知道: 要改分块策略,改 parser/routing.py;入库走 ainsert 时只支持 F 策略,其他策略需要直接调管道层;存储写入是通过 BaseVectorStorage.upsert 抽象隔离的,改分块不会影响存储后端。这是一张完整的工程改造地图,三路各自贡献了它能贡献的那一块。
工具背后:三路对应第 08 篇的什么设计
现在可以把 codebase-memory-mcp 的工具调用和第 08 篇的架构蓝图对应起来了:
| 第 08 篇架构组件 | codebase-memory-mcp 工具 | 实际做了什么 |
|---|---|---|
| 向量检索路径 | search_graph(query=...) |
BM25 + 向量双索引,自然语言 → 函数定义 |
| 图检索路径 | trace_path(function_name, mode=calls) |
调用图 BFS,函数名 → 调用链 |
| 符号检索路径 | search_code(pattern) |
图增强 grep,字符串 → 精确位置 + 调用上下文 |
| 查询路由 | 工具选择本身 | 不同问题选不同工具,无需统一入口 |
| 知识图谱索引 | index_repository() |
离线建图,20,674 节点 / 94,517 边 |
第 08 篇有一个关键设计原则:三路信号正交互补,不应该让其中一路做另一路能更好完成的事。这在实际使用中体现为:
- 不要用
trace_path来"搜索"功能(那是向量的活) - 不要用
search_graph来追调用链(那是图的活) - 不要用语义搜索来找精确函数名(那是符号的活,grep 更快)
工具选对了,每次查询都是一次精准打击。工具选错了,召回率直接腰斩。
一个反直觉的发现:规模让图路径更值钱
在玩具代码库(30 个函数)上,trace_path 显示的 BFS 只展开几个节点,图检索的价值不明显。但在 LightRAG(7,761 个函数 + 3,569 个方法)上,同一条调用链展开了 36 个节点,覆盖了从用户 API 入口到底层存储抽象的完整路径。
这不是线性增长,而是规模放大效应:代码库越大,一个函数的"邻居"越多,纯向量检索越难把结构关系体现出来,图路径的相对优势就越大。
这正是第 05 篇那个 Q8 问题在真实世界里的投影。ainsert 和 _run_pipeline_batch 在语义上没有明显关联------一个是"插入文档的高层 API",一个是"批量执行管道任务的底层方法"------任何 embedding 模型都很难把它们拉近。但调用图一步就把它们连起来了。
当你问的是"这个功能改了会影响什么",或者"这个函数是被谁调用的"------这类问题在 5,000 函数的代码库里的答案,和 30 函数的答案本质一样:都只在图里。
总结
经过九篇,这个系列从最基础的 embedding 基线出发,一路量出了每种方案的边界,最终落到一个可以在真实项目上运行的工具链。
把三路召回的定位最后总结一遍:
- 向量路径 :你不知道函数叫什么,只知道你在找什么功能------
search_graph(query=...)是起手式 - 图路径 :你知道一个入口函数,想理解它的执行路径和影响范围------
trace_path是主力 - 符号路径 :你知道精确的函数名或类名,想找定义和所有引用------
search_code一秒出结果
这三路不是三个竞争方案,是三个正交维度。任何需要"理解一个陌生代码库"的任务,都需要三路协同才能形成完整的认知地图。
这个系列到这里做一个阶段性收束。九篇从理论推导到实战验证,核心结论始终是同一句话:
代码库的语义理解是多维的,任何单一信号都有它到不了的盲区。把工具选对,把路径分清楚,才是真正工程意义上的解法。
欢迎访问 PrimeSkills ------ 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。
更多实用知识和有趣产品,欢迎访问我的个人主页