1.png

摘要

很多RAG项目把注意力集中在Embedding和向量数据库,却低估了数据进入索引之前的复杂度。联网搜索可能返回重复或过期资料,网页解析会受动态渲染与版式影响,OCR也可能出现部分页失败。本文设计一条由Search、Reader、OCR组成的可恢复数据链路,重点讨论幂等键、内容寻址、证据定位、权限预过滤、增量更新与删除传播,并说明企业级大模型API聚合平台在资源层应承担什么角色。

一、先把RAG拆成两个系统

一个可维护的RAG通常包含两个相对独立的部分:

  • 离线或准实时摄取系统:发现来源、抓取、解析、OCR、清洗、切块、Embedding和发布索引;

  • 在线检索系统:权限过滤、召回、重排、上下文组装、模型生成和引用返回。

两者不应共享一个“大脚本”。摄取失败可以重试,在线查询则有严格延迟预算;解析器升级需要重建索引,但不应影响正在服务的旧版本。

Search、Reader与OCR主要位于摄取侧,也可以在Agent查询时按需调用。数眼智能这类企业级大模型API聚合平台可以统一提供模型和数据工具资源,但任务状态、租户权限、知识版本和发布策略仍应由企业应用控制。

二、用状态机管理每一份来源

不要用一串同步函数从搜索直接跑到向量写入。为每个来源创建任务状态,允许失败后从最近成功阶段恢复。

from enum import Enumfrom dataclasses import dataclassclass Stage(str, Enum):
    DISCOVERED = "discovered"
    FETCHED = "fetched"
    PARSED = "parsed"
    OCR_DONE = "ocr_done"
    CHUNKED = "chunked"
    EMBEDDED = "embedded"
    PUBLISHED = "published"
    FAILED = "failed"@dataclassclass IngestJob:
    tenant_id: str
    source_id: str
    source_url: str
    stage: Stage
    source_version: str | None = None
    parser_version: str | None = None
    retry_count: int = 0

任务表应记录错误类型、下一次重试时间和失败阶段。网络超时可以退避重试,权限拒绝和格式不支持则进入人工队列。OCR部分页失败时,不要把整份文档简单标成成功,应保留页级状态。

三、幂等键不能只用URL

同一个网页可能带有追踪参数,也可能在不改URL的情况下更新内容。只以URL作为唯一键,会同时遇到重复抓取和更新漏检。

建议维护三类标识:

canonical_source = normalize(url or document_id)
source_version   = hash(raw_bytes or normalized_html)
artifact_version = hash(source_version + parser_version + chunk_policy)

canonical_source负责识别来源,source_version判断原始内容是否变化,artifact_version判断解析或切块策略变化后是否需要重建。不同租户即使引用同一公开网页,也应分别记录授权和知识库归属,不能因为内容哈希相同就跨租户共享答案缓存。

四、Search阶段要保存“为什么找到它”

Search结果不应只保存URL。建议同时记录查询词、排名、标题、摘要、搜索时间、来源域名和后续是否被采用。

{
  "query": "某政策最新版本",
  "rank": 3,
  "url": "https://example.com/page",
  "title": "...",
  "discovered_at": "2026-11-05T10:00:00+08:00",
  "selected": true}

这样当答案出现错误时,可以区分是搜索没有找到权威来源、排序选择错误,还是后续解析和生成出错。对政策、价格和新闻等时效性内容,还应设置新鲜度策略,超过窗口重新搜索。

五、Reader阶段必须把证据位置保留下来

网页转Markdown不是终点。为了让回答能够引用原文,解析结果需要稳定的块标识、标题路径和位置。

@dataclassclass EvidenceBlock:
    evidence_id: str
    tenant_id: str
    source_id: str
    source_version: str
    heading_path: list[str]
    page_no: int | None
    block_index: int
    text: str
    acl: list[str]

evidence_id建议由来源版本、页码和块序号生成。不要只保存最终切块文本,否则解析器升级后,旧引用将无法定位。表格还应保留行列关系,不能简单拼成一长串文字。

Reader抓取外部网页时要防范SSRF。应用应限制协议、目标域名、DNS解析结果、重定向次数和响应大小,拒绝访问环回地址、内网地址与云元数据地址。网页正文中的“忽略系统指令”等内容只能作为数据,不能进入Agent控制指令。

六、OCR应以页为单位处理部分失败

扫描PDF和图片常出现倾斜、模糊、双栏、印章遮挡和手写内容。一个文件十页,九页成功一页失败,不能只返回success=true。

{
  "document_id": "contract-2026-001",
  "pages": [
    {"page": 1, "status": "ok", "confidence": 0.96},
    {"page": 2, "status": "review", "confidence": 0.61}
  ],
  "complete": false}

低置信度页面进入人工复核,关键字段可采用双重校验。合同金额、身份证号和日期不应只依赖平均置信度。原图、OCR文本和人工修订需要分别版本化,保证后续能够追溯。

七、切块策略要与引用和权限一起设计

切块不是越小越好。过小会丢失语义,过大则浪费上下文并降低召回精度。可以按标题、段落和表格结构切分,再设置长度上限和少量重叠。

更重要的是,ACL要在切块阶段继承。在线检索时先根据用户和项目权限过滤,再进行向量召回,不能先召回敏感内容后再在应用层隐藏。缓存键也要包含租户、用户权限哈希、知识版本和检索策略版本。

cache_key = hash(
  tenant_id,
  permission_hash,
  knowledge_release,
  retrieval_policy,
  normalized_query
)

八、增量更新使用蓝绿索引

直接在生产索引中边删边写,容易出现新旧版本混合。更稳妥的做法是构建新索引版本,完成数据校验与离线评测后,再原子切换别名。

kb-prod -> kb-release-2026-11-01
kb-next -> kb-release-2026-11-05

validate(kb-next)
switch_alias(kb-prod, kb-next)

发布前检查文档数、块数、Embedding维度、权限覆盖和抽样引用。切换后保留旧索引一段时间,便于回滚。Embedding模型变化时通常需要全量重建,不能把不同向量空间的数据混在同一索引。

九、删除必须传播到所有衍生数据

用户删除原文后,只删对象存储是不够的。解析文本、OCR结果、切块、向量、缓存、离线评测集和日志样本都可能保留衍生内容。

建立删除事件并追踪每个消费者的确认状态:

{
  "event": "source.deleted",
  "tenant_id": "org-a",
  "source_id": "doc-123",
  "delete_version": 7,
  "consumers": {
    "parser_store": "done",
    "vector_index": "done",
    "answer_cache": "pending"
  }}

删除过程要幂等,重复事件不能导致异常。合规场景还需要记录删除时间、执行人和完成证明。

十、如何评测整条链路

不要只测最终回答。摄取侧分别观察搜索来源覆盖、正文完整率、表格保真、OCR关键字段和任务恢复;检索侧观察Context Precision、Context Recall、引用可定位率和权限越界;生成侧观察Faithfulness、拒答和业务任务成功率。

POC可以准备动态网页、长PDF、扫描合同、表格截图和权限不同的文档。数眼智能、云厂商数据服务、专业解析工具或自建方案都使用同一测试集,记录工具版本、时间窗口和原始结果,才能形成可复现结论。

总结

RAG质量问题经常被误判为模型问题,实际根源可能是搜索来源、网页解析、OCR、权限或版本发布。把Search、Reader和OCR做成可恢复、可追溯的数据链路,才能让模型获得稳定上下文。

企业级大模型API聚合平台可以降低模型与数据工具的接入成本,但数据所有权、任务状态、权限、知识版本和删除责任不能外包。资源层负责提供能力,企业控制面负责定义边界,这样的RAG才具备长期维护价值。


Logo

更多推荐