Avoid draft files in product-data generation

This commit is contained in:
wuyang6
2026-05-11 19:56:29 +08:00
parent 7dc7fa2e52
commit 41eb36a704
6 changed files with 71 additions and 12 deletions
+12
View File
@@ -53,3 +53,15 @@ cat input.json | python skills/product-data/scripts/<tool>.py
在 ZK Data Agent 里,前链路输入文本化和 review 状态机仍使用平台注册的 `data_agent_*` 工具;格式转换、校验和导出使用 `python_exec` 直接执行本目录 `scripts/` 下的 portable scripts。
通过 `python_exec` 运行时,脚本会根据 `PYTHON_EXEC_SCRATCHPAD` 自动把逻辑路径 `output/...` 路由到当前会话 output 目录。独立运行时,仍按当前目录下的 `output/` 输出。
大批量生成时,不要先写 `draft_part*.txt`。推荐每批最多 8 条,通过 stdin 调用 `normalize_dataset_draft.py`,并设置:
```json
{
"records_output_path": "scratchpad/normalized_records.jsonl",
"append": true,
"return_records": false
}
```
最后从 `scratchpad/normalized_records.jsonl` 统一校验和导出。
+22 -9
View File
@@ -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
allowed_tools: read_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”的统一入口。
@@ -168,9 +168,9 @@ review 展示必须简短清晰,不要重复解释工具和流程。每次 rev
8. 展示计划后停止本轮,等待用户 review。
9. 用户提出修改意见时,调用 `data_agent_update_generation_plan`,再展示计划。
10. 用户明确确认当前计划版本后,调用 `data_agent_confirm_generation_plan`
11. 生成 dataset draft text v1。
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`
11. 分批生成 dataset draft text v1,每批最多 8 条,不要用 `write_file` 保存 `draft_part*.txt` 这类中间草稿
12. 每生成一批,立刻使用 `python_exec` 执行 `skills/product-data/scripts/normalize_dataset_draft.py`:通过 `stdin` 传入小批量 JSON,必须带 `confirmed_plan_id``records_output_path="scratchpad/normalized_records.jsonl"``append=true``return_records=false`draft 中每条 case 都必须有 `complex: true/false`
13. 所有批次 normalize 完成后,使用 `python_exec` 执行 `skills/product-data/scripts/validate_dataset_records.py`,传 `records_path="scratchpad/normalized_records.jsonl"`
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`
@@ -178,11 +178,11 @@ review 展示必须简短清晰,不要重复解释工具和流程。每次 rev
生成阶段要避免一次性把大量数据塞进工具参数:
- 单次 `normalize_dataset_draft.py` 最多处理 8 条 case;计划数量更多时,分批生成、分批 normalize,再汇总校验和导出。
- 单次 `normalize_dataset_draft.py` 最多处理 8 条 case;计划数量更多时,分批生成、分批 normalize 到同一个 `scratchpad/normalized_records.jsonl`,再统一校验和导出。
- dataset draft 中的 Agent target 推荐写成单引号形式,例如 `target: Agent(tag='地图导航')`。工具会规范化为 `Agent(tag="地图导航")`,这样可以降低 tool call JSON 里双引号转义失败的概率。
- `complex:` 独立写一行,不要写进 `target:`;工具会在导出训练数据和流转表格时自动组合成 `complex=false\nAgent(...)`
- 如果 draft 已经保存在文件中,优先传 `draft_path`,不要再把大段 `draft_text` 作为工具参数传入
- 校验和导出 records 时,如果 records 已经保存在 JSON/JSONL 文件中,优先传 `records_path`
- 不要调用 `write_file` 保存模型新生成的 dataset draft;只有用户已经提供 draft 文件时,才使用 `draft_path`
- 校验和导出 records 时,优先传 `records_path="scratchpad/normalized_records.jsonl"`,不要把大量 records 数组塞进工具参数
## 数据生成输出格式
@@ -384,6 +384,20 @@ skills/product-data/scripts/normalize_dataset_draft.py
用途:把 dataset draft text v1 转成 canonical records。每条 case 推荐包含 `complex: true/false`;旧草稿缺失时会按 `false` 兼容。
大批量生成时必须把每批 normalize 结果追加到同一个 records 文件,避免大段 records 在模型上下文和工具参数里来回传递:
```json
{
"draft_text": "# dataset_label: 地图导航\n\n### case: ...",
"batch_id": "map_nav_batch_01",
"source_type": "generated",
"confirmed_plan_id": "data_plan_000001",
"records_output_path": "scratchpad/normalized_records.jsonl",
"append": true,
"return_records": false
}
```
调用方式示例:
```text
@@ -502,7 +516,6 @@ skills/product-data/scripts/export_planning_eval_csv.py
## 当前可用工具
- `read_file`:读取用户提供的产品定义、标签定义、样例 query 文件。
- `write_file`:在用户确认后落盘 draft、校验结果或说明文档;不要用它手写 canonical records 文件。
- `edit_file`:修改已有的计划、说明文档或生成结果文件。
- `grep_search`:在项目中搜索已有标签定义、历史数据样例或相关文档。
- `glob_search`:按路径模式查找定义文件、样例文件或历史产物。
@@ -517,7 +530,7 @@ 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`
- `normalize_dataset_draft.py`:用户确认计划后,把 dataset draft text v1 转成 canonical records;输入 JSON 必须带 `confirmed_plan_id`支持 `draft_text` `draft_path`,大草稿优先 `draft_path`;每条 case 要写 `complex: true/false`
- `normalize_dataset_draft.py`:用户确认计划后,把 dataset draft text v1 转成 canonical records;输入 JSON 必须带 `confirmed_plan_id`模型新生成的数据优先用小批量 `draft_text` 直接 stdin 输入,设置 `records_output_path``append=true``return_records=false`;只有用户已提供草稿文件时才使用 `draft_path`
- `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` 行。
@@ -21,6 +21,20 @@
},
"base_timestamp": {"type": "integer"},
"timestamp_step_ms": {"type": "integer", "minimum": 1, "default": 60000},
"default_request_id": {"type": "string", "default": "aabbccdd"}
"default_request_id": {"type": "string", "default": "aabbccdd"},
"records_output_path": {
"type": "string",
"description": "Optional JSONL output path for normalized records. In platform runtime, scratchpad/... and output/... are routed to the current session."
},
"append": {
"type": "boolean",
"description": "Append normalized records to records_output_path instead of overwriting it.",
"default": false
},
"return_records": {
"type": "boolean",
"description": "Whether to include records in stdout. Prefer false when records_output_path is set for large batches.",
"default": true
}
}
}
@@ -4,6 +4,8 @@
"ok": {"type": "boolean"},
"records": {"type": "array"},
"confirmed_plan_id": {"type": "string"},
"records_output_path": {"type": "string"},
"record_count": {"type": "integer"},
"warnings": {"type": "array", "items": {"type": "string"}},
"error": {"type": "string"}
},
@@ -1,5 +1,7 @@
from __future__ import annotations
import json
from product_data_portable import (
DEFAULT_REQUEST_ID,
DEFAULT_TIMESTAMP_STEP_MS,
@@ -29,6 +31,19 @@ def main() -> int:
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),
)
records_output_path = str(payload.get("records_output_path") or "").strip()
return_records = bool(payload.get("return_records", not records_output_path))
if records_output_path:
output_path = resolve_portable_path(records_output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
mode = "a" if bool(payload.get("append", False)) else "w"
with output_path.open(mode, encoding="utf-8") as fh:
for record in result["records"]:
fh.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n")
result["records_output_path"] = str(output_path)
result["record_count"] = len(result["records"])
if not return_records:
result.pop("records", None)
if confirmed_plan_id:
result["confirmed_plan_id"] = confirmed_plan_id
emit_success(result)