diff --git a/src/gateway/session.rs b/src/gateway/session.rs index 5e7f386..c93fded 100644 --- a/src/gateway/session.rs +++ b/src/gateway/session.rs @@ -1045,7 +1045,7 @@ mod tests { HashSet::new(), crate::config::TaskConfig::default(), crate::config::SubagentsConfig::default(), - crate::config::MemoryMaintenanceConfig::default(), + test_maintenance_config(), Some(24), crate::mcp::McpConfig::default(), ) @@ -1101,7 +1101,7 @@ mod tests { HashSet::new(), crate::config::TaskConfig::default(), crate::config::SubagentsConfig::default(), - crate::config::MemoryMaintenanceConfig::default(), + test_maintenance_config(), Some(24), crate::mcp::McpConfig::default(), ) @@ -1171,7 +1171,7 @@ mod tests { HashSet::new(), crate::config::TaskConfig::default(), crate::config::SubagentsConfig::default(), - crate::config::MemoryMaintenanceConfig::default(), + test_maintenance_config(), Some(24), crate::mcp::McpConfig::default(), ) @@ -1211,6 +1211,14 @@ mod tests { assert!(scheduled_prompt.content.contains("你是邮箱待办同步助手。")); } + /// 测试专用的 MemoryMaintenanceConfig,降低 min_memories_to_keep 以便于单条记忆测试 + fn test_maintenance_config() -> crate::config::MemoryMaintenanceConfig { + crate::config::MemoryMaintenanceConfig { + min_memories_to_keep: 1, + ..Default::default() + } + } + #[tokio::test] async fn test_summarize_memory_maintenance_for_scope_uses_model_output() { let mock_response_content = serde_json::to_string(&json!({ @@ -1259,7 +1267,7 @@ mod tests { HashSet::new(), crate::config::TaskConfig::default(), crate::config::SubagentsConfig::default(), - crate::config::MemoryMaintenanceConfig::default(), + test_maintenance_config(), Some(24), crate::mcp::McpConfig::default(), ) @@ -1349,7 +1357,7 @@ mod tests { HashSet::new(), crate::config::TaskConfig::default(), crate::config::SubagentsConfig::default(), - crate::config::MemoryMaintenanceConfig::default(), + test_maintenance_config(), Some(24), crate::mcp::McpConfig::default(), ) @@ -1438,7 +1446,7 @@ mod tests { HashSet::new(), crate::config::TaskConfig::default(), crate::config::SubagentsConfig::default(), - crate::config::MemoryMaintenanceConfig::default(), + test_maintenance_config(), Some(24), crate::mcp::McpConfig::default(), ) @@ -1509,7 +1517,7 @@ mod tests { HashSet::new(), crate::config::TaskConfig::default(), crate::config::SubagentsConfig::default(), - crate::config::MemoryMaintenanceConfig::default(), + test_maintenance_config(), Some(24), crate::mcp::McpConfig::default(), ) @@ -1589,7 +1597,7 @@ mod tests { HashSet::new(), crate::config::TaskConfig::default(), crate::config::SubagentsConfig::default(), - crate::config::MemoryMaintenanceConfig::default(), + test_maintenance_config(), Some(24), crate::mcp::McpConfig::default(), ) @@ -1656,7 +1664,7 @@ mod tests { HashSet::new(), crate::config::TaskConfig::default(), crate::config::SubagentsConfig::default(), - crate::config::MemoryMaintenanceConfig::default(), + test_maintenance_config(), Some(24), crate::mcp::McpConfig::default(), ) diff --git a/src/providers/openai.rs b/src/providers/openai.rs index cba07f8..507be14 100644 --- a/src/providers/openai.rs +++ b/src/providers/openai.rs @@ -397,11 +397,13 @@ impl OpenAIProvider { // 读取 SSE 流 let mut stream = resp.bytes_stream(); let mut buffer = String::new(); + let mut raw_body = String::new(); // 完整原始响应,用于非 SSE JSON 回退 let mut done_received = false; while let Some(chunk_result) = stream.next().await { let chunk = chunk_result?; let text = String::from_utf8_lossy(&chunk); + raw_body.push_str(&text); buffer.push_str(&text); // 处理缓冲区中的完整行 @@ -554,7 +556,45 @@ impl OpenAIProvider { } } - let response = accumulator.build_response(self.model_id.clone()); + let mut response = accumulator.build_response(self.model_id.clone()); + + // 回退:当流式解析未获取到任何内容且无 tool call 时, + // 服务器可能返回的是非 SSE 格式的纯 JSON,尝试直接反序列化整个响应体 + if response.content.is_empty() && response.tool_calls.is_empty() { + if let Ok(openai_resp) = serde_json::from_str::(&raw_body) { + let fallback_content = openai_resp.choices + .first() + .and_then(|c| c.message.content.as_deref()) + .unwrap_or("") + .to_string(); + if !fallback_content.is_empty() { + tracing::debug!( + model = %self.model_id, + "Streaming accumulator empty, falling back to non-SSE JSON parsing" + ); + response.content = fallback_content; + response.reasoning_content = openai_resp.choices + .first() + .and_then(|c| c.message.reasoning_content.clone()); + response.tool_calls = openai_resp.choices + .first() + .map(|c| { + c.message.tool_calls.iter().map(|tc| ToolCall { + id: tc.id.clone(), + name: tc.function.name.clone(), + arguments: match &tc.function.arguments { + OAIFunctionArguments::Json(args) => args.clone(), + OAIFunctionArguments::String(args) => { + serde_json::from_str(args).unwrap_or(serde_json::Value::Null) + } + }, + }).collect() + }) + .unwrap_or_default(); + } + } + } + tracing::debug!( content_len = response.content.len(), tool_calls_count = response.tool_calls.len(), diff --git a/src/skills/mod.rs b/src/skills/mod.rs index 4feaa48..6d1a9d4 100644 --- a/src/skills/mod.rs +++ b/src/skills/mod.rs @@ -1035,7 +1035,7 @@ mod tests { let prompt = catalog.system_index_prompt().unwrap(); assert!(prompt.contains("")); - assert!(prompt.contains("技能为特定任务提供专用说明和工作流。")); + assert!(prompt.contains("技能是预定义的工作流和指令集合,用于处理特定类型的任务。")); assert!(prompt.contains("demo-skill")); assert!(prompt.contains("demo <skill> & usage"));