AI 知识库 SAG 实战:用 SQL 与 GraphRAG 重构检索链路,RAG 该说再见了

发布时间:2026/9/29 3:40:46
AI 知识库 SAG 实战:用 SQL 与 GraphRAG 重构检索链路,RAG 该说再见了 1. 为什么你的向量库召回总是不稳如果你已经搭过一套 RAG 知识库大概率遇到过这种场景用户问“A 产品的上市时间和定价策略之间是什么关系”文档里明明两段都写了一段讲时间一段讲定价但检索出来的结果要么只命中时间要么只命中定价模型拿到半截信息就开始编。你调了 chunk size换了 embedding 模型加了 rerank召回率还是忽高忽低。问题不在你的参数在于传统 RAG 的检索链路本身只做了一件事算向量相似度。它擅长找“长得像”的文本块不擅长找“逻辑上相关”的文本块。两段文字可能一个讲时间一个讲价格字面完全不重叠向量距离很远但它们通过同一个产品实体关联在一起。这种关联纯向量检索看不见。GraphRAG 试图补上关系这一环思路是离线把实体和关系抽出来建一张全局图。但建图成本高增量更新更麻烦——新文档进来理论上整张图都要重算。对大多数团队来说这套方案太重了。SAGSQL-Retrieval Augmented Generation换了个思路不预先建全局图而是在查询时用 SQL 动态织一张只属于这次查询的局部关系网。文档入库时每个 chunk 提炼成事件同时抽出实体事件和实体之间的关联写进 SQL 表。查询来了先用向量找到种子事件再沿着共享实体用 SQL 把相关事件串起来。简单题走快速模式纯向量召回复杂题走精确模式现织超边。这篇文章面向已经有一套向量库、但召回不稳的开发者交付可复制的配置骨架、SQL 图查询示例以及召回对比验证动作。目标是把现有 RAG 链路平滑升级为 SAG 混合检索不需要推倒重来。2. TaoToken 前置把模型调用层先统一SAG 的检索链路里有两个地方要调模型一是文档入库时的事件提炼和实体抽取二是精确模式下的 LLM 精排。如果你用多个厂商的模型接口格式、鉴权方式、计费口径都不一样调试起来很烦。我建议先把模型调用层统一到一个 OpenAI 兼容的入口上。TaoToken 提供的就是这个入口。它兼容 OpenAI 的接口格式你现有的 LangChain、LlamaIndex 或者自己写的 requests 调用改一下 base_url 和 api_key 就能切过来。对 SAG 这种需要频繁调模型做抽取和精排的场景统一入口的好处是换模型不用改代码计费在一个地方看排查问题时链路清晰。具体操作上你需要在 TaoToken 控制台创建一个 API Key然后在 SAG 的配置里把模型端点指向 TaoToken 的 API 地址。SAG 本身接的是 OpenAI 兼容接口所以配置项就是标准的 base_url api_key model name 三件套。这里有个细节要注意SAG 的 embedding 模型和 LLM 可以分开配。embedding 建议用你本地已经验证过的模型LLM 走 TaoToken 统一调用。这样检索质量不受影响生成和精排部分享受统一入口的便利。如果你还没创建 Key可以先去控制台建一个接入文档里有完整的参数说明。模型对话入口可以用来快速验证 Key 是否可用不用写代码就能测通。3. 可复制配置config.toml 与 settings.json 骨架SAG 的配置分两层config.toml 管服务端和存储settings.json 管检索策略和模型端点。下面这份骨架你可以直接复制改掉标注的地方就能跑。3.1 config.toml存储与解析配置[server] host 0.0.0.0 port 8000 [storage] # 本地开发用 sqlite lancedb生产切 postgres pgvector backend sqlite sqlite_path ./data/sag.db lance_path ./data/lance # 生产环境取消下面注释改 backend postgres # [storage.postgres] # dsn postgresql://user:passlocalhost:5432/sag # vector_table sag_vectors [parser] # PDF 优先 MinerU失败回退 MarkItDown pdf_engine mineru fallback_engine markitdown office_engine markitdown [extraction] # 事件提炼和实体抽取的并发数按你机器配置调 concurrency 4 # 每个 chunk 最多抽几个实体 max_entities_per_chunk 8 # 事件摘要最大长度 max_event_summary_len 200这份配置的关键在 storage 段。本地开发用 SQLite LanceDB零依赖docker compose down 不丢数据。生产环境切 PostgreSQL pgvector改一行 backend 就行导入、抽取、检索的代码不用动。parser 段里 MinerU 负责 PDF 解析它对表格和复杂版面的还原比默认解析器好不少。如果 MinerU 没装或者解析失败自动回退 MarkItDown不会卡住整个入库流程。3.2 settings.json检索策略与模型端点{ retrieval: { mode: hybrid, fast_mode: { enabled: true, top_k: 10, vector_weight: 1.0 }, precise_mode: { enabled: true, seed_k: 5, expand_hops: 2, llm_rerank: true, final_k: 8 } }, model: { base_url: https://taotoken.net/api, api_key: sk-your-taotoken-key, llm_model: gpt-4o-mini, embedding_model: text-embedding-3-small, embedding_dim: 1536 }, sql_graph: { enable_dynamic_hyperedge: true, max_events_per_query: 50, shared_entity_threshold: 1 } }retrieval 段是 SAG 的核心。fast_mode 走纯向量召回top_k 设 10适合日常随手搜。precise_mode 走 multi 策略先用向量找 5 个种子事件然后沿着共享实体扩展 2 跳再用 LLM 精排最终返回 8 条。expand_hops 控制扩展深度2 跳通常够用设太大召回会发散。model 段的 base_url 指向 TaoToken 的 API 地址api_key 换成你自己的。embedding_model 和 llm_model 可以不同embedding 用你验证过的LLM 走统一入口。sql_graph 段控制动态超边行为。shared_entity_threshold 设 1 表示只要两个事件共享至少一个实体就建立关联设 2 则要求共享两个以上召回更严但可能漏。建议从 1 开始观察效果再调。4. SQL 图查询示例动态超边怎么织SAG 的 SQL 表结构不复杂核心就三张表events事件、entities实体、event_entities事件-实体关联。查询时动态超边的逻辑本质是在这三张表上做一次 join。4.1 表结构CREATE TABLE events ( id INTEGER PRIMARY KEY, chunk_id TEXT NOT NULL, summary TEXT NOT NULL, embedding_id TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE entities ( id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, entity_type TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE event_entities ( event_id INTEGER NOT NULL, entity_id INTEGER NOT NULL, PRIMARY KEY (event_id, entity_id), FOREIGN KEY (event_id) REFERENCES events(id), FOREIGN KEY (entity_id) REFERENCES entities(id) ); CREATE INDEX idx_event_entities_entity ON event_entities(entity_id); CREATE INDEX idx_event_entities_event ON event_entities(event_id);events 表存事件摘要和对应的向量 IDentities 表存实体名和类型event_entities 是关联表。两个索引分别加速“从实体找事件”和“从事件找实体”两个方向。4.2 动态超边查询假设向量检索已经返回了种子事件 ID 列表 [101, 205, 318]现在要沿着共享实体扩展WITH seed_events AS ( SELECT id FROM events WHERE id IN (101, 205, 318) ), seed_entities AS ( SELECT DISTINCT ee.entity_id FROM event_entities ee JOIN seed_events se ON ee.event_id se.id ), expanded_events AS ( SELECT DISTINCT ee.event_id FROM event_entities ee JOIN seed_entities se ON ee.entity_id se.entity_id WHERE ee.event_id NOT IN (SELECT id FROM seed_events) ) SELECT e.id, e.summary, COUNT(DISTINCT ee.entity_id) AS shared_count FROM events e JOIN event_entities ee ON e.id ee.event_id WHERE e.id IN (SELECT event_id FROM expanded_events) GROUP BY e.id HAVING shared_count 1 ORDER BY shared_count DESC LIMIT 20;这段 SQL 做了四件事先锁定种子事件再找出种子事件涉及的所有实体然后沿着这些实体找到关联的其他事件最后按共享实体数量排序。shared_count 越高说明这个事件和种子事件的关联越强。4.3 两跳扩展如果一跳不够可以再套一层WITH RECURSIVE hop_expand AS ( -- 第一跳种子事件 SELECT id AS event_id, 0 AS hop FROM events WHERE id IN (101, 205, 318) UNION -- 第二跳沿共享实体扩展 SELECT ee2.event_id, he.hop 1 FROM hop_expand he JOIN event_entities ee1 ON he.event_id ee1.event_id JOIN event_entities ee2 ON ee1.entity_id ee2.entity_id WHERE he.hop 2 ) SELECT DISTINCT e.id, e.summary FROM events e JOIN hop_expand he ON e.id he.event_id WHERE he.hop 0 LIMIT 30;递归 CTE 控制扩展跳数hop 2 表示最多两跳。实际用的时候两跳通常能覆盖大部分多跳问答场景再深就容易引入噪声。5. 验证请求召回对比怎么做配置改完得验证 SAG 混合检索比原来的纯向量召回好多少。我建议用同一批问题、同一套文档跑两组对比。5.1 准备测试集挑 20 到 30 个需要跨文档推理的问题比如“X 项目的技术选型和上线时间有什么关联”“Y 产品的定价调整对竞品策略的影响”。每个问题标注出应该命中的文档片段 ID。这批问题要覆盖简单事实查询和多跳推理两类比例大概 3:7。5.2 跑对比脚本import requests import json SAG_API http://localhost:8000/api/retrieve TAOTOKEN_KEY sk-your-taotoken-key def query_sag(question, modeprecise): resp requests.post(SAG_API, json{ query: question, mode: mode, top_k: 8 }, headers{Authorization: fBearer {TAOTOKEN_KEY}}) return resp.json()[results] def query_baseline(question, top_k8): # 你原来的纯向量检索接口 resp requests.post(http://localhost:8001/vector-search, json{ query: question, top_k: top_k }) return resp.json()[results] def recall_at_k(results, ground_truth_ids, k): retrieved [r[chunk_id] for r in results[:k]] hits len(set(retrieved) set(ground_truth_ids)) return hits / len(ground_truth_ids) test_cases json.load(open(test_cases.json)) sag_scores, baseline_scores [], [] for case in test_cases: q case[question] gt case[ground_truth_chunk_ids] sag_res query_sag(q, modeprecise) base_res query_baseline(q) sag_scores.append(recall_at_k(sag_res, gt, 5)) baseline_scores.append(recall_at_k(base_res, gt, 5)) print(fSAG Recall5: {sum(sag_scores)/len(sag_scores):.4f}) print(fBaseline Recall5: {sum(baseline_scores)/len(baseline_scores):.4f})5.3 看结果跑完你会看到两组数字。根据 SAG 论文的数据多跳推理场景下 Recall5 能从 65% 左右提到 80% 左右简单事实查询差距不大。如果你的测试集里多跳问题占七成整体提升应该在 10 到 15 个百分点。如果 SAG 的分数反而低了先检查三件事种子事件的质量向量检索本身召回差的话后面扩展也没用、shared_entity_threshold 是不是设太高、expand_hops 是不是设太大引入了噪声。6. 本篇常见错排查6.1 入库卡在“抽取中”不动最常见的原因是模型端点不通。SAG 入库时要调 LLM 做事件提炼和实体抽取如果 base_url 或 api_key 配错请求会一直重试。先去模型对话入口测一下 Key 是否可用再检查 settings.json 里的 base_url 有没有多写斜杠或者少写路径。另一个可能是 concurrency 设太高本地模型扛不住。把 extraction.concurrency 降到 2 试试。6.2 精确模式召回结果比快速模式还少检查 sql_graph.max_events_per_query 是不是设太小。这个值限制单次查询最多扩展多少事件设太小的话种子事件扩展不开结果自然少。默认 50可以调到 100 试试。还有可能是 shared_entity_threshold 设成了 2 或更高导致只有共享多个实体的事件才被召回。先降到 1看结果是否改善。6.3 SQL 查询报错“no such table”如果你切了 PostgreSQL 但没跑建表语句会报这个错。SAG 启动时通常会自动建表但如果用的是外部 PostgreSQL 且权限受限自动建表可能失败。手动执行第 4.1 节的建表 SQL 即可。SQLite 路径下报这个错检查 config.toml 里的 sqlite_path 目录是否存在SAG 不会自动创建目录。6.4 召回结果里重复 chunk 很多这是动态超边扩展的正常现象——同一个 chunk 可能通过多个实体路径被召回。在最终返回前做一次去重就行按 chunk_id 去重保留 shared_count 最高的那条。如果重复率超过 50%说明 expand_hops 设太大了降到 1 或者把 shared_entity_threshold 提到 2。6.5 切换 PostgreSQL 后检索变慢pgvector 的索引没建。默认建表只建了主键索引向量检索需要额外建 IVFFlat 或 HNSW 索引CREATE INDEX ON sag_vectors USING ivfflat (embedding vector_cosine_ops) WITH (lists 100);lists 的值按数据量调一般 sqrt(行数) 左右。数据量小的时候建索引反而慢可以先不建等数据过万再加。7. 把检索链路升级完接下来做什么配置跑通、召回对比做完之后你手里就有了一套 SAG 混合检索链路。原来的向量库不用拆SAG 是在它上面加了一层事件-实体关联和动态超边扩展。快速模式继续走纯向量精确模式走 SQL 图查询两套路径共用一个存储。如果你打算长期用这套链路做编码辅助或者 Agent 的知识后端建议把模型调用层固定到 TaoToken 的 Coding Plan 上。SAG 的精确模式每次查询都要调 LLM 做精排调用频率不低统一计费和统一端点能省不少事。接入文档里有完整的参数说明和示例代码照着改 base_url 和 api_key 就行。检索链路升级完之后下一步通常是把它接到 Agent 上。SAG 本身开放了 OpenAI 兼容接口你的 Agent 可以把它当一个带引用的模型来调。如果用的是 Claude Code 这类工具官方有个 sag-cli 可以一条命令挂载知识库JWT 存在系统凭据里不写进配置文件。这部分等你的检索链路稳定运行一周之后再折腾先把召回质量调到位。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询