|
| 1 | +# 浅谈SAG:在 Zleap AI 眼中,我们为什么需要 SAG? |
| 2 | +<br> |
| 3 | +最近在上网冲浪的时候又了解到了一个ai新概念,SAG,号称是一套可以替代传统 RAG 与 GraphRAG 的原创检索架构。为了研究其是否真的有那么大作用,我点开了它的论文和GitHub仓库,开始研究了起来。 |
| 4 | + |
| 5 | +# 为什么世界需要SAG? |
| 6 | + |
| 7 | +在现实生活中,对于需要通过查询来解答的问题,有时需要多步推理。 |
| 8 | + |
| 9 | +## 举个例子: |
| 10 | + |
| 11 | +你的邻居们每天会把吃了几顿饭记录在日历上(哦,邻居们啊邻居们。真是吃饱了闲的)。除此之外,我们手里还有一份邻居档案,记录着每位邻居的性别、住址以及与我家的距离。 |
| 12 | + |
| 13 | +**问:离我最近的男邻居在前天吃了几顿饭?** |
| 14 | + |
| 15 | +回答这样的问题,我们需要如下几步: |
| 16 | + |
| 17 | +1. 查询邻居档案,明确离我最近的男邻居是谁。(哦,是家铭。) |
| 18 | +2. 明确前天对应的是日历哪一天。(好的,是8月19日) |
| 19 | +3. 查询家铭8月20日的日历记录。(天哪,他一天吃了五顿!) |
| 20 | + |
| 21 | +我们把这样的问题,称为 **多跳问题(Multi-hop Questions)**。 |
| 22 | + |
| 23 | +对于解答这样的问题, |
| 24 | + |
| 25 | +### 传统RAG(NaiveRAG)会这么做: |
| 26 | + |
| 27 | +```text |
| 28 | +Query: "离我最近的男邻居在前天吃了几顿饭?" |
| 29 | + │ |
| 30 | + ↓ 向量化 |
| 31 | + │ |
| 32 | + ↓ 与知识库中的各个 chunk 计算语义相似度 |
| 33 | + │ |
| 34 | + ↓ 返回Top-5最相似的记录(这里我们设置是前五) |
| 35 | + │ |
| 36 | + ▼ |
| 37 | +结果: |
| 38 | + 记录1: "小红住在我家3米外,是女性" (相似度 0.78) |
| 39 | + 记录2: "8月19日,小明吃了3顿饭" (相似度 0.75) |
| 40 | + 记录3: "家铭住在我家5米外,是男性" (相似度 0.73) |
| 41 | + 记录4: "8月20日,家铭吃了4顿饭" (相似度 0.71) |
| 42 | + 记录5: "小明住在我家12米外,是男性" (相似度 0.69) |
| 43 | +``` |
| 44 | + |
| 45 | +问题来了:真正决定答案的另一条记录——**“8月19日,家铭吃了5顿饭”**——这一次恰好排在我们检索的 Top-5 之外。 |
| 46 | + |
| 47 | +传统RAG(Naive RAG)通常会把每个 chunk 独立地按照它与 Query 的语义相似度进行排序,但检索器本身并不会显式沿着: |
| 48 | + |
| 49 | +> **男性 → 最近 → 家铭 → 8月19日** |
| 50 | +
|
| 51 | +这样的关系链一步步寻找证据。 |
| 52 | + |
| 53 | +如果完整的证据刚好都被检索进上下文,后面的 LLM 完全可能自己推理出答案。但Naive RAG 的检索阶段并不能保证完成多跳推理所需要的所有证据都能进入 Top-k。 |
| 54 | + |
| 55 | +--- |
| 56 | + |
| 57 | +### 知识图谱(GraphRAG)会这么做(以 Microsoft GraphRAG 这类方案为例): |
| 58 | + |
| 59 | +```text |
| 60 | +文档 |
| 61 | + ↓ |
| 62 | +切 chunk |
| 63 | + ↓ |
| 64 | +LLM 提取实体 |
| 65 | + ↓ |
| 66 | +LLM 提取关系 |
| 67 | + ↓ |
| 68 | +实体消歧 / 合并 |
| 69 | + ↓ |
| 70 | +建立图 |
| 71 | + ↓ |
| 72 | +社区检测 |
| 73 | + ↓ |
| 74 | +生成社区摘要 |
| 75 | + ↓ |
| 76 | +建立索引 |
| 77 | +``` |
| 78 | + |
| 79 | +在构建实体阶段,GraphRAG 可能要让 LLM 提取出如下关系: |
| 80 | + |
| 81 | +```text |
| 82 | +实体: |
| 83 | +我 |
| 84 | +家铭 |
| 85 | +8月19日 |
| 86 | +
|
| 87 | +关系: |
| 88 | +家铭 --住在隔壁--> 我 |
| 89 | +家铭 --性别--> 男性 |
| 90 | +家铭 --距离我家--> 5米 |
| 91 | +``` |
| 92 | + |
| 93 | +而对于“8月19日吃了5顿饭”这种带有时间和属性的信息,我们也可以把它建模成一个事件: |
| 94 | + |
| 95 | +```text |
| 96 | +吃饭事件 E1 |
| 97 | +├── person → 家铭 |
| 98 | +├── date → 8月19日 |
| 99 | +└── meal_count → 5 |
| 100 | +``` |
| 101 | + |
| 102 | +这样一来,系统就可以利用图中的关系进行扩展: |
| 103 | + |
| 104 | +```text |
| 105 | +男性邻居 |
| 106 | + ↓ |
| 107 | +比较距离 |
| 108 | + ↓ |
| 109 | +家铭 |
| 110 | + ↓ |
| 111 | +找到8月19日相关事件 |
| 112 | + ↓ |
| 113 | +meal_count = 5 |
| 114 | +``` |
| 115 | + |
| 116 | +这看起来很不错,对吧?GraphRAG为了弥补纯向量检索缺乏显式关系的问题,一类 GraphRAG 方法会提前从文档中抽取实体与关系,再利用图结构辅助检索。 |
| 117 | + |
| 118 | +但在实际运用过程中,我们仍会发现如下问题: |
| 119 | + |
| 120 | +#### 首先,是构建成本高: |
| 121 | + |
| 122 | +1. 切出来的chunk越多,每个chunk都需要模型提取里面的实体和关系,这样的话处理成本就会很高。 |
| 123 | + |
| 124 | +2. 家铭可能有很多称呼:臭宝,小铭,jm… |
| 125 | + |
| 126 | + 这就会导致一个问题:**实体消歧(Entity Resolution)**:即GraphRAG 得下工夫判断这四个名字是不是同一个实体。比如臭宝和家铭是一个实体,那么就将他们合并成一个节点;家铭和小明不是一个实体,就要搞清楚小明到底指哪个实体。 |
| 127 | + |
| 128 | +3. 图建好以后,GraphRAG 还得下工夫去做 **社区检测(Community Detection)**,还需要去研究哪些节点彼此联系特别紧密。 |
| 129 | + |
| 130 | +#### 其次,是增量更新难: |
| 131 | + |
| 132 | +新的邻居搬进来,就可能导致实体,关系,图索引,社区等一系列内容的修改,一改一大片。 |
| 133 | + |
| 134 | +这里拓展一下:在普通RAG中新增一篇文档,可能就只需要获取新“邻居”档案,然后chunk,接着embedding,再插进去,就结束了。 |
| 135 | + |
| 136 | +#### 最后,是语义碎片化: |
| 137 | + |
| 138 | +一个完整事件在被拆成多个独立实体和关系后,其中的时间、条件、否定、计划、实际结果、因果等上下文信息,可能被削弱甚至丢失。 |
| 139 | + |
| 140 | +很多知识图谱方法会将自然语言中的信息抽取为实体、关系或三元组: |
| 141 | + |
| 142 | +```text |
| 143 | +(subject, relation, object) |
| 144 | +``` |
| 145 | + |
| 146 | +举个例子: |
| 147 | + |
| 148 | +家铭在8月19日的日历里写道: |
| 149 | + |
| 150 | +```text |
| 151 | +“今天本来打算吃5顿,但下午那顿没来得及吃,所以最后一共只吃了4顿。” |
| 152 | +``` |
| 153 | + |
| 154 | +可见,记录的信息包含计划与实际结果,而知识图谱又习惯于把句子拆成三元组,一旦其没有把“本来打算”“最后”“实际”等限定信息一起保存下来,那么就会得出: |
| 155 | + |
| 156 | +```text |
| 157 | +(家铭, 吃饭次数, 5) |
| 158 | +
|
| 159 | +和 |
| 160 | +
|
| 161 | +(家铭, 吃饭次数, 4) |
| 162 | +``` |
| 163 | + |
| 164 | +就可能变成两条看起来同样成立的事实。 |
| 165 | + |
| 166 | +当然,这并不意味着知识图谱必然会出现语义碎片化。我们完全可以把关系设计得更加复杂,例如: |
| 167 | + |
| 168 | +```text |
| 169 | +(家铭, planned_meal_count, 5) |
| 170 | +(家铭, actual_meal_count, 4) |
| 171 | +``` |
| 172 | + |
| 173 | +但新的问题也随之而来:图结构设计得简单,抽取和维护比较容易,但可能损失复杂上下文;图结构设计得复杂,能够保存更多完整语义,但抽取、构建、消歧和维护成本进一步上升。这样就很难同时兼顾。 |
| 174 | + |
| 175 | +--- |
| 176 | + |
| 177 | +## SAG来咯! |
| 178 | + |
| 179 | +基于以上痛点,Zleap AI团队提出了一个新架构:**SAG**。 |
| 180 | + |
| 181 | +他们发现,多跳推理所需的关联结构,其实“潜在地”存在于记录描述的事件和它们共享的实体中。与其提前把整个知识库画成一张巨大的图,不如先把事件和实体存好,等用户真正提问的时候,再通过 SQL 把当前问题需要的事件临时连接起来。 |
| 182 | + |
| 183 | +我们可以先把 SAG 的数据库极度简化成三张表。 |
| 184 | + |
| 185 | +```text |
| 186 | +第一张: |
| 187 | +
|
| 188 | +Events |
| 189 | +
|
| 190 | +E1 | 家铭是一名男性邻居,距离我家5米 |
| 191 | +E2 | 小明是一名男性邻居,距离我家12米 |
| 192 | +E3 | 8月19日,家铭一共吃了5顿饭 |
| 193 | +E4 | 8月19日,小明一共吃了3顿饭 |
| 194 | +
|
| 195 | +
|
| 196 | +第二张: |
| 197 | +
|
| 198 | +Entities |
| 199 | +
|
| 200 | +P1 | 家铭 |
| 201 | +P2 | 小明 |
| 202 | +P3 | 我 |
| 203 | +P4 | 8月19日 |
| 204 | +
|
| 205 | +
|
| 206 | +第三张最重要: |
| 207 | +
|
| 208 | +Event_Entity |
| 209 | +
|
| 210 | +E1 → 家铭 |
| 211 | +E1 → 我 |
| 212 | +
|
| 213 | +E2 → 小明 |
| 214 | +E2 → 我 |
| 215 | +
|
| 216 | +E3 → 家铭 |
| 217 | +E3 → 8月19日 |
| 218 | +
|
| 219 | +E4 → 小明 |
| 220 | +E4 → 8月19日 |
| 221 | +``` |
| 222 | + |
| 223 | +看到这里,SAG 最核心的东西其实已经出现了。 |
| 224 | + |
| 225 | +**注意:** |
| 226 | + |
| 227 | +```text |
| 228 | +E1: |
| 229 | +家铭是男性邻居,距离我5米 |
| 230 | +
|
| 231 | +E3: |
| 232 | +8月19日,家铭吃了5顿 |
| 233 | +``` |
| 234 | + |
| 235 | +SAG 没有提前画一条边说: |
| 236 | + |
| 237 | +```text |
| 238 | +E1 ─────────→ E3 |
| 239 | +``` |
| 240 | + |
| 241 | +但是它们都有: |
| 242 | + |
| 243 | +```text |
| 244 | +E1 → 家铭 |
| 245 | +
|
| 246 | +E3 → 家铭 |
| 247 | +``` |
| 248 | + |
| 249 | +也就是说: |
| 250 | + |
| 251 | +```text |
| 252 | +E1 |
| 253 | + \ |
| 254 | + \ |
| 255 | + 家铭 |
| 256 | + / |
| 257 | + / |
| 258 | +E3 |
| 259 | +``` |
| 260 | + |
| 261 | +E1 和 E3 的关系其实已经存在了。 |
| 262 | + |
| 263 | +只不过这个关系没有被提前做成一张全局图。 |
| 264 | + |
| 265 | +这就是 SAG 所说的: |
| 266 | + |
| 267 | +> **“潜在的关系”** |
| 268 | +
|
| 269 | +等有人问问题的时候,系统首先会围绕 Query 找到一些比较相关的 **Seed Event / Seed Entity**。 |
| 270 | + |
| 271 | +--- |
| 272 | + |
| 273 | +SAG的确给我们打开了一个解决多跳问题的新思路。不过对于每一个新出来的技术,我们应该保持科学严谨的态度。在了解其原理后,检验其是否真的能够如其所说,那么“有效”。 |
| 274 | + |
| 275 | +**欢迎与我讨论!** |
0 commit comments