修复普通 action 批次带 plan update 时预检自相矛盾
批次预检对每个成员要求 provider_batch_plan_update 等于 batch.plan.plan_update, 而同一函数的另一条规则又要求非 planning 批次成员不得携带该字段;创建侧只为 plan.submit_gdd 填值,其余动作保持 None。于是「同一轮既调 update_agent_plan 又调工具」的普通批次同时踩中两条互斥规则,报「批次成员身份或顺序不匹配」。 该字段随 M1B-2 引入,master 无此标识符,做游戏路径同样中招。 预检期望值改为按批次类型分叉:planning v4 保持相等(:241 已对 actions[0] 校验过一次),非 planning 期望 None。创建侧与非 planning 分支语义本自洽,不动。 补一条走完 prepare 到读回的回归用例;去掉修复后可复现线上同一条错误串。 顺带 cargo fmt 掉上次 master 合并带进来的一处未格式化代码,并补一份第三方 Provider 兼容性缺陷说明。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+9
-1
@@ -290,6 +290,14 @@ pub(in crate::agent) fn validate_game_creator_agent_runtime_provider_action_batc
|
||||
}
|
||||
let mut waiting_confirmation_count = 0_usize;
|
||||
let mut rejected_count = 0_usize;
|
||||
// planning v4 批次把 plan update 冻结进 standalone member 用于恢复;普通批次的成员则被下方
|
||||
// 分支要求不得携带 planning recovery material。期望值必须按批次类型分叉,否则「同一轮里既
|
||||
// 调 update_agent_plan 又调工具」的普通批次会同时踩中两条互斥规则。
|
||||
let expected_member_plan_update = batch
|
||||
.planning_session_binding
|
||||
.is_some()
|
||||
.then(|| batch.plan.plan_update.clone())
|
||||
.flatten();
|
||||
for (index, pending) in batch.actions.iter().enumerate() {
|
||||
validate_agent_runtime_pending_tool_action_record(root, pending)?;
|
||||
if pending.agent_id != batch.agent_id
|
||||
@@ -305,7 +313,7 @@ pub(in crate::agent) fn validate_game_creator_agent_runtime_provider_action_batc
|
||||
|| pending.planned_repository_context_fingerprint
|
||||
!= batch.planned_repository_context_fingerprint
|
||||
|| pending.planning_session_binding != batch.planning_session_binding
|
||||
|| pending.provider_batch_plan_update != batch.plan.plan_update
|
||||
|| pending.provider_batch_plan_update != expected_member_plan_update
|
||||
|| usize::try_from(pending.action_index).unwrap_or(usize::MAX) != index
|
||||
|| pending.action != batch.plan.actions[index]
|
||||
{
|
||||
|
||||
+12
-11
@@ -2057,17 +2057,18 @@ mod tests {
|
||||
vec!["准备委派".to_string()],
|
||||
)
|
||||
.expect("start supervisor");
|
||||
let (_, _, supervisor_request, _, _) = build_game_creator_agent_background_tool_plan_request(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
&supervisor_state.session_id,
|
||||
&supervisor_state.run_id,
|
||||
&supervisor_state.current_task,
|
||||
&[],
|
||||
0,
|
||||
&catalog,
|
||||
)
|
||||
.expect("build supervisor request");
|
||||
let (_, _, supervisor_request, _, _) =
|
||||
build_game_creator_agent_background_tool_plan_request(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
&supervisor_state.session_id,
|
||||
&supervisor_state.run_id,
|
||||
&supervisor_state.current_task,
|
||||
&[],
|
||||
0,
|
||||
&catalog,
|
||||
)
|
||||
.expect("build supervisor request");
|
||||
assert!(!supervisor_request.messages[0]
|
||||
.content
|
||||
.contains(planning_brief_marker));
|
||||
|
||||
@@ -1425,3 +1425,99 @@ async fn agent_runtime_file_tools_cannot_modify_collaboration_policy_or_advance_
|
||||
}
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
/// 回归:模型在同一轮里既调 update_agent_plan 又调协作工具时,普通批次必须能通过预检。
|
||||
///
|
||||
/// `provider_batch_plan_update` 只为 planning v4 的 standalone member 恢复而冻结,普通批次成员
|
||||
/// 一律保持 None;预检期望值若不按批次类型分叉,就会同时要求成员「等于 plan update」和「不得携带
|
||||
/// planning recovery material」,让「更新计划 + 委派专业 Agent」这一最常规动作直接失败。
|
||||
#[tokio::test]
|
||||
async fn supervisor_collaboration_batch_keeps_plan_update_off_members() {
|
||||
let root = unique_project_path();
|
||||
init_local_game_project_at(
|
||||
&root,
|
||||
"project-collaboration-plan-update",
|
||||
"协作批次携带 plan update 测试",
|
||||
)
|
||||
.expect("project init");
|
||||
write_supervisor_collaboration_policy_at(
|
||||
&root,
|
||||
supervisor_collaboration_mixed_policy_for_test(),
|
||||
)
|
||||
.expect("write mixed collaboration policy");
|
||||
write_project_permission_policy_at(
|
||||
&root,
|
||||
ProjectPermissionPolicy {
|
||||
denied_commands: Vec::new(),
|
||||
confirm_commands: Vec::new(),
|
||||
agent_policies: BTreeMap::new(),
|
||||
},
|
||||
)
|
||||
.expect("write permissive tool policy");
|
||||
let run_id = "supervisor-collaboration-plan-update-run";
|
||||
let runtime = start_game_creator_agent_runtime_task_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
"边更新计划边编排协作",
|
||||
run_id,
|
||||
"agent-chat",
|
||||
"编排协作并同步计划",
|
||||
vec!["收齐专业交付".to_string()],
|
||||
)
|
||||
.expect("start supervisor runtime");
|
||||
let revision = read_game_creator_agent_runtime_project_revision(&root)
|
||||
.expect("read revision before plan update batch");
|
||||
let repository_fingerprint = "d".repeat(64);
|
||||
let mut plan = supervisor_collaboration_plan_for_test(vec![
|
||||
supervisor_collaboration_delegate_action_for_test("design-director", None),
|
||||
supervisor_collaboration_delegate_action_for_test("art-director", None),
|
||||
supervisor_collaboration_spawn_action_for_test(2),
|
||||
]);
|
||||
plan.plan_update = Some(AgentRuntimePlanUpdate {
|
||||
explanation: "拆解协作波次".to_string(),
|
||||
steps: vec![
|
||||
AgentRuntimePlanUpdateStep {
|
||||
step: "冻结根 Goal Contract".to_string(),
|
||||
status: "completed".to_string(),
|
||||
},
|
||||
AgentRuntimePlanUpdateStep {
|
||||
step: "委派专业 Agent".to_string(),
|
||||
status: "in_progress".to_string(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let preparation = prepare_game_creator_agent_runtime_provider_action_batch(
|
||||
&root,
|
||||
&runtime,
|
||||
"边更新计划边编排协作",
|
||||
&plan,
|
||||
&[],
|
||||
&revision,
|
||||
&repository_fingerprint,
|
||||
)
|
||||
.await
|
||||
.expect("prepare batch carrying plan update");
|
||||
let AgentRuntimeProviderActionBatchPreparation::Ready(batch) = preparation else {
|
||||
panic!("collaboration wave must form ready durable batch");
|
||||
};
|
||||
assert!(batch.plan.plan_update.is_some());
|
||||
assert!(batch.planning_session_binding.is_none());
|
||||
assert!(
|
||||
batch
|
||||
.actions
|
||||
.iter()
|
||||
.all(|pending| pending.provider_batch_plan_update.is_none()),
|
||||
"普通批次成员不得携带 planning recovery material",
|
||||
);
|
||||
|
||||
let recovered = read_game_creator_agent_runtime_provider_action_batch(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
)
|
||||
.expect("read back batch carrying plan update");
|
||||
assert_eq!(recovered.batch_id, batch.batch_id);
|
||||
assert_eq!(recovered.plan.plan_update, batch.plan.plan_update);
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
# 【技术说明】AGC 接第三方 Provider 的兼容性缺陷
|
||||
|
||||
- 日期:2026-08-19
|
||||
- 分支:`feat/five_min_design`(HEAD `c2d527387`)
|
||||
- 触发场景:在「做方案」入口输入「贪吃蛇」,项目总控 Agent 第一个请求就失败
|
||||
- 结论:**这不是策划链路的问题**。做游戏 / 做素材 / 做方案共用同一套 Provider 分发,任何一条路换成非官方端点都会立刻挂。共发现 4 个独立缺陷,均已取得直接证据;其中缺陷 4 是本分支自己引入的回归,同样会影响做游戏路径,已修复。
|
||||
|
||||
---
|
||||
|
||||
## 0. 摘要
|
||||
|
||||
| # | 缺陷 | 表现 | 性质 |
|
||||
|---|---|---|---|
|
||||
| 1 | 前端保存设置时把 `agentMode` 硬写成 `codex_app_server` | UI 里换 provider 只改了 `llm.*`,运行模式换不掉,且界面上看不到这个字段 | 产品缺陷 |
|
||||
| 2 | `codex_app_server` 模式把第三方端点喂给 codex | apiKind≠openai_responses 时秒挂;否则 413 + 工具误用,180 秒超时后留下待核对的孤儿请求 | 模式前提未被约束 |
|
||||
| 3 | `provider` 模式下 `tool_choice=required` 与 DeepSeek 思考模式互斥 | 首个 tool-plan 请求 400,整个 runtime 起不来 | 参数空间缺一个值 |
|
||||
| 4 | 普通 action 批次带 plan update 时,两条预检规则互斥 | 「更新计划 + 委派专业 Agent」同一轮返回就报「批次成员身份或顺序不匹配」 | **本分支回归**(已修) |
|
||||
|
||||
缺陷 1~3 叠加的结果:**当前代码里没有任何一组配置能让 DeepSeek 跑起来**。缺陷 4 与 provider 无关,换成 `gpt-5.6-terra` 打通 LLM 链路后才暴露出来。
|
||||
|
||||
---
|
||||
|
||||
## 1. 缺陷 1:`agentMode` 无法从 UI 切换
|
||||
|
||||
### 现象
|
||||
|
||||
在「Agent 设置 - 常用设置」里把 provider 换成 DeepSeek 并保存,配置文件里 `llm.baseUrl/model/apiKind` 都变了,但 `agentMode` 始终是 `codex_app_server`。界面上也找不到这个字段。
|
||||
|
||||
### 根因
|
||||
|
||||
[RuntimeConfigDialog.tsx:844](../../apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx:844) 的保存分支:
|
||||
|
||||
```ts
|
||||
const config = normalizeRuntimeConfigDraft(
|
||||
{
|
||||
...runtimeConfigDraft,
|
||||
agentMode: 'codex_app_server', // ← 无条件覆盖 draft
|
||||
editorApi: ...,
|
||||
mcpServers: ...,
|
||||
},
|
||||
allowAdvancedExternalEditorConfig,
|
||||
);
|
||||
```
|
||||
|
||||
弹窗里没有对应控件,`defaultRuntimeConfigDraft`(同文件 :68)也把它写死。于是:
|
||||
|
||||
- 用户改不了;
|
||||
- 就算手工改了配置文件,**下一次在弹窗里点保存又会被写回 `codex_app_server`**,而且整个 `llm` 块会被弹窗草稿覆盖。
|
||||
|
||||
### 生效配置的位置
|
||||
|
||||
`writable_game_creator_config_path()`([config.rs:1367](../../apps/ai-game-creator-shell/src-tauri/src/config.rs:1367))→ runtime config dir → Tauri `app_config_dir`:
|
||||
|
||||
```
|
||||
%APPDATA%\world.genarrative.ai-game-creator\game-creator.config.json
|
||||
```
|
||||
|
||||
注意 `agentMode` 缺省值是 `codex_app_server`([main.rs:1252](../../apps/ai-game-creator-shell/src-tauri/src/main.rs:1252)),所以**旧配置文件里没有这个 key 时同样落到 codex 模式**。
|
||||
|
||||
---
|
||||
|
||||
## 2. 缺陷 2:`codex_app_server` 模式 + 第三方端点
|
||||
|
||||
### 2.1 分发关系
|
||||
|
||||
Provider 分发按全局 `agentMode` 分支([provider_retry.rs:1742](../../apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/provider_retry.rs:1742)),与入口(做游戏 / 做素材 / 做方案)无关。`codex_app_server` 模式下 AGC 不直连 LLM,而是拉起本机 `codex` 二进制当 app-server:
|
||||
|
||||
```
|
||||
codex.exe app-server --stdio -c mcp_servers={} -c web_search="disabled"
|
||||
-c agents.enabled=false
|
||||
--disable apps --disable browser_use --disable computer_use --disable goals
|
||||
--disable image_generation --disable plugins --disable shell_tool
|
||||
--disable unified_exec --disable workspace_dependencies ...
|
||||
-c model_provider="genarrative_agc"
|
||||
-c model_providers.genarrative_agc.base_url="https://api.deepseek.com"
|
||||
-c model_providers.genarrative_agc.env_key="GENARRATIVE_AGC_CODEX_API_KEY"
|
||||
-c model_providers.genarrative_agc.wire_api="responses"
|
||||
```
|
||||
|
||||
(取自 2026-08-19 13:35 / 13:44 / 13:48 三个存活进程的命令行。)
|
||||
|
||||
### 2.2 `apiKind=openai_chat` → 秒挂
|
||||
|
||||
[codex_app_server.rs:463](../../apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs:463) 的门禁在任何 HTTP 请求之前就返回 `InvalidConfig`:
|
||||
|
||||
> `codex_app_server 仅支持 apiKind=openai_responses;当前 apiKind=openai_chat,请改用 provider 模式`
|
||||
|
||||
**取证方式**:agent.db 里只留 `errorChars` + `errorSha256`(原文被 [loop_orchestration.rs:536](../../apps/ai-game-creator-shell/src-tauri/src/agent/generation/loop_orchestration.rs:536) 哈希掉了)。13:33:45 那次 run(`gameagent-a21e1b2a`)记录为 `errorChars=156`、`errorSha256=a9481526dab648ad70e7800446d9026a7abcde068f44c5c2bc5e466fd2b6850e`。按代码把公开错误串重建为
|
||||
|
||||
```
|
||||
agentLlm.project-supervisor 后台 Agent 工具计划调用 LLM 失败:kind=invalid-config fingerprint=<sha256(原文)> chars=<原文字数>
|
||||
```
|
||||
|
||||
后 sha256 完全命中,确认内层原文就是上面那句门禁。
|
||||
|
||||
### 2.3 `apiKind=openai_responses` → 413,然后超时
|
||||
|
||||
13:41 那次 run(`gameagent-edfa1ba1`)的 `agent.runtime.plan.provider_usage` 记录 `activeMillis: 180786`,正好等于当时的 `requestTimeoutMs: 180000`,随后写入 `agent.runtime.provider_request.needs_reconciliation` —— 也就是 UI 上的「待核对 / 确认孤立 Provider 请求后再恢复当前 run」。
|
||||
|
||||
codex 自己的 tracing 落在隔离 CODEX_HOME 里(`%TEMP%\genarrative-agc-codex-app-server-*\codex-home\logs_2.sqlite`),两类记录:
|
||||
|
||||
**(a) 上游 413**
|
||||
|
||||
```
|
||||
WARN codex_core::responses_retry ... model=deepseek-v4-flash:
|
||||
stream disconnected - retrying ...
|
||||
unexpected status 413 Payload Too Large:
|
||||
<html><head><title>413 Request Entity Too Large</title></head>
|
||||
<hr><center>openresty</center></html>
|
||||
, url: https://api.deepseek.com/responses
|
||||
```
|
||||
|
||||
codex 组的请求体(自带 system prompt + 全套工具 schema)超过了 DeepSeek 网关的 body 上限。codex 把 413 归类成「stream disconnected」并重试——11 秒内 5 次,turn 永远走不到终态。
|
||||
|
||||
**(b) 模型把 `view_image` 当文件读取工具**
|
||||
|
||||
52 条 `ERROR codex_core::tools::router`,形状一致:
|
||||
|
||||
```
|
||||
tool_name=view_image
|
||||
error=unable to locate image at `...\gameagent-1d26566b\memory\README.md`: (os error 2)
|
||||
```
|
||||
|
||||
目标全是 `memory/README.md`、`memory/gameplay.md`、`memory/play-memory.md`、`exports/index.html` 等文本文件。原因是 AGC 要求「AGC Runtime 是唯一 ToolHost」,把 codex 侧能读文件的工具全 `--disable` 掉了,模型只好拿剩下的 `view_image` 去读 `.md`。每次失败后模型继续试,对话越滚越长,反过来把 (a) 的请求体喂得更大。
|
||||
|
||||
### 2.4 结论
|
||||
|
||||
`codex_app_server` 模式是围绕自家 `dev.genarrative.world/gpt/v1` + `gpt-5.6-sol` + AGC 独占 ToolHost 这套组合设计的。换第三方端点时 (a)(b) 都不是配置能绕开的:413 是对方网关的硬限制,工具误用是模型对着被裁剪过的 codex 工具集乱猜。
|
||||
|
||||
**当前代码没有任何地方阻止这种组合**——用户只会等 180 秒然后拿到一个「待核对」。
|
||||
|
||||
---
|
||||
|
||||
## 3. 缺陷 3:`provider` 模式 + DeepSeek 的 `tool_choice`
|
||||
|
||||
把 `agentMode` 手工改成 `provider` 后重跑,14:03:38 仍然失败。这次原文有落盘(provider 模式会写 [logs/llm-raw](../../apps/ai-game-creator-shell/src-tauri/logs/llm-raw),文件 `1787148218861-33480-000001-upstream_status_failed.output.txt`):
|
||||
|
||||
```json
|
||||
{"error":{"message":"Thinking mode does not support this tool_choice",
|
||||
"type":"invalid_request_error","code":"invalid_request_error"}}
|
||||
```
|
||||
|
||||
### 复现矩阵(直接打 DeepSeek,非流式)
|
||||
|
||||
| 请求 | 结果 |
|
||||
|---|---|
|
||||
| `tool_choice: "required"` | **400** Thinking mode does not support this tool_choice |
|
||||
| `tool_choice: "auto"` | 200 |
|
||||
| `tool_choice: "none"` | 200 |
|
||||
| `tool_choice: "required"` + `reasoning.effort: "none"` | **200**,且正常返回 `function_call` |
|
||||
| `tool_choice: "required"` + `reasoning.effort: "minimal"` | 400 |
|
||||
| `tool_choice: "required"` + `thinking: {type:"disabled"}` | 400 |
|
||||
|
||||
`/chat/completions` 与 `/responses` 两条 wire、`deepseek-v4-flash` 与 `deepseek-v4-pro` 两个模型表现完全一致。即:**DeepSeek 支持强制工具调用,但必须先关掉思考模式,而唯一能关掉它的开关是 `reasoning.effort: "none"`。**
|
||||
|
||||
带 3 个工具、`effort=none`、`tool_choice=required` 连打 3 次,全部返回干净的 `function_call`(其中一次返回两个),行为稳定。
|
||||
|
||||
### 根因
|
||||
|
||||
两个硬约束正好互斥:
|
||||
|
||||
1. Runtime 主循环的 tool-plan 请求写死 `LlmToolChoice::Required`
|
||||
([provider_tool_plan.rs:1482](../../apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_tool_plan.rs:1482)、
|
||||
[provider_request_builders.rs:462](../../apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs:462) 与 :554、
|
||||
[autonomous_policy.rs:1971](../../apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs:1971))。这是协议要求,Agent 必须返回工具调用。
|
||||
2. `reasoningEffort` 的取值只有 `default / low / medium / high / max`
|
||||
([platform-llm lib.rs:195](../../server-rs/crates/platform-llm/src/lib.rs:195)、[config.rs:145](../../apps/ai-game-creator-shell/src-tauri/src/config.rs:145)、
|
||||
[types.ts:605](../../apps/ai-game-creator-shell/src/app/types.ts:605))。**没有 `none`**,而 `default` 的语义是整个字段不发 → 思考模式默认开着。
|
||||
|
||||
于是第一个请求必挂。
|
||||
|
||||
---
|
||||
|
||||
## 4. 缺陷 4:普通 action 批次带 plan update 时预检自相矛盾(已修)
|
||||
|
||||
换成 `gpt-5.6-terra` 后 LLM 链路终于通了(loop 1 成功冻结根 Goal Contract,计划推到 1/4),但 14:15:11 挂在第二步「委派 project-planning 产出 Fast GDD」。这一条与 provider 无关,是**本分支自己引入的回归**。
|
||||
|
||||
### 现象
|
||||
|
||||
agent.db 里这条错误没有被哈希,是明文:
|
||||
|
||||
```
|
||||
Provider action 批次预检失败:
|
||||
Agent Runtime Provider action 批次成员身份或顺序不匹配:index=0
|
||||
```
|
||||
|
||||
两轮的 `tool_plan.protocol` 记录:
|
||||
|
||||
| loop | 模型返回的 function call | 结果 |
|
||||
|---|---|---|
|
||||
| 1 | `runtime_tool_agent_goal_contract` | ok |
|
||||
| 2 | `update_agent_plan` + `runtime_tool_agent_delegate` | 预检失败 |
|
||||
|
||||
即「更新计划 + 委派专业 Agent」同一轮返回——总控最常规的动作。
|
||||
|
||||
### 根因:两条校验规则互斥
|
||||
|
||||
[provider_batch_ledger.rs:316](../../apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_batch_ledger.rs:316) 对**每个**批次成员要求 `pending.provider_batch_plan_update == batch.plan.plan_update`;
|
||||
同一函数 [:358](../../apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_batch_ledger.rs:358) 又对**非 planning** 批次成员要求该字段必须是 `None`("非 planning Provider action 批次成员不能携带 planning recovery material")。
|
||||
|
||||
而创建侧 [provider_action_batch.rs:636](../../apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_action_batch.rs:636) 只给 `plan.submit_gdd` 这一个工具填该字段,其余动作保持默认 `None`(同文件 :279)。
|
||||
|
||||
于是「非 planning 批次 + 该轮带 plan update」被同时要求"等于 Some"和"必须是 None",无解,前者先命中。
|
||||
|
||||
### 归属
|
||||
|
||||
```
|
||||
git log -S provider_batch_plan_update → 27c3eb847 立项策划:完成 M1B-2 GDD 提交与恢复
|
||||
master 中该标识符出现 0 次
|
||||
```
|
||||
|
||||
触发条件是「生成持久化批次 + 该轮带 plan update + 首动作不是 `plan.submit_gdd`」,**做游戏路径同样会撞**——违反本分支「做游戏/做素材与 master 一致」的准则。
|
||||
|
||||
测试没拦住的原因:`provider_batch_plan_update` 相关用例(pending_recovery.rs:2177 / 2552)全是 planning 提交路径的夹具,缺「非 planning 批次 + plan update」这个组合。
|
||||
|
||||
### 修法(已应用)
|
||||
|
||||
预检期望值按批次类型分叉:planning v4 批次保持相等(该约束在 [:241](../../apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_batch_ledger.rs:241) 已对 `actions[0]` 校验过一次),非 planning 批次期望 `None`。
|
||||
|
||||
```rust
|
||||
let expected_member_plan_update = batch
|
||||
.planning_session_binding
|
||||
.is_some()
|
||||
.then(|| batch.plan.plan_update.clone())
|
||||
.flatten();
|
||||
```
|
||||
|
||||
创建侧与 :358 的语义本来自洽,不动。
|
||||
|
||||
回归测试:`tests::collaboration::policy_batches::supervisor_collaboration_batch_keeps_plan_update_off_members`。已验证去掉修复后该用例复现出与线上完全一致的错误串。
|
||||
|
||||
---
|
||||
|
||||
## 5. 附:MiniMax 的强制工具调用不可靠
|
||||
|
||||
作为备选测过 `api.minimaxi.com/anthropic` + `MiniMax-M3` + `tool_choice:{type:"any"}`(AGC 的 `Required` 在 anthropic wire 上映射成 `any`):
|
||||
|
||||
- 请求本身 200,非流式与流式都通;
|
||||
- 但 3 次试验只有 1 次真的返回 `tool_use`,另外 2 次是 `stop_reason: end_turn` 的纯文本。
|
||||
|
||||
也就是说 MiniMax 的 anthropic 兼容层**不强制**执行 `tool_choice: any`。AGC 有 format repair 兜底(`format_repair_attempts`),但会白烧轮次,不建议作为主力。
|
||||
|
||||
---
|
||||
|
||||
## 6. 影响面
|
||||
|
||||
- 三个入口(做游戏 / 做素材 / 做方案)共用同一套分发,缺陷 1~3 与入口无关;
|
||||
- 缺陷 1 让绝大多数用户根本走不到 `provider` 模式;
|
||||
- 缺陷 2 的失败形态最差:不是报错而是等满超时 + 留下待核对的孤儿请求 + 池化的 codex 进程;
|
||||
- 缺陷 3 让 `provider` 模式对「思考型第三方模型」整体不可用(DeepSeek V4 全系);
|
||||
- 缺陷 4 与 provider 无关,只要模型在同一轮里既更新计划又调用协作工具就会撞,做游戏路径同样中招。
|
||||
|
||||
---
|
||||
|
||||
## 7. 建议改动清单
|
||||
|
||||
### 已完成
|
||||
|
||||
0. **缺陷 4 的预检分叉 + 回归测试**(见 §4)。这是分支回归、且波及做游戏路径,不等排期,已直接修在工作区。
|
||||
|
||||
### P0 —— 让第三方 provider 可用
|
||||
|
||||
1. **`reasoningEffort` 增加 `none`**
|
||||
- [platform-llm lib.rs:195](../../server-rs/crates/platform-llm/src/lib.rs:195):`LlmResponseReasoningEffort` 加 `None` 变体,`as_str()` 返回 `"none"`;注意仓库内对该枚举有多处 exhaustive match(`Max` 刚加时踩过),需一并补齐。
|
||||
- [config.rs:145](../../apps/ai-game-creator-shell/src-tauri/src/config.rs:145) `parse_game_creator_llm_reasoning_effort` 接受 `none`,同步 :155 的错误文案。
|
||||
- [types.ts:605](../../apps/ai-game-creator-shell/src/app/types.ts:605) `gameCreatorLlmReasoningEfforts` 加 `'none'`;RuntimeConfigDialog 的默认表(:40~:65)与下拉项同步。
|
||||
- 验收:`reasoningEffort: "none"` + DeepSeek + `provider` 模式,做方案能跑到第一个工具调用。
|
||||
|
||||
2. **保存设置时不要硬写 `agentMode`**
|
||||
- [RuntimeConfigDialog.tsx:844](../../apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx:844):删掉那行覆盖。
|
||||
- 二选一:在常用设置里暴露「运行模式」选择;或按 `baseUrl` 推断(非官方端点自动落 `provider`)。推荐后者 + 高级设置里可覆盖。
|
||||
|
||||
### P1 —— 让失败可解释
|
||||
|
||||
3. **禁止「`codex_app_server` + 非官方 baseUrl」这一组合**
|
||||
在配置校验期([config.rs:350](../../apps/ai-game-creator-shell/src-tauri/src/config.rs:350) `game_creator_codex_app_server_llm_route_error` 附近)直接拒绝并给出明确文案,而不是让用户等 180 秒超时、再手工核对孤儿请求。
|
||||
|
||||
4. **超时后的资源回收**
|
||||
turn 超时后 codex app-server session 仍留在池里(本次留下 3 个)。至少在 `needs_reconciliation` 时把对应 session 作废。
|
||||
|
||||
### P2 —— 排障体验
|
||||
|
||||
5. `reasoningEffort` 没有传到 codex(日志里 `codex.turn.reasoning_effort=default`),配置与实际行为不一致。
|
||||
6. 失败原文全部 sha256 化,本次定位缺陷 2 只能靠"按代码重建错误串再比对哈希"。建议 dev 构建下把公开错误串明文写进 agent.db,或至少把 `kind=` 留在 UI 的运行详情里。
|
||||
|
||||
---
|
||||
|
||||
## 8. 未验证 / 待确认
|
||||
|
||||
- 官方端点 + `provider` 模式:`gpt-5.6-terra` 已验证能跑通 loop 1(工具调用、Goal Contract 冻结、计划更新都正常),修掉缺陷 4 后能走多远还没实测。`gpt-5.6-sol` 未验证——仓库里 `apps/ai-game-creator-shell/game-creator.config.json` 的 key 打过去是 401(占位值)。
|
||||
- `effort=none` 下 DeepSeek 做策划的**质量**(本次只验证了协议层能跑通)。
|
||||
- DeepSeek 网关 413 的具体阈值,以及 `provider` 模式下 AGC 自组的请求体是否也会触顶(本次 provider 模式的请求约 39 KB,未触发)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 现场证据索引
|
||||
|
||||
| 证据 | 位置 |
|
||||
|---|---|
|
||||
| 秒挂 run(openai_chat) | `~/Documents/Genarrative GameAgent/gameagent-a21e1b2a/.agent/agent.db` |
|
||||
| 超时 + 待核对 run | `~/Documents/Genarrative GameAgent/gameagent-edfa1ba1/.agent/agent.db` |
|
||||
| codex 侧 413 与 view_image 报错 | `%TEMP%\genarrative-agc-codex-app-server-*\codex-home\logs_2.sqlite`(表 `logs`,按 `level in ('ERROR','WARN')` 查) |
|
||||
| provider 模式 400 原文 | `apps/ai-game-creator-shell/src-tauri/logs/llm-raw/1787148218861-33480-000001-upstream_status_failed.{input.json,output.txt}` |
|
||||
| 缺陷 4 的批次预检失败 run | `~/Documents/Genarrative GameAgent/gameagent-9a524132/.agent/agent.db`(`agent.runtime.context.failed` 明文错误 + 两轮 `tool_plan.protocol` 的 `functionNames`) |
|
||||
| 生效配置 | `%APPDATA%\world.genarrative.ai-game-creator\game-creator.config.json`(本次已手工把 `agentMode` 改为 `provider`,原件备份为同名 `.bak-agentmode`) |
|
||||
Reference in New Issue
Block a user