回答里的出处不直接把真实块 UUID 交给模型,也不信任模型手写的 <kb chunk_id="...">。WeKnora 在单次请求内把来源登记为短别名(如 c1、w1),要求模型只输出 <ref id="cN"/>;流式生成后再由系统展开成真实块引用。用户侧「点到原文」的主路径是 chunk_id → 块上存的文本;不同类型文件(MD、PDF、Office 等)在入库时已被抽成同一套文本块,呈现时再按类型决定是弹块内容、拼全文,还是另开原件预览。
方案怎么做
别名怎么来、cN 怎样变成 chunk_id
检索结果(或 Agent 工具结果)进入模型前,注册表 RegisterChunk:
- 按本请求内首次登记顺序编号 :第 1 个新块 →
c1,第 2 个 →c2......即c%d = 当前已登记别名数 + 1。 - 不是 按文档里的
chunk_index、也不是按库表插入顺序。 - 同一
chunk_id再出现只复用原别名,避免 Agent 多轮里同一块多个号。
go
// internal/llmreference/registry.go
if existing := r.chunkByID[ref.ChunkID]; existing != nil {
return existing.Alias // 复用
}
ref.Alias = fmt.Sprintf("c%d", len(r.chunkByAlias)+1) // 顺序递增
网页同理为 wN。展开时查 chunkByAlias,写成 <kb doc="..." chunk_id="真实UUID" ... />;未知别名与模型伪造的 <kb> 丢弃。别名仅存活于本次助手回答。
从 chunk 回到「原文」:各类型怎么呈现
先分清两层对象:
| 层 | 是什么 |
|---|---|
| 检索/引用层 | 库里的 Chunk:chunk_id + content + start_at/end_at(在解析后文本流上的字符区间) |
| 原件层 | 用户上传的 MD / PDF / DOCX 等文件本身 |
入库时 DocReader 等会把 PDF、Office 等先抽成 Markdown/纯文本,再分块;开源路径里 角标溯源默认先落到块文本 ,并不依赖拿 start_at 去 PDF 页上画红框。页级/框级高亮是另一类能力(见下文 PDF),技术上可做,部分基于 WeKnora 的产品已经做得更细。
用户点引用后的共同路径(关键)
找到 chunk_id 之后,并不是 再打开「全文 MD」、用 start_at 滚到某一行高亮。产品把该块自己存下的 content 当作可展示的原文片段:
text
<ref id="cN"/> → 展开 chunk_id
→ 悬停:GET 该块 → 浮层直接渲染 chunk.content(这一段就是给用户看的「原文」)
→ 点击:引用面板按 chunk_id 高亮列表项,展开的仍是检索时带上的块文本 / snippet
→ 另开文档抽屉:可看「分块列表 / 拼出来的全文 / 原件预览」------与角标「精确定位到全文第 N 字」不是同一条链路
所以 MD 也一样:定位主键是 chunk 记录,不是全文里的光标位置。 start_at/end_at 不参与「点引用 → 滚到全文某处」。
那 start_at 干什么用?「全文」又是什么?
入库切分时,start_at/end_at 记在当时那份文本流 上的字符区间,主要用于块列表排序、合并时估重叠窗口------不用于点引用时跳转到全文某处。
文档抽屉里的「全文」视图,本质是 多块相邻拼接、去掉重叠,凑出一篇可读长文:
text
块1.content + 块2.content + 块3.content + ...
│ │
└─ 按 start_at 排序
└─ 相邻若有公共后缀/前缀 → 文本重叠匹配去掉重复再接上
└─ 中间有明显间隙 → 空行硬接
↓
拼出来的「全文」(给用户通读)
要点:
- 凑的是各块里已存的
content,不是再打开磁盘上的 MD/PDF,用[start_at:end_at]去切现行文件。 - 分块本身常带 overlap,所以必须去重拼接,否则全文会重复段落。
- 点引用角标不走这条路 :角标只展示那一个块 的
content;「拼全文」是抽屉里另开的通读能力,不会先拼完全文再在里面二次定位该块。
文章改了,start_at 会不会不准?
会。偏移绑定的是分块当时 的文本;源文件变更后若未重新解析/分块,旧区间相对新文件就会漂。但角标展示走的是块里快照式的 content,不依赖拿现行文件按旧偏移去切,所以「点引用看到的那段话」仍是入库(或上次索引)时的块内容。要与最新原文一致,需要重新处理文档生成新 chunk;这也是不做「全文内字符级跳转」的原因之一------偏移体系本身怕文档漂移。
按类型:定位与呈现
| 类型 | chunk 里有什么 | 点引用时用户看到什么 | start/end 与「全文」 |
|---|---|---|---|
| Markdown / 纯文本 | 对源 MD/TXT 分块;content≈当时原文片段 |
直接展示该块 content;不在全文里二次定位 |
排序 + 相邻块去重拼接成「全文」视图 |
| PDF(电子页) | 内置路径从文字层抽出 MD 再分块;content 接近可选中文本 |
开源默认 :浮层/引用面板展示抽取块文本;抽屉可用 iframe 预览整份 PDF,不按 chunk 跳页画框。若解析写入页码,前端用文字层匹配/坐标,即可做到「第几页 + 高亮那段字」(腾讯 ima 等基于 WeKnora 的产品已有类似回溯高亮) | 开源里 start_at 主要在抽取 MD 上;页级能力要另存 page/bbox 或页内文本映射 |
| PDF(扫描页) | 页先渲染成图写入 MD(scanned_pdf),可检索正文多来自后续 VLM OCR / 配文子块 |
开源默认 :角标常落到 OCR 子块(识别稿)。若 OCR/版面引擎同时产出页码 + 文字框,预览可在页图上画框回标;纯「整页图 → 一段无框 OCR 文本」则很难精到框 | 比电子页多一跳「图 → 认字 → 子块」;框级回溯取决于 OCR 是否带几何信息,不是 chunk 文本本身自带的 |
| DOCX / PPT / 表格等 | 同样先抽取再分块 | 引用侧看块文本;抽屉可预览原件 | 拼全文 / 原件预览两条线,引用不映射版心坐标 |
| FAQ / 无文件知识 | 块即问答条目 | 浮层直接展示条目文本 | 无文件原件;一般不谈拼全文 |
网页引用 wN |
无 chunk,展开为 <web url> |
打开/展示 URL | 不走 chunk 区间 |
同一份 PDF 里电子页与扫描页可以混排(见《WeKnora 文档解析》):电子页走文字块,扫描页走图片 + OCR 子块。开源默认溯源都是 chunk_id → 块 content,但块里装的「原文」来源不同------一个是抽字,一个是认字。
text
MD/TXT: 文件 ≈ 文本流 ≈ chunk.content → 单块即片段;多块去重拼接 ≈ 通读全文
PDF 电子页: 文字层 → MD → text chunk → 开源默认看抽取稿;
增强:页码 + 文字层匹配/bbox → 预览内跳页高亮(如 ima)
PDF 扫描页: 渲染图 → OCR/配文子块 → 开源默认看识别稿;
增强:OCR 带页码+框 → 页图上画框;无框则难精到「哪个框」
开源默认 vs 产品增强(PDF)
- 开源 WeKnora:引用主路径是块文本;PDF 预览多为整文件打开,公开前端路径里未见「按 chunk 跳页 + 页内红框」的完整闭环。解析里出现的 bbox 更多用于抽图裁剪,不是引用回标协议。
- 可实现,且部分产品已做 :电子版用文字层定位相对直接;扫描版要 OCR/版面输出几何信息。腾讯 ima 等基于 WeKnora 的产品已能在回溯时高亮原文,属于在统一 chunk 之上叠加的阅读器能力。
提升了什么
别名协议把「能否引用」收成系统查表;开源呈现侧用统一的 chunk 文本 覆盖多文件类型,先保证「这段话从哪块来」。MD 接近「块即原文」;PDF/Office 默认「读内容看块、看版式看预览」。页级高亮作为可选增强层,不挡主链路落地。
好处与代价
好处
- 降低模型编造出处;展开失败则标签消失。
- Agent 多轮同块别名稳定;角标与引用面板可分开控制。
- 多格式共用一套 chunk 溯源;页内高亮可按产品需要叠加,不必绑死开源默认路径。
代价
- 流式标签需缓冲;自定义提示可能干扰
<ref>协议。 cN是当次登记序,跨请求不能说「还是 c3」。- 开源默认:PDF 电子页精到抽取段落,扫描页精到 OCR 子块------还不是「第几页哪个框」。要页/框级需改元数据与 PDF 阅读器;扫描页还受识别质量影响,角标文案可能与肉眼看页有出入。
- 全文合并依赖文本重叠,极端脏数据拼缝可能不完美。
基于 WeKnora v0.7.0
上一篇:WeKnora 上下文管理 | 下一篇:WeKnora 模型接入