一次结果为零的实验
对 LightRAG 和 graphrag 两个项目运行 cross-repo-intelligence:
index_repository(
repo_path="/path/to/LightRAG",
mode="cross-repo-intelligence",
target_projects=["mnt-hdd-...-graphrag"]
)输出:
status: success
cross_http_calls: 0
cross_async_calls: 0
cross_channel: 0
cross_grpc_calls: 0
total_cross_edges: 0
elapsed_ms: 1090 条跨库边。
第一反应可能是"工具没找到东西,分析失败了"。但这个结论是错的。这个结果是完全正确的,而且非常有信息量。
LightRAG 和 graphrag 是两个功能相近的开源框架——都在做基于图的 RAG——但它们是平行替代品,不是相互调用的集成系统。没有任何 LightRAG 的代码调用 graphrag 的 API,反过来也一样。cross-repo-intelligence 找的是集成关系,不是功能相似性。两者之间本来就没有集成关系,返回 0 才是正确答案。
这个结果之所以重要,是因为它划清了一条边界:跨库分析解决的问题,和单库分析完全不同。
单库分析 vs 跨库分析:两个不同的问题
回顾前十篇,单库知识库能回答的问题都在同一个仓库边界内:
- "这个函数在哪里"(符号路径)
- "它被谁调用"(图路径 inbound)
- "它调用了什么"(图路径 outbound)
- "哪些函数和它经常一起改动"(FILE_CHANGES_WITH)
这些问题的答案都在同一个代码库里,调用图是完整的,可以从任意节点出发做 BFS。
但当系统边界扩展到多个仓库时,出现了一类单库分析物理上无法回答的问题:
- "服务 A 调用了服务 B 的哪些接口?"
- "如果我修改了服务 B 的
/query接口,哪些上游服务会受影响?" - "服务 C 通过消息队列发给服务 D 的消息格式变了,D 能感知到吗?"
- "整个微服务网络里,谁是中心节点,谁是孤岛?"
这些问题的答案横跨仓库边界。单库的调用图在服务边界处戛然而止,成了一张"有很多悬空终节点"的残缺地图——看起来像调用了某个外部 URL,但不知道那个 URL 是谁处理的。
cross-repo-intelligence 的工作,就是把这些悬空的终节点连起来。
跨库边是怎么被发现的
cross-repo-intelligence 模式的核心逻辑是:把已索引项目里的 Route 节点和 HTTP_CALLS 节点做跨库匹配。
具体来说:
-
发现"服务端路由":扫描所有项目,找
Route类型的节点(即 HTTP endpoint 定义),建立path → 项目的路由表。 -
发现"客户端调用":扫描
HTTP_CALLS边(即代码里的 HTTP 请求),提取被调用的 URL 或路径模式。 -
路径匹配:对每条
HTTP_CALLS,尝试在其他项目的路由表里找到匹配的Route。找到了,就创建一条CROSS_HTTP_CALLS边,跨越仓库边界连接调用方和被调用方。
除了 HTTP,同样的逻辑也适用于:
CROSS_ASYNC_CALLS:消息队列(Kafka topic 名、RabbitMQ exchange 等)CROSS_GRPC_CALLS:gRPC service/method 名CROSS_CHANNEL:其他命名通道(WebSocket 频道、Redis pub/sub key)
这就解释了为什么 LightRAG × graphrag 返回 0:LightRAG 里没有任何代码向 graphrag 的路由发起 HTTP 请求,graphrag 也没有调用 LightRAG 的 API。工具没有失败——它正确地报告了"这两个系统之间没有集成关系"。
实际看两个系统的路由面貌
即使没有跨库边,看两个系统各自的路由定义,也能快速理解它们的角色。
LightRAG:REST API 服务端
Route 节点(lightrag/api/routers/):
GET /query
POST /query
GET /query/stream
POST /query/stream
GET /health
POST /login
GET /auth-status
GET /graph/label/list
POST /documents/paginated
...LightRAG 暴露了完整的 HTTP 服务端接口——文档管理、查询、图浏览、健康检查一应俱全。从跨库分析的视角看,它是一个被调用方:如果你在另一个服务里向 http://lightrag-host/query 发请求,那条请求就会成为跨库边的一端。
对应的代码结构:create_query_routes 是 1566 行的路由注册函数,复杂度 118、认知复杂度 266——这是整个项目里最难的函数之一,因为它需要处理所有 HTTP 层面的边界情况。
graphrag:LLM 客户端
Route 节点(graphrag/):
PATCH / (API root)
外部调用: https://litellm.ai
外部调用: https://raw.githubusercontent.com/...cspell.schema.jsongraphrag 不是一个服务,而是一个命令行工具 + Python SDK。它的"路由"是它向外部 LLM 服务(litellm)和 GitHub 发起的 HTTP 调用,不是它自己暴露的 API 端点。从跨库分析的视角看,它是一个纯粹的调用方——如果你在同一个系统里同时部署了 graphrag 和 LightRAG,会发现 graphrag 可能调用 LiteLLM(CROSS_HTTP_CALLS),但 graphrag 不调用 LightRAG,LightRAG 也不调用 graphrag。
这个角色差异,从代码层面一眼可辨——一个有真正的 REST API 路由,一个只有对外的 HTTP 客户端调用。
跨库分析在实际工程里的价值
既然 LightRAG × graphrag 没有跨库边,什么样的系统才有?
典型的微服务架构:
前端 App
└─ CROSS_HTTP_CALLS → API Gateway (gateway-service)
└─ CROSS_HTTP_CALLS → User Service (user-service)
└─ CROSS_HTTP_CALLS → Auth Service (auth-service)
└─ CROSS_HTTP_CALLS → Order Service (order-service)
└─ CROSS_ASYNC_CALLS → Kafka topic: order.created
└─ CROSS_ASYNC_CALLS → Notification Service在这个架构里,如果 User Service 的 GET /users/{id} 接口要修改(比如返回字段变了),跨库分析会告诉你:有哪些服务通过 CROSS_HTTP_CALLS 调用了这个接口,它们可能需要同步更新。这是纯单库分析永远做不到的。
cross_service 模式的 trace_path:
trace_path(
function_name="get_user_by_id",
project="user-service",
mode="cross_service",
depth=3
)这个调用会沿着 HTTP_CALLS → CROSS_HTTP_CALLS → Route 边界穿越,找出所有跨服务的调用链——从某个前端 handler 一路追到 User Service 的数据库查询。
跨库分析的三个实用场景
场景 1:服务依赖图
把所有微服务项目索引后,用 Cypher 查出跨库调用关系:
MATCH (a)-[r:CROSS_HTTP_CALLS]->(b)
RETURN a.file_path, r.path, b.file_path结果就是整个系统的服务依赖图。哪些服务是"枢纽"(被多个服务调用)、哪些是"孤岛"(没有跨库调用)、哪些存在循环依赖——一条查询全都看到。
场景 2:接口变更影响评估
# 1. 找接口定义
search_code("GET /api/v2/payments", project="payment-service")
# 2. 找跨库调用方
trace_path("get_payment", mode="cross_service", direction="inbound")
→ 返回所有通过 CROSS_HTTP_CALLS 调用这个接口的上游服务修改 /api/v2/payments 之前,先知道有哪些服务依赖它。这个操作在没有跨库分析的情况下,需要在全组所有仓库里手动 grep 这个 URL——往往有遗漏。
场景 3:消息契约追踪
如果服务间通过 Kafka 通信:
MATCH (producer)-[r:CROSS_ASYNC_CALLS]->(consumer)
WHERE r.channel = "order.completed"
RETURN producer.file_path, consumer.file_path找到 order.completed 这条消息的所有生产者和消费者。消息 schema 要变更时,影响面一览无余。
没有跨库边的也有价值:相似性对比
回到 LightRAG 和 graphrag 这对没有跨库边的例子。虽然跨库分析没有发现集成关系,但同时持有两个项目的索引,仍然可以做一件有价值的事:接口设计对比。
查 LightRAG 的查询接口:
# LightRAG
async def aquery(self, query: str, param: QueryParam) -> str查 graphrag 的查询接口:
# graphrag
async def local_search(
config: GraphRagConfig,
entities: pd.DataFrame,
communities: pd.DataFrame,
community_reports: pd.DataFrame,
text_units: pd.DataFrame,
relationships: pd.DataFrame,
covariates: pd.DataFrame | None,
community_level: int,
response_type: str,
query: str,
) -> tuple[str | dict, str | list[pd.DataFrame] | dict[str, pd.DataFrame]]两个做同类事情的框架,接口设计差异极大:LightRAG 把所有配置封装进 QueryParam(面向服务运行时),graphrag 要求调用方自己管理所有 DataFrame(面向批处理分析管道)。这两个接口设计哲学,反映了两个项目完全不同的使用场景定位。
这种跨项目接口对比,是技术选型调研时最有价值的视角之一,而它恰好是跨库知识库的副产品——不需要跨库边,只需要两个项目的索引同时存在。
总结
跨库分析不是"让单库分析变得更大",而是解决了一类全新的问题:服务边界处的知识连接。
三条核心结论:
-
0 条跨库边是有意义的答案:它说明两个系统之间没有集成关系,而不是分析失败。不要把"工具找到东西"和"工具工作正常"混同。
-
跨库分析的核心是路由匹配:把一个服务暴露的 Route 节点,和另一个服务发出的 HTTP_CALLS / ASYNC_CALLS 节点做路径匹配——这是它能发现的,也只有这个它能发现的。功能相似性、代码风格对比、重复逻辑检测,都是别的工具的事。
-
没有跨库边的项目对,同样可以做接口对比:跨库知识库让你同时查询多个项目,即使它们之间没有调用关系,并列的索引本身就有分析价值。
下一篇,我们把视角转向知识库的评测——如何知道你的知识库"够不够好"?用什么指标衡量,怎么设计评测数据集,以及当 Recall@5 不再是唯一关注指标时,生产系统应该追踪什么。
如果这个系列对你有帮助,欢迎关注我的个人主页 dongqi.dev,持续更新 LLM 工程实践内容。
在 PrimeSkills,我们帮助工程团队建立多服务代码库知识库,包括跨库依赖图的建立和维护,以及接口变更的跨服务影响评估。如果你的团队正在管理多个微服务,欢迎探讨如何落地跨库知识库。