From 0e26fe0dc9794e9ffed3e545f7b7a50f36bce2f0 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 11 Sep 2026 10:23:26 +0800 Subject: [PATCH] eval: align native collaboration discovery --- docs/agent-evaluation.md | 10 +++++++++ docs/refactor/agent-codex-parity-plan.md | 3 ++- scripts/evaluation/proxy.py | 3 ++- tests/test_agent_evaluation_proxy.py | 26 ++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/docs/agent-evaluation.md b/docs/agent-evaluation.md index 14e084321..ae63ae30c 100644 --- a/docs/agent-evaluation.md +++ b/docs/agent-evaluation.md @@ -100,6 +100,16 @@ MoviePilot 的真实 Agent 已在 `app/agent/llm/helper.py` 的 `runtime == "goo 严格配对摘要为 `/tmp/moviepilot-agent-round-luna-oauth-dedup-pair.json`,`pair_valid=true`、`both_passed=false`,模型/推理/预算、场景和四项 SHA 指纹均一致。MoviePilot 比 Codex 少 3 次模型调用、少 61341 个已知 token、少 51.091 秒,但这只是本轮具体轨迹的成本差,不抵消生产 Agent 的终态验收失败。该结果首次形成可复核的真实配对证据,也明确了下一目标是提高生产图的精确最终报告可靠性,而不是把失败改判为通过。 +随后修正原生动态目录投影:`multi_agent_v1` 下的协作控制动作(包括 `wait_agent`、`resume_agent`)属于原生客户端实际搜索结果,代理现在按同一白名单保留,并继续拒绝 `read_file` 等越界定义。修正后的三次同 Harness 配对均使用 `gpt-5.6-luna + max`、16 次模型调用上限、8192 输出上限、180 秒超时和 `harness_sha256=71be4c436acc5dc2d5a0cd6996c3ca10688a9c4ba98fd11594e536f7aa440c22`: + +| 场景 | MoviePilot | 原生 Codex | 配对结论 | +| --- | --- | --- | --- | +| `dedup_existing` | 通过;6 次模型调用、2 次业务读取、0 重复/副作用 | 通过;10 次模型调用、3 次业务读取、0 重复/副作用 | `pair_valid=true`、`both_passed=true`;摘要 `/tmp/moviepilot-agent-round-luna-oauth-dedup-pair-13.json` | +| `unknown_download` | 通过;6 次模型调用、6 次业务调用、1 个预期写入副作用 | 通过;14 次模型调用、7 次业务调用、1 个预期写入副作用 | `pair_valid=true`、`both_passed=true`;摘要 `/tmp/moviepilot-agent-round-luna-oauth-unknown-pair-14.json` | +| `honest_unknown` | 通过;9 次模型调用、2 次受控失败读取、1 个预期写入副作用,保留下载未核验 | 未通过;16 次模型调用达到上限,未产出最终 JSON,1 个预期写入副作用且无重复 | `pair_valid=true`、`both_passed=false`;摘要 `/tmp/moviepilot-agent-round-luna-oauth-honest-pair-16.json` | + +这组三场景说明当前生产 Agent 在两个写入/复用路径和一个未知回执安全路径上已形成同条件通过样本,但原生 Codex 在持续不可用场景仍会因继续读取 Skill 和消耗调用预算而没有终态报告。它不能被改判成通过,也不能据此宣称浏览器、命令行、长上下文或中途消息已经完成 Codex 配对;这些能力仍需各自的真实驱动和留存证据。 + 已有报告可用以下命令重新生成摘要;命令只读报告,不会再次调用模型: ```bash diff --git a/docs/refactor/agent-codex-parity-plan.md b/docs/refactor/agent-codex-parity-plan.md index 0bbe33c32..ce4d46827 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 能力:模型能看到准确的工具合同,工具能完成完整的命令行、浏览器、终端输入输出、子代理和长任务协作闭环,宿主能隔离任务身份、收口进程并在新消息到达时继续推进。高影响业务动作仍由宿主的授权和确认策略控制。 -真实模型评测必须使用显式配置的供应商模型和推理档位。历史 Google Gemini 基线为 `gemini-2.5-pro + high`;后续测评切换为更强的 `gemini-3.1-pro-preview + high`。官方 Google 主机由评测 worker 使用生产同源的 `langchain-google-genai` 原生工具通道,以保留 Gemini 3 的 `thought_signature`;报告会把实际 `runtime_transport` 记为 `google_generative_language`。不同模型、供应商或协议的结果不能直接合并;当前已经取得首个同条件原生 Codex 配对样本,但两侧尚未同时通过。 +真实模型评测必须使用显式配置的供应商模型和推理档位。历史 Google Gemini 基线为 `gemini-2.5-pro + high`;后续测评切换为更强的 `gemini-3.1-pro-preview + high`。官方 Google 主机由评测 worker 使用生产同源的 `langchain-google-genai` 原生工具通道,以保留 Gemini 3 的 `thought_signature`;报告会把实际 `runtime_transport` 记为 `google_generative_language`。不同模型、供应商或协议的结果不能直接合并;首个同条件原生 Codex 配对样本未同时通过,随后同 Harness 的 `dedup_existing` 和 `unknown_download` 配对已同时通过,`honest_unknown` 仍保留原生侧预算耗尽失败。 Google 的 OpenAI-compatible 层会丢失 Gemini 3 工具回复中的 `thought_signature`,第二轮请求因此可能被供应商以 400 拒绝。真实 MoviePilot Agent 的 `runtime == "google"` 已固定走 `ChatGoogleGenerativeAI` 和签名兼容补丁;需要修正的是评测或错误的 provider 配置,不能通过普通 OpenAI 消息层补回签名。该边界由 `tests/test_agent_vision_providers.py`、`tests/test_llm_helper_testcall.py` 和本轮真实原生 Google 运行共同覆盖。 @@ -72,4 +72,5 @@ uv run --locked --no-sync python -m scripts.evaluation \ - `0f8aa4dfa`:公共 `moviepilot_api` 描述只保留通用边界;参数错误回执附带当前 operation 的允许字段、必填字段和类型/枚举约束。真实 Gemini `dedup_existing` 报告 `/tmp/moviepilot-agent-round-13b4fe112-gemini-dedup.json` 证明错误回执能促成 `subscription.find` 字段位置修正且没有副作用,但模型仍错误声明未请求的站点和已完成下载,继续保持未通过。 - 本轮:真实测评切换到 `gemini-3.1-pro-preview + high`,官方 Google 主机改用生产同源的 `langchain-google-genai` 原生工具通道并保留 `thought_signature`。`dedup_existing`、`unknown_download`、`honest_unknown` 三个固定场景均通过(报告分别为 `/tmp/moviepilot-agent-round-gemini31native-dedup.json`、`/tmp/moviepilot-agent-round-gemini31native-unknown.json`、`/tmp/moviepilot-agent-round-gemini31native-honest.json`);这只是 API 假世界的模型行为证据,仍未形成与原生 Codex 的配对比较。 - 本轮第二阶段:用同一 `gpt-5.6-luna + max`、同一场景和预算显式启用 Codex OAuth,首次生成有效成对摘要 `/tmp/moviepilot-agent-round-luna-oauth-dedup-pair.json`。MoviePilot live `/tmp/moviepilot-agent-round-luna-oauth-live-dedup-5.json` 因最终 infohash 少两位而未通过;原生 Codex `/tmp/moviepilot-agent-round-luna-oauth-native-dedup-8.json` 通过。摘要 `pair_valid=true`、`both_passed=false`,证明真实生产图与 Codex 仍有可观测的终态可靠性差距;下一轮先针对精确最终报告和未知结果收口做多次重复,再扩展浏览器、终端和中途消息场景。 +- 本轮第三阶段:修正原生动态工具搜索对 `multi_agent_v1` 协作命名空间的投影,正向保留 `wait_agent`、`resume_agent` 等原生控制动作,负向继续拒绝 `read_file`。在同一 Harness(`71be4c436acc5dc2d5a0cd6996c3ca10688a9c4ba98fd11594e536f7aa440c22`)下,`dedup_existing` 配对 `/tmp/moviepilot-agent-round-luna-oauth-dedup-pair-13.json` 与 `unknown_download` 配对 `/tmp/moviepilot-agent-round-luna-oauth-unknown-pair-14.json` 均 `pair_valid=true`、`both_passed=true`;MoviePilot 分别使用 6/6 次模型调用,原生 Codex 使用 10/14 次。`honest_unknown` 配对 `/tmp/moviepilot-agent-round-luna-oauth-honest-pair-16.json` 仍为 `both_passed=false`:MoviePilot 在 9 次调用后如实保留下载未核验,原生 Codex 达到 16 次上限后没有最终 JSON,产生 `invalid_final_report`。这保留了真实失败边界,不能等同整体 Codex 智能已完成。 - 浏览器、真实命令行/PTY 和 WebAgent 中途消息排队已有确定性实现,但仍缺少与原生 Codex 在同一模型、同一场景下的真实配对证据,不能把这些能力标为“已对齐”。 diff --git a/scripts/evaluation/proxy.py b/scripts/evaluation/proxy.py index 1a95b034f..c9a5bd0ae 100644 --- a/scripts/evaluation/proxy.py +++ b/scripts/evaluation/proxy.py @@ -33,6 +33,7 @@ NATIVE_CONTROL_TOOLS = frozenset({ NATIVE_COLLABORATION_TOOLS = frozenset({ "spawn_agent", "followup_task", "interrupt_agent", "list_agents", "send_message", "wait_agent", }) +NATIVE_MULTI_AGENT_TOOLS = NATIVE_CONTROL_TOOLS | NATIVE_COLLABORATION_TOOLS KNOWN_NAMESPACES = frozenset({"functions", "clock", "collaboration", "mcp__evaluation", "multi_agent_v1"}) FIXTURE_TOOLS = frozenset({"moviepilot_api", "read_skill", "read_tool_result"}) @@ -44,7 +45,7 @@ def allowed_tool(name: str) -> bool: or normalized in {f"collaboration__{tool}" for tool in NATIVE_COLLABORATION_TOOLS} or normalized in {f"mcp__evaluation__{tool}" for tool in FIXTURE_TOOLS} or (normalized.startswith("multi_agent_v1__") - and normalized.removeprefix("multi_agent_v1__") in NATIVE_CONTROL_TOOLS)) + and normalized.removeprefix("multi_agent_v1__") in NATIVE_MULTI_AGENT_TOOLS)) def project_tools(tools: Any, prefix: str = "") -> tuple[list[dict[str, Any]], list[str], list[str]]: diff --git a/tests/test_agent_evaluation_proxy.py b/tests/test_agent_evaluation_proxy.py index 38c7a6520..34d7ae3ce 100644 --- a/tests/test_agent_evaluation_proxy.py +++ b/tests/test_agent_evaluation_proxy.py @@ -551,6 +551,32 @@ async def test_native_client_discovery_call_and_namespace_output_are_allowed(lim ] +@pytest.mark.asyncio +async def test_native_discovery_accepts_multi_agent_collaboration_namespace() -> None: + """原生协作命名空间的合法动作可通过目录投影并保留审计记录。""" + outbound = [] + + def handler(request: httpx.Request) -> httpx.Response: + """接收投影后的协作目录,不访问真实模型服务。""" + outbound.append(json.loads(request.content)) + return httpx.Response(200, json=_completed()) + + tools = [{ + "type": "namespace", "name": "multi_agent_v1", + "tools": [{"type": "function", "name": name, "parameters": {}} + for name in ("wait_agent", "send_input")], + }] + payload = _native_payload() + payload["input"].append(_search_output(tools=tools)) + proxy = EvaluationModelProxy(SETTINGS, transport=httpx.MockTransport(handler)) + async with _client(proxy) as client: + response = await client.post("/v1/responses", json=payload) + assert response.status_code == 200 and outbound + assert proxy.snapshot()["model_requests"][0]["tool_catalogs"][-1]["retained_tools"] == [ + "multi_agent_v1.wait_agent", "multi_agent_v1.send_input", + ] + + @pytest.mark.parametrize("updates", [ {"execution": "server"}, {"call_id": None}, {"call_id": ""}, {"arguments": {"query": " "}}, {"arguments": {"query": "x", "sources": ["external"]}}, {"arguments": {"query": "x", "limit": 0}},