From 41eb36a704212e51f0fc11bb2a33c6335aa60039 Mon Sep 17 00:00:00 2001 From: wuyang6 Date: Mon, 11 May 2026 19:56:29 +0800 Subject: [PATCH] Avoid draft files in product-data generation --- skills/product-data/README.md | 12 +++++++ skills/product-data/SKILL.md | 31 +++++++++++++------ .../normalize_dataset_draft.input.schema.json | 16 +++++++++- ...normalize_dataset_draft.output.schema.json | 2 ++ .../scripts/normalize_dataset_draft.py | 15 +++++++++ src/agent_runtime.py | 7 +++-- 6 files changed, 71 insertions(+), 12 deletions(-) diff --git a/skills/product-data/README.md b/skills/product-data/README.md index e5c8362..8ea7ed9 100644 --- a/skills/product-data/README.md +++ b/skills/product-data/README.md @@ -53,3 +53,15 @@ cat input.json | python skills/product-data/scripts/.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` 统一校验和导出。 diff --git a/skills/product-data/SKILL.md b/skills/product-data/SKILL.md index 9e9f117..159e26b 100644 --- a/skills/product-data/SKILL.md +++ b/skills/product-data/SKILL.md @@ -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` 行。 diff --git a/skills/product-data/schemas/normalize_dataset_draft.input.schema.json b/skills/product-data/schemas/normalize_dataset_draft.input.schema.json index 7615321..95342c2 100644 --- a/skills/product-data/schemas/normalize_dataset_draft.input.schema.json +++ b/skills/product-data/schemas/normalize_dataset_draft.input.schema.json @@ -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 + } } } diff --git a/skills/product-data/schemas/normalize_dataset_draft.output.schema.json b/skills/product-data/schemas/normalize_dataset_draft.output.schema.json index 2f8d28f..28f74c5 100644 --- a/skills/product-data/schemas/normalize_dataset_draft.output.schema.json +++ b/skills/product-data/schemas/normalize_dataset_draft.output.schema.json @@ -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"} }, diff --git a/skills/product-data/scripts/normalize_dataset_draft.py b/skills/product-data/scripts/normalize_dataset_draft.py index bb0f4bd..f721625 100644 --- a/skills/product-data/scripts/normalize_dataset_draft.py +++ b/skills/product-data/scripts/normalize_dataset_draft.py @@ -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) diff --git a/src/agent_runtime.py b/src/agent_runtime.py index 350ccd8..8ee0243 100644 --- a/src/agent_runtime.py +++ b/src/agent_runtime.py @@ -1601,8 +1601,11 @@ class LocalCodingAgent: f'工具:{name};位置:line {exc.lineno} column {exc.colno};' f'原因:{exc.msg};参数片段:{preview!r}。' '请回复“继续”,我会要求模型重新生成合法 JSON 参数;' - '对于大段 dataset draft,应分批或使用 draft_path/records_path,' - '不要把大量带引号的文本一次性塞进工具参数。' + '对于 product-data 生成数据,不要用 write_file 写大段 draft;' + '应每批最多 8 条,直接用 python_exec 调用 ' + 'skills/product-data/scripts/normalize_dataset_draft.py,' + '通过 stdin 传入小批量 JSON,并把 normalize 结果追加到 ' + 'scratchpad/normalized_records.jsonl。' ) from exc if not isinstance(arguments, dict): raise OpenAICompatError(