第 21 章 · Agent 知识图谱:让 LLM 看见关系 07-09
本章观点
前面 19-20 章我们讲的高级模式都建立在同一个假设上:Agent 通过工具调用拿到外部信息,再基于这些信息推理。但有一个隐性瓶颈--LLM 每次拿到的只是文本片段,它不知道这些片段之间有什么关系。
吴恩达 Agentic AI 课程(Bilibili BV1DfrdByE2H,p32-p34)提出一种更强的结构:
用知识图谱把结构化和非结构化数据统一成图结构:节点表示实体,边表示关系。Agent 检索时不只拿文本块,还拿相关实体和关系,让 LLM 在结构化上下文里推理。
这个模式对应本教程"四权模型"里的状态权:原本状态只是对话历史和工具结果,现在状态包含一个可查询的图结构,关系是第一等公民。
本章会讲清楚四件事:
- 知识图谱比朴素 RAG 强在哪里(什么时候值得用)。
- 三层图结构:领域图(结构化数据)+ 词汇图(原始文本块)+ 主题图(抽取的实体关系),三者怎么连接。
- 多 Agent 系统怎么构建知识图谱:用户意图 / 文件建议 / Schema 提案 / 知识提取计划 / 执行工具,五个代理的分工。
- 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、产品名、购买时间
(Person: ABK) -[PURCHASED]-> (Product: 桌子A) -[HAS_COMPONENT]-> (Component: 桌腿)
|
[SUPPLIED_BY]
|
(Supplier: 厂家X)这种结构让关系从"隐藏在文本里"变成"可查询的第一等对象"。
21.1.3 关系型 vs 图数据库
为什么不用关系型数据库(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
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)提出一个重要洞察:知识图谱不是一坨,而是三层结构。
┌─────────────────────────────────────────┐
│ 领域图 (Domain Graph) │
│ - 结构化数据:CSV / 数据库表 │
│ - 节点:产品 / 订单 / 客户 / 供应商 │
│ - 边:购买 / 属于 / 由...供应 │
└─────────────────────────────────────────┘
↕ (连接)
┌─────────────────────────────────────────┐
│ 词汇图 (Lexical Graph) │
│ - 原始文本块 │
│ - 节点:DocumentChunk │
│ - 属性:text / embedding │
│ - 边:NEXT_CHUNK / SAME_DOC │
└─────────────────────────────────────────┘
↕ (连接)
┌─────────────────────────────────────────┐
│ 主题图 (Topic Graph) │
│ - 从文本块抽取的实体和关系 │
│ - 节点:实体(人 / 产品 / 问题 / 部件) │
│ - 边:提到 / 描述 / 关联 │
└─────────────────────────────────────────┘21.2.1 领域图(Domain Graph)
来源:结构化数据(CSV、数据库表、JSON 配置)
构建方式:把表 / 字段 / 记录直接映射成图结构。
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)
来源:原始文本块(文档、评论、网页、报告)
构建方式:
- 文档切块(如每 500 字一块)
- 每块算 embedding
- 块之间建立关系(同一文档 / 相邻块 / 相似块)
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 从每个文本块抽取实体(人 / 产品 / 问题 / 部件 / 事件)和关系(提到 / 描述 / 关联)。
从 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 三层怎么连接
关键设计:主题图里的实体要连接回领域图。
评论 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
| 步骤 | 朴素 RAG | GraphRAG |
|---|---|---|
| 1 | 用户问题向量化 | 用户问题向量化 + 实体抽取 |
| 2 | 找相似文本块 | 找相似文本块 + 找相关实体 |
| 3 | 塞进 context | 沿图关系扩展(多跳) |
| 4 | LLM 答题 | 文本块 + 实体 + 关系 + 路径塞进 context |
| 5 | 答案 | LLM 答题(带可追溯证据) |
21.3.2 GraphRAG 的 5 步流程
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 难读),用自然语言描述:
[相关实体]
- 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 五个代理的分工
┌─────────────────────────────────────────────────┐
│ 顶层对话代理(不直接构建图谱,只引导用户) │
└─────────────────────────────────────────────────┘
↓
┌───────────────┴───────────────┐
↓ ↓
┌──────────────┐ ┌──────────────┐
│ 用户意图代理 │ │ 文件建议代理 │
└──────────────┘ └──────────────┘
↓ ↓
└───────────────┬───────────────┘
↓
┌───────────────┴───────────────┐
↓ ↓
┌──────────────────┐ ┌─────────────────────┐
│ Schema 提案代理 │ │ 知识提取计划代理 │
│ (结构化数据) │ │ (非结构化数据) │
│ 内部:提出者+批评者│ │ │
└──────────────────┘ └─────────────────────┘
↓ ↓
└───────────────┬───────────────┘
↓
┌────────────────┐
│ 图构建执行工具 │
│ (代码执行,非LLM) │
└────────────────┘
↓
┌────────────────┐
│ GraphRAG 代理 │
│ (基于图谱答题) │
└────────────────┘21.4.3 各代理的职责
1. 顶层对话代理
- 职责:介绍流程、引导用户选择工作流、解释阶段目标
- 不直接构建图谱
- 类似第 20 章的"经理代理",但更偏客服角色
2. 用户意图代理
- 输入:用户的业务问题
- 输出:结构化目标说明、成功标准、待回答问题
- 关键:澄清"为什么要构建图谱"、"希望回答什么问题"、"要支持哪类分析"
3. 文件建议代理
- 输入:用户目标 + 可用文件列表
- 输出:推荐文件、推荐原因、需要用户确认的文件清单
- 关键:不要让用户自己选所有文件,代理应该基于目标推荐
4. Schema 提案代理(结构化数据)
- 输入:用户目标 + 已确认的 CSV / 表格文件
- 输出:节点类型、关系类型、属性、图构建计划
- 关键设计:内部用"提出者 + 批评者"双代理模式(一个提 schema,一个批评改进,循环优化)
提出者:基于 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 / 文档文件
- 输出:可抽取实体类型、事实类型、文本块处理策略
- 关键:这个代理只规划"抽什么",不直接抽。抽取由执行工具完成
基于用户目标"分析客户投诉根因"和文件 review_123.md:
实体类型:
- Product(产品名)
- Component(部件名)
- Issue(问题描述:松动、破损、变色)
- Severity(严重程度)
事实类型:
- MENTIONS:文本块提到某产品
- HAS_ISSUE:产品存在某问题
- AFFECTS:问题影响某部件
文本块处理策略:
- 块大小:500 字
- 重叠:50 字
- 抽取 prompt:见 templates/extract_v1.md6. 图构建执行工具
- 输入:图构建计划 + 知识提取计划 + 数据文件
- 输出:领域图、词汇图、主题图,以及它们之间的连接
- 关键:这是代码执行,不是 LLM 调用。LLM 只规划,代码执行
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 提案代理和知识提取计划代理只输出规则和计划,不直接构建图。真正构建由代码执行。
为什么这个分离重要?
- 可审查:计划可以给人看,"我们要抽这些实体,建这些关系"是不是合理?
- 可复现:同样的计划 + 同样的数据,应该产生同样的图
- 可调试:图建错了,可以追溯到是计划错还是执行错
- 可重用:同一个 schema 计划可以用到不同数据集
这和第 19 章 19.2 节"规划循环和执行循环必须分离"是同一个原则。
21.4.5 用户检查点
多 Agent 构建图谱的流程中,需要在关键转换处让用户确认:
[检查点 1] 用户意图确认
代理:你的目标是 X,对吗?
用户:对 / 不对,应该是 Y
[检查点 2] 文件选择确认
代理:基于你的目标,推荐这 3 个文件,对吗?
用户:对 / 还要加这个文件
[检查点 3] Schema 确认
代理:建议节点类型 X、关系类型 Y,对吗?
用户:对 / 缺了 Z
[检查点 4] 知识提取计划确认
代理:建议抽取实体类型 X、关系类型 Y,对吗?
用户:对 / 不需要 X检查点的作用:避免系统误解方向后,构建了一个错的图,浪费成本。这是第 8 章 8.5 节"越权执行"防御在多 Agent 系统的延伸。
21.5 一个最小知识图谱示例
用 Neo4j + Python 写一个最小的知识图谱构建 + GraphRAG 检索示例。
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 的里程碑项目。它的核心流程:
- TextUnits:把文档切成文本单元
- 实体抽取:LLM 从每个 TextUnit 抽取实体
- 关系抽取:LLM 抽取实体之间的关系
- 社区检测:用 Leiden 算法把图划分成社区
- 社区摘要:自底向上为每个社区生成摘要
- 查询时:全局查询用社区摘要,局部查询用图扩展
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)。课程的三节递进:
- p32 Introduction:为什么需要 Agent + 知识图谱
- p33 什么是知识图谱:节点、边、属性、Cypher、三层图结构
- p34 多智能体系统的架构:五个代理的分工
课程的关键判断:"知识图谱不是把所有文本塞进去就结束,而是要先定义哪些实体和关系对业务目标有用。schema 定义得好,后续抽取、检索和推理才有方向"。这正是本章 21.4.4 "计划与执行分离"的来源。
课程还提到一个工程经验:多智能体在这里的价值是降低复杂度,不是为了炫技。每个代理的职责越清晰,越容易写好提示词、设计输出格式、插入用户确认点,也越容易在后续课程中替换或优化其中一环。这支撑了本章 21.4 的五代理分工设计。
GraphRAG vs Agentic RAG
第 5 章 Context Engineering 讲过 Agentic RAG:Agent 自己决定查什么、查几次、怎么综合。GraphRAG 和 Agentic RAG 的关系:
| Agentic RAG | GraphRAG | |
|---|---|---|
| 数据结构 | 向量库 | 图数据库 |
| 检索方式 | 多轮向量搜索 | 图扩展 + 向量搜索 |
| 适合 | 探索性、开放式问答 | 关系推理、跨数据源 |
| 工程成本 | 中(向量库 + Agent loop) | 高(schema + 抽取 + 图数据库) |
两者不互斥:GraphRAG 可以包在 Agentic RAG 里,作为 Agent 的一个工具。Agent 决定什么时候用向量检索,什么时候用图扩展。这是生产系统的常见架构。
实体对齐(Entity Alignment)的工程难点
本章 21.4.3 节提到"主题图节点对齐到领域图节点"。这是知识图谱工程最难的环节之一。
问题:LLM 抽取的实体是文本字符串("桌子A"),领域图里的实体是结构化 ID(Product:1)。怎么知道"桌子A" = Product:1?
工程做法:
- 规则匹配:字符串相等 / 模糊匹配 / 别名表
- embedding 匹配:算两个实体的 embedding 相似度
- LLM 判断:让 LLM 判断两个实体是不是同一个
- 混合:先用规则 + 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.