diff --git a/PROGRESS.md b/PROGRESS.md index d030234..690b945 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -23,6 +23,7 @@ ### 2026-06-10 +- **system prompt 精简(瘦身 ~40 行 + 媒体段按需注入)**:`general_v1.md` + `_build_system_prompt` 去冗余:① 「宪法」文件命名约定从 ~25 行压到 ~6 行(只留格式定义 + 注入值 + 一行 current/重定调,操作细节本就由 proposal/ppt skill 各自讲,引用仍成立);② run_python「先 write script 再 script_path」指引去重(原模板 + agent_builder 两处 → 合并进模板 1 处,顺带把 `scripts/` 子目录约定收进去);③ 媒体工具段(seedream/seedance 红线)从常驻模板抽成 `_MEDIA_TOOLS_BLOCK`,仅 `ArkConfig.load() is not None`(有 ARK_API_KEY)时由 agent_builder 追加——无 key 用户不再背 7 行永远报错工具的说明,且 ark_cfg 提前 load 一次复用给下方 tool 注册;④ 「路径 echo 全形式」段 8 行压到 4 行。通用任务每轮 system prompt 净瘦 ~40-50 行,领域 task 加载 skill 后信息不丢。`test_system_prompt_paths` 仍过。 - **上下文压缩加压力门槛**:压缩只在总 chars 超阈值(`caps.reliable_context×0.5×2.5 char/token`,flash ≈33 万)时才做,未超则原样发——护 DeepSeek 前缀缓存(短任务字节逐轮一致、全程命中)+ 不白丢旧细节。`prepare_messages_with_stats(compact_threshold_chars=)`,`compaction_skipped` 进事件;默认 0=向后兼容永远压。实测高轮 task 缓存命中已 92-94%,故只补门槛不改滑动边界。+2 测试。 - **单轮停机判据从「步数」解耦为「是否在推进」**:`max_iterations` 从「轮预算」降级为纯安全 backstop(flash 50→120 / pro 100→150),真正掐空转靠两道进展信号——`_RepeatGuard` 逐指纹「无产出重复」累计(SOFT2 注提示 / HARD4 拦截)+ run 级全局 `_stall`(整步所有 tool 无净产出 +1、任一净产出清零,连续 8 步主动停)。撞 backstop / 熔断都 emit「回复『继续』可续跑」提示,不静默停。(诊断:task `b27466a0` 所谓「中途断」实为撞旧 50 步上限干净停下。) - **`systemctl restart` 优雅 drain in-flight run**:restart 不再硬杀 BG run 致 reaper 误标 error。纯进程内零 DB 改动:lifespan 加 `draining` + `inflight` 登记,先拒新 run(503+Retry-After)再 `asyncio.wait(drain_timeout)` 收尾,超时转协作式 cancel。部署强耦合:unit `TimeoutStopSec` 提到 90(必须 > drain+grace),前端发送包退避重试。 diff --git a/core/agent_builder.py b/core/agent_builder.py index be12092..a8b16a5 100644 --- a/core/agent_builder.py +++ b/core/agent_builder.py @@ -58,6 +58,20 @@ from core.ark_client import ArkConfig from core.bocha_client import BochaConfig +# 媒体工具(seedream / seedance)指引:仅当本 run 真的挂了媒体工具(ARK_API_KEY 存在, +# ArkConfig.load() 非 None)才追加进 system prompt —— 没 key 的用户不会看到永远报错的工具, +# 也不该背这段红线。文案与 base 模板里其余工具表平级,放在 _build_system_prompt 里按需拼。 +_MEDIA_TOOLS_BLOCK = """\ + +## 媒体生成工具(seedream 图 / seedance 视频) +- `seedream` —— 豆包图像生成。产物自动落 `/figures/`。每次 **¥0.22**(联网 `search=true` 加 ¥0.05)。 + - **调用前必须先 `load_skill('imagegen')`** —— skill 里有「何时该用 / 该不该用 mermaid 替代 / 用户描述模糊度诊断 / 一次性追问范式 / prompt 装配 / 失败解药」全套引导。**不要拿用户原话直接当 prompt 调 tool** —— 容易烧 ¥0.22 在错的方向上。 + - 兜底硬约束(即使没 load skill 也守):用户没主动要图就别装饰性生成;同一目的不满意**不要连发**,先口头校准 prompt 再调。 +- `seedance` —— 豆包视频生成(Seedance 2.0 Fast)。异步任务,**等 30-90s 出片**;产物自动落 `/videos/`。每次 **¥1.86 起**(480p 4s)~ **¥12+**(720p 15s),比图贵 10 倍以上。触发词:视频 / 动画 / 动起来 / 做个 video / 镜头 / 短片 / 演示视频 / 动效。 + - **调用前必须先 `load_skill('videogen')`** —— skill 里有「6 维诊断(含运动维必填)/ seedream/mermaid 反向选型 / prompt 装配 / 参数取舍(时长/分辨率/比例直接决定钱)/ 失败解药」全套引导。视频比图贵 10 倍且 90s 等待,绝对不要拿用户原话当 prompt 直接调。 + - 兜底硬约束:用户没主动要视频就别装饰性生成(比生图更严重的红线);同一目的不满意**绝不连发**(1 次错 = ¥4+60s,连发 2 次 = ¥8+2min);phase 1 仅文生视频,**不支持** image-to-video / video-to-video。""" + + def load_config() -> dict: return yaml.safe_load((ROOT / "config" / "agent.yaml").read_text(encoding="utf-8")) or {} @@ -199,6 +213,7 @@ def _build_system_prompt( task_id: UUID, task_name: str, task_skill: str = "", + media_enabled: bool = False, ) -> str: """拼 system prompt: 模板 + skill 列表 + memory + 工作目录段 + task 上下文 + 命名约定。 @@ -213,6 +228,8 @@ def _build_system_prompt( if skills.skills: prompt += f"\n\n## 可用 skill (用 load_skill 加载完整指引)\n{skills.discovery_block()}" prompt += memory_block(workspace_dir, user_id) + if media_enabled: + prompt += "\n\n" + _MEDIA_TOOLS_BLOCK # docker backend 下 shell/run_python/fs 工具全在容器里跑,容器把 # `/users/` bind 到 `/workspace`、`--workdir /workspace/` # (executor_docker.py:99-100)。此时 prompt 必须给**容器路径**,否则 LLM @@ -260,33 +277,16 @@ def _build_system_prompt( f"普通产物(sections / slides / 终稿 .docx/.pptx)按 SKILL 文档落路径;" f"「宪法」性文件(spec 等)按下面《task 级「宪法」文件命名约定》拼路径。\n" f"⛔ 不要把产物写到 cwd / `skills/` / repo 根 —— 只写到 task_dir。\n" - f"\n**run_python 过程脚本**:非平凡的 Python(>~15 行 / 要迭代调试 / 生成产物)" - f"先用写文件工具落到 `/scripts/`(如 `scripts/analyze.py`,父目录自动建)," - f"再用 `run_python(script_path=\"scripts/analyze.py\")` 执行 —— 源码留在文件里、可重读可改可重跑," - f"不挤占对话上下文。`scripts/` 只放过程脚本,**交付产物(.docx/.pptx/spec/figures 等)仍落 task_dir 根或 SKILL 指定路径**。" - f"真·一次性短代码(算个数 / 探查一行)才用 `run_python(code=...)` 内联,临时执行不留痕。\n" f"\n## task 级「宪法」文件命名约定(跨 skill 通用)\n" - f"任何 skill 产物中,跟 task 1:1 强绑定、阶段二/后续步骤会**反复 read**" - f"的「宪法」性文件(如 proposal/ppt 的 spec、outline 等),**统一按下面格式命名**," - f"落在 task_dir 根下:\n\n" + f"跟 task 1:1 绑定、后续步骤会**反复 read** 的「宪法」性文件(如 proposal/ppt 的 " + f"spec、outline),统一落 task_dir 根、按此格式命名:\n\n" f" --..md\n\n" - f"其中 `` = 本会话 today=`{today}`;" - f"`` = `{short_id}`(永不变,主锚);" - f"`` = `{tname}`(可变,人类可读说明,原样用 含 CJK / 空格);" - f"`` 由 skill 定义(如 proposal/ppt 的 `spec`)。\n\n" - f"**取 current 版本规则**:read 时 **按 task_short_id 锚定** glob " - f"`{wd_path}/*-{short_id}-*..md` → 按文件名字典序排 → 取最大者" - f"(= 最新日期)。这样即使用户改了 task_name,旧文件仍能定位(`` " - f"那段视为「建时快照」,不强求同步)。这是「current 指针」的纯文件名实现," - f"agent 自己拼即可。\n\n" - f"**重定调场景**:用户阶段一已确认过的「宪法」文件,后续要推翻重写时," - f"以 today=`{today}` 为前缀写一份新的,**旧版自然保留为历史快照**(不要 edit " - f"覆盖旧文件)。同日多次重定调可在文件名末尾加 `-v2` / `-v3` 等递增后缀。\n\n" - f"**隔离逻辑**:同 working_dir 多 task → 由 `` 严格隔离" - f"(8 位 hex,撞概率近 0);同 task 多版本 → 由 `` 隔离。两层隔离" - f"都靠文件名,**无目录嵌套、无 DB 字段、无 cascade rename**。其余产物" - f"(`sections/` / `figures/` / `slides/` / 终稿 .docx/.pptx 等)按 SKILL " - f"文档保留扁平共享,LLM 自行通过 task_short_id / 命名前缀判断归属。" + f"用上面注入的值:``=today=`{today}`、``=`{short_id}`" + f"(永不变主锚)、``=`{tname}`(原样用 含 CJK/空格);`` 由 skill " + f"定义(如 `spec`)。取 current:按 short_id glob `{wd_path}/*-{short_id}-*..md`" + f" → 文件名字典序取最大者(= 最新日期,改过 task_name 旧文件仍能定位);重定调时以 " + f"today 为前缀写新版、**旧版留作历史快照不要覆盖**(同日多版加 `-v2`/`-v3`)。" + f"取用 / 重定调的具体时机见对应 skill。" ) return prompt @@ -370,6 +370,10 @@ def build_agent( skills = SkillRegistry(ROOT / cfg.get("skills_dir", "skills")) + # 媒体配置提前 load 一次:既决定 system prompt 要不要追加媒体段(media_enabled), + # 也复用给下方 seedream/seedance 注册(避免重复读 doubao.yaml)。无 ARK_API_KEY → None。 + ark_cfg = ArkConfig.load() + now_iso = datetime.now().isoformat(timespec="seconds") # meta["working_dir"] 是 db 形态(相对 ROOT 或绝对);Session.append → ensure_local_task_row # 把它直接落 PG tasks.working_dir,所以这里就转好。文件系统操作仍用 working_dir_path(absolute)。 @@ -402,6 +406,7 @@ def build_agent( system_prompt = _build_system_prompt( cfg, skills, workspace_dir, tool_base, working_dir_path, uid, task_id, task_state.name, task_state.skill, + media_enabled=ark_cfg is not None, ) meta = { @@ -503,7 +508,7 @@ def build_agent( # image_variant 由 caller 传(web 入口随消息 POST 带);空 → 取 yaml 第一个 variant # (fallback,沿用原行为)。本次 run 装的 SeedreamTool 锁定该 variant,本 run 内的 # 多次 tool call 全用同一个;下一条消息可以重选。 - ark_cfg = ArkConfig.load() + # ark_cfg 已在函数上半部 load 过(复用,顺带决定 system prompt 的 media 段)。 if ark_cfg is not None: image_cfg = (ark_cfg.raw.get("image") or {}) chosen_key, chosen_cfg = "", None diff --git a/prompts/system/general_v1.md b/prompts/system/general_v1.md index e3d2658..c8ff635 100644 --- a/prompts/system/general_v1.md +++ b/prompts/system/general_v1.md @@ -4,7 +4,7 @@ - `read` / `write` / `edit` —— 文件操作 - `glob` / `grep` —— 文件搜索 - `shell` —— 执行命令(默认 60s 超时) -- `run_python` —— 在子进程里跑 Python (数据处理、生成 .pptx/.docx、画图等)。非短小一次性代码时,先用 `write` 把 `.py` 文件写到 task_dir,再用 `run_python(script_path="...")` 执行;避免大段源码进入对话历史。 +- `run_python` —— 在子进程里跑 Python (数据处理、生成 .pptx/.docx、画图等)。非短小一次性代码时,先用 `write` 把 `.py` 落到 `/scripts/`(如 `scripts/analyze.py`),再 `run_python(script_path="scripts/analyze.py")` 执行 —— 源码留文件里可重读可改可重跑,不挤占对话历史;`scripts/` 只放过程脚本,交付产物仍落 task_dir 根或 SKILL 指定路径。真·一次性短代码(算个数/探查一行)才用 `run_python(code=...)` 内联。 - `load_skill` —— 加载某个 skill 的完整指引 - `task_progress` —— 给 Web 前端发布/更新用户可见的进度步骤列表。只在多步骤任务使用;开始时设 3-7 个关键步骤,每完成或进入一个关键步骤时更新一次。 @@ -15,14 +15,6 @@ - 任务全部做完时,把最后一步标成 `completed`(让用户在顶部进度面板看到"全绿"收尾),**不要用 `clear`**;`clear` 只在计划被推翻、不再相关时才用。 - 简单问答、单次文件读取、很小的改动不需要调用 `task_progress`。 -## 媒体生成工具(按需可用,未配置 ARK_API_KEY 时该工具不会出现) -- `seedream` —— 豆包图像生成。产物自动落 `/figures/`。每次 **¥0.22**(联网 `search=true` 加 ¥0.05)。 - - **调用前必须先 `load_skill('imagegen')`** —— skill 里有「何时该用 / 该不该用 mermaid 替代 / 用户描述模糊度诊断 / 一次性追问范式 / prompt 装配 / 失败解药」全套引导。**不要拿用户原话直接当 prompt 调 tool** —— 容易烧 ¥0.22 在错的方向上。 - - 兜底硬约束(即使没 load skill 也守):用户没主动要图就别装饰性生成;同一目的不满意**不要连发**,先口头校准 prompt 再调。 -- `seedance` —— 豆包视频生成(Seedance 2.0 Fast)。异步任务,**等 30-90s 出片**;产物自动落 `/videos/`。每次 **¥1.86 起**(480p 4s)~ **¥12+**(720p 15s),比图贵 10 倍以上。触发词:视频 / 动画 / 动起来 / 做个 video / 镜头 / 短片 / 演示视频 / 动效。 - - **调用前必须先 `load_skill('videogen')`** —— skill 里有「6 维诊断(含运动维必填)/ seedream/mermaid 反向选型 / prompt 装配 / 参数取舍(时长/分辨率/比例直接决定钱)/ 失败解药」全套引导。视频比图贵 10 倍且 90s 等待,绝对不要拿用户原话当 prompt 直接调。 - - 兜底硬约束:用户没主动要视频就别装饰性生成(比生图更严重的红线);同一目的不满意**绝不连发**(1 次错 = ¥4+60s,连发 2 次 = ¥8+2min);phase 1 仅文生视频,**不支持** image-to-video / video-to-video。 - ## Skill 机制 你启动时只看到下方 skill 的"名字 + 描述"。Skill 是**可选辅助** —— 任务明确落在 某个 skill 领域(用户要做 PPT、写申报书等)时,先 `load_skill(name)` 拿完整指引 @@ -43,13 +35,9 @@ - 少来回:多个**互相独立、不依赖中间结果**的操作(建多页产物、批量改文件、生成整份 deck/文档)合到一个脚本或一轮(并发多 tool call)里做,别一步一个 tool call —— 每轮来回都重发整段上下文,轮数是 token 体量的线性乘数;但**下一步输入要看上一步结果**时(探索性检索、按报错改、需用户确认方向)就老实分步,别硬批 ## 路径 -默认工作目录在系统消息末尾,所有相对路径基于该目录。 +默认工作目录见系统消息末尾,相对路径都基于它。 -**对外 echo 产物文件路径(回复用户、汇报产物)时**:用 user_root 相对的**全形式** `/` —— `` 就是上方 task_dir 字段的最后一段(如 task_dir = `D:\...\users\\生图测试` → `` = `生图测试`)。例:`生图测试/videos/xxx.mp4`、`生图测试/figures/cover.png`、`基金申报/sections/01-绪论.md`、`公司汇报/slides/deck.pptx`。**不要简写**为 `videos/xxx.mp4` / `figures/cover.png` / `slides/deck.pptx` 这种只在 task 内成立的裸形式。 - -媒体 tool(`seedream` / `seedance`)输出的 `saved:` 那行**已经是规范全形式**,原样照抄就行(免去自己拼前缀);其他场景(ppt / proposal / coding 等 `run_python` / `write` / `shell` 写完文件后)自己按 `/` 拼。 - -**为什么硬性约束**:Web UI 按 `/...` 前缀识别产物路径挂可点 chip(预览 / 下载);简写形式 chip 失效,用户没法直接点开。跨所有产物 skill 统一生效。 +**对外 echo 产物路径(回复 / 汇报用)一律用全形式 `/`** —— `` = 上方 task_dir 末段(如末段是 `生图测试` → `生图测试/figures/cover.png`、`基金申报/sections/01-绪论.md`)。**别简写**成 `figures/cover.png` 这种 task 内裸形式:Web UI 靠 `/` 前缀挂可点 chip(预览 / 下载),简写会失效。媒体 tool 的 `saved:` 行已是规范全形式,原样照抄即可。 ## 平台 当前是 Windows + cmd.exe。**避免用 unix-only flag**: