Files
auto_control/doc/AI_CONSOLE.md
T
butubb ed9e8bacb1 feat(AI 建任务): 直接创建 + 草稿沉淀 + MCP de_snapshot;修「建任务页收不到 done」
用户报的"探索完无法点击创建任务"真因:一个 run 的事件原先只有**一条** queue.Queue,
聊天页与建任务页同时开着时两个 EventSource 会**瓜分**它——建任务页的回放卡在中间、
`done` 被聊天页取走 → 永远等不到草稿,页面上自然没有可点的"创建"。

一、修(根因 + 表现)
- `web/agent_api.py` 新增 `_Fanout`:**每个订阅者一个专属队列**,多开页面各看各的,
  还带单轮事件缓冲(晚订阅/刷新重连也能补齐回放,终止事件一定送达)。
  实测两路订阅者收到完全一致的 1039 条事件(含 done)。
- `static/admin/agent.js`:断线重连的兜底订阅也按 `mode` 让开(此前漏了这一处)。

二、补齐上一批的三项
- **「直接创建」**:`POST /api/agent/task_draft/create`(草稿体只在服务端、创建前再校验一次、
  成功后清草稿避免重复建)+ 草稿预览里的「✓ 直接创建任务」按钮 + 「探索完直接创建任务」勾选框。
- **草稿沉淀经验/动作**:designer 轮次也走 `_distill_experience/_distill_actions`,
  但**只在草稿通过校验时**(没走通的试错不入库,免得把误点当经验)。
- **MCP `de_snapshot`**(第 20 个工具):截图+元素树一次取齐(省一次来回、不会因界面在动而错位),
  附带 `screen_state`/`unstable`;两套提示词都改为优先用它。
  平台侧 `/api/uiauto/snapshot` 随之多返回 `screen_state`。

三、文档
- AI_TASK_GEN §10:§10.3 记两个 bug 的真因与修法、§10.4 三项标完成、§10.5 剩余项。
- AI_CONSOLE(扇出语义、多页面同时看一轮)、API(task_draft/create、snapshot 字段)、
  MCP/MCP_DESIGN/staffdeck/README/ARCHITECTURE:工具数 19→20 + de_snapshot 条目。
- backlog:记一条新发现的缺陷——`mcp_server/platform_client._login()` 会把"登录页 200"
  当成登录成功(现场进程缺 `MCP_PLATFORM_PASS` 时表现为含糊的 platform_unavailable)。

自测:真机浏览器端到端(勾上"探索完直接创建")→ 探索 12 步 → 草稿 → 自动建任务成功;
`de_snapshot` 直连真机校验;校验器 21 条用例、扇出单元用例、本地工具契约用例全绿。
自测产生的任务/草稿已全部清理(未碰用户既有数据)。
2026-09-14 08:14:11 +08:00

12 KiB
Raw Blame History

AI 控制台(AI_CONSOLE)

适用读者:使用 AI 控制台的人 + 改这部分代码的开发者。 相关文档:API.md §12(接口)、MCP.md(AI 用的工具层)、AI_TASK_GEN.md("一句话建任务"的设计稿,尚未实现)。


1. 它是什么

「AI 控制台」是后台的一个顶级 Tab(仅管理员),下面有两个子分栏:

子分栏 做什么 文档
💬 聊天 选一台设备用自然语言下指令,AI 通过 MCP 工具看屏幕、点按、输入,边做边把过程和结论流式显示出来(本文内容) 本文
🧭 AI 建任务 描述"要什么样的自动化",AI 自己在真机上探索(看屏/读元素树/点按验证),把走通的路径写成一条可调度任务,校验后交人工在步骤编辑器确认 AI_TASK_GEN.md

两个子分栏共用同一个运行槽(全平台同时只允许一个 Agent 运行):建任务在探索时,聊天页会显示"● 建任务探索中"并禁用发送,反之亦然。

你:「打开小红书搜索苏州好吃的饭店,把前 5 条列出来」
AI:de_list_devices → de_screenshot → de_tap_text("搜索") → de_type_text("苏州好吃的饭店")
    → de_screenshot → de_ui_tree → … → 汇总结论(Markdown 表格)

两个"自进化记忆"让它越用越顺:

记忆 存什么 怎么产生 怎么用
🧠 经验库 任务级操作配方(这一步该怎么做) 一轮任务成功后由模型蒸馏 相似任务开始时召回注入 system prompt
🎬 动作库 命名动作(可复用的动作单元,带元素定位) 从成功步骤蒸馏,禁坐标 相似任务开始时召回注入,可直接复用定位

2. 使用

2.1 配置(首次必做)

右上角 ⚙ 配置:

项 说明
API Base OpenAI 兼容地址(默认 https://api.deepseek.com)
模型名 如 deepseek-v4-flash-vision-exp(需支持视觉,因为要看截图)
API Key 只存数据库 app_meta,回显打码
默认设备 不选目标设备时用它
最大步数 1-200,默认 40(每轮模型调用算一步)

配置存在数据库(app_meta 的 agent_* 键),不在 .env。CLI(mcp_agent/cli.py) 走的是环境变量 AGENT_*,两套互不影响。

2.2 跑一轮

  1. 选 🎯 目标设备(AI 只操作你选定的设备;有任务在跑的设备不可选)
  2. 输入指令,Enter 发送
  3. 右侧「📺 实时画面」自动跟随 AI 操作的设备(MJPEG)
  4. 中途可「■ 停止」(下一个检查点生效,通常几秒内)
  5. 刷新/换窗口:会话与运行状态都会自动恢复(见 §3.3)

2.3 会话管理

左侧会话列表 = 多轮对话(DeepSeek 风格):不同话题建不同会话,历史消息会作为上下文续上(最近 12 轮)。会话条目上显示 ID(前 8 位,点击复制),便于反馈问题时引用 conv=<id>。


3. 机制

3.1 执行链路

POST /api/agent/run            web/agent_api.py 起后台线程(单实例:同时只允许一个)
        │
        ├─ 经验召回 _find_experiences(prompt)   ─┐
        └─ 动作召回 _find_actions(prompt)        ├─ 拼成 extra_context 注入 system prompt
                                                 ┘
        ▼
mcp_agent.Agent.run_stream(prompt, serial, history, on_delta, on_tool, on_usage, …)
        │  循环(最多 max_steps 轮):
        │    ① 流式调模型(OpenAI 兼容 /chat/completions)
        │    ② 有 tool_calls → 执行 MCP 工具 → 结果回灌 → 继续
        │       截图工具的结果会转成 image_url 追加,模型"看得见"
        │    ③ 无 tool_calls → 本轮即最终回答
        ▼
   事件 → queue → SSE /api/agent/stream → 前端
  • 一轮整体超时 900 秒;达到步数上限会让模型做一次收尾总结
  • 工具调用由 MCP Server 执行(:8033),后者再调平台 HTTP 接口/直连设备
  • 设备忙时拒绝:目标设备正在跑任务 → 409("AI 不与任务抢设备")
  • mode:"designer"(AI 建任务)走同一条链路,差别:换一套 system prompt(任务设计师)、 输出上限提到 8192、不吃聊天历史、多一个平台级本地工具 submit_task (不经 MCP,服务端用 core/task_draft 校验,见 AI_TASK_GEN.md)
  • 工具结果必须是连续的 tool 消息:一轮里若同时调了截图与别的工具,图像会攒到本轮工具 消息发完后再作为一条 user 消息附上(否则模型侧会以"工具回应不足"报 400)

3.2 会话消息模型

agent_conversation.messages(JSON 数组):

[{"role": "user", "content": "…"},
 {"role": "assistant", "content": "最终回答(Markdown)",
  "usage":     {"prompt_tokens": 3480, "completion_tokens": 126, "total_tokens": 3606, "calls": 1},
  "reasoning": "模型的推理链(最多保留 6000 字符)"}]

usage / reasoning 只用于前端展示与回看;回灌给模型的历史只取 role / content(不污染上下文预算)。

3.3 SSE 事件与刷新恢复

event payload 前端行为
delta {"text","kind":"content"|"reasoning"} 正文增量渲染 Markdown;推理增量进入可折叠「💭 思考过程」
step {"tool","args","image"?} 追加工具卡片(含缩略截图,点击放大);伪卡片提示经验/动作命中与沉淀
usage {"prompt_tokens","completion_tokens","total_tokens","calls"} 刷新单条消息脚注与顶栏「本会话累计」
done {"answer","usage","mode","draft"?,"warnings"?,"draft_error"?} 最终答案 + 收尾;mode=designer 时带任务草稿(或草稿被拦的原因)
error {"message"} 展示错误(MCP 不可达等已转成明确文案)

刷新/重连不丢进度:服务端事件队列保留积压,页面重新订阅(GET /api/agent/stream?run_id=)后会补发 delta/step/usage/done;EventSource.onerror 刻意不结束运行,靠自动重连续上。另外 GET /api/agent/run 提供状态快照(其他窗口/8s 轮询用)。

多页面同时看同一轮:一轮的事件用扇出(web/agent_api.py 的 _Fanout)发给每个订阅者各自 的队列——两个页面都收到全量事件。不要退回"一个 run 一个 queue.Queue":那样第二个页面一订阅, 两个 EventSource 就开始瓜分同一条队列,谁先取到算谁的(2026-09-14 实测:聊天页把建任务页的 done 取走了,建任务页永远等不到草稿)。

3.4 token 统计

  • 请求带 stream_options.include_usage,服务端在末尾 chunk 返回 usage
  • 按「每次模型调用」累加(多轮工具调用会累加多次);calls = 模型调用次数
  • 个别网关不认该参数会直接 400/422 → 自动关掉并重试一次,不影响主流程(只是没有 token 数字)
  • 展示位置:单条消息脚注(🪙 3,606 tokens(↑3,480 ↓126 · 1 次调用))+ 顶栏「🪙 本会话 3,606 tokens」

3.5 回答渲染

  • Markdown:自研轻量渲染器(static/admin/markdown.js,无 CDN 依赖,生产在内网) 支持标题/段落/列表(含嵌套)/表格/代码块/引用/链接;先 esc() 转义再套标记,所以模型输出里的 HTML 只会显示为文本
  • 推理链:<details> 折叠块,流式时展开、正文开始时自动收起;手动点过后不再自动改;摘要显示字数
  • 工具卡片:每步 MCP 调用一行(工具名 + 参数 + 截图缩略)

4. 经验库

4.1 产生(蒸馏)

一轮任务成功执行过工具后,把「任务描述 + 工具序列」交给模型,提炼成一段可复用的操作配方(纯文本,不含具体坐标)。

健壮性设计(都是踩坑后加的):

措施 原因
蒸馏调用关闭推理(thinking: {"type":"disabled"}) 推理模型会把 token 预算烧在 reasoning 上,导致 content 为空/被截断 → 经验被静默丢弃
纯文本问法 + 质量门槛 早期提示词里放了可照抄的占位示例,模型会把 "1. …\n2. …" 原样当配方存下来
截断容忍解析 JSON 被截断时逐个对象抢救
失败有日志 不再静默

4.2 召回

相似任务开始时按 bigram 相似度检索(阈值 + 取前 2 条),拼进 system prompt,并在对话里推一张「🧠 经验记忆」卡片("命中 N 条同类历史经验,已注入参考")。命中会累加 hits。

4.3 巡检(质量治理)

  • 每日 03:47(独立 APScheduler)自动跑一轮:把经验交给模型评审,输出「保留 / 建议删除」+ 评分 + 理由,写入 experience_audit
  • 也可在面板里手动触发
  • 删除永远需要人工确认(巡检只打标 pending,面板上显示 ⚠ 建议删除 + 理由,人工点「确认删除」或「保留」)
  • 「保留」会记 action='kept',后续不再重复建议

4.4 面板

「🧠 经验库」按钮打开:列表(任务描述、配方摘要、引用次数、巡检建议)+ 删除/保留操作。


5. 动作库

5.1 与经验库的区别

经验库 动作库
粒度 整个任务的操作套路(文字) 单个可复用动作(结构化步骤)
内容 自由文本配方 命名动作 + steps(编辑器 schema,带元素定位)
用途 让 AI 知道"这类任务一般怎么做" 让 AI 直接复用"打开抖音"这种动作,跳过重新探索

5.2 产生(蒸馏)

从本轮成功的步骤轨迹里提炼命名动作(如「打开抖音」)。硬约束:

  • 禁坐标:带 click_xy 的步骤不会被沉淀(坐标换个设备/分辨率就失效)
  • 白名单步骤类型(18 种去掉 click_xy、keep_screen)
  • 每类型有必填参数校验(如 click 必须有选择器)
  • 输入/产出限量:最多 10 步输入、最多 3 个动作 × 4 步
  • 保存时服务端再校验一次,含坐标的提交直接 400

5.3 召回与使用

相似任务时按动作名/别名匹配(子串或 bigram 相似度),注入「## 可复用动作」段,并推「🧠 动作经验」卡片。AI 被提示"优先按其中的元素定位操作;若与当前界面不符,再自行截图确认"。

5.4 面板

「🎬 动作库」:查看/编辑/删除/手动新建。编辑时直接改 steps JSON(有格式说明),保存走同一套校验。


6. 配置项(app_meta)

key 含义 默认
agent_api_base 模型接口地址 https://api.deepseek.com
agent_model 模型名 —
agent_api_key API Key(明文存库) —
agent_default_serial 默认目标设备 空
agent_max_steps 最大步数(钳制 1-200) 40
agent_task_draft AI 建任务最近一份草稿(JSON:{draft, warnings, prompt, created},只留最近一份;不是任务,入库仍走人工确认) 空

其它常量:单轮超时 900s、推理链落库上限 6000 字符、会话消息上限 60 条、历史上下文取最近 12 轮; 建任务模式(designer)输出上限 8192、单工具调用上限 40 次(mcp_agent/agent.py)。


7. 故障排查

现象 原因 / 处理
报「MCP server(8033) 不可达」 MCP 没启动:容器由 start.sh 拉起;本机手动 MCP_ALLOW_WRITE=1 python -m mcp_server.mcp_server
报模型 API 401 API Key 无效/过期 → ⚙ 配置重填
报"请先选择目标设备" 没选设备且没配默认设备
报"设备正在执行任务…"(409) AI 不与任务抢设备:等任务结束,或去监控页停止该任务
没有 token 数字 该网关不支持 stream_options.include_usage(已自动降级,功能不受影响)
经验/动作没沉淀 只有成功执行过工具才会蒸馏;看 logs/web.log 的「经验提炼 / 动作提炼」日志
推理链看不到 模型未返回 reasoning_content(非推理模型),或本轮没有推理输出
界面样式/脚本异常 强刷浏览器(Ctrl+Shift+R)——markdown.js 等前端文件有缓存