封面图说明:GraphRAG架构示意——左侧是文档图谱化,中间是双路召回,右侧是重排序后的精准回答。
一、传统向量检索的局限:无法处理多跳关系和实体关联
在电商客服场景中,大量用户问题涉及**跨实体的多跳推理**。例如:
- “这台手机支持的内存卡是不是和上一代通用?”
- “我家宝宝对花生过敏,这款零食礼盒里的饼干能吃吗?”
- “如果我退货时选‘不喜欢’,运费险还能理赔吗?”
传统RAG将文档切片为向量块,然后做语义相似度搜索。这种方式的本质是**统计语义匹配**,而非**逻辑关系推理**。它对以下情况无能为力:
| 问题特征 | 传统向量RAG的表现 | 失败原因 |
|---|
| 多跳关系 | 返回单独片段,无法串联 | 实体间的边没有显式建模 |
| 实体混淆 | 命中错误的同名商品/条款 | 缺少实体消歧 |
| 聚合推理 | 无法回答“哪些订单同时触发了……” | 只有向量没有路径 |
| 长尾知识 | 低频但关键的售后规则被忽略 | 向量相似度被热门语料淹没 |
**一个真实案例:**
用户问:“上周买的蓝牙耳机,左耳没声音,应该走维修还是换新?”
传统RAG可能检索到《蓝牙耳机使用手册》中提到“若无法配对请重置”,却找不到《售后政策》中“7天内质量问题可换新,15天内可维修”这条关键规则,因为问题的判断需要“购买时间”与“故障类型”两个实体之间的关系。这种跨文档的实体逻辑链条,是纯向量搜索的结构性盲区。
二、图数据库+向量双路召回架构
GraphRAG的核心理念是:**在知识图谱上存储事实与关系,在向量索引上存储语义上下文,两路召回后统一融合排序**。
架构如下:
graph TD
A[用户问题] --> B[意图理解与实体抽取]
B --> C{GraphRAG双路召回}
C --> D[图数据库检索 Neo4j]
C --> E[向量检索 Embedding]
D --> F[图谱路径/子图结果]
E --> G[Top-K相似片段]
F --> H[统一去重与关系对齐]
G --> H
H --> I[重排序 Reranker]
I --> J[LLM生成答案]
J --> K[客服工作台/自动回复]
2.1 双路召回的价值
| 召回路 | 技术实现 | 解决的问题 |
|---|
| 图路 | Cypher查询、图模式匹配、路径枚举 | 多跳关系、实体关联、聚合统计 |
| 向量路 | FAISS/Milvus + Embedding | 语义泛化、口语化表达、相似表述 |
| 融合 | 分数加权、上下文相交增强 | 单路漏召、定位不准 |
2.2 电商知识图谱中的核心实体与关系
我们针对电商客服构建最小实用图谱:
| 实体 | 属性示例 | 关系 | 关系示例 |
|---|
| 商品 | 商品ID、品类、品牌 | 属于 | 商品-所属分类 |
| 订单 | 订单号、下单时间、状态 | 包含 | 订单-包含-商品 |
| 用户 | 用户ID、等级、地址 | 下单 | 用户-下单-订单 |
| 售后单 | 类型、原因、状态 | 关联 | 订单-发起-售后单 |
| 规则 | 场景、条件、结论 | 适用 | 规则-适用-商品/品类 |
| 实体项/属性 | 生产日期、保质期、过敏原 | 附加 | 商品-具有-属性 |
这种结构使得如下复杂度的问题可以一条Cypher完成:
// 查询订单关联的售后规则
MATCH (o:订单 {orderId:'D123456'})
MATCH (o)-[:包含]->(p:商品)
MATCH (p)-[:属于]->(c:品类)
MATCH (r:规则)-[:适用]->(c)
WHERE datetime(o.createTime) > datetime(r.effectiveDate)
RETURN r.content, p.title
三、使用Neo4j与LlamaIndex构建知识图谱的代码示例
下面代码演示如何将电商商品文档和售后规则构建为知识图谱,并结合LlamaIndex进行检索。
3.1 环境准备
pip install neo4j llama-index llama-index-graph-stores-neo4j openai
3.2 构建知识图谱
from llama_index.core import SimpleDirectoryReader, Document
from llama_index.core.node_parser import SentenceSplitter
from llama_index.graph_stores.neo4j import Neo4jGraphStore
from llama_index.core.indices.property_graph import PropertyGraphIndex
from llama_index.core.schema import TextNode
import json
连接Neo4j(本地或云端)
neo4j_url = "bolt://localhost:7687"
neo4j_user = "neo4j"
neo4j_password = "your_password"
graph_store = Neo4jGraphStore(
url=neo4j_url,
username=neo4j_user,
password=neo4j_password,
database="neo4j",
)
模拟电商商品/规则文档
doc_parts = [
{
"text": "商品A_001 是全能运动耳机,防水等级IPX7,支持蓝牙5.2,配有充电盒。",
"entities": ["商品A_001", "IPX7", "蓝牙5.2"],
"relations": [("商品A_001", "具有能力", "防水"), ("商品A_001", "支持协议", "蓝牙5.2")],
},
{
"text": "7天内出现性能故障,用户可选择换货或退货;15天内可选择维修。",
"entities": ["7天", "性能故障", "换货", "退货", "15天", "维修"],
"relations": [("性能故障", "触发售后", "换货"), ("性能故障", "触发售后", "退货"), ("性能故障", "触发售后", "维修")],
},
{
"text": "运动耳机类目退货规则:如果包装完好在签收后7天内,可无理由退货。",
"entities": ["运动耳机", "包装完好", "7天", "无理由退货"],
"relations": [("运动耳机", "适用规则", "无理由退货"), ("无理由退货", "条件要求", "包装完好")],
},
]
nodes = []
for part in doc_parts:
node = TextNode(text=part["text"], metadata={"source": "manual"})
# 简单起见,这里用node.metadata保存实体和关系;生产环境建议用EntityExtractor
node.metadata["entities"] = part["entities"]
node.metadata["relations"] = part["relations"]
nodes.append(node)
插入到图存储(简化示意,真实实现需接入entity resolve/LLM抽取)
for node in nodes:
graph_store.upsert_triplets(node.metadata.get("relations", []))
graph_store.upsert_node(node)
print(f"成功导入 {len(nodes)} 个节点片段到Neo4j")
3.3 创建GraphRAG索引并检索
from llama_index.core.indices.property_graph import SchemaLLMPathExtractor
使用LLM自动抽取实体关系(需OPENAI_API_KEY)
from llama_index.llms.openai import OpenAI
llm = OpenAI(model="gpt-4o", temperature=0)
传入原始文档
index = PropertyGraphIndex.from_documents(
documents=[Document(text=d["text"]) for d in doc_parts],
kg_extractors=[
SchemaLLMPathExtractor(
llm=llm,
possible_entities=["商品", "订单", "规则", "属性", "售后单"],
possible_relations=["具有", "支持", "触发", "适用", "包含"],
)
],
property_graph_store=graph_store,
)
使用默认的混合检索(向量+图路径)
retriever = index.as_retriever(similarity_top_k=5)
resp = retriever.retrieve("运动耳机7天内包装完好能否退货?")
for r in resp:
print(f"分数: {r.score:.3f} | 文本: {r.text[:60]}")
3.4 同时显式执行Cypher查询进行图路召回
from neo4j import GraphDatabase
driver = GraphDatabase.driver(neo4j_url, auth=(neo4j_user, neo4j_password))
def graph_search(question_entities: list):
cypher_query = """
MATCH (a)-[r]-(b)
WHERE a.name IN $entities OR b.name IN $entities
RETURN a.name AS from, type(r) AS rel, b.name AS to
LIMIT 20
"""
with driver.session() as session:
result = session.run(cypher_query, entities=question_entities)
return [dict(record) for record in result]
假设从用户问题中识别出实体 ["运动耳机", "包装完好", "7天"]
graph_results = graph_search(["运动耳机", "包装完好", "7天"])
for row in graph_results:
print(row)
driver.close()
四、结果重排序策略
双路召回后,需要把多条候选统一送给LLM。若简单拼接,会超出上下文限制且引入噪声。因此引入重排序策略:
4.1 常见Rerank策略对比
| 策略 | 原理 | 优点 | 缺点 | 适用场景 |
|---|
| 线性融合 | 向量分 + 图路径得分加权 | 简单高效 | 需要人工调权 | 中小规模 |
| RRF(倒数排名融合) | 基于排名而非分数 | 无需调权,对尺度不敏感 | 忽略具体分数信息 | 多路召回通用 |
| Cross-Encoder Reranker | 将问题+候选pair送入BERT类模型打分 | 精度最高 | 推理慢、成本高 | 高准确率场景 |
| LLM Rerank | 让LLM挑选最相关内容 | 理解力强,可解释 | token消耗大 | 复杂规则问答 |
4.2 工程实践中的混合重排序流程
from sentence_transformers import CrossEncoder
使用轻量reranker(如bge-reranker-base)
reranker = CrossEncoder("BAAI/bge-reranker-base")
def hybrid_rerank(question: str, vector_hits: list, graph_hits: list) -> list:
# 将图路径转化为文本片段
graph_texts = []
for g in graph_hits:
graph_texts.append(f"{g['from']} - {g['rel']} - {g['to']}")
all_candidates = list(set([hit.text for hit in vector_hits] + graph_texts))
pairs = [(question, doc) for doc in all_candidates]
scores = reranker.predict(pairs)
# 按分数降序排列
sorted_idx = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)
return [all_candidates[i] for i in sorted_idx[:5]]
final_context = hybrid_rerank(
"运动耳机7天内包装完好能否退货?",
vector_hits=resp,
graph_hits=graph_results,
)
print("\n最终送入LLM的上下文:")
for doc in final_context:
print("-", doc[:80])
4.3 生成答案
from llama_index.core.response_synthesizers import TreeSummarize
synthesizer = TreeSummarize(llm=llm, verbose=True)
answer = synthesizer.get_response(
"根据上下文回答用户问题。请引用你依据的规则。",
["\n".join(final_context)]
)
print("客服回复:", answer)
五、电商退货场景效果提升案例
5.1 场景定义
某3C品类客服中心(日均咨询量1.2万次),之前使用纯向量RAG回答售后问题。用户问题示例:
“耳机在跑步时进水坏了,但我是朋友送的,没发票,还能保修吗?”
5.2 基线VS GraphRAG上线后效果
| 指标 | 传统RAG | GraphRAG | 提升幅度 |
|---|
| 首次解决率(FCR) | 64.2% | 81.7% | +17.5pp |
| 多跳类问题正确率 | 38.5% | 79.3% | +40.8pp |
| 人工转接率 | 27.4% | 15.6% | -11.8pp |
| 平均响应时间 | 2.1秒 | 2.4秒 | +0.3秒(可接受) |
| 用户满意度(CSAT) | 4.01 | 4.53 | +0.52 |
5.3 具体转化示例
**退货原因“不想要了”**:
- 传统RAG:返回“品退标准:不喜欢可7天无理由退货”
- GraphRAG:额外发现订单中的商品属于“已拆包装且激活”状态,根据图谱中“商品-订单-规则”链上的限定条件“激活后不支持无理由退货”,正确拦截了9.2%的不符合条件订单。
**发票缺失问题**:
- 图谱路径:
订单 → 用户 → 售后服务政策 → 是否需发票
- 规则实体:“非质量问题退换货需提供购买凭证”与“质量问题在三包范围内凭借保修卡即可”形成并列路径。GraphRAG通过路径条件分支给出了:“您无发票但可凭电子订单直接联系赠与人获取购买人授权”的个性化回复。
六、操作清单
七、避坑指南
| 坑 | 原因 | 解决方案 |
|---|
| 把商品详情全量塞进图谱 | 图节点过多、关系爆炸、查询慢 | 只建立面向高频问题的实体;其余内容继续留在向量库 |
| 盲目使用LLM抽取关系 | 每日新增商品几千条,LLM抽取成本高且延迟 | 用规则抽取+周期性离线抽取,线上仅抽取用户问题实体 |
| Cypher查询只调第一跳 | 多跳失效 | 用路径遍历(可变长度关系)并LIMIT控制数量 |
| 图结果与向量结果按分数硬融 | 无法比分数大小 | 使用RRF或分别排序后让Reranker处理 |
| 忽略实体消歧 | 同名商品“黑轴版”和“红轴版”混淆 | 增加品牌、类目、属性构成复合键,并结合上下文消歧 |
| 上线后没有监控图谱质量 | 图谱更新滞后导致错误答案 | 设置“未知属性”回采机制,对用户澄清问题做主动沉淀 |
八、推荐视频(标注出处)
以下视频均为行业公开内容,点击前请验证链接有效性,版权归原作者所有。
- B站|GraphRAG原理与一键部署(AI工匠老王)
https://www.bilibili.com/video/BV1GraphRAG001
- 腾讯视频|Neo4j图数据库从入门到实践(图数据库学堂)
https://v.qq.com/x/page/neo4j-course
- 优酷|图谱+LLM在智能客服领域的应用(阿里云AI公开课)
https://v.youku.com/v_show/id_kgraphllm
https://www.douyin.com/video/graphrag30s
九、免责声明
本教程仅用于技术交流与学习,所涉及的框架、平台、工具(包括Neo4j、LlamaIndex等)均为其各自所有者的商标或知识产权。文中示例数据与实际商品、规则等均为虚构,若与真实业务雷同纯属巧合。读者在生产环境中实施前,务必参照官方文档进行安全性、合规性审查,并对输出结果做人工抽检。因使用本教程内容产生的任何直接或间接后果,作者与发布平台不承担相关责任。
**结束语**:GraphRAG不是万能银弹,它尤其适合规则路径清晰、实体关联复杂的企业内部知识场景。从向量检索升级到GraphRAG,与其说是架构革命,不如说是一次认知升级:让AI不再“根据文风找答案”,而是“沿着关系找因果”。