这篇不先堆名词。我们把《GraphRAG跑通那天,我才发现前面的学习顺序反了》拆成几级台阶,看完至少知道下一步该学什么、该练什么。

摘要

最近公司引入Claude Code做团队级AI编程,Demo阶段大家觉得挺香,结果联调时才发现:不同人的代码风格、权限边界、日志规范完全对不上,协作效率反而下降了。这件事让我意识到,AI工具从个人试用到团队协作,从来不缺能跑通Demo的人,缺的是能把边界和约束说清楚的人。

回到GraphRAG这个话题,我见过太多项目:一个人能跑通,团队接手就崩。原因往往不是模型不够强,而是知识图谱的建模标准和实体抽取逻辑没有统一。今天聊的,就是我在两个企业知识库项目里踩过的坑和总结的判断标准。

目录

  • 传统RAG的瓶颈:为什么纯向量检索不够用
  • 知识图谱建模:先别急着抽实体,先把边界想清楚
  • 实体关系抽取:规则优先,模型兜底
  • 图检索增强:查询时再决定怎么走图
  • 评估与优化:别只看准确率,要看场景覆盖率
  • 总结

传统RAG的瓶颈:为什么纯向量检索不够用

文章插图 1

第一个项目是做一个内部技术文档问答系统。当时用的是标准RAG流程:文档切片、向量化、检索、生成。Demo效果很好,准确率看起来不错。

但上线一个月后,问题开始暴露。

最典型的是一个问题:"我们去年Q3的API限流策略是什么?"

系统能检索到相关文档片段,但答案往往分散在多个切片里,模型需要自己拼凑。更严重的是,当问题涉及跨文档的关联推理时,比如"限流策略和之前的权限变更有什么关系",纯向量检索直接失效。

原因在于,向量检索的本质是语义相似度匹配,它擅长"找相似的内容",但不擅长"梳理内容之间的关系"。当知识库里的信息存在实体关联、层级结构、因果关系时,单纯靠向量相似度会丢失这些结构化信息。

我当时在文档里加了很多注释和标签,试图弥补这个问题,但效果有限。后来才明白,缺的不是更多标注,而是真正的知识图谱。

知识图谱建模:先别急着抽实体,先把边界想清楚

文章插图 2

第二个项目是做一个合规知识库系统,涉及制度文档、操作手册、历史案例三类数据。

第一个坑:建模阶段我们急于抽实体,先把文档扔进NER模型里跑了一遍,结果抽出来几千个实体,关系乱七八糟。

后来我们停下来重新想:这个系统的核心查询场景是什么?

经过和合规团队的沟通,我们明确了三类核心问题:
1. 某个操作是否符合规定?
2. 不同制度之间是否存在冲突?
3. 历史案例对当前问题有什么参考?

基于这三类问题,我们重新设计了图谱 schema:

  • 核心实体:制度、条款、操作、案例
  • 核心关系:引用、约束、冲突、参考

这个schema非常克制,只保留了能回答上述三类问题的实体和关系。很多人做GraphRAG的误区是一上来就追求"全",把文档里所有名词都当成实体,结果图谱膨胀、维护成本爆炸。

我的判断标准是:每个实体和关系,都必须能对应到至少一个真实查询场景。如果抽出来的实体没人会问,那就不要。

CSDN资料领取方式

实体关系抽取:规则优先,模型兜底

有了schema,接下来是抽取。这里我强烈建议规则优先,模型兜底。

我们当时的做法是:

1. 对制度类文档,用正则表达式提取条款编号、章节结构、生效日期等关键信息,这部分准确率接近100%。
2. 对案例类文档,用规则提取"问题描述-处理依据-处理结果"的三段式结构。
3. 模型只负责处理规则覆盖不到的模糊边界,比如从自然语言描述中提取操作主体和对象。

代码层面,我们写了一个简单的抽取管线:

import re
from typing import List, Dict, Tuple

class RuleBasedExtractor:
    """规则优先的实体关系抽取器"""

    def __init__(self):
        # 制度条款匹配模式
        self.clause_pattern = re.compile(
            r'(第[一二三四五六七八九十\d]+章|第[一二三四五六七八九十\d]+条)',
            re.UNICODE
        )
        # 案例结构匹配模式
        self.case_pattern = re.compile(
            r'(问题描述|处理依据|处理结果)',
            re.UNICODE
        )

    def extract_clauses(self, text: str) -> List[Dict]:
        """从制度文档中提取条款结构"""
        clauses = []
        current_chapter = None
        current_clause = None

        for line in text.split('\n'):
            line = line.strip()
            if not line:
                continue

            # 匹配章节
            chapter_match = re.search(r'第([一二三四五六七八九十\d]+)章', line)
            if chapter_match:
                current_chapter = chapter_match.group(0)
                continue

            # 匹配条款
            clause_match = re.search(r'第([一二三四五六七八九十\d]+)条', line)
            if clause_match:
                current_clause = clause_match.group(0)
                clauses.append({
                    'chapter': current_chapter,
                    'clause': current_clause,
                    'content': line
                })

        return clauses

    def extract_case_structure(self, text: str) -> Dict:
        """从案例文档中提取三段式结构"""
        result = {}
        sections = self.case_pattern.split(text)

        for i, section in enumerate(sections):
            if i == 0:
                continue
            key = sections[i-1].strip() if i > 0 else ''
            if key in ('问题描述', '处理依据', '处理结果'):
                result[key] = section.strip()

        return result

这个思路的核心是:规则处理结构化程度高的部分,模型处理模糊边界。这样既保证了关键信息的准确率,又控制了模型的调用成本。

实际运行中,我们对制度文档的条款抽取准确率达到了95%以上,案例文档的结构抽取准确率达到88%。剩下的部分才交给LLM处理。

图检索增强:查询时再决定怎么走图

图谱建好了,怎么用?这是第二个大坑。

我们最初的做法是:查询时先把问题转成实体,然后在图里做多跳遍历,把遍历到的子图喂给LLM。

结果发现两个问题:
1. 多跳遍历的候选答案太多,Prompt塞不下。
2. 有些问题其实不需要走图,直接向量检索更快。

后来我们改成了一种"按需走图"的策略:

1. 先用向量检索找到最相关的文档片段。
2. 从片段中提取实体,判断是否需要走图。
3. 如果需要,才在图里做1-2跳的邻居扩展,补充上下文。
4. 把向量检索结果和图扩展结果合并,一起喂给LLM。

这种策略的优势是:简单问题快速响应,复杂问题才有图谱参与。

代码层面,我们实现了一个简单的图扩展逻辑:

class GraphRAGRetriever:
    """按需走图的检索器"""

    def __init__(self, vector_store, graph_db):
        self.vector_store = vector_store
        self.graph_db = graph_db

    def retrieve(self, query: str, max_hops: int = 2) -> Dict:
        # 第一步:向量检索
        vector_results = self.vector_store.search(query, top_k=5)

        # 第二步:提取实体
        entities = self._extract_entities(query, vector_results)

        # 第三步:判断是否需要图扩展
        if self._need_graph_expansion(entities, vector_results):
            # 第四步:图遍历扩展
            graph_context = self._expand_graph(entities, max_hops)
            return {
                'vector_results': vector_results,
                'graph_context': graph_context,
                'used_graph': True
            }

        return {
            'vector_results': vector_results,
            'graph_context': None,
            'used_graph': False
        }

    def _need_graph_expansion(self, entities, vector_results):
        """判断是否需要图扩展的判断逻辑"""
        # 简单启发式:如果实体数量多且向量结果分散,说明可能需要图谱关联
        if len(entities) > 2:
            scores = [r.score for r in vector_results]
            if max(scores) - min(scores) > 0.15:
                return True
        return False

这里的关键是_need_graph_expansion的判断逻辑。我没有用复杂的模型判断,而是用了一个简单的启发式规则:实体数量多且向量结果分散时,说明问题可能涉及多个概念之间的关联,需要图谱参与。

这个判断逻辑可以根据实际业务场景调整,比如加入实体类型权重、关系类型权重等。

评估与优化:别只看准确率,要看场景覆盖率

很多项目做到这里就停了,因为没有明确的评估标准。

我们当时做了两件事:

第一,建立了场景覆盖矩阵。把合规团队提出的真实查询场景列出来,每个场景标注:需要向量检索、需要图谱、两者都需要。然后统计系统对每个场景的回答质量。

第二,建立了负例收集机制。让合规团队在系统上线后标记错误回答,定期分析错误类型,针对性优化。

经过三个月的迭代,场景覆盖率从65%提升到了92%。提升的关键不是模型换成了更强的,而是实体抽取规则和图扩展策略的优化。

总结

GraphRAG从Demo到上线,最大的差距不在技术,而在对边界和场景的理解。

我做这两个项目的最大体会是:

1. 建模阶段先想清楚查询场景,而不是急于抽实体。schema越克制,后期维护越轻松。
2. 抽取阶段规则优先,模型兜底。结构化信息用规则,模糊边界用模型。
3. 检索阶段按需走图,不是所有问题都需要图谱参与。
4. 评估阶段建立场景覆盖矩阵和负例收集机制,用真实业务数据驱动优化。

这些经验,和Claude Code从个人试用走向团队协作遇到的坑,本质上是同一个问题:能把Demo跑通的人很多,能把边界和约束说清楚的人很少。

GraphRAG的价值不在于用了多复杂的模型,而在于有没有把知识的关系说清楚。

资料展示

下面是我整理的AI大模型学习资料和工具包预览,适合收藏后按主题逐步学习。

AI大模型资料展示 1

AI大模型资料展示 2

AI大模型资料展示 3

AI大模型资料展示 4

AI大模型资料展示 5

如果你想看完整资料目录,可以在评论区留言「资料」;也欢迎告诉我你更关注AI大模型里的哪类内容。

CSDN官方大礼包

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐