AI图RAG实践笔记

向量检索找的是"像不像",图检索找的是"连没连"——两类问题,两套工具。

在做知识库项目的时候,一直有个痛点:传统的向量检索虽然好用,但在处理复杂关联问题时总是不够灵活。比如用户问"哪些项目同时涉及前后端和数据库技术",简单的相似度检索很难理解这种"同时涉及"的逻辑关系。

我试过很多方法——换 embedding、调检索策略、手动打标签——效果都不太理想,直到接触了 Graph RAG。下面记录把图结构引入检索系统的全过程,坑和结果都写在里面。

背景:什么是图 RAG

简单来说,Graph RAG 就是把知识库的实体和关系用图的方式组织起来,然后在检索时利用图的结构信息。

传统的 RAG 是这样的:

graph LR A[问题] --> B[向量化] B --> C[向量检索] C --> D[相似度匹配] D --> E[相关文档]

而 Graph RAG 是这样的:

graph LR A[问题] --> B[实体识别] B --> C[图检索] C --> D[关系扩展] D --> E[相关实体] E --> F[文档映射] F --> G[相关文档]

区别在于:前者是"找相似的内容",后者是"找相关联的内容"。

我的需求

基于项目的实际情况,我需要解决几个具体问题:

  1. 多实体关联查询:用户经常问"项目 A 和项目 B 有什么共同的技术栈"
  2. 层级关系查询:比如"某个分类下的所有子分类"
  3. 路径推理:比如"从技术 A 到技术 B 的推荐路径"
  4. 上下文聚合:把相关的文档按关系聚合起来,而不是简单的列表

这些需求用向量检索都不太合适,而图结构正好能解决。

实现过程

知识图谱构建

先要构建一个知识图谱。我的方案是从文档中提取实体和关系:

# 实体和关系提取
def extract_entities_and_relations(document):
    entities = {
        'projects': [],
        'technologies': [],
        'categories': []
    }
    relations = []

    # 使用 NER 提取项目名
    # 使用关键词匹配提取技术栈
    # 从标题和分类提取分类信息

    # 构建关系
    for project in entities['projects']:
        for tech in entities['technologies']:
            if tech in document['content']:
                relations.append({
                    'from': project,
                    'to': tech,
                    'type': 'uses'
                })

    return entities, relations

这一步遇到的问题是实体识别不够准确,特别是项目名和技术名的区分。最后我加了个项目名白名单,效果好多了。

构建出来的图谱大概是这样:

graph TD A[用户管理系统] --> B[Python] A --> C[Django] A --> D[PostgreSQL] E[订单处理服务] --> B E --> F[Go] E --> G[Redis] H[数据分析平台] --> I[Python] H --> J[Pandas] H --> K[机器学习] B --> L[后端技术] F --> L C --> M[Web框架]

这样就能清楚看到项目和技术之间的使用关系,以及技术本身的分类关系。

图数据库选型

我测试了几个图数据库:

  1. Neo4j:功能强大,但部署太重
  2. NetworkX:纯 Python,简单但性能有限
  3. Memgraph:中间路线,性能和易用性平衡

最后选了 Memgraph,主要是部署简单,而且 Cypher 查询语言比较友好。

# 连接图数据库
from neo4j import GraphDatabase

class GraphRAG:
    def __init__(self, uri, user, password):
        self.driver = GraphDatabase.driver(uri, auth=(user, password))

    def create_graph(self, documents):
        for doc in documents:
            entities, relations = extract_entities_and_relations(doc)
            self._create_nodes(entities)
            self._create_relations(relations)

混合检索策略

我做了个混合策略:先向量检索找候选文档,再用图检索扩展相关的实体和关系。

def hybrid_search(query, top_k=10):
    # 向量检索找初始候选
    vector_results = vector_search(query, top_k=top_k)

    # 从结果中提取实体
    entities = extract_entities_from_results(vector_results)

    # 图检索扩展
    graph_results = graph_search(entities)

    # 合并去重
    combined = merge_results(vector_results, graph_results)

    return combined

这样既能保证基本的相关性,又能利用图的结构信息。

关系聚合

用户拿到结果后,除了文档列表,还需要看关系结构。我加了个简单的可视化:

def visualize_results(results):
    # 用 NetworkX 构建子图
    G = nx.DiGraph()
    for node in results['nodes']:
        G.add_node(node['id'], **node['data'])

    for edge in results['edges']:
        G.add_edge(edge['from'], edge['to'], **edge['data'])

    # 绘图
    plt.figure(figsize=(12, 8))
    pos = nx.spring_layout(G)
    nx.draw(G, pos, with_labels=True, node_color='lightblue')
    plt.show()

踩坑记录

这一段没有少踩坑,我把主要问题整理了下:

graph TD A[构建图谱] --> B[实体识别] B --> C[准确率低] C --> D[白名单+领域模型] A --> E[图数据库] E --> F[查询慢] F --> G[索引优化+缓存] H[混合检索] --> I[相关性下降] I --> J[限制深度+权重过滤] H --> K[可视化复杂] K --> L[关键节点+交互]

每个问题都是实际遇到过的,而且都要调试好几次才找到合适的方案。

问题 1:实体识别准确率低

最开始用通用的 NER 模型,结果把普通词都识别成项目名。比如"项目推进"的"项目"也被识别了。

解决方案

  1. 建立项目名白名单
  2. 用特定领域的模型
  3. 加了后处理规则

问题 2:图数据库查询慢

一开始图上有个 10 万节点,查询要几秒。用户受不了。

解决方案

  1. 做了索引优化
  2. 限制子图大小
  3. 加了缓存层
// 索引优化
CREATE INDEX ON :Project(id);
CREATE INDEX ON :Technology(name);
CREATE INDEX ON (p:Project)-[:USES]->(t:Technology) WHERE t.name IN ['Python', 'JavaScript'];

问题 3:结果相关性下降

加了图检索后,有时候结果不太相关,因为图关系扩展太宽了。

解决方案

  1. 限制关系扩展深度
  2. 按关系权重过滤
  3. 加上向量检索的分数加权
def score_hybrid_results(vector_score, graph_score, weights=(0.7, 0.3)):
    return weights[0] * vector_score + weights[1] * graph_score

问题 4:可视化太复杂

子图大了之后,可视化一团乱,看不清关系。

解决方案

  1. 只显示关键节点
  2. 用颜色区分节点类型
  3. 加了交互功能

结果对比

做了个简单的对比测试,用 Python 画了下数据对比:

import matplotlib.pyplot as plt
import numpy as np

# 数据
metrics = ['准确率', '用户满意度']
vector = [72, 3.2]
hybrid = [85, 4.1]

# 绘图
x = np.arange(len(metrics))
width = 0.35

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))

# 准确率对比
bars1 = ax1.bar(x - width/2, vector, width, label='纯向量检索')
bars2 = ax1.bar(x + width/2, hybrid, width, label='混合检索')
ax1.set_ylabel('分数')
ax1.set_title('准确率对比')
ax1.set_xticks(x)
ax1.set_xticklabels(metrics)
ax1.legend()

# 用户满意度对比
bars3 = ax2.bar([0], vector[1], width, label='纯向量检索')
bars4 = ax2.bar([width], hybrid[1], width, label='混合检索')
ax2.set_ylabel('满意度 (1-5分)')
ax2.set_title('用户满意度对比')
ax2.set_xticks([0, width])
ax2.set_xticklabels(['纯向量检索', '混合检索'])
ax2.legend()

plt.tight_layout()
plt.show()

从图表能更直观地看到提升效果。

指标纯向量检索混合检索提升
准确率72%85%+13%
响应时间0.3s1.2s-300%
用户满意度3.2/54.1/5+28%

准确率和满意度都有明显提升,但响应时间变长了。这是预料之中的,毕竟多了图检索的开销。

用户反馈说:“现在能找到之前找不到的关联信息,特别是项目之间的共同技术栈。”

写在后面

Graph RAG 折腾下来,没有银弹。构建和维护知识图谱成本高,不是所有问题都适合图检索,知识更新后图谱也要跟着改。

但它确实补上了传统 RAG 的短板——关联性查询。简单问答继续用向量检索,复杂关联走图检索,我这边是混合策略两边都沾。

如果你也在做知识库,且被"项目 A 和 B 有什么共同技术栈"这类问题卡住,可以从小规模试起,基础功能跑通再引图结构。有问题欢迎交流。


相关文章

版权声明: 本文首发于 指尖魔法屋-AI图RAG实践笔记https://blog.thinkmoon.cn/post/372-ai-graph-rag-structure-association-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!