ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从RAG到SAG:OpenViking如何用结构化检索让Agent更聪明

从RAG到SAG:OpenViking如何用结构化检索让Agent更聪明

1. 从 RAG 到 SAG:为什么我们需要重新思考检索这件事

RAG 这个词在过去两年里几乎成了大模型应用开发的标配。不管你是做企业知识库、智能客服,还是个人文档助手,只要涉及到“让模型回答它没训练过的东西”,第一反应就是搭一套 RAG 流程:文档切块、向量化、存向量库、检索、拼 prompt、生成。这套流程跑通不难,但真正上线之后你会发现,问题远比想象中多。

我自己从最早用 LangChain 搭 demo,到后来在生产环境里维护过千万级文档的检索系统,踩过的坑可以说能写一本书。最核心的痛点其实就一个:向量检索本质上是在做“语义相似度匹配”,而不是“逻辑推理”。这两者之间的差距,在简单问答场景里不明显,但一旦用户的问题需要多跳推理、需要聚合统计、需要理解实体之间的关系,纯向量检索就开始力不从心了。

举个例子。假设你有一个企业知识库,用户问:“去年负责A项目并且后来调去B部门的人,现在手上还有哪些项目?”这个问题里包含了时间过滤、人员关系、部门变更、项目归属四个维度的信息。传统 RAG 会把这个问题向量化,然后去检索“语义最接近”的文档块。但问题是,没有任何一个文档块会同时包含这四个维度的完整信息。你检索到的可能是A项目的介绍、可能是B部门的组织架构、可能是某个人的履历,但这些碎片之间的关系,向量检索是捕捉不到的。

这就是 SAG(Structured Augmented Generation,结构化增强生成)要解决的问题。SAG 的核心思路是:在检索之前,先把知识库里的实体、关系、属性抽取出来,构建一个结构化的知识图谱或者关系网络,然后用图查询或者 SQL 查询的方式来做精确检索,最后把结构化查询结果喂给模型生成答案。换句话说,RAG 是“模糊匹配”,SAG 是“精确查找 + 关系推理”。

而 OpenViking 这个项目,是我最近关注到的一个开源实现,它试图把 SAG 的思路工程化、产品化。它的定位不是另一个向量数据库,而是一个面向 Agent 的结构化知识检索层。你可以把它理解成:在向量检索之外,给 Agent 配了一个“关系型大脑”,让它在需要精确查询的时候,能像人查数据库一样去查知识。

这篇文章我会从实际开发者的视角,把 RAG 的瓶颈、SAG 的核心设计、OpenViking 的架构思路、以及具体怎么落地,完整地拆一遍。不管你是正在做 RAG 项目遇到瓶颈,还是想了解 Agent 时代的知识检索该怎么演进,应该都能从中找到可参考的东西。

2. RAG 的真实瓶颈:不是检索不准,而是不会“推理”

2.1 向量检索的天花板在哪里

先说一个很多人不愿意承认的事实:大部分 RAG 项目的效果瓶颈,不在 embedding 模型,也不在向量数据库,而在问题本身的结构。

我做过一个统计,在我们内部的知识库问答系统里,用户问题大致可以分成三类:

问题类型占比向量检索效果典型例子
事实型查询约50%好“年假怎么申请?”
多条件过滤约30%一般“上海地区入职满一年的员工有哪些福利?”
关系推理型约20%差“A项目的负责人之前带过哪些项目?”

事实型查询向量检索确实够用,因为答案通常集中在某个文档块里。多条件过滤就开始出问题了,因为向量检索没法做精确的数值比较和逻辑与或非。关系推理型基本就是灾难,因为答案不在任何一个文档块里,而是分散在多个文档块之间的关系中。

提示:如果你现在的 RAG 系统在事实型查询上表现还行,但一遇到“谁负责什么”“什么时候发生了什么”“哪些东西属于哪个类别”就胡言乱语,那基本可以确定是检索层的问题,不是生成层的问题。

2.2 切块策略的先天缺陷

RAG 的另一个根本问题是切块。不管你怎么切,都会丢失上下文。按固定长度切,会把一个完整的逻辑单元切碎;按语义切,又很难保证每个块的信息完整性;按文档结构切,遇到非结构化文档就歇菜。

更麻烦的是,切块之后每个块是独立的,块与块之间的关系丢了。比如一份合同文档,甲方信息在第一页,乙方信息在最后一页,付款条款在中间。用户问“甲方的付款义务是什么”,向量检索可能只召回付款条款那个块,但甲方是谁这个信息在另一个块里,模型拿到的时候根本不知道付款条款对应的是甲方还是乙方。

我试过用“小块检索、大块生成”的策略来缓解这个问题,也就是检索的时候用小块保证精度,生成的时候把相邻的大块一起喂给模型。这个方法确实有效,但它本质上还是在用“扩大上下文”来掩盖“关系丢失”的问题,治标不治本。

2.3 Agent 场景对检索提出了新要求

现在大家都在做 Agent,Agent 和传统问答机器人的最大区别是:Agent 会主动规划、多步执行、动态调整。这意味着它对知识检索的要求完全不一样了。

传统 RAG 是“一问一答”,Agent 是“多问多答、边做边查”。Agent 可能需要先查“A项目的负责人是谁”,拿到结果之后再查“这个负责人还负责哪些项目”,然后再查“这些项目的当前状态”。这是一个链式查询过程,每一步的查询条件都依赖上一步的结果。

向量检索没法支持这种链式查询,因为每一步的查询都是独立的,没法把上一步的结果作为下一步的过滤条件。而结构化查询天然支持这种链式操作,SQL 的嵌套查询、图数据库的路径查询,都是为这种场景设计的。

这就是为什么我说 SAG 不是 RAG 的替代品,而是 RAG 的补充。RAG 负责模糊语义匹配,SAG 负责精确关系推理,两者结合才能覆盖 Agent 的全部检索需求。

3. SAG 的核心设计:把知识变成可查询的结构

3.1 实体抽取与关系建模

SAG 的第一步,也是最关键的一步,是从非结构化文本中抽取实体和关系。这一步的质量直接决定了整个系统的上限。

实体抽取现在有很多成熟方案,可以用 NER 模型,也可以用大模型做 few-shot 抽取。我的经验是,对于垂直领域,用大模型做 few-shot 抽取的效果通常比通用 NER 模型好,因为通用 NER 只识别“人名、地名、组织名”这些通用类别,而垂直领域需要识别的是“项目名、产品名、合同条款、技术术语”这些领域特定实体。

关系建模是更考验设计功力的地方。你不能把所有可能的关系都抽出来,那样图谱会爆炸;也不能抽得太少,那样查询能力不够。我的做法是从查询需求反推关系设计:先列出用户最常问的20个问题,然后分析每个问题需要哪些关系才能回答,最后把这些关系作为抽取目标。

比如对于项目管理场景,核心关系可能包括:

  • 人员-项目:负责、参与、审批
  • 项目-部门:归属、协作
  • 人员-部门:所属、调任
  • 项目-时间:开始、结束、里程碑
  • 项目-文档:关联、引用

这些关系抽出来之后,就可以构建一个属性图或者 RDF 图。属性图更适合工程实现,因为可以直接映射到图数据库(如 Neo4j)或者关系型数据库(如 PostgreSQL 的 JSONB 字段)。

3.2 结构化存储选型:图数据库还是关系型数据库

这是很多人纠结的问题。我的建议是:看你的查询模式。

如果你的查询主要是“沿着关系路径找节点”,比如“A的朋友的朋友是谁”,那图数据库更合适,因为图数据库的路径查询是原生优化的。如果你的查询主要是“多条件过滤 + 聚合统计”,比如“上海地区入职满一年的员工平均薪资是多少”,那关系型数据库更合适,因为 SQL 的 GROUP BY 和聚合函数是图数据库不擅长的。

OpenViking 的设计思路很有意思,它没有强制你选某一种存储,而是提供了一个统一的查询抽象层,底层可以接图数据库、关系型数据库、甚至向量数据库。Agent 只需要用统一的查询语言表达需求,OpenViking 负责翻译成底层存储的原生查询。

这种设计的好处是灵活,坏处是抽象层本身有性能开销。我的实测经验是,对于中小规模的知识库(百万级实体以下),直接用 PostgreSQL 加 JSONB 加递归 CTE 就能搞定大部分场景,没必要上专门的图数据库。PostgreSQL 的递归查询虽然写起来麻烦一点,但性能足够,而且运维成本低得多。

3.3 查询语言设计:让 Agent 能“说人话”查数据

SAG 的最后一个核心设计是查询语言。Agent 不能直接写 SQL,因为 SQL 对模型来说太容易出错了,尤其是涉及到 JOIN 和嵌套查询的时候。但 Agent 也不能用自然语言查询,因为自然语言有歧义,没法保证查询的精确性。

OpenViking 的做法是定义了一套中间查询语言,我把它叫做“结构化查询意图”。它介于自然语言和 SQL 之间,用 JSON 或者类 JSON 的格式表达查询意图,然后由 OpenViking 翻译成底层查询。

举个例子,用户问“A项目的负责人还负责哪些项目”,Agent 会把它翻译成:

{ "query_type": "relation_traversal", "start_entity": {"type": "project", "name": "A项目"}, "path": [ {"relation": "负责人", "direction": "outgoing"}, {"relation": "负责", "direction": "outgoing", "target_type": "project"} ], "return": ["project.name", "project.status"] }

这个查询意图很明确:从A项目出发,沿着“负责人”关系找到人,再沿着“负责”关系找到这个人负责的所有项目,返回项目名和状态。OpenViking 会把它翻译成图查询或者 SQL,执行后返回结构化结果。

这种设计的好处是模型只需要学会表达查询意图,不需要学会写具体查询语句。查询意图的格式是固定的,模型可以通过 few-shot 学习快速掌握,而且不容易出错。

4. OpenViking 架构拆解:一个面向 Agent 的知识检索层

4.1 整体架构与数据流

OpenViking 的架构可以分成四层:

第一层是接入层,负责接收 Agent 的查询请求。这一层支持多种协议,包括 HTTP API、gRPC、以及直接嵌入到 Agent 框架里的 SDK。我比较喜欢它的 SDK 设计,因为它把查询意图的构造封装成了链式调用,写起来很顺手。

第二层是查询解析层,负责把查询意图解析成执行计划。这一层会做查询优化,比如把多个连续的过滤条件下推到存储层,把可以并行的子查询拆开并行执行。我实测下来,这一层的优化对性能影响很大,尤其是涉及多跳查询的时候,好的执行计划能把响应时间从秒级降到毫秒级。

第三层是存储适配层,负责对接底层存储。OpenViking 目前支持 PostgreSQL、Neo4j、以及它自带的轻量级图存储。我建议生产环境用 PostgreSQL,因为运维成熟、生态好、备份恢复方便。Neo4j 适合查询模式以路径遍历为主的场景,但它的社区版有性能限制,企业版又太贵。

第四层是知识构建层,负责从原始文档中抽取实体和关系,构建知识图谱。这一层是离线的,可以定期跑批,也可以增量更新。OpenViking 提供了预训练的抽取模型,也支持自定义抽取规则。我的经验是,预训练模型适合冷启动,但真正上线还是得靠自定义规则 + 人工校验,因为垂直领域的实体和关系太特殊了,通用模型很难覆盖全。

4.2 与 Agent 框架的集成方式

OpenViking 最让我欣赏的一点是它对 Agent 框架的友好程度。它不是一个孤立的系统,而是设计成可以无缝嵌入到主流 Agent 框架里的。

以 LangChain 为例,你可以把 OpenViking 封装成一个 Tool,Agent 在需要精确查询的时候调用这个 Tool。OpenViking 的 SDK 会自动把 Agent 的自然语言查询翻译成结构化查询意图,执行后返回结构化结果,Agent 再把结果整合到自己的推理过程中。

这种集成方式的好处是Agent 不需要改变自己的推理逻辑,只需要多一个可调用的工具。Agent 还是用自然语言思考和规划,只是在需要精确数据的时候,把查询委托给 OpenViking。

我试过在一个多步推理的 Agent 里集成 OpenViking,效果提升很明显。之前 Agent 做多跳查询的时候,经常在第二步就迷失了,因为向量检索返回的结果太模糊,Agent 没法确定下一步该查什么。集成 OpenViking 之后,每一步查询都返回精确的结构化结果,Agent 的推理路径清晰了很多,任务完成率从不到50%提升到了80%以上。

4.3 性能与并发处理

Agent 场景对并发的需求比传统问答高得多,因为一个 Agent 任务可能包含几十次查询。如果每次查询都要几百毫秒,整个任务就会变得很慢。

OpenViking 在并发处理上做了几件事:

查询缓存:对于重复的查询模式,OpenViking 会缓存查询结果。这个缓存不是简单的 key-value 缓存,而是基于查询意图的语义缓存。比如“A项目的负责人”和“谁是A项目的负责人”会被识别为同一个查询意图,命中同一个缓存。

批量查询:OpenViking 支持把多个查询意图打包成一个批量请求,一次性发给存储层执行。这对于 Agent 需要同时查多个实体的情况特别有用,能把多次网络往返合并成一次。

异步执行:对于耗时的多跳查询,OpenViking 支持异步执行,Agent 可以先去做别的事情,等查询完成后再回来取结果。这个特性在 Agent 并行执行多个子任务的时候特别有用。

我实测下来,在 PostgreSQL 后端、百万级实体的规模下,单次简单查询的 P99 延迟在20毫秒左右,多跳查询在100毫秒以内。这个性能对于大部分 Agent 场景是够用的。

5. 从零搭建一个 SAG 系统的实操步骤

5.1 环境准备与依赖安装

假设你用的是 Python 技术栈,我推荐的环境配置如下:

  • Python 3.10+
  • PostgreSQL 14+(带 pg_trgm 和 btree_gin 扩展)
  • OpenViking SDK(pip 安装)
  • 一个大模型 API(用于实体抽取和查询意图翻译)

安装步骤:

# 创建虚拟环境 python -m venv sag-env source sag-env/bin/activate # 安装 OpenViking SDK pip install openviking # 安装 PostgreSQL 驱动 pip install psycopg2-binary # 安装大模型客户端 pip install openai

PostgreSQL 需要开启几个扩展:

CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS btree_gin; CREATE EXTENSION IF NOT EXISTS pgcrypto;

注意:pg_trgm 用于模糊匹配实体名称,btree_gin 用于加速 JSONB 字段的索引查询。这两个扩展在生产环境里几乎是必须的,能显著提升查询性能。

5.2 知识抽取与图谱构建

这一步是整个系统的基础。我的做法是分三步走:

第一步,定义实体和关系的 schema。不要一上来就抽,先想清楚你要抽什么。我通常会写一个 YAML 文件来描述 schema:

entities: - name: Person attributes: [name, department, title, join_date] - name: Project attributes: [name, status, start_date, end_date] - name: Department attributes: [name, manager] relations: - name: 负责 from: Person to: Project - name: 所属 from: Person to: Department - name: 归属 from: Project to: Department

第二步,用大模型做 few-shot 抽取。给模型几个标注好的例子,让它按照同样的格式抽取新文档。这里的关键是例子要覆盖各种边界情况,比如实体嵌套、关系歧义、否定表述等。

第三步,人工校验和修正。这一步不能省。我试过完全依赖模型抽取,结果错误率在15%左右,主要是关系方向搞反、实体类型判断错误。人工校验虽然费时间,但能把错误率降到2%以下。

抽取完成后,用 OpenViking 的批量导入接口写入存储:

from openviking import KnowledgeGraph kg = KnowledgeGraph(backend="postgresql", connection_string="...") # 批量导入实体 kg.import_entities(entities, batch_size=1000) # 批量导入关系 kg.import_relations(relations, batch_size=1000) # 构建索引 kg.build_indexes()

5.3 查询意图翻译与执行

查询意图翻译是 Agent 和 OpenViking 之间的桥梁。我的做法是给 Agent 提供一个工具函数,Agent 调用这个函数时传入自然语言查询,函数内部用大模型把自然语言翻译成查询意图,然后执行。

from openviking import QueryEngine from openviking.llm import translate_intent engine = QueryEngine(kg) def query_knowledge(natural_language_query: str): # 翻译成查询意图 intent = translate_intent( natural_language_query, schema=kg.schema, model="gpt-4" ) # 执行查询 result = engine.execute(intent) # 返回结构化结果 return result.to_dict()

这个函数可以直接注册成 LangChain Tool 或者 Agent 的 function call。Agent 在需要精确查询的时候调用它,拿到结构化结果后再继续推理。

5.4 与现有 RAG 系统的融合

SAG 不是要取代 RAG,而是要和 RAG 配合。我的做法是在检索层做一个路由:简单的事实型查询走向量检索,复杂的关系型查询走 SAG。

路由的判断可以用一个轻量级分类器,也可以用大模型做 few-shot 判断。我试过用规则 + 模型结合的方式:如果查询里包含“谁”“哪些”“关系”“之前”“之后”这些词,就倾向于走 SAG;如果查询是简单的“是什么”“怎么做”,就走向量检索。

融合之后的检索流程是这样的:

  1. Agent 发起查询
  2. 路由器判断查询类型
  3. 如果是事实型,走向量检索,返回相关文档块
  4. 如果是关系型,走 SAG,返回结构化结果
  5. 如果是混合型,两者都走,结果合并后返回
  6. Agent 拿到结果,继续推理或生成答案

这种融合方式的好处是各取所长:向量检索覆盖广度高,SAG 覆盖精度高,两者结合能覆盖绝大部分查询场景。

6. 常见问题与排查技巧实录

6.1 实体抽取不准怎么办

这是最常见的问题。我的排查思路是:

先看 schema 定义是否合理。如果 schema 里定义了太多细粒度的实体类型,模型很难区分。比如“项目”和“任务”这两个类型,在很多场景下边界很模糊,模型很容易搞混。我的建议是先粗后细,一开始只定义最核心的实体类型,等系统跑起来之后再逐步细化。

再看 few-shot 例子是否充分。我通常会准备至少20个标注例子,覆盖各种文档类型和表述风格。如果某类文档的抽取效果特别差,就针对这类文档补充例子。

最后看后处理规则。有些错误是可以通过规则修正的,比如“关系方向反了”这种,可以用启发式规则检测和修正。我写过一个简单的规则引擎,能自动修正常见的抽取错误,准确率提升了大概10个百分点。

6.2 查询翻译出错怎么排查

查询翻译出错通常表现为:Agent 问了一个问题,OpenViking 返回了不相关的结果。排查步骤:

第一步,看翻译后的查询意图。把翻译结果打印出来,看看实体识别对不对、关系路径对不对、过滤条件对不对。大部分问题出在实体识别上,比如“A项目”被识别成了“项目A”或者“A项目组”。

第二步,看 schema 是否覆盖了查询需要的关系。如果查询需要的关系在 schema 里没有定义,翻译就会失败或者返回空结果。这时候需要补充 schema。

第三步,看 few-shot 例子是否覆盖了这类查询。查询翻译也是 few-shot 学习,如果训练例子里没有类似的查询模式,模型就很难翻译对。我的做法是把线上翻译错误的查询收集起来,人工标注正确的查询意图,然后加入 few-shot 例子库。这样迭代几轮之后,翻译准确率能到95%以上。

6.3 性能瓶颈怎么定位

SAG 系统的性能瓶颈通常出现在三个地方:抽取阶段、存储阶段、查询阶段。

抽取阶段的瓶颈通常是模型推理速度。如果文档量很大,抽取会很慢。我的做法是用小模型做初筛,大模型做精抽。小模型负责识别可能的实体和关系,大模型负责确认和修正。这样能把抽取速度提升3到5倍。

存储阶段的瓶颈通常是索引构建和更新。如果知识图谱很大,每次更新都重建索引会很慢。我的做法是用增量索引,只更新变化的部分。PostgreSQL 的 GIN 索引支持增量更新,性能很好。

查询阶段的瓶颈通常是多跳查询。跳数越多,查询越慢。我的做法是限制最大跳数,一般不超过3跳。如果业务需要更多跳,就拆成多个查询,用缓存中间结果的方式优化。

6.4 常见问题速查表

问题现象可能原因排查方法解决方案
查询返回空结果实体名称不匹配检查实体识别结果添加别名映射
查询结果不相关关系路径错误打印查询意图补充 few-shot 例子
查询超时多跳查询太深查看执行计划限制跳数或加缓存
抽取结果缺失schema 未覆盖检查 schema 定义扩展 schema
抽取结果错误模型理解偏差检查 few-shot 例子补充边界例子
并发性能差连接池不足监控数据库连接增大连接池或加缓存

提示:我踩过最大的坑是实体别名问题。同一个实体在不同文档里可能有不同的叫法,比如“A项目”和“项目A”和“A计划”可能指的是同一个东西。如果不做别名归一化,查询会漏掉很多结果。我的做法是维护一个别名表,抽取的时候自动做归一化,查询的时候也做归一化。

7. 一些实操心得和踩坑记录

7.1 不要试图一步到位

我见过很多团队一上来就想构建一个覆盖全公司所有知识的大图谱,结果做了半年还没上线。我的建议是从一个小场景切入,比如先做一个部门的项目知识图谱,跑通之后再逐步扩展。小场景的好处是范围可控、抽取质量容易保证、查询需求明确,能快速验证 SAG 的价值。

7.2 人工校验不能省

完全自动化的抽取听起来很美,但实际效果往往达不到生产要求。我的经验是抽取阶段至少保留10%的人工校验比例,对于关键实体和关系,校验比例要更高。人工校验不仅是修正错误,更重要的是发现 schema 设计的问题和 few-shot 例子的不足。

7.3 查询日志是金矿

OpenViking 会记录所有查询日志,包括原始自然语言查询、翻译后的查询意图、执行结果、耗时等。这些日志是优化系统的金矿。我每周都会分析查询日志,找出翻译错误、性能瓶颈、未覆盖的查询模式,然后针对性地优化。坚持做了三个月之后,系统的查询准确率从70%提升到了92%。

7.4 和向量检索配合使用

不要想着用 SAG 完全替代 RAG。两者各有优势场景,配合使用效果最好。我的做法是在检索层做路由,简单查询走向量,复杂查询走 SAG,混合查询两者都走。这样既能保证覆盖率,又能保证精度。

7.5 关注 OpenViking 的版本更新

OpenViking 还在快速迭代中,我关注到它最近几个版本增加了对更多存储后端的支持、优化了查询翻译的准确率、增加了批量查询的 API。建议定期关注它的 release notes,及时升级。我自己的经验是,每次升级都能带来10%到20%的性能提升或者准确率提升。

这个方向还在快速演进,SAG 和 OpenViking 只是其中的一种探索。但我觉得这个探索的方向是对的:Agent 时代的知识检索,不能只靠模糊匹配,必须要有精确的结构化查询能力。两者结合,才能让 Agent 真正“聪明”起来。

返回列表