🏠 总目录📚 本教程 11b · 文档入库与治理 ← →
📑 本页目录(点开跳转)

智能体工程教程 · 按需阅读

先证明文档入对了库,再追问模型为什么答错。

内容、版本和权限是三件事;任何一件没验收,回答都可能没有可信依据。

先观察一个失败也能解释的结果

同一版本重复导入不应制造重复块;发布失败时旧版继续服务;撤权后旧引用也必须被拒绝。

11b · 文档入库:更新、删除和撤权都算数据

⏱ 按阶段实践 | 先跑文本,再按岗位补扫描件与复杂表格

🎯 一句话

先证明进入索引的内容正确、当前且有权访问,再讨论模型是否聪明。 这一页把“上传文档”展开成可回放的入库任务。

🔗 前置是检索与 RAG;环境见共同项目。命令在仓库根目录运行。实现见 core.py,负例见 test_core.py。

先看到内容:解析与文档身份

先跑 inspect,再用字段与输入类型检查表核对结果。

🔬 先观察解析后的内容

python examples/knowledge-assistant/lab.py inspect examples/knowledge-assistant/corpus/limits.md --chunk-chars 450
python examples/knowledge-assistant/lab.py init
python examples/knowledge-assistant/lab.py jobs

第一条要看 body 中表头、数值和单位是否在一起;后两条分别发布初始版本、查看任务记录。

重复 init 应报告已有相同版本,而不是制造重复块。先核对文件名与 manifest,不要把失败当成“模型没理解”。

一个文档至少携带哪些信息

字段 来源 为什么需要
tenant、readers 已认证连接器与权限目录 用户不能在提问里自报租户获得权限
doc_id 源系统稳定 ID 改标题后仍知道是同一个文档
revision 源系统递增版本,或服务端分配 阻止迟到的旧事件覆盖新版本
原始内容哈希 文件字节 发现同版本内容冲突、定位输入
parser / chunk 配置 构建版本 解释为什么同一文件产生不同块
页码、章节、块序号 解析器 引用能返回到原文位置
状态、尝试次数、错误码 入库任务 失败后能回放,区别“未读到”和“已发布”

本项目 manifest 是受信任管理员的导入接口,不能直接暴露为匿名上传 JSON。路径被限制在语料目录内;将来接对象存储还要限制桶、前缀、文件大小与下载超时。

一、解析完成不等于解析正确

输入 先检查的可观察结果 失败去向
Markdown / UTF-8 文本 标题归属、中文解码、段落顺序 空内容或坏编码拒绝发布
普通 PDF 文本层 每页字符、阅读顺序、页码对应 本项目可选 pypdf;稀疏页进入人工/OCR 复核
扫描 PDF / 照片 OCR 文本、置信度、旋转、漏页 OCR 是扩展任务,当前代码没有实现
双栏、跨页表格 左右栏顺序、重复表头、单位、脚注 逐页视觉核对,不能仅看提取字符数
图表、公式、合同印章 原图定位、识别内容与适用范围 保留图像来源;无法识别时显式说明

有文本的 PDF 也可能读错顺序。 pypdf 能读到字符不代表复杂版面已验收;本项目的“短页拒绝”只是保守筛查,不能检测全部 OCR 错误。

岗位要求文档智能时,单独准备文字页、扫描页、旋转页、双栏页、跨页表各类夹具,并保存页图与抽取结果对照。

切块保留语义,不只追求长度

表头、单位、条件不要离开它们约束的答案。

二、切块先保护语义,再测长度

本项目按标题、段落和句子切分;Markdown 表格连同前一段说明作为整体保留。chunk_chars 按字符计算,不是模型 token,表格可能超过目标长度。上线模型前,用它自己的 tokenizer 统计实际输入长度和截断率。

调整 想解决什么 可能付出的代价 验收
减小块 大块混入多个主题 条件、单位与答案分离 检查原问题仍能在同块或关联块得到依据
增大块 答案缺上下文 相似度被稀释、上下文挤占 Recall 与有效证据占比一起看
overlap 边界断句 重复召回、更多向量与费用 去重后有效文档数,不只看块命中
父子块 小块定位、大段回答 引用与权限映射复杂 父段同样验 ACL,不能借扩展段泄漏
上下文补全 孤立片段缺产品/章节名 生成上下文可能编造条件 保存原块与补全文分栏,抽查事实一致性

当前参考实现没有 overlap、父子索引和 LLM 上下文补全;它保留简单基线,便于逐项加上后做消融。调参记录方法见11c。

🛑 现在可以停:已经能解释一个块来自哪一页、为什么这样切。下一段处理数据上线后才出现的问题。

发布与迁移:不能露出半个版本

先看事件到达后的正确状态,再运行负例测试。

三、发布是一笔事务,失败不能露出半个版本

本项目的顺序是:登记任务 → 解析和校验 → 在事务内检查版本 → 替换该文档全部块 → 发布元数据。事务失败时旧版本仍可检索;任务单独留下失败记录。读者不会看到“新文档标题搭配旧段落”。

到达的事件 正确结果 参考实现中的证据
相同版本、相同内容重投 成功且不重复 test_duplicate_ingestion_is_idempotent
相同版本、不同内容 明确冲突 test_same_revision_different_content_is_rejected
v3 后收到 v2 保留 v3 test_out_of_order_revision_cannot_win
解析后崩溃 / 提交前失败 旧版继续服务,可回放任务 test_publish_failure_preserves_previous_version
删除后迟到的旧任务 保留删除墓碑,不复活 test_delete_tombstone_blocks_delayed_job
文档撤权但旧引用还在 再次验证时拒绝 test_acl_change_invalidates_old_citation

运行这些负例:

python -m unittest discover -s examples/knowledge-assistant -p test_core.py -v

本地 SQLite 事务演示发布边界;它没有分布式队列、自动租约抢占与死信平台。

接生产队列时,在同一逻辑上补任务租约、心跳、最大尝试次数、退避、人工重放入口和唯一事件键。队列“至少一次”投递要求消费者幂等,不能靠“通常不会重复”。

改解析器和 embedding 时怎么迁移

解析器版本和切块参数进入指纹;同一文档版本不能悄悄改结果。本地实验换配置用新的数据库。正式系统建立新索引版本,回填历史数据并接住增量,比较抽样与总数、权限和时间水位,再切读取别名。

回滚依赖旧索引仍能表达当前撤权和删除状态。只保留一份旧向量快照,却不继续应用权限变更,会把“回滚”变成信息泄漏。新旧 embedding 空间不能混搜;记录模型、维度、归一化、距离函数、查询前缀与权重哈希。

权限不是检索末尾的一次过滤

下载、父段扩展、缓存和恢复都要重新核对权限。

四、权限要穿过每一条取数路径

SQL 先按可信租户筛选,本项目再检查 readers;BM25、向量、融合都只看可见块。引用复核再查当前权限与版本。向量仅在进程内缓存可见快照,模型权重可以落盘。

扩展时逐个检查:搜索、资源 URI、父段扩展、下载原文、历史消息、向量缓存、答案缓存和备份恢复。答案缓存键至少绑定租户、权限版本、语料版本与模型配置;撤权要能使缓存失效。答案缓存命中不能跳过当前授权。

检查自己的解释,再进入检索调优

能说出一个失败的最终状态,就有了下一轮排查起点。

✅ 检查点

  1. 为什么文件名和内容哈希不能代替版本号?
  2. 换切块参数后,用相同版本直接覆盖有什么问题?
  3. 为什么过滤检索结果后再做父段扩展仍可能泄漏?
展开答案
  1. 文件名会改,哈希只能判断是否相同,不能判断事件先后。需要稳定 ID 与可比较的版本。
  2. 无法区分同版本冲突与新索引规则,也难以回放。用新索引版本或明确的服务端版本迁移。
  3. 父段可能跨权限边界;每条扩展取数都需要按当前身份重新授权。
⚡ 走神救援

先验解析,再切块;同一版本原子发布;旧事件不能覆盖更新与删除;权限跟着每一次取数走。

🔗 接下来去哪

➡️ 下一站:11c · 检索评测与调优实验。

打卡记录保存在你的浏览器里,首页能看到总进度