ai agent -- graphRAG 之 Neo4j

文章来源声明: 原文作者:snow来了; 来源站点:掘金; 原文链接:https://juejin.cn/post/7688993872608903203; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

Neo4j 加持的 GraphRAG 能补上传统 RAG 缺失的关联推理能力,适合需要多跳查询、实体关系挖掘的企业知识库场景,与 ES、Milvus 组合可显著提升检索准确性,是构建 AI Agent 记忆与推理层的重要组件。

一. graphRAG 是什么? ----------------

GraphRAG(Graph Retrieval‑Augmented Generation),微软研究院 2024 年提出,是知识图谱 + RAG 检索增强生成的高级方案Microsoft ...

下图就是一个典型的传统RAG解决方案:利用ES 和 milvus实现数据检索,然后合并去重,之后利用重排模型将RAG到的文档全部重新排序,然后将文档交给大模型搜索答案。

image.png

上图传统RAG有个问题:无法捕捉数据之间的关联关系,只能实现“单点式”检索,难以应对需要挖掘数据内在逻辑、关联链路的场景。

比如我搜索奶茶,ES 和 milvus 只去搜索奶茶,不会考虑奶茶的原料,商店,口味,填料比如珍珠,椰果等等信息。

如果我想要知道货物的来龙去脉的时候,ES 和 milvus 就会显得很局促,不能胜任这份工作。此时我们就想到了neo4j 工具。

由neo4j 加持的RAG 就叫GraphRAG ,

GraphRAG 会先将非结构化数据中的实体、关系提取出来,用 Neo4j 这样的图数据库构建知识图谱,再结合向量检索的语义优势,实现“图谱关联+语义匹配”的双重检索,既能找到语义相近的内容,又能顺着知识图谱的关联链路,完成跨文档、多步骤的复杂推理,让 RAG 生成的答案更精准、更具可解释性。

image.png

二.neo4j是什么?

Neo4j 是一个图数据库——数据不是存在表里,而是存成「点」和「线」。点就是实体(芋圆、木薯淀粉、泰国),线就是它们之间的关系(芋圆-主要成分是-木薯淀粉)。

和mysql比较

image.png

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语句,添加节点和连接。

image.png

在红框里面写入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: 擅长实体关联、多跳推理、层级脉络,但不擅长模糊语义、全文海量文档检索