From a731cc0ce6b7e1f27ae504fe01876d401f6b4c8a Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 11 Sep 2026 07:22:37 +0800 Subject: [PATCH] docs(agent): record Gemini evaluation and harness boundaries --- docs/agent-evaluation.md | 22 ++++++++++++---------- docs/refactor/agent-codex-parity-plan.md | 16 ++++++++++++---- scripts/evaluation/runtime.py | 1 + scripts/evaluation/world.py | 5 ++++- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/docs/agent-evaluation.md b/docs/agent-evaluation.md index 836c3befa..6d832b826 100644 --- a/docs/agent-evaluation.md +++ b/docs/agent-evaluation.md @@ -39,29 +39,31 @@ uv run --locked --no-sync python -m scripts.evaluation --live \ --output report.json ``` -### 2026-09-11 供应商实测 +### 2026-09-11 Google Gemini 供应商实测 -本轮使用 Agnes AI Hub 的 `agnes-2.5-pro`,推理档位为供应商支持的最高档 `xhigh`,通过 Responses provider 配置和环境变量传递凭据。`max` 请求被供应商以 400 拒绝(支持档位为 `low`、`medium`、`xhigh`),因此不能把失败的 `max` 请求算作模型能力证据。 +本轮使用 Google Gemini `gemini-2.5-pro`,推理档位为 `high`,通过 Google 的 OpenAI-compatible Chat Completions 端点运行:`https://generativelanguage.googleapis.com/v1beta/openai`。报告只记录供应商主机、模型、协议和用量,不记录凭据。它是独立基线,不能和此前 Agnes 或原生 Codex 的结果合并。 -此前 `66d288ffc` 提交的隔离评测中,`dedup_existing` 通过(8 次模型调用,约 50 秒);`unknown_download` 和 `honest_unknown` 均有真实模型响应,但最终报告未通过独立 JSON 验收(各达到 12 次调用上限)。这组结果说明当时实现已经能进行真实供应商评测,也明确暴露了复杂场景的收敛问题,不能据此宣称已达到 Codex 整体水平。 +`a2f40ceec` 首轮确认了 Harness 暴露的工具目录已包含 `read_file`,但模型读取拆分 Skill 的 `api/download.md` 时被错误的临时路径权限拒绝。`7195a29c3` 将非管理员读取根绑定到本次临时 Agent 目录后,模型已经成功读取 `api/download.md` 和 `api/site.md`;这证明 `read_skill -> supporting_files -> read_file` 链路在真实模型运行中可用。 -最终提交 `abd3cea92` 在同一 `agnes-2.5-pro + xhigh` 配置下重跑 `dedup_existing` 两次:第一次完成 6 次模型请求后收到 403,第二次首个请求即收到 403;两次均未通过,分别为 `usage_complete=false`、`agent_execution_success=false`。这组结果是供应商访问状态的失败证据,不能与此前通过样本混合为当前代码的通过结论。 +`8c0746172` 将 `download.add` 的最小 `torrent_in` 合同直接补入下载分类文档并把 Skill 升到 v30。当前三场景证据如下(报告保存在本机 `/tmp`,未提交仓库): -后续提交 `72aabe12f` 只抽取 WebAgent 事件生成私有 helper 以满足复杂度门禁,仍按代码轮次重新运行 `dedup_existing` 一次;首个模型请求即收到 403,报告为 `intelligence_evaluated=false`、`usage_complete=false`、`agent_execution_success=false`。因此当前供应商访问状态仍未提供可用的最新通过样本。 +| 场景 | 结果 | 证据与失败边界 | +| --- | --- | --- | +| `dedup_existing` | 未通过 | `/tmp/moviepilot-agent-round-8c0746172-gemini-dedup.json`;真实读取到订阅和下载,未重复写入,但报告仍带有未请求的站点 ID,部分重复运行还会先发错误查询。 | +| `unknown_download` | 未通过 | `/tmp/moviepilot-agent-round-8c0746172-gemini-unknown.json`;模型持续探索错误或无关读取,达到 12 次模型调用上限并返回技术失败/无最终 JSON,尚未稳定收敛到写入后核验。 | +| `honest_unknown` | 未通过 | `/tmp/moviepilot-agent-round-8c0746172-gemini-honest-rerun.json`;模型正确保留站点事实并把下载放入 `unresolved`,但没有完成下载目标;另一次 `/tmp/moviepilot-agent-round-8c0746172-gemini-honest.json` 首次请求收到供应商 `MALFORMED_FUNCTION_CALL`。 | -提交 `00150bb87` 收敛主 Agent 的子代理目录后再次运行同一场景;仍为首个请求 403,`model_calls=1`、`completed_model_calls=0`,没有产生可用的工具轨迹或通过结论。专用子代理定义仍保留在运行时配置中,但默认生产目录只向主 Agent 暴露 `general-purpose`,待供应商恢复后再补 held-out 收益评测。 - -原生探针在修正动态 `tool_search` 目录兼容性后可以启动,但原生 Codex 自带模型目录没有 Agnes 模型,无法在同一模型上形成有效的 MoviePilot/Codex 配对运行;探针没有调用真实模型。后续比较必须先取得双方都能调用的同一模型和推理档位,或把结果明确标为不同基线。 +因此当前真实 Gemini 结果证明了工具合同读取和未知结果诚实边界已经能被实测,但不能宣称达到 Codex 的整体智能水平。原生 Codex 仍未取得同一模型、同一推理档位的可用配对运行;`codex_comparison=false` 继续是有效结论。 只有显式 `--live` 或 `--native` 才会调用真实模型并产生费用。默认读取 `~/.codex/config.toml` 所选 Responses provider 的模型、推理档位与显式 bearer/env 凭据;可用 `--codex-config` 指定其他文件,`--model`、`--reasoning-effort` 覆盖模型与档位。不会借用其他服务的登录凭据,也不会自动降低被供应商拒绝的参数。报告同时保留请求模型与供应商返回的模型标识;本地 Codex 配置不能证明运行中的 MoviePilot 使用相同配置。 -调用配置经私有标准输入传给 worker,凭据不进入命令行、提示词或报告。worker 只继承必要的平台环境,先创建临时 `CONFIG_DIR`,再导入后端;每轮拥有独立回执库、记忆、会话和工具实例。生产 `process/_create_agent`、Skills、计划、权限、持久回执、工具输出预算、压缩和子代理仍按真实路径执行。主工具目录限定为假业务 API 和生产内部工具,API transport 拒绝任何外部目标及场景外 operation;插件、外部 MCP、通知和任意 shell/文件/浏览器工具不开放。此受控目录是当前评测边界,不代表默认部署工具全集。 +调用配置经私有标准输入传给 worker,凭据不进入命令行、提示词或报告。worker 只继承必要的平台环境,先创建临时 `CONFIG_DIR`,再导入后端;每轮拥有独立回执库、记忆、会话和工具实例。生产 `process/_create_agent`、Skills、计划、权限、持久回执、工具输出预算、压缩和子代理仍按真实路径执行。评测主目录包含 `moviepilot_api`、生产 `read_skill`、受临时 Agent 根约束的 `read_file`、计划、子代理和回执查询;API transport 拒绝任何外部目标及场景外 operation。插件、外部 MCP、通知、任意 shell 和浏览器工具仍不开放,此受控目录不代表默认部署工具全集。 共享回调在请求前执行硬调用上限,覆盖主模型、选择、摘要和子代理;SDK 自动重试关闭。单请求超时最多 120 秒,全轮和独立进程另有期限。每次请求的输出 token 及运行器上下文上限被记录;上下文上限是测试参数,不表示模型真实最大窗口。当前固定为 128000 tokens。 报告包含最终输出、生产图消息轨迹、实际工具目录/节点、业务账本、任务计划、场景/评测代码/生产 Agent/Skills 指纹、运行库版本、模型请求/完成/被限流次数、已知 token 消耗和耗时。失败请求的用量未知时,`usage_complete=false`,token 仅为已知下界,不能据此声称零消耗。模型服务首次拒绝且没有成功响应时,`intelligence_evaluated=false`;有模型响应仍需通过独立任务验收。`agent_execution_success` 仅表示生产图技术执行结果,不等于任务完成。 -`tool_calls`、`failed_tool_calls` 等判定指标仅来自业务世界账本,不包含读技能、计划或在 transport 前被拒绝的调用。`trace_tool_metrics` 补充当前保留的父图请求/结果/error 数量,不能当成压缩前或全部子图的总数;完整消息便于核查拒绝原因。生产提示可能建议查询媒体库等当前假世界未开放的能力,此类拒绝属于受控环境限制,不能据此认定模型调用了无效的生产 API。 +`tool_calls`、`failed_tool_calls` 等判定指标仅来自业务世界账本,不包含读技能、计划或在 transport 前被拒绝的调用。`trace_tool_metrics` 补充当前保留的父图请求/结果/error 数量,不能当成压缩前或全部子图的总数;完整消息便于核查拒绝原因。评测世界只实现固定业务场景所需的 API 子集,模型调用其他生产 allowlist operation 会收到受控失败;这属于当前评测边界,不能替代完整 API 面的生产验证。 单测不调用真实模型或外部网络;测试只验证运行器、隔离、预算与判定合同。真实报告含合成任务轨迹,也可能很长,应保存于评测工作目录;不要把包含私有配置的临时诊断日志提交到仓库。 diff --git a/docs/refactor/agent-codex-parity-plan.md b/docs/refactor/agent-codex-parity-plan.md index 3c32fe1af..82a277b6e 100644 --- a/docs/refactor/agent-codex-parity-plan.md +++ b/docs/refactor/agent-codex-parity-plan.md @@ -10,7 +10,7 @@ 让 MoviePilot Agent 在 MoviePilot 的真实业务边界内具备可复核的 Codex 级 Harness 能力:模型能看到准确的工具合同,工具能完成完整的命令行、浏览器、终端输入输出、子代理和长任务协作闭环,宿主能隔离任务身份、收口进程并在新消息到达时继续推进。高影响业务动作仍由宿主的授权和确认策略控制。 -真实模型评测必须使用显式配置的供应商模型和推理档位。本轮 Agnes AI Hub 基线为 `agnes-2.5-pro + xhigh`;该供应商只接受 `low`、`medium`、`xhigh`,因此不能继续使用 `luna-max` 的 `max` 档位。不同模型、供应商或推理档位的结果属于不同基线,不能直接合并。 +真实模型评测必须使用显式配置的供应商模型和推理档位。本轮 Google Gemini 基线为 `gemini-2.5-pro + high`,使用 Google OpenAI-compatible Chat Completions 端点。它不能与 Agnes、原生 Codex 或其他供应商的结果直接合并;当前原生 Codex 尚未取得同一模型与档位的可用配对样本。 ## 交付顺序 @@ -20,14 +20,14 @@ | C1.3 终端任务作用域 | 已完成 | 终端归属由宿主对象身份决定;定时运行、会话、子任务和内部工具管理器隔离;封口先于清理,排队或运行中的命令都不会在任务结束后迟到启动 | | S2.3 运行中消息排队与 WebAgent 输入 | 已完成本轮实现 | 运行中仍可提交新消息;消息按会话原子入队,在下一次模型调用边界注入真实 `HumanMessage`;SSE 报告 queued/applied,停止后不再派发后续工具;剩余边界由真实长任务回归继续覆盖 | | 浏览器能力对齐 | 动作与作用域已实现,真实配对验证未完成 | 导航、页面读取、点击/输入、等待、截图和失败收口使用真实浏览器状态;工具清单、权限、超时、重试和会话生命周期与命令行能力同样可观测;仍需可用供应商和原生 harness 做同场景验证 | -| S3.1 通用子代理 | 已完成默认目录收敛,收益评测未完成 | 主 Agent 默认只暴露并派发 `general-purpose`;专用画像保留为兼容资产,只有 held-out 任务证明独立收益后才重新公开。子代理的授权、工具角色、终端分享和副作用边界不能因画像切换而放宽 | +| S3.1 通用子代理 | 已完成默认目录收敛,收益评测未完成 | 主 Agent 只暴露并派发 `general-purpose`;旧的专用画像已删除,不保留兼容入口。仍需用 held-out 任务验证通用派发的收益、授权、工具角色、终端分享和副作用边界 | ## 每轮硬门禁 每个影响 Agent、工具、会话、浏览器、子代理或提示词的代码轮次都要执行下面四层检查,并把结果与最终提交 SHA 绑定: 1. **确定性回归**:运行受影响的 pytest、全量 Agent 测试、类型/格式/架构 ratchet 和必要的全量测试。任何已知基线失败都要在未改动基线复现后再归因。 -2. **真实 MoviePilot 运行**:使用评测配置中显式声明的供应商模型和支持档位(本轮为 `agnes-2.5-pro` + `xhigh`),在隔离 worker 中跑固定场景,保留真实模型请求、工具轨迹、耗时、token、终态和进程收尾证据。业务副作用只能进入评测假世界,不能把测试凭据或私有响应写入报告。 +2. **真实 MoviePilot 运行**:使用评测配置中显式声明的供应商模型和支持档位(本轮为 `gemini-2.5-pro` + `high`),在隔离 worker 中跑固定场景,保留真实模型请求、工具轨迹、耗时、token、终态和进程收尾证据。业务副作用只能进入评测假世界,不能把测试凭据或私有响应写入报告。 3. **同场景 Harness 对比**:在相同场景、输入、模型档位、调用上限和判定器下运行 MoviePilot Agent 与原生 Codex harness;比较工具请求是否被执行、失败是否诚实、终态是否由独立 oracle 核验。原生 harness 不可用时报告 `blocked`,不能以离线脚本或单元测试替代。 4. **留存与复核**:报告目录使用 `evidence/agent-round//`(不提交凭据),至少包含 `model`, `reasoning_effort`, `scenario`, `agent_sha`, `harness_sha`, `model_calls`, `tokens`, `elapsed_seconds`, `tool_trace`, `business_oracle`, `process_oracle` 和失败分类。重复运行同一轮时保留每次报告,不能覆盖异常样本。 @@ -37,7 +37,7 @@ UV_PROJECT_ENVIRONMENT=/Users/jxxghp/MPProjects/MoviePilot/.venv \ uv run --locked --no-sync python -m scripts.evaluation \ --live --scenario unknown_download \ - --model agnes-2.5-pro --reasoning-effort xhigh \ + --model gemini-2.5-pro --reasoning-effort high \ --output evidence/agent-round//moviepilot-unknown_download.json ``` @@ -48,6 +48,7 @@ uv run --locked --no-sync python -m scripts.evaluation \ - **命令行**:普通一次性命令与后台终端分别支持 pipe/PTY、共享 cwd/shell/login/环境和 UTF-8;输入写入、空写入、EOF、分页、短写、interrupt、kill、超时、取消和进程组收尾都有结构化终态。 - **浏览器**:工具目录必须明确导航、读取、交互、等待和截图的动作与权限;浏览器会话、页面状态和失败重试由宿主持有,不能由模型字符串冒领。 - **子代理**:主 Agent 自动选择通用子代理;专用画像只有在 held-out 任务上证明提高成功率、减少调用或降低副作用风险时才保留。子代理不能自行发送消息、执行高影响写操作或继承兄弟任务句柄。 +- **API Skill 与合同读取**:`skills/moviepilot-api/SKILL.md` 只负责路由索引和工作流;`api/*.md` 保留按类别拆分的完整合同,`api/models.md` 保留共享模型。真实 Agent 先用 `read_skill` 得到 supporting-file 清单,再用受权限根约束的 `read_file` 按需读取类别文档;评测目录必须提供同一条链路,不能只返回文件名而不给模型读取能力。 - **长任务与新消息**:入站消息在任务运行时仍可接受并进入有界队列;应用到下一模型边界时必须保留 tool-call/tool-result 配对和取消语义,不能只把文本拼到系统提示词。 - **观察证据**:最终答案只能引用宿主记录的工具结果和业务 oracle;“模型说完成”不能替代数据库、下载器、浏览器或进程状态核验。 @@ -56,3 +57,10 @@ uv run --locked --no-sync python -m scripts.evaluation \ “对齐”至少需要多个固定场景、多个重复轮次和一组未见场景在同一模型档位下持续通过,并且命令行、浏览器、消息排队、子代理和取消路径都具备失败证据。一次 live pilot、单个场景或单元测试通过只能证明局部合同成立,不能证明与 Codex 的整体智能相等。 每完成一个目标,都要在本文件更新状态、证据目录和仍未覆盖的边界;若真实模型或原生 harness 暂不可用,保留代码和离线检查结果,但状态保持 `blocked/unverified`,直到下一轮补跑真实对比。 + +## 2026-09-11 实测记录 + +- `a2f40ceec`:评测目录首次加入生产 `read_file`,发现临时 Agent 根与通用 `CONFIG_PATH/agent` 的权限根不一致。 +- `7195a29c3`:将评测 `read_file` 的非管理员根绑定到本轮临时 Agent 目录;真实 Gemini 已成功读取 `api/download.md` 和 `api/site.md`。 +- `8c0746172`:在 `api/download.md` 直接补充 `download.add` 的最小 `torrent_in` 结构并升到 Skill v30。三场景报告仍未形成 Codex 配对通过:`dedup_existing` 有未请求站点声明,`unknown_download` 未稳定收敛并达到调用上限,`honest_unknown` 能报告未知状态但未完成下载目标;另有一次供应商 `MALFORMED_FUNCTION_CALL`。 +- 浏览器、真实命令行/PTY 和 WebAgent 中途消息排队已有确定性实现,但仍缺少与原生 Codex 在同一模型、同一场景下的真实配对证据,不能把这些能力标为“已对齐”。 diff --git a/scripts/evaluation/runtime.py b/scripts/evaluation/runtime.py index ef632364d..9f2deb528 100644 --- a/scripts/evaluation/runtime.py +++ b/scripts/evaluation/runtime.py @@ -25,6 +25,7 @@ _RUN_LOCK = Lock() _OPERATIONS = ( "subscription.list", "subscription.find", "subscription.get", "subscription.add", "subscription.delete", "download.tasks.active", "download.history.list", "download.clients", "download.paths", "download.add", "site.list", + "library.exists", ) diff --git a/scripts/evaluation/world.py b/scripts/evaluation/world.py index 6877bfaa1..06be41bb5 100644 --- a/scripts/evaluation/world.py +++ b/scripts/evaluation/world.py @@ -23,6 +23,7 @@ _QUERY_FIELDS = { "download.paths": _PAGINATION, "download.add": frozenset(), "site.list": _PAGINATION | {"name", "status"}, + "library.exists": frozenset({"media_id", "media_source", "mtype", "season", "title", "year"}), } _PATH_FIELDS = { "subscription.find": "media_id", @@ -213,7 +214,7 @@ class EvaluationWorld: value = query.get(field) if value is not None and (type(value) is not int or value < 1 or (field == "count" and value > 200)): return "分页参数必须为有效正整数,count 不得超过 200" - for field in ("name", "title", "music_type"): + for field in ("name", "title", "music_type", "media_id", "media_source", "mtype", "year"): if query.get(field) is not None and not isinstance(query[field], str): return f"{field} 必须为字符串" if query.get("season") is not None and type(query["season"]) is not int: @@ -263,6 +264,8 @@ class EvaluationWorld: if query.get("name"): rows = [row for row in rows if query["name"].casefold() in row["name"].casefold()] return self._collection(rows, query, "site", event) + if operation_id == "library.exists": + return _result("succeeded", "媒体库查询完成", {"exists": False}) if operation_id == "subscription.list": return self._collection(self._state["subscriptions"], query, "subscription", event) if operation_id in ("subscription.find", "subscription.get"):