概述
GraphRAG 是将知识图谱与 RAG 结合的高级检索范式。通过实体抽取构建知识图谱(存储在 Apache AGE 中),结合向量检索(pgvector),实现比纯向量 RAG 更精准的多跳推理能力。
GraphRAG vs 传统 RAG
维度 | 传统 RAG | GraphRAG |
检索方式 | 纯向量相似度 | 图遍历 + 向量混合 |
多跳推理 | 不支持 | 通过图关系推理 |
实体关系 | 隐含在文本中 | 显式存储为图结构 |
全局理解 | 局限于单个 chunk | 通过图获得全局视角 |
适用场景 | 简单问答 | 复杂推理、关系查询 |
架构设计
┌──────────────────────────────────────────────────────┐│ GraphRAG 系统 │├──────────────────────────────────────────────────────┤│ ││ 文档输入 → 实体抽取 → 图谱构建 → 索引建立 ││ ││ 用户查询 → 实体识别 → 图检索 + 向量检索 → 融合生成 ││ │├──────────────────────────────────────────────────────┤│ 存储层 ││ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ ││ │ Apache AGE │ │ pgvector │ │ 关系表 │ ││ │ (知识图谱) │ │ (文档向量) │ │ (元数据) │ ││ └──────────────┘ └──────────────┘ └───────────┘ ││ ││ AI 层 ││ ┌─────────────────────────────────────────────┐ ││ │ tencentdb_ai(实体抽取 / 关系识别 / 生成) │ ││ └─────────────────────────────────────────────┘ │└──────────────────────────────────────────────────────┘
数据库准备
-- 创建扩展CREATE EXTENSION IF NOT EXISTS vector;CREATE EXTENSION IF NOT EXISTS age;-- 设置 search_pathSET search_path = ag_catalog, "$user", public;-- 创建知识图谱SELECT create_graph('knowledge_graph');-- 文档存储表CREATE TABLE graphrag.documents (id BIGSERIAL PRIMARY KEY,title TEXT,content TEXT NOT NULL,embedding vector(1024),metadata JSONB DEFAULT '{}',created_at TIMESTAMPTZ DEFAULT NOW());CREATE INDEX ON graphrag.documentsUSING hnsw (embedding vector_cosine_ops);-- 实体表(辅助索引)CREATE TABLE graphrag.entities (id BIGSERIAL PRIMARY KEY,name TEXT NOT NULL,entity_type TEXT NOT NULL,description TEXT,embedding vector(1024),source_doc_id BIGINT REFERENCES graphrag.documents(id),UNIQUE(name, entity_type));CREATE INDEX ON graphrag.entitiesUSING hnsw (embedding vector_cosine_ops);
核心流程
说明:
向量维度需与您使用的大模型输出维度一致。常见模型维度:OpenAI text-embedding-3-small(1536维)、BGE-M3(1024维)、ChatGLM Embedding(1024维)。
实体抽取
-- 使用大模型从文档中抽取实体和关系CREATE OR REPLACE FUNCTION graphrag.extract_entities(doc_content TEXT)RETURNS JSONB AS $$DECLAREresult TEXT;BEGINSELECT tencentdb_ai.chat_completions('<your-llm-model>','请从以下文本中抽取实体和关系,以 JSON 格式返回。' ||'格式:{"entities": [{"name": "实体名", "type": "类型", "description": "描述"}], ' ||'"relations": [{"source": "源实体", "target": "目标实体", "relation": "关系类型", "description": "描述"}]}' ||E'\\n\\n文本:' || doc_content) INTO result;RETURN result::JSONB;END;$$ LANGUAGE plpgsql;
图谱构建(含安全校验)
来自 LLM 的
entity.type / relation.relation 会作为 Cypher 标签拼入查询,必须使用白名单校验,禁止任意字符串进入 Cypher:-- 允许的实体类型与关系类型白名单CREATE TABLE IF NOT EXISTS graphrag.allowed_entity_types (type TEXT PRIMARY KEY);CREATE TABLE IF NOT EXISTS graphrag.allowed_relation_types (type TEXT PRIMARY KEY);INSERT INTO graphrag.allowed_entity_types(type) VALUES('Person'), ('Company'), ('Product'), ('Technology'), ('Concept')ON CONFLICT DO NOTHING;INSERT INTO graphrag.allowed_relation_types(type) VALUES('WORKS_AT'), ('USES'), ('HAS_FEATURE'), ('RELATED_TO'), ('PART_OF')ON CONFLICT DO NOTHING;-- 将抽取的实体存入图CREATE OR REPLACE FUNCTION graphrag.build_graph(doc_id BIGINT)RETURNS VOID AS $$DECLAREdoc_content TEXT;extracted JSONB;entity JSONB;relation JSONB;e_type TEXT;r_type TEXT;BEGINSELECT content INTO doc_content FROM graphrag.documents WHERE id = doc_id;extracted := graphrag.extract_entities(doc_content);-- 创建实体节点(白名单校验类型)FOR entity IN SELECT * FROM jsonb_array_elements(extracted->'entities')LOOPe_type := entity->>'type';IF NOT EXISTS (SELECT 1 FROM graphrag.allowed_entity_types WHERE type = e_type) THENRAISE NOTICE 'Skip entity with disallowed type: %', e_type;CONTINUE;END IF;PERFORM * FROM cypher('knowledge_graph', format($$MERGE (n:%I {name: %L})SET n.description = %L, n.doc_id = %s$$, e_type, entity->>'name', entity->>'description', doc_id))AS (v agtype);END LOOP;-- 创建关系(白名单校验关系类型)FOR relation IN SELECT * FROM jsonb_array_elements(extracted->'relations')LOOPr_type := relation->>'relation';IF NOT EXISTS (SELECT 1 FROM graphrag.allowed_relation_types WHERE type = r_type) THENRAISE NOTICE 'Skip relation with disallowed type: %', r_type;CONTINUE;END IF;PERFORM * FROM cypher('knowledge_graph', format($$MATCH (a {name: %L}), (b {name: %L})MERGE (a)-[r:%I]->(b)SET r.description = %L$$, relation->>'source', relation->>'target',r_type, relation->>'description'))AS (r agtype);END LOOP;END;$$ LANGUAGE plpgsql;
关键点:
节点标签 / 关系类型:使用 %I 安全标识符 + 白名单双重校验。
属性值:使用 %L 安全转义。
不通过白名单:直接跳过并打日志,不允许任意字符串落入 Cypher。
GraphRAG 检索
-- 混合检索:图遍历 + 向量搜索CREATE OR REPLACE FUNCTION graphrag.hybrid_retrieve(query TEXT, top_k INT DEFAULT 5)RETURNS TABLE(source TEXT, content TEXT, score FLOAT) AS $$BEGIN-- 向量检索文档RETURN QUERYSELECT 'vector'::TEXT, d.content,(1 - (d.embedding <=> tencentdb_ai.get_embedding('bge-m3', query)))::FLOATFROM graphrag.documents dORDER BY d.embedding <=> tencentdb_ai.get_embedding('bge-m3', query)LIMIT top_k;-- 图检索相关实体RETURN QUERYSELECT 'graph'::TEXT,(a->>'name' || ' --[' || r->>'relation' || ']--> ' || b->>'name')::TEXT,0.8::FLOATFROM cypher('knowledge_graph', format($$MATCH (a)-[r]->(b)WHERE a.name CONTAINS %L OR b.name CONTAINS %LRETURN properties(a) as a, properties(r) as r, properties(b) as bLIMIT %s$$, query, query, top_k)) AS (a agtype, r agtype, b agtype);END;$$ LANGUAGE plpgsql;
生成回答
-- GraphRAG 问答CREATE OR REPLACE FUNCTION graphrag.answer(question TEXT)RETURNS TEXT AS $$DECLAREcontext TEXT;answer TEXT;BEGINSELECT string_agg('[' || source || '] ' || content, E'\\n')INTO contextFROM graphrag.hybrid_retrieve(question, 5);SELECT tencentdb_ai.chat_completions('<your-llm-model>','基于以下知识图谱信息和文档内容回答问题。图谱信息标记为 [graph],文档内容标记为 [vector]。' ||E'\\n\\n上下文:\\n' || COALESCE(context, '无') ||E'\\n\\n问题:' || question) INTO answer;RETURN answer;END;$$ LANGUAGE plpgsql;
使用示例
-- 导入文档并构建图谱INSERT INTO graphrag.documents (title, content, embedding)VALUES ('PostgreSQL 与 AI','腾讯云 PostgreSQL 通过 tencentdb_ai 插件支持在 SQL 中直接调用大模型...',tencentdb_ai.get_embedding('bge-m3', '腾讯云 PostgreSQL 通过 tencentdb_ai 插件支持在 SQL 中直接调用大模型'));-- 构建图谱SELECT graphrag.build_graph(1);-- GraphRAG 问答SELECT graphrag.answer('PostgreSQL 支持哪些 AI 能力?');
实践
1. 实体去重:使用
MERGE 避免重复实体。2. 增量更新:新文档只增量构建,不全量重建。
3. 图索引:为频繁查询的属性创建索引。
4. 社区发现:利用图算法做文档聚类。
5. 混合召回:图上下文 + 向量文档互补。
6. 白名单保护:实体类型 / 关系类型必须经过白名单,杜绝来自 LLM 的任意字符串注入。