From ee544b37f59c62630dc1de32dcf75662a3aca326 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Mon, 13 Oct 2025 21:48:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E7=BD=91=E7=BB=9C=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/controllers/agent_controller.dart | 30 +++++++----- .../agent_service/AgentService.kt | 29 +++++++----- .../agent_service/AgentServiceImpl.swift | 16 ++++--- .../chat_api/ChatApiService.kt | 47 ++++++++++++------- .../Sources/chat_api/ChatApiService.swift | 24 +++++----- 5 files changed, 87 insertions(+), 59 deletions(-) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index f8ec07f3e..83016d4ef 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -707,23 +707,25 @@ class AgentController extends GetxController with WidgetsBindingObserver { break; case AgentServiceEventType.error: - //验证错误码 + final sessionid = event.data['sessionid'] ?? ''; + final message = event.data['message'] ?? ''; + + // //验证错误码 if (event.data['code'] == 1000) { isListening.value = false; isSpeaking.value = false; isProcessing.value = false; isImageProcessing.value = false; + if (sessionid != "") { + final index = messages.lastIndexWhere( + (msg) => msg.sessionid == sessionid && !msg.isUser); + if (index >= 0) { + messages.removeAt(index); + messages.refresh(); + } + } + Get.snackbar('error'.tr, '${event.data['message']}'); } - // 移除临时的识别消息 - final index = - messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser); - if (index >= 0) { - messages.removeAt(index); - // messages.refresh(); - } - - // Get.snackbar('error'.tr, '${event.data['message']}'); - Logger.e(TAG, '代理服务错误: ${event.data['message']}'); break; @@ -2073,7 +2075,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { color: Colors.grey.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), - child: const Icon(Icons.navigation, color: Colors.orange), + child: const Icon(Icons.map, color: Colors.orange), ), title: const Text('苹果地图'), subtitle: const Text('使用苹果地图导航'), @@ -2491,6 +2493,8 @@ class AgentController extends GetxController with WidgetsBindingObserver { // 播放成功后,如果歌曲不在当前列表中,保持列表显示状态不变 // Get.snackbar('开始播放', song.name); _qqmusicManager.syncCurrentPlayInfo(); + } else { + Get.snackbar('播放失败', result['error'] ?? '无法播放歌曲'); } return; } @@ -2511,6 +2515,8 @@ class AgentController extends GetxController with WidgetsBindingObserver { // 播放成功后,如果歌曲不在当前列表中,保持列表显示状态不变 // Get.snackbar('开始播放', song.name); _qqmusicManager.syncCurrentPlayInfo(); + } else { + Get.snackbar('播放失败', result['error'] ?? '无法播放歌曲'); } } } 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 fead00630..aeb97e695 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 @@ -39,6 +39,7 @@ import java.util.Date import android.app.PendingIntent import android.app.NotificationManager import android.app.NotificationChannel +import android.os.Message import androidx.core.app.NotificationCompat @@ -702,6 +703,7 @@ object AgentService : CoroutineScope { _isRecognitionActive.set(false) stopIdleCheck() BleService.closeCodec() + audioPlayer?.stopAudio() Log.d(TAG, "语音识别错误: $error") sendEvent( "error", mapOf( @@ -1051,20 +1053,23 @@ object AgentService : CoroutineScope { } } - override fun onError(sessionid: String, e: Exception) { + override fun onError(sessionid: String, code:Int,message: String) { try { - Log.e(TAG, "AI处理出错", e) - sendEvent( - "error", mapOf( - "sessionid" to sessionid, - "code" to "AI_ERROR", - "message" to e.message.toString() + if (sessionid == currsessionId) { + audioPlayer?.stopAudio() + Log.e(TAG, "AI处理出错 $message") + // 优先使用 ChatApiException.code;否则根据底层异常类型推断错误码 + sendEvent( + "error", mapOf( + "sessionid" to sessionid, + "code" to code, + "message" to message + ) ) - ) - - // 标记AI流式输出已完成 - _isAiStreaming.set(false) - currentAiJob = null + // 标记AI流式输出已完成 + _isAiStreaming.set(false) + currentAiJob = null + } } catch (e: Exception) { Log.e(TAG, "liwei--------------- AI Call onError 异常", e) } 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 bcd1eba48..282175fa4 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 @@ -1708,26 +1708,27 @@ class ChatApiStreamCallback: StreamCallback { } - func onError(_ sessionid:String,_ error: Error) { + func onError(_ sessionid:String,_ code: Int,_ message:String) { do { guard let agentService = try agentService else { return } if sessionid == agentService.currsessionId { // 停止等待音效 agentService.audioPlayer?.stopAwaitSound() - } + - os_log("ChatAPI错误: %{public}@", log: agentService.logger, type: .error, error.localizedDescription) +// os_log("ChatAPI错误: code:%{public}@ message:%{public}@", log: agentService.logger, type: .error, code,message) if (sessionid != agentService.currsessionId) { return } try agentService.sendEvent(name: "error", data: [ "sessionid":sessionid, - "code": "AI_ERROR", - "message": error.localizedDescription + "code": code, + "message": message ]) - +// agentService.isAiStreaming = false + } os_log("因错误设置AI流式状态为false", log: agentService.logger, type: .info) }catch{ print("liwei--------------- AI Call onError 异常: \(error)") @@ -2179,7 +2180,8 @@ extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback { } func onError(sessionid:String ,_ errorCode: Int, _ error: String) { - let data: [String: Any] = ["sessionid":sessionid,"message": error.isEmpty ? "未知错误" : error] + let data: [String: Any] = ["sessionid":sessionid,"code":errorCode, "message": error.isEmpty ? "未知错误" : error] + audioPlayer?.stopAwaitSound() sendEvent(name: "error", data: data) isRecognizing = false // 新增:出错时复位"启动中/待停止"状态 diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt index 6ab4db783..f17b7d27c 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt @@ -29,8 +29,7 @@ import java.util.Collections /** * ChatAPI服务异常 */ -class ChatApiException(message: String) : Exception("ChatApiException: $message") - +class ChatApiException(message: String, val code: String? = null, cause: Throwable? = null) : Exception("ChatApiException: $message", cause) /** * 流式回调接口 * 与 iOS 版本 StreamCallback 协议保持完全一致 @@ -53,7 +52,7 @@ interface StreamCallback { /** * 出现错误 */ - fun onError(sessionid: String,error: Exception) + fun onError(sessionid: String,code:Int,message:String) /** * 函数调用 - 兼容JSONObject格式 @@ -354,13 +353,20 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor if (!isInitialized || apiKey.isEmpty() || openAI == null) { Log.e("ChatApiService", "ChatAPI服务未初始化,无法发送消息") try { - getSessionCallback(sessionid)?.onError(sessionid,ChatApiException("ChatAPI服务未初始化")) + getSessionCallback(sessionid)?.onError(sessionid,1002,"ChatAPI服务未初始化") } catch (e: Exception) { Log.e(TAG, "onError回调异常: ${e.message}", e) } return } + // 在开始流式请求前检查网络状态 + if (!isNetworkAvailable()) { + Log.w("ChatApiService", "[Session: $sessionid] 网络不可用,直接返回网络错误") + getSessionCallback(sessionid)?.onError(sessionid,1000,"网络不可用") + return + } + // 重置状态 currentMessages = messages toolCalls.clear() @@ -418,7 +424,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor Log.e("ChatApiService", "创建ChatCompletionRequest或调用chatCompletions失败: ${e.message}", e) if (sessionid == currSessionId) { try { - getSessionCallback(sessionid)?.onError(sessionid,ChatApiException("流式请求失败: ${e.message}")) + getSessionCallback(sessionid)?.onError(sessionid,1000,"流式请求失败: ${e.message}") } catch (ex: Exception) { Log.e(TAG, "onError回调异常: ${ex.message}", ex) } @@ -477,14 +483,6 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor } } } - //else{ - // delta.content?.let { content -> - // Log.d("ChatApiService", "liwei------------ [Session: $sessionid] 中间过程不输出 $content") - // } - - // } - - // Log.d(TAG, "liwei-------------------------开始AI 对话 7-4") // 收集工具调用信息 delta.toolCalls?.forEach { toolCall -> @@ -559,7 +557,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor } catch (e: Exception) { if (sessionid == currSessionId) { try { - getSessionCallback(sessionid)?.onError(sessionid,ChatApiException("流式请求失败: ${e.message}")) + Log.d(TAG, "AI聊天异常 Session $sessionid 错误类型:${e::class.simpleName} 错误完整类型:${e::class.qualifiedName}") + getSessionCallback(sessionid)?.onError(sessionid,1000,"流式请求失败: ${e.message}") } catch (ex: Exception) { Log.e(TAG, "onError回调异常: ${ex.message}", ex) } @@ -632,13 +631,13 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor } // 调用MCP工具 val toolResult = try { - withTimeout(60000) { // 60秒超时 + withTimeout(10000) { // 100秒超时 // 再次检查会话状态 if (sessionid != currSessionId) { throw CancellationException("Session cancelled") } _mcpClient?.callTool(functionName, arguments) - } + } } catch (e: TimeoutCancellationException) { Log.w("ChatApiService", "[Session: $sessionid] MCP工具调用超时: $functionName") mapOf( @@ -708,7 +707,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor convertMapToJsonObject(result) ) }else{ - getSessionCallback(sessionid)?.onError(sessionid, ChatApiException(errorText)) + Log.d(TAG, "[Session: $sessionid] 工具调用失败 $errorText") + //getSessionCallback(sessionid)?.onError(sessionid, ChatApiException(errorText)) } } catch (e: Exception) { Log.e(TAG, "onFunctionCallResult回调异常: ${e.message}", e) @@ -798,6 +798,19 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor sendMessageStream(sessionid,fullMessages) } + // 添加网络检查方法 + private fun isNetworkAvailable(): Boolean { + return try { + val connectivityManager = context?.getSystemService(android.content.Context.CONNECTIVITY_SERVICE) as? android.net.ConnectivityManager + val activeNetwork = connectivityManager?.activeNetworkInfo + activeNetwork?.isConnectedOrConnecting == true + } catch (e: Exception) { + Log.w("ChatApiService", "检查网络状态失败: ${e.message}") + true // 如果检查失败,假设网络可用,让后续的网络请求来处理 + } + } + + /** * 取消当前流式请求 */ diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift index 87b87fff3..17f4ab905 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift @@ -20,7 +20,7 @@ public protocol StreamCallback { func onUsage(_ sessionid: String,_ prompt_tokens: Int?,_ completion_tokens: Int?,_ total_tokens: Int?) func onToken(_ sessionId:String,_ token: String) func onComplete(_ sessionId:String) - func onError(_ sessionId:String,_ error: Error) + func onError(_ sessionId:String,_ code:Int,_ message:String) func onFunctionCall(_ sessionId:String,_ functionCall: [String: Any]) func onFunctionCallResult(_ sessionId:String,_ functionCall: [String: Any], _ functionCallResult: [String: Any]) } @@ -59,6 +59,7 @@ public class ChatApiService: NSObject { private var currentMessages: [[String: Any]] = [] private var toolCalls: [Int: ToolCallInfo] = [:] private var currSessionId = "" + // MARK: - 初始化 public override init() { super.init() @@ -274,8 +275,7 @@ public func sendMessageStream(_ sessionId: String, messages: [[String: Any]]) { currSessionId = sessionId guard isInitialized && !apiKey.isEmpty, let openAI = openAI else { - let error = ChatApiException("ChatAPI服务未初始化") - getSessionCallback(sessionId)?.onError(sessionId, error) + getSessionCallback(sessionId)?.onError(sessionId,1002,"服务未初始化") return } @@ -398,9 +398,9 @@ public func sendMessageStream(_ sessionId: String, messages: [[String: Any]]) { } catch { if sessionId == self.currSessionId { - let errorMessage = "流式请求失败: \(error.localizedDescription)" - let chatApiError = ChatApiException(errorMessage) - self.getSessionCallback(sessionId)?.onError(sessionId, chatApiError) +// let errorMessage = "流式请求失败: \(error.localizedDescription)" +// let chatApiError = ChatApiException(errorMessage) + self.getSessionCallback(sessionId)?.onError(sessionId, 1001,"流式请求失败: \(error.localizedDescription)") self.clearCurrentSession() } } @@ -528,12 +528,13 @@ private func processToolCalls(_ sessionId:String) async -> Bool { functionCall, result ) - }else{ - self.getSessionCallback(sessionId)?.onError( - sessionId, - ChatApiException(errorStr) - ) } + //else{ mcp 的错误需要交给ai处理 不需要返回到上层 +// self.getSessionCallback(sessionId)?.onError( +// sessionId, +// ChatApiException(errorStr) +// ) +// } // 将结果发送回OpenAI继续对话 await self.sendFunctionCallResultInternal( sessionId: sessionId, @@ -1009,3 +1010,4 @@ private class LocalFunctionHandler: FunctionHandler { return "LOCAL_FUNCTION:\(functionName)" } } +