phase2-normalize.md 6.9 KB

阶段二 · 归一化与标注 (主 Agent fan-out 2 个子 Agent)

主 Agent 把 workflow.json 发给两个子 Agent 并行处理, 都 in-place 加字段 (不要 Write 重写整个文件). 收集后合并的状态仍是同一份 workflow.json (子 Agent 改的是不同字段, 不冲突).

工作模式 (重要): Phase 1 已经 Write 了 workflow.json 骨架, Phase 2 子 Agent Read workflow.json + 给已有 step/IO 逐字段填值, 不写新文件。每个值都是逐 step 的语义判断(这个 step 的 effect 是哪片叶子、这个 IO 的 type 是什么)—— 不要写 normalize 脚本去机械批改, 那等于把判断退化成代码里的映射规则。落字段的两种方式:

  • 批量 (一个子任务几十个 effect/action/type/substance/form): 用 wf-patch.py —— 把决策写成 --patch _scratch/2a.json 清单 ([{"path":"p1.s2.effect","value":"主体生成"}, ...]) 一次应用。工具写入即校验 (effect 必须是叶子、type 必须叶子或已注册、substance/form 走词表校验), 非法整批不写, 你不碰 JSON 序列化。详见 tools.md §2
  • 零星单处: 直接 Edit。

这样:

  • 单一真理源, 没有 phase 间复制 → 零冗余
  • 你只出 path=value 决策, 工具保证不写坏 JSON + 字段合法 → 出错率低 (告别"脚本拼坏 JSON 再 fix" 的螺旋)
  • Resume 中断时看 workflow.json 里某 step 是否有 effect 字段就知道 Phase 2A 做没做
2A 子 Agent — 作用 / 动作 / 类型 归一化

context: workflow.json + spec §A.1 (作用树, 9 叶) + §A.2 (动作树, 5 L1 + 控制) + §A.3 (类型树, 4 大类) + 现有 type_registry — 全部小, 整体进 context。

任务:

  • 作用归一: 每步的"在哪个工艺位置"映射到 §A.1 叶子之一 (工艺规约 / 预准备 / 预处理 / 主体生成 / 装配 / 后期 / 配套伴生 / 检验 / 交付)。必须命中, 不命中 = 抽错回 1.3
  • 动作归一: 每步的 action 自然语言映射到 §A.2 树路径 (e.g. 提取/化学提取/反推生成/元素生成); 控制类 控制/并行 控制/遍历 自动转到 特性 字段
  • 类型归一 (对每个 input/output 的 type, 走以下 funnel 不要省步骤):

    1. 候选扩展: 基于 IO 的 name + value 描述, 显式列出 3-5 个候选 type 词 (不是只猜一个).

      • e.g. value=<图: 苏晚 25 岁年轻女性肖像, 卧室床上> → 候选: [参考图, 主角图, 人物肖像, 人物参考, 分镜图]
      • 候选要覆盖不同抽象层: 通用 (参考图) + 具体 (主角图) + 邻近 (分镜图), 让匹配有挑选余地
    2. 字典匹配 (按优先级, 命中即停):

      • a. 命中 §A.3 字典树叶子: 候选里有哪个直接命中叶子 (type.json $leaves)? 用它 (e.g. 候选含"参考图" → 命中)
      • b. extends 桥接: 都没命中, 但有候选语义贴近某叶子 → 选最近 leaf 做 extends. Edit workflow.json, 在该 procedure 顶层加 type_registry 段 (每个 procedure 独立 type_registry, 单 case 多工序时不冲突). 格式:

        // workflow.json (部分)
        {
        "procedures": [
        {
         "id": "p1-simple",
         "name": "...",
         "type_registry": {
           "主角图": {"extends": "参考图", "desc": "case-specific 主角肖像"}
         },
         "steps": [ ... ]
        }
        ]
        }
        

        不要只在 IO item 内 inline 写 extends 子字段 — renderer 走 procedure 顶层 type_registry 找, IO inline 字段它不读.

        1. 不必手工写 type_suggestions.md: Phase 3 跑 bin/lint-case.py --workflow 时, 会自动扫每个 procedure 的 type_registry 把所有 case-specific entry record 到 spec/taxonomy/type_suggestions.md (幂等, 同 (type_name, case_id) 只写一次). 你只要保证 procedure.type_registry 每个 entry 含 extends + desc 就行, suggestions 文件让工具维护.

        2. lint 自查 (轻量自检, 真正校验由 Phase 3 lint-case.py 跑):

        3. [ ] 每个 IO 的 type 要么命中 §A.3 叶子 (type.json $leaves), 要么所在 procedure 的 type_registry 段里有该 type 的 extends + desc 项

        4. [ ] 走 2b 的所有新 type 都在对应 procedure 的 type_registry 段出现

        5. [ ] 不允许 type 字段写"自由名"但所在 procedure 的 type_registry 缺对应 entry — 这种 silent gap 会让 Phase 3 渲染 HTML 时 drawer metadata 全丢, Phase 3 lint-case.py 会捕捉到

        输出: 写回 workflow.json (几十个字段批量用 wf-patch.py --patch, 见开头工作模式; type_registry 也能 --set p1.type_registry.X.extends=... 注册), 给每个 step 加 effect / action / feature / control 字段 + 给每个 IO 加 type 字段 (Phase 1.2 已有则归一, 没有则填). 不写新文件, 不写生成脚本.

        2B 子 Agent — 实质 / 形式 匹配

        context: workflow.json 中所有 input/output 的 name + value + 上下文 + 调 spec/tools/taxonomy-lookup.py tool. 实质·形式 JSON 词表本身不进 context, 完全通过 tool 查询。

        tool 接口: ```

spec/tools/taxonomy-lookup.py --dim {实质|形式} --list-l2 # 列二级路径 spec/tools/taxonomy-lookup.py --dim {实质|形式} --subtree # 返回子树 (叶子 + alias) spec/tools/taxonomy-lookup.py --dim {实质|形式} --match "" # 多 token 拆词聚合 (推荐用法) spec/tools/taxonomy-lookup.py --dim {实质|形式} --narrow "" # 层级下钻 (--match miss 兜底) spec/tools/taxonomy-lookup.py --dim {实质|形式} --validate # 校验路径存在 ```

任务 (对每个 input/output value):

  1. 从 value 抽 2-5 个 ≥2 字描述性 token, 一次调用 --match "tok1 tok2 tok3" (空格分隔, tool 自动拆词聚合)
    • 好例: value=<图: 苏晚 25 岁年轻女性肖像, 卧室床上>--match "年轻女性 卧室 床上 肖像"
    • 不要再一个个单 token 试错 — tool 内部已经做了拆词聚合 + coverage bonus
  2. --match 返 (无匹配) → 同 query 切 --narrow 走层级下钻 (按子树整体语义打分)
  3. 上面拿到 top 候选后, 用 --subtree <候选> 列叶子细节, 选最贴的
  4. --validate <chosen_leaf> 确认前再写回; 完全无法匹配 → unmatched
  5. 抽象容器/纯工具参数 → null (不标)

避坑: 不要把整段 value 描述塞进 --match (e.g. --match "苏晚 25 岁年轻女性, 卧室床上, 湿发素颜"), 标点会被当 token 一部分干扰拆词. 提炼 2-5 个干净的描述性词组就够.

输出: 写回 workflow.json (几十个 IO 批量用 wf-patch.py --patch, 见开头工作模式; substance/form 会自动走 taxonomy-lookup 校验, 设 null 传 __null__), 给每个 IO 加 substance: /xxx/yyy + form: /xxx/yyy (或 null). 不写新文件, 不写生成脚本.