GraphRAG(Graph Retrieval‑Augmented Generation),微软研究院 2024 年提出,是知识图谱 + RAG 检索增强生成的高级方案Microsoft ...
下图就是一个典型的传统RAG解决方案:利用ES 和 milvus实现数据检索,然后合并去重,之后利用重排模型将RAG到的文档全部重新排序,然后将文档交给大模型搜索答案。
上图传统RAG有个问题:无法捕捉数据之间的关联关系,只能实现“单点式”检索,难以应对需要挖掘数据内在逻辑、关联链路的场景。
比如我搜索奶茶,ES 和 milvus 只去搜索奶茶,不会考虑奶茶的原料,商店,口味,填料比如珍珠,椰果等等信息。
如果我想要知道货物的来龙去脉的时候,ES 和 milvus 就会显得很局促,不能胜任这份工作。此时我们就想到了neo4j 工具。
由neo4j 加持的RAG 就叫GraphRAG ,
GraphRAG 会先将非结构化数据中的实体、关系提取出来,用 Neo4j 这样的图数据库构建知识图谱,再结合向量检索的语义优势,实现“图谱关联+语义匹配”的双重检索,既能找到语义相近的内容,又能顺着知识图谱的关联链路,完成跨文档、多步骤的复杂推理,让 RAG 生成的答案更精准、更具可解释性。
二.neo4j是什么?
Neo4j 是一个图数据库——数据不是存在表里,而是存成「点」和「线」。点就是实体(芋圆、木薯淀粉、泰国),线就是它们之间的关系(芋圆-主要成分是-木薯淀粉)。
和mysql比较
neo4j 和 mysql 一样,有个服务 server,用来装数据。也有个可视化软件用来连接server,对数据直接增删改查。
你还可以在nodejs乡里面安装 neo4j-driver 将项目和neo4j 数据库连接起来,在项目里面对图数据库里面的数据做增删改查。
mysql的连接
<span>const</span> mysql = <span>require</span>(<span>"mysql2/promise"</span>);
<span>const</span> pool = mysql.<span>createPool</span>({ <span>host</span>: <span>"localhost"</span>, <span>user</span>: <span>"root"</span>, <span>password</span>: <span>"xxx"</span>, <span>database</span>: <span>"test"</span> });
<span>//建表</span>
<span>// 增</span>
<span>await</span> pool.<span>execute</span>(<span>"INSERT INTO users (name) VALUES (?)"</span>, [<span>"张三"</span>]);
<span>// 查</span>
<span>const</span> [rows] = <span>await</span> pool.<span>execute</span>(<span>"SELECT * FROM users WHERE name = ?"</span>, [<span>"张三"</span>]);
neo4j的连接
<span>const</span> neo4j = <span>require</span>(<span>"neo4j-driver"</span>);
<span>const</span> driver = neo4j.<span>driver</span>(<span>"bolt://localhost:7687"</span>, neo4j.<span>auth</span>.<span>basic</span>(<span>"neo4j"</span>, <span>"xxx"</span>));
<span>// 增(CREATE = INSERT)</span>
<span>const</span> session = driver.<span>session</span>();
<span>await</span> session.<span>executeWrite</span>(<span><span>tx</span> =></span>
tx.<span>run</span>(<span>"CREATE (u:User {name: $name})"</span>, { <span>name</span>: <span>"张三"</span> })
);
<span>// 查(MATCH = SELECT)</span>
<span>const</span> result = <span>await</span> session.<span>executeRead</span>(<span><span>tx</span> =></span>
tx.<span>run</span>(<span>"MATCH (u:User {name: $name}) RETURN u"</span>, { <span>name</span>: <span>"张三"</span> })
);
session.<span>close</span>();
mysql和Neo4j的区别
MySQL 的表是预先定义 schema 的(CREATE TABLE),Neo4j 的节点不用建表,直接 CREATE 就有结构了,字段随便加。这是图数据库的特点——schema-free。
MySQL看数据需要下载MySQL Workbench软件, Neo4j Browser看数据只需要在浏览器打 http://localhost:7474。
mysql增删改查的语句是sql语句。neo4j用的是cypher语句。在MySQL Workbench软件里面执行mysql可以对数据表做增删改查操作。在 Neo4j Browser里面可以执行cypher语句,添加节点和连接。
在红框里面写入cypher 语句,直接创建知识图谱,删除的时候,是先删除关系线,然后才能删除节点,只有节点上没有任何关联线的时候才能删除节点。
下载
去 neo4j.com/download 下 Desktop 版,装完新建一个本地库,连接方式选 bolt://localhost:7687,用户名 neo4j,首次登录会强制改密码。
验证:浏览器打开 http://localhost:7474,输密码能进去就行。
在nestjs项目里面使用
npm install neo4j-driver
npm install -D @types/node
添加环境变量
<span>NEO4J</span>_URI=<span>bolt</span>:<span>//localhost:7687</span>
<span>NEO4J</span>_USERNAME=neo4j
<span>NEO4J</span>_PASSWORD=yourpassword
<span>NEO4J</span>_DATABASE=neo4j
最小连接案例
<span>const</span> neo4j = <span>require</span>(<span>"neo4j-driver"</span>);
<span>// 1. 创建 driver(全局一个,自带连接池)</span>
<span>const</span> driver = neo4j.<span>driver</span>(
<span>"bolt://localhost:7687"</span>,
neo4j.<span>auth</span>.<span>basic</span>(<span>"neo4j"</span>, <span>"yourpassword"</span>),
{
<span>maxConnectionPoolSize</span>: <span>50</span>,
<span>connectionAcquisitionTimeout</span>: <span>30000</span>,
}
);
<span>// 2. 启动时探活</span>
<span>async</span> <span>function</span> <span>main</span>(<span></span>) {
<span>await</span> driver.<span>verifyConnectivity</span>();
<span>console</span>.<span>log</span>(<span>"✅ 已连接到 Neo4j"</span>);
<span>// 3. 每个请求开自己的 session</span>
<span>const</span> session = driver.<span>session</span>({ <span>database</span>: <span>"neo4j"</span> });
<span>try</span> {
<span>// 4. 写操作放 executeWrite,读操作放 executeRead</span>
<span>const</span> result = <span>await</span> session.<span>executeRead</span>(<span>(<span>tx</span>) =></span> {
<span>return</span> tx.<span>run</span>(
<span>"MATCH (e:Entity {name: $name})-[r]->(b) RETURN type(r) AS rel, b.name AS target"</span>,
{ <span>name</span>: <span>"芋圆"</span> } <span>// 参数化,防注入</span>
);
});
result.<span>records</span>.<span>forEach</span>(<span>(<span>r</span>) =></span> {
<span>console</span>.<span>log</span>(<span>`<span>${r.get(<span>"rel"</span>)}</span> → <span>${r.get(<span>"target"</span>)}</span>`</span>);
});
} <span>finally</span> {
<span>// 5. 必须关 session,否则连接池会被吃光</span>
<span>await</span> session.<span>close</span>();
}
}
<span>main</span>()
.<span>catch</span>(<span>console</span>.<span>error</span>)
.<span>finally</span>(<span>() =></span> driver.<span>close</span>()); <span>// 进程退出前关 driver</span>
批量写操作
<span>async</span> <span>function</span> <span>createGraph</span>(<span>driver</span>) {
<span>const</span> session = driver.<span>session</span>({ <span>database</span>: <span>"neo4j"</span> });
<span>try</span> {
<span>await</span> session.<span>executeWrite</span>(<span>async</span> (tx) => {
<span>// 批量写入用 UNWIND,一次网络往返搞定</span>
<span>await</span> tx.<span>run</span>(
<span>`UNWIND $rows AS row
MERGE (a:Entity {name: row.from})
MERGE (b:Entity {name: row.to})
MERGE (a)-[:CONTAINS]->(b)`</span>,
{
<span>rows</span>: [
{ <span>from</span>: <span>"芋圆奶茶"</span>, <span>to</span>: <span>"芋圆"</span> },
{ <span>from</span>: <span>"芋圆"</span>, <span>to</span>: <span>"木薯淀粉"</span> },
{ <span>from</span>: <span>"木薯淀粉"</span>, <span>to</span>: <span>"泰国"</span> },
],
}
);
});
<span>console</span>.<span>log</span>(<span>"✅ 图谱写入完成"</span>);
} <span>finally</span> {
<span>await</span> session.<span>close</span>();
}
}
单个写数据操作
<span>async</span> <span>function</span> <span>createGraph</span>(<span>driver</span>) {
<span>const</span> session = driver.<span>session</span>({ <span>database</span>: <span>"neo4j"</span> });
<span>try</span> {
<span>// 单条写入:依次建节点和关系,每条独立执行</span>
<span>await</span> session.<span>executeWrite</span>(<span>async</span> (tx) => {
<span>// 1. 创建节点:芋圆奶茶</span>
<span>await</span> tx.<span>run</span>(
<span>"CREATE (:Entity {name: $name})"</span>,
{ <span>name</span>: <span>"芋圆奶茶"</span> }
);
<span>// 2. 创建节点:芋圆</span>
<span>await</span> tx.<span>run</span>(
<span>"CREATE (:Entity {name: $name})"</span>,
{ <span>name</span>: <span>"芋圆"</span> }
);
<span>// 3. 创建节点:木薯淀粉</span>
<span>await</span> tx.<span>run</span>(
<span>"CREATE (:Entity {name: $name})"</span>,
{ <span>name</span>: <span>"木薯淀粉"</span> }
);
<span>// 4. 创建节点:泰国</span>
<span>await</span> tx.<span>run</span>(
<span>"CREATE (:Entity {name: $name})"</span>,
{ <span>name</span>: <span>"泰国"</span> }
);
<span>// 5. 建立关系:芋圆奶茶 -> 芋圆</span>
<span>await</span> tx.<span>run</span>(
<span>`MATCH (a:Entity {name: $from})
MATCH (b:Entity {name: $to})
CREATE (a)-[:CONTAINS]->(b)`</span>,
{ <span>from</span>: <span>"芋圆奶茶"</span>, <span>to</span>: <span>"芋圆"</span> }
);
<span>// 6. 建立关系:芋圆 -> 木薯淀粉</span>
<span>await</span> tx.<span>run</span>(
<span>`MATCH (a:Entity {name: $from})
MATCH (b:Entity {name: $to})
CREATE (a)-[:CONTAINS]->(b)`</span>,
{ <span>from</span>: <span>"芋圆"</span>, <span>to</span>: <span>"木薯淀粉"</span> }
);
<span>// 7. 建立关系:木薯淀粉 -> 泰国</span>
<span>await</span> tx.<span>run</span>(
<span>`MATCH (a:Entity {name: $from})
MATCH (b:Entity {name: $to})
CREATE (a)-[:CONTAINS]->(b)`</span>,
{ <span>from</span>: <span>"木薯淀粉"</span>, <span>to</span>: <span>"泰国"</span> }
);
});
<span>console</span>.<span>log</span>(<span>"✅ 图谱写入完成"</span>);
} <span>finally</span> {
<span>await</span> session.<span>close</span>();
}
}
读数据
<span>async</span> <span>function</span> <span>findPath</span>(<span>driver, start, maxHops = <span>2</span></span>) {
<span>const</span> session = driver.<span>session</span>({ <span>database</span>: <span>"neo4j"</span> });
<span>try</span> {
<span>const</span> result = <span>await</span> session.<span>executeRead</span>(<span>(<span>tx</span>) =></span>
tx.<span>run</span>(
<span>`MATCH path = (a:Entity {name: $start})-[*1..<span>${maxHops}</span>]->(end)
RETURN [n IN nodes(path) | n.name] AS path, length(path) AS hops
ORDER BY hops
LIMIT 20`</span>,
{ start }
)
);
<span>return</span> result.<span>records</span>.<span>map</span>(<span>(<span>r</span>) =></span> ({
<span>path</span>: r.<span>get</span>(<span>"path"</span>),
<span>hops</span>: r.<span>get</span>(<span>"hops"</span>).<span>toNumber</span>(), <span>// Integer 转 JS number</span>
}));
} <span>finally</span> {
<span>await</span> session.<span>close</span>();
}
}
<span>// 调用</span>
<span>const</span> paths = <span>await</span> <span>findPath</span>(driver, <span>"芋圆奶茶"</span>, <span>2</span>);
paths.<span>forEach</span>(<span>(<span>p</span>) =></span> <span>console</span>.<span>log</span>(<span>`<span>${p.hops}</span> 跳: <span>${p.path.join(<span>" → "</span>)}</span>`</span>));
<span>// 输出: 1 跳: 芋圆奶茶 → 芋圆</span>
<span>// 2 跳: 芋圆奶茶 → 芋圆 → 木薯淀粉</span>
封装neo4j
<span>// db.js</span>
<span>const</span> neo4j = <span>require</span>(<span>"neo4j-driver"</span>);
<span>let</span> driver;
<span>function</span> <span>connect</span>(<span>config</span>) {
driver = neo4j.<span>driver</span>(
config.<span>uri</span>,
neo4j.<span>auth</span>.<span>basic</span>(config.<span>username</span>, config.<span>password</span>),
{ <span>maxConnectionPoolSize</span>: <span>50</span> }
);
<span>return</span> driver.<span>verifyConnectivity</span>();
}
<span>async</span> <span>function</span> <span>read</span>(<span>cypher, params = {}</span>) {
<span>const</span> session = driver.<span>session</span>();
<span>try</span> {
<span>const</span> result = <span>await</span> session.<span>executeRead</span>(<span>(<span>tx</span>) =></span>
tx.<span>run</span>(cypher, params)
);
<span>return</span> result.<span>records</span>.<span>map</span>(<span>(<span>r</span>) =></span> r.<span>toObject</span>());
} <span>finally</span> {
<span>await</span> session.<span>close</span>();
}
}
<span>async</span> <span>function</span> <span>write</span>(<span>cypher, params = {}</span>) {
<span>const</span> session = driver.<span>session</span>();
<span>try</span> {
<span>return</span> <span>await</span> session.<span>executeWrite</span>(<span>(<span>tx</span>) =></span> tx.<span>run</span>(cypher, params));
} <span>finally</span> {
<span>await</span> session.<span>close</span>();
}
}
<span>async</span> <span>function</span> <span>close</span>(<span></span>) {
<span>return</span> driver && driver.<span>close</span>();
}
<span>module</span>.<span>exports</span> = { connect, read, write, close };
使用
<span>const</span> db = <span>require</span>(<span>"./db"</span>);
<span>async</span> <span>function</span> <span>main</span>(<span></span>) {
<span>await</span> db.<span>connect</span>({
<span>uri</span>: <span>"bolt://localhost:7687"</span>,
<span>username</span>: <span>"neo4j"</span>,
<span>password</span>: <span>"yourpassword"</span>,
});
<span>const</span> data = <span>await</span> db.<span>read</span>(
<span>"MATCH (e:Entity) RETURN e.name AS name LIMIT 10"</span>
);
<span>console</span>.<span>log</span>(data);
<span>await</span> db.<span>close</span>();
}
<span>main</span>();
三.基于neo4j实现graphRAG
安装包
pnpm install @langchain/community @langchain/openai @langchain/core @langchain/langgraph dotenv
配置环境变量
<span>OPENAI_API_KEY</span>=sk-xx
<span>OPENAI_BASE_URL</span>=<span>https</span>:<span>//dashscope.aliyuncs.com/compatible-mode/v1</span>
<span>RERANK_URL</span>=<span>https</span>:<span>//dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank</span>
<span>MODEL_NAME</span>=qwen-plus
graphRAG+neo4j
<span>import</span> <span>'dotenv/config'</span>
<span>import</span> { <span>Neo4</span>jGraph } <span>from</span> <span>'@langchain/community/graphs/neo4j_graph'</span>
<span>import</span> { <span>ChatOpenAI</span> } <span>from</span> <span>'@langchain/openai'</span>
<span>import</span> { <span>StateGraph</span>, <span>END</span>, <span>START</span> } <span>from</span> <span>'@langchain/langgraph'</span>
<span>import</span> { <span>HumanMessage</span> } <span>from</span> <span>'@langchain/core/messages'</span>
<span>// ----------------------</span>
<span>// 连接 Neo4j 知识图谱</span>
<span>// ----------------------</span>
<span>const</span> graph = <span>new</span> <span>Neo4</span>jGraph({
<span>url</span>: <span>'bolt://localhost:7687'</span>,
<span>username</span>: <span>'neo4j'</span>,
<span>password</span>: <span>'12345678'</span>,
})
<span>// ----------------------</span>
<span>// 大模型</span>
<span>// ----------------------</span>
<span>const</span> llm = <span>new</span> <span>ChatOpenAI</span>({
<span>model</span>: process.<span>env</span>.<span>MODEL_NAME</span>,
<span>temperature</span>: <span>0</span>,
<span>configuration</span>: { <span>baseURL</span>: process.<span>env</span>.<span>OPENAI_BASE_URL</span> }
})
<span>// ----------------------</span>
<span>// 定义状态</span>
<span>// ----------------------</span>
<span>const</span> state = {
<span>messages</span>: {
<span>value</span>: <span>(<span>left, right</span>) =></span>
left.<span>concat</span>(<span>Array</span>.<span>isArray</span>(right) ? right : [right]),
<span>default</span>: <span>() =></span> [],
},
<span>cypher</span>: <span>null</span>,
<span>context</span>: <span>null</span>,
<span>answer</span>: <span>null</span>,
}
<span>function</span> <span>userQuery</span>(<span>state</span>) {
<span>const</span> last = state.<span>messages</span>[state.<span>messages</span>.<span>length</span> - <span>1</span>]
<span>return</span> last.<span>content</span>
}
<span>// ----------------------</span>
<span>// 步骤1:生成 Cypher</span>
<span>// ----------------------</span>
<span>async</span> <span>function</span> <span>generateCypher</span>(<span>state</span>) {
<span>const</span> prompt = <span>`
你是一个专业的 Neo4j Cypher 生成器。
严格按照下面的结构生成正确语句,只返回纯 Cypher 代码,不要任何解释、不要标点、不要 markdown。
节点:
- Product: 奶茶产品
- Ingredient: 配料
- Type: 奶茶类型
- Method: 制作工艺
- People: 适合人群
关系方向(必须严格遵守):
- (Product)-[:属于]->(Type)
- (Product)-[:包含]->(Ingredient)
- (Product)-[:适合]->(People)
- (Ingredient)-[:使用]->(Method)
规则:
1. 关系方向绝对不能反
2. 多跳查询请使用多个 MATCH,不要连错路径
3. 只返回最终可运行的 Cypher 语句
用户问题:<span>${userQuery(state)}</span>
`</span>
<span>const</span> res = <span>await</span> llm.<span>invoke</span>([<span>new</span> <span>HumanMessage</span>(prompt)])
<span>return</span> { <span>cypher</span>: res.<span>content</span> }
}
<span>// ----------------------</span>
<span>// 步骤2:执行图查询</span>
<span>// ----------------------</span>
<span>async</span> <span>function</span> <span>executeGraphQuery</span>(<span>state</span>) {
<span>try</span> {
<span>const</span> res = <span>await</span> graph.<span>query</span>(state.<span>cypher</span>)
<span>return</span> { <span>context</span>: <span>JSON</span>.<span>stringify</span>(res) }
} <span>catch</span> (e) {
<span>return</span> { <span>context</span>: <span>'未查询到相关知识'</span> }
}
}
<span>// ----------------------</span>
<span>// 步骤3:生成答案</span>
<span>// ----------------------</span>
<span>async</span> <span>function</span> <span>generateAnswer</span>(<span>state</span>) {
<span>const</span> prompt = <span>`
你是奶茶专家,根据下方「检索结果」回答用户问题;检索结果为空或不足时简要说明无法从图谱得到答案,不要编造。
回答要求:
- 直接列出事实,不要推断图谱里未出现的配料(如水、冰、添加剂等)。
检索结果:<span>${state.context}</span>
用户问题:<span>${userQuery(state)}</span>
`</span>
<span>const</span> res = <span>await</span> llm.<span>invoke</span>([<span>new</span> <span>HumanMessage</span>(prompt)])
<span>return</span> { <span>answer</span>: res.<span>content</span> }
}
<span>// ----------------------</span>
<span>// 构建 LangGraph 工作流</span>
<span>// ----------------------</span>
<span>const</span> workflow = <span>new</span> <span>StateGraph</span>({ <span>channels</span>: state })
.<span>addNode</span>(<span>'generateCypher'</span>, generateCypher)
.<span>addNode</span>(<span>'executeGraph'</span>, executeGraphQuery)
.<span>addNode</span>(<span>'generateAnswer'</span>, generateAnswer)
.<span>addEdge</span>(<span>START</span>, <span>'generateCypher'</span>)
.<span>addEdge</span>(<span>'generateCypher'</span>, <span>'executeGraph'</span>)
.<span>addEdge</span>(<span>'executeGraph'</span>, <span>'generateAnswer'</span>)
.<span>addEdge</span>(<span>'generateAnswer'</span>, <span>END</span>)
<span>const</span> app = workflow.<span>compile</span>()
<span>async</span> <span>function</span> <span>printWorkflowMermaid</span>(<span></span>) {
<span>const</span> drawable = <span>await</span> app.<span>getGraphAsync</span>()
<span>const</span> mermaid = drawable.<span>drawMermaid</span>({ <span>withStyles</span>: <span>true</span> })
<span>console</span>.<span>log</span>(<span>'--- LangGraph 工作流 (Mermaid) ---'</span>)
<span>console</span>.<span>log</span>(mermaid)
<span>console</span>.<span>log</span>(<span>'-----------------------------------------------------------'</span>)
}
<span>// ----------------------</span>
<span>// 运行 GraphRAG</span>
<span>// ----------------------</span>
<span>async</span> <span>function</span> <span>runGraphRAG</span>(<span>question</span>) {
<span>const</span> res = <span>await</span> app.<span>invoke</span>({
<span>messages</span>: [<span>new</span> <span>HumanMessage</span>(question)],
})
<span>console</span>.<span>log</span>(<span>'======================================'</span>)
<span>console</span>.<span>log</span>(<span>'用户问题:'</span>, question)
<span>console</span>.<span>log</span>(<span>'生成 Cypher:'</span>, res.<span>cypher</span>)
<span>console</span>.<span>log</span>(<span>'检索结果:'</span>, res.<span>context</span>)
<span>console</span>.<span>log</span>(<span>'最终回答:'</span>, res.<span>answer</span>)
<span>console</span>.<span>log</span>(<span>'======================================'</span>)
}
<span>// ======================</span>
<span>// 测试</span>
<span>// ======================</span>
;(<span>async</span> () => {
<span>await</span> <span>printWorkflowMermaid</span>()
<span>await</span> <span>Promise</span>.<span>all</span>([
<span>runGraphRAG</span>(<span>'我们这款珍珠奶茶有哪些配料?'</span>),
<span>runGraphRAG</span>(<span>'台式奶茶的饮品都有哪些配料?'</span>),
<span>runGraphRAG</span>(<span>'珍珠奶茶适合哪些人群饮用?'</span>),
])
})().<span>catch</span>(<span>console</span>.<span>error</span>)
在企业级项目里面,neo4j是在ES,milvus检索的平级使用的,就是他们三个一起搜索,提高RAG文档片段的准确性。因为给大模型的文档越准确,他的回复就越接近正确答案。
四.比较Milvus,ES,neo4j
Milvus 向量语义检索
适合场景
- 用户提问没有明确关键词,是自然语言大白话;
- 需要语义相似、意思相近匹配,不是字面一样;
- 模糊查询、泛化查询、推荐类场景;
- 非结构化文档:笔记、手册、文章、FAQ 模糊问答。
不懂关键词、只看意思相近,交给 Milvus。
ElasticSearch BM25 关键词检索
适合场景
- 用户有明确专有名词、专业术语、编号、文件名;
- 需要精准分词、字面命中、高亮匹配;
- 官方文档、规章条款、接口文档、目录检索;
- 过滤、排序、时间筛选、字段精准匹配。
要精准匹配关键词、专业名词、固定术语,交给 ES。
Neo4j 知识图谱检索(GraphRAG) 适合场景
- 需要实体关联、关系查询、多跳推理;
- 要查「A 和 B 什么关系、A 包含哪些、A 属于哪类」;
- 层级结构、分类体系、上下游、从属、配料、品类等链路查询;
- 传统检索给的是零散文本,需要逻辑推理、脉络梳理的场景。
要查关系、层级、脉络、多跳推理,交给知识图谱。
三者短板刚好互补:
Milvus: 擅长语义模糊匹配,但没有结构、不懂关系
ES: 擅长关键词精准命中、分词倒排、过滤筛选,但也只是文本孤岛
Neo4j: 擅长实体关联、多跳推理、层级脉络,但不擅长模糊语义、全文海量文档检索
Neo4j 加持的 GraphRAG 能补上传统 RAG 缺失的关联推理能力,适合需要多跳查询、实体关系挖掘的企业知识库场景,与 ES、Milvus 组合可显著提升检索准确性,是构建 AI Agent 记忆与推理层的重要组件。