elksmmx 1 день назад
Родитель
Сommit
4124821190

+ 7 - 7
examples/process_pipeline/prompts/apply_to_grounding.prompt

@@ -1,20 +1,20 @@
-你是内容树映射助手。现在是 Stage 2:把 apply_to_draft 映射为精确 apply_to,并为每个 fragment/query 生成一个 suggest_apply_to。
+你是内容树映射助手。现在是 Stage 2:把 apply_to_draft 映射为精确 apply_to,并为每个 capability/query 生成一个 suggest_apply_to。
 
 
 # 绝对规则
 # 绝对规则
 
 
 - 不要调用任何工具,不要查树;只能使用本 prompt 中给出的完整内容树。
 - 不要调用任何工具,不要查树;只能使用本 prompt 中给出的完整内容树。
-- 只处理:fragments 数组中的每一条 fragment
+- 只处理:capability 数组中的每一条 capability
 - **只输出 apply_to 和 suggest_apply_to 字段**,不要回显 inputs、outputs、action、body、effects、stage、tools、criterion、unstructured_what 等字段。
 - **只输出 apply_to 和 suggest_apply_to 字段**,不要回显 inputs、outputs、action、body、effects、stage、tools、criterion、unstructured_what 等字段。
 - apply_to.实质 只能选择 source_type=实质 的节点;apply_to.形式 只能选择 source_type=形式 的节点。
 - apply_to.实质 只能选择 source_type=实质 的节点;apply_to.形式 只能选择 source_type=形式 的节点。
 - category_id 和 category_path 必须逐字来自内容树里的 id/path。
 - category_id 和 category_path 必须逐字来自内容树里的 id/path。
 - element 只有在该节点 elements 中逐字存在时才能填写;否则省略 element 或填 null。
 - element 只有在该节点 elements 中逐字存在时才能填写;否则省略 element 或填 null。
 - 每侧 1-3 项即可。优先选择最贴近 apply_to_draft 的节点;不确定时选较粗分类,不要编造。
 - 每侧 1-3 项即可。优先选择最贴近 apply_to_draft 的节点;不确定时选较粗分类,不要编造。
 - rationale 用一句话说明 draft 短语为何落在该节点。
 - rationale 用一句话说明 draft 短语为何落在该节点。
-- **每个 fragment/query 必须包含且只能包含一个 suggest_apply_to**,不得在 apply_to 条目内输出 suggest_apply_to。
+- **每个 capability/query 必须包含且只能包含一个 suggest_apply_to**,不得在 apply_to 条目内输出 suggest_apply_to。
 
 
 # suggest_apply_to 规则
 # suggest_apply_to 规则
 
 
-每个 fragment/query 必须输出一个 suggest_apply_to,表示该 query 的整体描述"理想上应该挂在哪个路径"(即使树上不存在):
+每个 capability/query 必须输出一个 suggest_apply_to,表示该 query 的整体描述"理想上应该挂在哪个路径"(即使树上不存在):
 - 先完成 apply_to.实质 和 apply_to.形式 的真实树节点映射。
 - 先完成 apply_to.实质 和 apply_to.形式 的真实树节点映射。
 - 综合 apply_to_draft 的语义,以及你选出的真实 category_path,判断这个 query 最理想的单一路径。
 - 综合 apply_to_draft 的语义,以及你选出的真实 category_path,判断这个 query 最理想的单一路径。
 - 优先以最能代表 query 核心语义的真实 category_path 为基准;如果实质和形式都存在,通常优先以实质路径为基准,形式路径只作为命名和细化参考。
 - 优先以最能代表 query 核心语义的真实 category_path 为基准;如果实质和形式都存在,通常优先以实质路径为基准,形式路径只作为命名和细化参考。
@@ -41,10 +41,10 @@
 
 
 # 输出格式
 # 输出格式
 
 
-输出 `{ "fragments": [ { "fragment_id": "f_s1_0", "apply_to": {...}, "suggest_apply_to": "..." } ] }`
+输出 `{ "capability": [ { "capability_id": "c_s1_0", "apply_to": {...}, "suggest_apply_to": "..." } ] }`
 
 
-- fragments 数组长度和输入 fragments 保持一致。
-- 每个输出项必须带原输入的 fragment_id,逐字照抄。
+- capability 数组长度和输入 capability 保持一致。
+- 每个输出项必须带原输入的 capability_id,逐字照抄。
 
 
 每个 apply_to 条目格式:`{ "category_id": 123, "category_path": "...", "element": null, "rationale": "..." }`
 每个 apply_to 条目格式:`{ "category_id": 123, "category_path": "...", "element": null, "rationale": "..." }`
 
 

+ 12 - 7
examples/process_pipeline/prompts/apply_to_grounding_capability.schema.json

@@ -2,27 +2,32 @@
   "$schema": "http://json-schema.org/draft-07/schema#",
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "apply_to_grounding_capability_output",
   "title": "apply_to_grounding_capability_output",
   "type": "object",
   "type": "object",
-  "required": ["capabilities"],
+  "required": ["capability"],
+  "additionalProperties": false,
   "properties": {
   "properties": {
-    "capabilities": {
+    "capability": {
       "type": "array",
       "type": "array",
       "items": {
       "items": {
         "type": "object",
         "type": "object",
-        "required": ["apply_to"],
+        "required": ["capability_id", "apply_to", "suggest_apply_to"],
+        "additionalProperties": false,
         "properties": {
         "properties": {
+          "capability_id": { "type": "string", "pattern": "^c_(s[0-9]+_[0-9]+|standalone_[0-9]+)$" },
+          "suggest_apply_to": { "type": "string", "minLength": 1 },
           "apply_to": {
           "apply_to": {
             "type": "object",
             "type": "object",
             "required": ["实质", "形式"],
             "required": ["实质", "形式"],
+            "additionalProperties": false,
             "properties": {
             "properties": {
               "实质": {
               "实质": {
                 "type": "array",
                 "type": "array",
                 "items": {
                 "items": {
                   "type": "object",
                   "type": "object",
-                  "required": ["category_id", "category_path", "ideal_path", "rationale"],
+                  "required": ["category_id", "category_path", "rationale"],
+                  "additionalProperties": false,
                   "properties": {
                   "properties": {
                     "category_id": { "type": "integer" },
                     "category_id": { "type": "integer" },
                     "category_path": { "type": "string", "minLength": 1 },
                     "category_path": { "type": "string", "minLength": 1 },
-                    "ideal_path": { "type": "string", "minLength": 1 },
                     "element": { "type": ["string", "null"] },
                     "element": { "type": ["string", "null"] },
                     "rationale": { "type": "string", "minLength": 1 }
                     "rationale": { "type": "string", "minLength": 1 }
                   }
                   }
@@ -32,11 +37,11 @@
                 "type": "array",
                 "type": "array",
                 "items": {
                 "items": {
                   "type": "object",
                   "type": "object",
-                  "required": ["category_id", "category_path", "ideal_path", "rationale"],
+                  "required": ["category_id", "category_path", "rationale"],
+                  "additionalProperties": false,
                   "properties": {
                   "properties": {
                     "category_id": { "type": "integer" },
                     "category_id": { "type": "integer" },
                     "category_path": { "type": "string", "minLength": 1 },
                     "category_path": { "type": "string", "minLength": 1 },
-                    "ideal_path": { "type": "string", "minLength": 1 },
                     "element": { "type": ["string", "null"] },
                     "element": { "type": ["string", "null"] },
                     "rationale": { "type": "string", "minLength": 1 }
                     "rationale": { "type": "string", "minLength": 1 }
                   }
                   }

+ 5 - 5
examples/process_pipeline/prompts/apply_to_grounding_fragment.schema.json

@@ -1,18 +1,18 @@
 {
 {
   "$schema": "http://json-schema.org/draft-07/schema#",
   "$schema": "http://json-schema.org/draft-07/schema#",
-  "title": "apply_to_grounding_fragment_output",
+  "title": "apply_to_grounding_capability_output",
   "type": "object",
   "type": "object",
-  "required": ["fragments"],
+  "required": ["capability"],
   "additionalProperties": false,
   "additionalProperties": false,
   "properties": {
   "properties": {
-    "fragments": {
+    "capability": {
       "type": "array",
       "type": "array",
       "items": {
       "items": {
         "type": "object",
         "type": "object",
-        "required": ["fragment_id", "apply_to", "suggest_apply_to"],
+        "required": ["capability_id", "apply_to", "suggest_apply_to"],
         "additionalProperties": false,
         "additionalProperties": false,
         "properties": {
         "properties": {
-          "fragment_id": { "type": "string", "minLength": 1 },
+          "capability_id": { "type": "string", "pattern": "^c_(s[0-9]+_[0-9]+|standalone_[0-9]+)$" },
           "suggest_apply_to": { "type": "string", "minLength": 1 },
           "suggest_apply_to": { "type": "string", "minLength": 1 },
           "apply_to": {
           "apply_to": {
             "type": "object",
             "type": "object",

+ 80 - 114
examples/process_pipeline/prompts/extract_workflow.prompt

@@ -1,136 +1,103 @@
----
-temperature: 0.1
----
-
-$system$
-
-你是 AI 图片制作工序沉淀助手。本阶段是 Stage 1:只抽取语义,不做内容树映射。
-
+你是 AI 图片制作工序沉淀助手。
 # 任务概述
 # 任务概述
-
-从帖子中同时完成两件事:
-1. 识别 workflow steps(按"提交动作"边界划分)
-2. 对每个 step,识别其中的 1+ 原子操作,每个原子操作输出为一个 fragment(带完整 capability 字段)
-
-# 工序提取规则(workflow steps)
-
+从帖子中同时完成两件事:
+1. 识别 workflow steps(按"提交动作"边界划分)
+2. 对每个 step,识别其中的 1+ 原子操作,每个原子操作输出为一个 capability
+# 工序提取规则(workflow steps)
 - 将帖子内容总结为 AI 图片制作工序。
 - 将帖子内容总结为 AI 图片制作工序。
-- 步骤粒度是"做了什么"而非"怎么做"。
-- 以"触发生成 / 处理的动作"为步骤边界,同一次提交前的所有配置(模型选择、参数调整、描述词输入等)合并为一步。
-- 若本质上只有一步,也输出一步,不要返回 workflow=null。
+- 步骤粒度是"做了什么",而非"怎么做"。
+- 以"触发生成 / 处理的动作"为步骤边界,同一次提交前的所有配置(模型选择、参数调整、描述词输入等)合并为一步。
+- 若本质上只有一步,也输出一步,不要返回 workflow=null。
 - 可选步骤也应提取。
 - 可选步骤也应提取。
-- step 是薄壳:只装结构性元数据(step_id、order、phase、relation、body),不含 capability 字段。
-- 若原帖纯营销、信息密度太低或完全没怎么做,则 skip=true。
-- 不要调用任何工具,不要查树。
-
+- step 是薄壳:只装结构性元数据(step_id、order、phase),不含 capability 字段。
+- 若原帖纯营销、信息密度太低或完全没怎么做,则 skip=true。
 # step 字段
 # step 字段
-
-每个 step 包含:
-
-- step_id:格式为 "s{order}",如 "s1"、"s2"
-- order:步骤序号,整数
-- phase:该步骤所属阶段,取值为「非制作」/「预处理」/「生成」/「编辑」之一
-- relation:该步骤输出的去向,格式同 inputs/outputs 的 relation 字段,如 "[去向.最终成品]"、"[去向.s2I]"
-- body:具体做法,包含 prompt 写法、参数配置、操作细节等;从帖子原文中提取,未提及则为 null
-
-# fragment 提取规则
-
-- 每个 step 的 body 中识别 1+ 原子操作,每个原子操作输出为一个 fragment。
-- 同一 step 内的不同方案(如"用 MJ 生成 / 用 SD 生成")互为 alternative:
-  - 每种方案单独输出一个 fragment,各自填写完整字段(inputs、action、outputs、tools 等均可不同)
-  - 在 is_alternative_to 中互相标注对方的 fragment_id
-- 帖子中没有 workflow 上下文的能力提及 → fragment,workflow_step_ref = null。
-- 不跨 step 合并 fragments。
-- fragment_id 格式:步内原子操作用 "f_{step_id}_{i}"(如 "f_s1_0"、"f_s1_1"),standalone 用 "f_standalone_{i}"。
-
-# fragment 字段
-
-每个 fragment 包含完整 capability 字段:
-
-- fragment_id:字符串,见上方规则
-- action:{ main_action, mechanism },见下方 action 字段规则
-- inputs / outputs:结构化接口,见下方规则
-- body:该原子操作在原帖中的描述(可能是 step body 的子片段);未提及则为 null
-- effects:该原子操作产生的可观测效果,数组,每项为结构体(见下方 effects 字段规则)
-- control_target:该操作控制的对象,字符串数组,如 ["人物姿态", "背景风格"];未提及则为 []
-- artifact_type:该操作产出的工件类型,如 "正向提示词"、"蒙版"、"参考图";未提及则为 null
-- tools:使用的工具或平台,数组;未提及则为 []
-- apply_to_draft:{ 实质: [...], 形式: [...] },只写自然语言短语
-- workflow_step_ref:{ workflow_id, step_id } 或 null(standalone fragment)
-- is_alternative_to:同一 step 内互为可选方案的其他 fragment_id 数组,无则为 []
-
+每个 step 包含:
+- step_id:格式为 "s{order}",如 "s1"、"s2"
+- order:步骤序号,整数
+- phase:该步骤所属阶段,取值为「非制作」/「预处理」/「生成」/「编辑」之一
+# capability 提取规则
+- 每个 step 中识别 1+ 原子操作,每个原子操作输出为一个 capability。
+- 同一 step 内的不同方案(如"用 MJ 生成 / 用 SD 生成")互为 alternative:
+  - 每种方案单独输出一个 capability,各自填写完整字段(inputs、action、outputs、tools 等均可不同)
+  - 在 is_alternative_to 中互相标注对方的 capability_id
+- 帖子中没有 workflow 上下文的能力提及 → capability.workflow_step_ref = null。
+- 不跨 step 合并 capability。
+- capability_id 格式:步内原子操作用 "c_{step_id}_{i}"(如 "c_s1_0"、"c_s1_1"),standalone 用 "c_standalone_{i}"。
+# capability 字段
+- capability_id:字符串,见上方规则
+- action:{ description, reasoning },见下方 action 字段规则
+- inputs / outputs:结构化接口,见下方规则
+- body:该原子操作在原帖中的描述(可能是对应 step 内容里的子片段);未提及则为 null
+- effects:该原子操作产生的可观测效果,数组,每项为结构体(见下方 effects 字段规则)
+- control_target:该操作控制的对象,字符串数组,如 ["人物姿态", "背景风格"];未提及则为 []
+- artifact_type:该操作产出的工件类型,如 "正向提示词"、"蒙版"、"参考图";未提及则为 null
+- tools:使用的工具或平台,数组;未提及则为 []
+- apply_to_draft:{ 实质: [...], 形式: [...] },只写自然语言短语
+- workflow_step_ref:{ workflow_id, step_id } 或 null(standalone capability)
+- is_alternative_to:同一 step 内互为可选方案的其他 capability_id 数组,无则为 []
 # action 字段
 # action 字段
-
-action 写成对象:
-
+action 写成对象:
 ```json
 ```json
-{ "main_action": "编辑", "mechanism": "局部重绘" }
+{
+  "description": "修复",
+  "reasoning": "输入包含待处理图片,输出为局部瑕疵被周围信息填补后的图片,客观信息变化是修复"
+}
 ```
 ```
-
-- main_action 从以下选择:生成 / 编辑 / 提取 / 组织 / 筛选
-- mechanism 是 main_action 的细分:
-  - 生成:直接生成 / 参考引导 / 一致性保持 / 动画化 / 多模态合成 / 多候选生成
-  - 编辑:局部重绘 / 风格迁移 / 颜色调整 / 蒙板重绘 / 拼接组合 / 裁切扩展
-  - 提取:提示词反推 / 关键帧提取 / 蒙板提取 / 知识库检索 / 特征向量化
-  - 组织:分类入库 / 模板化 / 标签化 / 变量抽象 / 结构抽象
-  - 筛选:抽卡选优 / 评分排序top k / 人工挑选 / 阈值过滤
-
+- description:动作名称,必须包含动词,只写输入到输出之间客观发生的信息变化
+- reasoning:一句话说明从输入、输出和信息变化的哪个维度判断出该 action
+
+定义:动作是:输入到输出之间,客观发生的信息变化;需要包含动词
+距离来说:
+一个人脸上有一颗痣,你用 AI 把它去掉。
+以意图描述:美化、精修
+以场景描述:祛痘、磨皮
+以信息变化描述:修复(用周围信息填补某个区域)
+判断标准:
+- 去掉主语和宾语后,这个词仍然能独立表达一种变化 → 是动作
+- 混入了操作对象 → 不是动作,如"换脸"应写为"替换"
+- 混入了意图或场景 → 不是动作,如"修复划痕"应写为"修复"
+举例(仅供参考,不限于此):
+生成、替换、融合、提取、局部修复、风格迁移
 # inputs / outputs
 # inputs / outputs
-
 ```json
 ```json
 {
 {
   "modality": "文本",
   "modality": "文本",
-  "description": "该项在当前步骤中实际起到的作用,用简短名词短语表达",
+  "description": "该项在当前步骤中实际起到的作用,用简短名词短语表达",
   "relation": "来源或去向"
   "relation": "来源或去向"
 }
 }
 ```
 ```
-
-- modality 是数据形态:文本 / 图片 / 视频 / 音频 / 特征点 / 参数 / 模型 / 向量
+- modality 是数据形态:文本 / 图片 / 视频 / 音频 / 特征点 / 参数 / 模型 / 向量
 - 同一次提交给模型的所有文字描述统一合并为一个输入项
 - 同一次提交给模型的所有文字描述统一合并为一个输入项
-- relation 格式:[来源.1O]、[去向.2I]、[来源.原始输入]、[去向.最终成品]
-
+- relation 格式:[来源.1O]、[去向.2I]、[来源.原始输入]、[去向.最终成品]
 # effects 字段
 # effects 字段
-
-每个 effect 写成结构体:
-
+每个 effect 写成结构体:
 ```json
 ```json
 {
 {
   "statement": "实现XXX",
   "statement": "实现XXX",
-  "criteria": "判断该效果是否达成的具体标准一句话描述",
+  "criteria": "判断该效果是否达成的具体标准,一句话描述",
   "judge_method": "vlm",
   "judge_method": "vlm",
   "negative_examples": ["反例描述1"]
   "negative_examples": ["反例描述1"]
 }
 }
 ```
 ```
-
-- statement:以"实现"开头,描述该操作产生的可观测效果
-- criteria:判断标准,具体、可操作,描述"什么情况下算达成"
-- judge_method:判断方式,从以下选择:
-  - `llm`:纯文本推理可判断
-  - `vlm`:需要看图才能判断
-  - `rule`:可用规则/代码判断(如分辨率、文件大小)
-  - `human`:需要人工主观判断
-- negative_examples:反例列表,描述"什么情况下算没达成";无明显反例则为 []
-
-每个 fragment 必须有 effects,至少一项。
-
+- statement:以"实现"开头,描述该操作产生的可观测效果
+- criteria:判断标准,具体、可操作,描述"什么情况下算达成"
+- judge_method:判断方式,从以下选择:
+  - `llm`:纯文本推理可判断
+  - `vlm`:需要看图才能判断
+  - `rule`:可用规则/代码判断(如分辨率、文件大小)
+  - `human`:需要人工主观判断
+- negative_examples:反例列表,描述"什么情况下算没达成";无明显反例则为 []
+每个 capability 必须有 effects,至少一项。
 # apply_to_draft 字段
 # apply_to_draft 字段
-
-- 本阶段严禁生成 apply_to,只生成 apply_to_draft。
-- apply_to_draft.实质 写内容关于什么:主体、题材、场景、情境等。
-- apply_to_draft.形式 写内容怎么呈现:镜头、构图、光线、叙事、排版、质感等。
-
+- apply_to_draft.实质 写内容关于什么:主体、题材、场景、情境等。
+- apply_to_draft.形式 写内容怎么呈现:镜头、构图、光线、叙事、排版、质感等。
 {interface_vocab}
 {interface_vocab}
-
 $user$
 $user$
-
-# 输入:原帖
-
+# 输入:原帖
 ---
 ---
-
 ## %context%
 ## %context%
-
 # 输出 JSON 形状
 # 输出 JSON 形状
-
 ```json
 ```json
 {
 {
   "skip": false,
   "skip": false,
@@ -141,16 +108,17 @@ $user$
       {
       {
         "step_id": "s1",
         "step_id": "s1",
         "order": 1,
         "order": 1,
-        "phase": "生成",
-        "relation": "[去向.最终成品]",
-        "body": "string | null"
+        "phase": "生成"
       }
       }
     ]
     ]
   },
   },
-  "fragments": [
+  "capability": [
     {
     {
-      "fragment_id": "f_s1_0",
-      "action": { "main_action": "生成", "mechanism": "直接生成" },
+      "capability_id": "c_s1_0",
+      "action": {
+         "description":"直接生成",
+         "reasoning": "从什么维度的变化,得出了action 的结论"
+       },
       "inputs": [
       "inputs": [
         {
         {
           "modality": "文本",
           "modality": "文本",
@@ -184,10 +152,8 @@ $user$
   ]
   ]
 }
 }
 ```
 ```
-
 # 输出硬规则
 # 输出硬规则
-
-- 只输出最终严格 JSON,不要 Markdown 代码块。
+- 只输出最终严格 JSON,不要 Markdown 代码块。
 - 不要任何前言、解释、标题。
 - 不要任何前言、解释、标题。
-- 字符串值内禁止出现 ASCII 双引号需要引号请用中文书名号。
+- 字符串值内禁止出现 ASCII 双引号;需要引号请用中文书名号。
 - effects 的每项都必须以"实现"开头。
 - effects 的每项都必须以"实现"开头。

+ 213 - 47
examples/process_pipeline/prompts/extract_workflow.schema.json

@@ -2,33 +2,64 @@
   "$schema": "http://json-schema.org/draft-07/schema#",
   "$schema": "http://json-schema.org/draft-07/schema#",
   "title": "extract_workflow_output_v6",
   "title": "extract_workflow_output_v6",
   "type": "object",
   "type": "object",
-  "required": ["skip", "skip_reason", "workflow", "fragments"],
+  "required": [
+    "skip",
+    "skip_reason",
+    "workflow",
+    "capability"
+  ],
   "properties": {
   "properties": {
-    "skip": { "type": "boolean" },
-    "skip_reason": { "type": "string" },
+    "skip": {
+      "type": "boolean"
+    },
+    "skip_reason": {
+      "type": "string"
+    },
     "workflow": {
     "workflow": {
       "anyOf": [
       "anyOf": [
-        { "type": "null" },
+        {
+          "type": "null"
+        },
         {
         {
           "type": "object",
           "type": "object",
-          "required": ["steps"],
+          "required": [
+            "steps"
+          ],
           "properties": {
           "properties": {
-            "workflow_id": { "type": ["string", "null"] },
+            "workflow_id": {
+              "type": [
+                "string",
+                "null"
+              ]
+            },
             "steps": {
             "steps": {
               "type": "array",
               "type": "array",
               "minItems": 1,
               "minItems": 1,
               "items": {
               "items": {
                 "type": "object",
                 "type": "object",
-                "required": ["step_id", "order", "phase", "relation", "body"],
+                "required": [
+                  "step_id",
+                  "order",
+                  "phase"
+                ],
                 "properties": {
                 "properties": {
-                  "step_id": { "type": "string", "pattern": "^s[0-9]+$" },
-                  "order": { "type": "integer", "minimum": 1 },
-                  "phase": {
+                  "step_id": {
                     "type": "string",
                     "type": "string",
-                    "enum": ["非制作", "预处理", "生成", "编辑"]
+                    "pattern": "^s[0-9]+$"
                   },
                   },
-                  "relation": { "type": "string", "minLength": 1 },
-                  "body": { "type": ["string", "null"] }
+                  "order": {
+                    "type": "integer",
+                    "minimum": 1
+                  },
+                  "phase": {
+                    "type": "string",
+                    "enum": [
+                      "非制作",
+                      "预处理",
+                      "生成",
+                      "编辑"
+                    ]
+                  }
                 }
                 }
               }
               }
             }
             }
@@ -36,35 +67,77 @@
         }
         }
       ]
       ]
     },
     },
-    "fragments": {
+    "capability": {
       "type": "array",
       "type": "array",
       "items": {
       "items": {
         "type": "object",
         "type": "object",
         "required": [
         "required": [
-          "fragment_id", "action", "inputs", "outputs",
-          "body", "effects", "control_target", "artifact_type",
-          "tools", "apply_to_draft",
-          "workflow_step_ref", "is_alternative_to"
+          "capability_id",
+          "action",
+          "inputs",
+          "outputs",
+          "body",
+          "effects",
+          "control_target",
+          "artifact_type",
+          "tools",
+          "apply_to_draft",
+          "workflow_step_ref",
+          "is_alternative_to"
         ],
         ],
         "properties": {
         "properties": {
-          "fragment_id": { "type": "string", "minLength": 1 },
+          "capability_id": {
+            "type": "string",
+            "pattern": "^c_(s[0-9]+_[0-9]+|standalone_[0-9]+)$"
+          },
           "action": {
           "action": {
             "type": "object",
             "type": "object",
-            "required": ["main_action", "mechanism"],
+            "required": [
+              "description",
+              "reasoning"
+            ],
             "properties": {
             "properties": {
-              "main_action": { "type": "string", "minLength": 1 },
-              "mechanism": { "type": "string", "minLength": 1 }
+              "description": {
+                "type": "string",
+                "minLength": 1
+              },
+              "reasoning": {
+                "type": "string",
+                "minLength": 1
+              }
             }
             }
           },
           },
           "inputs": {
           "inputs": {
             "type": "array",
             "type": "array",
             "items": {
             "items": {
               "type": "object",
               "type": "object",
-              "required": ["modality", "description", "relation"],
+              "required": [
+                "modality",
+                "description",
+                "relation"
+              ],
               "properties": {
               "properties": {
-                "modality": { "type": "string", "minLength": 1 },
-                "description": { "type": "string", "minLength": 1 },
-                "relation": { "type": "string", "minLength": 1 }
+                "modality": {
+                  "type": "string",
+                  "enum": [
+                    "文本",
+                    "图片",
+                    "视频",
+                    "音频",
+                    "特征点",
+                    "参数",
+                    "模型",
+                    "向量"
+                  ]
+                },
+                "description": {
+                  "type": "string",
+                  "minLength": 1
+                },
+                "relation": {
+                  "type": "string",
+                  "pattern": "^\\[(来源|去向)\\..+\\]$"
+                }
               }
               }
             }
             }
           },
           },
@@ -72,61 +145,154 @@
             "type": "array",
             "type": "array",
             "items": {
             "items": {
               "type": "object",
               "type": "object",
-              "required": ["modality", "description", "relation"],
+              "required": [
+                "modality",
+                "description",
+                "relation"
+              ],
               "properties": {
               "properties": {
-                "modality": { "type": "string", "minLength": 1 },
-                "description": { "type": "string", "minLength": 1 },
-                "relation": { "type": "string", "minLength": 1 }
+                "modality": {
+                  "type": "string",
+                  "enum": [
+                    "文本",
+                    "图片",
+                    "视频",
+                    "音频",
+                    "特征点",
+                    "参数",
+                    "模型",
+                    "向量"
+                  ]
+                },
+                "description": {
+                  "type": "string",
+                  "minLength": 1
+                },
+                "relation": {
+                  "type": "string",
+                  "pattern": "^\\[(来源|去向)\\..+\\]$"
+                }
               }
               }
             }
             }
           },
           },
-          "body": { "type": ["string", "null"] },
+          "body": {
+            "type": [
+              "string",
+              "null"
+            ]
+          },
           "effects": {
           "effects": {
             "type": "array",
             "type": "array",
+            "minItems": 1,
             "items": {
             "items": {
               "type": "object",
               "type": "object",
-              "required": ["statement", "criteria", "judge_method", "negative_examples"],
+              "required": [
+                "statement",
+                "criteria",
+                "judge_method",
+                "negative_examples"
+              ],
               "properties": {
               "properties": {
-                "statement": { "type": "string", "pattern": "^实现" },
-                "criteria": { "type": "string", "minLength": 1 },
-                "judge_method": { "type": "string", "enum": ["llm", "vlm", "rule", "human"] },
-                "negative_examples": { "type": "array", "items": { "type": "string", "minLength": 1 }, "default": [] }
+                "statement": {
+                  "type": "string",
+                  "pattern": "^实现"
+                },
+                "criteria": {
+                  "type": "string",
+                  "minLength": 1
+                },
+                "judge_method": {
+                  "type": "string",
+                  "enum": [
+                    "llm",
+                    "vlm",
+                    "rule",
+                    "human"
+                  ]
+                },
+                "negative_examples": {
+                  "type": "array",
+                  "items": {
+                    "type": "string",
+                    "minLength": 1
+                  },
+                  "default": []
+                }
               }
               }
             }
             }
           },
           },
           "control_target": {
           "control_target": {
             "type": "array",
             "type": "array",
-            "items": { "type": "string", "minLength": 1 }
+            "items": {
+              "type": "string",
+              "minLength": 1
+            }
+          },
+          "artifact_type": {
+            "type": [
+              "string",
+              "null"
+            ]
           },
           },
-          "artifact_type": { "type": ["string", "null"] },
           "tools": {
           "tools": {
             "type": "array",
             "type": "array",
-            "items": { "type": "string" }
+            "items": {
+              "type": "string"
+            }
           },
           },
           "apply_to_draft": {
           "apply_to_draft": {
             "type": "object",
             "type": "object",
-            "required": ["实质", "形式"],
+            "required": [
+              "实质",
+              "形式"
+            ],
             "properties": {
             "properties": {
-              "实质": { "type": "array", "items": { "type": "string" } },
-              "形式": { "type": "array", "items": { "type": "string" } }
+              "实质": {
+                "type": "array",
+                "items": {
+                  "type": "string"
+                }
+              },
+              "形式": {
+                "type": "array",
+                "items": {
+                  "type": "string"
+                }
+              }
             }
             }
           },
           },
           "workflow_step_ref": {
           "workflow_step_ref": {
             "anyOf": [
             "anyOf": [
-              { "type": "null" },
+              {
+                "type": "null"
+              },
               {
               {
                 "type": "object",
                 "type": "object",
-                "required": ["step_id"],
+                "required": [
+                  "workflow_id",
+                  "step_id"
+                ],
                 "properties": {
                 "properties": {
-                  "workflow_id": { "type": "string" },
-                  "step_id": { "type": "string", "pattern": "^s[0-9]+$" }
+                  "workflow_id": {
+                    "type": [
+                      "string",
+                      "null"
+                    ]
+                  },
+                  "step_id": {
+                    "type": "string",
+                    "pattern": "^s[0-9]+$"
+                  }
                 }
                 }
               }
               }
             ]
             ]
           },
           },
           "is_alternative_to": {
           "is_alternative_to": {
             "type": "array",
             "type": "array",
-            "items": { "type": "string" }
+            "items": {
+              "type": "string",
+              "pattern": "^c_(s[0-9]+_[0-9]+|standalone_[0-9]+)$"
+            }
           }
           }
         }
         }
       }
       }

+ 6 - 68
examples/process_pipeline/run_pipeline.py

@@ -899,59 +899,19 @@ async def main():
                 print(f"   ✓ case.json + workflow: success={result['success']}, failed={result['failed']}")
                 print(f"   ✓ case.json + workflow: success={result['success']}, failed={result['failed']}")
 
 
             elif step == "capability-extract":
             elif step == "capability-extract":
-                # Phase 1.6b: 提取 capabilities 到 case.json
+                # Phase 1.6b 已废弃:capability 现在由 workflow-extract 一并写入 case.json
                 case_file = output_dir / "case.json"
                 case_file = output_dir / "case.json"
                 if not case_file.exists():
                 if not case_file.exists():
                     print(f"   ❌ case.json not found. Run --only-step generate-case first.")
                     print(f"   ❌ case.json not found. Run --only-step generate-case first.")
                     sys.exit(1)
                     sys.exit(1)
 
 
-                # 如果指定了 --case-index,先过滤 case.json
-                if args.case_index is not None:
-                    with open(case_file, "r", encoding="utf-8") as f:
-                        case_data = json.load(f)
-                    original_cases = case_data.get("cases", [])
-                    target_case = next((c for c in original_cases if c.get("index") == args.case_index), None)
-                    if not target_case:
-                        print(f"   ❌ Case with index {args.case_index} not found in case.json")
-                        sys.exit(1)
-                    case_data["cases"] = [target_case]
-                    temp_case_file = output_dir / f"case_temp_{args.case_index}.json"
-                    with open(temp_case_file, "w", encoding="utf-8") as f:
-                        json.dump(case_data, f, ensure_ascii=False, indent=2)
-                    print(f"   [Target] Filtering to case index {args.case_index}: {target_case.get('title', 'untitled')[:30]}")
-                    case_file_to_use = temp_case_file
-                else:
-                    case_file_to_use = case_file
-
-                from examples.process_pipeline.script.extract_capability import extract_capability
-                result = await extract_capability(
-                    case_file_to_use,
-                    claude_llm_call, model=claude_model
-                )
-
-                # 如果使用了临时文件,需要合并回原始 case.json
-                if args.case_index is not None:
-                    with open(case_file_to_use, "r", encoding="utf-8") as f:
-                        updated_data = json.load(f)
-                    updated_case = updated_data["cases"][0]
-                    with open(case_file, "r", encoding="utf-8") as f:
-                        original_data = json.load(f)
-                    for i, c in enumerate(original_data["cases"]):
-                        if c.get("index") == args.case_index:
-                            original_data["cases"][i] = updated_case
-                            break
-                    with open(case_file, "w", encoding="utf-8") as f:
-                        json.dump(original_data, f, ensure_ascii=False, indent=2)
-                    temp_case_file.unlink()
-                    print(f"   ✓ Merged case {args.case_index} back to case.json")
-
-                print(f"   ✓ case.json + capabilities: success={result['success']}, failed={result['failed']}")
+                print("   ⏭️ capability-extract is integrated into workflow-extract; skipping old extractor.")
 
 
             elif step == "apply-grounding":
             elif step == "apply-grounding":
                 # Phase 1.7: 将 apply_to_draft 映射为正式 apply_to
                 # Phase 1.7: 将 apply_to_draft 映射为正式 apply_to
                 case_file = output_dir / "case.json"
                 case_file = output_dir / "case.json"
                 if not case_file.exists():
                 if not case_file.exists():
-                    print(f"   ❌ case.json not found. Run workflow-extract and capability-extract first.")
+                    print(f"   ❌ case.json not found. Run workflow-extract first.")
                     sys.exit(1)
                     sys.exit(1)
 
 
                 # 如果指定了 --case-index,先过滤 case.json
                 # 如果指定了 --case-index,先过滤 case.json
@@ -1250,7 +1210,7 @@ async def main():
                     print(f"⚠️ [Warning] {err_msg}")
                     print(f"⚠️ [Warning] {err_msg}")
                     global_errors.append(err_msg)
                     global_errors.append(err_msg)
 
 
-        # Phase 1.6: Extract workflow and capabilities sequentially → case.json
+        # Phase 1.6: Extract workflow and capability together → case.json
         if should_run("workflow-extract"):
         if should_run("workflow-extract"):
             case_file = output_dir / "case.json"
             case_file = output_dir / "case.json"
             if case_file.exists():
             if case_file.exists():
@@ -1281,30 +1241,8 @@ async def main():
         if should_run("capability-extract"):
         if should_run("capability-extract"):
             case_file = output_dir / "case.json"
             case_file = output_dir / "case.json"
             if case_file.exists():
             if case_file.exists():
-                try:
-                    from examples.process_pipeline.script.extract_capability import extract_capability
-                    print(f"\n--- Phase 1.6b: Capability Extraction ({claude_model}) ---")
-
-                    capability_stats = await extract_capability(
-                        case_file,
-                        claude_llm_call,
-                        model=claude_model,
-                        max_concurrent=3
-                    )
-
-                    total_cost += capability_stats.get("total_cost", 0.0)
-                    costs_breakdown["P1.6b_CapabilityExtraction"] = round(capability_stats.get("total_cost", 0.0), 4)
-
-                    print(
-                        f"🧩 [Capability Extraction] "
-                        f"success={capability_stats['success']} "
-                        f"failed={capability_stats['failed']} "
-                        f"→ {case_file}"
-                    )
-                except Exception as e:
-                    err_msg = f"Capability extraction failed: {type(e).__name__}: {e}"
-                    print(f"⚠️ [Warning] {err_msg}")
-                    global_errors.append(err_msg)
+                print("\n--- Phase 1.6b: Capability Extraction ---")
+                print("🧩 [Capability Extraction] integrated into workflow-extract; skipping old extractor.")
 
 
         # Phase 1.7: Apply grounding (map apply_to_draft to apply_to)
         # Phase 1.7: Apply grounding (map apply_to_draft to apply_to)
         if should_run("apply-grounding"):
         if should_run("apply-grounding"):

+ 63 - 63
examples/process_pipeline/script/apply_to_grounding.py

@@ -1,7 +1,7 @@
 """
 """
 Stage 2: 将 apply_to_draft 映射为正式 apply_to
 Stage 2: 将 apply_to_draft 映射为正式 apply_to
 
 
-从 case.json 读取,只对每个 case 的 fragments 中的 apply_to_draft 做映射。
+从 case.json 读取,只对每个 case 的 capability 中的 apply_to_draft 做映射。
 调用 LLM 映射到内容树的正式节点,原位回填到 case.json
 调用 LLM 映射到内容树的正式节点,原位回填到 case.json
 
 
 改造版本:通过远程 API 获取内容树,不再依赖本地文件
 改造版本:通过远程 API 获取内容树,不再依赖本地文件
@@ -24,7 +24,7 @@ load_dotenv(PROJECT_ROOT / ".env")
 
 
 # 搜索 API 配置
 # 搜索 API 配置
 SEARCH_API = os.getenv("SEARCH_API", "http://8.147.104.190:8001").rstrip("/")
 SEARCH_API = os.getenv("SEARCH_API", "http://8.147.104.190:8001").rstrip("/")
-FRAGMENT_GROUNDING_BATCH_SIZE = int(os.getenv("FRAGMENT_GROUNDING_BATCH_SIZE", "8"))
+CAPABILITY_GROUNDING_BATCH_SIZE = int(os.getenv("CAPABILITY_GROUNDING_BATCH_SIZE", "8"))
 
 
 # 本地文件路径(作为回退方案)
 # 本地文件路径(作为回退方案)
 EXTRACT_DIR = Path(__file__).resolve().parent / "resource"
 EXTRACT_DIR = Path(__file__).resolve().parent / "resource"
@@ -202,11 +202,11 @@ def iter_batches(items: List[Any], batch_size: int) -> List[List[Any]]:
     return [items[i:i + batch_size] for i in range(0, len(items), batch_size)]
     return [items[i:i + batch_size] for i in range(0, len(items), batch_size)]
 
 
 
 
-def build_fragment_grounding_input(frag: Dict[str, Any]) -> Dict[str, Any]:
-    """只保留 fragment grounding 需要的最小字段"""
+def build_capability_grounding_input(capability: Dict[str, Any]) -> Dict[str, Any]:
+    """只保留 capability grounding 需要的最小字段"""
     return {
     return {
-        "fragment_id": frag.get("fragment_id"),
-        "apply_to_draft": frag.get("apply_to_draft", {}),
+        "capability_id": capability.get("capability_id"),
+        "apply_to_draft": capability.get("apply_to_draft", {}),
     }
     }
 
 
 
 
@@ -218,7 +218,7 @@ def render_grounding_prompt(
     reference_paths: List[str] = None,
     reference_paths: List[str] = None,
 ) -> str:
 ) -> str:
     """渲染 Stage 2 prompt"""
     """渲染 Stage 2 prompt"""
-    target = "fragments 数组中的每一条 fragment"
+    target = "capability 数组中的每一条 capability"
     paths_str = json.dumps(reference_paths or [], ensure_ascii=False)
     paths_str = json.dumps(reference_paths or [], ensure_ascii=False)
     return (
     return (
         template
         template
@@ -238,31 +238,31 @@ async def ground_single_case(
     compact_tree: str = None,
     compact_tree: str = None,
 ) -> tuple[Dict[str, Any], float]:
 ) -> tuple[Dict[str, Any], float]:
     """
     """
-    对单个 case 的 fragments[*].apply_to_draft 做 apply_to 映射。
+    对单个 case 的 capability[*].apply_to_draft 做 apply_to 映射。
 
 
-    只处理 fragments,不读取 workflow/capabilities
+    只处理 capability
     """
     """
     total_cost = 0.0
     total_cost = 0.0
     result = dict(case_item)
     result = dict(case_item)
     title = case_item.get("title", "")[:20] or "untitled"
     title = case_item.get("title", "")[:20] or "untitled"
 
 
-    fragments = case_item.get("fragments")
-    if not isinstance(fragments, list) or not fragments:
+    capability_items = case_item.get("capability")
+    if not isinstance(capability_items, list) or not capability_items:
         return result, total_cost
         return result, total_cost
 
 
-    draft_fragment_pairs = [
-        (idx, frag)
-        for idx, frag in enumerate(fragments)
-        if isinstance(frag, dict) and "apply_to_draft" in frag
+    draft_capability_pairs = [
+        (idx, capability)
+        for idx, capability in enumerate(capability_items)
+        if isinstance(capability, dict) and "apply_to_draft" in capability
     ]
     ]
-    if not draft_fragment_pairs:
+    if not draft_capability_pairs:
         return result, total_cost
         return result, total_cost
 
 
-    # 收集 fragment 的关键词(用于 API 搜索)
+    # 收集 capability 的关键词(用于 API 搜索)
     if use_api:
     if use_api:
         all_keywords = []
         all_keywords = []
-        for _, frag in draft_fragment_pairs:
-            apply_to_draft = frag.get("apply_to_draft", {})
+        for _, capability in draft_capability_pairs:
+            apply_to_draft = capability.get("apply_to_draft", {})
             for key in ["实质", "形式"]:
             for key in ["实质", "形式"]:
                 for draft_text in apply_to_draft.get(key, []):
                 for draft_text in apply_to_draft.get(key, []):
                     all_keywords.extend(extract_keywords_from_draft(draft_text))
                     all_keywords.extend(extract_keywords_from_draft(draft_text))
@@ -270,35 +270,35 @@ async def ground_single_case(
 
 
         if all_keywords:
         if all_keywords:
             categories = await search_categories_by_keywords(all_keywords, top_k=5)
             categories = await search_categories_by_keywords(all_keywords, top_k=5)
-            frag_compact_tree = build_compact_tree(categories)
-            frag_ref_paths = list(dict.fromkeys(
+            capability_compact_tree = build_compact_tree(categories)
+            capability_ref_paths = list(dict.fromkeys(
                 c["path"] for c in categories if c.get("path")
                 c["path"] for c in categories if c.get("path")
             ))
             ))
         else:
         else:
-            frag_compact_tree = compact_tree or "[]"
-            frag_ref_paths = []
+            capability_compact_tree = compact_tree or "[]"
+            capability_ref_paths = []
     else:
     else:
-        frag_compact_tree = compact_tree or "[]"
-        frag_ref_paths = []
+        capability_compact_tree = compact_tree or "[]"
+        capability_ref_paths = []
 
 
-    updated_fragments = [
-        dict(frag) if isinstance(frag, dict) else frag
-        for frag in fragments
+    updated_capabilities = [
+        dict(capability) if isinstance(capability, dict) else capability
+        for capability in capability_items
     ]
     ]
     id_to_index = {
     id_to_index = {
-        frag.get("fragment_id"): idx
-        for idx, frag in draft_fragment_pairs
-        if isinstance(frag.get("fragment_id"), str)
+        capability.get("capability_id"): idx
+        for idx, capability in draft_capability_pairs
+        if isinstance(capability.get("capability_id"), str)
     }
     }
 
 
-    batches = iter_batches(draft_fragment_pairs, FRAGMENT_GROUNDING_BATCH_SIZE)
+    batches = iter_batches(draft_capability_pairs, CAPABILITY_GROUNDING_BATCH_SIZE)
     for batch_idx, batch_pairs in enumerate(batches, start=1):
     for batch_idx, batch_pairs in enumerate(batches, start=1):
-        draft_fragments = [
-            build_fragment_grounding_input(frag)
-            for _, frag in batch_pairs
+        draft_capabilities = [
+            build_capability_grounding_input(capability)
+            for _, capability in batch_pairs
         ]
         ]
-        draft = {"fragments": draft_fragments}
-        prompt = render_grounding_prompt(template, "fragment", draft, frag_compact_tree, frag_ref_paths)
+        draft = {"capability": draft_capabilities}
+        prompt = render_grounding_prompt(template, "capability", draft, capability_compact_tree, capability_ref_paths)
         messages = [{"role": "user", "content": prompt}]
         messages = [{"role": "user", "content": prompt}]
 
 
         grounded, cost = await call_llm_with_retry(
         grounded, cost = await call_llm_with_retry(
@@ -308,41 +308,41 @@ async def ground_single_case(
             temperature=0.1,
             temperature=0.1,
             max_tokens=4000,
             max_tokens=4000,
             max_retries=3,
             max_retries=3,
-            schema_name="apply_to_grounding_fragment",
-            task_name=f"Ground_F_{title}_B{batch_idx}/{len(batches)}",
+            schema_name="apply_to_grounding_capability",
+            task_name=f"Ground_C_{title}_B{batch_idx}/{len(batches)}",
         )
         )
         total_cost += cost
         total_cost += cost
 
 
-        if not grounded or not isinstance(grounded.get("fragments"), list):
+        if not grounded or not isinstance(grounded.get("capability"), list):
             continue
             continue
 
 
-        grounded_frags = grounded["fragments"]
+        grounded_capabilities = grounded["capability"]
         used_indices = set()
         used_indices = set()
-        for output_idx, grounded_frag in enumerate(grounded_frags):
-            if not isinstance(grounded_frag, dict):
+        for output_idx, grounded_capability in enumerate(grounded_capabilities):
+            if not isinstance(grounded_capability, dict):
                 continue
                 continue
-            frag_idx = None
-            fragment_id = grounded_frag.get("fragment_id")
-            if isinstance(fragment_id, str):
-                frag_idx = id_to_index.get(fragment_id)
-            if frag_idx is None and output_idx < len(batch_pairs):
-                frag_idx = batch_pairs[output_idx][0]
-            if frag_idx is None or frag_idx in used_indices:
+            capability_idx = None
+            capability_id = grounded_capability.get("capability_id")
+            if isinstance(capability_id, str):
+                capability_idx = id_to_index.get(capability_id)
+            if capability_idx is None and output_idx < len(batch_pairs):
+                capability_idx = batch_pairs[output_idx][0]
+            if capability_idx is None or capability_idx in used_indices:
                 continue
                 continue
-            apply_to = grounded_frag.get("apply_to")
-            suggest_apply_to = grounded_frag.get("suggest_apply_to")
+            apply_to = grounded_capability.get("apply_to")
+            suggest_apply_to = grounded_capability.get("suggest_apply_to")
             if (
             if (
                 apply_to is not None
                 apply_to is not None
                 and isinstance(suggest_apply_to, str)
                 and isinstance(suggest_apply_to, str)
                 and suggest_apply_to.strip()
                 and suggest_apply_to.strip()
-                and isinstance(updated_fragments[frag_idx], dict)
+                and isinstance(updated_capabilities[capability_idx], dict)
             ):
             ):
-                updated_fragments[frag_idx]["apply_to"] = apply_to
-                updated_fragments[frag_idx]["suggest_apply_to"] = suggest_apply_to.strip()
-                updated_fragments[frag_idx].pop("apply_to_draft", None)
-                used_indices.add(frag_idx)
+                updated_capabilities[capability_idx]["apply_to"] = apply_to
+                updated_capabilities[capability_idx]["suggest_apply_to"] = suggest_apply_to.strip()
+                updated_capabilities[capability_idx].pop("apply_to_draft", None)
+                used_indices.add(capability_idx)
 
 
-    result["fragments"] = updated_fragments
+    result["capability"] = updated_capabilities
 
 
     return result, total_cost
     return result, total_cost
 
 
@@ -384,14 +384,14 @@ async def apply_grounding(
     # 加载 prompt 模板
     # 加载 prompt 模板
     template = load_prompt_template("apply_to_grounding")
     template = load_prompt_template("apply_to_grounding")
 
 
-    # 过滤出需要处理的 case(只看 fragments[*].apply_to_draft)
+    # 过滤出需要处理的 case(只看 capability[*].apply_to_draft)
     needs_grounding = []
     needs_grounding = []
     for case in cases:
     for case in cases:
-        fragments = case.get("fragments")
-        has_frag_draft = isinstance(fragments, list) and any(
-            isinstance(frag, dict) and "apply_to_draft" in frag for frag in fragments
+        capability_items = case.get("capability")
+        has_capability_draft = isinstance(capability_items, list) and any(
+            isinstance(capability, dict) and "apply_to_draft" in capability for capability in capability_items
         )
         )
-        if has_frag_draft:
+        if has_capability_draft:
             needs_grounding.append(case)
             needs_grounding.append(case)
 
 
     print(f"Grounding apply_to for {len(needs_grounding)}/{len(cases)} cases...")
     print(f"Grounding apply_to for {len(needs_grounding)}/{len(cases)} cases...")

+ 28 - 27
examples/process_pipeline/script/extract_workflow.py

@@ -1,15 +1,15 @@
 """
 """
-逐 case 提取 workflow + fragments (v6版本)
+逐 case 提取 workflow + capability (v6版本)
 
 
 从 case.json 读取,按 index 遍历每个 case,
 从 case.json 读取,按 index 遍历每个 case,
-调用 LLM 同时提取 workflow(薄壳 steps)和 fragments(原子操作,含完整 capability 字段),
+调用 LLM 同时提取 workflow(薄壳 steps)和 capability(原子操作),
 按 index 原位回填到 case.json
 按 index 原位回填到 case.json
 
 
 v6 架构特性:
 v6 架构特性:
-- workflow.steps 是薄壳:step_id / order / phase / relation / body,不含 capability 字段
-- fragments 是原子操作列表:每个 fragment 含完整 capability 字段 + workflow_step_ref + is_alternative_to
-- 步内多原子操作 + 步内 alternative 都在 fragment 层表达
-- standalone fragment(workflow_step_ref=null)用于无 workflow 上下文的能力提及
+- workflow.steps 是薄壳:step_id / order / phase,不含 capability 字段
+- capability 是原子操作列表:每个 capability 含 workflow_step_ref + is_alternative_to
+- 步内多原子操作 + 步内 alternative 都在 capability 层表达
+- standalone capability(workflow_step_ref=null)用于无 workflow 上下文的能力提及
 """
 """
 
 
 import asyncio
 import asyncio
@@ -31,7 +31,7 @@ DEFAULT_METHOD_VOCAB = {
         "区域控制", "参数配置", "模型资源", "源素材", "中间产物",
         "区域控制", "参数配置", "模型资源", "源素材", "中间产物",
         "成品", "模板", "评估结果"
         "成品", "模板", "评估结果"
     ],
     ],
-    "模态": ["文本", "图片", "视频", "音频", "特征点", "参数", "模型", "向量", "表格"],
+    "模态": ["文本", "图片", "视频", "音频", "特征点", "参数", "模型", "向量"],
     "主动作": [
     "主动作": [
         "生成", "编辑", "提取", "改写", "合成", "修复", "增强",
         "生成", "编辑", "提取", "改写", "合成", "修复", "增强",
         "训练", "评估", "剪辑", "模板化", "排版", "转写", "配音",
         "训练", "评估", "剪辑", "模板化", "排版", "转写", "配音",
@@ -82,7 +82,7 @@ def render_method_vocab_block(vocab: Dict[str, list]) -> str:
         "- `role/流程角色` 只写接口职责,不写具体内容 what。",
         "- `role/流程角色` 只写接口职责,不写具体内容 what。",
         "- `modality/模态` 只写媒介或数据形态;统一用 `图片`,不要写 `图像`;统一用 `文本`,不要写 `文字`。",
         "- `modality/模态` 只写媒介或数据形态;统一用 `图片`,不要写 `图像`;统一用 `文本`,不要写 `文字`。",
         "- `artifact_type/工件类型` 写该模态下的具体工件,如 `正向提示词`、`蒙版`。",
         "- `artifact_type/工件类型` 写该模态下的具体工件,如 `正向提示词`、`蒙版`。",
-        "- `action.main_action` 写主动作;`action.mechanism` 写动作内部机制。",
+        "- `action.description` 写输入到输出之间客观发生的信息变化;`action.reasoning` 写判断依据。",
         "- 只有词库确实不够时才新增术语;新增术语也必须抽象、短、可复用。",
         "- 只有词库确实不够时才新增术语;新增术语也必须抽象、短、可复用。",
         "",
         "",
         "当前词库:",
         "当前词库:",
@@ -100,12 +100,12 @@ async def extract_workflow_from_case(
     model: str = "anthropic/claude-sonnet-4-5"
     model: str = "anthropic/claude-sonnet-4-5"
 ) -> tuple[Optional[Dict[str, Any]], Optional[List[Dict[str, Any]]], float]:
 ) -> tuple[Optional[Dict[str, Any]], Optional[List[Dict[str, Any]]], float]:
     """
     """
-    从单个 case item 同时提取 workflow(薄壳 steps)和 fragments(原子操作列表)。
+    从单个 case item 同时提取 workflow(薄壳 steps)和 capability(原子操作列表)。
 
 
     Returns:
     Returns:
-        (workflow_dict, fragments_list, cost)
+        (workflow_dict, capability_list, cost)
         workflow_dict 为 None 表示 skip 或提取失败
         workflow_dict 为 None 表示 skip 或提取失败
-        fragments_list 为 None 表示 skip 或提取失败
+        capability_list 为 None 表示 skip 或提取失败
     """
     """
     images = case_item.get("images", [])
     images = case_item.get("images", [])
 
 
@@ -113,8 +113,9 @@ async def extract_workflow_from_case(
     case_copy.pop("images", None)
     case_copy.pop("images", None)
     case_copy.pop("_raw", None)
     case_copy.pop("_raw", None)
     case_copy.pop("workflow", None)
     case_copy.pop("workflow", None)
-    case_copy.pop("fragments", None)
+    case_copy.pop("capability", None)
     case_copy.pop("capabilities", None)
     case_copy.pop("capabilities", None)
+    case_copy.pop("fragments", None)
 
 
     if not case_copy and not images:
     if not case_copy and not images:
         return None, None, 0.0
         return None, None, 0.0
@@ -153,16 +154,14 @@ async def extract_workflow_from_case(
       {{
       {{
         "step_id": "s1",
         "step_id": "s1",
         "order": 1,
         "order": 1,
-        "phase": "生成",
-        "relation": "[去向.最终成品]",
-        "body": "string | null"
+        "phase": "生成"
       }}
       }}
     ]
     ]
   }},
   }},
-  "fragments": [
+  "capability": [
     {{
     {{
-      "fragment_id": "f_s1_0",
-      "action": {{"main_action": "生成", "mechanism": "直接生成"}},
+      "capability_id": "c_s1_0",
+      "action": {{"description": "生成", "reasoning": "输入为文本提示词,输出为图片,客观信息变化是生成"}},
       "inputs": [{{"modality": "文本", "description": "...", "relation": "[来源.原始输入]"}}],
       "inputs": [{{"modality": "文本", "description": "...", "relation": "[来源.原始输入]"}}],
       "outputs": [{{"modality": "图片", "description": "...", "relation": "[去向.最终成品]"}}],
       "outputs": [{{"modality": "图片", "description": "...", "relation": "[去向.最终成品]"}}],
       "body": "string | null",
       "body": "string | null",
@@ -221,9 +220,9 @@ async def extract_workflow_from_case(
         return None, None, cost
         return None, None, cost
 
 
     workflow_data = result_data.get("workflow")
     workflow_data = result_data.get("workflow")
-    fragments_data = result_data.get("fragments", [])
+    capability_data = result_data.get("capability", [])
 
 
-    return workflow_data, fragments_data, cost
+    return workflow_data, capability_data, cost
 
 
 
 
 async def extract_workflow(
 async def extract_workflow(
@@ -267,15 +266,17 @@ async def extract_workflow(
 
 
             print(f"  -> [{index}] [{case_id}] extracting workflow: {title[:60]}")
             print(f"  -> [{index}] [{case_id}] extracting workflow: {title[:60]}")
 
 
-            workflow, fragments, cost = await extract_workflow_from_case(case_item, llm_call, model)
+            workflow, capability, cost = await extract_workflow_from_case(case_item, llm_call, model)
 
 
-            frag_count = len(fragments) if fragments else 0
-            status = f"ok ({frag_count} fragments)" if workflow else "null"
+            capability_count = len(capability) if capability else 0
+            status = f"ok ({capability_count} capability)" if workflow else "null"
             print(f"  <- [{index}] [{case_id}] workflow {status}")
             print(f"  <- [{index}] [{case_id}] workflow {status}")
 
 
             result = dict(case_item)
             result = dict(case_item)
             result["workflow"] = workflow
             result["workflow"] = workflow
-            result["fragments"] = fragments if fragments is not None else []
+            result["capability"] = capability if capability is not None else []
+            result.pop("capabilities", None)
+            result.pop("fragments", None)
             return result, cost
             return result, cost
 
 
     tasks = [process_with_semaphore(case) for case in cases_to_process]
     tasks = [process_with_semaphore(case) for case in cases_to_process]
@@ -285,7 +286,7 @@ async def extract_workflow(
     costs = [r[1] for r in results_with_costs]
     costs = [r[1] for r in results_with_costs]
     total_cost = sum(costs)
     total_cost = sum(costs)
 
 
-    success_count = sum(1 for r in results if r.get("workflow") and r.get("fragments"))
+    success_count = sum(1 for r in results if r.get("workflow") and r.get("capability"))
     failed_count = len(results) - success_count
     failed_count = len(results) - success_count
 
 
     # 如果是部分更新,需要合并回原始 cases 列表
     # 如果是部分更新,需要合并回原始 cases 列表
@@ -305,13 +306,13 @@ async def extract_workflow(
     with open(case_file, "w", encoding="utf-8") as f:
     with open(case_file, "w", encoding="utf-8") as f:
         json.dump(case_data, f, ensure_ascii=False, indent=2)
         json.dump(case_data, f, ensure_ascii=False, indent=2)
 
 
-    fragments_count = sum(len(r.get("fragments") or []) for r in results)
+    capability_count = sum(len(r.get("capability") or []) for r in results)
 
 
     return {
     return {
         "total": len(results),
         "total": len(results),
         "success": success_count,
         "success": success_count,
         "failed": failed_count,
         "failed": failed_count,
-        "fragments_total": fragments_count,
+        "capability_total": capability_count,
         "total_cost": total_cost,
         "total_cost": total_cost,
         "output_file": str(case_file),
         "output_file": str(case_file),
     }
     }

+ 8 - 8
examples/process_pipeline/script/generate_case.py

@@ -10,7 +10,7 @@
 
 
 输出格式:
 输出格式:
   index, category, user_kept, user_comment, description, method,
   index, category, user_kept, user_comment, description, method,
-  cover, title, author, body, images, url, note, _raw, workflow, capabilities
+  cover, title, author, body, images, url, note, _raw, workflow, capability
 """
 """
 
 
 import asyncio
 import asyncio
@@ -268,7 +268,7 @@ async def normalize_source_item(
             "channel_content_id": source_item.get("channel_content_id", ""),
             "channel_content_id": source_item.get("channel_content_id", ""),
         },
         },
         "workflow": None,
         "workflow": None,
-        "capabilities": None,
+        "capability": None,
     }
     }
 
 
 
 
@@ -281,7 +281,7 @@ async def generate_case_from_source(
     """
     """
     从 raw_cases/source.json 生成标准化的 case.json
     从 raw_cases/source.json 生成标准化的 case.json
 
 
-    如果 case.json 已存在,会保留已有的 workflow 和 capabilities
+    如果 case.json 已存在,会保留已有的 workflow 和 capability
     """
     """
     raw_cases_dir = Path(raw_cases_dir)
     raw_cases_dir = Path(raw_cases_dir)
     source_file = raw_cases_dir / "source.json"
     source_file = raw_cases_dir / "source.json"
@@ -311,7 +311,7 @@ async def generate_case_from_source(
                     case_id = case.get("_raw", {}).get("case_id")
                     case_id = case.get("_raw", {}).get("case_id")
                     if case_id:
                     if case_id:
                         existing_cases[case_id] = case
                         existing_cases[case_id] = case
-                print(f"Found {len(existing_cases)} existing cases, will preserve workflow and capabilities")
+                print(f"Found {len(existing_cases)} existing cases, will preserve workflow and capability")
         except Exception as e:
         except Exception as e:
             print(f"Warning: Failed to read existing case.json: {e}")
             print(f"Warning: Failed to read existing case.json: {e}")
 
 
@@ -332,15 +332,15 @@ async def generate_case_from_source(
                 images_dir=images_dir,
                 images_dir=images_dir,
             )
             )
 
 
-            # 如果已有该 case,保留其 workflow 和 capabilities
+            # 如果已有该 case,保留其 workflow 和 capability
             case_id = case.get("_raw", {}).get("case_id")
             case_id = case.get("_raw", {}).get("case_id")
             if case_id and case_id in existing_cases:
             if case_id and case_id in existing_cases:
                 existing = existing_cases[case_id]
                 existing = existing_cases[case_id]
                 if existing.get("workflow") is not None:
                 if existing.get("workflow") is not None:
                     case["workflow"] = existing["workflow"]
                     case["workflow"] = existing["workflow"]
-                if existing.get("capabilities") is not None:
-                    case["capabilities"] = existing["capabilities"]
-                print(f"  [{idx}] {case['title'][:40]} (preserved workflow & capabilities)")
+                if existing.get("capability") is not None:
+                    case["capability"] = existing["capability"]
+                print(f"  [{idx}] {case['title'][:40]} (preserved workflow & capability)")
             else:
             else:
                 print(f"  [{idx}] {case['title'][:40]}")
                 print(f"  [{idx}] {case['title'][:40]}")
 
 

+ 1 - 1
examples/process_pipeline/server.py

@@ -331,7 +331,7 @@ def get_pipeline_status(index: int):
                         for c in cdata["cases"]:
                         for c in cdata["cases"]:
                             if c.get("workflow"):
                             if c.get("workflow"):
                                 has_workflow = True
                                 has_workflow = True
-                            if c.get("capabilities"):
+                            if c.get("capability"):
                                 has_capability = True
                                 has_capability = True
                             if has_workflow and has_capability:
                             if has_workflow and has_capability:
                                 break
                                 break

+ 69 - 58
examples/process_pipeline/ui/app.js

@@ -184,15 +184,15 @@ function renderRawCases(rawCasesObj) {
                 const cId = c.case_id || (c._raw && c._raw.case_id);
                 const cId = c.case_id || (c._raw && c._raw.case_id);
                 const cUrl = c.source_url || c.url;
                 const cUrl = c.source_url || c.url;
                 if (c.workflow) calcWorkflow++;
                 if (c.workflow) calcWorkflow++;
-                if (c.capabilities && c.capabilities.length > 0) calcCapabilities++;
+                if (c.capability && c.capability.length > 0) calcCapabilities++;
                 
                 
                 if (cId) {
                 if (cId) {
                     if (c.workflow) detailMap[cId] = { ...detailMap[cId], workflow: c.workflow };
                     if (c.workflow) detailMap[cId] = { ...detailMap[cId], workflow: c.workflow };
-                    if (c.capabilities) detailMap[cId] = { ...detailMap[cId], capabilities: c.capabilities };
+                    if (c.capability) detailMap[cId] = { ...detailMap[cId], capability: c.capability };
                 }
                 }
                 if (cUrl) {
                 if (cUrl) {
                     if (c.workflow) detailMapByUrl[cUrl] = { ...detailMapByUrl[cUrl], workflow: c.workflow };
                     if (c.workflow) detailMapByUrl[cUrl] = { ...detailMapByUrl[cUrl], workflow: c.workflow };
-                    if (c.capabilities) detailMapByUrl[cUrl] = { ...detailMapByUrl[cUrl], capabilities: c.capabilities };
+                    if (c.capability) detailMapByUrl[cUrl] = { ...detailMapByUrl[cUrl], capability: c.capability };
                 }
                 }
             });
             });
         }
         }
@@ -956,7 +956,7 @@ function renderAggregatedPerCaseData(cases, type) {
         if (type === 'workflow') {
         if (type === 'workflow') {
             items = c.workflow ? [c.workflow] : null;
             items = c.workflow ? [c.workflow] : null;
         } else if (type === 'capabilities') {
         } else if (type === 'capabilities') {
-            items = c.capabilities && Array.isArray(c.capabilities) ? c.capabilities : null;
+            items = c.capability && Array.isArray(c.capability) ? c.capability : null;
         }
         }
         
         
         const targetId = `case-${type}-${idx}`;
         const targetId = `case-${type}-${idx}`;
@@ -983,7 +983,7 @@ function renderAggregatedPerCaseData(cases, type) {
         
         
         contentHtml += `<div id="${targetId}" class="case-section" style="margin-bottom: 3.5rem; padding-top: 1rem;">`;
         contentHtml += `<div id="${targetId}" class="case-section" style="margin-bottom: 3.5rem; padding-top: 1rem;">`;
         
         
-        const stepName = type === 'workflow' ? 'workflow-extract' : 'capability-extract';
+        const stepName = 'workflow-extract';
         const caseIndexToPass = c.index || (idx + 1);
         const caseIndexToPass = c.index || (idx + 1);
         const btnHtml = `<button class="btn btn-secondary" style="font-size: 0.85em; padding: 0.4rem 0.8rem; border-radius: 4px;" onclick="event.stopPropagation(); triggerSingleCaseRerun('${stepName}', ${caseIndexToPass})">🔄 重跑${typeLabel}</button>`;
         const btnHtml = `<button class="btn btn-secondary" style="font-size: 0.85em; padding: 0.4rem 0.8rem; border-radius: 4px;" onclick="event.stopPropagation(); triggerSingleCaseRerun('${stepName}', ${caseIndexToPass})">🔄 重跑${typeLabel}</button>`;
 
 
@@ -1971,7 +1971,7 @@ window.openCaseDetail = function(p, initialIdx) {
         let metaHtml = '';
         let metaHtml = '';
         const wf = ctx.detailMap[cId] || (cUrl ? ctx.detailMapByUrl[cUrl] : null) || c;
         const wf = ctx.detailMap[cId] || (cUrl ? ctx.detailMapByUrl[cUrl] : null) || c;
         if (wf && wf.workflow && wf.workflow.steps) metaHtml += `<span>工序 ${wf.workflow.steps.length}</span>`;
         if (wf && wf.workflow && wf.workflow.steps) metaHtml += `<span>工序 ${wf.workflow.steps.length}</span>`;
-        if (wf && wf.capabilities) metaHtml += `<span>能力 ${wf.capabilities.length}</span>`;
+        if (wf && wf.capability) metaHtml += `<span>能力 ${wf.capability.length}</span>`;
         if (!metaHtml) metaHtml = '<span>无提取</span>';
         if (!metaHtml) metaHtml = '<span>无提取</span>';
         
         
         sidebarHtml += `<div class="modal-sidebar-item ${idx === globalInitialIdx ? 'active' : ''}" id="sidebar-item-${idx}" onclick="window.renderSingleCaseDetail(${idx})">
         sidebarHtml += `<div class="modal-sidebar-item ${idx === globalInitialIdx ? 'active' : ''}" id="sidebar-item-${idx}" onclick="window.renderSingleCaseDetail(${idx})">
@@ -2156,7 +2156,7 @@ window.renderSingleCaseDetail = function(idx) {
     );
     );
     const caseIndexToPass = realCaseIndex >= 0 ? (caseJsonCases[realCaseIndex].index || (realCaseIndex + 1)) : -1;
     const caseIndexToPass = realCaseIndex >= 0 ? (caseJsonCases[realCaseIndex].index || (realCaseIndex + 1)) : -1;
     const btnWorkflowHtml = caseIndexToPass !== -1 ? `<button class="btn btn-secondary" style="font-size: 0.8em; padding: 0.3rem 0.6rem; border-radius: 4px;" onclick="event.stopPropagation(); triggerSingleCaseRerun('workflow-extract', ${caseIndexToPass})">🔄 重跑工序</button>` : '';
     const btnWorkflowHtml = caseIndexToPass !== -1 ? `<button class="btn btn-secondary" style="font-size: 0.8em; padding: 0.3rem 0.6rem; border-radius: 4px;" onclick="event.stopPropagation(); triggerSingleCaseRerun('workflow-extract', ${caseIndexToPass})">🔄 重跑工序</button>` : '';
-    const btnCapabilityHtml = caseIndexToPass !== -1 ? `<button class="btn btn-secondary" style="font-size: 0.8em; padding: 0.3rem 0.6rem; border-radius: 4px;" onclick="event.stopPropagation(); triggerSingleCaseRerun('capability-extract', ${caseIndexToPass})">🔄 重跑能力</button>` : '';
+    const btnCapabilityHtml = caseIndexToPass !== -1 ? `<button class="btn btn-secondary" style="font-size: 0.8em; padding: 0.3rem 0.6rem; border-radius: 4px;" onclick="event.stopPropagation(); triggerSingleCaseRerun('workflow-extract', ${caseIndexToPass})">🔄 重跑能力</button>` : '';
 
 
     mainScrollableHtml += `
     mainScrollableHtml += `
         <div class="case-section" style="margin-top: 2rem;">
         <div class="case-section" style="margin-top: 2rem;">
@@ -2181,7 +2181,7 @@ window.renderSingleCaseDetail = function(idx) {
                 ${btnCapabilityHtml}
                 ${btnCapabilityHtml}
             </div>
             </div>
             <div class="hidden" style="padding-top: 1.2rem;">
             <div class="hidden" style="padding-top: 1.2rem;">
-                ${window.renderStructuredData(wf && wf.capabilities ? wf.capabilities : null, 'capabilities', wf)}
+                ${window.renderStructuredData(wf && wf.capability ? wf.capability : null, 'capabilities', wf)}
             </div>
             </div>
         </div>
         </div>
     `;
     `;
@@ -2230,16 +2230,16 @@ window.renderStructuredData = function(items, type, parentItem = null) {
         const hasValidIO = (arr) => Array.isArray(arr) && arr.length > 0 && (arr[0].role || arr[0].description);
         const hasValidIO = (arr) => Array.isArray(arr) && arr.length > 0 && (arr[0].role || arr[0].description);
         if (hasValidIO(item.inputs) || hasValidIO(item.outputs) || (item.steps && item.steps.length > 0)) {
         if (hasValidIO(item.inputs) || hasValidIO(item.outputs) || (item.steps && item.steps.length > 0)) {
             let actionStr = '';
             let actionStr = '';
-            if (item.action && item.action.main_action) {
-                actionStr = item.action.main_action;
+            if (item.action && item.action.description) {
+                actionStr = item.action.description;
             } else if (item.method && !item.method.includes('[')) {
             } else if (item.method && !item.method.includes('[')) {
                 actionStr = item.method;
                 actionStr = item.method;
             } else if (item.steps && Array.isArray(item.steps)) {
             } else if (item.steps && Array.isArray(item.steps)) {
                 const hasAnyValidIO = item.steps.some(s => hasValidIO(s.inputs) || hasValidIO(s.outputs));
                 const hasAnyValidIO = item.steps.some(s => hasValidIO(s.inputs) || hasValidIO(s.outputs));
                 if (hasAnyValidIO) {
                 if (hasAnyValidIO) {
                     actionStr = item.steps.map(s => {
                     actionStr = item.steps.map(s => {
-                        if (s.action && s.action.main_action) {
-                            return s.action.mechanism ? `[${s.action.main_action}] ${s.action.mechanism}` : s.action.main_action;
+                        if (s.action && s.action.description) {
+                            return s.action.description;
                         }
                         }
                         if (s.method) return s.method;
                         if (s.method) return s.method;
                         if (s.phase) return s.phase;
                         if (s.phase) return s.phase;
@@ -2258,9 +2258,8 @@ window.renderStructuredData = function(items, type, parentItem = null) {
         } else {
         } else {
             const escapeHtml = (s) => String(s).replace(/</g, '&lt;').replace(/>/g, '&gt;');
             const escapeHtml = (s) => String(s).replace(/</g, '&lt;').replace(/>/g, '&gt;');
             title = escapeHtml(item.method || item.name || '');
             title = escapeHtml(item.method || item.name || '');
-            if (!title && item.action && item.action.main_action) {
-                const actText = item.action.mechanism ? `[${item.action.main_action}] ${item.action.mechanism}` : item.action.main_action;
-                title = escapeHtml(actText);
+            if (!title && item.action && item.action.description) {
+                title = escapeHtml(item.action.description);
             }
             }
             if (!title) {
             if (!title) {
                 title = escapeHtml(type === 'workflow' ? '工作流' : `节点 ${idx + 1}`);
                 title = escapeHtml(type === 'workflow' ? '工作流' : `节点 ${idx + 1}`);
@@ -2393,6 +2392,18 @@ window.renderStructuredData = function(items, type, parentItem = null) {
                 <div class="structured-value">${renderApplyToVal(applyToData.val, applyToData.suggest)}</div>
                 <div class="structured-value">${renderApplyToVal(applyToData.val, applyToData.suggest)}</div>
             </div>`;
             </div>`;
         }
         }
+
+        if (item.action && typeof item.action === 'object' && (item.action.description || item.action.reasoning)) {
+            const actionDescription = item.action.description ? String(item.action.description).replace(/</g, '&lt;').replace(/>/g, '&gt;') : '';
+            const actionReasoning = item.action.reasoning ? String(item.action.reasoning).replace(/</g, '&lt;').replace(/>/g, '&gt;') : '';
+            html += `<div class="structured-row">
+                <div class="structured-label">action</div>
+                <div class="structured-value">
+                    ${actionDescription ? `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;">${actionDescription}</span>` : ''}
+                    ${actionReasoning ? `<span style="color:var(--text-secondary);">${actionReasoning}</span>` : ''}
+                </div>
+            </div>`;
+        }
         
         
         // Stage rendering removed per request
         // Stage rendering removed per request
         // Render effects
         // Render effects
@@ -2570,15 +2581,15 @@ window.renderStructuredData = function(items, type, parentItem = null) {
 
 
         // Render steps array specially
         // Render steps array specially
         if (item.steps && Array.isArray(item.steps)) {
         if (item.steps && Array.isArray(item.steps)) {
-            const allFragments = (parentItem && parentItem.fragments) || [];
+            const allCapabilities = (parentItem && parentItem.capability) || [];
             const escapeHtml = (s) => String(s).replace(/</g, '&lt;').replace(/>/g, '&gt;');
             const escapeHtml = (s) => String(s).replace(/</g, '&lt;').replace(/>/g, '&gt;');
             const minWidth = 1250;
             const minWidth = 1250;
             const renderAction = (src) => {
             const renderAction = (src) => {
                 if (!src) return '-';
                 if (!src) return '-';
-                if (src.action && src.action.main_action) {
-                    const mainAction = escapeHtml(src.action.main_action);
-                    const mechanism = src.action.mechanism ? escapeHtml(src.action.mechanism) : '';
-                    return `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;margin-bottom:2px;display:inline-block;">${mainAction}</span>${mechanism}`;
+                if (src.action && src.action.description) {
+                    const description = escapeHtml(src.action.description);
+                    const reasoning = src.action.reasoning ? escapeHtml(src.action.reasoning) : '';
+                    return `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;margin-bottom:2px;display:inline-block;">${description}</span>${reasoning ? `<div style="font-size:0.85em;color:#64748b;margin-top:4px;">${reasoning}</div>` : ''}`;
                 }
                 }
                 if (src.method) return escapeHtml(src.method);
                 if (src.method) return escapeHtml(src.method);
                 if (src.description) return escapeHtml(src.description);
                 if (src.description) return escapeHtml(src.description);
@@ -2612,34 +2623,34 @@ window.renderStructuredData = function(items, type, parentItem = null) {
                     </div>`;
                     </div>`;
                 }).join('')}</div>`;
                 }).join('')}</div>`;
             };
             };
-            const getStepFragments = (step) => {
+            const getStepCapabilities = (step) => {
                 if (!step || !step.step_id) return [];
                 if (!step || !step.step_id) return [];
-                return allFragments.filter(f => {
-                    const refStepId = f.workflow_step_ref && f.workflow_step_ref.step_id;
+                return allCapabilities.filter(capability => {
+                    const refStepId = capability.workflow_step_ref && capability.workflow_step_ref.step_id;
                     return refStepId === step.step_id || (
                     return refStepId === step.step_id || (
-                        f.fragment_id && (
-                            f.fragment_id === `f_${step.step_id}` ||
-                            f.fragment_id.startsWith(`f_${step.step_id}_`)
+                        capability.capability_id && (
+                            capability.capability_id === `c_${step.step_id}` ||
+                            capability.capability_id.startsWith(`c_${step.step_id}_`)
                         )
                         )
                     );
                     );
                 });
                 });
             };
             };
-            const matchedFragments = new Set();
-            const renderFragmentColumns = (fragment) => {
-                const applyTo = fragment && (fragment.apply_to_draft || fragment.apply_to_grounding || fragment.apply_to);
-                const suggestApplyTo = fragment && fragment.apply_to_draft ? null : fragment && fragment.suggest_apply_to;
+            const matchedCapabilities = new Set();
+            const renderCapabilityColumns = (capability) => {
+                const applyTo = capability && (capability.apply_to_draft || capability.apply_to_grounding || capability.apply_to);
+                const suggestApplyTo = capability && capability.apply_to_draft ? null : capability && capability.suggest_apply_to;
                 return `
                 return `
-                    <td class="fragment-cell" style="font-family: monospace;">
+                    <td class="capability-cell" style="font-family: monospace;">
                         <span class="row-expand-icon">▶</span>
                         <span class="row-expand-icon">▶</span>
-                        ${fragment && fragment.fragment_id ? `<span style="display:inline-block; color:#94a3b8; font-size:0.85em; font-weight:400;">${escapeHtml(fragment.fragment_id)}</span>` : '-'}
+                        ${capability && capability.capability_id ? `<span style="display:inline-block; color:#94a3b8; font-size:0.85em; font-weight:400;">${escapeHtml(capability.capability_id)}</span>` : '-'}
                     </td>
                     </td>
-                    <td class="fragment-cell">${renderAction(fragment)}</td>
-                    <td class="fragment-cell">${fragment && fragment.inputs && fragment.inputs.length > 0 ? renderDataObjList(fragment.inputs) : '-'}</td>
-                    <td class="fragment-cell">${fragment && fragment.outputs && fragment.outputs.length > 0 ? renderDataObjList(fragment.outputs) : '-'}</td>
-                    <td class="fragment-cell"><div class="fragment-clamp">${fragment ? renderEffects(fragment.effects) : '-'}</div></td>
-                    <td class="fragment-cell" style="font-size:0.9em;"><div class="fragment-clamp">${applyTo ? renderApplyToVal(applyTo, suggestApplyTo) : '-'}</div></td>
-                    <td class="fragment-cell"><div class="fragment-clamp">${fragment ? renderTools(fragment.tools) : '-'}</div></td>
-                    <td class="fragment-cell"><div class="fragment-clamp fragment-text">${fragment && fragment.body ? escapeHtml(fragment.body) : '-'}</div></td>
+                    <td class="capability-cell">${renderAction(capability)}</td>
+                    <td class="capability-cell">${capability && capability.inputs && capability.inputs.length > 0 ? renderDataObjList(capability.inputs) : '-'}</td>
+                    <td class="capability-cell">${capability && capability.outputs && capability.outputs.length > 0 ? renderDataObjList(capability.outputs) : '-'}</td>
+                    <td class="capability-cell"><div class="capability-clamp">${capability ? renderEffects(capability.effects) : '-'}</div></td>
+                    <td class="capability-cell" style="font-size:0.9em;"><div class="capability-clamp">${applyTo ? renderApplyToVal(applyTo, suggestApplyTo) : '-'}</div></td>
+                    <td class="capability-cell"><div class="capability-clamp">${capability ? renderTools(capability.tools) : '-'}</div></td>
+                    <td class="capability-cell"><div class="capability-clamp capability-text">${capability && capability.body ? escapeHtml(capability.body) : '-'}</div></td>
                 `;
                 `;
             };
             };
 
 
@@ -2651,14 +2662,14 @@ window.renderStructuredData = function(items, type, parentItem = null) {
                         .steps-table tbody tr:hover { background: rgba(0,0,0,0.02) !important; }
                         .steps-table tbody tr:hover { background: rgba(0,0,0,0.02) !important; }
                         .steps-table td { border-bottom: 1px solid rgba(0,0,0,0.05); }
                         .steps-table td { border-bottom: 1px solid rgba(0,0,0,0.05); }
                         .steps-table .step-merged-cell { background: #fbfdff; border-right: 1px dashed rgba(0,0,0,0.08); }
                         .steps-table .step-merged-cell { background: #fbfdff; border-right: 1px dashed rgba(0,0,0,0.08); }
-                        .steps-table .fragment-cell { padding: 12px 10px; vertical-align: top; line-height: 1.5; color: var(--text-main); }
-                        .steps-table .fragment-clamp { max-height: 72px; overflow: hidden; position: relative; }
-                        .steps-table .fragment-clamp::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 24px; background: linear-gradient(to bottom, rgba(255,255,255,0), white); pointer-events: none; }
-                        .steps-table tr.fragment-expanded .fragment-clamp { max-height: none; overflow: visible; }
-                        .steps-table tr.fragment-expanded .fragment-clamp::after { display: none; }
+                        .steps-table .capability-cell { padding: 12px 10px; vertical-align: top; line-height: 1.5; color: var(--text-main); }
+                        .steps-table .capability-clamp { max-height: 72px; overflow: hidden; position: relative; }
+                        .steps-table .capability-clamp::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 24px; background: linear-gradient(to bottom, rgba(255,255,255,0), white); pointer-events: none; }
+                        .steps-table tr.capability-expanded .capability-clamp { max-height: none; overflow: visible; }
+                        .steps-table tr.capability-expanded .capability-clamp::after { display: none; }
                         .steps-table .row-expand-icon { display: inline-block; margin-right: 6px; color: var(--text-muted); font-size: 0.8em; transition: transform 0.2s; }
                         .steps-table .row-expand-icon { display: inline-block; margin-right: 6px; color: var(--text-muted); font-size: 0.8em; transition: transform 0.2s; }
-                        .steps-table tr.fragment-expanded .row-expand-icon { transform: rotate(90deg); }
-                        .steps-table .fragment-text { white-space: pre-wrap; word-break: break-word; }
+                        .steps-table tr.capability-expanded .row-expand-icon { transform: rotate(90deg); }
+                        .steps-table .capability-text { white-space: pre-wrap; word-break: break-word; }
                         .steps-table .effect-item { padding-bottom:8px; margin-bottom:8px; border-bottom:1px dashed rgba(0,0,0,0.08); color:var(--text-main); line-height:1.5; }
                         .steps-table .effect-item { padding-bottom:8px; margin-bottom:8px; border-bottom:1px dashed rgba(0,0,0,0.08); color:var(--text-main); line-height:1.5; }
                         .steps-table .effect-item:last-child { margin-bottom:0; padding-bottom:0; border-bottom:0; }
                         .steps-table .effect-item:last-child { margin-bottom:0; padding-bottom:0; border-bottom:0; }
                         .steps-table .effect-content { min-width:0; flex:1; }
                         .steps-table .effect-content { min-width:0; flex:1; }
@@ -2686,34 +2697,34 @@ window.renderStructuredData = function(items, type, parentItem = null) {
                         <tbody>`;
                         <tbody>`;
 
 
             item.steps.forEach((step, stepIdx) => {
             item.steps.forEach((step, stepIdx) => {
-                const stepFragments = getStepFragments(step);
-                stepFragments.forEach(fragment => matchedFragments.add(fragment));
-                const fragsToRender = stepFragments.length > 0 ? stepFragments : [null];
-                const rowspan = fragsToRender.length;
+                const stepCapabilities = getStepCapabilities(step);
+                stepCapabilities.forEach(capability => matchedCapabilities.add(capability));
+                const capabilitiesToRender = stepCapabilities.length > 0 ? stepCapabilities : [null];
+                const rowspan = capabilitiesToRender.length;
 
 
-                fragsToRender.forEach((fragment, fragmentIdx) => {
+                capabilitiesToRender.forEach((capability, capabilityIdx) => {
                     html += `
                     html += `
-                        <tr style="vertical-align: top;" onclick="this.classList.toggle('fragment-expanded')">
-                            ${fragmentIdx === 0 ? `
+                        <tr style="vertical-align: top;" onclick="this.classList.toggle('capability-expanded')">
+                            ${capabilityIdx === 0 ? `
                             <td class="step-merged-cell" rowspan="${rowspan}" style="padding: 14px 10px; font-weight: 600; color: var(--text-muted); text-align: center;">
                             <td class="step-merged-cell" rowspan="${rowspan}" style="padding: 14px 10px; font-weight: 600; color: var(--text-muted); text-align: center;">
                                 ${step.order || stepIdx + 1}
                                 ${step.order || stepIdx + 1}
                             </td>
                             </td>
                             <td class="step-merged-cell" rowspan="${rowspan}" style="padding: 14px 10px;">
                             <td class="step-merged-cell" rowspan="${rowspan}" style="padding: 14px 10px;">
                                 ${step.phase ? `<span class="structured-badge" style="background:#f1f5f9; color:#475569; font-weight: 500;">${escapeHtml(step.phase)}</span>` : '-'}
                                 ${step.phase ? `<span class="structured-badge" style="background:#f1f5f9; color:#475569; font-weight: 500;">${escapeHtml(step.phase)}</span>` : '-'}
                             </td>` : ''}
                             </td>` : ''}
-                            ${renderFragmentColumns(fragment)}
+                            ${renderCapabilityColumns(capability)}
                         </tr>
                         </tr>
                     `;
                     `;
                 });
                 });
             });
             });
-            allFragments.filter(fragment => !matchedFragments.has(fragment)).forEach(fragment => {
+            allCapabilities.filter(capability => !matchedCapabilities.has(capability)).forEach(capability => {
                 html += `
                 html += `
-                    <tr style="vertical-align: top;" onclick="this.classList.toggle('fragment-expanded')">
+                    <tr style="vertical-align: top;" onclick="this.classList.toggle('capability-expanded')">
                         <td class="step-merged-cell" style="padding: 14px 10px; font-weight: 600; color: var(--text-muted); text-align: center;">-</td>
                         <td class="step-merged-cell" style="padding: 14px 10px; font-weight: 600; color: var(--text-muted); text-align: center;">-</td>
                         <td class="step-merged-cell" style="padding: 14px 10px;">
                         <td class="step-merged-cell" style="padding: 14px 10px;">
-                            <span class="structured-badge" style="background:#f8fafc; color:#64748b; font-weight: 500;">独立片段</span>
+                            <span class="structured-badge" style="background:#f8fafc; color:#64748b; font-weight: 500;">独立能力</span>
                         </td>
                         </td>
-                        ${renderFragmentColumns(fragment)}
+                        ${renderCapabilityColumns(capability)}
                     </tr>
                     </tr>
                 `;
                 `;
             });
             });

+ 11 - 1
examples/process_pipeline/ui/scratchpad.js

@@ -5,7 +5,7 @@ function renderStructuredData(items, type) {
     
     
     let html = '';
     let html = '';
     items.forEach((item, idx) => {
     items.forEach((item, idx) => {
-        let title = item.method || item.name || (type === 'workflow' ? '工作流' : `节点 ${idx + 1}`);
+        let title = item.method || item.name || (item.action && item.action.description) || (type === 'workflow' ? '工作流' : `节点 ${idx + 1}`);
         
         
         // Unstructured what tags
         // Unstructured what tags
         let unstructTags = '';
         let unstructTags = '';
@@ -62,6 +62,16 @@ function renderStructuredData(items, type) {
             }
             }
             html += `</div></div>`;
             html += `</div></div>`;
         }
         }
+
+        if (item.action && typeof item.action === 'object' && (item.action.description || item.action.reasoning)) {
+            html += `<div class="structured-row">
+                <div class="structured-label">action</div>
+                <div class="structured-value">
+                    ${item.action.description ? `<span class="data-type-badge">${String(item.action.description).replace(/</g, '&lt;').replace(/>/g, '&gt;')}</span>` : ''}
+                    ${item.action.reasoning ? `<span style="margin-left:6px;">${String(item.action.reasoning).replace(/</g, '&lt;').replace(/>/g, '&gt;')}</span>` : ''}
+                </div>
+            </div>`;
+        }
         
         
         // Render stage
         // Render stage
         if (item.stage) {
         if (item.stage) {