结构化与多模态 RAG
为什么“全部转 Markdown”经常会失败
假设用户问三个问题:
1. 华东区 2026 年第二季度退款金额比第一季度增长多少?
2. update_user 调用了哪些函数,其中哪个会写数据库?
3. 合同右下角签章覆盖的是甲方还是乙方签字栏?把表格、代码和 PDF 全部抽成普通段落后再做向量检索,可能找到主题相近的内容,却难以可靠完成:
- 数值过滤、分组、求和和环比计算;
- 符号定义、调用图和版本范围查询;
- 坐标、页面区域、图表、印章与文本之间的空间关系。
问题不在 embedding“不够强”,而在于检索表示丢失了回答所需的结构。 结构化与多模态 RAG 的基本原则是:
数据可以有多种表示,但最终交给生成模型的每条证据,都必须保留来源、定位、权限、时间和验证方式。
一套统一架构
不要为每种数据各搭一个互不认识的机器人。更可维护的方式是统一查询入口,按意图路由到不同检索器, 再把结果归一成证据对象。
路由器决定“去哪里查”,数据连接器决定“如何安全地查”,生成模型负责“如何基于证据解释”。 SQL 执行权限、租户过滤、时间边界不能交给生成模型自由发挥。
统一证据模型
不同模态如果返回完全不同的数据结构,生成层会充满条件分支,也很难统一做引用、缓存和审计。 可以定义一个通用 Evidence:
{
"evidence_id": "ev_01J...",
"modality": "table_row",
"content": "华东区 | 2026-Q2 | 退款金额 | 1,250,000 CNY",
"structured_payload": {
"region": "华东",
"quarter": "2026-Q2",
"metric": "refund_amount",
"value": 1250000,
"unit": "CNY"
},
"source": {
"uri": "warehouse://finance/refund_summary",
"version": "snapshot_2026-07-01",
"locator": {
"table": "refund_summary",
"primary_key": ["华东", "2026-Q2"]
}
},
"time": {
"valid_from": "2026-04-01T00:00:00Z",
"observed_at": "2026-07-01T00:05:00Z"
},
"authorization": {
"tenant_id": "company-a",
"policy_decision_id": "pdp_01J..."
},
"provenance": {
"extractor": "sql-readonly-v3",
"query_hash": "sha256:..."
},
"score": 0.91
}通用字段解决共同问题,structured_payload 保留模态特有结构。至少应支持:
- 来源:原文件、表、仓库、URL、媒体资产;
- 定位:页码与坐标、行列、SQL 主键、代码路径与行号、音视频时间段;
- 版本与时间:内容版本、有效时间、抓取时间;
- 权限:租户、安全标签、授权决策;
- 生成过程:解析器、OCR/ASR 模型、查询或工具版本;
- 可验证载荷:原始数值、代码片段、图像区域或转写片段。
引用编号应由服务端绑定 evidence_id,不要让模型凭文本自行编页码或 URL。
如何路由:问题的动词比名词更重要
同一个主题可能需要不同检索器:
“退款政策怎么规定?” -> 文本/PDF RAG
“上月退款总额是多少?” -> SQL/指标服务
“退款接口在哪里实现?” -> Code RAG
“这张退款趋势图说明什么?” -> 视觉 RAG
“会议里谁决定延长退款期?” -> 音视频 RAG路由特征可以包含:
- 运算词:多少、合计、平均、排名、同比、环比;
- 结构词:字段、表、函数、调用、类、接口;
- 定位词:第几页、图中、截图、时间点、谁说;
- 新鲜度:现在、最新、今天、实时;
- 语义实体:订单、代码仓库、合同、会议、网站。
路由器不必只能选一个目标。很多问题需要组合:先用文本 RAG 找到指标定义,再用 SQL 查询数值; 先从代码检索找到函数,再查事故文档解释为何这样设计。
from dataclasses import dataclass
@dataclass
class RoutePlan:
routes: list[str]
needs_fresh_data: bool
needs_computation: bool
as_of: str | None
def enforce_route_policy(plan: RoutePlan, user) -> RoutePlan:
allowed = policy_service.allowed_connectors(user)
plan.routes = [route for route in plan.routes if route in allowed]
if not plan.routes:
raise PermissionError("当前身份没有可用于回答该问题的数据源")
return plan这里的安全裁决在模型路由之后再次执行。模型输出只是建议,不是授权。
Table RAG:保留表头、类型和行列关系
表格为什么不能按普通段落切
下面一行离开表头就失去意义:
华东 | 2026-Q2 | 125 | 1,250,000必须知道第三列是退款单数,第四列是退款金额,金额单位是人民币。 合并单元格、多级表头、脚注、单位和空白格也都可能改变含义。
抽取
表格来源包括 HTML、Excel、数据库查询结果和 PDF。抽取阶段要保存:
- 工作表/表名、标题、表头层级、行列坐标;
- 数据类型、单位、币种、百分号和日期格式;
- 合并单元格展开规则与脚注;
- 原始显示值和规范值,例如
¥1.25M与1250000; - PDF 中表格的页码和 bounding box。
OCR 得到的 1,250,000 可能被识别为 1,250,OOO。涉及数值时,抽取器应做类型校验和异常标记, 低置信单元格在回答里不能伪装成确定事实。
表示与索引
常见三种表示可以并存:
| 表示 | 例子 | 擅长 | 不擅长 |
|---|---|---|---|
| 行文本 | 地区=华东;季度=2026-Q2;退款金额=... | 语义检索、引用单行 | 跨行聚合 |
| 结构化记录 | 类型化列与主键 | 过滤、排序、计算 | 模糊自然语言匹配 |
| 表级摘要 | 表用途、字段含义、时间范围 | 找到正确表 | 精确回答具体数值 |
推荐先检索表级 schema/摘要,再按结构过滤到行列。不要把数万行都 embedding 后让相似度承担求和工作。
检索与上下文
问题:退款金额最高的两个区域是哪两个?
1. 语义检索找到“区域季度退款汇总表”
2. 读取 schema:region, quarter, refund_amount, currency
3. 结构化排序 refund_amount DESC LIMIT 2
4. 返回两行原始值、单位、快照时间
5. 生成模型解释结果,不自己心算或猜列含义给模型的上下文应使用小而明确的 Markdown 表格或 JSON,同时附字段定义和单位。 大表要由计算工具先聚合,不要截取前 50 行后让模型回答“全表最大值”。
引用、权限与评估
表格引用应定位到工作表、表名、主键/行列和快照,不能只写“来源:财务表”。 列级和行级权限要在查询阶段生效,尤其工资、医疗、客户字段不能先取出再让模型忽略。
评估至少覆盖:表选择准确率、行/列定位准确率、数值解析、聚合结果、单位一致性、引用定位和越权测试。 可加入“空值是否计入平均数”“金额是否跨币种相加”“百分比是 0.18 还是 18”这类边界样例。
Text-to-SQL 与数据库 RAG
Table RAG 面向已经抽取的静态或半静态表格;Text-to-SQL 面向可查询数据库。 它不是把 schema 塞给模型后直接执行字符串,而是一条受约束的程序生成链路。
完整流程
Schema Linking
用户说“退款额”,数据库字段可能叫 refund_gmv,也可能需要 refund_amount - coupon_refund。 系统先检索:
- 表和列的描述;
- 指标口径、单位、时区;
- 主外键与允许 join;
- 枚举含义;
- 已验证查询模板;
- 数据新鲜度和所有者。
只把相关 schema 给模型,既减少 token,也降低模型误用敏感表的机会。
安全执行边界
Text-to-SQL 生产底线:
- 连接只读副本或受限查询引擎,不给写权限;
- 解析 SQL AST,只允许
SELECT,拒绝多语句和危险函数; - 表、列、函数白名单;
- 租户谓词与行级安全由服务端注入,不能依赖模型生成;
- 参数化用户值,避免把用户输入拼接成 SQL;
- 设置扫描字节、执行时间、返回行数和并发上限;
- 执行前可用
EXPLAIN或查询引擎成本估计阻止全表爆炸; - 结果脱敏,审计保存 query hash、策略决策和数据快照。
def execute_generated_query(ast, user, params):
assert ast.statement_type == "SELECT"
policy = policy_service.resolve(user)
validated = validator.validate(
ast,
allowed_tables=policy.tables,
allowed_columns=policy.columns,
forbidden_functions={"pg_sleep", "dblink_connect"},
)
secured = inject_row_policy(validated, tenant_id=policy.tenant_id)
limited = enforce_limit_and_cost(secured, max_rows=500, timeout_ms=3000)
return readonly_connection.execute(limited, params)示例表达的是控制点,不是完整 SQL 防火墙。生产实现应使用目标方言的 AST 解析器, 不能靠正则检查是否出现 DROP:注释、子查询、函数和方言差异很容易绕过字符串规则。
文本 RAG 与 SQL 的组合
很多业务问题不能只靠 SQL:
“本月退款率为什么超过目标?”可以先从指标文档检索“退款率定义和目标阈值”,再用 SQL 获取本月分子、分母和分渠道数据, 最后检索同期活动与事故记录解释可能原因。数值来自数据库,定义来自版本化文档,原因必须有事件证据; 三者的引用类型不能混在一起。
评估
Spider 是跨领域 Text-to-SQL 数据集,适合理解 schema 泛化, 但企业数据库还必须自建包含真实业务口径和权限的评测集。不要只看 SQL 字符串完全匹配:不同 SQL 可能结果等价。
可评估:schema linking、执行正确性、结果集正确性、业务口径、拒绝危险查询、扫描成本、延迟和权限隔离。 对于随时间变化的数据库,预期答案要绑定快照,否则昨天正确的数字今天会“自动失败”。
Code RAG:检索的是程序结构,不只是代码字符串
抽取与表示
代码适合按 AST 和符号边界切分:函数、类、接口、方法、模块、配置项、测试用例。 每个单元保存:
- 仓库、commit SHA、分支或 release tag;
- 文件路径、语言、符号全名、起止行;
- docstring、注释、签名与代码体;
- imports、调用关系、继承/实现关系;
- 相关测试、配置和所有者;
- 生成代码是否存在、是否需要忽略。
固定 token 切分会把函数签名和实现拆开,也可能把两个同名方法混在一起。 对自然语言说明可建 dense embedding,对标识符和错误码保留 BM25,对调用关系建图索引。
检索策略
| 问题 | 首选检索 |
|---|---|
“ERR_1042 在哪里定义?” | 精确词/符号索引 |
| “哪个模块负责刷新 OAuth token?” | 语义 + 符号检索 |
| “改这个接口会影响哪些调用方?” | 引用图/调用图 |
| “为什么这个函数要重试三次?” | 代码 + blame/commit/设计文档 |
| “给这个函数补测试” | 定义 + 相邻测试 + fixture + API 契约 |
代码上下文不应只返回命中函数。常见 small-to-big 做法是:小符号用于精确召回,再扩展到所属类、必要 imports、 被调用函数签名和相关测试。扩展要受 token 预算控制,不能默认把整个仓库塞给模型。
引用、权限与评估
引用必须包含仓库、commit、路径和行号。只写 src/auth.py 无法保证用户看到的是同一版本。 私有仓库、分支保护、团队权限和 secret scanning 同样适用于索引;.env、密钥、生产证书不应入库。
评估可分为:符号定位、相关文件 Recall@k、调用边覆盖、生成修改所需上下文完整率、引用版本准确性。 最终代码能编译或测试通过是有价值的端到端指标,但不能替代权限和检索阶段诊断。
Layout-aware PDF RAG
PDF 不是“带分页符的文本”
PDF 记录的是页面上的绘制对象。双栏阅读顺序、页眉页脚、表格、脚注、公式、图片说明和扫描页都会让纯文本抽取错乱。 Layout-aware RAG 要同时保留文本内容和空间结构。
处理流程
PDF 文件
-> 判断 born-digital / scanned / mixed
-> 文本对象抽取 + OCR
-> 版面检测:标题、段落、列表、表格、图、页眉页脚
-> 阅读顺序恢复
-> 区域级/章节级 chunk
-> 文本向量 + 页面/区域视觉向量 + 关键词索引
-> 页面坐标引用每个区域至少保存:
{
"page": 12,
"bbox": [84, 210, 522, 436],
"block_type": "table",
"reading_order": 7,
"text": "...",
"ocr_confidence": 0.86,
"document_version": "sha256:..."
}坐标系要说明原点、单位和页面旋转。否则前端高亮会错位,引用虽然写着“第 12 页”,用户仍找不到证据。
检索与上下文
文本问题可以先检索区域文本;命中表格、图或低置信 OCR 时,再把对应页面裁剪图交给视觉模型复核。 这比所有问题都发送整页图片便宜,也避免只读 OCR 丢失空间关系。
上下文可同时包含:区域转写、页面截图引用、相邻标题、图注和表头。公式应保留 LaTeX 或结构化表达, 不要只留下 OCR 后的字符流。
引用与权限
PDF 引用应绑定文件版本、页码和 bbox;前端打开原页时重新做授权,不能把可长期访问的页面截图 URL 塞进答案。 文档级 ACL 要传播到 OCR 文本、区域图片、表格和缩略图;若同一 PDF 的附件或批注有更细权限,索引时也应拆分安全域, 不能因为它们位于同一文件就继承最宽权限。
评估
DocVQA 等文档视觉问答任务可以帮助认识文档理解问题, 但企业验收还应覆盖本地真实模板:双栏、旋转页、扫描件、复杂表格、盖章、手写批注和版本替换。
指标包括 OCR 字符/词错误、阅读顺序、区域检测、表格结构、页面召回、答案准确性、引用 bbox 准确性和低置信拒答。
视觉与多模态 RAG
视觉 RAG 的对象可以是商品图、设备照片、故障截图、设计稿、图表或医学影像。不同对象需要不同索引语义, “图片看起来相似”不等于“包含相同事实”。
抽取与表示
常见表示包括:
- 整图 embedding:适合构图或主题相似;
- 区域 embedding:检测物体、图例、按钮、缺陷区域;
- OCR 文本:检索截图里的错误码、标签和数值;
- caption:便于自然语言搜索,但 caption 会遗漏细节或产生幻觉;
- 结构化检测结果:对象类别、坐标、置信度;
- 图表数据:尽量还原系列、坐标轴、单位,而不是只生成一句摘要。
检索时可并行搜索 OCR、caption、视觉向量和 metadata,再重排。命中后将原图或必要裁剪区域交给支持视觉输入的模型, 不能只依据自动 caption 回答“红色指示灯是否亮”。
引用与权限
视觉引用要能打开资产版本并高亮区域:asset_id + frame/page + bbox。 人脸、车牌、工牌和屏幕截图可能包含个人信息;索引前需要分类、脱敏和访问控制。 低权限用户不应收到缩略图,因为缩略图本身也可能泄露内容。
评估
分别测资产召回、区域定位、OCR、属性识别、计数、图表数值和回答依据。构造 hard negative: 两张图片外观很像但版本号、指示灯颜色或图表单位不同,检查系统是否只按整体相似度误判。
音视频 RAG
从媒体到可检索时间轴
音视频通常先转成多个对齐轨道:
音轨 -> ASR 转写 -> 词级/句级时间戳 -> 说话人分离
画面 -> 镜头切分 -> 关键帧 -> OCR / 视觉描述
字幕/章节/评论 -> 结构化 metadata
所有轨道 -> 统一 timeline只做 ASR 会漏掉“屏幕上显示的错误码”和“演示者点击了哪个按钮”;只做关键帧又不知道谁在何时说了什么。 检索单元可以是 20-60 秒的时间窗,但边界应结合句子、镜头和话题变化,而不是机械切割。
表示、检索与上下文
每个 segment 保存媒体版本、开始/结束时间、speaker、转写、屏幕 OCR、关键帧 id 和 ASR 置信度。 检索可组合:语义搜转写、关键词搜名称与错误码、视觉搜关键帧、speaker/time filter 缩小范围。
给生成模型的上下文包括时间片转写和关键帧,需要时扩展前后窗口确认指代:
[00:18:42-00:19:08] 张工:回滚是因为连接池配置没有同步。
[关键帧 00:18:55] 屏幕显示 config/database.yaml 的 pool_size 修改。引用、权限与评估
引用使用媒体 id、版本和时间范围,并允许播放器直接跳转。会议录音可能有参与者、部门和保留期限制; 说话人身份属于敏感 metadata,不能因为转写已生成就自动向所有人开放。
评估包括 ASR 错误、说话人归属、时间定位误差、关键帧召回、跨轨道证据一致性和回答引用。 专有名词、口音、多人抢话、背景噪声和屏幕快速切换应进入真实评测集。
Temporal RAG:回答“在那个时间点,什么是真的”
普通 RAG 把时间当 metadata 过滤;Temporal RAG 进一步建模事件、状态变化和有效区间。
三类时间不要混
event_time:事情实际发生的时间
valid_time:事实或规则适用的时间区间
ingestion_time:系统收到并建立索引的时间例子:政策 9 月 1 日发布,规定从 8 月 1 日起生效,系统 9 月 2 日接入。 问“8 月 15 日按什么规则”要看 valid time;问“9 月 1 日系统当时能查到什么”要看 ingestion/system time。
表示与检索
事件型证据可表示为:
{
"subject": "order:A1002",
"predicate": "status",
"object": "refunded",
"valid_from": "2026-09-01T10:02:00Z",
"valid_to": null,
"observed_at": "2026-09-01T10:03:12Z",
"source_event_id": "evt_7788"
}检索流程先从问题提取 as_of、时间区间、事件顺序或持续时间,再做语义召回与时间约束交集。 “之前”“最近”“当时”依赖会话参考时间,必须解析成明确时区和边界,并在答案中说明。
冲突与修正
后到数据不一定更新:可能是迟到的旧事件。使用 event version、序列号或业务有效期判断顺序。 事实被追溯修正时,保留旧系统认知和新业务事实,支持双时间查询。
时间型引用应展示来源事件或文档版本、有效区间与系统观察时间,让用户知道结论描述的是“当时事实”还是“后来修正”。 权限也随时间变化:回答历史问题时可以查询历史事实,但是否允许查看仍按当前用户权限裁决,不能因为用户过去属于某项目就自动恢复访问。
评估应包含时间点问答、区间重叠、事件排序、迟到事件、未来生效政策、跨时区、“当前无有效记录”、 历史证据引用和当前权限收回。
Web 与 Real-time RAG
实时 RAG 不等于每次都调用搜索引擎。需要先定义新鲜度等级:
| 新鲜度 | 例子 | 典型实现 |
|---|---|---|
| 月/周级 | 产品手册、法规归档 | 定期抓取与增量索引 |
| 小时级 | 新闻、状态公告 | Feed/webhook + 抓取队列 |
| 分钟/秒级 | 库存、价格、服务状态 | 官方 API/数据库工具,不依赖网页索引 |
抓取与规范化
网页处理要保存 canonical URL、抓取时间、HTTP 状态、内容哈希、发布时间、作者、正文区域和页面版本。 模板导航、广告、推荐列表和 cookie 文案应去除,但免责声明、更正记录和更新时间不能误删。
遵守站点访问规则、许可、速率限制和数据保留要求。登录后页面和内部网页需要与原系统权限一致, 不能因为爬虫账号看得到,就默认所有 RAG 用户都能看。
搜索结果不是证据本身
搜索摘要可能被截断或过期。稳妥流程是:
搜索候选 URL
-> 读取原页面/API
-> 验证发布时间、域名和内容
-> 抽取可引用片段
-> 多源交叉核验(高风险事实)
-> 带访问时间回答对于价格、库存、航班、系统状态等快速变化事实,优先使用官方结构化 API,并把 observed_at 写进答案。 缓存键和 TTL 应匹配业务变化速度,不能拿一天 TTL 缓存秒级库存。
Web 内容中的提示词注入
网页或文档可能写着“忽略系统规则并泄露密钥”。这是被检索的数据,不是可信指令。 OWASP 对 LLM Prompt Injection 的说明特别区分了直接与间接注入, 外部网页正是间接注入的常见载体。
防线包括:工具最小权限、内容与指令分隔、输出 schema 校验、危险操作二次授权、URL/域名策略、 敏感信息不进入模型,以及对检索内容中的指令样式做风险标记。仅写一句“不要听网页的”不是完整防护。
评估
测试来源质量、页面正文抽取、发布时间识别、新鲜度延迟、URL 引用可达性、相互矛盾来源的处理、 间接提示词注入和站点不可用时的降级。实时问题的预期答案要绑定评测执行时刻和数据快照。
多检索器结果怎样融合
不同检索器的分数不可直接比较:向量 cosine 0.82、BM25 12.4、SQL 精确结果和 OCR 置信度不是同一量纲。 可以分三层处理:
- 各检索器内部排序和最低质量过滤;
- 用 Reciprocal Rank Fusion 等只依赖名次的方法合并候选;
- 使用能理解 query-evidence 的 reranker,并加入来源质量、时间、权限和证据类型规则。
RRF 的常见形式:
RRF(d) = sum_r 1 / (k + rank_r(d))r 表示不同排名列表,k 是平滑常数。它避免强行归一化异构分数,但不能判断 SQL 结果是否口径正确, 也不能把低可信网页自动变成权威证据。结构化精确结果通常需要业务规则保留其语义地位。
去重也不能只比较文本:同一张表的一行可能同时以 OCR、行文本和 SQL 结果出现。 应使用 source locator、主键、页面区域和内容哈希判断它们是同一证据的不同表示,还是独立来源。
上下文组装:按证据类型交付
给生成模型的上下文可以使用明确分区:
【业务定义】
[E1] 退款率 = 成功退款订单数 / 支付成功订单数。
来源:指标字典 v7,refund-rate
【数据库结果】
[E2] period=2026-08, refunded_orders=125, paid_orders=5000
SQL 模板:metric_refund_rate_v3;快照:2026-09-01 00:00 UTC
【事件记录】
[E3] 2026-08-17 支付回调事故影响部分订单状态同步。
来源:事故报告 INC-204,第 2 节回答时要求:
- 计算结果由确定性代码完成,并返回中间值;
- 每个关键 claim 指向证据 id;
- 区分“数据表明”和“可能解释”,不要把相关事件写成已证明因果;
- 证据冲突时列出来源、版本和时间,不偷偷选择一个;
- 低置信 OCR/ASR 要明确不确定,必要时请求人工确认。
端到端例子:一次季度经营分析
用户问:
为什么华东区第二季度退款额比第一季度高?给出数字、政策变化和会议依据。执行计划:
- 文本 RAG 检索“退款额”指标定义,确认币种、是否含取消订单;
- SQL 工具按华东和两个季度聚合,服务端注入租户与区域权限;
- 确定性代码计算差额和增长率,处理 Q1 为零的边界;
- Temporal RAG 检索 Q2 生效的退款政策变更;
- 音视频 RAG 检索经营会议中讨论退款原因的时间片;
- 融合证据并区分数值事实、政策事实与人的分析判断;
- 答案引用表快照、制度版本和会议时间范围。
增长率由代码计算:
def growth_rate(current: float, previous: float) -> float | None:
if previous == 0:
return None
return (current - previous) / previous当 Q1 为 0 时返回 None,答案写“无法用通常环比百分比表示”,不能让模型生成无穷大或随意写 100%。 若会议参与者只是提出猜测,答案必须写“会议中有人提出……,现有证据未证明因果”。
统一权限边界
多模态系统容易出现“文本库有权限,缩略图和转写没有权限”的旁路。权限要覆盖:
- 原始资产及其所有派生物;
- OCR、ASR、caption、embedding 和摘要;
- 数据库表、列、行与聚合结果;
- 代码仓库、分支、commit 与生成代码;
- 搜索缓存、答案缓存、日志和评测集;
- 前端引用链接和媒体跳转。
统一证据对象里的授权字段只是审计结果,真正授权仍需在每次读取或生成可访问链接时验证。 短期签名 URL 的过期时间不能超过会话和权限策略允许范围。
统一评估框架
端到端“答案看起来对”不足以定位问题。分层评估:
| 层级 | 通用指标 | 模态特有检查 |
|---|---|---|
| 抽取 | 完整率、字段准确性、低置信覆盖 | OCR、ASR、表格结构、AST、时间戳 |
| 路由 | 正确路由率、多路由完整率、无效调用率 | 是否把精确聚合错送到文本检索 |
| 召回 | Recall@k、MRR、权限过滤后召回 | 页面/区域、符号、表/列、媒体时间片 |
| 执行 | 成功率、延迟、成本、资源限制 | SQL 结果、计算结果、网页新鲜度 |
| 生成 | 正确性、faithfulness、拒答 | 单位、版本、时间、模态信息保留 |
| 引用 | claim 覆盖、支持性、可定位性 | bbox、行列、commit 行号、时间范围 |
| 安全 | 越权率、注入抵抗、敏感数据暴露 | 行列权限、缩略图、转写和私有代码 |
评测集要包含多模态组合题和不可回答题。典型 hard cases:
- 表格标题相同但币种不同;
- SQL 字段名相似但业务口径不同;
- 同名函数分布在不同服务和版本;
- PDF OCR 文本正确但阅读顺序错误;
- 图像整体相似但小区域状态不同;
- ASR 把产品名识别成常见词;
- 迟到事件改变历史状态;
- 搜索摘要与原网页内容不一致;
- 用户能访问正文但不能访问附件或某列数据。
成本与延迟怎么控制
异构 RAG 的成本主要来自解析、索引、在线工具和上下文:
| 阶段 | 成本来源 | 常见控制手段 |
|---|---|---|
| PDF/图像 | OCR、版面模型、视觉模型 | 内容哈希、低成本初筛、按需视觉复核 |
| 音视频 | ASR、说话人分离、关键帧理解 | 离线批处理、按镜头抽帧、专名词典 |
| SQL | 扫描字节、仓库计算 | 语义层、物化聚合、成本门禁、限时限行 |
| Code | 多版本索引、调用图构建 | 只索引目标分支、增量 AST 更新 |
| Web | 搜索、抓取、解析 | 新鲜度分级、ETag/内容哈希、域名策略 |
| 生成 | 多模态输入 token、长证据 | 先检索后裁剪、结构化结果、证据预算 |
在线延迟预算可按关键路径拆分:
T_total = T_route
+ max(T_text, T_sql, T_code, T_media, T_web)
+ T_fusion
+ T_generation
+ T_verification并行检索降低的是可并行分支之和,不会消除最慢分支。给每个连接器设置独立 deadline 和降级语义: SQL 超时不能伪装成“退款额为 0”,网页抓取失败不能伪装成“没有新闻”。
典型失败模式
1. 所有数据都转成纯文本
症状是能找到相关表或代码,却算不准、定位不准。修复不是继续增大 top_k,而是保留类型、行列、AST、坐标和时间轴, 把计算交给结构化工具。
2. 路由只看关键词
“退款”可能问制度、金额、接口或会议。路由要结合问题动作、期望输出、新鲜度和数据实体,并允许组合路由。
3. Text-to-SQL 直接连生产主库
生成错误 SQL 可能全表扫描、拖慢业务,甚至越权。应连接只读副本或受限引擎,做 AST 白名单、服务端权限注入、 参数化、超时、扫描和行数限制。
4. Caption 代替原图
caption 是模型生成的有损摘要,可能遗漏按钮状态、图例和小字。命中图片后仍应把原图区域交给视觉模型或人工复核。
5. 引用只有文件名
复杂 PDF、代码、音视频和数据库结果需要页码+bbox、commit+行号、时间范围、表+主键+快照。 没有精确 locator,用户无法验证,系统也无法自动检查 citation。
6. 不区分数据时间
把抓取时间当事件时间、把当前版本用于历史问题,会生成“现在正确、当时错误”的答案。必须建模 event/valid/system time。
7. 派生物权限遗漏
原图受限但 OCR 可搜、私有会议受限但 ASR 公开、数据库列受限但聚合缓存共享,都会形成旁路。 权限必须沿派生血缘传播,并进入缓存键与引用访问。
8. 混合分数直接相加
不同检索器分数量纲不同,直接 0.5 * cosine + 0.5 * BM25 没有可靠含义。 先做每路校准或使用 rank fusion,再用业务规则和 reranker;精确数据库结果不应与模糊 caption 按一个分数争真假。
9. 把人的发言写成事实
会议转写中的“我觉得是缓存导致”只是一条观点,不是故障根因。Evidence 应标记 speaker 与语句类型, 答案区分事实、观察和推测。
10. 评测没有绑定版本和快照
代码、数据库和网页会变化。没有 commit、DB snapshot 和抓取时间,评测失败可能只是数据更新,不是系统回归。
生产落地顺序
- 先列出真实问题类型,不从“支持所有模态”开始。
- 为每类数据定义事实源、所有者、版本、权限和新鲜度。
- 建立统一 Evidence schema 与精确 locator。
- 给每个连接器实现抽取质量检查和低置信标记。
- 从规则路由起步,积累误路由数据后再训练或使用 LLM 路由。
- 把 SQL、计算、媒体读取和网页抓取放在受限工具中。
- 先做单路由评测,再做表格+文本、SQL+文档等组合题。
- 给每种派生物做权限继承、删除传播和缓存隔离测试。
- 建立端到端 trace,保存数据/模型/解析器/索引版本。
- 用真实 hard cases 做发布回归,并抽查引用能否打开到准确位置。
关键理解
结构化与多模态 RAG 不是多买几个 embedding 模型,而是保留数据原本能表达的关系:
- 表格的行、列、类型和单位;
- 数据库的 schema、口径和可执行运算;
- 代码的符号、依赖、版本和调用关系;
- PDF 与图像的页面、区域和空间布局;
- 音视频的说话人、画面和时间轴;
- 知识的有效时间、系统认知时间和实时新鲜度。
统一架构的核心也不是统一成纯文本,而是统一成可授权、可定位、可验证的证据对象。 生成模型可以解释证据,但计算、权限、版本和来源定位应由确定性系统守住。
先看一个小场景
“第二季度退款金额比第一季度增长多少”适合交给 SQL 或表格计算;“代码函数哪个会写数据库”需要调用图;“合同右下角的印章盖在哪一方签字栏”则要保留页面坐标。三种问题都可以从同一个入口进入,但证据对象和验证方式不能混成一段普通文本。
读完后可以分别准备一张报表、一段代码和一页合同,检查系统是否为每类数据保留了正确的证据定位方式。