Skip to content

第 21 章 · Agent 知识图谱:让 LLM 看见关系 07-09

本章观点

前面 19-20 章我们讲的高级模式都建立在同一个假设上:Agent 通过工具调用拿到外部信息,再基于这些信息推理。但有一个隐性瓶颈--LLM 每次拿到的只是文本片段,它不知道这些片段之间有什么关系。

吴恩达 Agentic AI 课程(Bilibili BV1DfrdByE2H,p32-p34)提出一种更强的结构:

用知识图谱把结构化和非结构化数据统一成图结构:节点表示实体,边表示关系。Agent 检索时不只拿文本块,还拿相关实体和关系,让 LLM 在结构化上下文里推理。

这个模式对应本教程"四权模型"里的状态权:原本状态只是对话历史和工具结果,现在状态包含一个可查询的图结构,关系是第一等公民。

本章会讲清楚四件事:

  1. 知识图谱比朴素 RAG 强在哪里(什么时候值得用)。
  2. 三层图结构:领域图(结构化数据)+ 词汇图(原始文本块)+ 主题图(抽取的实体关系),三者怎么连接。
  3. 多 Agent 系统怎么构建知识图谱:用户意图 / 文件建议 / Schema 提案 / 知识提取计划 / 执行工具,五个代理的分工。
  4. GraphRAG 的检索流程:从用户问题到图查询到结构化上下文。

21.1 为什么需要知识图谱

21.1.1 朴素 RAG 的瓶颈

第 5 章 Context Engineering 讲过朴素 RAG:把文档切块 -> 向量化 -> 用户问问题时算相似度 -> 拿最相关的 K 个块塞进 context。

这个模式在简单问答上够用,但有几类问题它答不好:

问题 1:关系推理

用户:和 ABK 购买相同产品的人,还买了什么他没买的?

朴素 RAG:
  -> 向量相似度找到 ABK 的购买记录块
  -> 找到几个其他客户的购买记录块
  -> LLM 拼命推理:谁的购买和 ABK 重合?重合的人还买了什么?
  -> 错了,因为 LLM 没法在 context 里做多跳 join

这类问题需要"沿关系路径走多跳",向量相似度找不到这种结构。

问题 2:跨数据源关联

用户:客户投诉里提到的"桌子腿部松动",是哪个供应商的哪个部件?

数据源:
  - CSV:产品 / 部件 / 供应商(结构化)
  - 评论:客户投诉文本(非结构化)

朴素 RAG:
  -> 只能检索文本块
  -> 找到"桌子腿部松动"的评论
  -> 但不知道这个桌子对应 CSV 里哪个产品、哪个部件、哪个供应商

跨数据源关联需要把结构化和非结构化数据连起来,朴素 RAG 做不到。

问题 3:可解释性

用户:你为什么说这个产品有问题?

朴素 RAG:
  -> 返回一段相似文本:"有客户反馈这款产品存在松动问题"
  -> 但 LLM 怎么从这段文本得出"产品有问题"的结论?黑箱

知识图谱可以显式给出推理路径:评论 -> 提到 -> 产品 -> 包含 -> 部件 -> 由...供应 -> 供应商。每一步都可追溯。

21.1.2 知识图谱的核心思想

知识图谱把信息组织成图:

  • 节点(Node):实体,如人、产品、评论、文档、部件、供应商
  • 边(Edge):关系,如"购买了"、"提到了"、"属于"、"由供应商提供"
  • 属性(Property):节点和边都可以带属性,如用户 ID、产品名、购买时间
text
(Person: ABK) -[PURCHASED]-> (Product: 桌子A) -[HAS_COMPONENT]-> (Component: 桌腿)
                                                                |
                                                       [SUPPLIED_BY]
                                                                |
                                                         (Supplier: 厂家X)

这种结构让关系从"隐藏在文本里"变成"可查询的第一等对象"。

21.1.3 关系型 vs 图数据库

为什么不用关系型数据库(SQL)?

SQL 的局限

sql
-- 找和 ABK 购买相同产品的人还买了什么
SELECT DISTINCT p2.product_id
FROM purchases p1
JOIN purchases p2 ON p1.product_id = p2.product_id AND p1.person_id != p2.person_id
LEFT JOIN purchases p3 ON p3.person_id = 'ABK' AND p3.product_id = p2.product_id
WHERE p1.person_id = 'ABK' AND p3.product_id IS NULL;

这种多表 join 在 SQL 里写起来很绕,而且每加一跳就要多一个 join。3-4 跳以后 SQL 几乎不可读。

图数据库(Neo4j / Cypher)

cypher
// 同样的查询,用 Cypher
MATCH (a:Person {id: 'ABK'})-[:PURCHASED]->(p:Product)<-[:PURCHASED]-(b:Person)
MATCH (b)-[:PURCHASED]->(rec:Product)
WHERE NOT (a)-[:PURCHASED]->(rec)
RETURN DISTINCT rec;

Cypher 用箭头直接描述关系路径,加跳只是多写一段 ->,可读性强。

选型

  • 关系简单、查询固定 -> SQL 够用
  • 关系复杂、需要多跳推理、可变查询路径 -> 图数据库
  • Agent + 知识检索 -> 图数据库(Agent 不知道查询路径,图数据库支持任意路径)

21.2 三层图结构

吴恩达课程(p33)提出一个重要洞察:知识图谱不是一坨,而是三层结构。

text
┌─────────────────────────────────────────┐
│  领域图 (Domain Graph)                   │
│  - 结构化数据:CSV / 数据库表             │
│  - 节点:产品 / 订单 / 客户 / 供应商       │
│  - 边:购买 / 属于 / 由...供应             │
└─────────────────────────────────────────┘
                  ↕ (连接)
┌─────────────────────────────────────────┐
│  词汇图 (Lexical Graph)                  │
│  - 原始文本块                            │
│  - 节点:DocumentChunk                   │
│  - 属性:text / embedding                │
│  - 边:NEXT_CHUNK / SAME_DOC             │
└─────────────────────────────────────────┘
                  ↕ (连接)
┌─────────────────────────────────────────┐
│  主题图 (Topic Graph)                    │
│  - 从文本块抽取的实体和关系                │
│  - 节点:实体(人 / 产品 / 问题 / 部件)   │
│  - 边:提到 / 描述 / 关联                  │
└─────────────────────────────────────────┘

21.2.1 领域图(Domain Graph)

来源:结构化数据(CSV、数据库表、JSON 配置)

构建方式:把表 / 字段 / 记录直接映射成图结构。

text
products.csv:
  id, name, category, price
  1, 桌子A, 家具, 200
  2, 椅子B, 家具, 80

映射成图:
  (Product:1 {name: "桌子A", category: "家具", price: 200})
  (Product:2 {name: "椅子B", category: "家具", price: 80})

suppliers.csv:
  supplier_id, name, component_id
  S1, 厂家X, C1

关系:
  (Component:C1) <-[:SUPPLIED_BY]- (Supplier:S1 {name: "厂家X"})

特点

  • 数据是"已知事实",结构稳定
  • 实体类型从 schema 直接来
  • 关系类型从外键 / 关联表来
  • 抽取不需要 LLM,纯 ETL

21.2.2 词汇图(Lexical Graph)

来源:原始文本块(文档、评论、网页、报告)

构建方式

  1. 文档切块(如每 500 字一块)
  2. 每块算 embedding
  3. 块之间建立关系(同一文档 / 相邻块 / 相似块)
text
DocumentChunk: chunk_001
  text: "我买的桌子A用了两个月,桌腿就松了..."
  embedding: [0.12, -0.34, ...]
  doc_id: "review_123"
  position: 0

DocumentChunk: chunk_002
  text: "厂家客服说要换整个桌腿,但费用自理..."
  embedding: [0.15, -0.31, ...]
  doc_id: "review_123"
  position: 1

关系:
  (chunk_001) -[:NEXT_CHUNK]-> (chunk_002)
  (chunk_001) -[:SAME_DOC]-> (chunk_002)

特点

  • 保留原始文本,不丢失细节
  • 支持向量相似度检索(找最相似的块)
  • 块之间的关系让"找同一段上下文"更容易

21.2.3 主题图(Topic Graph)

来源:从词汇图的文本块里抽取的实体和关系

构建方式:用 LLM 从每个文本块抽取实体(人 / 产品 / 问题 / 部件 / 事件)和关系(提到 / 描述 / 关联)。

text
从 chunk_001 "我买的桌子A用了两个月,桌腿就松了..." 抽取:

实体:
  (Product: 桌子A)  ← 注意:这个会连接到领域图里的 Product:1
  (Component: 桌腿)
  (Issue: 松动)

关系:
  (chunk_001) -[:MENTIONS]-> (Product: 桌子A)
  (Product: 桌子A) -[:HAS_ISSUE]-> (Issue: 松动)
  (Issue: 松动) -[:AFFECTS]-> (Component: 桌腿)

特点

  • 实体是从非结构化文本"长"出来的
  • 关系是 LLM 推理出来的
  • 抽取有噪声,需要清洗和合并

21.2.4 三层怎么连接

关键设计:主题图里的实体要连接回领域图。

text
评论 chunk_001 "桌子A 桌腿松动"
  ↓ 抽取
主题图:(Product: 桌子A) -[:HAS_ISSUE]-> (Issue: 松动)
                                  ↓ 实体对齐
领域图:(Product:1 {name: "桌子A"})

         (Component: 桌腿) <-[:SUPPLIED_BY]- (Supplier: 厂家X)

连接后,Agent 可以做这种推理:

用户:桌腿松动是哪个供应商的问题?

GraphRAG 检索:
  1. 在词汇图找"桌腿松动"相关文本块 -> chunk_001
  2. 在主题图找 chunk_001 提到的实体 -> Product:1, Issue: 松动, Component: 桌腿
  3. 沿领域图扩展 -> Component: 桌腿 的供应商是 Supplier: 厂家X
  4. 返回:文本块 + 实体 + 关系 + 推理路径

LLM 答案:
  根据 review_123 的反馈,桌子A 存在桌腿松动问题。
  桌腿由厂家X供应,可能是该批次部件质量问题。
  推理路径:评论 -> 桌子A -> 桌腿 -> 厂家X

这种"文本证据 + 结构化事实 + 可追溯路径"的组合,是知识图谱相对于朴素 RAG 的核心优势。

21.3 GraphRAG 检索流程

知识图谱建好之后,Agent 怎么用它回答问题?这个过程叫 GraphRAG。

21.3.1 朴素 RAG vs GraphRAG

步骤朴素 RAGGraphRAG
1用户问题向量化用户问题向量化 + 实体抽取
2找相似文本块找相似文本块 + 找相关实体
3塞进 context沿图关系扩展(多跳)
4LLM 答题文本块 + 实体 + 关系 + 路径塞进 context
5答案LLM 答题(带可追溯证据)

21.3.2 GraphRAG 的 5 步流程

text
1. 实体抽取
   用户问题:"桌腿松动是哪个供应商的问题?"
   抽取实体:桌腿、松动、供应商

2. 节点定位
   在图里找到这些实体对应的节点
   - 桌腿 -> Component: 桌腿
   - 松动 -> Issue: 松动
   - 供应商 -> (Schema 类型,不是具体节点)

3. 关系扩展(多跳)
   从 Component: 桌腿 出发,沿关系走 N 跳
   - 桌腿 <-[:SUPPLIED_BY]- Supplier: 厂家X
   - 桌腿 <-[:HAS_COMPONENT]- Product:1
   - Product:1 <-[:MENTIONS]- chunk_001
   - chunk_001 -[:MENTIONS]-> Issue: 松动

4. 子图收集
   把扩展到的节点和边打包成子图
   - 节点:chunk_001, Product:1, Component: 桌腿, Supplier: 厂家X, Issue: 松动
   - 边:MENTIONS, HAS_COMPONENT, SUPPLIED_BY, HAS_ISSUE

5. 上下文组装
   把子图转成文本描述,连同原始文本块一起塞进 LLM context
   "相关文本:chunk_001 = '我买的桌子A...'"
   "相关实体:Product:1 (桌子A), Component: 桌腿, Supplier: 厂家X"
   "关系路径:chunk_001 -MENTIONS-> Product:1 -HAS_COMPONENT-> 桌腿 <-SUPPLIED_BY- 厂家X"
   "问题:桌腿松动是哪个供应商的问题?"

LLM 基于结构化上下文答题,并保留可追溯路径。

21.3.3 检索的工程细节

实体抽取用什么模型?

  • 简单场景:用 LLM 直接抽取(少样本 prompt)
  • 生产场景:fine-tuned NER 模型 + LLM 兜底
  • 极致性能:专门的实体链接模型(如 spaCy + 自定义规则)

多跳扩展几跳?

  • 1 跳:找直接相关实体,覆盖窄但准确
  • 2-3 跳:覆盖广,但容易引入噪声
  • 工程做法:默认 2 跳,关键场景 3 跳,避免 >3 跳

子图怎么塞进 context?

不要直接塞 JSON(太大且 LLM 难读),用自然语言描述:

text
[相关实体]
- Product: 桌子A (id=1)
- Component: 桌腿
- Supplier: 厂家X
- Issue: 松动

[关系]
- 桌子A 包含 桌腿
- 桌腿 由 厂家X 供应
- 桌子A 存在 松动 问题

[原始文本]
"我买的桌子A用了两个月,桌腿就松了..."

这种格式 token 紧凑、LLM 易读,且保留了图结构信息。

21.4 多 Agent 构建知识图谱

知识图谱的构建本身就是个复杂任务,吴恩达课程(p34)提出用多 Agent 系统来构建。这正好把第 20 章的多 Agent 模式应用到一个真实场景。

21.4.1 为什么构建图谱需要多 Agent

构建知识图谱涉及多个不同技能的任务:

  • 理解用户业务目标(要回答什么问题)
  • 选择相关数据文件(CSV / 文档 / 评论)
  • 设计图 schema(节点类型、关系类型)
  • 从非结构化文本抽取实体
  • 把抽取结果写入图数据库
  • 验证图谱质量

这些任务技能差异大,让一个 Agent 全做容易顾此失彼。多 Agent 拆分让每个 Agent 专注一个环节。

21.4.2 五个代理的分工

text
┌─────────────────────────────────────────────────┐
│  顶层对话代理(不直接构建图谱,只引导用户)        │
└─────────────────────────────────────────────────┘

       ┌───────────────┴───────────────┐
       ↓                               ↓
┌──────────────┐              ┌──────────────┐
│ 用户意图代理  │              │ 文件建议代理  │
└──────────────┘              └──────────────┘
       ↓                               ↓
       └───────────────┬───────────────┘

       ┌───────────────┴───────────────┐
       ↓                               ↓
┌──────────────────┐         ┌─────────────────────┐
│ Schema 提案代理    │         │ 知识提取计划代理     │
│ (结构化数据)       │         │ (非结构化数据)       │
│ 内部:提出者+批评者│         │                     │
└──────────────────┘         └─────────────────────┘
       ↓                               ↓
       └───────────────┬───────────────┘

              ┌────────────────┐
              │ 图构建执行工具   │
              │ (代码执行,非LLM) │
              └────────────────┘

              ┌────────────────┐
              │ GraphRAG 代理   │
              │ (基于图谱答题)  │
              └────────────────┘

21.4.3 各代理的职责

1. 顶层对话代理

  • 职责:介绍流程、引导用户选择工作流、解释阶段目标
  • 不直接构建图谱
  • 类似第 20 章的"经理代理",但更偏客服角色

2. 用户意图代理

  • 输入:用户的业务问题
  • 输出:结构化目标说明、成功标准、待回答问题
  • 关键:澄清"为什么要构建图谱"、"希望回答什么问题"、"要支持哪类分析"

3. 文件建议代理

  • 输入:用户目标 + 可用文件列表
  • 输出:推荐文件、推荐原因、需要用户确认的文件清单
  • 关键:不要让用户自己选所有文件,代理应该基于目标推荐

4. Schema 提案代理(结构化数据)

  • 输入:用户目标 + 已确认的 CSV / 表格文件
  • 输出:节点类型、关系类型、属性、图构建计划
  • 关键设计:内部用"提出者 + 批评者"双代理模式(一个提 schema,一个批评改进,循环优化)
text
提出者:基于 products.csv 和 suppliers.csv,建议节点:
  - Product (id, name, category, price)
  - Supplier (id, name)
  关系:Product -[:SUPPLIED_BY]-> Supplier

批评者:缺了 Component 节点。products.csv 里有 component_id 字段,
说明产品和部件是分离的,应该有:
  - Component (id, name)
  - Product -[:HAS_COMPONENT]-> Component
  - Component -[:SUPPLIED_BY]-> Supplier

提出者(修改后):接受批评,加入 Component 节点...

5. 知识提取计划代理(非结构化数据)

  • 输入:用户目标 + 已确认的 Markdown / 文档文件
  • 输出:可抽取实体类型、事实类型、文本块处理策略
  • 关键:这个代理只规划"抽什么",不直接抽。抽取由执行工具完成
text
基于用户目标"分析客户投诉根因"和文件 review_123.md:

实体类型:
  - Product(产品名)
  - Component(部件名)
  - Issue(问题描述:松动、破损、变色)
  - Severity(严重程度)

事实类型:
  - MENTIONS:文本块提到某产品
  - HAS_ISSUE:产品存在某问题
  - AFFECTS:问题影响某部件

文本块处理策略:
  - 块大小:500 字
  - 重叠:50 字
  - 抽取 prompt:见 templates/extract_v1.md

6. 图构建执行工具

  • 输入:图构建计划 + 知识提取计划 + 数据文件
  • 输出:领域图、词汇图、主题图,以及它们之间的连接
  • 关键:这是代码执行,不是 LLM 调用。LLM 只规划,代码执行
python
def build_graph(schema_plan, extraction_plan, files):
    # 1. 构建领域图(结构化数据)
    for csv_file in files.structured:
        df = pd.read_csv(csv_file)
        for _, row in df.iterrows():
            create_domain_nodes(row, schema_plan)
            create_domain_relations(row, schema_plan)

    # 2. 构建词汇图(文本切块)
    for doc_file in files.unstructured:
        chunks = chunk_text(read(doc_file), size=500, overlap=50)
        for i, chunk in enumerate(chunks):
            create_chunk_node(chunk, doc_file, i)
            if i > 0:
                link_to_prev_chunk()

    # 3. 构建主题图(LLM 抽取实体)
    for chunk in all_chunks:
        entities = llm_extract(chunk, extraction_plan)
        for entity in entities:
            topic_node = create_topic_node(entity)
            link_chunk_to_entity(chunk, topic_node)
            # 实体对齐:把主题图节点连接到领域图节点
            align_to_domain(topic_node)

    # 4. 生成向量嵌入
    for chunk in all_chunks:
        chunk.embedding = embed(chunk.text)

7. GraphRAG 代理

  • 输入:用户问题 + 已构建图谱
  • 输出:基于图谱和文本证据的回答
  • 关键:这个代理是图谱的"用户",不是构建者

21.4.4 计划与执行的分离

吴恩达课程强调一个关键设计原则:计划(planning)和执行(execution)的分离

Schema 提案代理和知识提取计划代理只输出规则和计划,不直接构建图。真正构建由代码执行。

为什么这个分离重要?

  1. 可审查:计划可以给人看,"我们要抽这些实体,建这些关系"是不是合理?
  2. 可复现:同样的计划 + 同样的数据,应该产生同样的图
  3. 可调试:图建错了,可以追溯到是计划错还是执行错
  4. 可重用:同一个 schema 计划可以用到不同数据集

这和第 19 章 19.2 节"规划循环和执行循环必须分离"是同一个原则。

21.4.5 用户检查点

多 Agent 构建图谱的流程中,需要在关键转换处让用户确认:

text
[检查点 1] 用户意图确认
  代理:你的目标是 X,对吗?
  用户:对 / 不对,应该是 Y

[检查点 2] 文件选择确认
  代理:基于你的目标,推荐这 3 个文件,对吗?
  用户:对 / 还要加这个文件

[检查点 3] Schema 确认
  代理:建议节点类型 X、关系类型 Y,对吗?
  用户:对 / 缺了 Z

[检查点 4] 知识提取计划确认
  代理:建议抽取实体类型 X、关系类型 Y,对吗?
  用户:对 / 不需要 X

检查点的作用:避免系统误解方向后,构建了一个错的图,浪费成本。这是第 8 章 8.5 节"越权执行"防御在多 Agent 系统的延伸。

21.5 一个最小知识图谱示例

用 Neo4j + Python 写一个最小的知识图谱构建 + GraphRAG 检索示例。

python
from neo4j import GraphDatabase
import anthropic

client = anthropic.Anthropic()
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))

# === 阶段 1:构建领域图(结构化数据)===
def build_domain_graph(tx):
    # 创建产品
    tx.run("MERGE (p:Product {id: 1, name: '桌子A', category: '家具', price: 200})")
    tx.run("MERGE (c:Component {id: 'C1', name: '桌腿'})")
    tx.run("MERGE (s:Supplier {id: 'S1', name: '厂家X'})")
    # 创建关系
    tx.run("MATCH (p:Product {id:1}), (c:Component {id:'C1'}) MERGE (p)-[:HAS_COMPONENT]->(c)")
    tx.run("MATCH (c:Component {id:'C1'}), (s:Supplier {id:'S1'}) MERGE (c)-[:SUPPLIED_BY]->(s)")

# === 阶段 2:构建词汇图(文本块)===
def build_lexical_graph(tx):
    chunk = "我买的桌子A用了两个月,桌腿就松了,质量很差。"
    tx.run(
        "MERGE (c:DocumentChunk {id: 'chunk_001', text: $text, doc_id: 'review_123'})",
        text=chunk
    )

# === 阶段 3:构建主题图(LLM 抽取实体 + 对齐到领域图)===
EXTRACT_PROMPT = """从以下文本抽取实体和关系,输出 JSON:

文本:{text}

实体类型:Product, Component, Issue
关系类型:MENTIONS, HAS_ISSUE, AFFECTS

输出格式:
{{
  "entities": [{{"type": "...", "name": "...", "domain_id": "对应领域图的 id,没有则 null"}}],
  "relations": [{{"subject": "...", "type": "...", "object": "..."}}]
}}
"""

def extract_and_build_topic_graph(tx, chunk_text, chunk_id):
    resp = client.messages.create(
        model="claude-sonnet-4-5-20250929",
        max_tokens=512,
        messages=[{"role": "user",
                   "content": EXTRACT_PROMPT.format(text=chunk_text)}]
    )
    import json
    data = json.loads(resp.content[0].text)

    # 创建主题图节点
    for ent in data["entities"]:
        if ent["type"] == "Product":
            tx.run("""
                MERGE (t:Topic:Product {name: $name})
                WITH t
                MATCH (d:Product {id: $domain_id}) MERGE (t)-[:ALIASED_TO]->(d)
            """, name=ent["name"], domain_id=ent.get("domain_id", 1))
        elif ent["type"] == "Issue":
            tx.run("MERGE (t:Topic:Issue {name: $name})", name=ent["name"])
        elif ent["type"] == "Component":
            tx.run("MERGE (t:Topic:Component {name: $name})", name=ent["name"])

    # 创建主题图关系 + chunk -> 实体
    for rel in data["relations"]:
        tx.run("""
            MATCH (s:Topic {name: $subj}), (o:Topic {name: $obj})
            MERGE (s)-[r:`%s`]->(o)
        """ % rel["type"], subj=rel["subject"], obj=rel["object"])

    # chunk 提到所有实体
    tx.run("""
        MATCH (c:DocumentChunk {id: $chunk_id}), (t:Topic)
        WHERE t.name IN $entity_names
        MERGE (c)-[:MENTIONS]->(t)
    """, chunk_id=chunk_id, entity_names=[e["name"] for e in data["entities"]])

# === 阶段 4:GraphRAG 检索 ===
def graph_rag(question: str) -> str:
    # 简化版:直接做 Cypher 查询扩展
    # 实际系统应该先用 LLM 抽取问题里的实体,再生成 Cypher
    with driver.session() as session:
        result = session.run("""
            MATCH (c:DocumentChunk)-[:MENTIONS]->(t:Topic)
            OPTIONAL MATCH (t)-[:ALIASED_TO]->(d)
            OPTIONAL MATCH (d)-[:HAS_COMPONENT]->(comp:Component)
            OPTIONAL MATCH (comp)-[:SUPPLIED_BY]->(s:Supplier)
            RETURN c.text AS chunk_text,
                   collect(DISTINCT t.name) AS topics,
                   collect(DISTINCT s.name) AS suppliers
        """)

        context = []
        for record in result:
            context.append({
                "text": record["chunk_text"],
                "topics": record["topics"],
                "suppliers": record["suppliers"]
            })

    # 把结构化上下文塞给 LLM
    resp = client.messages.create(
        model="claude-sonnet-4-5-20250929",
        max_tokens=512,
        messages=[{"role": "user", "content":
            f"""基于以下结构化上下文回答问题。

上下文:
{context}

问题:{question}

请给出答案,并说明推理路径。"""}]
    )
    return resp.content[0].text

# === 跑起来 ===
with driver.session() as session:
    session.execute_write(build_domain_graph)
    session.execute_write(build_lexical_graph)
    session.execute_write(extract_and_build_topic_graph,
                          "我买的桌子A用了两个月,桌腿就松了,质量很差。",
                          "chunk_001")

print(graph_rag("桌腿松动是哪个供应商的问题?"))

预期输出

根据客户评论,桌子A 存在桌腿松动问题。

推理路径:
1. 评论 chunk_001 提到 桌子A 和 桌腿松动
2. 桌子A (Product:1) 包含 桌腿 (Component:C1)
3. 桌腿 (Component:C1) 由 厂家X (Supplier:S1) 供应

可能原因:厂家X 供应的桌腿部件质量问题。
建议:联系厂家X 核查该批次部件,考虑退货或更换供应商。

这个例子虽然简化,但完整展示了三层图结构 + GraphRAG 检索的核心流程。第 22 章会用 Google ADK 把这个流程封装成多 Agent 系统。

21.6 适用场景与反模式

适合知识图谱

场景为什么适合
产品投诉根因分析文本投诉 + 结构化产品/部件/供应商数据
客户支持客户历史 + 工单 + 产品信息
供应链追踪跨多个数据源追踪事件链路
合规审计需要可追溯的推理路径
医疗知识检索症状 + 疾病 + 药物 + 禁忌的复杂关系
金融风控实体关联、资金流向、可疑模式识别
企业知识库跨部门、跨系统的知识整合
推荐系统基于多跳关系的推荐

不适合知识图谱

反模式 1:数据关系简单

如果只是"产品列表 + 简单搜索",向量检索够用。知识图谱需要 schema 设计、抽取、清洗、图数据库维护,工程投入大。简单场景下朴素 RAG 更快。

反模式 2:一次性小实验

建一个知识图谱要:schema 设计 + 数据导入 + 实体抽取 + 实体对齐 + 图查询优化。如果只是跑一次的小实验,直接用 LLM 处理原始文本更划算。

反模式 3:合规风险高但 schema 不稳

金融、医疗这类高合规场景,schema 必须稳定可审计。如果业务还在快速变化,schema 频繁调整,图谱会一直在重构,没法真正用起来。

反模式 4:把所有文本塞进图

不是所有文本都值得抽实体。日志、聊天记录这类低信息密度文本,抽取会产生大量噪声节点。应该先选高价值文档(产品规格、客户反馈、合同条款)建图。

前人智慧 / Prior Art

本章把知识图谱讲成 Agent 的"结构化记忆",并用多 Agent 系统构建。这个思路来自微软 GraphRAG、Neo4j 工程实践和吴恩达课程的共同归纳。

Microsoft GraphRAG 07-09

Microsoft GraphRAG(2024)是知识图谱 + LLM 的里程碑项目。它的核心流程:

  1. TextUnits:把文档切成文本单元
  2. 实体抽取:LLM 从每个 TextUnit 抽取实体
  3. 关系抽取:LLM 抽取实体之间的关系
  4. 社区检测:用 Leiden 算法把图划分成社区
  5. 社区摘要:自底向上为每个社区生成摘要
  6. 查询时:全局查询用社区摘要,局部查询用图扩展

GraphRAG 的工程贡献是证明了图谱 + LLM 比朴素 RAG 在复杂问答上更准。在若干 benchmark 上,GraphRAG 在需要跨文档推理的问题上准确率显著高于朴素 RAG。

本章 21.3 节的 GraphRAG 流程是 Microsoft GraphRAG 的简化版。简化点:

  • 省略了社区检测(适合超大规模图谱,小图不需要)
  • 省略了社区摘要(适合全局查询,本章聚焦局部查询)
  • 加入了三层图结构(领域 + 词汇 + 主题),这是工业实践的扩展

Neo4j 的 GraphRAG 实践

Neo4j GraphRAG 文档是本章三层图结构的重要参考。Neo4j 工程团队提出:

  • Domain Graph:结构化业务数据
  • Lexical Graph:原始文本块及其关系
  • Topic Graph:从文本抽取的实体关系

三层连接后,既能用结构化查询(Cypher)也能用向量检索(embedding),还能做图扩展(multi-hop)。

本章 21.2 节的三层图结构直接来自 Neo4j 的工程实践。这种结构在产品投诉分析、供应链追踪、合规审计等场景已经被工业验证。

Cypher:声明式图查询语言

Cypher 是 Neo4j 的图查询语言,本章 21.1.3 节用了它的语法。Cypher 的设计哲学:

  • 模式优先:用 (node)-[rel]->(node) 描述要匹配的图模式
  • 声明式:说"我要什么",不说"怎么找"
  • 可视化友好:查询语句就是图的样子

Cypher 对比 SQL 的优势在多跳查询:SQL 要写多个 JOIN,Cypher 只要写一段路径。这种差异在 Agent 场景更重要--Agent 不知道查询路径,图数据库支持任意路径查询,SQL 必须预先定义 JOIN。

LLM 知识图谱抽取的研究脉络

用 LLM 从文本抽取实体和关系,这个方向的研究脉络:

  • 早期(2020 前):传统 NER 模型(spaCy / Stanford NER),只能抽预定义类型
  • 2022-2023:LLM zero-shot 抽取,能处理任意类型但精度有限
  • 2024:fine-tuned 抽取模型(GLiNER / OneKE),精度接近传统 NER 但灵活得多
  • 2025:Agent 化抽取,LLM 不仅抽取还做实体对齐、关系验证、schema 适配

本章 21.4 节的"知识提取计划代理"是 Agent 化抽取的体现:LLM 不直接抽取,而是规划"抽什么、怎么抽",由执行工具批量抽取。这种分离让抽取过程可审查、可复现。

Andrew Ng Agentic AI 课程的图谱模块 07-09

本章内容主要整理自吴恩达 Agentic AI 课程 Module 6(Bilibili BV1DfrdByE2H,p32-p34)。课程的三节递进:

  1. p32 Introduction:为什么需要 Agent + 知识图谱
  2. p33 什么是知识图谱:节点、边、属性、Cypher、三层图结构
  3. p34 多智能体系统的架构:五个代理的分工

课程的关键判断:"知识图谱不是把所有文本塞进去就结束,而是要先定义哪些实体和关系对业务目标有用。schema 定义得好,后续抽取、检索和推理才有方向"。这正是本章 21.4.4 "计划与执行分离"的来源。

课程还提到一个工程经验:多智能体在这里的价值是降低复杂度,不是为了炫技。每个代理的职责越清晰,越容易写好提示词、设计输出格式、插入用户确认点,也越容易在后续课程中替换或优化其中一环。这支撑了本章 21.4 的五代理分工设计。

GraphRAG vs Agentic RAG

第 5 章 Context Engineering 讲过 Agentic RAG:Agent 自己决定查什么、查几次、怎么综合。GraphRAG 和 Agentic RAG 的关系:

Agentic RAGGraphRAG
数据结构向量库图数据库
检索方式多轮向量搜索图扩展 + 向量搜索
适合探索性、开放式问答关系推理、跨数据源
工程成本中(向量库 + Agent loop)高(schema + 抽取 + 图数据库)

两者不互斥:GraphRAG 可以包在 Agentic RAG 里,作为 Agent 的一个工具。Agent 决定什么时候用向量检索,什么时候用图扩展。这是生产系统的常见架构。

实体对齐(Entity Alignment)的工程难点

本章 21.4.3 节提到"主题图节点对齐到领域图节点"。这是知识图谱工程最难的环节之一。

问题:LLM 抽取的实体是文本字符串("桌子A"),领域图里的实体是结构化 ID(Product:1)。怎么知道"桌子A" = Product:1?

工程做法

  1. 规则匹配:字符串相等 / 模糊匹配 / 别名表
  2. embedding 匹配:算两个实体的 embedding 相似度
  3. LLM 判断:让 LLM 判断两个实体是不是同一个
  4. 混合:先用规则 + embedding 候选,再让 LLM 确认

实体对齐的精度直接决定图谱质量。错的 对齐会让 Agent 推理出错误结论。生产系统应该把对齐结果给人审核,不要全自动。

知识图谱的演化:从人工到 Agent 化

知识图谱的构建方式演化:

阶段方式特点
1.0(2010 前)人工构建质量高,成本极高,不可扩展
2.0(2010-2020)规则 + NER中等成本,类型固定
3.0(2022-2024)LLM 抽取灵活,但精度有限
4.0(2025-)Agent 化构建多 Agent 协作,计划/执行分离

本章 21.4 节的多 Agent 构建属于 4.0 阶段。关键特征:

  • 不是"LLM 直接抽",而是"LLM 规划 + 代码执行"
  • 用户检查点保留人在环路
  • 计划可审查、可复现、可重用

这是知识图谱工程的前沿方向,也是 Agent 技术真正落地的高价值场景之一。

21.7 小结

  • 知识图谱把信息组织成图:节点是实体、边是关系、属性是细节。关系从"隐藏在文本里"变成"可查询的第一等对象"。
  • 三层图结构:领域图(结构化数据 ETL)+ 词汇图(文本块 + embedding)+ 主题图(LLM 抽取的实体关系)。三层连接让结构化和非结构化数据统一。
  • GraphRAG 检索:实体抽取 -> 节点定位 -> 多跳扩展 -> 子图收集 -> 上下文组装。比朴素 RAG 多了"关系路径"维度。
  • 多 Agent 构建图谱:顶层对话 / 用户意图 / 文件建议 / Schema 提案(提出者+批评者)/ 知识提取计划 / 执行工具 / GraphRAG。计划与执行分离,关键转换加用户检查点。
  • 适合:跨数据源关联、关系推理、可解释性要求高的场景。不适合:数据关系简单、一次性小实验、schema 不稳定、低信息密度文本。

下一章我们用 Google ADK 把本章的多 Agent 架构落地--从单 Agent 的 Agent / Runner / Session,到多 Agent 委派 + ToolContext 共享状态,跑通一个真实的多 Agent 系统。

happy ending, love you.

基于 CC BY-SA 4.0 发布