Improve long product-data generation runs
This commit is contained in:
@@ -589,6 +589,7 @@ bash "$HOME/zk-data-agent/scripts/install-from-git.sh"
|
||||
OPENAI_API_KEY
|
||||
OPENAI_BASE_URL
|
||||
OPENAI_MODEL
|
||||
OPENAI_TIMEOUT_SECONDS
|
||||
CLAW_BACKEND_HOST
|
||||
CLAW_BACKEND_PORT
|
||||
CLAW_FRONTEND_HOST
|
||||
@@ -730,6 +731,7 @@ cat .env.deploy
|
||||
- `OPENAI_API_KEY`
|
||||
- `OPENAI_BASE_URL`
|
||||
- `OPENAI_MODEL`
|
||||
- `OPENAI_TIMEOUT_SECONDS`
|
||||
|
||||
然后看后端日志:
|
||||
|
||||
|
||||
@@ -278,6 +278,7 @@ class AgentInstanceConfig:
|
||||
model: str
|
||||
base_url: str
|
||||
api_key: str
|
||||
timeout_seconds: float
|
||||
allow_shell: bool
|
||||
allow_write: bool
|
||||
|
||||
@@ -483,6 +484,7 @@ class AgentState:
|
||||
model: str,
|
||||
base_url: str,
|
||||
api_key: str,
|
||||
timeout_seconds: float,
|
||||
allow_shell: bool,
|
||||
allow_write: bool,
|
||||
session_directory: Path,
|
||||
@@ -498,6 +500,7 @@ class AgentState:
|
||||
model=model,
|
||||
base_url=base_url,
|
||||
api_key=api_key,
|
||||
timeout_seconds=timeout_seconds,
|
||||
allow_shell=allow_shell,
|
||||
allow_write=allow_write,
|
||||
)
|
||||
@@ -772,6 +775,7 @@ class AgentState:
|
||||
model=config.model,
|
||||
base_url=config.base_url,
|
||||
api_key=config.api_key,
|
||||
timeout_seconds=config.timeout_seconds,
|
||||
)
|
||||
return LocalCodingAgent(
|
||||
model_config=model_config,
|
||||
@@ -1753,6 +1757,9 @@ def _interrupted_session_message(status: str) -> str:
|
||||
def _runtime_event_stage(event: dict[str, object]) -> str:
|
||||
event_type = event.get('type')
|
||||
if event_type == 'tool_start':
|
||||
stage_note = str(event.get('assistant_content') or '').strip()
|
||||
if stage_note.startswith(('进度:', '进度:')):
|
||||
return stage_note
|
||||
tool_name = str(event.get('tool_name') or '').strip()
|
||||
return f'调用工具 {tool_name}' if tool_name else '正在调用工具'
|
||||
if event_type == 'tool_result':
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "@/lib/claw-auth";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const maxDuration = 300;
|
||||
export const maxDuration = 3600;
|
||||
|
||||
type ClawChatResponse = {
|
||||
final_output?: string;
|
||||
|
||||
@@ -566,8 +566,16 @@ function summarizeRunEvents(runStatus: ClawActiveRunStatus) {
|
||||
lines.push("后端已开始执行本轮任务。");
|
||||
}
|
||||
if (event.type === "tool_start") {
|
||||
const stageNote =
|
||||
typeof event.assistant_content === "string"
|
||||
? event.assistant_content.trim()
|
||||
: "";
|
||||
lines.push(
|
||||
event.tool_name ? `调用工具 ${event.tool_name}` : "正在调用工具",
|
||||
stageNote.startsWith("进度:") || stageNote.startsWith("进度:")
|
||||
? stageNote
|
||||
: event.tool_name
|
||||
? `调用工具 ${event.tool_name}`
|
||||
: "正在调用工具",
|
||||
);
|
||||
}
|
||||
if (event.type === "tool_result") {
|
||||
@@ -636,7 +644,6 @@ function getReplayMessages(
|
||||
const visibleMessages = isActiveRunStatus(runStatus)
|
||||
? messages.filter((message) => !isRunStatusMessage(message))
|
||||
: [...messages];
|
||||
if (isActiveRunStatus(runStatus)) return visibleMessages;
|
||||
return trimIncompleteReplayTail(visibleMessages);
|
||||
}
|
||||
|
||||
|
||||
@@ -1601,6 +1601,17 @@ function formatActivityDuration(ms: number) {
|
||||
|
||||
function compactActivityLabel(label: string, maxLength = 15) {
|
||||
const normalized = label.replace(/\s+/g, " ").trim();
|
||||
const progress = normalized.match(
|
||||
/^进度[::]\s*批\s*(\d+)\s*\/\s*(\d+)(?:[,,]\s*已生成\s*(\d+)\s*\/\s*(\d+))?/,
|
||||
);
|
||||
if (progress) {
|
||||
const batch = `批${progress[1]}/${progress[2]}`;
|
||||
const count =
|
||||
progress[3] && progress[4] ? ` 已${progress[3]}/${progress[4]}` : "";
|
||||
const compactProgress = `${batch}${count}`;
|
||||
if (compactProgress.length <= maxLength) return compactProgress;
|
||||
return batch;
|
||||
}
|
||||
if (normalized.length <= maxLength) return normalized;
|
||||
return `${normalized.slice(0, Math.max(1, maxLength - 1))}…`;
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ write_env_file() {
|
||||
export OPENAI_API_KEY=$(printf "%q" "${api_key}")
|
||||
export OPENAI_BASE_URL=$(printf "%q" "${base_url}")
|
||||
export OPENAI_MODEL=$(printf "%q" "${model}")
|
||||
export OPENAI_TIMEOUT_SECONDS="${OPENAI_TIMEOUT_SECONDS:-3600}"
|
||||
|
||||
export CLAW_DEPLOY_INSTANCE=$(printf "%q" "${DEPLOY_INSTANCE}")
|
||||
export CLAW_BACKEND_HOST=$(printf "%q" "${backend_host}")
|
||||
@@ -184,6 +185,7 @@ ensure_env_file() {
|
||||
echo "CLAW_DEPLOY_INSTANCE=${DEPLOY_INSTANCE}"
|
||||
echo "OPENAI_BASE_URL=${OPENAI_BASE_URL:-}"
|
||||
echo "OPENAI_MODEL=${OPENAI_MODEL:-}"
|
||||
echo "OPENAI_TIMEOUT_SECONDS=${OPENAI_TIMEOUT_SECONDS:-3600}"
|
||||
echo "OPENAI_API_KEY=已配置"
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -19,6 +19,7 @@ fi
|
||||
export OPENAI_API_KEY
|
||||
export OPENAI_BASE_URL="${OPENAI_BASE_URL:-http://model.mify.ai.srv/v1}"
|
||||
export OPENAI_MODEL="${OPENAI_MODEL:-xiaomi/mimo-v2-flash}"
|
||||
export OPENAI_TIMEOUT_SECONDS="${OPENAI_TIMEOUT_SECONDS:-3600}"
|
||||
|
||||
# 后端也会调用 Node 生态工具,例如 feishu-mcp-pro。systemd 不读取交互式
|
||||
# shell 的 nvm 配置,所以需要把部署时记录的 Node 路径显式传给 Python 进程。
|
||||
@@ -38,6 +39,7 @@ exec "${PYTHON_BIN}" -m src.gui \
|
||||
--host "${BACKEND_HOST}" \
|
||||
--port "${BACKEND_PORT}" \
|
||||
--cwd "${ROOT_DIR}" \
|
||||
--timeout-seconds "${OPENAI_TIMEOUT_SECONDS}" \
|
||||
--session-dir "${ROOT_DIR}/.port_sessions/agent" \
|
||||
--allow-write \
|
||||
--allow-shell \
|
||||
|
||||
@@ -141,6 +141,7 @@ fi
|
||||
export OPENAI_API_KEY="${OPENAI_API_KEY:-}"
|
||||
export OPENAI_BASE_URL="${OPENAI_BASE_URL:-http://model.mify.ai.srv/v1}"
|
||||
export OPENAI_MODEL="${OPENAI_MODEL:-xiaomi/mimo-v2-flash}"
|
||||
export OPENAI_TIMEOUT_SECONDS="${OPENAI_TIMEOUT_SECONDS:-3600}"
|
||||
|
||||
BACKEND_LOG="${RUN_DIR}/webui-backend.log"
|
||||
FRONTEND_LOG="${RUN_DIR}/webui-frontend.log"
|
||||
@@ -151,6 +152,7 @@ start_detached_process "backend" "${RUN_DIR}/webui-backend.pid" "${BACKEND_LOG}"
|
||||
--host "${BACKEND_HOST}" \
|
||||
--port "${BACKEND_PORT}" \
|
||||
--cwd "${ROOT_DIR}" \
|
||||
--timeout-seconds "${OPENAI_TIMEOUT_SECONDS}" \
|
||||
--session-dir "${RUN_DIR}/agent" \
|
||||
--allow-write \
|
||||
--allow-shell \
|
||||
|
||||
@@ -66,4 +66,10 @@ cat input.json | python skills/product-data/scripts/<tool>.py
|
||||
|
||||
最后从 `scratchpad/normalized_records.jsonl` 统一校验和导出。
|
||||
|
||||
为避免长时间连续生成导致模型请求超时,单轮最多连续生成 3 批;超过 24 条时先阶段性停顿,用户回复“继续生成”后先统计 `scratchpad/normalized_records.jsonl` 已有行数,再从下一批 append。
|
||||
用户确认计划后,生成任务应在同一轮内尽量跑完整个批次链路;不要因为超过 24 条就主动停顿等待继续。每批工具调用前用固定格式输出进度,方便 Web 摘要行展示:
|
||||
|
||||
```text
|
||||
进度:批 i/n,已生成 x/y,主题
|
||||
```
|
||||
|
||||
如果确实因为超时、取消或工具错误中断,用户回复继续后先统计 `scratchpad/normalized_records.jsonl` 已有行数,再从下一批 append。
|
||||
|
||||
@@ -171,7 +171,7 @@ review 展示必须简短清晰,不要重复解释工具和流程。每次 rev
|
||||
11. 分批生成 dataset draft text v1,每批最多 8 条,不要用 `write_file` 保存 `draft_part*.txt` 这类中间草稿。
|
||||
12. 如果是继续一个已确认但中断/超时的生成任务,先检查 `scratchpad/normalized_records.jsonl` 是否已存在;存在时先用 `python_exec` 统计已有记录数,然后从下一批继续追加,禁止覆盖已有记录。
|
||||
13. 每生成一批,立刻使用 `python_exec` 的 `script_path` 模式执行 `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`。
|
||||
14. 单轮最多连续生成并 normalize 3 批;如果计划还没完成,先停止并汇报“已生成 X/Y 条,回复继续生成可从下一批继续”,不要在同一轮里继续生成第 4 批。
|
||||
14. 批次数超过 1 时,每批工具调用前必须用一句阶段说明标记进度,格式固定为:`进度:批 i/n,已生成 x/y,主题`。例如:`进度:批 3/7,已生成 16/50,路线偏好追加`。摘要行会优先展示这个格式。
|
||||
15. 所有批次 normalize 完成后,使用 `python_exec` 执行 `skills/product-data/scripts/validate_dataset_records.py`,传 `records_path="scratchpad/normalized_records.jsonl"`。
|
||||
16. 如果用户要求落盘 canonical records,使用 `python_exec` 执行 `skills/product-data/scripts/export_dataset_records.py`,逻辑输出固定为 `output/records.jsonl`,默认导出紧凑 JSONL,并同时生成同目录 `records.csv` 表格,不要用 `write_file` 手写 JSON 或 CSV。
|
||||
17. 如果用户已经明确要训练数据,使用 `python_exec` 执行 `skills/product-data/scripts/export_training_jsonl.py`,优先传 `records_path`,输出固定为 `output/training.jsonl`。
|
||||
@@ -181,7 +181,8 @@ review 展示必须简短清晰,不要重复解释工具和流程。每次 rev
|
||||
生成阶段要避免一次性把大量数据塞进工具参数:
|
||||
|
||||
- 单次 `normalize_dataset_draft.py` 最多处理 8 条 case;计划数量更多时,分批生成、分批 normalize 到同一个 `scratchpad/normalized_records.jsonl`,再统一校验和导出。
|
||||
- 单轮最多推进 3 批,超过 24 条的数据要做阶段性停顿,等待用户回复“继续生成”再继续;这是为了避免模型长时间连续生成导致请求超时。
|
||||
- 不要因为批次数多就中途停止等待用户继续;用户确认计划后,除非遇到工具错误、模型错误、用户取消或标签/边界歧义,否则在同一轮里把所有批次生成、校验和导出跑完。
|
||||
- 每个批次工具调用前的自然语言进度必须使用 `进度:批 i/n,已生成 x/y,主题`,让 Web 摘要行能展示当前进度。
|
||||
- 继续生成前必须先统计 `scratchpad/normalized_records.jsonl` 的现有行数;如果已有记录,后续 normalize 必须 `append=true`,不要重新从第 1 批覆盖。
|
||||
- dataset draft 中的 Agent target 推荐写成单引号形式,例如 `target: Agent(tag='地图导航')`。工具会规范化为 `Agent(tag="地图导航")`,这样可以降低 tool call JSON 里双引号转义失败的概率。
|
||||
- `complex:` 独立写一行,不要写进 `target:`;工具会在导出训练数据和流转表格时自动组合成 `complex=false\nAgent(...)`。
|
||||
|
||||
@@ -38,6 +38,12 @@ def main() -> None:
|
||||
'--api-key',
|
||||
default=os.environ.get('OPENAI_API_KEY', ''),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--timeout-seconds',
|
||||
type=float,
|
||||
default=float(os.environ.get('OPENAI_TIMEOUT_SECONDS', '3600')),
|
||||
help='Timeout for each model API request in seconds.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--session-dir',
|
||||
default=str(DEFAULT_AGENT_SESSION_DIR),
|
||||
@@ -58,6 +64,7 @@ def main() -> None:
|
||||
model=args.model,
|
||||
base_url=args.base_url,
|
||||
api_key=args.api_key,
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
allow_shell=args.allow_shell,
|
||||
allow_write=args.allow_write,
|
||||
session_directory=Path(args.session_dir),
|
||||
@@ -69,6 +76,7 @@ def main() -> None:
|
||||
print(f' cwd : {state.cwd}')
|
||||
print(f' model : {state.model}')
|
||||
print(f' base-url : {state.base_url}')
|
||||
print(f' timeout : {args.timeout_seconds:g}s')
|
||||
print(f' sessions : {state.session_directory}')
|
||||
print(f' shell : {"on" if state.allow_shell else "off"}')
|
||||
print(f' write : {"on" if state.allow_write else "off"}')
|
||||
|
||||
@@ -20,6 +20,7 @@ def _build_client(tmp: Path) -> tuple[TestClient, AgentState]:
|
||||
model='test-model',
|
||||
base_url='http://127.0.0.1:8000/v1',
|
||||
api_key='local-token',
|
||||
timeout_seconds=120.0,
|
||||
allow_shell=False,
|
||||
allow_write=False,
|
||||
session_directory=tmp / 'sessions',
|
||||
|
||||
@@ -37,6 +37,7 @@ def _build_client(tmp: Path) -> tuple[TestClient, AgentState]:
|
||||
model='test-model',
|
||||
base_url='http://127.0.0.1:8000/v1',
|
||||
api_key='local-token',
|
||||
timeout_seconds=120.0,
|
||||
allow_shell=False,
|
||||
allow_write=False,
|
||||
session_directory=tmp / 'sessions',
|
||||
|
||||
@@ -115,6 +115,7 @@ class ModelListTests(unittest.TestCase):
|
||||
model='xiaomi/mimo-v2-flash',
|
||||
base_url='http://model.example/v1',
|
||||
api_key='token',
|
||||
timeout_seconds=120.0,
|
||||
allow_shell=False,
|
||||
allow_write=False,
|
||||
session_directory=Path(tmp_dir) / 'sessions',
|
||||
|
||||
Reference in New Issue
Block a user