Files
MoviePilot/docs/agent.md

99 lines
15 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.
# Agent 复杂任务执行与恢复
MoviePilot Agent 通过模型、Skills、工具和会话状态共同完成任务。本次增强关注长任务的连续性:明确目标和检查结果,按需补充工具,并在执行中断后保留已知事实。能力对齐路线与每轮真实运行验收见 [Agent 与 Codex Harness 对齐路线](refactor/agent-codex-parity-plan.md)。
## 任务计划
复杂任务可以使用内部 `update_plan` 工具保存目标、步骤、状态、检查依据和阻塞原因。例如“排查下载后未入库”可以依次检查下载状态、整理队列、目标目录和媒体库。
- 简单查询不需要创建计划。
- 步骤支持 `pending`、`in_progress`、`completed`、`blocked`;完成和阻塞状态需要填写依据。
- 计划单独保存在会话图状态中,每次模型请求都能读取;压缩旧消息不会删除计划。
- 计划快照随聊天消息保存,在会话图重建后恢复。用户询问进度时继续当前目标,明确开始新任务时替换计划。
- 计划是模型记录的工作状态,不代表工具执行结果或写操作授权。仍需通过工具核实实际结果。
## 运行中补充消息
WebAgent 在 Agent 正在运行时仍可提交文本和附件。宿主为每个会话生成消息 ID,先把消息放入有界 steering inbox;下一次模型调用前由中间件把它作为真实 `HumanMessage` 写入图状态,并通过当前 SSE 报告 `queued`、`applied`。提交和运行收尾共享原子边界,收尾竞态中已接受的消息会在同一 worker 内继续处理,停止会话不会继续派发新的工具动作。补充消息不会启动第二张 Agent 图,也不会替换当前输出回调。
## 按需发现工具
设置 `LLM_MAX_TOOLS > 0` 时,Agent 首轮仍只筛选一批相关工具。后续读取 Skill 或发现新问题后,可以通过内部 `search_tools(search, limit)` 按名称、说明或标签搜索当前会话工具目录,并在下一次模型调用获得匹配工具的完整参数定义。
每次最多返回 8 个匹配工具,并保留最近 16 个额外工具候选;再次发现会刷新候选优先级。匹配支持工具精确名称和有限中英文能力别名,例如“整理”对应 transfer。没有匹配结果时不会展开全部工具。
额外工具的参数定义共享最多 4096 tokens、且不超过已知模型窗口 10% 的预算,总输入保留 15% 余量。搜索先报告候选,下一次模型调用根据统一预算明确报告实际启用和未启用原因。初选、常驻和供应商工具保持可用。发现状态仅作用于当前用户请求,新的用户请求重新筛选。`LLM_MAX_TOOLS` 约束首轮筛选数量;发现不会安装工具、连接新的 MCP 服务或扩大执行权限。
`update_plan`、`search_tools`、`read_tool_result` 和 `get_tool_execution` 是 Agent 内部会话能力,不通过外部 MCP 或 `moviepilot tool` 发布。
## 工具结果与大结果续读
宿主统一区分 `succeeded`(成功)、`failed`(失败)、`pending`(任务未完成)、`unknown`(实际结果未知)。明确的业务失败、MCP `isError` 和普通异常会以失败消息返回模型;异步任务已提交不等于任务完成。普通业务对象中的错误说明或下载器状态不会被当成整个工具的失败或执行状态。
超长结果在当前会话图中临时保存,预览带 `result_id` 和 `next_offset`。Agent 使用 `read_tool_result` 按 Unicode 字符位置续读,无需重新执行原工具。每条完整结果最多 1 MiB,每个图最多 8 条、合计 4 MiB,15 分钟后过期;容量超限、图重建和进程退出也会使编号失效。无法保存的结果明确要求缩小查询范围。不同图线程不能互读,管理员结果在降权后不能续读。结果原文只短期驻留内存,不归档到磁盘或日志。
`execute_command(action="run")` 返回结构化 JSON:`exit_code`、`timed_out`、`execution_outcome` 和 `status` 表示实际执行结果,`output` 保存输出预览,`output_file` 指向超长输出的临时归档。仅正常退出且退出码为 0 时成功;非零退出或已停止的超时命令为失败,无法确认进程结束时为未知,不能凭“有输出”判断成功。超时和取消不会撤销命令已经产生的外部副作用。取消继续向外传播,同时回收输出读取任务、关闭归档文件;`run` 与 `start` 都支持 `env`,并采用同一工作目录与解释器策略。
`run`、后台 pipe 和 PTY 共用 `cwd`、`shell`、`login` 解析。省略 `cwd` 使用 MoviePilot 根目录,相对路径也相对该目录,并支持 `~`;错误目录在进程启动前拒绝。POSIX 默认使用配置的 `SHELL`(未配置时 `/bin/sh`)且不启动登录模式,显式 `shell` 可选择已安装解释器,`login=true/false` 控制其支持的登录行为。登录启动文件可能改变目录或环境;PTY 本身不再隐式开启登录模式。Windows 未显式指定时保留 Git Bash → PowerShell 7 → cmd 的选择顺序与 UTF-8 策略;不支持的解释器/登录组合明确失败。回包的 `shell`、`login` 说明实际策略,默认解释器不可用不会阻断其它 Agent 业务能力。
管道会话可用 `write(input_text="末段输入", close_stdin=true)` 在交付末段后关闭输入,或用空输入显式关闭;输出仍通过 `read/wait` 读取,`stdin_closed` 表示输入已关闭。普通空 `write` 不发送 EOF;输入关闭后不能再追加数据,重复空关闭可确认已有状态。`run` 的 stdin 始终为 EOF。PTY 的输入和输出共用端点,因此拒绝 `close_stdin=true`,也不会先写入附带输入或关闭输出;PTY 控制字节的行为取决于终端模式,pipe 中 `\u0003`、`\u0004` 只是普通字节。
终端句柄绑定宿主创建的用户及任务作用域,知道 `session_id` 不代表可以操作它。对话正常多轮和图重建保留同一作用域;每次定时执行按真实 `run_id` 单独持有终端,执行结束、失败或取消后回收。清空对话、空闲回收或同会话更换用户时封闭旧作用域;只清理它拥有的进程,未收敛时保留记录供重试。停止当前交互推理仍保留已交付的后台终端,后续对话可继续读取或显式终止。
父任务使用 `task` 或 `subagent_task` 的 `terminal_sessions=[{"session_id":"term_…","actions":["read","wait"]}]` 显式分享指定终端给该子任务。批次及管道分别在每个任务定义里声明;提示词中提到句柄不会授权,兄弟任务也不会继承。子任务保有独立作用域,结束时撤销分享但不终止父进程;当前只读子代理策略继续限制动作。宿主控制授权与工具角色权限分别校验,共享不能扩大角色权限。
缺少、已关闭或无权访问的终端均返回 `terminal_access_denied`,不回显其他任务的命令和输出。进程重启后不恢复旧句柄,也不会自动重跑命令。内部 `MoviePilotToolsManager` 每个实例独立持有作用域,通过 `close()` 收口;直接调用命令工具必须由宿主绑定真实上下文,工具 JSON 不能声明或覆盖身份。
`interrupt` 仅发送一次 POSIX `SIGINT` 或支持的 Windows `CTRL_BREAK_EVENT`,不等待升级强杀,也不把会话标记为主动终止;实际命令可以处理信号后继续运行,也可能自行退出。回包含实际 `signal` 和 `signal_sent`。不具备该控制能力时明确失败;`kill` 仍负责终止并在需要时强杀,但未知名称、无效编号和没有真实映射的信号会在任何状态修改前拒绝。两种输入控制均不增加只读子代理权限。
后台命令 `start` 默认最多等待 250ms 的首次输出,可用 `yield_time_ms=0` 立即返回。后续 `read/wait/write/interrupt/kill` 用返回的 `output_until_seq` 和 `output_until_offset` 一起续读:前者是完整交付的最后一个分片,后者是下一分片中已交付的 UTF-8 字节位置。首次传 `since_offset=0` 开启分片内分页;不传 offset 时保持完整分片模式,小页装不下一个分片会明确提示调整限额。`last_seq` 只表示已经产生的输出,不能用于跳过未读内容。
`wait` 在有未读输出时立即返回,等待中产生新输出或读取结束也会唤醒;`timeout_ms=0` 只查询,不终止命令。进程退出后仍可能有尾部输出,获取完整日志应继续到游标读完且 `output_complete=true`;退出码缺失时仍为 `unknown`。缓冲保留窗口之外的缺口由 `output_lost=true` 明确标记。若 `start/write/interrupt/kill` 已执行动作但返回 `output_error`,会保留会话 ID 和未消费游标;只需调整 `max_bytes` 后用 `read` 获取输出,不应重新执行动作。首次 `start` 尚未交付会话 ID 时被取消会回收该进程,取消已有会话的 `wait` 则保留进程。
终端分页还受最终 JSON 字符预算约束:必要时减少本页正文并重新计算游标,避免 JSON 转义后又被通用工具预览截断。过长的命令、目录和错误回显会带显式截断标记;会话内部保留完整命令。
## 工具图片与模型视觉
`browse_webpage(action="screenshot")` 和 `view_image(url=...|file_path=...|image_data=...)` 在 Agent 中返回真实图像块和有限来源说明。图像在通用文本截断之前处理,最终请求按完整工具回复批次附加带工具来源的临时图像观察,兼容 Chat、Responses、Anthropic 与 Gemini 的图片输入;原始用户消息、工具调用 ID 和授权不变。`view_image` 的远程 URL 只允许通过公网安全校验的 HTTP(S) 地址,本地路径继续遵守 Agent 文件访问边界,`image_data` 支持纯 Base64、data URL 和原始字节。
每次模型请求都检查实际模型资料和 `LLM_SUPPORT_IMAGE_INPUT` 开关。已知纯文本模型会得到明确的“未接收工具图片”说明。服务明确拒绝图片时,只对该次模型调用做一次文字回退,随后本轮沿用文字观察,不重复执行图片工具或其他工具;认证、限流等错误保持原来的错误语义。临时观察不会写入会话历史。
浏览器截图最多约 150 KiB 原始 JPEG,`view_image` 图片最多约 768 KiB,实际输入还受 1 MiB data URL 和像素上限约束;超限或数据无效时明确失败。运行图可保留完整图片,持久化与中断恢复仅保存来源及图像未保留说明,日志不记录图片数据。恢复后的旧图片不能用于声称已看到视觉细节,需要重新调用图片工具获取当前内容。视觉 token 预算是估算,实际消耗以模型供应商的 usage 为准。
## 写工具的持久执行记录
生产 Agent 在副作用之前向 `agentinvocation` 表提交原子认领。记录只包含用户、会话、工具身份、参数指纹、执行 token、状态和固定宿主文案,不保存原始参数或工具输出。无法认领时不执行写入;相同调用 ID 携带不同参数时拒绝执行。
- 同一用户请求内,参数规范化后完全相同的 MoviePilot API 写调用共用一个执行身份,防止模型并行或重复调用;明确的新用户请求可再次执行已完成操作。
- 普通工具使用原始工具调用 ID 去重,不把重复执行同一条诊断命令误合并为同一业务意图。
- 上一轮相同 API 参数仍为运行中或未知时,新请求先核验旧记录,不盲目发起第二次写入。已确认提交的异步请求保存为 pending:同一执行身份不重复提交,新的明确用户意图可以再次提交,pending 不代表外部任务已完成。`get_tool_execution` 可凭宿主返回的 `invocation_id` 查询当前用户、当前会话的最近观测状态。
- 当前自动核验支持非敏感设置完整替换:通过 `config.system.get` 只读确认目标值,匹配后收口为成功并跳过重复写入。敏感、未匹配、列表追加及其他无法确定的副作用保持未知,不允许模型自行把它改成成功或“未执行”。
- 写入请求发出后的传输中断、超时和响应正文不可用保留未知;调用前参数错误和明确 HTTP 拒绝仍为失败。
- 冷启动将未收口的 running 转为 unknown,并更换 token。旧执行者不能覆盖新恢复状态,也不会仅因时间过去而自动重放。
成功、失败和已确认提交的回执随会话删除回收;没有聊天记录的后台同类回执按共享会话保留期清理。pending 是已确认提交的历史,实际异步任务由原任务系统管理,不是待执行队列。`DATA_CLEANUP_ENABLE` 和保留期 0 的禁用语义保持一致,running/unknown 恢复状态不会按时间删除。这些机制不构成任意外部系统的“恰好一次”事务保证。
## 异常和取消后的继续
对已启用聊天持久化的会话,执行异常或用户取消时,Agent 在释放会话图之前尝试保存脱敏恢复快照。已收到的工具回执保留;未收到结果的调用明确标记为“结果未知”,不能当成未执行或已成功。
用户随后说“继续”时,Agent 可以读取已有结果和计划,从剩余工作继续。下载、修改配置、删除文件等操作如缺少回执,应先只读核验实际状态,再决定是否重试。取消仍会终止当前轮次,不会自动重放工具。
聊天快照保存采用有界等待。进程强制退出、持久化失败,或工具在中断后才完成的外部副作用,不能保证被聊天快照记录;写工具通过上面的持久执行记录保留恢复边界。
## 子代理连续工作
默认子代理在宿主执行边界强制只读。多功能 API 网关按具体 `operation_id` 判断副作用,继承管理员身份也不能修改、删除、触发下载或服务操作;敏感设置原值读取同样不开放。终端仅允许读取/等待已有输出;浏览器仅允许观察已有页面,不允许导航、输入、点击、脚本、关闭或修改 Cookie。外部 MCP 的兼容 Read 标签没有动作级保证,需交由主代理处理。正常业务查询和声明只读的本地工具仍可使用。
一轮结束会回收未完成的子任务,正常收敛后同一会话下一轮仍可委派。会话关闭时永久停止接收子任务;无法在等待上限内收敛的任务仍由原 owner 持有并封闭新提交。
子代理沿用主 Agent 的最终请求预算和上下文压缩机制。长调查可以压缩旧消息;最新单条工具事务或固定提示本身超过模型窗口时仍会明确失败,不会静默删除关键内容。
## 验证范围
任务评测与离线轨迹验收的区别、场景及命令见 [Agent 任务评测](agent-evaluation.md)。固定响应单测不代表真实模型完成率。
自动化测试使用脚本模型和真实 LangGraph 执行图,覆盖工具发现和预算、计划恢复、结果状态、分页续读、持久认领和只读核验、执行中断以及连续两轮子代理委派。SQLite 测试覆盖并发认领、token 隔离、迁移和清理。测试不调用付费模型或外部服务。
这些机制提高执行可靠性;真实任务的理解、规划质量和成功率仍取决于所选模型、上下文窗口、工具权限及外部服务状态。模型能力需要用真实业务场景单独评估。