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。
225 lines
12 KiB
Markdown
225 lines
12 KiB
Markdown
# 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` 等前端文件有缓存 |
|