diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 7a7f18b10..1fdaea5df 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -888,7 +888,7 @@ object AgentService : CoroutineScope { responseBuilder.append(token) if (speakResponse && reply && broadcast) { ttsService?.speakStream(token) - audioPlayer?.stopAudio() + // audioPlayer?.stopAudio() } if (reply && token.isNotEmpty()) { // 发送流式回复token diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 7e54e2c4e..bfac028cd 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -996,7 +996,7 @@ class ChatApiStreamCallback: StreamCallback { if speakResponse && reply && broadcast{ try agentService.azureTtsHelper?.speakStream(token) - try agentService.audioPlayer?.stopAwaitSound() +// try agentService.audioPlayer?.stopAwaitSound() } if (reply && token != "") { try agentService.sendEvent(name: "assistant_token", data: ["token": token]) @@ -1142,7 +1142,7 @@ extension AgentServiceImpl: TtsEventListener { isSpeaking = true restartIdleCheck() sendEvent(name: "tts_started", data: ["status": "started"]) - + audioPlayer?.stopAwaitSound() case .synthesisCompleted: isSpeaking = false restartIdleCheck() diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift index b549e5f1a..ea6aedcda 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift @@ -583,9 +583,9 @@ public class AudioSessionHub { switch action { case .increase: - newValue = min(currentValue + 0.1, 1.0) + newValue = min(currentValue + 0.2, 1.0) // 从 0.1 改为 0.2 (20%) case .decrease: - newValue = max(currentValue - 0.1, 0.0) + newValue = max(currentValue - 0.2, 0.0) // 从 0.1 改为 0.2 (20%) case .mute: newValue = 0.0 case .setMax: diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift index 3f0caeab3..b65a7aaa4 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift @@ -28,6 +28,7 @@ public protocol FunctionHandler { /// - 安全的JSON-RPC消息解析,包含错误修复逻辑 /// - 完整的SSE连接生命周期管理 /// - 与Android端CustomSseClientTransport功能对等 +// 在 MCPSubClient 类中添加工具调用超时和重试配置 public class MCPSubClient { private let serverId: String private let serverUrl: String @@ -47,6 +48,10 @@ public class MCPSubClient { private var retryCount = 0 private var reconnectTask: Task? + // 工具调用配置 - 添加这两行 + private let maxToolCallRetries = 3 + private let toolCallTimeout: TimeInterval = 30.0 + // 连接状态锁 private let connectionLock = NSLock() @@ -178,27 +183,55 @@ public class MCPSubClient { /// 调用工具前检查连接 public func callTool(name: String, arguments: [String: Any]) async -> [String: Any]? { + return await callToolWithRetry(name: name, arguments: arguments, retryCount: 0) + } + + private func callToolWithRetry(name: String, arguments: [String: Any], retryCount: Int) async -> [String: Any]? { + print("[MCPSubClient] 工具调用尝试 \(retryCount + 1)/\(maxToolCallRetries + 1): \(name)") + // 先检查连接状态 if !(await checkConnection()) { + if retryCount < maxToolCallRetries { + print("[MCPSubClient] 连接断开,尝试重连...") + if await connect() { + print("[MCPSubClient] 重连成功,重试工具调用") + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + } return [ "content": [[ "type": "text", - "text": "Tool call failed: mcp: service not connected" + "text": "工具调用失败,请重试:MCP 服务连接中断" ]], "isError": true ] } guard isConnectedFlag, let client = mcpClient else { - return createErrorResult("Not connected to MCP server") + if retryCount < maxToolCallRetries { + print("[MCPSubClient] MCP 客户端未连接,尝试重连...") + if await connect() { + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + } + return createErrorResult("工具调用失败,请重试:MCP 服务未连接") } do { - let mcpArguments = convertToMCPValue(arguments) - let (content, isError) = try await client.callTool(name: name, arguments: mcpArguments) + // 使用超时控制的工具调用 + let result = try await withTimeout(seconds: toolCallTimeout) { + let mcpArguments = self.convertToMCPValue(arguments) + return try await client.callTool(name: name, arguments: mcpArguments) + } + + let (content, isError) = result if isError == true { - return createErrorResult("Tool execution failed") + if retryCount < maxToolCallRetries { + print("[MCPSubClient] 工具执行失败,重试中...") + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + return createErrorResult("工具调用失败,请重试:工具执行错误") } var contextText = "" @@ -225,13 +258,45 @@ public class MCPSubClient { } } + print("[MCPSubClient] 工具调用成功: \(name)") return [ "content": [["type": "text", "text": contextText.trimmingCharacters(in: .whitespacesAndNewlines)]], "isError": false ] + } catch is TimeoutError { + print("[MCPSubClient] 工具调用超时: \(name)") + if retryCount < maxToolCallRetries { + print("[MCPSubClient] 超时重试中...") + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + return createErrorResult("工具调用失败,请重试:连接超时") } catch { - return createErrorResult("Tool call failed: \(error.localizedDescription)") + print("[MCPSubClient] 工具调用异常: \(name), 错误: \(error.localizedDescription)") + + // 检查是否是连接相关错误 + let errorDescription = error.localizedDescription.lowercased() + if errorDescription.contains("connection") || + errorDescription.contains("network") || + errorDescription.contains("timeout") || + errorDescription.contains("disconnected") { + + if retryCount < maxToolCallRetries { + print("[MCPSubClient] 检测到连接错误,尝试重连并重试...") + // 标记连接断开 + isConnectedFlag = false + + // 尝试重连 + if await connect() { + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + } + } else if retryCount < maxToolCallRetries { + print("[MCPSubClient] 其他错误,直接重试...") + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + + return createErrorResult("工具调用失败,请重试:\(error.localizedDescription)") } } diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift index 98773d41f..bcacf4eb5 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift @@ -795,7 +795,7 @@ private class GetCurrentTimeHandler: FunctionHandler { private class MediaPlayHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { NotificationCenter.default.post(name: NSNotification.Name("MediaPlay"), object: nil) - return "{\"result\": \"已开始播放媒体\"}" + return "{\"notreply\": true,\"result\": \"已开始播放媒体\"}" } } @@ -803,7 +803,7 @@ private class MediaPlayHandler: FunctionHandler { private class MediaPauseHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { NotificationCenter.default.post(name: NSNotification.Name("MediaPause"), object: nil) - return "{\"result\": \"已暂停媒体播放\"}" + return "{\"broadcast\": true,\"result\": \"已暂停媒体播放\"}" } } @@ -811,7 +811,7 @@ private class MediaPauseHandler: FunctionHandler { private class MediaPreviousHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { NotificationCenter.default.post(name: NSNotification.Name("MediaPrevious"), object: nil) - return "{\"result\": \"已切换到上一首\"}" + return "{\"notreply\": true,\"result\": \"已切换到上一首\"}" } } @@ -819,7 +819,7 @@ private class MediaPreviousHandler: FunctionHandler { private class MediaNextHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { NotificationCenter.default.post(name: NSNotification.Name("MediaNext"), object: nil) - return "{\"result\": \"已切换到下一首\"}" + return "{\"notreply\": true,\"result\": \"已切换到下一首\"}" } } @@ -833,7 +833,7 @@ private class OpenRecorderHandler: FunctionHandler { } } - return "{\"result\": \"无法打开录音机应用\"}" + return "{\"notreply\": true,\"result\": \"无法打开录音机应用\"}" } } @@ -905,7 +905,7 @@ private class AddVolumeHandler: FunctionHandler { object: volumeInfo ) - return "{\"result\": \"音量增加请求已发送\"}" + return "{\"broadcast\": false,\"result\": \"音量增加请求已发送\"}" } } @@ -921,7 +921,7 @@ private class ReduceVolumeHandler: FunctionHandler { object: volumeInfo ) - return "{\"result\": \"音量降低请求已发送\"}" + return "{\"broadcast\": false,\"result\": \"音量降低请求已发送\"}" } } @@ -937,7 +937,7 @@ private class MuteVolumeHandler: FunctionHandler { object: volumeInfo ) - return "{\"result\": \"静音请求已发送\"}" + return "{\"broadcast\": false,\"result\": \"静音请求已发送\"}" } } @@ -953,6 +953,6 @@ private class MaxVolumeHandler: FunctionHandler { object: volumeInfo ) - return "{\"result\": \"最大音量请求已发送\"}" + return "{\"broadcast\": false,\"result\": \"最大音量请求已发送\"}" } }