Migrate product-data exports to skill scripts
This commit is contained in:
@@ -48,8 +48,8 @@ cat input.json | python skills/product-data/scripts/<tool>.py
|
||||
| `scripts/export_training_jsonl.py` | 把 canonical records 转成训练 JSONL,`output` 自动组合 `complex` 和标签 |
|
||||
| `scripts/export_planning_eval_csv.py` | 把 canonical records 转成含 chat-template `newPrompt` 的评测 CSV,`complex` 列来自元数据并输出为 `TRUE/FALSE` |
|
||||
|
||||
## 平台内优先级
|
||||
## 平台内使用方式
|
||||
|
||||
在 ZK Data Agent 里,仍优先使用已注册的 `data_agent_*` 工具,因为它们带有平台级 review 状态门禁和 session 输出路由。
|
||||
在 ZK Data Agent 里,前链路输入文本化和 review 状态机仍使用平台注册的 `data_agent_*` 工具;格式转换、校验和导出使用 `python_exec` 直接执行本目录 `scripts/` 下的 portable scripts。
|
||||
|
||||
当运行环境没有这些工具,或其他 Agent 迁移使用本 skill 时,再使用 `scripts/` 里的 portable scripts。
|
||||
通过 `python_exec` 运行时,脚本会根据 `PYTHON_EXEC_SCRATCHPAD` 自动把逻辑路径 `output/...` 路由到当前会话 output 目录。独立运行时,仍按当前目录下的 `output/` 输出。
|
||||
|
||||
@@ -3,7 +3,7 @@ name: product-data
|
||||
description: 从产品/标签定义、手写边界规则或示例 query 中提取标签边界,并生成可 review 的数据计划、dataset draft text 和 canonical metadata records。
|
||||
when_to_use: 当用户提供产品定义、标签规则、路由边界文档、示例 query、手写标签边界,并希望生成训练/评测/专项数据时使用。
|
||||
aliases: definition-data, label-data
|
||||
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question, python_exec, data_agent_load_input_sources, data_agent_render_source_context, data_agent_extract_case_evidence, data_agent_prepare_generation_goal, data_agent_confirm_generation_goal, data_agent_prepare_generation_plan, data_agent_show_generation_plan, data_agent_update_generation_plan, data_agent_confirm_generation_plan, data_agent_normalize_dataset_draft, data_agent_validate_dataset_records, data_agent_export_dataset_records, data_agent_export_training_jsonl, data_agent_export_planning_eval_csv
|
||||
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question, python_exec, data_agent_load_input_sources, data_agent_render_source_context, data_agent_extract_case_evidence, data_agent_prepare_generation_goal, data_agent_confirm_generation_goal, data_agent_prepare_generation_plan, data_agent_show_generation_plan, data_agent_update_generation_plan, data_agent_confirm_generation_plan
|
||||
---
|
||||
|
||||
使用这个 skill 作为“产品/标签定义/手写规则/示例 query -> 输入文本化 -> generation goal 草案 -> 人工 review -> 生成计划 review -> dataset draft text -> canonical metadata records”的统一入口。
|
||||
@@ -33,9 +33,9 @@ skills/product-data/
|
||||
export_planning_eval_csv.py
|
||||
```
|
||||
|
||||
在 ZK Data Agent 平台里,优先使用已注册的 `data_agent_*` 工具,因为它们带有平台级 review 状态、确认门禁和会话 output 路由。
|
||||
在 ZK Data Agent 平台里,前链路输入文本化和 review 状态机仍使用已注册的 `data_agent_*` 工具;数据格式转换、校验和导出一律使用 `python_exec` 执行本 skill 的 `scripts/`。
|
||||
|
||||
迁移到其他 Agent 或没有平台注册能力时,可以直接执行 `scripts/` 下的 portable scripts。脚本支持 `--input input.json` 或 stdin JSON,stdout 只输出一个 JSON 对象。`tools.yaml` 描述了这些脚本如何被平台注册为工具;如果平台不支持 `tools.yaml`,不影响脚本独立使用。
|
||||
迁移到其他 Agent 或没有平台注册能力时,可以直接执行 `scripts/` 下的 portable scripts。脚本支持 `--input input.json` 或 stdin JSON,stdout 只输出一个 JSON 对象。`tools.yaml` 描述这些脚本如何被其他平台注册为工具;当前 ZK Data Agent 主流程不再依赖这些格式转换类平台注册工具。
|
||||
|
||||
脚本能力和平台注册的关系是:
|
||||
|
||||
@@ -103,7 +103,7 @@ source_refs:
|
||||
- 负例/排除项:哪些表达不要生成,或哪些边界容易误判。
|
||||
- 落盘路径:canonical records 固定使用 `output/records.jsonl`,由工具路由到当前会话 output 目录。
|
||||
|
||||
如果任一信息缺失,不要生成数据,不要调用 `data_agent_prepare_generation_plan`,不要调用 `data_agent_normalize_dataset_draft`,不要调用 `data_agent_validate_dataset_records`,只向用户提出需要确认的问题。
|
||||
如果任一信息缺失,不要生成数据,不要调用 `data_agent_prepare_generation_plan`,不要执行 portable scripts,只向用户提出需要确认的问题。
|
||||
|
||||
两段确认模式下,信息完整后,调用 `data_agent_prepare_generation_goal` 创建 pending goal。这个工具会暂停本轮,必须把返回的 `generation_goal` 展示给用户 review。用户确认 goal 之后,调用 `data_agent_confirm_generation_goal` 获取 `confirmed_goal_id`,再调用 `data_agent_prepare_generation_plan` 创建 pending plan。创建 plan 后也会暂停本轮,必须等待用户 review。
|
||||
|
||||
@@ -111,7 +111,7 @@ source_refs:
|
||||
|
||||
用户确认后,拿到 `confirmed_plan_id`,才能生成 dataset draft text,并继续调用工具。
|
||||
|
||||
`data_agent_normalize_dataset_draft` 对生成数据有代码级门禁:没有 `confirmed_plan_id`,或者计划未确认,会拒绝执行。
|
||||
portable scripts 不维护平台 review 状态。生成式数据必须先通过 `data_agent_confirm_generation_plan` 取得 `confirmed_plan_id`,再执行格式转换脚本;调用 normalize 脚本时在输入 JSON 中携带 `confirmed_plan_id` 方便追踪。
|
||||
|
||||
如果用户在原始需求里已经写出 `Agent(tag="xxx")`、function 调用或其他完整标签表达,`target` 必须原样保留这个完整表达,不要简化成纯标签名。例如用户说 `Agent(tag="餐饮服务")`,则 `target_definitions[*].target` 和后续 draft 的 `target:` 都必须写 `Agent(tag="餐饮服务")`,不要写成 `餐饮服务`。
|
||||
|
||||
@@ -169,16 +169,16 @@ review 展示必须简短清晰,不要重复解释工具和流程。每次 rev
|
||||
9. 用户提出修改意见时,调用 `data_agent_update_generation_plan`,再展示计划。
|
||||
10. 用户明确确认当前计划版本后,调用 `data_agent_confirm_generation_plan`。
|
||||
11. 生成 dataset draft text v1。
|
||||
12. 调用 `data_agent_normalize_dataset_draft`,必须传入 `confirmed_plan_id`;draft 中每条 case 都必须有 `complex: true/false`。
|
||||
13. 调用 `data_agent_validate_dataset_records`。
|
||||
14. 如果用户要求落盘 canonical records,调用 `data_agent_export_dataset_records`,`output_path` 固定传 `output/records.jsonl`,默认导出紧凑 JSONL,并同时生成同目录 `records.csv` 表格,不要用 `write_file` 手写 JSON 或 CSV。
|
||||
15. 如果用户已经明确要训练数据,调用 `data_agent_export_training_jsonl`,优先传 `records_path`,输出固定为 `output/training.jsonl`。
|
||||
16. 如果用户已经明确要评测 planningPrompt 数据,调用 `data_agent_export_planning_eval_csv`,优先传 `records_path`,输出固定为 `output/eval_planning.csv`。
|
||||
12. 使用 `python_exec` 执行 `skills/product-data/scripts/normalize_dataset_draft.py`,必须在输入 JSON 中传入 `confirmed_plan_id`;draft 中每条 case 都必须有 `complex: true/false`。
|
||||
13. 使用 `python_exec` 执行 `skills/product-data/scripts/validate_dataset_records.py`。
|
||||
14. 如果用户要求落盘 canonical records,使用 `python_exec` 执行 `skills/product-data/scripts/export_dataset_records.py`,逻辑输出固定为 `output/records.jsonl`,默认导出紧凑 JSONL,并同时生成同目录 `records.csv` 表格,不要用 `write_file` 手写 JSON 或 CSV。
|
||||
15. 如果用户已经明确要训练数据,使用 `python_exec` 执行 `skills/product-data/scripts/export_training_jsonl.py`,优先传 `records_path`,输出固定为 `output/training.jsonl`。
|
||||
16. 如果用户已经明确要评测 planningPrompt 数据,使用 `python_exec` 执行 `skills/product-data/scripts/export_planning_eval_csv.py`,优先传 `records_path`,输出固定为 `output/eval_planning.csv`。
|
||||
17. 如果用户只说“生成数据”但没有说明下游用途,生成 canonical records 和 `records.csv` 后,简短询问用户是否还需要导出训练 jsonl 或评测 csv;不要自己默认生成全部最终格式。
|
||||
|
||||
生成阶段要避免一次性把大量数据塞进工具参数:
|
||||
|
||||
- 单次 `data_agent_normalize_dataset_draft` 最多处理 8 条 case;计划数量更多时,分批生成、分批 normalize,再汇总校验和导出。
|
||||
- 单次 `normalize_dataset_draft.py` 最多处理 8 条 case;计划数量更多时,分批生成、分批 normalize,再汇总校验和导出。
|
||||
- dataset draft 中的 Agent target 推荐写成单引号形式,例如 `target: Agent(tag='地图导航')`。工具会规范化为 `Agent(tag="地图导航")`,这样可以降低 tool call JSON 里双引号转义失败的概率。
|
||||
- `complex:` 独立写一行,不要写进 `target:`;工具会在导出训练数据和流转表格时自动组合成 `complex=false\nAgent(...)`。
|
||||
- 如果 draft 已经保存在文件中,优先传 `draft_path`,不要再把大段 `draft_text` 作为工具参数传入。
|
||||
@@ -226,14 +226,14 @@ notes: 可选,说明覆盖的问题或边界
|
||||
|
||||
## Canonical Record
|
||||
|
||||
`data_agent_normalize_dataset_draft` 会把 dataset draft text 转成 canonical records,并统一补齐 `record_id`、`source`、`timestamp`、`context`、`target_type` 等机械字段。
|
||||
`normalize_dataset_draft.py` 会把 dataset draft text 转成 canonical records,并统一补齐 `record_id`、`source`、`timestamp`、`context`、`target_type` 等机械字段。
|
||||
|
||||
canonical records 落盘必须使用 `data_agent_export_dataset_records`,默认格式是紧凑 JSONL:一行一个 canonical record,不带外层数组,不手写缩进 JSON。默认 `output_path` 固定传 `output/records.jsonl`;不要传 `output/<数据集名>/records.jsonl`,不要传 `tasks/...`,不要自定义文件名。工具默认同时在同目录生成 `records.csv`,用于同事之间流转和表格查看。只有用户明确要求兼容旧文件时,才使用 `output_format="json"` 导出紧凑 JSON 数组,此时工具会归一为 `output/records.json`,但表格仍然是同目录 `records.csv`。
|
||||
canonical records 落盘必须使用 `export_dataset_records.py`,默认格式是紧凑 JSONL:一行一个 canonical record,不带外层数组,不手写缩进 JSON。默认 `output_path` 固定传 `output/records.jsonl`;不要传 `output/<数据集名>/records.jsonl`,不要传 `tasks/...`,不要自定义文件名。脚本默认同时在同目录生成 `records.csv`,用于同事之间流转和表格查看。只有用户明确要求兼容旧文件时,才使用 `output_format="json"` 导出紧凑 JSON 数组,此时输出为 `output/records.json`,但表格仍然是同目录 `records.csv`。
|
||||
|
||||
派生格式也必须从 canonical records 转换,不要让模型手写:
|
||||
|
||||
- 训练数据:调用 `data_agent_export_training_jsonl`,输出 `training.jsonl`,每行包含 `system`、`instruction`、`output`。
|
||||
- 评测数据:调用 `data_agent_export_planning_eval_csv`,输出 `eval_planning.csv`,字段为 `request_id,newPrompt,query,类别真实标签,code标签,complex`。
|
||||
- 训练数据:执行 `export_training_jsonl.py`,输出 `training.jsonl`,每行包含 `system`、`instruction`、`output`。
|
||||
- 评测数据:执行 `export_planning_eval_csv.py`,输出 `eval_planning.csv`,字段为 `request_id,newPrompt,query,类别真实标签,code标签,complex`。
|
||||
- 训练 `output` 和流转表格 `function` 列都会自动组合为两行:第一行 `complex=true/false`,第二行原监督标签;评测 `complex` 列直接来自 canonical record 的 `dimensions.complex`,按评测表习惯输出 `TRUE/FALSE`。
|
||||
- 训练 `instruction` 和评测 `newPrompt` 复用相同 prompt 主体:`[知识注入]`、`[系统状态]`、`[对话历史]`、`[当前query]`、`[function]`;评测 `newPrompt` 还会额外包上 `<|im_start|>system`、`<|im_start|>user`、`<|im_start|>assistant` chat template。
|
||||
- 对话历史默认最多取 5 轮,且相邻轮间隔不超过 5 分钟;`context` 默认注入 `location` 和 `rag` 两个字段。
|
||||
@@ -355,7 +355,7 @@ canonical records 落盘必须使用 `data_agent_export_dataset_records`,默
|
||||
|
||||
## Portable Scripts
|
||||
|
||||
当平台注册工具不可用时,使用 `python_exec` 执行这些脚本。不要用 `bash` 执行 Python。
|
||||
生成计划确认后,格式转换、校验和导出一律使用 `python_exec` 执行这些脚本。不要用 `bash` 执行 Python。脚本在平台内会自动把逻辑路径 `output/...` 路由到当前会话的 output 目录;独立运行时则写入当前目录下的 `output/`。
|
||||
|
||||
### `product_data_normalize_dataset_draft`
|
||||
|
||||
@@ -371,18 +371,25 @@ skills/product-data/scripts/normalize_dataset_draft.py
|
||||
{
|
||||
"draft_text": "# dataset_label: 地图餐饮边界\n\n### case: 找附近美食\n用户: 附近有什么好吃的\ncomplex: false\ntarget: Agent(tag='餐饮服务')",
|
||||
"batch_id": "aabbccdd",
|
||||
"source_type": "generated"
|
||||
"source_type": "generated",
|
||||
"confirmed_plan_id": "data_plan_000001"
|
||||
}
|
||||
```
|
||||
|
||||
如果草稿已经落盘,也可以传:
|
||||
|
||||
```json
|
||||
{"draft_path": "output/dataset_draft.txt", "batch_id": "aabbccdd", "source_type": "generated"}
|
||||
{"draft_path": "scratchpad/dataset_draft.txt", "batch_id": "aabbccdd", "source_type": "generated", "confirmed_plan_id": "data_plan_000001"}
|
||||
```
|
||||
|
||||
用途:把 dataset draft text v1 转成 canonical records。每条 case 推荐包含 `complex: true/false`;旧草稿缺失时会按 `false` 兼容。
|
||||
|
||||
调用方式示例:
|
||||
|
||||
```text
|
||||
python_exec(script_path="skills/product-data/scripts/normalize_dataset_draft.py", stdin="<上面的 JSON>")
|
||||
```
|
||||
|
||||
### `product_data_validate_dataset_records`
|
||||
|
||||
脚本:
|
||||
@@ -500,7 +507,7 @@ skills/product-data/scripts/export_planning_eval_csv.py
|
||||
- `grep_search`:在项目中搜索已有标签定义、历史数据样例或相关文档。
|
||||
- `glob_search`:按路径模式查找定义文件、样例文件或历史产物。
|
||||
- `ask_user_question`:需要用户明确选择或补充关键信息时使用;如果不可用,就用普通回复提问并停止。
|
||||
- `python_exec`:仅在已注册 `data_agent_*` 工具不可用、或需要迁移验证 portable scripts 时使用;优先执行 `skills/product-data/scripts/` 下脚本。
|
||||
- `python_exec`:执行 `skills/product-data/scripts/` 下的 portable scripts;格式转换、校验、导出必须优先用它,不要用 `bash` 执行 Python。
|
||||
- `data_agent_load_input_sources`:读取用户给的目录或文件,把 docx/xlsx/pdf 等输入统一抽成段落、表格和 source refs。
|
||||
- `data_agent_render_source_context`:把结构化输入渲染成大模型可读文本,支持 `max_chars`、表格行数和关键词过滤,用于后续模型语义抽取。
|
||||
- `data_agent_extract_case_evidence`:从 badcase/评测/走查表中抽取 query、预期标签、模型预测、上下文和备注;字段歧义会返回需要确认的问题。
|
||||
@@ -510,18 +517,18 @@ skills/product-data/scripts/export_planning_eval_csv.py
|
||||
- `data_agent_show_generation_plan`:用户要求查看当前计划,或继续上下文时需要恢复计划详情时使用。
|
||||
- `data_agent_update_generation_plan`:用户对计划提出修改意见后使用,更新计划并重新展示。
|
||||
- `data_agent_confirm_generation_plan`:用户明确确认当前计划版本后使用,获取 `confirmed_plan_id`。
|
||||
- `data_agent_normalize_dataset_draft`:用户确认计划后,把 dataset draft text v1 转成 canonical records;必须传入 `confirmed_plan_id`;支持 `draft_text` 或 `draft_path`,大草稿优先 `draft_path`;每条 case 要写 `complex: true/false`。
|
||||
- `data_agent_validate_dataset_records`:对 canonical records 做结构、标签、时间戳和多轮上下文校验;支持 `records` 或 `records_path`。
|
||||
- `data_agent_export_dataset_records`:校验 canonical records 并落盘;支持 `records` 或 `records_path`;默认写紧凑 JSONL,一行一条,固定传 `output/records.jsonl`,并同时生成 `output/records.csv` 表格,不要再用 `write_file` 手写 records 或表格文件。
|
||||
- `data_agent_export_training_jsonl`:把 canonical records 转成训练 JSONL;支持 `records` 或 `records_path`;默认写 `output/training.jsonl`;`output` 自动包含 `complex` 行。
|
||||
- `data_agent_export_planning_eval_csv`:把 canonical records 转成评测 CSV;支持 `records` 或 `records_path`;默认写 `output/eval_planning.csv`;`complex` 列从元数据生成。
|
||||
- `normalize_dataset_draft.py`:用户确认计划后,把 dataset draft text v1 转成 canonical records;输入 JSON 必须带 `confirmed_plan_id`;支持 `draft_text` 或 `draft_path`,大草稿优先 `draft_path`;每条 case 要写 `complex: true/false`。
|
||||
- `validate_dataset_records.py`:对 canonical records 做结构、标签、时间戳和多轮上下文校验;支持 `records` 或 `records_path`。
|
||||
- `export_dataset_records.py`:校验 canonical records 并落盘;支持 `records` 或 `records_path`;默认写紧凑 JSONL,一行一条,固定传 `output/records.jsonl`,并同时生成 `output/records.csv` 表格,不要再用 `write_file` 手写 records 或表格文件。
|
||||
- `export_training_jsonl.py`:把 canonical records 转成训练 JSONL;支持 `records` 或 `records_path`;默认写 `output/training.jsonl`;`output` 自动包含 `complex` 行。
|
||||
- `export_planning_eval_csv.py`:把 canonical records 转成评测 CSV;支持 `records` 或 `records_path`;默认写 `output/eval_planning.csv`;`complex` 列从元数据生成。
|
||||
|
||||
## 约束
|
||||
|
||||
- 不要静默解决产品或标签歧义。
|
||||
- 如果 `ask_user_question` 不可用,使用普通回复向用户提问并停止,不要自己替用户确认。
|
||||
- canonical records 通过校验前,不要生成最终导出格式。
|
||||
- canonical records 需要落盘时,必须用 `data_agent_export_dataset_records`;不要自己拼接 JSON/JSONL/CSV;不要创建数据集子目录或自定义 records 文件名。
|
||||
- canonical records 需要落盘时,必须用 `python_exec` 执行 `export_dataset_records.py`;不要自己拼接 JSON/JSONL/CSV;不要创建数据集子目录或自定义 records 文件名。
|
||||
- 训练/评测派生格式必须从 canonical records 通过工具导出;不要让模型自己拼 prompt、手写 jsonl 或 csv。
|
||||
- 如果使用 portable scripts,它们只负责格式转换、校验和导出,不替代 `generation_goal` / `generation_plan` 的用户 review。
|
||||
- 除非用户明确要求,否则不要把“修改标签定义”和“生成数据”混在一起做。
|
||||
|
||||
@@ -51,7 +51,7 @@ canonical record 是产品数据生成链路的中间元数据格式。它不是
|
||||
|
||||
## 默认导出
|
||||
|
||||
调用 `data_agent_export_dataset_records` 或 portable `export_dataset_records.py` 时,默认产物包括:
|
||||
执行 portable `export_dataset_records.py` 时,默认产物包括:
|
||||
|
||||
- `records.jsonl`:canonical records,一行一条紧凑 JSON。
|
||||
- `records.csv`:同事流转表格,字段为 `request_id,timestamp,query,prev_session,context,label,是否迁移Function,function`。
|
||||
@@ -64,7 +64,7 @@ canonical record 是产品数据生成链路的中间元数据格式。它不是
|
||||
|
||||
训练和评测格式都从 canonical records 转换,不由模型手写:
|
||||
|
||||
- `training.jsonl`:每行 `{"system": "...", "instruction": "...", "output": "..."}`,由 `export_training_jsonl.py` 或 `data_agent_export_training_jsonl` 生成;`output` 会输出 `complex=true/false` 加监督标签两行。
|
||||
- `eval_planning.csv`:字段为 `request_id,newPrompt,query,类别真实标签,code标签,complex`,由 `export_planning_eval_csv.py` 或 `data_agent_export_planning_eval_csv` 生成;`newPrompt` 会包上 `<|im_start|>system/user/assistant` chat template;`complex` 列来自 `dimensions.complex`,按评测表习惯输出 `TRUE/FALSE`,不是固定默认值。
|
||||
- `training.jsonl`:每行 `{"system": "...", "instruction": "...", "output": "..."}`,由 `export_training_jsonl.py` 生成;`output` 会输出 `complex=true/false` 加监督标签两行。
|
||||
- `eval_planning.csv`:字段为 `request_id,newPrompt,query,类别真实标签,code标签,complex`,由 `export_planning_eval_csv.py` 生成;`newPrompt` 会包上 `<|im_start|>system/user/assistant` chat template;`complex` 列来自 `dimensions.complex`,按评测表习惯输出 `TRUE/FALSE`,不是固定默认值。
|
||||
|
||||
`instruction` 和 `newPrompt` 使用同一套 prompt 主体,默认包含 `[知识注入]`、`[系统状态]`、`[对话历史]`、`[当前query]`、`[function]`。历史轮次默认最多取 5 轮,且相邻时间间隔不超过 5 分钟。
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
"enum": ["generated", "online", "manual", "mixed"],
|
||||
"default": "generated"
|
||||
},
|
||||
"confirmed_plan_id": {
|
||||
"type": "string",
|
||||
"description": "Required when source_type is generated; returned by data_agent_confirm_generation_plan."
|
||||
},
|
||||
"base_timestamp": {"type": "integer"},
|
||||
"timestamp_step_ms": {"type": "integer", "minimum": 1, "default": 60000},
|
||||
"default_request_id": {"type": "string", "default": "aabbccdd"}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"properties": {
|
||||
"ok": {"type": "boolean"},
|
||||
"records": {"type": "array"},
|
||||
"confirmed_plan_id": {"type": "string"},
|
||||
"warnings": {"type": "array", "items": {"type": "string"}},
|
||||
"error": {"type": "string"}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from product_data_portable import (
|
||||
DEFAULT_REQUEST_ID,
|
||||
DEFAULT_TIMESTAMP_STEP_MS,
|
||||
@@ -9,6 +7,7 @@ from product_data_portable import (
|
||||
emit_success,
|
||||
load_json_payload,
|
||||
normalize_dataset_draft,
|
||||
resolve_portable_path,
|
||||
)
|
||||
|
||||
|
||||
@@ -17,15 +16,21 @@ def main() -> int:
|
||||
payload = load_json_payload()
|
||||
draft_text = str(payload.get("draft_text") or "")
|
||||
if not draft_text and payload.get("draft_path"):
|
||||
draft_text = Path(str(payload["draft_path"])).expanduser().read_text(encoding="utf-8")
|
||||
draft_text = resolve_portable_path(str(payload["draft_path"])).read_text(encoding="utf-8")
|
||||
source_type = str(payload.get("source_type") or "generated")
|
||||
confirmed_plan_id = str(payload.get("confirmed_plan_id") or "").strip()
|
||||
if source_type == "generated" and not confirmed_plan_id:
|
||||
raise ValueError("confirmed_plan_id is required for generated data")
|
||||
result = normalize_dataset_draft(
|
||||
draft_text,
|
||||
batch_id=str(payload.get("batch_id") or DEFAULT_REQUEST_ID),
|
||||
source_type=str(payload.get("source_type") or "generated"),
|
||||
source_type=source_type,
|
||||
base_timestamp=payload.get("base_timestamp"),
|
||||
timestamp_step_ms=int(payload.get("timestamp_step_ms") or DEFAULT_TIMESTAMP_STEP_MS),
|
||||
default_request_id=str(payload.get("default_request_id") or DEFAULT_REQUEST_ID),
|
||||
)
|
||||
if confirmed_plan_id:
|
||||
result["confirmed_plan_id"] = confirmed_plan_id
|
||||
emit_success(result)
|
||||
return 0
|
||||
except Exception as exc: # noqa: BLE001 - CLI 需要把错误稳定转成 JSON
|
||||
|
||||
@@ -9,6 +9,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
@@ -79,7 +80,7 @@ def load_records(payload: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
|
||||
|
||||
def read_records_file(path: str) -> list[dict[str, Any]]:
|
||||
file_path = Path(path).expanduser()
|
||||
file_path = resolve_portable_path(path)
|
||||
text = file_path.read_text(encoding="utf-8")
|
||||
if file_path.suffix.lower() == ".jsonl":
|
||||
records: list[dict[str, Any]] = []
|
||||
@@ -814,40 +815,82 @@ def default_output_path(payload: dict[str, Any]) -> str:
|
||||
output_format = str(payload.get("output_format") or "jsonl")
|
||||
filename = "records.json" if output_format == "json" else "records.jsonl"
|
||||
if isinstance(payload.get("output_path"), str) and payload["output_path"].strip():
|
||||
return payload["output_path"].strip()
|
||||
return str(resolve_portable_path(payload["output_path"].strip()))
|
||||
if isinstance(payload.get("output_dir"), str) and payload["output_dir"].strip():
|
||||
return str(Path(payload["output_dir"]).expanduser() / filename)
|
||||
return str(Path("output") / filename)
|
||||
return str(resolve_portable_path(payload["output_dir"].strip()) / filename)
|
||||
return str(default_output_root() / filename)
|
||||
|
||||
|
||||
def default_table_output_path(payload: dict[str, Any]) -> str:
|
||||
if isinstance(payload.get("output_path"), str) and payload["output_path"].strip():
|
||||
return payload["output_path"].strip()
|
||||
return str(resolve_portable_path(payload["output_path"].strip()))
|
||||
if isinstance(payload.get("output_dir"), str) and payload["output_dir"].strip():
|
||||
return str(Path(payload["output_dir"]).expanduser() / "records.csv")
|
||||
return str(resolve_portable_path(payload["output_dir"].strip()) / "records.csv")
|
||||
if isinstance(payload.get("records_path"), str) and payload["records_path"].strip():
|
||||
return str(Path(payload["records_path"]).expanduser().with_name("records.csv"))
|
||||
return str(Path("output") / "records.csv")
|
||||
return str(resolve_portable_path(payload["records_path"].strip()).with_name("records.csv"))
|
||||
return str(default_output_root() / "records.csv")
|
||||
|
||||
|
||||
def default_training_output_path(payload: dict[str, Any]) -> str:
|
||||
if isinstance(payload.get("output_path"), str) and payload["output_path"].strip():
|
||||
return payload["output_path"].strip()
|
||||
return str(resolve_portable_path(payload["output_path"].strip()))
|
||||
if isinstance(payload.get("output_dir"), str) and payload["output_dir"].strip():
|
||||
return str(Path(payload["output_dir"]).expanduser() / "training.jsonl")
|
||||
return str(resolve_portable_path(payload["output_dir"].strip()) / "training.jsonl")
|
||||
if isinstance(payload.get("records_path"), str) and payload["records_path"].strip():
|
||||
return str(Path(payload["records_path"]).expanduser().with_name("training.jsonl"))
|
||||
return str(Path("output") / "training.jsonl")
|
||||
return str(resolve_portable_path(payload["records_path"].strip()).with_name("training.jsonl"))
|
||||
return str(default_output_root() / "training.jsonl")
|
||||
|
||||
|
||||
def default_planning_eval_output_path(payload: dict[str, Any]) -> str:
|
||||
if isinstance(payload.get("output_path"), str) and payload["output_path"].strip():
|
||||
return payload["output_path"].strip()
|
||||
return str(resolve_portable_path(payload["output_path"].strip()))
|
||||
if isinstance(payload.get("output_dir"), str) and payload["output_dir"].strip():
|
||||
return str(Path(payload["output_dir"]).expanduser() / "eval_planning.csv")
|
||||
return str(resolve_portable_path(payload["output_dir"].strip()) / "eval_planning.csv")
|
||||
if isinstance(payload.get("records_path"), str) and payload["records_path"].strip():
|
||||
return str(Path(payload["records_path"]).expanduser().with_name("eval_planning.csv"))
|
||||
return str(Path("output") / "eval_planning.csv")
|
||||
return str(resolve_portable_path(payload["records_path"].strip()).with_name("eval_planning.csv"))
|
||||
return str(default_output_root() / "eval_planning.csv")
|
||||
|
||||
|
||||
def default_output_root() -> Path:
|
||||
"""返回 portable 脚本默认输出目录。
|
||||
|
||||
在 ZK Data Agent 中,python_exec 的 cwd 是当前会话 scratchpad;
|
||||
通过 PYTHON_EXEC_SCRATCHPAD 可以定位同级 output 目录。离开平台独立
|
||||
运行时,保持原来的相对 output/ 行为。
|
||||
"""
|
||||
|
||||
scratchpad = os.environ.get("PYTHON_EXEC_SCRATCHPAD")
|
||||
if scratchpad:
|
||||
return Path(scratchpad).expanduser().parent / "output"
|
||||
return Path("output")
|
||||
|
||||
|
||||
def resolve_portable_path(path: str) -> Path:
|
||||
"""解析 portable 脚本路径,并兼容平台逻辑路径。
|
||||
|
||||
平台内运行时,把 output/、scratchpad/、input/ 映射到当前会话目录;
|
||||
其他相对路径仍按当前 cwd 解析,便于普通 Python 环境复用。
|
||||
"""
|
||||
|
||||
raw_path = Path(path).expanduser()
|
||||
if raw_path.is_absolute():
|
||||
return raw_path
|
||||
scratchpad = os.environ.get("PYTHON_EXEC_SCRATCHPAD")
|
||||
if not scratchpad:
|
||||
return raw_path
|
||||
session_root = Path(scratchpad).expanduser().parent
|
||||
parts = raw_path.parts
|
||||
if not parts:
|
||||
return raw_path
|
||||
head, *tail = parts
|
||||
tail_path = Path(*tail) if tail else Path()
|
||||
if head in {"output", "outputs"}:
|
||||
return session_root / "output" / tail_path
|
||||
if head in {"scratchpad", "scratch"}:
|
||||
return Path(scratchpad).expanduser() / tail_path
|
||||
if head in {"input", "inputs"}:
|
||||
return session_root / "input" / tail_path
|
||||
return raw_path
|
||||
|
||||
|
||||
def normalize_target_expression(target: str) -> str:
|
||||
|
||||
@@ -351,7 +351,7 @@ def confirm_generation_plan(
|
||||
return {
|
||||
'plan': plan,
|
||||
'confirmed_plan_id': plan_id,
|
||||
'message': '计划已确认,可以把 confirmed_plan_id 传给 data_agent_normalize_dataset_draft。',
|
||||
'message': '计划已确认,可以把 confirmed_plan_id 传给 product-data 的 normalize_dataset_draft.py 脚本。',
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user