VDR DATA OS
VDR / 项目文档 / 合成流程

合成流程

输入、生成、QC、终审与审计规范

VDR 数据合成流程

本文定义合成链路的长期规则,不负责记录项目当前阶段或重复汇报批次结果。当前状态见 STATUS.md,实际结果见 生成样本分析.md

目标是将页面材料转成可验证的 VDR 训练候选。模型仅以页面图片和固定任务 Prompt 为输入;OCR、Markdown、HTML、caption 和页面事实不得进入 Prompt,只用于离线校验。

页面来源
→ 统一页面 manifest
→ 图片 + 固定生成 Prompt(禁止附带 OCR/Markdown/page text/caption)
→ 生成 Query / Answer / Evidence 候选
→ 离线用 OCR/Markdown/HTML 校验 Answer / Evidence
→ 规则校验与去重
→ Q-A 质检集
→ query-image 检索训练集
→ 难负例与训练导出

开源数据的事实依据见 开源数据集分析.md,Qwen 服务参数见 Qwen3.6-27B服务调用.md

基本原则

  • 训练 VDR embedding 最终只需要 query + image,但合成阶段必须保存 answer + evidence,以验证 Query 是否确实由正样本页面支持。
  • 图片本身用于生成 Query;OCR/布局文本或结构化表格仅作离线自动校验依据,绝不作为模型输入或 Prompt 附加上下文。
  • evidence 不能由模型自由编造,也不能把页面摘要当作数值真值。可在页面文本中验证的样本标记为 pass;只有图片、缺少可验证文本的样本,在 V1.1 宽松策略下可标记为 provisional 并进入趋势观察导出;正式严格训练仍可只取 pass
  • shennong2_chart,只有 markdown/html 可作数值和表格答案依据;caption 不得作为生成输入,仅可用于离线人工抽检参考。
  • 训练和评测按 document_id 切分,不能将同一文档的不同页同时放入训练与评测。
  • 原始页面、OCR、manifest、生成结果都属于业务/数据产物,不提交 Git。

统一页面清单与校验真值

V1 同时支持 shennong2_chart 与业务页面。生成输入和校验真值必须物理隔离,避免页面文字被误传给模型。

生成清单 _过程文件/页面清单.jsonl

{
  "document_id": "稳定文档 ID",
  "page_id": "稳定页面 ID",
  "image_path": "/可读取的页面图片路径",
  "image_hash": "sha256",
  "source": "shennong2_chart | business",
  "data_role": "train_source | evaluation_only",
  "source_split": "train | test | custom",
  "language": "zh | en | mixed",
  "metadata": {}
}

离线真值 _过程文件/校验参考.jsonl

{
  "document_id": "稳定文档 ID",
  "page_id": "稳定页面 ID",
  "validation_mode": "strict_text | image_review",
  "refs": [
    {"type": "ocr | markdown | html", "content": "仅校验脚本可读的页面真值"}
  ]
}
  • 模型请求构造只使用 _过程文件/页面清单.jsonl 中的图片和固定 Prompt;响应返回后,校验阶段才读取 _过程文件/校验参考.jsonl
  • data_role=train_source 才能生成;evaluation_only 必须立即拒绝。不能用上游名为 train 的 split 代替该门禁。
  • image_path 在生成和训练导出时必须可读。Parquet 内嵌图片先落为 PNG/JPEG;PDF 页面先渲染为 PNG。
  • document_id + page_id 是唯一页面身份;简单文件名和内部存储 URI 不能作为全局 ID。
  • 有 OCR/Markdown/HTML 时使用 strict_text;只有图片时使用 image_review。V1.1 宽松策略下,image_review 且 schema 通过的样本标记为 provisional,可进入趋势观察导出;若要回到严格模式,只允许人工/独立视觉复核后的 pass

V1:单页、证据可校验的合成

范围

  • 单页问题;当前严格基线每页生成 1 条,代码允许后续调为 2–3 条。
  • 模型输入仅限页面图片和固定生成 Prompt;禁止将 OCR、Markdown、HTML、caption、结构化字段或页面真值答案放入模型 content
  • 仅支持 span:字段抽取、说明项查找、图表/表格单元格查找。
  • 同时覆盖检索式 Query 和页面问答式 Query,但不得使用“本页”“上图”“图 1”“subfigure (b)”等离开页面就失去语义的指代。
  • 先使用训练 batch 内其他页面作为负样本;不挖掘显式 hard negatives。

这对应 colpali_train_set 的短字段问答、VisRAG 的检索式 Query、MP-DocVQA 的页面问答,以及 shennong2_chart 的表格单元格查询。

生成配置

  • 使用 Qwen 非思考模式:chat_template_kwargs.enable_thinking=false
  • 多模态 content 固定为图片在前、文本 Prompt 在后;Prompt 不使用 Think as needed,不要求模型输出推理过程。
  • 正式请求使用 response_format.type=json_schemastrict=true 约束候选结构;json_object 不能保证字段完整。
  • 当前初版统一使用 temperature=0;后续生成多样 Query 时再试 0.6–0.8,并单独统计格式通过率、证据通过率和重复率。
  • 当前使用 top_p=0.8top_k=20presence_penalty=1.5max_tokens=1024512 在 84 页试点中出现过长度截断;提高上限不改变短 JSON 和 finish_reason=stop 门禁。
  • 模型输出必须是严格 JSON 对象,顶层为 candidates 数组;每个候选包含 queryanswerevidenceanswer_typequery_type
  • 模型边界的 answerevidence 始终为字符串数组;写入 QC 时再把 evidence 转为带 page_id 的对象数组。V1 只引用当前页,V3 可自然扩展到多页。
  • JSON 示例只能描述字段结构,不能写入当前页面的真实答案作为示范,避免答案泄漏。
  • answer_type 固定为 span;不要在 V1 生成需要计算、跨页或视觉定位的问题。

严格质量门槛

  1. 输入:图片可读,document_id + page_id 唯一,页面语言已标记。
  2. Query:非空、长度合理、语言与页面一致;过滤纯噪声、答案式/列表式问题和泛化实体(如“某企业”“某时期”)。
  3. 上下文:V1.1 只拒绝“本页/上图/图 1/subfigure/this page”等强指代;轻度页面依赖不再硬拒。
  4. 证据:strict_text 要求答案命中真值;证据可适度放宽。答案命中但证据略偏 → provisionalimage_reviewprovisional
  5. 去重:同页 Query 精确去重;同文档后续做语义去重;终审拦截官方/测试重合。
  6. 导出:V1.1 趋势批次导出 pass + provisional;正式严格训练可只保留 pass
  7. 切分:先按 document_id 留出约 5% 作为 synthesis_dev_holdout,用于合成链路的内部开发检查,再生成/导出训练集,避免页级泄漏;它不是冻结 benchmark。

为什么初版门控较严格

V1 的目标不是最大化候选数量,而是先证明“图片 → Query → 可验证证据 → 训练对”的链路不会静默写入错误样本。严格门控基于以下风险:

  1. VDR 对比学习会把 Query 和页面直接拉近。错误 Query、错误数值或假正例会直接教坏 embedding,而且训练导出不含答案,导出后很难追查。
  2. 模型即使关闭思考并返回合法 JSON,仍会看错图表数值。试点中曾把 shennong2_chart1200 读成 1250,格式门控无法发现,必须依靠离线真值。
  3. 模型会生成“图中、根据本文、文档标题”等页面依赖或低信息问题;这些问题作为页面问答可能成立,但作为独立检索 Query 容易形成捷径。
  4. 开源训练集与公开测试集存在真实重合;如果只相信上游 split 名,可能把 benchmark 页面用于训练。
  5. 多候选、高温和普通 json_object 在实测中会增加字段缺失、候选数量不符、代码围栏和长度截断。初版先固定稳定组合,再逐项放宽。

严格门控是初版质量基线,不是永久标准。被拒绝的候选保留在 _过程文件/失败记录.jsonl,方便根据真实失败分布决定后续放宽,而不是直接丢失原因。

当前全部生成设置

以下设置对应 scripts/数据合成/流程阶段/生成检索样本.py

  • 服务:model=ms-g9248pbm;默认读取 Git 忽略的 scripts/数据合成/secrets.qwen.json,环境变量可覆盖。
  • 图片:读取本地 PNG/JPEG/WebP/GIF,校验 SHA-256 后编码为 base64 data URI。
  • 消息顺序:image_url 在前,固定文本 Prompt 在后。
  • 思考:chat_template_kwargs.enable_thinking=false;Prompt 禁止推导和解释。
  • 结构:response_format.type=json_schemastrict=true;顶层只能有 candidates
  • 采样:temperature=0top_p=0.8top_k=20presence_penalty=1.5
  • 输出:默认每页 1 个候选,max_tokens=1024,请求超时 180s
  • 并发:默认 32,当前服务支持最高 64;可通过 --concurrency 调整。
  • 单次重试:API、网络和响应级 JSON 失败默认额外重试 2 次。长度截断只自适应从 1024 提升到 2048;若仍截断则停止,避免温度 0 下反复生成同一退化长输出。
  • 断点续跑:每页结束后立即追加并 fsync_过程文件/生成断点.jsonl。再次执行相同命令时,成功页面直接跳过,只重跑请求级失败和中断前尚未落盘的页面。
  • “请求成功但候选被 Query/证据门控拒绝”属于已完成质检,默认不重采样,避免靠反复调用绕过质量规则。需要重新采样全部页面时显式增加 --fresh
  • 断点按 API 地址、模型、Prompt 版本、采样参数及脚本代码哈希隔离;这些条件变化时自动视为新批次,不与旧结果混合。
  • V1 类型:answer_type 只能为 span;不生成计算、计数、多答案推理或跨页问题。
  • 风格:按来源选择固定 style_profile,只模仿数据集的问题类型,不把官方 Query、答案或页面文字放进 Prompt。
  • Query 类型:由 profile 固定为 field/retrieval/visual/table/chart 之一,模型不能自定义新值。

当前全部响应门控

API 响应必须满足:

  1. 只有一个 choice,且 finish_reason=stop
  2. reasoning_content 缺失、null 或空字符串;统计中的 reasoning_tokens 必须为 0 或缺失。
  3. content 不能为空,不能包含 <think> 或 Markdown 代码围栏,必须是单个严格 JSON 对象。
  4. 顶层只能包含 candidates;候选数量与请求值一致。数量不一致时记录失败,但可继续检查已返回候选。
  5. 每个候选只能包含 queryansweranswer_typeevidencequery_type
  6. answerevidence 都必须是非空字符串数组;不能出现未解码的 \uXXXX
  7. answer_type 必须为 spanquery_type 必须与当前 source profile 完全一致。

当前全部 Query 门控

  • Query 非空,并与页面语言一致。当前轻量检查要求中文页面含中文字符,英/法/德/西/意页面不得生成中文;它不是完整语言识别器。
  • V1.1 放宽:只拒绝强指代,如“本页、上图、下图、图 1、subfigure、this page、above/below、Figure N”。不再因“图中/表中/文档中/according to the chart/the figure”一律拒绝。
  • 中文 Query 出现重复的 4 字片段时拒绝,用于拦截退化表达。
  • Prompt 仍禁止计算、趋势推断和跨页问题;V1 只接受页面可直接读取的事实。

这些规则已从初版保守设置下调一档,以便观察 27B 生成趋势。若后续发现页面依赖 Query 伤害检索独立性,可再收紧 DISALLOWED_CONTEXT

当前证据与导出门控

  • strict_text:答案必须在 Markdown/HTML/OCR 真值中命中;证据允许规范化子串/前缀匹配。
  • 答案命中但证据略偏 → provisional,不再直接 fail
  • image_review:V1.1 标记为 provisional,进入趋势观察导出;不是严格证据验证后的 pass
  • 生成后由独立 终审检索样本.py 拦截官方/测试 Query 重合与批内重复。
  • 最终训练数据.jsonl 当前只是导出 pass + provisional 的趋势产物;正式 Release 需经过单独门禁,严格训练可先筛 pass
  • 训练导出字段固定为 sample_id/query/image_path/document_id/page_id

后续如何调整

  • 扩大页面数:修改 准备生成数据.py --samples-per-source
  • 调整多样性:修改 生成检索样本.py --temperature--candidates-per-page;每次只改一个变量并保留独立统计。
  • 调整吞吐:修改 --concurrency,继续记录成功率、P95、长度截断和 token。
  • 放宽上下文/标题规则:修改 生成检索样本.pyDISALLOWED_CONTEXT,并重新审计此前失败样本。
  • 放宽语言规则:用正式语言识别器替换当前中日韩字符启发式检查。
  • 放宽证据匹配:在 normalize_text()verify_candidate() 中加入表格行列对齐、数值标准化或可解释的模糊匹配;不能直接删除证据校验。
  • 自动处理 needs_review:增加独立 OCR 或第二个视觉模型复核,且复核模型不能读取生成答案作为提示。
  • 进入 V2:扩展 answer_type、增加 derivation,分别实现 arithmetic/count/multi-span 校验后再开放。

V1 输出

完整质检集 人工抽检结果.jsonl(Git 忽略的离线质检产物,不得并入训练导出):

{
  "sample_id": "shennong2_chart:page-003:01",
  "document_id": "doc-001",
  "page_id": "page-003",
  "image_path": "/data/doc-001/page-003.png",
  "image_hash": "sha256",
  "query": "agent 的 CPU 使用率是多少?",
  "answer": ["49.70%"],
  "answer_type": "span",
  "evidence": [
    {"page_id": "page-003", "text": "|CPU|45.84%|49.70%|"}
  ],
  "source": "shennong2_chart",
  "query_type": "图表字段",
  "language": "zh",
  "generation": {
    "model": "ms-g9248pbm",
    "prompt_version": "v1.0",
    "temperature": 0,
    "thinking": false,
    "finish_reason": "stop",
    "usage": {"reasoning_tokens": 0}
  },
  "verification": {
    "status": "pass",
    "reasons": []
  }
}

状态定义:

  • pass:格式正确,答案和证据已由页面文本自动验证,或已通过独立视觉/人工复核;
  • fail:格式、语言、证据或去重检查失败;
  • provisional:格式通过,但缺少独立文本真值,或答案命中而证据仅宽松匹配;可用于趋势观察,不等于严格验证通过。

当前趋势批次的 最终训练数据.jsonl 保留 pass + provisional 样本的 sample_idqueryimage_pathdocument_idpage_id。该文件名暂未改动,但其语义是趋势导出,不是正式训练 Release;训练导出禁止包含 answerevidence、页面真值、caption 或校验记录。正式严格训练可再筛为只保留 pass

V2:丰富问法与首轮难负例

在 V1 通过小规模试点后再加入:

  • comparisonarithmeticcountmulti-span 和每页多 Query。计算题要保存公式、输入数值和表格行;多答案必须保存真正的 JSON 数组及原始顺序。
  • general + specific 两阶段问题生成,只保留实体、条件和范围完整的 specific Query。
  • 多温度采样、语言检测、格式清洗、n-gram 去重和语义去重;控制长度,避免页面原文被直接复制为 Query。
  • 页面类型覆盖:知识点、操作步骤、前置条件、参数/接口、图表、方法、比较、故障排查。
  • 首轮难负例:以 ANN 检索 Top-K 页面作为候选;若候选页包含正样本答案证据,则视为潜在假负例并剔除。

不同答案类型的校验:

  • span:答案可在当前页定位;
  • multi-span:所有答案均存在且顺序一致;
  • arithmetic:从被引用的值重新计算;
  • count:保存可枚举的命中行、列或对象。

V3:跨页、判别与定位

  • 支持跨页/跨文档、多跳问题和跨语言 Query;evidence 数组可引用多个页面。
  • 难负例采用两阶段校验:ANN 召回候选后,由文本 reranker 或 VLM judge 判断相关性,剔除假负例。
  • 采用课程学习:早期使用 in-batch 负例,中期逐步注入验证过的 hard negatives。
  • 加入难度分层、平行文档和跨语言对齐;语言识别和格式清洗仍为必经步骤。
  • 加入 bbox/patch 证据定位。shennong2_chart[0, 0, 1000, 1000] 是占位 bbox,不能直接使用;必须由 OCR/布局或视觉模型重新定位。

开源数据带来的具体规则

  • colpali_train_set:同页可有多个不同 Query;不要混用其 test split;其列表答案是字符串表示,业务输出必须规范为 JSON 数组。
  • VisRAG Synthetic:保留少量关键词式检索,但不能让其主导;过滤空 Query、超长页面原文和不唯一的文件名 ID。
  • VisRAG In-domain:同时保留检索式与页面问答式问题,但移除强页面上下文指代。
  • vdr-multilingual-train:生成、清洗、语义过滤和难负例是顺序流程;同文档其他页面不天然是负样本。
  • tatdqa_train:答案类型决定验证规则;计算题必须保留公式和原始值。
  • tabfquad_train_set:页面 caption 不是答案证据;自然问法需要防止实体与条件被泛化。
  • shennong2_chart:生成输入为 PNG + 固定 Prompt;Markdown/HTML 仅作离线真值校验,caption 数值可能错误;自定义 train/eval split。

当前脚本

  1. scripts/数据合成/流程阶段/准备生成数据.py:从本地开源训练 case 生成安全页面清单、隔离的校验真值和 review-only 原始 case。
  2. scripts/数据合成/流程阶段/生成检索样本.py:执行严格非思考请求、JSON/Query 门控、证据校验和最小训练导出;使用逐页 _过程文件/生成断点.jsonl 断点续跑。
  3. scripts/数据合成/流程阶段/终审检索样本.py:生成后检查官方/测试 Query 重合与批内重复,并重建训练导出。
  4. scripts/数据合成/流程阶段/审计生成结果.py:检查测试集隔离、官方 Query 拦截、reasoning token 和训练导出字段。
  5. scripts/数据合成/一键运行生成流程.py:按“准备(可选)→断点生成→终审→审计”顺序执行完整流程。

已有 _过程文件/页面清单.jsonl 时直接续跑:

source activate.sh
python scripts/数据合成/一键运行生成流程.py --concurrency 32

首次准备并运行:

source activate.sh
python scripts/数据合成/一键运行生成流程.py --prepare --clean --samples-per-source 100 --concurrency 32

同一配置中断或请求失败后,重新执行原命令即可;生成脚本若仍有请求级失败,一键脚本仍会继续对已完成样本执行终审和审计,最后返回非零状态。--fresh 会忽略生成断点,--prepare --clean 会删除整个工作目录(包括断点),两者均不是普通续跑参数。

当前实现使用 v1.1-relaxed;趋势批次已跑通,但不是正式 Release,且 ViDoRe eval* 尚未接入完整 blocklist。数量、Case 和失败分布统一见 生成样本分析.md

由本地 docs/数据合成流程.md 自动构建 · 本地 Markdown 是内容源