From e58570842c0fec6f780e2f702f19f71b395dbdf6 Mon Sep 17 00:00:00 2001 From: wolfplus Date: Sat, 7 Jun 2025 14:53:09 +0100 Subject: [PATCH] add --- .../yunqiinnovation/deepsound/MainActivity.kt | 26 +- .../agent_service/AgentService.kt | 30 +- .../azure_speech/AzureAsrHelper.kt | 187 ++++---- .../azure_speech/AzureTtsHelper.kt | 79 +++- .../chat_api/ChatApiService.kt | 404 ++++++------------ .../chat_api/CustomSseClientTransport.kt | 14 +- .../com/yunqiinnovation/chat_api/MCPClient.kt | 30 +- .../yunqiinnovation/chat_api/MCPSubClient.kt | 36 +- 8 files changed, 346 insertions(+), 460 deletions(-) diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt index e7b34ac65..68e3f58b8 100644 --- a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt @@ -19,7 +19,9 @@ class MainActivity: FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + + // 设置日志级别,减少系统级日志 + suppressSystemLogs() } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { @@ -32,5 +34,27 @@ class MainActivity: FlutterActivity() { override fun onDestroy() { super.onDestroy() } + + /** + * 抑制系统级组件的日志输出 + */ + private fun suppressSystemLogs() { + val systemTags = arrayOf( + "MediaCodec", + "CCodec", + "CCodecBufferChannel", + "OpusManager", + "MediaCodecList", + "MediaPlayer", + "AudioTrack", + "AudioManager", + "AudioFlinger", + "AudioService" + ) + + systemTags.forEach { tag -> + System.setProperty("log.tag.$tag", "WARN") + } + } } 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 d560c4684..90e318861 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 @@ -152,15 +152,13 @@ object AgentService : CoroutineScope { config["openaiApiKey"]?.toString() ?: "", config["openaiBaseUrl"]?.toString() ?: "", config["openaiModel"]?.toString() ?: "", - "" - //config["mcpServer"]?.toString() ?: "" + config["mcpServer"]?.toString() ?: "" ) // 加载最近的聊天记录 loadChatHistory() isInitialized = true - Log.d(TAG, "代理服务初始化成功") return true } catch (e: Exception) { Log.e(TAG, "初始化失败: ${e.message}") @@ -195,8 +193,6 @@ object AgentService : CoroutineScope { job.cancel() clearListeners() isInitialized = false - - Log.d(TAG, "代理服务资源已释放") } catch (e: Exception) { Log.e(TAG, "释放资源异常: ${e.message}") } @@ -230,9 +226,7 @@ object AgentService : CoroutineScope { language = ttsLanguage ) - if (success) { - Log.d(TAG, "Azure TTS服务初始化成功") - } else { + if (!success) { Log.e(TAG, "Azure TTS服务初始化失败") } @@ -268,13 +262,10 @@ object AgentService : CoroutineScope { } else -> { // 处理其他类型的事件 - Log.d(TAG, "TTS事件: ${event.type}") } } } }) - - Log.d(TAG, "TTS服务配置完成") } catch (e: Exception) { Log.e(TAG, "初始化TTS引擎失败: ${e.message}") } @@ -372,7 +363,6 @@ object AgentService : CoroutineScope { } else { AzureAsrHelper.AudioSourceType.MICROPHONE } - Log.d(TAG, "开始语音识别, 音频源类型: $audioSourceType") azureAsrHelper?.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback { override fun onRecognizing(recognizing: String, detectedLanguage: String) { if (recognizing.isNotEmpty()) { @@ -477,7 +467,6 @@ object AgentService : CoroutineScope { BleService.closeCodec() isRecognitionActive = false stopIdleCheck() - Log.d(TAG, "语音识别已停止") } catch (e: Exception) { Log.e(TAG, "停止语音识别异常: ${e.message}") isRecognitionActive = false @@ -518,8 +507,6 @@ object AgentService : CoroutineScope { // 通知ChatAPI服务终止当前流式请求 chatApiService.cancelCurrentStream() - // 记录日志 - Log.d(TAG, "AI流输出已停止") } catch (e: Exception) { Log.e(TAG, "停止AI流输出异常", e) // 确保状态被重置,即使发生异常 @@ -586,8 +573,6 @@ object AgentService : CoroutineScope { text: String = "", speakResponse: Boolean = false ) { - Log.d(TAG, "处理图片输入: ${if (text.isEmpty()) "无附加文本" else "附带文本: $text"}") - // 创建带图片的用户消息并处理 val userMessage = createUserMessageWithImage(text, imageBase64) // 图片描述用于存储 @@ -687,7 +672,6 @@ object AgentService : CoroutineScope { } override fun onComplete() { - Log.d(TAG, "AI完整回复: $responseBuilder") // 视情况决定是否朗读回复 if (speakResponse) { ttsService?.flushStream() @@ -751,7 +735,6 @@ object AgentService : CoroutineScope { "function_call" to functionCall.toString(), "result" to functionCallResult.toString(), )) - Log.d(TAG, "mcp调用结果: $functionCallResult") val (metestr, broadcast)= autoHandleFcunCallResult(functionCallResult); aiMetadata = metestr; nobroadcast = broadcast @@ -803,8 +786,6 @@ object AgentService : CoroutineScope { addToHistoryMessages(createAssistantMessage(content)) } } - - Log.d(TAG, "已加载${recentMessages.size}条历史记录") } catch (e: Exception) { Log.e(TAG, "加载聊天历史失败: ${e.message}") } @@ -906,7 +887,6 @@ object AgentService : CoroutineScope { historyMessages.remove(0) } } - Log.d(TAG, "聊天历史已清除") } else { Log.e(TAG, "清除聊天历史失败") } @@ -1099,7 +1079,6 @@ object AgentService : CoroutineScope { } } } - Log.d(TAG, "开始播放音频资源") } catch (e: Exception) { Log.e(TAG, "播放音频资源异常: ${e.message}", e) release() @@ -1134,7 +1113,6 @@ object AgentService : CoroutineScope { // Log.d(TAG, "mcp调用结果: $metaStr") if (meta.has("card_music")) { //音乐卡片 val cardMusic = meta.getJSONObject("card_music") - Log.d(TAG, "检查到音乐卡片: $cardMusic") val id = cardMusic.optString("id", "") val url = cardMusic.optString("url", "") val name = cardMusic.optString("name", "") @@ -1157,7 +1135,6 @@ object AgentService : CoroutineScope { playlist.add(mapOf("id" to id,"url" to url, "title" to name, "artist" to sgener,"coverUrl" to image)) } if (playlist.isNotEmpty()) { - Log.w(TAG, "自动播放音乐列表 ${playlist}") broadcast = true; processMusicPlayList(playlist) } else { @@ -1190,7 +1167,6 @@ object AgentService : CoroutineScope { */ fun processMusicPlay(song: Map){ // 在其他 Service、BroadcastReceiver 或 Application 中调用 - Log.i(TAG, "启动音乐服务 播放音乐 $song") MusicServiceStarter.startServiceWithCommand(context, command = "play", song = song) } @@ -1206,7 +1182,6 @@ object AgentService : CoroutineScope { fun processMusicPlayList(songs:List>){ // 如果 playlist 中有有效的歌曲,开始播放 if (songs.isNotEmpty()) { - Log.i(TAG, "启动音乐服务 播放音乐列表 ${songs}") MusicServiceStarter.startServiceWithPlaylist(context, command = "playlist", songs = songs) } else { Log.i(TAG, "音乐列表为空,未启动播放服务") @@ -1224,7 +1199,6 @@ object AgentService : CoroutineScope { fun processNavigation(start:String,end:String){ // 如果 playlist 中有有效的歌曲,开始播放 if (!start.isNullOrEmpty() && !end.isNullOrEmpty()) { - Log.i(TAG, "启动导航服务 ${start} ${end}") NavigationServiceHelper.startNavigation(context,"start", startpos = start, endpos = end) } else { Log.i(TAG, "启动导航服务失败") diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt index 31a09c506..52a86ce10 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt @@ -123,16 +123,26 @@ class AzureAsrHelper(private val context: Context) { speechRecognitionLanguage = currentLanguage } - // 设置静音超时时间(毫秒) - setProperty("SpeechServiceConnection_EndSilenceTimeoutMs", "800") - setProperty("Speech_SegmentationSilenceTimeoutMs", "800") + // 优化:大幅减少静音超时时间(从800ms到300ms) + setProperty("SpeechServiceConnection_EndSilenceTimeoutMs", "300") + setProperty("Speech_SegmentationSilenceTimeoutMs", "300") + + // 优化:添加低延迟连接配置 + setProperty("SpeechServiceConnection_InitialSilenceTimeoutMs", "200") + setProperty("SpeechServiceConnection_RecoMode", "INTERACTIVE") + setProperty("Speech_PushStreamFormat", "PCM") // 设置分段策略为时间模式 setProperty("Speech_SegmentationStrategy", "Time") } // 创建识别器 - return setupRecognizer() + val setupSuccess = setupRecognizer() + if (setupSuccess) { + // 优化:初始化完成后进行预热 + warmupRecognizer() + } + return setupSuccess } catch (e: Exception) { Log.e(tag, "初始化失败: ${e.message}") return false @@ -216,6 +226,36 @@ class AzureAsrHelper(private val context: Context) { } } + /** + * 预热识别器(减少首次识别延迟) + */ + private fun warmupRecognizer() { + try { + // 创建极短的音频数据进行预热 + val warmupData = ByteArray(320) // 10ms 16kHz 单声道 + // 模拟静音数据 + warmupData.fill(0) + + // 如果是外部音频流,进行预热 + if (audioSourceType == AudioSourceType.EXTERNAL) { + externalAudioStream?.pushAudio(warmupData) + } + Log.d(tag, "ASR预热完成") + } catch (e: Exception) { + // 预热失败不影响正常使用 + Log.d(tag, "ASR预热失败: ${e.message}") + } + } + + /** + * 强制设置单一语言(优化语言检测逻辑) + */ + fun setForceLanguage(language: String) { + this.currentLanguage = language + this.isAutoDetectLanguage = false + speechConfig?.speechRecognitionLanguage = language + } + /** * 设置外部音频流 - 使用拉流方式 */ @@ -337,21 +377,30 @@ class AzureAsrHelper(private val context: Context) { * 设置事件监听器 */ private fun setupEventListeners(callback: ContinuousRecognizeCallback) { - // 识别中事件 + // 优化:识别中事件 - 添加文本长度检查 recognizer?.recognizing?.addEventListener( EventHandler { _, event -> - val detectedLanguage = AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: "" - // 直接在当前线程调用回调 - callback.onRecognizing(event.result.text, detectedLanguage) + // 优化:只处理非空结果 + if (event.result.text.isNotEmpty()) { + val detectedLanguage = if (isAutoDetectLanguage) { + AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: "" + } else { + currentLanguage + } + callback.onRecognizing(event.result.text, detectedLanguage) + } } ) - // 识别完成事件 + // 优化:识别完成事件 - 优化语言检测 recognizer?.recognized?.addEventListener( EventHandler { _, event -> - if (event.result.reason == ResultReason.RecognizedSpeech) { - val detectedLanguage = AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: "" - // 直接在当前线程调用回调 + if (event.result.reason == ResultReason.RecognizedSpeech && event.result.text.isNotEmpty()) { + val detectedLanguage = if (isAutoDetectLanguage) { + AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: supportedLanguages[0] + } else { + currentLanguage + } callback.onResult(event.result.text, detectedLanguage) } } @@ -538,19 +587,14 @@ class AzureAsrHelper(private val context: Context) { private inner class MicrophoneStream : PullAudioInputStreamCallback() { private var audioRecord: AudioRecord? = null private var echoCanceler: AcousticEchoCanceler? = null - private var currentAudioFile: File? = null - private var fos: FileOutputStream? = null - - // 用于存储音频数据的缓冲区 - private val dataBuffer = mutableListOf() - private var totalBytesWritten = 0 // 音频配置 private val sampleRate = 16000 private val channelConfig = AudioFormat.CHANNEL_IN_MONO private val audioFormat = AudioFormat.ENCODING_PCM_16BIT + // 优化:减少缓冲区大小以降低延迟 private val bufferSize = AudioRecord.getMinBufferSize( sampleRate, channelConfig, audioFormat - ).let { if (it < 0) 4096 else it * 2 } + ).let { if (it < 0) 2048 else it } init { initMicrophone() @@ -561,20 +605,6 @@ class AzureAsrHelper(private val context: Context) { */ private fun initMicrophone() { try { - // 创建新的音频文件 - val timestamp = System.currentTimeMillis() - val filePath = context.getExternalFilesDir(null)?.absolutePath + "/recorded_audio_$timestamp.wav" - currentAudioFile = File(filePath) - currentAudioFile?.createNewFile() - - // 仅写入初始文件头 - fos = FileOutputStream(currentAudioFile).apply { - write(generateWavHeader(0)) // 初始0长度 - close() - } - - // 追加音频数据到文件 - fos = FileOutputStream(currentAudioFile, true) // 创建录音对象 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { val format = AudioFormat.Builder() @@ -619,14 +649,20 @@ class AzureAsrHelper(private val context: Context) { } /** - * 应用音频效果 + * 优化:条件性应用音频效果 */ private fun applyAudioEffects() { val sessionId = audioRecord?.audioSessionId ?: return - // 回音消除 - echoCanceler = AcousticEchoCanceler.create(sessionId) - echoCanceler?.enabled = true; + // 优化:只在真正需要时启用回音消除 + val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + if (audioManager.isSpeakerphoneOn) { + echoCanceler = AcousticEchoCanceler.create(sessionId) + echoCanceler?.enabled = true + Log.d(tag, "启用回音消除") + } else { + Log.d(tag, "跳过回音消除(使用耳机)") + } } /** @@ -638,6 +674,9 @@ class AzureAsrHelper(private val context: Context) { * Azure SDK 调用此方法获取音频数据 * 这是拉流模式的核心方法,由SDK调用以获取音频数据 */ + /** + * 优化:简化音频数据读取(移除文件保存) + */ override fun read(buffer: ByteArray): Int { try { val result = audioRecord?.read(buffer, 0, buffer.size) ?: -1 @@ -645,80 +684,18 @@ class AzureAsrHelper(private val context: Context) { Log.e(tag, "读取音频数据失败: $result") return 0 } - // 将音频数据保存成wav格式的音频文件 - saveAudioDataToWav(buffer, result) return result } catch (e: Exception) { Log.e(tag, "读取音频异常: ${e.message}") return 0 } } - /** - * 保存音频数据到 WAV 文件 - */ -private fun saveAudioDataToWav(buffer: ByteArray, length: Int) { - // 复制数据到新数组 - val copy = ByteArray(length) - System.arraycopy(buffer, 0, copy, 0, length) - - // 添加到缓冲区 - dataBuffer.add(copy) - totalBytesWritten += length - - // 写入文件 - try { - fos?.write(copy, 0, length) - } catch (e: Exception) { - Log.e(tag, "写入音频数据失败: ${e.message}") - } -} - // 更新文件头生成(修正RIFF长度计算) - private fun generateWavHeader(dataLength: Int): ByteArray { - val totalLength = 36 + dataLength // RIFF块总长度 = 头部36字节 + 音频数据 - val byteRate = sampleRate * 2 * 1 // 采样率 * 字节/样本 * 通道数 - - return byteArrayOf( - 'R'.code.toByte(), 'I'.code.toByte(), 'F'.code.toByte(), 'F'.code.toByte(), - (totalLength and 0xFF).toByte(), ((totalLength shr 8) and 0xFF).toByte(), - ((totalLength shr 16) and 0xFF).toByte(), ((totalLength shr 24) and 0xFF).toByte(), - 'W'.code.toByte(), 'A'.code.toByte(), 'V'.code.toByte(), 'E'.code.toByte(), - 'f'.code.toByte(), 'm'.code.toByte(), 't'.code.toByte(), ' '.code.toByte(), - 16, 0, 0, 0, // PCM头长度 - 1, 0, // PCM格式 - 1, 0, // 单声道 - (sampleRate and 0xFF).toByte(), ((sampleRate shr 8) and 0xFF).toByte(), 0, 0, // 采样率 - (byteRate and 0xFF).toByte(), ((byteRate shr 8) and 0xFF).toByte(), 0, 0, // 字节率 - 2, 0, // 块对齐 (通道数 * 样本位数/8) - 16, 0, // 样本位数 - 'd'.code.toByte(), 'a'.code.toByte(), 't'.code.toByte(), 'a'.code.toByte(), - (dataLength and 0xFF).toByte(), ((dataLength shr 8) and 0xFF).toByte(), - ((dataLength shr 16) and 0xFF).toByte(), ((dataLength shr 24) and 0xFF).toByte() - ) - } /** - * 关闭音频资源 + * 优化:简化资源关闭 */ override fun close() { releaseAudioResources() - try { - // 关闭文件流 - fos?.close() - - // 更新WAV文件头 - currentAudioFile?.let { file -> - RandomAccessFile(file, "rw").use { raf -> - raf.seek(0) - raf.write(generateWavHeader(totalBytesWritten)) - } - Log.d(tag, "音频文件保存完成: ${file.absolutePath}") - } - } catch (e: Exception) { - Log.e(tag, "更新WAV文件头失败: ${e.message}") - } finally { - fos = null - currentAudioFile = null - } } /** @@ -763,14 +740,20 @@ private fun saveAudioDataToWav(buffer: ByteArray, length: Int) { } /** - * SDK调用:从队列中拉取数据 + * 修复:外部音频流恢复阻塞读取 * @param buffer SDK提供的缓冲区 * @return 读取的字节数,0表示流结束 */ override fun read(buffer: ByteArray): Int { try { - // 阻塞等待下一块数据 + // 修复:恢复阻塞等待,确保外部音频数据完整性 val chunk = queue.take() + + // 检查是否是结束标志(空数组) + if (chunk.isEmpty()) { + return 0 + } + val toCopy = minOf(chunk.size, buffer.size) System.arraycopy(chunk, 0, buffer, 0, toCopy) return toCopy diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt index 5f973bb44..1e5251de5 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt @@ -70,7 +70,14 @@ class AzureTtsHelper(private val context: Context) : ITtsService { // 创建语音配置 speechConfig = SpeechConfig.fromSubscription(ttsAppToken, ttsResource) - speechConfig?.setSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm) + + // 优化:使用更低延迟的音频格式(从24KHz降到16KHz) + speechConfig?.setSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm) + + // 优化:设置低延迟连接属性 + speechConfig?.setProperty("SpeechServiceConnection_InitialSilenceTimeoutMs", "300") + speechConfig?.setProperty("SpeechServiceConnection_EndSilenceTimeoutMs", "300") + speechConfig?.setSpeechSynthesisVoiceName(currentVoice) // 创建音频配置 @@ -91,6 +98,9 @@ class AzureTtsHelper(private val context: Context) : ITtsService { isInitialized = true FileLogger.d(TAG, "TTS引擎初始化成功: 区域=$ttsResource") + // 优化:初始化完成后立即预热 + warmupSynthesizer() + return true } catch (e: Exception) { FileLogger.e(TAG, "TTS引擎初始化失败: ${e.message}") @@ -243,6 +253,30 @@ class AzureTtsHelper(private val context: Context) : ITtsService { )) } } + + /** + * 预热TTS引擎(减少首次合成延迟) + */ + private fun warmupSynthesizer() { + try { + // 使用极短文本进行预热 + val warmupSsml = """ + + + + . + + + + """.trimIndent() + + // 静音预热(音量设为0) + synthesizer?.SpeakSsmlAsync(warmupSsml) + } catch (e: Exception) { + // 预热失败不影响正常使用 + FileLogger.d(TAG, "TTS预热失败: ${e.message}") + } + } //清洗播报语音内容 fun cleanTextForTTS(text: String): String { return text @@ -320,6 +354,30 @@ class AzureTtsHelper(private val context: Context) : ITtsService { """.trimIndent() } + /** + * 生成优化的SSML(减少复杂度提升速度) + */ + private fun generateOptimizedSsml(rawText: String): String { + // 优化:简化文本预处理,减少正则表达式使用 + val processedText = rawText + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace(Regex("[😀-🟿]+"), "") // 简化表情符号移除 + .trim() + + // 优化:简化SSML结构,减少嵌套层级 + return """ + + + + $processedText + + + + """.trimIndent() + } + /** * 单次播放文本(非流式) */ @@ -334,8 +392,8 @@ class AzureTtsHelper(private val context: Context) : ITtsService { } try { - // 生成SSML并播放 - val ssml = generateSsml(text) + // 优化:使用简化的SSML生成 + val ssml = generateOptimizedSsml(text) isSpeaking = true @@ -373,21 +431,22 @@ class AzureTtsHelper(private val context: Context) : ITtsService { // 添加新文本到缓冲区 streamBuffer.append(text) - // 增加500ms防抖逻辑 + // 优化:大幅减少防抖时间从600ms到150ms val currentTime = System.currentTimeMillis() - if (currentTime - lastSpeakTime < 600) { + if (currentTime - lastSpeakTime < 150) { return true } lastSpeakTime = currentTime val currentText = streamBuffer.toString() //cleanTextForTTS(streamBuffer.toString()) - // 定义标点符号列表 - val punctuationMarks = listOf('.', '。', '!', '!', '?', '?', ';', ';', ',', ',', ':', ':', '\n') - // 查找最后一个标点符号的位置 + // 优化:使用字符集合替代列表,提升查找效率 + val punctuationSet = setOf('.', '。', '!', '!', '?', '?', ';', ';', ',', ',', ':', ':', '\n') + + // 优化:从后往前查找最后一个标点符号 var lastPunctuationIndex = -1 - for (i in currentText.indices.reversed()) { - if (currentText[i] in punctuationMarks) { + for (i in currentText.length - 1 downTo 0) { + if (currentText[i] in punctuationSet) { lastPunctuationIndex = i break } 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 d54ce8298..190e2a1f0 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 @@ -68,7 +68,13 @@ private data class ToolCallInfo( var name: String = "", var arguments: String = "" ) { - fun isValid(): Boolean = id.isNotEmpty() && name.isNotEmpty() + fun isValid(): Boolean { + return try { + id.isNotEmpty() && name.isNotEmpty() + } catch (e: Exception) { + false + } + } } /** @@ -108,6 +114,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor private var toolCalls: MutableMap = mutableMapOf() private var isCanceled = false + + // JSON处理 private val gson = Gson() @@ -124,13 +132,6 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor * 初始化ChatAPI服务 */ fun initialize(apiKey: String, baseUrl: String, model: String, mcpServer: String): Boolean { - Log.d("ChatApiService", "=== ChatApiService 初始化开始 ===") - Log.d("ChatApiService", "参数检查:") - Log.d("ChatApiService", " - apiKey: ${if (apiKey.isNotEmpty()) "已提供(${apiKey.length}字符)" else "未提供"}") - Log.d("ChatApiService", " - baseUrl: '$baseUrl'") - Log.d("ChatApiService", " - model: '$model'") - Log.d("ChatApiService", " - mcpServer: '$mcpServer'") - this.apiKey = apiKey if (baseUrl.isNotEmpty()) { this.baseUrl = baseUrl @@ -138,7 +139,6 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor if (model.isNotEmpty()) { this.model = model } - Log.d("ChatApiService", "原始 baseUrl: $baseUrl") // 处理 baseUrl:移除末尾的 /chat/completions(如果存在) // 因为 openai-kotlin 会自动拼接 /chat/completions @@ -153,24 +153,16 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor processedBaseUrl += "/" } - Log.d("ChatApiService", "处理后 baseUrl: $processedBaseUrl") - return try { - Log.d("ChatApiService", "开始创建OpenAI配置...") // 创建OpenAI配置 val config = OpenAIConfig( token = apiKey, timeout = Timeout(socket = 60.seconds), host = OpenAIHost(baseUrl = processedBaseUrl) ) - Log.d("ChatApiService", "OpenAI配置创建成功") - Log.d("ChatApiService", "正在创建OpenAI客户端...") openAI = OpenAI(config) - Log.d("ChatApiService", "OpenAI客户端创建成功") - // 初始化MCP客户端 - Log.d("ChatApiService", "准备初始化MCP客户端...") // 保存MCP配置以便后续使用 mcpConfigJson = mcpServer // 异步初始化MCP客户端 @@ -179,7 +171,6 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor } isInitialized = apiKey.isNotEmpty() - Log.d("ChatApiService", "ChatApiService初始化完成,isInitialized: $isInitialized") true } catch (e: Exception) { Log.e("ChatApiService", "ChatApiService初始化失败: ${e.message}", e) @@ -231,72 +222,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor // 转换消息格式 val chatMessages = convertToChatMessages(messages) - // 获取MCP工具列表 - val tools = mutableListOf() - mcpClient?.getToolMaps()?.forEach { toolMap -> - try { - Log.d("ChatApiService", "处理工具映射: $toolMap") - - val type = toolMap["type"] as? String - if (type == "function") { - @Suppress("UNCHECKED_CAST") - val functionMap = toolMap["function"] as? Map - if (functionMap == null) { - Log.e("ChatApiService", "工具function映射为null") - return@forEach - } - - val name = functionMap["name"] as? String - if (name == null) { - Log.e("ChatApiService", "工具name为null") - return@forEach - } - - val description = functionMap["description"] as? String ?: "" - val parametersMap = functionMap["parameters"] as? Map - - Log.d("ChatApiService", "工具信息 - 名称: $name, 描述: $description") - Log.d("ChatApiService", "参数映射: $parametersMap") - - if (parametersMap == null) { - Log.e("ChatApiService", "工具 $name 的parameters为null") - return@forEach - } - - // 验证parametersMap的基本结构 - if (!parametersMap.containsKey("type")) { - Log.e("ChatApiService", "工具 $name 的parameters缺少type字段") - return@forEach - } - - val parametersJson = gson.toJson(parametersMap) - Log.d("ChatApiService", "参数JSON: $parametersJson") - - try { - val parameters = com.aallam.openai.api.core.Parameters.fromJsonString(parametersJson) - Log.d("ChatApiService", "成功创建Parameters对象") - - tools.add(Tool.function( - name = name, - description = description, - parameters = parameters - )) - - Log.d("ChatApiService", "成功添加工具: $name") - - } catch (e: Exception) { - Log.e("ChatApiService", "创建Parameters对象失败 for 工具 $name: ${e.message}", e) - Log.e("ChatApiService", "失败的参数JSON: $parametersJson") - // 跳过这个工具,继续处理其他工具 - } - } else { - Log.d("ChatApiService", "跳过非function类型的工具: $type") - } - } catch (e: Exception) { - Log.e("ChatApiService", "处理工具映射时出错: ${e.message}", e) - Log.e("ChatApiService", "出错的工具映射: $toolMap") - } - } + // 直接获取工具列表 + val tools = getOpenAiTools() // 构建请求 val chatCompletionRequest = ChatCompletionRequest( @@ -344,12 +271,6 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor * 发送消息(流式输出) */ fun sendMessageStream(messages: List>) { - Log.d("ChatApiService", "=== sendMessageStream 开始 ===") - Log.d("ChatApiService", "isInitialized: $isInitialized") - Log.d("ChatApiService", "apiKey isEmpty: ${apiKey.isEmpty()}") - Log.d("ChatApiService", "openAI is null: ${openAI == null}") - Log.d("ChatApiService", "messages size: ${messages.size}") - if (!isInitialized || apiKey.isEmpty() || openAI == null) { Log.e("ChatApiService", "ChatAPI服务未初始化,无法发送消息") streamCallback?.onError(ChatApiException("ChatAPI服务未初始化")) @@ -363,140 +284,28 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor // 检查是否包含图片,决定使用哪个模型 val currentModel = if (containsImage(messages)) visionModel else model - Log.d("ChatApiService", "选择的模型: $currentModel (默认模型: $model, 视觉模型: $visionModel)") currentStreamJob = launch { try { - Log.e("ChatApiService", "=== 开始流式请求 ===") - Log.e("ChatApiService", "消息数量: ${messages.size}") - Log.e("ChatApiService", "当前模型: $currentModel") - Log.e("ChatApiService", "MCP客户端状态: ${mcpClient != null}") - // 转换消息格式 - Log.e("ChatApiService", "开始转换消息格式...") val chatMessages = try { convertToChatMessages(messages) } catch (e: Exception) { - Log.e("ChatApiService", "ERROR: 转换消息格式失败: ${e.message}", e) + Log.e("ChatApiService", "转换消息格式失败: ${e.message}", e) throw e } - Log.e("ChatApiService", "转换后的聊天消息数量: ${chatMessages.size}") - // 验证转换后的消息 - chatMessages.forEachIndexed { index, message -> - Log.e("ChatApiService", "消息 $index: role=${message.role}, content类型=${message.content?.javaClass?.simpleName}") - } - - // 获取MCP工具列表 - Log.e("ChatApiService", "开始获取MCP工具列表...") - val tools = mutableListOf() - - val toolMaps = mcpClient?.getToolMaps() - Log.e("ChatApiService", "获取到的工具映射数量: ${toolMaps?.size ?: 0}") - - toolMaps?.forEach { toolMap -> - try { - Log.e("ChatApiService", "处理工具映射: $toolMap") - - val type = toolMap["type"] as? String - if (type == "function") { - @Suppress("UNCHECKED_CAST") - val functionMap = toolMap["function"] as? Map - if (functionMap == null) { - Log.e("ChatApiService", "ERROR: 工具function映射为null") - return@forEach - } - - val name = functionMap["name"] as? String - if (name == null) { - Log.e("ChatApiService", "ERROR: 工具name为null") - return@forEach - } - - val description = functionMap["description"] as? String ?: "" - val parametersMap = functionMap["parameters"] as? Map - - Log.e("ChatApiService", "工具信息 - 名称: $name, 描述: $description") - Log.e("ChatApiService", "参数映射: $parametersMap") - - if (parametersMap == null) { - Log.e("ChatApiService", "ERROR: 工具 $name 的parameters为null") - return@forEach - } - - // 验证parametersMap的基本结构 - if (!parametersMap.containsKey("type")) { - Log.e("ChatApiService", "ERROR: 工具 $name 的parameters缺少type字段") - return@forEach - } - - val parametersJson = gson.toJson(parametersMap) - Log.e("ChatApiService", "参数JSON: $parametersJson") - - try { - Log.e("ChatApiService", "正在调用 Parameters.fromJsonString...") - Log.e("ChatApiService", "参数JSON长度: ${parametersJson.length}") - Log.e("ChatApiService", "参数JSON内容预览: ${parametersJson.take(200)}...") - - if (parametersJson.isBlank()) { - Log.e("ChatApiService", "ERROR: parametersJson为空或空白") - return@forEach - } - - val parameters = com.aallam.openai.api.core.Parameters.fromJsonString(parametersJson) - Log.e("ChatApiService", "SUCCESS: 成功创建Parameters对象") - - Log.e("ChatApiService", "正在创建Tool.function...") - Log.e("ChatApiService", "工具参数 - name: '$name' (isEmpty: ${name.isEmpty()})") - Log.e("ChatApiService", "工具参数 - description: '$description' (isEmpty: ${description.isEmpty()})") - Log.e("ChatApiService", "工具参数 - parameters: ${parameters != null}") - - if (name.isEmpty()) { - Log.e("ChatApiService", "ERROR: 工具名称为空,跳过") - return@forEach - } - - val tool = Tool.function( - name = name, - description = description, - parameters = parameters - ) - tools.add(tool) - - Log.e("ChatApiService", "SUCCESS: 成功添加工具: $name") - - } catch (e: Exception) { - Log.e("ChatApiService", "ERROR: 创建Parameters对象失败 for 工具 $name: ${e.message}", e) - Log.e("ChatApiService", "失败的参数JSON: $parametersJson") - Log.e("ChatApiService", "异常堆栈: ${e.stackTraceToString()}") - // 跳过这个工具,继续处理其他工具 - } - } else { - Log.e("ChatApiService", "跳过非function类型的工具: $type") - } - } catch (e: Exception) { - Log.e("ChatApiService", "ERROR: 处理工具映射时出错: ${e.message}", e) - Log.e("ChatApiService", "出错的工具映射: $toolMap") - } - } - - Log.e("ChatApiService", "最终工具列表大小: ${tools.size}") + // 直接获取工具列表 + val tools = getOpenAiTools() // 构建请求 - Log.e("ChatApiService", "正在构建ChatCompletionRequest...") - Log.e("ChatApiService", "构建参数检查:") - Log.e("ChatApiService", " - currentModel: '$currentModel' (isEmpty: ${currentModel.isEmpty()})") - Log.e("ChatApiService", " - chatMessages size: ${chatMessages.size}") - Log.e("ChatApiService", " - tools size: ${tools.size}") - Log.e("ChatApiService", " - openAI对象: ${openAI != null}") - if (currentModel.isEmpty()) { - Log.e("ChatApiService", "ERROR: 模型名称为空") + Log.e("ChatApiService", "模型名称为空") throw IllegalArgumentException("模型名称不能为空") } if (chatMessages.isEmpty()) { - Log.e("ChatApiService", "ERROR: 消息列表为空") + Log.e("ChatApiService", "消息列表为空") throw IllegalArgumentException("消息列表不能为空") } @@ -508,53 +317,41 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor temperature = 0.7, tools = if (tools.isNotEmpty()) tools else null ) - Log.e("ChatApiService", "SUCCESS: ChatCompletionRequest创建成功") if (openAI == null) { - Log.e("ChatApiService", "ERROR: openAI对象为null") + Log.e("ChatApiService", "openAI对象为null") throw IllegalStateException("OpenAI客户端未初始化") } - Log.e("ChatApiService", "正在调用 openAI.chatCompletions...") val flow = openAI!!.chatCompletions(chatCompletionRequest) - Log.e("ChatApiService", "SUCCESS: 获得chatsFlow,开始收集...") flow } catch (e: Exception) { - Log.e("ChatApiService", "ERROR: 创建ChatCompletionRequest或调用chatCompletions失败: ${e.message}", e) + Log.e("ChatApiService", "创建ChatCompletionRequest或调用chatCompletions失败: ${e.message}", e) throw e } chatsFlow.collect { result -> if (isCanceled) return@collect - Log.e("ChatApiService", "收到流式响应,结果类型: ${result.javaClass.simpleName}") - Log.e("ChatApiService", "result.choices大小: ${result.choices?.size ?: 0}") - val choice = result.choices?.firstOrNull() if (choice == null) { - Log.e("ChatApiService", "ERROR: choice为null") + Log.e("ChatApiService", "choice为null") return@collect } - Log.e("ChatApiService", "choice存在,delta类型: ${choice.delta?.javaClass?.simpleName ?: "null"}") - val delta = choice.delta if (delta == null) { - Log.e("ChatApiService", "ERROR: delta为null") + Log.e("ChatApiService", "delta为null") return@collect } - Log.e("ChatApiService", "delta存在,content: ${delta.content}, toolCalls: ${delta.toolCalls?.size ?: 0}") - // 处理普通文本内容 delta.content?.let { content -> - Log.e("ChatApiService", "收到文本内容: $content") streamCallback?.onToken(content) } // 收集工具调用信息 delta.toolCalls?.forEach { toolCall -> - Log.e("ChatApiService", "处理工具调用: index=${toolCall.index}, id=${toolCall.id}") val index = toolCall.index // 创建或获取现有的工具调用信息 @@ -564,7 +361,6 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor try { toolCall.id?.let { id -> toolCallInfo.id = id.toString() - Log.e("ChatApiService", "更新工具调用ID: $id") } } catch (e: Exception) { Log.e("ChatApiService", "处理工具调用ID异常: ${e.message}") @@ -573,13 +369,20 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor // 安全处理函数信息 try { toolCall.function?.let { function -> - function.name?.let { name -> - toolCallInfo.name = name - Log.e("ChatApiService", "更新工具名称: $name") + try { + function.name?.let { name -> + toolCallInfo.name = name + } + } catch (e: Exception) { + Log.e("ChatApiService", "处理工具调用函数名称异常: ${e.message}") } - function.arguments?.let { args -> - toolCallInfo.arguments += args - Log.e("ChatApiService", "添加参数片段: $args") + + try { + function.arguments?.let { args -> + toolCallInfo.arguments += args + } + } catch (e: Exception) { + Log.e("ChatApiService", "处理工具调用参数异常: ${e.message}") } } } catch (e: Exception) { @@ -589,7 +392,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor } if (!isCanceled) { - // 处理工具调用或完成 + // 检查是否有工具调用需要处理 val hasToolCalls = processToolCalls() if (!hasToolCalls) { streamCallback?.onComplete() @@ -608,25 +411,28 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor * 处理工具调用 */ private suspend fun processToolCalls(): Boolean { - Log.d("ChatApiService", "开始处理工具调用,工具调用数量: ${toolCalls.size}") + val firstToolCall = try { + toolCalls.values.firstOrNull { it.isValid() } + } catch (e: Exception) { + Log.e("ChatApiService", "查找有效工具调用异常: ${e.message}") + null + } - val firstToolCall = toolCalls.values.firstOrNull { it.isValid() } if (firstToolCall == null) { - Log.d("ChatApiService", "没有有效的工具调用") return false } - Log.d("ChatApiService", "第一个有效的工具调用: name=${firstToolCall.name}, id=${firstToolCall.id}") - Log.d("ChatApiService", "工具调用参数: ${firstToolCall.arguments}") - // 创建函数调用字典 - val functionCall = mapOf( - "name" to firstToolCall.name, - "arguments" to firstToolCall.arguments, - "id" to firstToolCall.id - ) - - Log.d("ChatApiService", "创建的函数调用字典: $functionCall") + val functionCall = try { + mapOf( + "name" to (firstToolCall.name.takeIf { it.isNotEmpty() } ?: ""), + "arguments" to (firstToolCall.arguments.takeIf { it.isNotEmpty() } ?: "{}"), + "id" to (firstToolCall.id.takeIf { it.isNotEmpty() } ?: "") + ) + } catch (e: Exception) { + Log.e("ChatApiService", "创建函数调用字典异常: ${e.message}") + return false + } // 通知上层工具调用事件 streamCallback?.onFunctionCall(convertMapToJsonObject(functionCall)) @@ -639,36 +445,25 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor val functionName = firstToolCall.name val argumentsJson = firstToolCall.arguments - Log.d("ChatApiService", "准备调用MCP工具: $functionName") - Log.d("ChatApiService", "参数JSON: $argumentsJson") - val result = if (_mcpClient?.hasToolWithName(functionName) == true) { - Log.d("ChatApiService", "MCP客户端中找到工具: $functionName") - // 解析参数 val arguments = _mcpClient?.parseJsonArguments(argumentsJson) ?: emptyMap() - Log.d("ChatApiService", "解析后的参数: $arguments") // 调用MCP工具 - Log.d("ChatApiService", "开始调用MCP工具...") val toolResult = _mcpClient?.callTool(functionName, arguments) - Log.d("ChatApiService", "MCP工具调用结果: $toolResult") // 处理结果 if (toolResult != null) { if (toolResult["isError"] == true) { - Log.w("ChatApiService", "MCP工具调用返回错误") // 处理错误情况 val content = toolResult["content"] as? List<*> val firstContent = content?.firstOrNull() as? Map<*, *> val errorText = firstContent?.get("text") as? String ?: "Tool execution failed" mapOf("context" to errorText) } else if (toolResult.containsKey("context")) { - Log.d("ChatApiService", "本地函数结果") // 本地函数结果 toolResult } else { - Log.d("ChatApiService", "MCP工具结果") // MCP工具结果 val content = toolResult["content"] as? List<*> val firstContent = content?.firstOrNull() as? Map<*, *> @@ -680,13 +475,10 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor mapOf("context" to "Tool call failed") } } else { - Log.w("ChatApiService", "MCP客户端中未找到工具: $functionName") // 工具不存在 mapOf("context" to "Tool not found: $functionName") } - Log.d("ChatApiService", "最终处理结果: $result") - if (!isCanceled) { // 处理结果 streamCallback?.onFunctionCallResult( @@ -808,6 +600,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor handler = handler ) + + true } catch (e: Exception) { Log.e("ChatApiService", "Failed to register function: $name", e) @@ -826,8 +620,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor // 直接使用类的CoroutineScope启动协程 launch { try { - val result = _mcpClient?.connectToSSE(serverUrl) ?: false - Log.d("ChatApiService", "MCP客户端初始化${if (result) "成功" else "失败"}") + _mcpClient?.connectToSSE(serverUrl) + } catch (e: Exception) { Log.e("ChatApiService", "MCP客户端初始化失败: ${e.message}", e) } @@ -843,6 +637,88 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor return _mcpClient?.isConnected() ?: false } + /** + * 直接从MCPClient获取OpenAI工具格式 + * 将MCP工具映射转换为OpenAI工具格式 + */ + private fun getOpenAiTools(): List { + try { + val tools = mutableListOf() + val toolMaps = mcpClient?.getToolMaps() ?: return emptyList() + + toolMaps.forEach { toolMap -> + try { + val type = toolMap["type"] as? String + if (type == "function") { + @Suppress("UNCHECKED_CAST") + val functionMap = toolMap["function"] as? Map + if (functionMap == null) { + Log.e("ChatApiService", "工具function映射为null") + return@forEach + } + + val name = functionMap["name"] as? String + if (name == null) { + Log.e("ChatApiService", "工具name为null") + return@forEach + } + + val description = functionMap["description"] as? String ?: "" + val parametersMap = functionMap["parameters"] as? Map + + if (parametersMap == null) { + Log.e("ChatApiService", "工具 $name 的parameters为null") + return@forEach + } + + // 验证parametersMap的基本结构 + if (!parametersMap.containsKey("type")) { + Log.e("ChatApiService", "工具 $name 的parameters缺少type字段") + return@forEach + } + + val parametersJson = gson.toJson(parametersMap) + + try { + if (parametersJson.isBlank()) { + Log.e("ChatApiService", "parametersJson为空或空白") + return@forEach + } + + val parameters = com.aallam.openai.api.core.Parameters.fromJsonString(parametersJson) + + if (name.isEmpty()) { + Log.e("ChatApiService", "工具名称为空,跳过") + return@forEach + } + + val tool = Tool.function( + name = name, + description = description, + parameters = parameters + ) + tools.add(tool) + + } catch (e: Exception) { + Log.e("ChatApiService", "创建Parameters对象失败 for 工具 $name: ${e.message}", e) + // 跳过这个工具,继续处理其他工具 + } + } + } catch (e: Exception) { + Log.e("ChatApiService", "处理工具映射时出错: ${e.message}", e) + } + } + + return tools + + } catch (e: Exception) { + Log.e("ChatApiService", "获取OpenAI工具格式失败: ${e.message}", e) + return emptyList() + } + } + + + /** * 关闭MCP客户端 */ @@ -864,7 +740,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor return "MCP客户端未初始化" } - try { + return try { // 获取函数名称 val name = functionCall.getString("name") @@ -876,10 +752,10 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor val result = _mcpClient?.callTool(name, arguments) // 返回工具调用结果 - return result?.get("context") as? String ?: "工具调用失败" + result?.get("context") as? String ?: "工具调用失败" } catch (e: Exception) { Log.e("ChatApiService", "处理MCP工具调用失败: ${e.message}", e) - return "处理MCP工具调用失败: ${e.message}" + "处理MCP工具调用失败: ${e.message}" } } @@ -890,6 +766,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor return _mcpClient?.getToolMaps() ?: emptyList() } + + /** * 处理图片 */ diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt index 945dde9f3..2e6fa877e 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt @@ -105,13 +105,12 @@ class CustomSseClientTransport( } "open" -> { - Log.d(TAG, "SSE连接已打开") + // SSE连接已打开 } "endpoint" -> { try { val eventData = event.data ?: "" - Log.d(TAG, "收到endpoint事件: $eventData") // 构建完整的端点URL val fullEndpoint = if (eventData.contains(hostPart)) { @@ -135,7 +134,6 @@ class CustomSseClientTransport( fullEndpoint } - Log.d(TAG, "最终消息端点: $endpointWithParams") endpoint.complete(endpointWithParams) } catch (e: Exception) { Log.e(TAG, "处理endpoint事件失败: ${e.message}", e) @@ -182,11 +180,6 @@ class CustomSseClientTransport( hostPart = urlInfo.first pathPart = urlInfo.second queryParams = urlInfo.third - - Log.d(TAG, "原始URL: $urlString") - Log.d(TAG, "主机部分: $hostPart") - Log.d(TAG, "路径部分: $pathPart") - Log.d(TAG, "查询参数: $queryParams") } // 创建SSE会话 - 直接使用原始URL @@ -202,8 +195,6 @@ class CustomSseClientTransport( "$hostPart$pathPart" } - Log.d(TAG, "SSE连接URL: $sseConnectUrl") - client.sseSession( urlString = sseConnectUrl, reconnectionTime = reconnectionTime, @@ -219,7 +210,6 @@ class CustomSseClientTransport( // 等待endpoint就绪 endpoint.await() - Log.d(TAG, "传输层启动完成,消息端点已就绪") } /** @@ -234,7 +224,6 @@ class CustomSseClientTransport( try { val messageEndpoint = endpoint.getCompleted() - Log.d(TAG, "发送消息到: $messageEndpoint") val jsonString = json.encodeToString(message) @@ -266,6 +255,5 @@ class CustomSseClientTransport( session.cancel() _onClose() job?.cancelAndJoin() - Log.d(TAG, "传输层已关闭") } } \ No newline at end of file diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPClient.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPClient.kt index 9aadf3d43..7231f4999 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPClient.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPClient.kt @@ -98,14 +98,12 @@ class MCPClient(private val context: Context? = null) : AutoCloseable { if (subClient.connect()) { subClients[serverId] = subClient connectedCount++ - Log.d(TAG, "Connected to MCP server: $serverId") } else { Log.w(TAG, "Failed to connect to MCP server: $serverId") } } isConnectedFlag = connectedCount > 0 - Log.d(TAG, "Connected to $connectedCount MCP servers") connectedCount > 0 } catch (e: Exception) { @@ -121,7 +119,6 @@ class MCPClient(private val context: Context? = null) : AutoCloseable { subClients.forEach { (serverId, client) -> try { client.close() - Log.d(TAG, "已关闭子客户端 [$serverId]") } catch (e: Exception) { Log.e(TAG, "关闭子客户端 [$serverId] 失败: ${e.message}") } @@ -177,7 +174,6 @@ class MCPClient(private val context: Context? = null) : AutoCloseable { ) localFunctionDefs[name] = functionDef - Log.d(TAG, "Registered local function: $name") return true } catch (e: Exception) { Log.e(TAG, "注册本地函数失败: ${e.message}", e) @@ -192,7 +188,6 @@ class MCPClient(private val context: Context? = null) : AutoCloseable { val removed = localFunctions.remove(name) != null if (removed) { localFunctionDefs.remove(name) - Log.d(TAG, "Unregistered local function: $name") } return removed } @@ -284,10 +279,29 @@ class MCPClient(private val context: Context? = null) : AutoCloseable { */ fun parseJsonArguments(json: String): Map { return try { - val jsonObject = JSONObject(json) + // 处理空字符串或空白字符串 + val trimmedJson = json.trim() + if (trimmedJson.isEmpty()) { + return emptyMap() + } + + // 如果不是以{开头,尝试包装为{} + val jsonToUse = if (!trimmedJson.startsWith("{")) { + if (trimmedJson.contains("=") || trimmedJson.contains(":")) { + // 简单的键值对,包装成JSON对象 + "{$trimmedJson}" + } else { + // 空参数,返回空对象 + "{}" + } + } else { + trimmedJson + } + + val jsonObject = JSONObject(jsonToUse) convertJsonObjectToMap(jsonObject) } catch (e: Exception) { - Log.w(TAG, "Failed to parse JSON arguments", e) + Log.w(TAG, "Failed to parse JSON arguments: '$json'", e) emptyMap() } } @@ -304,7 +318,6 @@ class MCPClient(private val context: Context? = null) : AutoCloseable { */ suspend fun disconnectAll() { closeAllConnections() - Log.d(TAG, "Disconnected all MCP clients") } /** @@ -315,7 +328,6 @@ class MCPClient(private val context: Context? = null) : AutoCloseable { closeAllConnections() localFunctions.clear() localFunctionDefs.clear() - Log.d(TAG, "已关闭MCP客户端") } } diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt index eb3a7d4f4..60eacea0a 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt @@ -41,8 +41,6 @@ class MCPSubClient( if (isConnected) return true return try { - Log.d(TAG, "[$serverId] 开始连接到MCP服务器: $serverUrl") - // 创建MCP客户端实例 val client = Client( clientInfo = Implementation( @@ -55,7 +53,6 @@ class MCPSubClient( val transport = when { serverUrl.startsWith("http://") || serverUrl.startsWith("https://") -> { // SSE传输 - 使用自定义的CustomSseClientTransport - Log.d(TAG, "[$serverId] 创建自定义SSE传输,URL: $serverUrl") val mcpHttpClient = httpClient ?: createMcpHttpClient() CustomSseClientTransport( client = mcpHttpClient, @@ -77,12 +74,6 @@ class MCPSubClient( if (toolsResult != null) { availableTools.clear() availableTools.addAll(toolsResult.tools) - Log.d(TAG, "[$serverId] 获取到 ${availableTools.size} 个工具") - - // 打印工具信息 - availableTools.forEach { tool -> - Log.d(TAG, "[$serverId] 工具: ${tool.name} - ${tool.description}") - } } } catch (e: Exception) { Log.w(TAG, "[$serverId] 获取工具列表失败: ${e.message}") @@ -91,7 +82,6 @@ class MCPSubClient( mcpClient = client isConnected = true - Log.d(TAG, "[$serverId] MCP连接成功") true } catch (e: Exception) { @@ -111,13 +101,7 @@ class MCPSubClient( * 获取工具映射列表 */ fun getToolMaps(): List> { - Log.d(TAG, "[$serverId] 开始获取工具映射,工具数量: ${availableTools.size}") - val toolMaps = availableTools.map { tool -> - Log.d(TAG, "[$serverId] 处理工具: ${tool.name}") - Log.d(TAG, "[$serverId] 工具描述: ${tool.description}") - Log.d(TAG, "[$serverId] 输入Schema: ${tool.inputSchema}") - val parametersMap = tool.inputSchema?.let { inputSchema -> convertInputSchemaToMap(inputSchema) } ?: mapOf( @@ -126,8 +110,6 @@ class MCPSubClient( "required" to emptyList() ) - Log.d(TAG, "[$serverId] 转换后的参数映射: $parametersMap") - val toolMap = mapOf( "type" to "function", "function" to mapOf( @@ -137,11 +119,9 @@ class MCPSubClient( ) ) - Log.d(TAG, "[$serverId] 最终工具映射: $toolMap") toolMap } - Log.d(TAG, "[$serverId] 完成工具映射生成,返回 ${toolMaps.size} 个工具") return toolMaps } @@ -152,8 +132,6 @@ class MCPSubClient( val client = mcpClient ?: return null return try { - Log.d(TAG, "[$serverId] 调用工具: $name, 参数: $arguments") - // 创建工具调用请求 - 将Map转换为JsonObject val argumentsJson = kotlinx.serialization.json.buildJsonObject { arguments.forEach { (key, value) -> @@ -175,8 +153,6 @@ class MCPSubClient( val result = client.callTool(request) result?.let { callResult -> - Log.d(TAG, "[$serverId] 工具调用结果: ${callResult.content.size} 个内容项") - // 将结果转换为统一格式 val contentList = callResult.content.map { contentItem -> // 根据不同的内容类型处理 @@ -208,16 +184,12 @@ class MCPSubClient( * 将Tool.Input转换为Map格式,供OpenAI使用 */ private fun convertInputSchemaToMap(inputSchema: Tool.Input): Map { - Log.d(TAG, "开始转换Input Schema: $inputSchema") - val properties = mutableMapOf() val required = mutableListOf() // 处理properties inputSchema.properties?.let { propsJsonObject -> - Log.d(TAG, "处理properties: $propsJsonObject") for ((key, value) in propsJsonObject) { - Log.d(TAG, "处理属性: $key = $value (${value::class.java.simpleName})") when (value) { is JsonPrimitive -> { if (value.isString) { @@ -235,13 +207,12 @@ class MCPSubClient( } } } - } ?: Log.d(TAG, "properties为null") + } // 处理required inputSchema.required?.let { requiredList -> - Log.d(TAG, "处理required: $requiredList") required.addAll(requiredList) - } ?: Log.d(TAG, "required为null") + } val result = mapOf( "type" to "object", @@ -249,7 +220,6 @@ class MCPSubClient( "required" to required ) - Log.d(TAG, "转换后的Schema Map: $result") return result } @@ -296,7 +266,6 @@ class MCPSubClient( if (toolsResult != null) { availableTools.clear() availableTools.addAll(toolsResult.tools) - Log.d(TAG, "[$serverId] 刷新工具列表成功,共 ${availableTools.size} 个工具") true } else { false @@ -318,7 +287,6 @@ class MCPSubClient( mcpClient = null isConnected = false availableTools.clear() - Log.d(TAG, "[$serverId] MCP连接已关闭") } catch (e: Exception) { Log.e(TAG, "[$serverId] 关闭MCP连接时出错: ${e.message}", e) }