首页 / 文章 / 实用笔记:图式RAG实战——为何传统RAG无法应对复杂查询

实用笔记:图式RAG实战——为何传统RAG无法应对复杂查询

《实用笔记》操作指南:图解RAG应用——为何标准RAG无法处理复杂查询:面向采用该模式的团队提供的合同、校验规则以及可直接插入的代码模板。

3051 词

可将此内容视为《Graph RAG in Action:为何标准RAG无法处理复杂查询(以及Graph RAG如何解决这一问题)》中理念面向操作人员的重构版本:包含清晰的阶段划分、有序的代码模块,以及便于交接时参考的恢复说明。 在“概览”阶段,若能将其视为可度量的界面来使用效果最佳。在扩大范围之前,先记录一份理想的处理过程、一个失败案例以及对应的回滚说明。 相较于庞大的脚本,应优先选择小型且可测试的单元。当某一步骤出现故障时,故障原因应能明确指向某个具体责任模块,而非整个复杂的流程链。

真正的问题:相互孤立的信息碎片

对于“真正的问题分离阶段”,在修改代码之前需明确输入内容、该步骤的负责人以及退出标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 应将此阶段视为输入与已验证输出之间的契约。为相关成果命名,定义成功判定标准,并拒绝默许的半完成状态。 需引用实际作为答案依据的段落。没有引用的话,操作人员就无法区分幻觉内容与索引缺失的问题。

Graph RAG的独特之处

在修改代码之前,需先明确What Graph RAG所执行阶段的输入内容、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境切换到共享环境时出现意外费用。必须注明实际用于生成答案的文本片段;若没有引用,操作人员就无法区分是幻觉内容还是索引缺失导致的错误。

Feature            | Vector RAG          | Graph RAG
-------------------|---------------------|-------------------------
Storage unit       | Text chunks         | Entities + relationships
Retrieval method   | Semantic similarity | Graph traversal
Best for           | Direct lookup       | Multi-hop reasoning
Context scope      | Local fragment      | Connected network

提取瓶颈

在“提取瓶颈”阶段,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,以便操作人员无需查看整个系统结构即可进行审计。 需引用实际作为答案依据的段落。没有引用的话,操作人员就无法区分是幻觉内容还是索引缺失导致的错误。 在“提取瓶颈”阶段,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 相较于庞大的脚本,应优先使用小型且可测试的单元。当某个步骤失败时,故障原因应能明确指向单一责任模块,而非复杂的相互关联结构。

管道。

Graph RAG管道的工作原理

在处理“Graph RAG如何工作”这一阶段时,首先需明确相关约定:所需输入、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 将这一阶段视为输入与经过验证的输出之间的契约。为相关产物命名,定义成功检测标准,并拒绝默许的部分完成情况。 在调整提示词之前,先使用固定的问题集来衡量召回率。仅仅更换提示词往往无法改善较差的检索效果。

flowchart LR
    Q[User query] --> E[Entity extraction]
    E --> G[Graph construction]
    G --> T[Traversal + path ranking]
    T --> C[Path context]
    C --> L[LLM answer generation]
    L --> R[Final response]

实现蓝图(概念验证)

在完成“实施蓝图概念验证”阶段时,首先需列出相关约定:所需输入、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免从演示环境过渡到共享环境时出现意外费用。 在调整提示词之前,先用固定的问题集测试召回率。仅仅更换提示词往往无法改善较差的检索效果。

1. 提取结构化事实

在完成“1. 提取结构化事实”这一阶段时,首先需明确相关规范:所需的输入参数、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 应将配置信息与应用程序代码分开存放。环境文件、密钥存储以及功能开关应集中于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 在调整提示词之前,先使用固定的问题集来测试系统的召回率。仅仅更换提示词往往无法解决检索效果不佳的问题。 在完成“1. 提取结构化事实”这一阶段时,首先需明确相关规范:所需的输入参数、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 相较于庞大的脚本,更应优先使用小型且可测试的单元。当某个步骤出现故障时,故障原因应能明确指向某个具体的功能模块,而非整个复杂的处理流程。

import networkx as nx

SAMPLE_TRIPLES = [
    ("John Doe", "is CEO of", "Acme Corp"),
    ("Jane Smith", "sits on board of", "Acme Corp"),
    ("Jane Smith", "mentors", "John Doe"),
]

def build_graph(triples):
    graph = nx.DiGraph()
    for source, relation, target in triples:
        graph.add_node(source)
        graph.add_node(target)
        graph.add_edge(source, target, relation=relation)
    return graph

2. 寻找候选路径

在将“寻找候选路径”这一阶段视为可度量的工作面时,其效果最佳。在扩大范围之前,先收集一份理想的转录文本、一个失败案例以及回滚说明。 应将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,明确成功标准,杜绝默许的不完整完成。 需将分块策略与检索策略分开处理。当质量指标发生变化时,调整其中一项不应强制要求重新编写另一项。

def traverse_graph(graph, seeds, depth=2):
    paths = []
    seen = set()
    undirected = graph.to_undirected()

    for seed in seeds:
        for target in graph.nodes:
            if seed == target:
                continue
            for path in nx.all_simple_paths(undirected, source=seed, target=target, cutoff=depth):
                canonical = tuple(path) if tuple(path) <= tuple(reversed(path)) else tuple(reversed(path))
                if canonical in seen:
                    continue
                seen.add(canonical)
                paths.append(path)
    return paths

3. 为路径评分

将“3个得分路径”阶段视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。在功能结果旁同时记录执行时间以及令牌或查询成本。提前了解这些成本,就能避免在路径从演示环境转移到共享环境时出现意外费用。应将分块策略与检索策略分开,当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。

def score_path(graph, path, query):
    query_tokens = set(tokenize(query))
    path_nodes = [node.lower() for node in path]
    path_rels = []

    for i in range(len(path) - 1):
        rel, _ = get_edge_relation(graph, path[i], path[i + 1])
        path_rels.append(rel.lower())

    path_text = " ".join(path_nodes + path_rels)

    overlap_score = len(query_tokens.intersection(set(tokenize(path_text)))) * 10
    node_score = sum(1 for node in path_nodes if any(token in node for token in query_tokens)) * 5
    rel_score = sum(1 for rel in path_rels if any(token in rel for token in query_tokens)) * 8

    length_penalty = max(0, len(path) - 2) * 2
    connection_bonus = sum(len(node.split()) for node in path_nodes)

    return overlap_score + node_score + rel_score + connection_bonus - length_penalty

4. 将最佳路径转化为上下文

将“4个转换最佳阶段”视为可测量的对象时,其效果最为理想。在扩大范围之前,需记录一份最佳处理方案、一个失败案例以及回滚说明。 应将配置与应用程序代码分开。环境文件、密钥存储和功能标志应集中存放于一处,以便操作人员无需查看整个系统结构即可进行审计。 应将分块策略与检索策略分开。当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。 将“4个转换最佳阶段”视为可测量的对象时,其效果最为理想。在扩大范围之前,需记录一份最佳处理方案、一个失败案例以及回滚说明。 相比庞大的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障应指向单一责任模块,而非复杂的处理流程。

def path_to_text(graph, path):
    lines = []
    for i in range(len(path) - 1):
        source = path[i]
        target = path[i + 1]
        relation, reversed_edge = get_edge_relation(graph, source, target)
        if reversed_edge:
            lines.append(f"{target} {relation} {source}.")
        else:
            lines.append(f"{source} {relation} {target}.")
    return " ".join(lines)

5. 按选定路径向大语言模型提问

在“5. 提问大语言模型”阶段,应在修改代码之前明确输入内容、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,定义成功判定标准,并拒绝默许的半完成状态。 当下一步操作是编写代码或调用工具时,优先选择具有结构化格式且经过模式验证的输出,而非自由形式的文字描述。

def generate_llm_answer(query, context):
    prompt = (
        "You are a helpful assistant. Use only the graph facts below to answer the query clearly. "
        "Do not introduce any new information. "
        f"If the answer is not directly supported by these facts, say you don't know.\n\n"
        f"Question: {query}\n\n"
        "Graph facts:\n"
        f"{context}\n\n"
        "Answer with a short explanation of the supporting facts:"
    )

    response = client.responses.create(
        model=config["deployment_name"],
        input=prompt,
        max_output_tokens=250,
        temperature=0.1,
    )
    return response.output_text.strip()

6. 以演示 API 的形式将其公开

对于6个需要分阶段展示的案例,在修改代码之前需明确输入参数、各步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行相应步骤,而无需猜测隐藏状态。除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解这些成本信息,可避免在从演示环境过渡到共享环境时出现意外费用。必须注明支撑答案的具体内容,否则操作人员就无法区分是虚假信息还是索引缺失导致的错误。

@app.post("/query")
def query_graph_rag(request: QueryRequest):
    query = request.query.strip()
    graph = build_graph(SAMPLE_TRIPLES)
    answer, path, context, ranked_paths = answer_query(query, graph)
    return {
        "query": query,
        "answer": answer,
        "reasoning_path": context,
        "path_nodes": path,
        "ranked_paths": ranked_paths,
    }

从原型到生产环境:规模扩展

在将桥接原型过渡到生产环境之前,应在修改代码前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,以便操作人员无需查看整个系统结构即可进行审计。 需引用实际作为答案依据的段落。没有引用的话,操作人员就无法区分是虚假信息还是索引缺失导致的错误。 在将桥接原型过渡到生产环境之前,应在修改代码前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 相较于庞大的脚本,更应优先使用小型且可测试的单元。当某个步骤出现故障时,故障原因应能指向单一的责任主体。

而非错综复杂的管道。

1. 自动提取流程(真正的瓶颈)

在处理“自动提取”阶段时,首先明确合同条款:所需输入、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 将此阶段视为输入与验证后输出之间的契约。为相关成果命名,定义成功检测标准,并拒绝默许的部分完成。 在调整提示词之前,先使用固定的问题集来衡量召回率。仅仅更换提示词很难改善较差的检索效果。

2. 持久化的企业图存储

在处理“2个持久化企业图谱”阶段时,首先需记录相关契约:所需输入、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免从演示环境过渡到共享环境时出现意外费用。 在调整提示词之前,先使用固定的问题集测试召回率。仅仅更换提示词往往无法改善较差的检索效果。

3. 优化后的检索与延迟管理

在实施“优化检索延迟”的三个阶段时,首先需明确相关规范:所需输入、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 应将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,以便操作人员无需查看整个系统结构即可进行审计。 在调整提示词之前,先使用固定的问题集来测试召回率。仅仅更换提示词很难解决检索效果不佳的问题。 在实施“优化检索延迟”的三个阶段时,首先需明确相关规范:所需输入、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 相比庞大的脚本,更应优先使用小型且可测试的单元。当某个步骤出现故障时,故障原因应能明确指向某个具体的功能模块,而非整个复杂的流程。

4. 实施与安全

将“实施安全”这一阶段视为可测量的指标体系,效果最佳。在扩大范围之前,需记录一份标准范本、一个故障案例以及回滚说明。 应将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,明确成功标准,杜绝默许的半完成状态。 需将分块策略与检索策略分开处理。当质量指标发生变化时,调整其中一项不应强制要求重新编写另一项。

架构概要

将“概要架构”阶段视为可度量的对象来处理,效果最佳。在扩大范围之前,先记录一份优秀的测试用例、一个故障案例以及回滚说明。在功能结果旁同时记录执行时间以及令牌或查询成本。提前了解这些成本,就能避免在系统从演示环境过渡到共享环境时出现意外费用。应将分块策略与检索策略分开,当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。

POC系统设计概览

在当前阶段,将POC系统设计视为可度量的对象最为有效。在扩大范围之前,需记录一份理想的处理结果、一个故障案例以及回滚说明。 应将配置与应用程序代码分开。环境文件、密钥存储和功能开关应集中存放,以便操作人员无需查看整个系统结构即可进行审计。 需将分块策略与检索策略区分开来。当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。 在当前阶段,将POC系统设计视为可度量的对象最为有效。在扩大范围之前,需记录一份理想的处理结果、一个故障案例以及回滚说明。 相比庞大的脚本,更应采用小型且可测试的单元。当某个步骤出现故障时,故障原因应能明确指向单一责任模块,而非复杂的流程链。

结果

在“结果”阶段,应在修改代码之前明确输入内容、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 将此阶段视为输入与已验证输出之间的契约。为相关成果命名,定义成功判定标准,并拒绝默许的半完成状态。 需引用实际作为答案依据的段落。没有引用的话,操作人员就无法区分幻觉内容与索引缺失的情况。

何时使用向量RAG与图RAG

在确定何时使用向量阶段时,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。必须引用实际作为答案依据的段落;没有引用的话,操作人员就无法区分是幻觉内容还是索引缺失导致的错误。

为何这很重要

在“为何重要”这一阶段,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,以便操作人员无需查看整个流程即可进行审计。 需引用实际作为答案依据的段落。没有引用的话,操作人员就无法区分是虚假信息还是索引缺失导致的问题。 在“为何重要”这一阶段,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 相较于庞大的脚本,应优先使用小型且可测试的单元。当某个步骤失败时,故障原因应能明确指向某个特定功能,而非整个复杂的流程。

资源

在处理“资源”阶段时,首先写下合同条款:所需的输入、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 将此阶段视为输入与验证后输出之间的契约。为相关成果命名,明确成功标准,杜绝无声的半完成状态。 在调整提示词之前,先使用固定的问题集来测试召回率。仅仅更换提示词很难改善较差的检索效果。

你的看法是什么?

在“你的看法”阶段工作时,首先写下接口规范:所需输入、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。

操作检查清单

在“操作检查清单”阶段工作时,首先写下接口规范:所需输入、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。

同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误处理方式都是产品的一部分,而非后续需要补充的内容。

在调整提示词之前,先在固定的问题集上测试召回率。频繁更换提示词很难改善较差的检索效果。

锁定依赖版本,并记录用于演示的图像摘要。可重复性比经验知识更重要。

优先选择小型、易于测试的单元,而非庞大的脚本。当某个步骤出错时,错误应指向单一责任点,而非复杂的流程链。

在调整提示词之前,先在固定的问题集上测试召回率。频繁更换提示词很难改善较差的检索效果。

在推广整个技术栈之前,先冻结版本,为关键路径保存标准记录,并确认回滚步骤。共享环境需要设置速率限制、租户检查机制,以及明确的密钥轮换负责人。与其追求花哨的一次性演示,不如注重扎实的可靠性。

关于8e81aec03ffd的批处理说明:不要将提供者密钥放入代码仓库,为每个会话设置令牌上限,并将转录内容存储在评估测试用例的旁边,以便后续更换模型时仍能保持可比性。