Files
auto_control/doc/AI_CONSOLE.md
T
butubb 46e6ea1f37 feat(AI 建任务): AI 自己在真机探索 → 写出可调度任务 → 人工确认入库
AI 控制台下新增子分栏「🧭 AI 建任务」:描述要做什么(例:建一个跑 2 小时的任务、自动刷
某 App、随机点赞),AI 用 de_* 工具自己在设备上探索(看屏/读元素树/点按验证),把走通的
路径写成一条任务草稿,经服务端校验后交人在步骤编辑器里核对/手改/试跑,保存才入库。

链路:POST /api/agent/run{mode:"designer", settings}
  → Agent 自探 → 本地工具 submit_task(draft)
  → core/task_draft 校验(失败把 errors 回灌模型让它改)
  → 只暂存(运行态 + app_meta.agent_task_draft,**不落库**)
  → SSE done{mode,draft,warnings} → 页面草稿预览 → openTaskModal(null, prefill) 预填

关键实现
- core/task_draft.py(新):把执行器的"静默跳过点"(未知 type/空 selector/空 children/
  嵌套>5/节点>60/cron 非法/必填缺失)前移成显式 error——POST /api/jobs 对 params 是盲存的,
  执行器又静默跳过错误步骤,没有这道闸门就是"任务建好了、跑起来什么都没做"。
  归一化兜底任务名/target/schedule/retry/时长;页面填的设置以 overrides 优先于模型。
  故意**不比执行器更严**:loop_mode 近义值归一(count→rounds)、缺 max_iterations 补默认 10
  (执行器本来就默认)——实测卡太死会把一轮探索耗在改字段上。
  有副作用的步骤(评论/发送/购买/删除…)只警告并把触发概率压到 30%(编辑器可改回)。
- mcp_agent/agent.py:双系统提示词(CHAT/DESIGNER)+ 平台级本地工具
  (LOCAL_TOOL_SPECS,不进 MCP)+ 每工具调用上限 40 + designer 输出上限 8192 +
  **json.loads 容错**(草稿被截断时给模型可读错误,而不是整轮失败)。
- web/agent_api.py:mode/settings 透传、submit_task 处理器(app_context 内校验+暂存)、
  done 带 draft、GET /api/agent/task_draft{,+POST,/clear}(草稿走 app_meta,不新建表)。
- 前端:static/admin/taskgen.js + #agent-sub-taskgen 子面板(showSubTab 机制);
  tasks.js 的 openTaskModal(jobId, prefill) + 信封归一化 + 唯一 draftKey;
  agent.js 按 mode 门控(一个 run 只有一个事件队列,两个 EventSource 会互相瓜分事件)。

顺带修掉一个 chat 也踩的协议 bug:一轮里同时调 de_screenshot 与别的工具时,截图图像会被
插在两条 tool 消息之间 → 模型侧判"工具回应不足"直接 400。改为本轮 tool 消息发完再附图像,
_repair_tool_messages 也改成只数**连续**的 tool 消息。

真机实测(Redmi 22120RN86C,设置页):8 步探索(含 tap_text 验证)→ submit_task 一次通过 →
草稿 8 个顶层步骤(screen_on/open_app/wait_el/click/wait/key_event…)、max_duration 1800、
无 click_xy、3 条 evidence;页面恢复草稿 + 预填编辑器 + 提示块渲染均正常,无 JS 报错。
自测数据已清理(草稿已丢弃、未创建任何任务)。

文档:AI_TASK_GEN.md 状态改「P0 已实现」+ §10 实现记录(差异/护栏/未做项)、AI_CONSOLE.md
(子分栏、designer 分支、SSE done 负载、app_meta 键)、API.md、DATA_MODEL.md、ARCHITECTURE.md、
DEVELOPMENT.md(自测入口)、README.md 索引、backlog 勾掉 P0。
2026-09-13 23:08:59 +08:00

225 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI 控制台(AI_CONSOLE)
> 适用读者:使用 AI 控制台的人 + 改这部分代码的开发者。
> 相关文档:[API.md](API.md) §12(接口)、[MCP.md](MCP.md)(AI 用的工具层)、[AI_TASK_GEN.md](AI_TASK_GEN.md)("一句话建任务"的设计稿,尚未实现)。
---
## 1. 它是什么
「AI 控制台」是后台的一个顶级 Tab(仅管理员),下面有两个子分栏:
| 子分栏 | 做什么 | 文档 |
|--------|--------|------|
| 💬 **聊天** | 选一台设备用自然语言下指令,AI 通过 MCP 工具**看屏幕、点按、输入**,边做边把过程和结论流式显示出来(本文内容) | 本文 |
| 🧭 **AI 建任务** | 描述"要什么样的自动化",AI **自己在真机上探索**(看屏/读元素树/点按验证),把走通的路径写成**一条可调度任务**,校验后交人工在步骤编辑器确认 | [AI_TASK_GEN.md](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](AI_TASK_GEN.md))
- 工具结果必须是**连续的** tool 消息:一轮里若同时调了截图与别的工具,图像会攒到本轮工具
消息发完后再作为一条 user 消息附上(否则模型侧会以"工具回应不足"报 400)
### 3.2 会话消息模型
`agent_conversation.messages`(JSON 数组):
```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 轮询用)。
### 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` 等前端文件有缓存 |