Skip to content

合并算法

富化管道现在采用字段级合并架构:

  • src/pipeline/merge_resolver.py 中的 MergeResolver 负责结构化字段决策。
  • src/pipeline/adjudicator.py 中的 SpecialNotesAdjudicator 负责 Special Notes 语义整合。

分阶段富化流程先通过 MergeResolver.resolve() 完成结构化字段决策,再通过 SpecialNotesAdjudicator 完成语义化备注综合。

字段级权威矩阵

合并优先级由 src/pipeline/field_authority.yaml 配置驱动,而非全局固定策略顺序。

矩阵支持:

  • global:全局字段来源排序
  • template_overrides:按模板覆盖字段排序
  • field_aliases:字段别名与拼写修正

示例:

  • Width 可按模板优先 dimension_cardmain_table_seed
  • Material 可优先 text_rule 而非表格值
  • Special Notes 不走常规抢占,改由 adjudicator 处理

候选值收集

MergeResolver 会为每个 row_id + field 构建 FieldCandidate

  1. 主表种子候选main_table_seed
    主表中带 __row_id__ 的原值始终作为候选。
  2. 专业策略候选
    所有策略输出(GeminiEnrichedRowResult)均参与。

每个候选包含:

  • strategy
  • value
  • field_source
  • row_conf
  • 可选 field_claim_conf
  • stage_index
  • reasoning

胜出规则

对每个非备注字段,候选按以下顺序评分:

  1. 权威矩阵中的来源排名(rankings(template, field)
  2. 枚举合法性惩罚(合法优先)
  3. 更高 field_claim_conf
  4. 更高 row_conf
  5. 更小 stage_index(同分时早阶段优先)

这样可以在字段级获得确定性结果,避免“单策略全行统治”。

Source Type 计算字段

Source Type 在矩阵中配置为计算字段([computed]),不参与常规候选排名抢占。

在非备注字段胜出后,resolver 按胜出字段的 field_sources 计算 Source Type

  • 只要存在 FieldSource.IMAGE_CONTEXT -> Image
  • 否则若存在 FieldSource.MAIN_TABLE / FieldSource.AUXILIARY_TABLE / FieldSource.TEXT_CONTEXT -> Table
  • 否则 -> 空字符串

枚举优先合法值

若同一枚举字段同时存在合法与不合法候选,resolver 会优先选择合法候选。最终 Other: ... 强制兜底仍在富化末尾的枚举校验阶段执行。

Special Notes 语义裁决

Special Notes 采用语义裁决,而不是子串去重。在基准测试中,纯词面去重会合并或丢失有效条款,导致备注召回下降。这符合 Cartex 的 guardrail 设计:开放文本字段采用软约束与语义综合,硬约束仅用于可边界验证的字段(如枚举;详见pipeline 运行技术规格)。

流程:

  1. resolver 从 main_table_seed 和所有策略收集 note observations。
  2. SpecialNotesAdjudicatorrow_id 聚合。
  3. 单 observation 行直接输出一个 bullet。
  4. 多 observation 行按批次调用 FAST 模型综合(GeminiSpecialNotesSynthesisResult)。
  5. 模型失败时回退到确定性去重 bullet 逻辑。

最终 EnrichedRow.data["Special Notes"] 为按换行分隔的 bullet 文本。

置信度、来源与推理

对每个结果行:

  • confidence:取所有胜出非备注字段的最小 row_conf(若无更低值则为 1.0)
  • field_sources:来自胜出候选的来源元数据
  • reasoning:胜出候选推理去重拼接,并附加 adjudicator 合成说明

行恢复

权威 row_id 集合先由 Enricher 在调用 resolver 前准备:

  • 对纯索引回退 ID({table_id}_row_{n})的表,不纳入权威集合,避免误恢复

resolver 再基于该集合执行“主行不丢失”恢复:

  • 若某权威 row_id 无输出,则补回空 EnrichedRowconfidence=0.0

合并流程图

flowchart TB
    subgraph candidates["候选收集"]
        MAIN["main_table_seed 候选"]
        S1["所有已完成阶段的专业输出"]
        MAIN --> CAND["按 row_id + field 聚合候选池"]
        S1 --> CAND
    end

    subgraph resolve["MergeResolver"]
        CAND --> PICK["逐字段评分<br/>(排名、合法性、置信度、阶段)"]
        PICK --> ROWS["list[EnrichedRow](未最终写入备注)"]
    end

    subgraph notes["SpecialNotesAdjudicator"]
        CAND --> OBS["按 row_id 聚合 note observations"]
        OBS --> LLM["批量语义综合<br/>(FAST model)"]
        LLM --> APPLY["bullet 输出 + 失败回退"]
    end

    ROWS --> APPLY --> FINAL["最终 EnrichedRow 列表"]