Browse Source

add

newdev_shunjiawei
wolfplus2048 1 year ago
parent
commit
8fff315c2a
  1. 2
      lib/modules/agent/controllers/agent_controller.dart
  2. 113
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  3. 40
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt
  4. 47
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt
  5. 4
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt

2
lib/modules/agent/controllers/agent_controller.dart

@ -350,7 +350,7 @@ class AgentController extends GetxController {
final token = event.data['token'] ?? ''; final token = event.data['token'] ?? '';
final responseId = event.data['responseId'] ?? ''; final responseId = event.data['responseId'] ?? '';
Logger.i(TAG, 'AI回复Token: $token, responseId: $responseId'); // Logger.i(TAG, 'AI回复Token: $token, responseId: $responseId');
if (token.isNotEmpty) { if (token.isNotEmpty) {
// 如果是新的回复或者响应ID改变,创建新消息 // 如果是新的回复或者响应ID改变,创建新消息
if (_isNewAssistantResponse || if (_isNewAssistantResponse ||

113
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -16,6 +16,9 @@ import java.util.Collections
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import com.yunqiinnovation.ble_service.BleService import com.yunqiinnovation.ble_service.BleService
import android.media.MediaPlayer import android.media.MediaPlayer
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import com.deep_voice.speech.tts.TtsEvent import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType import com.deep_voice.speech.tts.TtsEventType
@ -43,7 +46,7 @@ object AgentService : CoroutineScope {
// 协程相关 // 协程相关
private val job = SupervisorJob() private val job = SupervisorJob()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job get() = Dispatchers.IO + job
// 上下文和监听器 // 上下文和监听器
private lateinit var context: Context private lateinit var context: Context
@ -73,16 +76,24 @@ object AgentService : CoroutineScope {
// 音频播放器 // 音频播放器
private var audioPlayer: AudioPlayer? = null // 初始化音频播放器 private var audioPlayer: AudioPlayer? = null // 初始化音频播放器
// 状态 // 状态 - 使用原子类型确保线程安全
private var isInitialized = false private val _isInitialized = AtomicBoolean(false)
var isRecognitionActive = false val isInitialized: Boolean get() = _isInitialized.get()
private set
var isTtsSpeaking = false private val _isRecognitionActive = AtomicBoolean(false)
private set val isRecognitionActive: Boolean get() = _isRecognitionActive.get()
var hasSpeechDetected = false
private set private val _isTtsSpeaking = AtomicBoolean(false)
var isAiStreaming = false val isTtsSpeaking: Boolean get() = _isTtsSpeaking.get()
private set
private val _hasSpeechDetected = AtomicBoolean(false)
val hasSpeechDetected: Boolean get() = _hasSpeechDetected.get()
private val _isAiStreaming = AtomicBoolean(false)
val isAiStreaming: Boolean get() = _isAiStreaming.get()
// 用于保护复杂状态操作的互斥锁
private val stateMutex = Mutex()
// AI流生成相关 // AI流生成相关
private var currentAiJob: Job? = null private var currentAiJob: Job? = null
@ -175,7 +186,7 @@ object AgentService : CoroutineScope {
// 加载最近的聊天记录 // 加载最近的聊天记录
loadChatHistory() loadChatHistory()
isInitialized = true _isInitialized.set(true)
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "初始化失败: ${e.message}") Log.e(TAG, "初始化失败: ${e.message}")
@ -201,6 +212,7 @@ object AgentService : CoroutineScope {
// 释放ChatAPI服务 // 释放ChatAPI服务
if (::chatApiService.isInitialized) { if (::chatApiService.isInitialized) {
chatApiService.cancelCurrentStream() chatApiService.cancelCurrentStream()
chatApiService.dispose()
} }
// 释放TTS服务 // 释放TTS服务
@ -209,7 +221,7 @@ object AgentService : CoroutineScope {
job.cancel() job.cancel()
clearListeners() clearListeners()
isInitialized = false _isInitialized.set(false)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "释放资源异常: ${e.message}") Log.e(TAG, "释放资源异常: ${e.message}")
} }
@ -252,17 +264,17 @@ object AgentService : CoroutineScope {
override fun onEvent(event: TtsEvent) { override fun onEvent(event: TtsEvent) {
when (event.type) { when (event.type) {
TtsEventType.SYNTHESIS_STARTED -> { TtsEventType.SYNTHESIS_STARTED -> {
isTtsSpeaking = true _isTtsSpeaking.set(true)
restartIdleCheck() restartIdleCheck()
sendEvent("tts_started", mapOf("status" to "started")) sendEvent("tts_started", mapOf("status" to "started"))
} }
TtsEventType.SYNTHESIS_COMPLETED -> { TtsEventType.SYNTHESIS_COMPLETED -> {
isTtsSpeaking = false _isTtsSpeaking.set(false)
restartIdleCheck() restartIdleCheck()
sendEvent("tts_completed", mapOf("status" to "completed")) sendEvent("tts_completed", mapOf("status" to "completed"))
} }
TtsEventType.SYNTHESIS_CANCELED -> { TtsEventType.SYNTHESIS_CANCELED -> {
isTtsSpeaking = false _isTtsSpeaking.set(false)
restartIdleCheck() restartIdleCheck()
sendEvent("tts_canceled", mapOf("status" to "canceled")) sendEvent("tts_canceled", mapOf("status" to "canceled"))
} }
@ -275,7 +287,7 @@ object AgentService : CoroutineScope {
sendEvent("playback_completed", mapOf("status" to "playback_completed")) sendEvent("playback_completed", mapOf("status" to "playback_completed"))
} }
TtsEventType.ERROR -> { TtsEventType.ERROR -> {
isTtsSpeaking = false _isTtsSpeaking.set(false)
restartIdleCheck() restartIdleCheck()
val params = event.params val params = event.params
val code = params["errorCode"] as? String ?: "UNKNOWN_ERROR" val code = params["errorCode"] as? String ?: "UNKNOWN_ERROR"
@ -379,8 +391,8 @@ object AgentService : CoroutineScope {
return false return false
} }
isRecognitionActive = true _isRecognitionActive.set(true)
hasSpeechDetected = false _hasSpeechDetected.set(false)
try { try {
val audioSourceType = if (isExternalActive) { val audioSourceType = if (isExternalActive) {
@ -393,7 +405,7 @@ object AgentService : CoroutineScope {
if (recognizing.isNotEmpty()) { if (recognizing.isNotEmpty()) {
// 检测到语音,更新状态 // 检测到语音,更新状态
val previousHasSpeech = hasSpeechDetected val previousHasSpeech = hasSpeechDetected
hasSpeechDetected = true _hasSpeechDetected.set(true)
if (!previousHasSpeech) { if (!previousHasSpeech) {
restartIdleCheck() restartIdleCheck()
@ -408,21 +420,21 @@ object AgentService : CoroutineScope {
if (isTtsSpeaking || isAiStreaming) { if (isTtsSpeaking || isAiStreaming) {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
// // 防抖处理:避免过于频繁的打断 // 防抖处理:避免过于频繁的打断
// if (currentTime - lastInterruptTime < INTERRUPT_DEBOUNCE_MS) { if (currentTime - lastInterruptTime < INTERRUPT_DEBOUNCE_MS) {
// return return
// } }
// // 简单过滤:太短的内容可能是噪音 // 简单过滤:太短的内容可能是噪音
// if (recognizing.trim().length < 2) { if (recognizing.trim().length < 2) {
// return return
// } }
// // 过滤纯语气词 // 过滤纯语气词
// val trimmedText = recognizing.trim().lowercase() val trimmedText = recognizing.trim().lowercase()
// if (FILLER_WORDS.contains(trimmedText)) { if (FILLER_WORDS.contains(trimmedText)) {
// return return
// } }
// 执行打断 // 执行打断
lastInterruptTime = currentTime lastInterruptTime = currentTime
@ -443,7 +455,7 @@ object AgentService : CoroutineScope {
// 重置状态,继续识别 // 重置状态,继续识别
val previousHasSpeech = hasSpeechDetected val previousHasSpeech = hasSpeechDetected
hasSpeechDetected = false _hasSpeechDetected.set(false)
if (previousHasSpeech) { if (previousHasSpeech) {
restartIdleCheck() restartIdleCheck()
@ -460,13 +472,13 @@ object AgentService : CoroutineScope {
override fun onSessionStopped() { override fun onSessionStopped() {
sendEvent("recognition_stopped", mapOf("status" to "stopped")) sendEvent("recognition_stopped", mapOf("status" to "stopped"))
isRecognitionActive = false _isRecognitionActive.set(false)
stopIdleCheck() stopIdleCheck()
audioPlayer?.playAudio(R.raw.stop) audioPlayer?.playAudio(R.raw.stop)
} }
override fun onCanceled(reason: String, errorDetails: String) { override fun onCanceled(reason: String, errorDetails: String) {
isRecognitionActive = false _isRecognitionActive.set(false)
stopIdleCheck() stopIdleCheck()
BleService.closeCodec() BleService.closeCodec()
sendEvent("recognition_canceled", mapOf( sendEvent("recognition_canceled", mapOf(
@ -476,7 +488,7 @@ object AgentService : CoroutineScope {
} }
override fun onError(error: String) { override fun onError(error: String) {
isRecognitionActive = false _isRecognitionActive.set(false)
stopIdleCheck() stopIdleCheck()
BleService.closeCodec() BleService.closeCodec()
Log.e(TAG, "语音识别错误: $error") Log.e(TAG, "语音识别错误: $error")
@ -488,7 +500,7 @@ object AgentService : CoroutineScope {
}, audioSourceType) }, audioSourceType)
return true return true
} catch (e: Exception) { } catch (e: Exception) {
isRecognitionActive = false _isRecognitionActive.set(false)
Log.e(TAG, "启动语音识别失败: ${e.message}") Log.e(TAG, "启动语音识别失败: ${e.message}")
sendEvent("error", mapOf( sendEvent("error", mapOf(
"code" to "RECOGNITION_START_ERROR", "code" to "RECOGNITION_START_ERROR",
@ -510,11 +522,11 @@ object AgentService : CoroutineScope {
try { try {
azureAsrHelper?.stopContinuousRecognition() azureAsrHelper?.stopContinuousRecognition()
BleService.closeCodec() BleService.closeCodec()
isRecognitionActive = false _isRecognitionActive.set(false)
stopIdleCheck() stopIdleCheck()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "停止语音识别异常: ${e.message}") Log.e(TAG, "停止语音识别异常: ${e.message}")
isRecognitionActive = false _isRecognitionActive.set(false)
stopIdleCheck() stopIdleCheck()
} }
} }
@ -549,7 +561,7 @@ object AgentService : CoroutineScope {
if (isAiStreaming) { if (isAiStreaming) {
try { try {
// 先更新状态,避免回调时的状态不一致 // 先更新状态,避免回调时的状态不一致
isAiStreaming = false _isAiStreaming.set(false)
// 取消当前AI生成任务 // 取消当前AI生成任务
currentAiJob?.cancel() currentAiJob?.cancel()
@ -561,7 +573,7 @@ object AgentService : CoroutineScope {
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "停止AI流输出异常", e) Log.e(TAG, "停止AI流输出异常", e)
// 确保状态被重置,即使发生异常 // 确保状态被重置,即使发生异常
isAiStreaming = false _isAiStreaming.set(false)
currentAiJob = null currentAiJob = null
} }
} }
@ -655,7 +667,7 @@ object AgentService : CoroutineScope {
currentAiJob = launch { currentAiJob = launch {
try { try {
// 设置状态为正在流式输出 // 设置状态为正在流式输出
isAiStreaming = true _isAiStreaming.set(true)
// 使用历史记录作为上下文发送到OpenAI // 使用历史记录作为上下文发送到OpenAI
val responseBuilder = StringBuilder() val responseBuilder = StringBuilder()
@ -753,7 +765,7 @@ object AgentService : CoroutineScope {
// 保存聊天记录 // 保存聊天记录
saveChatMessage(displayText, response,aiMetadata,userMetadata.toString()) saveChatMessage(displayText, response,aiMetadata,userMetadata.toString())
// 标记AI流式输出已完成 // 标记AI流式输出已完成
isAiStreaming = false _isAiStreaming.set(false)
currentAiJob = null currentAiJob = null
} }
@ -765,7 +777,7 @@ object AgentService : CoroutineScope {
)) ))
// 标记AI流式输出已完成 // 标记AI流式输出已完成
isAiStreaming = false _isAiStreaming.set(false)
currentAiJob = null currentAiJob = null
} }
@ -809,7 +821,7 @@ object AgentService : CoroutineScope {
)) ))
// 确保状态被重置 // 确保状态被重置
isAiStreaming = false _isAiStreaming.set(false)
currentAiJob = null currentAiJob = null
} }
} }
@ -873,7 +885,7 @@ object AgentService : CoroutineScope {
if (text.isEmpty()) return if (text.isEmpty()) return
// 更新状态 // 更新状态
isTtsSpeaking = true _isTtsSpeaking.set(true)
restartIdleCheck() // 状态变化,重启检测 restartIdleCheck() // 状态变化,重启检测
// 直接调用TTS,无需协程包装 // 直接调用TTS,无需协程包装
@ -887,7 +899,7 @@ object AgentService : CoroutineScope {
if (isTtsSpeaking) { if (isTtsSpeaking) {
ttsService?.stop() ttsService?.stop()
isTtsSpeaking = false _isTtsSpeaking.set(false)
restartIdleCheck() // 状态变化,重启检测 restartIdleCheck() // 状态变化,重启检测
sendEvent("tts_stopped", mapOf("status" to "stopped")) sendEvent("tts_stopped", mapOf("status" to "stopped"))
} }
@ -964,9 +976,8 @@ object AgentService : CoroutineScope {
* 发送事件 * 发送事件
*/ */
private fun sendEvent(eventName: String, data: Map<String, Any>) { private fun sendEvent(eventName: String, data: Map<String, Any>) {
// 使用协程确保在主线程上执行 // 切换到主线程执行监听器回调,避免 UI 更新问题
launch { launch(Dispatchers.Main) {
// 我们已在主线程上下文中启动协程,无需再切换线程
listeners.forEach { listener -> listeners.forEach { listener ->
try { try {
listener.onEvent(eventName, data) listener.onEvent(eventName, data)

40
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt

@ -24,6 +24,7 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
private const val SAMPLE_RATE = 24000 private const val SAMPLE_RATE = 24000
private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_MONO private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_MONO
private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
private const val VOLUME_REDUCTION = 0.7f // 降低音量以减少回音
} }
private var audioTrack: AudioTrack? = null private var audioTrack: AudioTrack? = null
@ -206,6 +207,7 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
} }
} }
/** /**
* 初始化 AudioTrack * 初始化 AudioTrack
*/ */
@ -213,13 +215,18 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
audioTrack?.release() audioTrack?.release()
val minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT) val minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT)
val bufferSize = minBufferSize * 2 val bufferSize = minBufferSize * 2 // 使用较小的缓冲区以降低延迟
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
AudioTrack.Builder() val builder = AudioTrack.Builder()
.setAudioAttributes( .setAudioAttributes(
AudioAttributes.Builder() AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA) .setUsage(
// 扬声器模式下默认使用语音通信模式以启用回声消除
if (audioOutputDevice == AudioOutputDevice.SPEAKER)
AudioAttributes.USAGE_VOICE_COMMUNICATION
else AudioAttributes.USAGE_MEDIA
)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build() .build()
) )
@ -232,7 +239,13 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
) )
.setBufferSizeInBytes(bufferSize) .setBufferSizeInBytes(bufferSize)
.setTransferMode(AudioTrack.MODE_STREAM) .setTransferMode(AudioTrack.MODE_STREAM)
.build()
// API 26+ 设置低延迟模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
}
builder.build()
} else { } else {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
AudioTrack( AudioTrack(
@ -247,6 +260,9 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
audioTrack?.setPlaybackPositionUpdateListener(playbackListener) audioTrack?.setPlaybackPositionUpdateListener(playbackListener)
// 设置音量以减少回音
audioTrack?.setVolume(VOLUME_REDUCTION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setPreferredDeviceForTrack() setPreferredDeviceForTrack()
} else { } else {
@ -268,13 +284,17 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
AudioOutputDevice.DEFAULT -> { AudioOutputDevice.DEFAULT -> {
track.setPreferredDevice(null) track.setPreferredDevice(null)
manager.isSpeakerphoneOn = false manager.isSpeakerphoneOn = false
manager.mode = AudioManager.MODE_NORMAL // 默认模式下使用通信模式以获得更好的音频处理
manager.mode = AudioManager.MODE_IN_COMMUNICATION
} }
AudioOutputDevice.SPEAKER -> { AudioOutputDevice.SPEAKER -> {
val speaker = manager.getDevices(AudioManager.GET_DEVICES_OUTPUTS) val speaker = manager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER } .firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER }
speaker?.let { track.setPreferredDevice(it) } speaker?.let { track.setPreferredDevice(it) }
// 扬声器模式下始终启用通信模式以获得回声消除
manager.mode = AudioManager.MODE_IN_COMMUNICATION
manager.isSpeakerphoneOn = true
} }
AudioOutputDevice.HEADPHONES -> { AudioOutputDevice.HEADPHONES -> {
@ -287,6 +307,9 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
} }
headphones?.let { track.setPreferredDevice(it) } headphones?.let { track.setPreferredDevice(it) }
?: track.setPreferredDevice(null) ?: track.setPreferredDevice(null)
// 耳机模式下可以使用普通模式
manager.mode = AudioManager.MODE_NORMAL
manager.isSpeakerphoneOn = false
} }
} }
} }
@ -300,16 +323,19 @@ class BytedanceAudioPlayer(private val context: Context) : AudioDataListener {
audioManager?.let { manager -> audioManager?.let { manager ->
when (audioOutputDevice) { when (audioOutputDevice) {
AudioOutputDevice.DEFAULT -> { AudioOutputDevice.DEFAULT -> {
manager.mode = AudioManager.MODE_NORMAL // 默认模式下使用通信模式以获得更好的音频处理
manager.mode = AudioManager.MODE_IN_COMMUNICATION
manager.isSpeakerphoneOn = false manager.isSpeakerphoneOn = false
} }
AudioOutputDevice.SPEAKER -> { AudioOutputDevice.SPEAKER -> {
manager.mode = AudioManager.MODE_NORMAL // 扬声器模式下始终启用通信模式以获得回声消除
manager.mode = AudioManager.MODE_IN_COMMUNICATION
manager.isSpeakerphoneOn = true manager.isSpeakerphoneOn = true
} }
AudioOutputDevice.HEADPHONES -> { AudioOutputDevice.HEADPHONES -> {
// 耳机模式下可以使用普通模式
manager.mode = AudioManager.MODE_NORMAL manager.mode = AudioManager.MODE_NORMAL
manager.isSpeakerphoneOn = false manager.isSpeakerphoneOn = false
} }

47
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt

@ -111,6 +111,10 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
override val coroutineContext: CoroutineContext = Dispatchers.IO + SupervisorJob() override val coroutineContext: CoroutineContext = Dispatchers.IO + SupervisorJob()
companion object {
private const val TAG = "ChatApiService"
}
// MARK: - 属性 // MARK: - 属性
private var baseUrl = "https://api.openai.com/v1/" private var baseUrl = "https://api.openai.com/v1/"
private var apiKey = "" private var apiKey = ""
@ -218,8 +222,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
mcpConfigJson = mcpServer mcpConfigJson = mcpServer
// 异步初始化MCP客户端 // 异步初始化MCP客户端
launch { launch {
// initializeMcpClient(mcpServer) initializeMcpClient(mcpServer)
initializeMcpClient("{}") // initializeMcpClient("{}")
} }
isInitialized = apiKey.isNotEmpty() isInitialized = apiKey.isNotEmpty()
@ -514,8 +518,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
) )
// 通知上层工具调用事件 // 通知上层工具调用事件
currSessionCallback?.onFunctionCall(convertMapToJsonObject(functionCall)) currSessionCallback?.onFunctionCall(convertMapToJsonObject(functionCall))
// 在后台队列处理工具调用 // 在当前协程作用域内处理工具调用,使用async确保生命周期管理
launch { val toolCallDeferred = async {
try { try {
if (sessionid == currSessionId) { if (sessionid == currSessionId) {
// 通过MCP客户端处理工具调用 // 通过MCP客户端处理工具调用
@ -587,6 +591,15 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} }
} }
// 等待工具调用完成,确保生命周期管理
try {
toolCallDeferred.await()
} catch (e: CancellationException) {
// 协程被取消,确保子任务也被取消
toolCallDeferred.cancel()
throw e
}
return true return true
} }
@ -780,7 +793,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
description = description, description = description,
parameters = parameters parameters = parameters
) )
Log.d("ChatApiService", "AI携带工具: $name, 参数定义: $parameters") // Log.d("ChatApiService", "AI携带工具: $name, 参数定义: $parameters")
tools.add(tool) tools.add(tool)
} catch (e: Exception) { } catch (e: Exception) {
@ -813,6 +826,30 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} }
} }
/**
* 释放所有资源
* 统一的资源管理方法,确保所有协程被正确取消
*/
fun dispose() {
runBlocking {
// 取消当前流式任务
currentStreamJob?.cancelAndJoin()
currentStreamJob = null
// 关闭MCP客户端
closeMcpClient()
// 取消所有子协程
coroutineContext[Job]?.cancelChildren()
// 清理其他资源
currSessionCallback = null
currSessionId = ""
currentMessages = emptyList()
toolCalls.clear()
}
}
/** /**
* 处理MCP工具调用 * 处理MCP工具调用
* *

4
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt

@ -129,7 +129,7 @@ class MCPSubClient(
"properties" to emptyMap<String, Any>(), "properties" to emptyMap<String, Any>(),
"required" to emptyList<String>() "required" to emptyList<String>()
) )
Log.w(TAG, "解析工具数据: ${tool.name} ${parametersMap}") // Log.w(TAG, "解析工具数据: ${tool.name} ${parametersMap}")
val toolMap = mapOf( val toolMap = mapOf(
"type" to "function", "type" to "function",
"function" to mapOf( "function" to mapOf(
@ -175,7 +175,7 @@ class MCPSubClient(
name = name, name = name,
arguments = argumentsJson arguments = argumentsJson
) )
Log.d(TAG, "[$serverId] 调用工具 '$name',参数: ${argumentsJson.toString()}")
// 调用工具 // 调用工具
val result = mcpClient?.callTool(request) val result = mcpClient?.callTool(request)

Loading…
Cancel
Save