说明:本文讨论的是把图表、截图、扫描页这类非文本内容接进检索链路的索引与切分层问题,属于 AI 前沿动态解读话题,不涉及具体模型版本与价格。AI 领域版本迭代极快,凡涉及版本号、价格、可用性,请以你阅读时的官方页面为准。文中代码为结构示意,请按自己的技术栈调整后再上生产。
一、非文本内容是怎么从检索里消失的
知识库里最值钱的那部分信息,往往不在正文段落里。季度分析里的毛利率走势、架构评审稿里的部署拓扑、扫描版合同里的付款条款,全都在图里。纯文本的抽取与切分链路处理不了这些内容,而且它丢东西的方式很隐蔽——检索不会报错,它只是安静地少返回几条。
丢法有三种,症状不同,修的位置也不在同一层。混在一起排查,最常见的后果是拿修「顺序乱」的方案去治「抽不到」,白改一遍。
抽不到:版面里本身就是像素
最常见的一类。扫描件、聊天截图、会议拍照,以及被报表工具导出成贴图的图表,抽取阶段拿到的字符序列里根本没有这些字。同一份 PDF 里可能同时存在两种丢法——正文有文字层,图表是贴图。
症状很好认:某个主题在原文里有一整页,检索结果里却一条都没有。这类内容在索引侧等于不存在,任何依托文本的召回都不可能命中。
顺序乱:能抽到文字,读出来是错的
第二类更隐蔽。文字确实在文件里,但版式是二维的:多栏排版、跨页表格、图注与图之间隔着半页正文。抽取器按默认阅读顺序把字符串成一段,拼出来的是乱的。
症状是召回片段读起来像打散的碎片——同一句话被拦腰截断,表格的行跑到列里,图注接到了别的段落尾巴上。用户看到的是一段语法说得通、语义已错位的文本,比完全召不回来更难发现。根在版面分析(layout analysis)与阅读顺序还原:先判出区块属于哪一栏、哪张表、哪个图注,再按区域内顺序拼。
语义散:抽得又对又全,语义已经不成形
第三类最难判断,因为抽取环节没有任何错误。表格被正确抽成一行行单元格,柱状图被正确抽成坐标轴标签与数字序列——每个字符都对,整体已经不是一个可检索的语义单元。
一张「各地区营收同比」的表,抽平之后是几十个数字与地区名的序列。用户问「华东的同比是多少」,向量检索拿到的是一堆离散数字,命中率取决于嵌入模型能不能从中认出结构,多数情况下认不出来。修复点在切分与描述层,不在抽取层。
| 丢法 | 触发条件 | 典型材料 | 检索侧症状 | 修在哪一层 |
|---|---|---|---|---|
| 抽不到 | 版面内容本身是像素 | 扫描件、截图、贴图图表 | 该主题一条都召回不到 | 抽取层或旁路索引 |
| 顺序乱 | 文字在,但版式是二维的 | 多栏排版、跨页表格 | 召回片段语义错位 | 版面分析与阅读顺序还原 |
| 语义散 | 抽取正确但结构被拉平 | 数据表、坐标图 | 召回到一堆无语义碎片 | 切分与结构化描述 |
三类里只有第一类必须动抽取链路,后两类在切分和描述侧就能救。
二、先问查询长什么样,再选做法
选做法之前先回答一个问题:用户会拿什么样的问句来查这批图。反过来,凭做法的先进程度倒推需求,几乎一定会选贵。
查询的四种形态
非文本内容的查询落到检索侧只有四种。
取数值——问「去年三季度毛利率是多少」,要的是图中那个数或某一行。找相似——手里有一张架构图,想知道库里有没有画法类似的,这要求图与图落在同一个可比空间里。问关系——问「接入流程的第二步是什么」,图周围文字与图注里往往已经写了步骤与顺序。找出处——问「哪份材料讲过灰度策略」,只需要定位到材料和页面,内容自己去看。
按查询形态选做法
四种形态对应的最少够用做法如下。「最少够用」的反面不是更保险——多给一层,是把成本和失效面一起放大了。
| 查询形态 | 例子 | 最少够用的做法 | 为什么够 |
|---|---|---|---|
| 取数值 | 去年三季度毛利率多少 | 结构化描述 | 要的是字段级的值与表头 |
| 找相似 | 这张拓扑图还有类似的吗 | 多模态嵌入 | 需要图与图落在同一向量空间 |
| 问关系 | 接入流程第二步是什么 | 图注增强 | 图周围文字已含步骤与顺序 |
| 找出处 | 哪份材料讲过灰度策略 | 图注增强 | 只要能定位到材料即可 |
两种常见的选贵错法
上多模态嵌入最省事,是第一种。它把每张图的嵌入生成、查询侧嵌入、向量索引体积一起拉起来,而这三项里最贵的是维护:图一更新向量就得重算,重算的触发条件还得单独维护。如果「找相似」在实际查询里占比很低,这笔投入基本收不回来。
先全量跑一遍摘要,再想查询,是第二种。摘要按图生成,几万张图逐张调模型,是一次性大额开销;而摘要一生成就直接决定检索质量,跑完之前你无法知道查询能不能被满足。更稳的顺序是先圈定查询形态,再决定给多少张图生成摘要。
⚠️ 代码待验证
# 按查询形态决定走哪条检索路径,避免默认全上多模态QUERY_SHAPES={"value":{"path":"figure_text","need":["table_structured"]},"similar":{"path":"figure_vector","need":["image_embedding"]},"relation":{"path":"text","need":["caption"]},"locate":{"path":"text","need":["caption","doc_link"]},}defroute_query(shape:str,index_state:dict)->dict:plan=QUERY_SHAPES.get(shape)ifplanisNone:# 形态判不出来时不猜,退回文本路,交由合并层兜底return{"paths":["text"],"degraded":True}# 所需索引不存在时不能静默降级成空结果,要显式标记缺哪一项missing=[nforninplan["need"]ifnotindex_state.get(n)]return{"paths":[plan["path"]],"missing":missing,"degraded":bool(missing)}missing这个字段是关键。索引还没建好时,检索层如果直接返回空,排查起来会误以为是召回质量差,实际是那一层压根没建。
三、图表进索引的四种做法
四种做法的效果、成本与失效面依次递增。它们不是四选一,而是两组:前两种解决「图里的信息能不能被文本检索到」,后两种解决「图本身能不能被直接检索」。
图注增强与结构化描述
图注增强(caption augmentation)用图周围的文字给图打标签:图号、图题、所在章节标题、正文里指向这张图的引用句。这些文字本来就在文档里,抽取成本近零,但覆盖面对写作习惯依赖大:图注写得清楚的收益大,只有「图 3」两个字的收益接近零。图注一改标签即失效,好在重生成很便宜。
结构化描述针对表格:把表格还原成表头加行的键值形式,让「华东」「同比」这类词和对应数值绑在一起,而不是一行行数字。这一步需要表格结构识别,跨页表和合并单元格最容易出错。
自然语言摘要与多模态嵌入
自然语言摘要让模型读图并写一段描述,把任意形态的图都转成文本,接进现成文本检索即可,不需要改检索层。代价是每张图一次模型调用,且这段文字会「替」图发言——描述偏了检索就跟着偏,偏在哪很难从结果里看出来。
多模态嵌入(multimodal embedding)把图与文映射到同一向量空间,支持图搜图与图文互搜。它能力最强也最重:每张图一次嵌入调用、查询侧也要嵌入、索引体积显著上涨、图一更新向量即失效,更适合文本描述不出来的「找相似」查询。
四种做法的成本与失效条件
| 做法 | 生成成本 | 查询侧成本 | 更新失效面 | 维护负担 |
|---|---|---|---|---|
| 图注增强 | 复用已有文字,近零 | 无额外 | 图注、章节标题一改即失效 | 低,可随时重跑 |
| 结构化描述 | 一次性解析表格结构 | 无额外 | 表格结构变即失效 | 中,跨页表易错 |
| 自然语言摘要 | 每张图一次模型调用 | 无额外 | 图或内容更新即失效 | 中高,描述偏差不可见 |
| 多模态嵌入 | 每张图一次嵌入调用 | 查询也要嵌入 | 图一更新向量即失效 | 高,需维护重算触发 |
看这张表不是比「谁更好」,而是比「谁贵在哪一层」:图注增强贵在覆盖率不稳定,结构化描述贵在解析准确率,摘要贵在调用量,多模态嵌入贵在维护——它把失效判定变成了一个必须持续运行的任务。
⚠️ 代码待验证
{"figure_id":"doc-8812-fig-03","doc_id":"doc-8812","page_no":12,"block_type":"table","caption_raw":"图 3 各地区营收与同比(2026 年三季度)","structured_rows":[{"region":"华东","revenue":1280,"yoy":0.14},{"region":"华南","revenue":940,"yoy":0.09}],"summary_text":"华东营收最高、增速也最快,华南次之。","descriptions_source":["caption","structured","summary"],"asset_uri":"s3://kb/doc-8812/p12-fig03.png","updated_at":"2026-10-14T09:20:00Z"}descriptions_source记的是这条记录的文本从哪几层来。检索命中时能立刻判断是图注命中还是摘要命中,评估和排查都用得上。
四、切分与元数据
索引侧最容易做错的一步是切分单位。按「图」切,切出来的是没有语义的图片块;按「段落」切,图与其说明被拆到两个片段。
切分单位:图 + 图注 + 所在章节
先把非文本内容定义成一个可检索单元,组成是:图本体(或表格本体)+ 图注原文 + 所在章节路径 + 邻近引用句。四样拼成一个块,块内文本供文本检索,图本体供多模态检索。
章节路径要带层级,比如「第三章 > 3.2 成本结构 > 图 3-1」。检索时按它约束范围,命中后用它告诉用户图在文档里的位置。颗粒度上有个反直觉的点——一个块塞进整章正文,语义被稀释;只塞图注,信息太薄。通常做法是图注加所在小节标题,再加指向这张图的最近一句正文,这句往往是解释性的,命中价值最高。
元数据字段清单
元数据的用途只有三个:过滤、回链、判失效。少一个字段,就会有一个环节退化成人工。
| 字段 | 类型 | 用途 | 缺失后果 |
|---|---|---|---|
| doc_id | 字符串 | 回链到原文 | 召回后找不到出处 |
| page_no | 整数 | 定位到具体页 | 用户要点开整篇翻找 |
| block_type | 枚举 | 按表格/图/扫描页过滤 | 无法按类型收窄召回 |
| caption | 文本 | 图注原文,供文本路召回 | 文本路召不回这张图 |
| section_path | 文本 | 所属章节层级 | 无法按章节约束召回范围 |
| figure_no | 字符串 | 图号,用于相邻图去重 | 同一张图重复入索引 |
| asset_uri | 字符串 | 指向图片本体 | 召回了但渲染不出来 |
| updated_at | 时间 | 判断摘要与向量是否过期 | 过期内容仍被召回 |
block_type要在抽取阶段就落下来,检索时再补需要重扫原文。asset_uri与正文分开存——正文索引条目多,把图片二进制塞进去会让索引体积失控。
⚠️ 代码待验证
# 一个非文本检索块,切分单位 = 图 + 图注 + 章节 + 邻近引用句block:id:"${doc_id}-${figure_no}"doc_id:"doc-8812"page_no:12block_type:table# 枚举:table / figure / scanned_pagecaption:"图 3 各地区营收与同比(2026 年三季度)"section_path:"第三章 > 3.2 成本结构"neighbor_sentence:"下表给出各地区营收与同比。"figure_no:"fig-03"asset_uri:"s3://kb/doc-8812/p12-fig03.png"text_for_index:"${caption} ${section_path} ${neighbor_sentence} ${structured_rows_text}"updated_at:"2026-10-14T09:20:00Z"# 失效判定:updated_at 晚于 summary_built_at 时,摘要与向量必须重算summary_built_at:"2026-10-10T00:00:00Z"embedding_built_at:"2026-10-10T00:00:00Z"summary_built_at与embedding_built_at是两组独立的时间戳,因为摘要和向量的重建成本不同,触发条件也不该合并成一个。
相邻图的去重
一份材料里的图常常成组出现:总览图加局部放大图,或同一指标的多个时间切片。逐张处理会让同一主题生成多条高度相似的块,检索时一次占满结果位。
去重键有两层:doc_id + figure_no做精确去重,拦住同一张图被重复处理;以及内容相似度——同章节下、图注高度重叠的块,保留信息量最大的那张,其余降权而非删除,因为局部放大图可能在细节查询里更对题,删了就彻底召不回。
五、两路结果怎么合并
多数落地不是全走多模态,而是正文照旧按文本索引,非文本走旁路索引,检索时两路各自出结果再合并。合并这一步做不好,前面所有工作会被一次错误的排序抹掉。
分值口径统一
两路的分值量纲不一样:文本路可能是倒排检索的相关性分,非文本路可能是向量余弦相似度,直接相加或者直接比大小都是错的。
先在各路内部做归一(min-max 或分位数归一),压到统一的 0 到 1 区间,再各自乘权重求和。权重不能拍——它应该用一个两路都能覆盖的评估集标定,在这个集合上调整权重,看合并后的排序指标什么时候最好。
| 合并中的问题 | 处理方式 | 理由 |
|---|---|---|
| 两路分值量纲不同 | 各路内部归一后再映射到统一区间 | 相关性分与余弦值不可直接比 |
| 两路召回粒度不同 | 按 doc_id + figure_no 去重 | 同一张图可能被两路同时召回 |
| 某一路无结果 | 标记缺失,不补零分 | 零分会被当成强负样本压低排序 |
| 合并后排序 | 加权求和,权重由评估集标定 | 凭手感设权重必然偏 |
合并顺序与去重
顺序上有两种做法。先合并再截断:两路各取较多候选,合并去重后统一排序取前 N,质量更好,不会错过「文本路排名靠后但实际最相关」的条目。各路先截断再合并:每路只取前若干条,延迟更低。折中是分层——主路取足量,旁路只取少量高置信结果,其分数超过主路当前第 N 名时才插入。
去重必须在排序之前做,否则会留下「去重后有效结果不足 N 条」的空档。
哪一路作为兜底
两路都空是常态,尤其在新接入非文本索引的初期。这时不能返回空结果结束,要让主路放宽条件再召回一次。
反过来,即使非文本路的质量稳定高于文本路,也不建议直接以它为主。原因是可确认性:文本命中能把原文片段直接展示,图命中还得渲染,用户的确认成本更高。两路的分工是主路保覆盖、旁路保精度,而不是谁强谁上位。
⚠️ 代码待验证
defmerge_two_paths(text_hits:list,figure_hits:list,*,w_text:float,w_fig:float,top_n:int)->list:defnorm(hits):ifnothits:return[]lo=min(h["score"]forhinhits)hi=max(h["score"]forhinhits)span=(hi-lo)or1.0return[{"id":h["id"],"n":(h["score"]-lo)/span}forhinhits]merged={}# 各路在自己的范围内归一,权重才有多路可比的意义forhinnorm(text_hits):merged[h["id"]]=merged.get(h["id"],0.0)+w_text*h["n"]forhinnorm(figure_hits):# 同一张图被两路同时召回时分数相加,去重键为 idmerged[h["id"]]=merged.get(h["id"],0.0)+w_fig*h["n"]ranked=sorted(merged.items(),key=lambdakv:kv[1],reverse=True)[:top_n]# 两路都空时返回空列表,交给上层走"放宽文本路"的兜底分支return[{"id":i,"score":s}fori,sinranked]norm是在每一路内部做的。如果对合并后的分数再归一,权重就失去意义了。
完整版资料清单:本文用到的两路合并对照表与实测记录都整理在里面了,扫码即可获取:
六、怎么评这类检索
非文本检索的评估不能沿用文本那套。文本检索可以拿命中率加人工判断,图表类不行——你无法判断一次提升到底是检索变准了,还是渲染变好了。
「问题 — 正确答案所在图」对照集
对照集每条记录三个字段:问题、答案所在的图(用doc_id + figure_no标识)、以及这个问题需不需要渲染图片才能回答。第三个字段最容易被漏掉:有些问题只要召回图旁边的文字就够了,有些必须真的看到图,混在一起算指标会把渲染问题误读成召回问题。
对照集要从真实检索日志里长出来,而不是凭工程师想象写。先把用户实际问过的问题、以及它们实际命中的图收集起来,人工标注「这张图是不是正确答案」,这样得到的对照集覆盖真实查询分布,也自带一批难例。
⚠️ 代码待验证
# 一条对照集记录(字段示例) question: "去年三季度华东的同比是多少" answer_ref: doc-8812 + fig-03 need_render: true # 必须看到图里的数值,只靠图注答不了 difficulty: medium label_source: "人工标注,取自 2026 年 9 月检索日志"区分「没召回」与「召回了但没渲染」
这两类失败在用户侧看起来一样,修的位置却不同。判断方法是在检索层与展示层之间加一个埋点:检索返回的候选里有没有目标图。有,就是渲染或展示的问题(asset_uri失效、图片权限、前端不展示图块);没有,才是召回问题(切分、描述、嵌入)。
指标要分开算:召回率(目标图出现在候选里的比例)与可用率(召回后用户真的能看到内容的比例)。把两者乘起来当成一个「命中率」,出了问题就无法归因。
更新后的重评节奏
非文本索引的失效是持续发生的:材料换版本、图表重排版、章节结构调整,都会让摘要与向量对不上。可行的节奏是固定两件事——材料每次更新触发一次只针对它的定向重评,每个固定周期跑一次全量对照集。前者抓「更新引入的错」,后者抓「积累的漂移」。
七、落地顺序与观测
非文本检索涉及的层多,任何一层有问题都表现为「召回不好」,一起上会分不清是谁的锅。
先挑一类材料试点
试点材料的选择标准有两条:查询量够大、结构够规整。表格类通常同时满足——它是非文本内容里结构最规整的一种,版面解析的准确率比自由图形高,业务对表格数据的查询需求也最密集。
从表格切进去还有个好处是评估最好做:表格有明确的表头与行列,对照集里的「答案」可以精确到某个单元格,判定没有歧义。跑通这条链再往图形类扩展,改动的只是描述层和嵌入层。
要看的四组数
| 指标 | 口径 | 上升说明什么 |
|---|---|---|
| 图表召回率 | 对照集里答案图被召回的比例 | 检索在变好 |
| 索引体积 | 非文本索引的条目数与存储量 | 切分太细或相邻图重复入索引 |
| 摘要生成成本 | 单位材料触发的模型调用次数 | 更新过于频繁或重算条件过宽 |
| 更新后失效比例 | 材料更新后摘要/向量未同步的比例 | 更新管道没有接上 |
四组数要一起看。召回率上升但索引体积翻倍,说明提升是靠「多切多召回」换来的,长期会拖垮维护;摘要成本上升而召回率不动,说明重算被无谓触发了。
从试点到常态化的判据
扩到全量之前,先确认三件事都成立:抽取侧的block_type判定准确率稳定(否则后面的过滤与评估都建立在错误分类上)、更新管道能在材料变更后自动触发重算(否则失效比例会持续积累)、两路合并权重已在评估集上标定(否则旁路加进来只会让排序变差)。三件里任何一件没落地,扩量只会放大一个无法归因的问题。
⚠️ 代码待验证
# 试点脚本:只对表格类块跑一遍抽取与结构化,先看 block_type 判定准确率# 具体命令按自己的抽取管道调整python run_extract.py\--in./pilot_docs\--block-types table\--emit-block-type\--out./index/pilot_figures.jsonl# 统计 block_type 判定分布,误判率高就不要往下一步走jq-r'.block_type'./index/pilot_figures.jsonl|sort|uniq-c完整版资料清单:本文用到的试点材料清单与观测指标定义都整理在里面了,扫码即可获取:
附表 A:关键取舍一览
本文涉及的所有工程判断集中在这里,方便按需回看。
| 取舍 | 本文结论 | 判断依据 | 位置 |
|---|---|---|---|
| 先判断丢的是哪一类 | 分抽不到、顺序乱、语义散三类 | 三者修复层不同 | 第一章 |
| 抽取层要不要动 | 只有「抽不到」必须动抽取 | 后两类在切分与描述侧即可救 | 第一章 |
| 做法怎么选 | 先定查询形态,再取最少够用的一层 | 凭先进程度选会选贵 | 第二章 |
| 多模态嵌入要不要上 | 只在「找相似」占比高时上 | 它的成本主要在维护重算 | 第二章 |
| 摘要要不要全量跑 | 先圈查询,再决定给多少张图生成 | 一次性开销大且质量不可预知 | 第三章 |
| 切分单位 | 图 + 图注 + 章节路径 + 邻近引用句 | 单切图无语义,单切段语义被稀释 | 第四章 |
| 章节路径 | 必须带层级,不能只留页码 | 支持按章节约束召回与定位 | 第四章 |
| block_type 何时定 | 抽取阶段就落枚举 | 检索时再补要重扫原文 | 第四章 |
| 相邻图怎么处理 | 精确去重 + 相似图降权不删 | 局部图在细节查询里可能更对题 | 第四章 |
| 两路分值怎么比 | 各自路内归一再加权求和 | 不同量纲的相关性分不可直接比 | 第五章 |
| 合并权重怎么定 | 用两路都覆盖的评估集标定 | 凭手感设权重必然偏 | 第五章 |
| 某一路为空怎么办 | 标记缺失,不补零分 | 零分会被当成强负样本 | 第五章 |
| 两路分工 | 主路保覆盖,旁路保精度 | 文本命中比图命中更易确认 | 第五章 |
| 评估怎么建 | 从真实检索日志长出对照集 | 想象构造覆盖不到真实分布 | 第六章 |
| 失败怎么归因 | 召回率与可用率分开算 | 合算会混淆召回与渲染问题 | 第六章 |
| 重评节奏 | 定向重评跟更新,全量重评按周期 | 前者抓新错,后者抓漂移 | 第六章 |
| 试点选什么材料 | 表格类,查询量大且结构规整 | 评估可精确到单元格 | 第七章 |
| 什么时候能扩量 | block_type 稳、更新能自动触发、权重已标定 | 三缺一都会得到不可归因的低召回 | 第七章 |
附表 B:术语速查表
| 术语 | 含义 |
|---|---|
| 非文本内容 | 以图像形态存在、正文段落里没有对应字符的内容 |
| 版面分析 | 判断页面区块所属栏、表、图注的分析步骤 |
| 阅读顺序还原 | 按二维版式把字符重新拼成正确语序 |
| 图注增强 | 用图周围文字(图号、图题、章节、引用句)给图打标签 |
| 结构化描述 | 把表格还原成表头加行的键值形式,保留字段语义 |
| 自然语言摘要 | 由模型读图并写出描述性文本,接进文本检索 |
| 多模态嵌入 | 把图与文映射到同一向量空间,支持图文互搜 |
| 可检索单元 | 图本体加图注、章节、引用句组成的索引基本块 |
| 章节路径 | 带层级的所属章节标识,用于约束召回与定位 |
| 相邻图去重 | 对成组出现的图做精确去重并对相似图降权 |
| 分值归一 | 把各路检索分数压到统一区间再合并的做法 |
| 兜底路 | 两路都空时放宽条件重新召回的那一条路 |
| 对照集 | 问题、答案所在图、是否需渲染组成的评估集合 |
| 召回率 | 目标图出现在候选结果里的比例 |
| 可用率 | 召回后用户真的能看到内容的比例 |
| 失效比例 | 材料更新后摘要或向量未同步的比例 |
写在最后:这篇用到的资料
写这篇文章时,我把几个模型侧与检索侧的文档、字段表和实测记录都对了一遍,顺手整理成几份配套的东西:
- 大模型学习路线图:从 LLM 基础到 Agent 开发,各阶段该学什么、用什么资料
- 大模型全套教程:按主题分好的视频与文档清单
- 大模型实战好书:24 本,附每本适合的阶段
资料是我自己整理的,放在下面这个码上,扫码即可获取:
添加时备注「大模型」,优先通过。
拿到之后建议先看学习路线图那一份,先定位自己在哪个阶段,再决定学什么,比一上来就啃框架效率高得多。