diff --git a/PROGRESS.md b/PROGRESS.md index 97e8ebb..9344685 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -21,6 +21,23 @@ ## 已完成关键能力 +### 2026-06-15 / system prompt 按 backend 注入「运行环境」段:纠正平台误报 + 写明禁外网 + +- 接上两条(--shm-size + mmdc wrapper 修执行层)。再查发现**引导层的根问题在 system prompt**:`general_v1.md` 的「平台」段写死 "Windows + cmd.exe",但线上是 **docker = Ubuntu 容器 + bash** ── 模型被误导在 Linux 里打 cmd 构文(`where mmdc 2>nul`),且没引导"渲图走本地",模型以为 mermaid.ink 等在线服务能用、反复去试(其实**境外被墙**,容器有外网但渲图不该依赖出站)白烧 token。 +- 修法(引导层,环境事实归 system 而非 skill): + - `general_v1.md`:删写死的 Windows 平台段,改为中立一句"平台以系统消息「运行环境」段为准"。 + - `agent_builder.py`:`_build_system_prompt` 按 backend 注入环境段 ── **docker** = `_CONTAINER_ENV_BLOCK`(Linux/Ubuntu·bash·**渲图走本地 mmdc 别调境外在线服务**·mmdc/chromium/中文字体已装·`mmdc -i x -o y` 直接渲图·/tmp 可写);**host** = `_HOST_ENV_BLOCK`(一行 Windows/cmd 提示,免 general_v1 指向落空)。 + - 撤回上一条加到 imagegen skill 的渲图引导(环境事实收归 system,不重复)。 +- 原则沉淀:**全局不变的环境事实(在哪/能否联网/装了啥)→ system(高杠杆,一句省一类试错);具体可选方法/流程 → skill**。这是"换"不是"加" ── 删掉的是每轮都发且 docker 下错误的 Windows 段,token 量级相当、信息变对。改动文件:`prompts/system/general_v1.md`、`core/agent_builder.py`、`skills/imagegen/SKILL.md`、`RUN.md`。bump 0.12.6 → 0.12.7。 + +### 2026-06-14 / mmdc wrapper:容器内裸调 mmdc 自动带 puppeteer config,渲图开箱即用 + +- 接上条 `--shm-size` 修复。`--shm-size` 只填了"模型自己摸对 config 后那一下能成";模型**初始裸调 `mmdc`** 仍因 chromium 缺 `--no-sandbox`(容器 `--cap-drop=ALL`)直接跪,然后反复试 `mermaid.ink` 等在线服务 ── 但那是**境外、被墙/不稳**(容器虽有外网,渲图也不该依赖出站),实测又一条对话这么烧掉上百 k token。 +- 修法(执行层 + 引导层,均不破坏对外契约): + - **执行层 wrapper**:Dockerfile 给 `/usr/local/bin/mmdc` 套 wrapper,没显式 `-p` 时自动注入 `-p /sandbox/puppeteer-config.json`(含 `--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage`)。裸调 `mmdc -i x.md -o x.png` 一次成;`render_diagrams.py` 等走 `which mmdc` 的脚本透明受益。删掉没人读的 `MERMAID_PUPPETEER_CONFIG` env(mmdc 本就不认它,只认 `-p`)。 + - **引导层**:imagegen skill「mermaid vs seedream」段加硬引导 ── 渲图直接 `mmdc -i x -o y`、⛔ 容器禁外网别试 mermaid.ink 等在线 API。 +- 取舍:没开 first-class `render_mermaid` tool ── mermaid 是纯本地计算,zcbot 专用 tool 只留给带 key/计费的能力(seedream/seedance);wrapper(执行兜底)+ skill 一句(affordance 引导)已覆盖,不扩工具面。**需 rebuild 镜像**才带 wrapper(旧容器没有)。改动文件:`deploy/sandbox/Dockerfile`、`skills/imagegen/SKILL.md`、`RUN.md`。bump 0.12.5 → 0.12.6。 + ### 2026-06-14 / sandbox 容器加 `--shm-size`:修 mmdc 渲 mermaid 挂超时 - 实测一个"生图测试"任务(`caoqianming@foxmail.com`)对话:模型裸调 `mmdc` 渲 mermaid,自造的 puppeteer config 漏了 `--disable-dev-shm-usage`,chromium 用 64MB 的 `/dev/shm` 起不来 → 连试 6 次全超时,烧约 120k token 才绕道 mermaid.ink 出了个 SVG。根因:`pool.py` 的 `docker run` 没传 `--shm-size`,容器 `/dev/shm` = docker 默认 64MB(镜像备的 `/sandbox/puppeteer-config.json` 虽有 `--disable-dev-shm-usage`,但模型不一定用那份;且 `mmdc` 不读 `MERMAID_PUPPETEER_CONFIG` env)。 diff --git a/RUN.md b/RUN.md index 2a78260..40a4bc3 100644 --- a/RUN.md +++ b/RUN.md @@ -366,13 +366,16 @@ sudo -u zcbot docker build \ # npm 源同款(@mermaid-js/mermaid-cli + 依赖,境内访问 registry.npmjs.org 也慢): # --build-arg NPM_REGISTRY=https://mirrors.cloud.tencent.com/npm/ # 腾讯云 # --build-arg NPM_REGISTRY=https://registry.npmmirror.com/ # 阿里(npmmirror) -# 镜像内自带 Chromium + mermaid-cli + puppeteer-config.json,proposal/patent skill -# 的 render_diagrams.py 看到 MERMAID_PUPPETEER_CONFIG env 自动 -p 注入, -# host 上跑时该 env 没设,行为不变 +# 镜像内自带 Chromium + mermaid-cli + puppeteer-config.json;mmdc 被 wrapper 包了一层 +# (/usr/local/bin/mmdc → 自动注入 -p /sandbox/puppeteer-config.json,除非显式传 -p), +# 所以容器内**裸调 `mmdc -i x.md -o x.png` 就能出图**,无需 --no-sandbox / 自写配置。 +# render_diagrams.py 等走 `which mmdc` 的脚本透明受益(原靠 MERMAID_PUPPETEER_CONFIG +# env,已删 ── mmdc 本就不读它,改 wrapper 兜底)。host 上跑无 wrapper,行为不变 -# 3) 创建 sandbox 网络(--internal,默认无 outbound) -sudo -u zcbot docker network create --internal zcbot-sandbox-net -# 或 SandboxPool.setup_pool() 自动 ensure +# 3) 创建 sandbox 网络(bridge,dogfood 阶段保留 outbound NAT —— 让模型能 pip/curl 公网; +# iptables 仍挡内网/cloud metadata/PG。--internal 完全禁出站是外部用户开放时才改,见 §7.5 #2) +sudo -u zcbot docker network create zcbot-sandbox-net +# 或 SandboxPool.setup_pool() 自动 ensure(ensure_network 即建非 internal bridge) ``` ### Sandbox 相关 env(.env 加) @@ -694,8 +697,9 @@ sudo xfs_quota -x -c "limit -p bhard=10g zcbot_" /opt | 镜像 build npm 装 mermaid-cli 慢 / fail | npm 源境内慢。默认已用腾讯 `https://mirrors.cloud.tencent.com/npm/`(清华无 npm 源;npmmirror 访问不稳被弃)。备选:华为 `https://repo.huaweicloud.com/repository/npm/` / USTC `https://npmreg.mirrors.ustc.edu.cn/`,手动 build 加 `--build-arg NPM_REGISTRY=...` | | 镜像 build apt 报 `OpenSSL error: ... unexpected eof while reading` | 某些 mirror HTTPS 端偶发 close_notify 缺失,OpenSSL 3 严格 fail(腾讯 / 阿里见过;清华一般不犯)。改用 http 形式:`--build-arg APT_MIRROR=http://mirrors.tuna.tsinghua.edu.cn`(apt 包 GPG 签名校验,无 HTTPS 安全收益)。Dockerfile 已配 apt retry=5 + 关 pipeline,重 build 一般直接过 | | 容器内 shell 写工作目录报 `Permission denied`(but `sandbox check` ⑤ HOST_UID aligned ok) | DockerExecutor 写死了 `--user 1000:1000` 不会自动跟 build 的 HOST_UID 同步(改 `--user zcbot` 后已修)。仍报错检查镜像内 `docker run --rm --entrypoint id zcbot-sandbox:latest zcbot` 输出 uid 是否 = `id -u $(whoami)` | -| 模型用 run_python 跑 `render_diagrams.py` 报 `mmdc returncode=1: Failed to launch chromium` | 容器内 chromium 缺 puppeteer no-sandbox 配置。镜像已落 `/sandbox/puppeteer-config.json` + ENV `MERMAID_PUPPETEER_CONFIG`,render_diagrams.py 已读 env 自动 -p 注入;仍跪查 `docker exec ... env \| grep MERMAID` 看 env 是否在 | -| 模型裸调 `mmdc -i x.md -o x.png`(自造 puppeteer config / 不走 render_diagrams.py)卡到 **timeout** 而非报错 | chromium 默认用 `/dev/shm`,docker 不传 `--shm-size` 时只 64MB → 起不来一直挂。已在 `pool.py` 给 `docker run` 加 `--shm-size`(默 512m,env `ZCBOT_SANDBOX_SHM_SIZE` / yaml `sandbox.shm_size`)。**已 running 的旧容器不会自动生效**,重启 web + 等 idle 回收(或 `docker rm -f zcbot-sandbox-`)后新起的才带。注:`mmdc` 自身不读 `MERMAID_PUPPETEER_CONFIG` env(只认 `-p`),裸调要么靠 shm-size 兜底要么显式 `-p /sandbox/puppeteer-config.json`。实测脚本 `deploy/sandbox/probe_mermaid.sh` | +| 容器内 mmdc 渲 mermaid 报 `Failed to launch chromium` / `No usable sandbox` | chromium 在 `--cap-drop=ALL` 下自家 setuid sandbox 起不来,要 `--no-sandbox`。镜像已落 `/sandbox/puppeteer-config.json` + 给 mmdc 套了 wrapper 自动 `-p` 注入 ── **裸调 `mmdc -i x -o y` 就该成**。仍跪:`docker exec ... cat /usr/local/bin/mmdc` 看 wrapper 在不在(老镜像没 rebuild 则没有);或显式 `mmdc -p /sandbox/puppeteer-config.json -i x -o y` | +| 容器内 mmdc 渲图卡到 **timeout** 而非报错 | chromium 默认用 `/dev/shm`,docker 不传 `--shm-size` 时只 64MB → 起不来一直挂。已在 `pool.py` 给 `docker run` 加 `--shm-size`(默 512m,env `ZCBOT_SANDBOX_SHM_SIZE` / yaml `sandbox.shm_size`)。**已 running 的旧容器不会自动生效**,重启 web + 等 idle 回收(或 `docker rm -f zcbot-sandbox-`)后新起的才带。实测脚本 `deploy/sandbox/probe_mermaid.sh` | +| 模型不渲本地 mmdc、反复试 `mermaid.ink` 等在线渲图服务还失败 | 容器**有外网**(bridge+NAT),但 `mermaid.ink` 等**境外服务易被墙/不稳**,渲图不该依赖出站。docker backend 的 system prompt「运行环境」段(`agent_builder.py` 注入)已写明"渲图走本地 mmdc、别调在线服务";撞到多半是 prompt 没更新 / 跑在 host backend。渲 mermaid 一律 `mmdc -i x.md -o x.png` | | Export 报 "无可导出内容" | task 没 messages(只 system 不算);先发条消息再 export | | `NoSubtaskError: working_dir ... 前缀嵌套` | §7.4 no-subtask:同 user 不允许 working_dir 嵌套(child / parent)。**同项目多对话**用**完全相同**的 working_dir;否则改成 sibling(平级) | | `main.py web` 启动后 curl 连不上 | 检查 proxy(`HTTP_PROXY` / `HTTPS_PROXY`):本地服务 127.0.0.1,系统 proxy 拦截会 502。临时 `unset HTTP_PROXY HTTPS_PROXY` 或 `curl --noproxy '*'`。验通:`curl --noproxy '*' http://127.0.0.1:8765/healthz` | diff --git a/core/__init__.py b/core/__init__.py index b866157..3329927 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.12.5" +__version__ = "0.12.7" diff --git a/core/agent_builder.py b/core/agent_builder.py index 8efdd47..2623429 100644 --- a/core/agent_builder.py +++ b/core/agent_builder.py @@ -73,6 +73,23 @@ _MEDIA_TOOLS_BLOCK = """\ - 兜底硬约束:用户没主动要视频就别装饰性生成(比生图更严重的红线);同一目的不满意**绝不连发**(1 次错 = ¥4+60s,连发 2 次 = ¥8+2min);phase 1 仅文生视频,**不支持** image-to-video / video-to-video。""" +# 运行环境段(按 backend 注入,general_v1.md 的「平台」段指向这里)。环境事实(在哪 / +# 能否联网 / 装了啥)是全局不变量,放 system 比塞进某个 skill 高杠杆 —— 一句话省掉一整类 +# 试错(外网试错 / 平台命令试错)。docker = 线上真实形态(Ubuntu 容器,无外网);host = +# 本地 dogfood(Windows),给一行最小提示免 general_v1 里那句指向落空。 +_CONTAINER_ENV_BLOCK = """\ + +## 运行环境(容器) +你的 `shell` / `run_python` / 文件工具都在一个 **Linux(Ubuntu)容器**里执行 —— 是 **bash 不是 cmd**:unix 命令 / 管道 / 重定向正常用,`mkdir -p`、`&&`、`2>&1` 都行。 +- **渲 mermaid 图一律走本地 `mmdc`**:`mmdc -i 图.md -o 图.png`(要矢量就 `-o 图.svg`;chromium 已配好,**不用加 `--no-sandbox`、不用自己写 puppeteer 配置**)。⛔ **别去调 `mermaid.ink` 等在线渲图服务** —— 境外、易被墙 / 不稳,实测有对话在上面反复试编码、改压缩,白烧上百 k token;本地 mmdc 一条命令就出图。 +- 中文字体已装(matplotlib / mermaid 出图不乱码);常用 Python 库已预装;`/tmp` 可写、其余 rootfs 只读。""" + +_HOST_ENV_BLOCK = """\ + +## 运行环境(本地 host) +`shell` 走的是 **Windows cmd.exe**(非 bash):避免 unix-only flag,`mkdir -p` 不识别 → 用 `run_python` 的 `os.makedirs(..., exist_ok=True)` 建目录;复杂管道/重定向用 run_python 更稳。""" + + def load_config() -> dict: return yaml.safe_load((ROOT / "config" / "agent.yaml").read_text(encoding="utf-8")) or {} @@ -250,14 +267,16 @@ def _build_system_prompt( today 当场算,落 prompt 给 LLM 直接拼路径(避免 LLM 不知道当前日期)。 """ prompt = (ROOT / cfg["system_prompt"]).read_text(encoding="utf-8") - if skills.skills: - prompt += f"\n\n## 可用 skill (用 load_skill 加载完整指引)\n{skills.discovery_block()}" # docker backend 下 shell/run_python/fs 工具全在容器里跑,容器把 # `/users/` bind 到 `/workspace`、`--workdir /workspace/` # (executor_docker.py:99-100)。此时 prompt 给 agent 的所有可写/可读绝对路径 # (含 .memory/ 写入锚点)都必须是**容器路径**,否则 LLM 拿着宿主绝对路径在沙盒里 # find 不到任何东西。host backend 不变,直接用宿主绝对路径。 is_docker = os.getenv("ZCBOT_SANDBOX_BACKEND", "host").lower() == "docker" + # 运行环境段紧跟模板(平台/网络是基础事实,放前面);general_v1 的「平台」段指向这里。 + prompt += _CONTAINER_ENV_BLOCK if is_docker else _HOST_ENV_BLOCK + if skills.skills: + prompt += f"\n\n## 可用 skill (用 load_skill 加载完整指引)\n{skills.discovery_block()}" # .memory/ 在 agent 视角下的可写路径:docker 给容器路径,host 给宿主绝对路径。 mem_dir_display = "/workspace/.memory" if is_docker else str( user_root(workspace_dir, user_id) / ".memory" diff --git a/deploy/sandbox/Dockerfile b/deploy/sandbox/Dockerfile index 36cf068..f89fba6 100644 --- a/deploy/sandbox/Dockerfile +++ b/deploy/sandbox/Dockerfile @@ -95,7 +95,6 @@ ARG NPM_REGISTRY=https://registry.npmjs.org/ # Puppeteer 用容器内已装的 chromium 而非自带下载(省 ~300MB + 避免下载失败) ENV PUPPETEER_SKIP_DOWNLOAD=true ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium -ENV MERMAID_PUPPETEER_CONFIG=/sandbox/puppeteer-config.json RUN npm config set registry ${NPM_REGISTRY} \ && npm install -g @mermaid-js/mermaid-cli@latest \ @@ -115,6 +114,27 @@ RUN mkdir -p /sandbox && cat > /sandbox/puppeteer-config.json <<'EOF' } EOF +# mmdc wrapper ── 容器 --cap-drop=ALL 下 chromium 自家 setuid sandbox 起不来,必须 +# --no-sandbox(+ --disable-dev-shm-usage),这些在上面的 /sandbox/puppeteer-config.json 里。 +# 但 mmdc **不读任何 env 自动加载**该 config(只认 -p/--puppeteerConfigFile);模型裸调 +# `mmdc -i x.md -o x.png` 会因缺 --no-sandbox 直接跪 → 然后反复试 mermaid.ink 等在线 API +# (容器 internal network 禁外网,死路),实测一条对话这么烧掉 ~120k token。wrapper 在调用方 +# 没显式传 -p 时自动注入这份 config,让裸调一次成;已显式 -p 则尊重不覆盖。proposal 的 +# render_diagrams.py 等走 `which mmdc` 的脚本同样透明受益(原靠 MERMAID_PUPPETEER_CONFIG +# env,已删 ── wrapper 兜底,不再依赖那个谁都不读的 env)。 +RUN mv /usr/local/bin/mmdc /usr/local/bin/mmdc.real \ + && cat > /usr/local/bin/mmdc <<'EOF' +#!/bin/sh +for a in "$@"; do + case "$a" in + -p|--puppeteerConfigFile|--puppeteerConfigFile=*) + exec /usr/local/bin/mmdc.real "$@" ;; + esac +done +exec /usr/local/bin/mmdc.real -p /sandbox/puppeteer-config.json "$@" +EOF +RUN chmod +x /usr/local/bin/mmdc + # fs 工具进容器(§7.5 #6,2026-05-26 修正)── tool_runner.py 在容器内通过 # `python /sandbox/tool_runner.py ` 调用 tools/fs.py 的 Tool 子类,read/write/ # edit/glob/grep 全在容器内执行,物理边界替代代码护栏。tools/ 目录与 host 同步 diff --git a/prompts/system/general_v1.md b/prompts/system/general_v1.md index c8ff635..3d750a7 100644 --- a/prompts/system/general_v1.md +++ b/prompts/system/general_v1.md @@ -40,7 +40,5 @@ **对外 echo 产物路径(回复 / 汇报用)一律用全形式 `/`** —— `` = 上方 task_dir 末段(如末段是 `生图测试` → `生图测试/figures/cover.png`、`基金申报/sections/01-绪论.md`)。**别简写**成 `figures/cover.png` 这种 task 内裸形式:Web UI 靠 `/` 前缀挂可点 chip(预览 / 下载),简写会失效。媒体 tool 的 `saved:` 行已是规范全形式,原样照抄即可。 ## 平台 -当前是 Windows + cmd.exe。**避免用 unix-only flag**: -- 建目录用 `run_python` 的 `os.makedirs(path, exist_ok=True)`,**不要** `shell mkdir -p`(cmd 不识别 -p,会创建名为 '-p' 的字面目录;shell 工具已对此做兜底但仍以 run_python 为优先) -- 路径分隔符用 `/` 或 `\\`,Python 内部都识别;字符串 raw 路径用 `r"..."` -- shell 工具走的是 cmd,不是 bash,管道/重定向语义可能不同 —— 复杂逻辑用 run_python 更稳 +运行平台(Linux 容器 / Windows host)由系统消息里的「运行环境」段说明,以那段为准。 +通用习惯:建目录优先 `run_python` 的 `os.makedirs(path, exist_ok=True)`;路径分隔符用 `/` 最稳;复杂 shell 逻辑(管道/重定向)拿不准就用 run_python。