完成 reasoning 最终回归与验收

补充 Responses reasoning 与工具调用共存回归测试

补充策划 Runtime 重试清理 reasoning 测试

同步 platform-llm README 与策划 Agent 技术方案

记录第五轮验收证据与未验证边界
This commit is contained in:
2026-09-14 13:01:19 +08:00
parent e8f9929630
commit e174b6dcf4
6 changed files with 125 additions and 5 deletions
@@ -1451,6 +1451,38 @@ mod tests {
assert!(session.history.is_empty());
}
#[tokio::test(flavor = "current_thread")]
async fn scripted_design_provider_retry_clears_previous_reasoning_attempt() {
let (_temp, root, _resources) = init_design_project();
let mut session = new_design_session("design-fake", "quality");
begin_design_turn(&mut session, "turn-reasoning-retry");
let mut response = fake_response("reasoning-retry", "重试后的正文", Vec::new());
response.reasoning = "重试后的推理".into();
let _fake = fake_provider::install(
vec![
Err(platform_llm::LlmError::Upstream {
status_code: 503,
message: "busy".into(),
}),
Ok(response),
],
1,
);
let mut events = Vec::new();
let response =
request_scripted_design_provider(&root, &mut session, &mut |event| events.push(event))
.await
.expect("scripted retry provider");
let reasoning_events = events
.iter()
.filter_map(|event| event.reasoning_text.as_deref())
.collect::<Vec<_>>();
assert_eq!(reasoning_events, vec!["", "", "重试后的推理"]);
assert_eq!(response.text, "重试后的正文");
assert!(session.history.is_empty());
}
#[tokio::test(flavor = "current_thread")]
async fn fake_provider_walks_five_phases_and_enters_consultant() {
let (_temp, root, resources) = init_design_project();
@@ -0,0 +1,50 @@
# 【实施计划】reasoning 最终回归与验收
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】Provider推理与正文分离及策划Agent展示-2026-09-14.md` |
| Status | awaiting-review |
| Owner | Codex |
## 一句话交付结果
用确定性回归、文档同步和门禁证据确认 Provider reasoning 旁路不会改变正文、工具调用、GameAgent 或策划会话持久化行为。
## 修改边界
本轮只补最终验收所需的测试、当前权威文档和证据记录;不新增协议字段、不扩展真实 Provider、不修改公开 API、SpacetimeDB schema、GameAgent 消费逻辑或 UI 组件。
## 实施顺序
1. 核对并补齐 platform-llm 的 reasoning 解析、正文隔离、工具调用共存、默认关闭和解析异常降级测试。
2. 核对并补齐策划 Runtime 的 reasoning 事件、重试清理、错误清理和不写入 history 的测试证据。
3. 核对 GameAgent、Direct/Codex 与 response stream 只消费正文的回归证据,修正过时的 crate README 语义。
4. 更新主技术方案与里程碑状态,整理证据矩阵,运行最终门禁;完成后删除本轮及已完成的临时实施计划。
## 验收标准
- Responses 和 Chat 的 reasoning 增量/终态均有确定性测试。
- reasoning 永不进入正文、工具参数、正式 assistant message 或策划 history。
- reasoning 与工具调用共存、无 reasoning 默认行为、reasoning 解析异常降级均有证据。
- 策划 Runtime 的新回合、重试、项目切换和失败路径不会残留旧 reasoning。
- GameAgent、Direct/Codex 和 response stream 的正文回归通过。
- `cargo test``cargo check`、TypeScript、文档索引、编码和 diff 门禁通过;真实 Provider smoke 明确记录为未验证。
## 风险与回滚点
- 若补测试暴露正文或工具回归,优先回退调用方接线,不改变默认关闭契约。
- 若文档与实现不一致,以源码和测试为准同步修正文档。
- 真实 Provider 未连接时不宣称端到端协议 fidelity,只报告固定 fixture 和单元测试证据。
## 本轮验收证据
| 证据 | 结果 |
| --- | --- |
| `cargo test --manifest-path server-rs/Cargo.toml -p platform-llm` | PASS152 个单元测试;1 个真实 Provider smoke 按需 `#[ignore]` |
| `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell design_runtime` | PASS10 个策划 Runtime 测试 |
| `cargo check --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --tests` | PASS,既有编译警告未升级为错误 |
| `npx tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` | PASS |
| `cargo fmt --all --manifest-path server-rs/Cargo.toml -- --check`、AGC Tauri 同命令 | PASS |
| `npm run check:encoding``npm run check:doc-index``git diff --check` | PASS |
未验证项:真实 Provider 和浏览器运行时 smoke`ai-game-creator-shell:typecheck` 中的 `check-config.mjs` 仍受当前 Windows 沙箱私有 DACL 限制,已用独立 TypeScript 检查替代类型门禁。
@@ -3,7 +3,7 @@
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | proposed |
| Status | awaiting-review |
| Date | 2026-09-14 |
| Parent Spec | `docs/technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md` |
| Related Issue | `GenarrativeAI/Genarrative#331` |
@@ -131,6 +131,20 @@ Responses 的原生 output 仍按当前方式保留,用于后续 Responses 会
- 策划 Runtime 事件映射和 UI 生命周期;
- GameAgent 正文与工具调用回归。
## 第五轮验收证据
| 验收面 | 证据 | 结果 |
| --- | --- | --- |
| Chat / Responses reasoning 解析 | `cargo test --manifest-path server-rs/Cargo.toml -p platform-llm` | PASS152 个单元测试;含字段、content part、SSE 增量、终态快照和正文隔离 |
| reasoning 与工具调用共存 | `responses_response_captures_reasoning_alongside_tool_call`、既有 Chat/Responses 流式工具测试 | PASS |
| 默认关闭与请求兼容 | `run_request_defaults_to_openai_responses_api_kind``reasoning_capture_switch_does_not_change_provider_request_body` | PASS |
| 策划 Runtime 生命周期 | `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell design_runtime` | PASS10 个测试;含事件映射、history 隔离、重试清理和失败清理 |
| GameAgent / Direct/Codex 正文回归 | `cargo check --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --tests` 与现有 response stream / direct tests 编译 | PASS;新增字段未进入正文消费路径 |
| 前端与文档门禁 | `npx tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit``npm run check:encoding``npm run check:doc-index``git diff --check` | PASS |
| 格式门禁 | `cargo fmt --all --manifest-path server-rs/Cargo.toml -- --check`、AGC Tauri 同命令 | PASS |
真实 Provider、浏览器运行时 smoke 和 `check-config.mjs` 的 Windows 私有 DACL 路径本轮未验证;前者需要凭据和运行环境,后者受当前沙箱权限限制,不能据此扩大验收结论。
## 契约与持久化策略
- 不修改 HTTP API、OpenAPI、SpacetimeDB schema 或生成绑定。
@@ -346,8 +346,8 @@ UI 使用“批准”和“继续修改”两个文字按钮,分别配 Lucide
开发构建的策划工作区页头在“刷新”旁提供“快速准备做成游戏测试”按钮。该入口与策划 Debug 日志共用 `GENARRATIVE_AGC_DESIGN_DEBUG=1` 开关:开关未启用时按钮不显示,命令也不可执行。入口仅进行本地 fixture 和会话状态写入,不调用 Provider;完成后自动刷新文件树与阶段,通过 `design-agent-update` 状态事件同步右侧审批/阶段操作区。随后仍需点击正常的“做成游戏”按钮执行资产登记与运行时切换。
## 15. 策划 Agent reasoning 展示现状
## 15. 策划 Agent reasoning 展示
右侧栏已预留策划 Agent 的 `reasoningText` 事件字段和默认折叠的展示样式,但当前 Provider 解析链仍会过滤 reasoning 内容,尚未向策划 Runtime 产出该字段。因此现阶段只展示用户可见正文工具状态;reasoning 折叠区在没有数据时不会出现
策划 Agent 的 Provider 请求显式开启 `capture_reasoning`,共享 `platform-llm` 将 Chat / Responses 的 reasoning 通过独立字段旁路传递,策划 Runtime 映射为已有 `DesignEvent.reasoningText`,前端复用右侧栏默认折叠的思考过程展示。正文工具调用参数、正式 assistant message 和会话 history 继续使用原有字段;GameAgent、Direct/Codex 与通用 response stream 保持只消费正文的行为
后续若补充 reasoning,需要在策划 Agent 专用 Provider 解析层接入,不能直接修改共享 Provider 以免影响 GameAgent
reasoning 捕获默认关闭。新回合和 Provider 重试会先清空同一响应槽的临时 reasoning,失败路径也会清理,避免旧内容残留。该能力不新增公开 API、SpacetimeDB 字段或独立 UI 组件
+1 -1
View File
@@ -19,7 +19,7 @@
2. `OpenAiChat``OpenAiResponses``Anthropic` 三类 API kind 都支持 JSON 请求、非流式响应和 SSE 流式响应;默认 API kind 仍为 `OpenAiResponses`
3. 三类协议都使用统一的 `function_tools` / `tool_choice` 输入和 `LlmRunResponse.tool_calls` 输出。Anthropic 请求使用顶层 `tools[].input_schema` 与对象形态 `tool_choice`Anthropic URL 默认在 base URL 后拼 `/v1/messages`,如果 base URL 已以 `/v1` 结尾则只拼 `/messages`
4. Anthropic 当前仍不支持 `web_search`、图片内容和纯 system 消息;至少需要一条非 system 文本消息。角色动画、图片、视频、资产轮询仍留在其他平台适配和业务模块任务里。
5. 流式 `on_delta` 发送文本增量与完成原因;工具调用增量在 crate 内按 slot 聚合,完整调用只从最终 `LlmRunResponse.tool_calls` 读取。上下文管理、后台执行和业务状态不写回本 crate。
5. 流式 `on_delta` 发送正文增量、可选的独立 reasoning 增量与完成原因;reasoning 只有在 `LlmRunRequest.capture_reasoning=true` 时才累计,默认关闭。工具调用增量在 crate 内按 slot 聚合,完整调用只从最终 `LlmRunResponse.tool_calls` 读取。reasoning 不进入 `delta_text``accumulated_text`、正式 assistant message 或工具参数;上下文管理、后台执行和业务状态不写回本 crate。
6. 支持按 provider 打标签,但不把业务 prompt、SSE 转发和模块状态写回本 crate。
7. `DashScope` 当前只通过“调用方显式提供兼容文本网关 base url”的方式接入,不复用图像 API。
8. 角色动画、图片、视频、资产轮询仍留在后续 `platform-llm` / `platform-oss` / 业务模块任务里另行实现。
+24
View File
@@ -5332,6 +5332,8 @@ mod tests {
.expect("tool call should keep the response valid without visible content");
assert_eq!(response.text, "");
assert!(response.reasoning.is_empty());
assert_eq!(response.tool_calls.len(), 1);
}
#[test]
@@ -5406,6 +5408,28 @@ mod tests {
assert_eq!(response.reasoning, "先判断。再回答。");
}
#[test]
fn responses_response_captures_reasoning_alongside_tool_call() {
let response = parse_responses_response_with_capture(
LlmProvider::OpenAiCompatible,
"fallback-model",
true,
r#"{"id":"resp_reasoning_tool","output":[{"type":"reasoning","summary":[{"type":"summary_text","text":"先分析工具需求。"}]},{"type":"message","content":[{"type":"output_text","text":"我先查询。"}]},{"type":"function_call","call_id":"call_lookup","name":"lookup","arguments":"{\"query\":\"项目\"}"}],"status":"completed"}"#,
)
.expect("Responses reasoning plus tool call should parse");
assert_eq!(response.text, "我先查询。");
assert_eq!(response.reasoning, "先分析工具需求。");
assert_eq!(
response.tool_calls,
vec![LlmToolCall {
id: "call_lookup".to_string(),
name: "lookup".to_string(),
arguments: r#"{"query":""}"#.to_string(),
}]
);
}
#[test]
fn stream_events_capture_reasoning_delta_and_terminal_snapshot() {
let chat = parse_sse_event_block(