diff --git a/apps/ai-game-creator-shell/src-tauri/src/main.rs b/apps/ai-game-creator-shell/src-tauri/src/main.rs index 536c749f6..19dcf1467 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -1439,6 +1439,7 @@ async fn request_llm_game_draft_with_client( "", "", "", + None, ) .await } @@ -1606,6 +1607,7 @@ async fn run_game_creator_agent_loop_at( &findings_markdown, &group_briefs_markdown, &read_optional_text(&root.join(&agenda.relative_path))?, + progress, ) .await { @@ -1788,6 +1790,7 @@ async fn request_generator_game_draft_with_client( findings_markdown: &str, group_briefs_markdown: &str, agenda_markdown: &str, + progress: Option<&AgentProgressEmitter<'_>>, ) -> Result { let system_prompt = game_creator_system_prompt(); let user_prompt = game_creator_generator_user_prompt( @@ -1799,21 +1802,43 @@ async fn request_generator_game_draft_with_client( group_briefs_markdown, agenda_markdown, ); - let request = LlmTextRequest::new(vec![ - LlmMessage::system(system_prompt), - LlmMessage::user(user_prompt.clone()), - ]) - .with_max_tokens(GAME_CREATOR_LLM_MAX_OUTPUT_TOKENS); - let response = match request_game_creator_llm_text(client, request).await { - Ok(response) => response, - Err(error) => { - // LLM 调用失败(如 “LLM 返回内容为空”)时,把本次输入连同错误一并落盘,便于复现定位。 - persist_llm_game_draft_error_input( - system_prompt, - user_prompt.as_str(), - &error.to_string(), - ); - return Err(format!("LLM 生成失败:{error}")); + // deepseek-v4-pro 等推理模型偶发返回空 content(HTTP 200、completion_tokens=0, + // 参见 DeepSeek-V3 issue #1453)。这类空返回是上游瞬时故障,原样重发通常即可恢复, + // 因此仅对 EmptyResponse 最多重试 3 次(含首次共 4 次请求),其它错误不重试。 + const MAX_EMPTY_RETRIES: u32 = 3; + let mut empty_retries = 0u32; + let response = loop { + let request = LlmTextRequest::new(vec![ + LlmMessage::system(system_prompt), + LlmMessage::user(user_prompt.clone()), + ]) + .with_max_tokens(GAME_CREATOR_LLM_MAX_OUTPUT_TOKENS); + match request_game_creator_llm_text(client, request).await { + Ok(response) => break response, + Err(platform_llm::LlmError::EmptyResponse) if empty_retries < MAX_EMPTY_RETRIES => { + empty_retries += 1; + eprintln!( + "llm.chat.generator.empty-response 重试 {empty_retries}/{MAX_EMPTY_RETRIES}(上游返回空 content,原样重发)" + ); + // 同步推送到 App 进度面板,便于在界面上看到重试(无需盯命令行)。 + emit_agent_progress( + progress, + "llm.generator.empty_retry", + &format!( + "Generator 收到空返回(DeepSeek 偶发),正在自动重试 {empty_retries}/{MAX_EMPTY_RETRIES}" + ), + ); + continue; + } + Err(error) => { + // 调用失败(含空返回重试耗尽)时,把本次输入连同错误一并落盘,便于复现定位。 + persist_llm_game_draft_error_input( + system_prompt, + user_prompt.as_str(), + &error.to_string(), + ); + return Err(format!("LLM 生成失败:{error}")); + } } }; // 先把原始返回落盘,再解析;解析失败(如输出截断)时仍能从仓库里拿到完整原文。