Browse Source

上传测试

weicu
liwei1dao 1 year ago
parent
commit
d7d5c7a55d
  1. 215
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  2. 120
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt
  3. 44
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt
  4. 73
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt

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

@ -1,9 +1,11 @@
package com.yunqiinnovation.agent_service package com.yunqiinnovation.agent_service
import android.content.Context import android.content.Context
import android.content.Intent
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import com.yunqiinnovation.azure_speech.AzureAsrHelper import com.yunqiinnovation.azure_speech.AzureAsrHelper
import com.yunqiinnovation.azure_speech.AzureTtsHelper
import com.yunqiinnovation.chat_api.ChatApiService import com.yunqiinnovation.chat_api.ChatApiService
import com.yunqiinnovation.chat_api.StreamCallback import com.yunqiinnovation.chat_api.StreamCallback
import com.yunqiinnovation.chat_storage.ChatStorageHelper import com.yunqiinnovation.chat_storage.ChatStorageHelper
@ -23,6 +25,8 @@ 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
import com.deep_voice.bytedance_speech.BytedanceTTS import com.deep_voice.bytedance_speech.BytedanceTTS
import java.util.UUID
/** /**
* 代理服务事件监听接口 * 代理服务事件监听接口
*/ */
@ -56,7 +60,7 @@ object AgentService : CoroutineScope {
private var azureAsrHelper: AzureAsrHelper? = null private var azureAsrHelper: AzureAsrHelper? = null
// 使用BytedanceTTS作为唯一的TTS实现 // 使用BytedanceTTS作为唯一的TTS实现
private var ttsService: BytedanceTTS? = null private var ttsService: AzureTtsHelper? = null
// ChatAPI服务 - 使用新的ChatApiService // ChatAPI服务 - 使用新的ChatApiService
private lateinit var chatApiService: ChatApiService private lateinit var chatApiService: ChatApiService
@ -97,7 +101,7 @@ object AgentService : CoroutineScope {
// AI流生成相关 // AI流生成相关
private var currentAiJob: Job? = null private var currentAiJob: Job? = null
private var currsessionId:String = ""
// 空闲检测相关 // 空闲检测相关
private var idleCheckJob: Job? = null private var idleCheckJob: Job? = null
private val maxIdleSeconds = 10 // 最大空闲秒数 private val maxIdleSeconds = 10 // 最大空闲秒数
@ -244,14 +248,14 @@ object AgentService : CoroutineScope {
val ttsLanguage = config["ttsLanguage"]?.toString() ?: "zh-CN" val ttsLanguage = config["ttsLanguage"]?.toString() ?: "zh-CN"
// 创建并初始化BytedanceTTS // 创建并初始化BytedanceTTS
val bytedanceTts = BytedanceTTS(context) val azureTtsHelper = AzureTtsHelper(context)
ttsService = bytedanceTts ttsService = azureTtsHelper
// 初始化Bytedance TTS // 初始化Bytedance TTS
val success = bytedanceTts.initialize( val success = azureTtsHelper.initialize(
ttsAppId = ttsAppId, ttsAppId = "", // Azure TTS不需要appId
ttsAppToken = ttsAppToken, ttsAppToken = config["azureSpeechKey"]?.toString() ?: "", // Azure需要subscription key
ttsResource = "", // Bytedance TTS不需要resource参数 ttsResource = config["azureSpeechRegion"]?.toString() ?: "", // Azure需要region信息
language = ttsLanguage language = ttsLanguage
) )
@ -278,13 +282,23 @@ object AgentService : CoroutineScope {
restartIdleCheck() restartIdleCheck()
sendEvent("tts_canceled", mapOf("status" to "canceled")) sendEvent("tts_canceled", mapOf("status" to "canceled"))
} }
TtsEventType.PLAYBACK_STARTED -> { TtsEventType.PLAYBACK_STARTED -> { //开始AI播报内容
restartIdleCheck() restartIdleCheck()
sendEvent("playback_started", mapOf("status" to "playback_started")) sendEvent("playback_started", mapOf("status" to "playback_started"))
// 发送AI说话开始广播
val intent = Intent("com.yunqiinnovation.music_service.AI_SAY_START").apply {
setPackage(context.packageName)
}
context.sendBroadcast(intent)
} }
TtsEventType.PLAYBACK_COMPLETED -> { TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容
restartIdleCheck() restartIdleCheck()
sendEvent("playback_completed", mapOf("status" to "playback_completed")) sendEvent("playback_completed", mapOf("status" to "playback_completed"))
// 发送AI说话开始广播
val intent = Intent("com.yunqiinnovation.music_service.AI_SAY_END").apply {
setPackage(context.packageName)
}
context.sendBroadcast(intent)
} }
TtsEventType.ERROR -> { TtsEventType.ERROR -> {
_isTtsSpeaking.set(false) _isTtsSpeaking.set(false)
@ -666,6 +680,7 @@ object AgentService : CoroutineScope {
// 记录新的AI任务 // 记录新的AI任务
currentAiJob = launch { currentAiJob = launch {
try { try {
currsessionId = UUID.randomUUID().toString()
// 设置状态为正在流式输出 // 设置状态为正在流式输出
_isAiStreaming.set(true) _isAiStreaming.set(true)
audioPlayer?.playAudio(R.raw.await, true,0.3f) audioPlayer?.playAudio(R.raw.await, true,0.3f)
@ -692,8 +707,8 @@ object AgentService : CoroutineScope {
) )
val _systemPrompt = Utils.fillTemplate(systemPrompt,location) val _systemPrompt = Utils.fillTemplate(systemPrompt,location)
messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt)) messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt))
// Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}") // Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}")
} }
// 再添加历史消息 // 再添加历史消息
for (i in 0 until historyMessages.length()) { for (i in 0 until historyMessages.length()) {
@ -730,61 +745,84 @@ object AgentService : CoroutineScope {
} }
// Log.e("ChatApiService", "liwei---------sendMessageStream messages:${messagesWithSystemPrompt}") // Log.e("ChatApiService", "liwei---------sendMessageStream messages:${messagesWithSystemPrompt}")
chatApiService.sendMessageStream( chatApiService.sendMessageStream(
currsessionId,
messagesWithSystemPrompt, messagesWithSystemPrompt,
object : StreamCallback { object : StreamCallback {
override fun onToken(token: String) { override fun onToken(sessionid:String,token: String) {
responseBuilder.append(token) try {
if (speakResponse && broadcast) { if (sessionid != currsessionId){
ttsService?.speakStream(token) return
if (token.length > 0){
audioPlayer?.stopAudio()
} }
} responseBuilder.append(token)
if (broadcast){ if (speakResponse && broadcast) {
// 发送流式回复token ttsService?.speakStream(token)
sendEvent("assistant_token", mapOf("token" to token)) if (token.length > 0) {
audioPlayer?.stopAudio()
}
}
if (broadcast) {
// 发送流式回复token
sendEvent("assistant_token", mapOf("token" to token))
}
} catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onToken 异常", e)
} }
} }
override fun onComplete() { override fun onComplete(sessionid:String) {
// 视情况决定是否朗读回复 try {
if (speakResponse && broadcast) { // 视情况决定是否朗读回复
ttsService?.flushStream() if (speakResponse && broadcast && sessionid == currsessionId){
} ttsService?.flushStream()
val response = responseBuilder.toString() }
// 发送完整回复,包含是否有图片的标记 val response = responseBuilder.toString()
val responseData = mutableMapOf<String, Any>( // 发送完整回复,包含是否有图片的标记
"text" to response, val responseData = mutableMapOf<String, Any>(
"userInput" to displayText "text" to response,
) "userInput" to displayText
if (hasImage) { )
responseData["hasImage"] = true if (hasImage) {
} responseData["hasImage"] = true
sendEvent("assistant_response", responseData) }
if (response.isNotEmpty()) { sendEvent("assistant_response", responseData)
// 添加AI回复到历史记录 if (response.isNotEmpty()) {
addToHistoryMessages(createAssistantMessage(response)) // 添加AI回复到历史记录
addToHistoryMessages(createAssistantMessage(response))
}
// 保存聊天记录
saveChatMessage(
displayText,
response,
aiMetadata,
userMetadata.toString()
)
// 标记AI流式输出已完成
_isAiStreaming.set(false)
currentAiJob = null
} catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onComplete 异常", e)
} }
// 保存聊天记录
saveChatMessage(displayText, response,aiMetadata,userMetadata.toString())
// 标记AI流式输出已完成
_isAiStreaming.set(false)
currentAiJob = null
} }
override fun onError(e: Exception) { override fun onError(sessionid:String,e: Exception) {
Log.e(TAG, "AI处理出错", e) try {
sendEvent("error", mapOf( Log.e(TAG, "AI处理出错", e)
"code" to "AI_ERROR", sendEvent(
"message" to e.message.toString() "error", mapOf(
)) "code" to "AI_ERROR",
"message" to e.message.toString()
// 标记AI流式输出已完成 )
_isAiStreaming.set(false) )
currentAiJob = null
// 标记AI流式输出已完成
_isAiStreaming.set(false)
currentAiJob = null
}catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onError 异常", e)
}
} }
override fun onFunctionCall(call: JSONObject) { override fun onFunctionCall(sessionid:String,call: JSONObject) {
try { try {
// audioPlayer?.playAudio(R.raw.calling, true) // audioPlayer?.playAudio(R.raw.calling, true)
val name = call.getString("name") val name = call.getString("name")
@ -796,28 +834,34 @@ object AgentService : CoroutineScope {
stopRecognition() stopRecognition()
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "处理函数调用事件失败", e) Log.e(TAG, "liwei--------------- AI Call onFunctionCall 异常", e)
} }
} }
override fun onFunctionCallResult(functionCall: JSONObject, functionCallResult: JSONObject) { override fun onFunctionCallResult(sessionid:String,functionCall: JSONObject, functionCallResult: JSONObject) {
audioPlayer?.stopAudio() try {
val name = functionCall.get("name") as String; audioPlayer?.stopAudio()
val resultmap = autoHandleFcunCallResult(name,functionCallResult); val name = functionCall.get("name") as String;
aiMetadata = resultmap["meta"] as String; val resultmap = autoHandleFcunCallResult(name, functionCallResult);
broadcast = resultmap["broadcast"] as Boolean; aiMetadata = resultmap["meta"] as String;
sendEvent("function_call_result", mapOf( broadcast = resultmap["broadcast"] as Boolean;
"function_call" to functionCall.toString(), sendEvent(
"result" to functionCallResult.toString(), "function_call_result", mapOf(
"meta" to aiMetadata, "function_call" to functionCall.toString(),
)) "result" to functionCallResult.toString(),
"meta" to aiMetadata,
)
)
} catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onFunctionCallResult 异常", e)
}
} }
} }
) )
} catch (e: Exception) { } catch (e: Exception) {
val errorType = if (hasImage) "AI_IMAGE_PROCESS_ERROR" else "AI_PROCESS_ERROR" val errorType = if (hasImage) "AI_IMAGE_PROCESS_ERROR" else "AI_PROCESS_ERROR"
Log.e(TAG, "AI处理出错", e) Log.e(TAG, "liwei--------------- AI处理出错", e)
sendEvent("error", mapOf( sendEvent("error", mapOf(
"code" to errorType, "code" to errorType,
"message" to e.message.toString() "message" to e.message.toString()
@ -1119,6 +1163,7 @@ object AgentService : CoroutineScope {
*/ */
class AudioPlayer(private val context: Context) { class AudioPlayer(private val context: Context) {
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
private var isInitialized = false
/** /**
* 播放音频资源 * 播放音频资源
@ -1155,21 +1200,47 @@ object AgentService : CoroutineScope {
release() release()
} }
} }
fun stopAudio() { fun stopAudio() {
mediaPlayer?.stop() try {
mediaPlayer?.apply {
when {
isPlaying -> {
stop()
Log.d(TAG, "音频已停止")
}
else -> {
Log.d(TAG, "音频未在播放状态,无需停止")
}
}
}
} catch (e: IllegalStateException) {
Log.e(TAG, "MediaPlayer 状态异常,无法停止: ${e.message}", e)
// 重置 MediaPlayer
release()
} catch (e: Exception) {
Log.e(TAG, "停止音频播放异常: ${e.message}", e)
}
} }
/** /**
* 释放资源 * 释放资源
*/ */
fun release() { fun release() {
try { try {
mediaPlayer?.apply { mediaPlayer?.apply {
if (isPlaying) stop() if (isPlaying) {
stop()
}
reset()
release() release()
} }
mediaPlayer = null mediaPlayer = null
isInitialized = false
} catch (e: Exception) { } catch (e: Exception) {
// 忽略异常 Log.e(TAG, "释放音频资源异常: ${e.message}", e)
mediaPlayer = null
isInitialized = false
} }
} }
} }

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

@ -39,27 +39,27 @@ interface StreamCallback {
/** /**
* 接收到新的token * 接收到新的token
*/ */
fun onToken(token: String) fun onToken(sessionid: String,token: String)
/** /**
* 流式响应完成 * 流式响应完成
*/ */
fun onComplete() fun onComplete(sessionid: String)
/** /**
* 出现错误 * 出现错误
*/ */
fun onError(error: Exception) fun onError(sessionid: String,error: Exception)
/** /**
* 函数调用 - 兼容JSONObject格式 * 函数调用 - 兼容JSONObject格式
*/ */
fun onFunctionCall(functionCall: org.json.JSONObject) fun onFunctionCall(sessionid: String,functionCall: org.json.JSONObject)
/** /**
* 函数调用结果 - 兼容JSONObject格式 * 函数调用结果 - 兼容JSONObject格式
*/ */
fun onFunctionCallResult(functionCall: org.json.JSONObject, functionCallResult: org.json.JSONObject) fun onFunctionCallResult(sessionid: String,functionCall: org.json.JSONObject, functionCallResult: org.json.JSONObject)
} }
/** /**
@ -142,7 +142,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
private var currentMessages: List<Map<String, Any>> = emptyList() private var currentMessages: List<Map<String, Any>> = emptyList()
private var toolCalls: MutableMap<Int, ToolCallInfo> = Collections.synchronizedMap(mutableMapOf()) private var toolCalls: MutableMap<Int, ToolCallInfo> = Collections.synchronizedMap(mutableMapOf())
private var currSessionId = "" private var currSessionId = ""
private var currSessionCallback: StreamCallback? = null // 为每个会话维护独立的回调函数
private val sessionCallbacks: MutableMap<String, StreamCallback> = Collections.synchronizedMap(mutableMapOf())
@ -156,17 +157,18 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
*/ */
private fun abortCurrentSession() { private fun abortCurrentSession() {
if (currSessionId.isNotEmpty()) { if (currSessionId.isNotEmpty()) {
val oldSessionId = currSessionId
// 1. 取消当前的协程任务 // 1. 取消当前的协程任务
currentStreamJob?.cancel() currentStreamJob?.cancel()
currentStreamJob = null currentStreamJob = null
// 2. 通知旧会话被中止 // 2. 通知旧会话被中止
// currSessionCallback?.onError(ChatApiException("Session aborted by new request")) getSessionCallback(oldSessionId)?.onComplete(currSessionId) // 直接完成当前会话
currSessionCallback?.onComplete() // 直接完成当前会话
// 3. 清理状态 // 3. 清理状态
currSessionId = "" currSessionId = ""
currSessionCallback = null sessionCallbacks.remove(oldSessionId)
toolCalls.clear() toolCalls.clear()
} }
} }
@ -175,12 +177,20 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
* 清理当前会话 * 清理当前会话
*/ */
private fun clearCurrentSession() { private fun clearCurrentSession() {
val oldSessionId = currSessionId
currSessionId = "" currSessionId = ""
currSessionCallback = null sessionCallbacks.remove(oldSessionId)
currentStreamJob = null currentStreamJob = null
toolCalls.clear() toolCalls.clear()
} }
/**
* 获取指定会话的回调函数
*/
private fun getSessionCallback(sessionId: String): StreamCallback? {
return if (sessionId == currSessionId) sessionCallbacks[sessionId] else null
}
/** /**
* 初始化ChatAPI服务 * 初始化ChatAPI服务
*/ */
@ -312,17 +322,18 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
/** /**
* 发送消息(流式输出)- 兼容JSONArray格式 * 发送消息(流式输出)- 兼容JSONArray格式
*/ */
fun sendMessageStream(messages: org.json.JSONArray, callback: StreamCallback) { fun sendMessageStream(sessionid: String,messages: org.json.JSONArray, callback: StreamCallback) {
// 1. 立即中止旧会话 // 1. 立即中止旧会话
abortCurrentSession() abortCurrentSession()
// 2. 创建新会话 // 2. 创建新会话
currSessionId = UUID.randomUUID().toString() // val newSessionId = UUID.randomUUID().toString()
currSessionCallback = callback currSessionId = sessionid
sessionCallbacks[sessionid] = callback
// 3. 转换消息并开始处理 // 3. 转换消息并开始处理
val messageList = convertJsonArrayToList(messages) val messageList = convertJsonArrayToList(messages)
sendMessageStream(currSessionId, messageList) sendMessageStream(sessionid, messageList)
} }
/** /**
@ -336,7 +347,11 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
if (!isInitialized || apiKey.isEmpty() || openAI == null) { if (!isInitialized || apiKey.isEmpty() || openAI == null) {
Log.e("ChatApiService", "ChatAPI服务未初始化,无法发送消息") Log.e("ChatApiService", "ChatAPI服务未初始化,无法发送消息")
currSessionCallback?.onError(ChatApiException("ChatAPI服务未初始化")) try {
getSessionCallback(sessionid)?.onError(sessionid,ChatApiException("ChatAPI服务未初始化"))
} catch (e: Exception) {
Log.e(TAG, "onError回调异常: ${e.message}", e)
}
return return
} }
@ -345,7 +360,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
toolCalls.clear() toolCalls.clear()
// 检查是否包含图片,决定使用哪个模型 // 检查是否包含图片,决定使用哪个模型
val currentModel = if (containsImage(messages)) visionModel else model val currentModel = if (containsImage(messages)) visionModel else model
// Log.d(TAG, "liwei-------------------------开始AI 对话 0")
currentStreamJob = launch { currentStreamJob = launch {
try { try {
// 转换消息格式 // 转换消息格式
@ -355,22 +370,22 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
Log.e("ChatApiService", "转换消息格式失败: ${e.message}", e) Log.e("ChatApiService", "转换消息格式失败: ${e.message}", e)
throw e throw e
} }
// Log.d(TAG, "liwei-------------------------开始AI 对话 1")
// 直接获取工具列表 // 直接获取工具列表
val tools = getOpenAiTools() val tools = getOpenAiTools()
// Log.d(TAG, "liwei-------------------------开始AI 对话 2")
// 构建请求 // 构建请求
if (currentModel.isEmpty()) { if (currentModel.isEmpty()) {
Log.e("ChatApiService", "模型名称为空") Log.e("ChatApiService", "模型名称为空")
throw IllegalArgumentException("模型名称不能为空") throw IllegalArgumentException("模型名称不能为空")
} }
// Log.d(TAG, "liwei-------------------------开始AI 对话 3")
if (chatMessages.isEmpty()) { if (chatMessages.isEmpty()) {
Log.e("ChatApiService", "消息列表为空") Log.e("ChatApiService", "消息列表为空")
throw IllegalArgumentException("消息列表不能为空") throw IllegalArgumentException("消息列表不能为空")
} }
// Log.e("ChatApiService", "AI解析工具列表: ${tools}") // Log.e("ChatApiService", "AI解析工具列表: ${tools}")
// Log.d(TAG, "liwei-------------------------开始AI 对话 4")
val chatsFlow = try { val chatsFlow = try {
val chatCompletionRequest = ChatCompletionRequest( val chatCompletionRequest = ChatCompletionRequest(
model = ModelId(currentModel), model = ModelId(currentModel),
@ -379,42 +394,49 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
temperature = 0.7, temperature = 0.7,
tools = if (tools.isNotEmpty()) tools else null tools = if (tools.isNotEmpty()) tools else null
) )
// Log.d(TAG, "liwei-------------------------开始AI 对话 5")
if (openAI == null) { if (openAI == null) {
Log.e("ChatApiService", "openAI对象为null") Log.e("ChatApiService", "openAI对象为null")
throw IllegalStateException("OpenAI客户端未初始化") throw IllegalStateException("OpenAI客户端未初始化")
} }
// Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}") // Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}")
val flow = openAI!!.chatCompletions(chatCompletionRequest) val flow = openAI!!.chatCompletions(chatCompletionRequest)
// Log.d(TAG, "liwei-------------------------开始AI 对话 6")
flow flow
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ChatApiService", "创建ChatCompletionRequest或调用chatCompletions失败: ${e.message}", e) Log.e("ChatApiService", "创建ChatCompletionRequest或调用chatCompletions失败: ${e.message}", e)
throw e throw e
} }
// Log.d(TAG, "liwei-------------------------开始AI 对话 7")
chatsFlow.collect { result -> chatsFlow.collect { result ->
// 每次处理前检查是否仍是当前会话 // 每次处理前检查是否仍是当前会话
if (sessionid != currSessionId) { if (sessionid != currSessionId) {
throw CancellationException("Session cancelled") // throw CancellationException("Session cancelled")
return@collect
} }
// Log.d(TAG, "liwei-------------------------开始AI 对话 7-1")
val choice = result.choices.firstOrNull() val choice = result.choices.firstOrNull()
if (choice == null) { if (choice == null) {
Log.w("ChatApiService", "[Session: $sessionid] choice为null") Log.w("ChatApiService", "[Session: $sessionid] choice为null")
return@collect return@collect
} }
// Log.d(TAG, "liwei-------------------------开始AI 对话 7-2")
val delta = choice.delta val delta = choice.delta
if (delta == null) { if (delta == null) {
Log.w("ChatApiService", "[Session: $sessionid] delta为null") Log.w("ChatApiService", "[Session: $sessionid] delta为null")
return@collect return@collect
} }
// Log.d(TAG, "liwei-------------------------开始AI 对话 7-3")
// 处理普通文本内容 // 处理普通文本内容
delta.content?.let { content -> delta.content?.let { content ->
currSessionCallback?.onToken(content) try {
getSessionCallback(sessionid)?.onToken(sessionid,content)
} catch (e: Exception) {
Log.e(TAG, "onToken回调异常: ${e.message}", e)
// 不重新抛出异常,继续处理后续内容
}
} }
// Log.d(TAG, "liwei-------------------------开始AI 对话 7-4")
// 收集工具调用信息 // 收集工具调用信息
delta.toolCalls?.forEach { toolCall -> delta.toolCalls?.forEach { toolCall ->
try { try {
@ -461,23 +483,32 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} }
} }
} }
// Log.d(TAG, "liwei-------------------------开始AI 对话 8")
// 完成处理 // 完成处理
if (sessionid == currSessionId) { if (sessionid == currSessionId) {
// Log.d(TAG, "liwei-------------------------开始AI 对话 9")
// 检查是否有工具调用需要处理 // 检查是否有工具调用需要处理
val hasToolCalls = processToolCalls(sessionid) val hasToolCalls = processToolCalls(sessionid)
if (!hasToolCalls) { if (!hasToolCalls) {
currSessionCallback?.onComplete() try {
getSessionCallback(sessionid)?.onComplete(sessionid)
} catch (e: Exception) {
Log.e(TAG, "onComplete回调异常: ${e.message}", e)
}
clearCurrentSession() clearCurrentSession()
} }
} }
// Log.d(TAG, "liwei-------------------------开始AI 对话 10")
} catch (e: CancellationException) { } catch (e: CancellationException) {
// 会话被取消,不需要处理 // 会话被取消,不需要处理
Log.d("ChatApiService", "Session $sessionid cancelled") Log.d(TAG, "Session $sessionid cancelled")
} catch (e: Exception) { } catch (e: Exception) {
if (sessionid == currSessionId) { if (sessionid == currSessionId) {
currSessionCallback?.onError(ChatApiException("流式请求失败: ${e.message}")) try {
getSessionCallback(sessionid)?.onError(sessionid,ChatApiException("流式请求失败: ${e.message}"))
} catch (ex: Exception) {
Log.e(TAG, "onError回调异常: ${ex.message}", ex)
}
clearCurrentSession() clearCurrentSession()
} }
} }
@ -517,7 +548,11 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
"id" to firstToolCall.id "id" to firstToolCall.id
) )
// 通知上层工具调用事件 // 通知上层工具调用事件
currSessionCallback?.onFunctionCall(convertMapToJsonObject(functionCall)) try {
getSessionCallback(sessionid)?.onFunctionCall(sessionid,convertMapToJsonObject(functionCall))
} catch (e: Exception) {
Log.e(TAG, "onFunctionCall回调异常: ${e.message}", e)
}
// 在当前协程作用域内处理工具调用,使用async确保生命周期管理 // 在当前协程作用域内处理工具调用,使用async确保生命周期管理
val toolCallDeferred = async { val toolCallDeferred = async {
try { try {
@ -564,10 +599,15 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} }
if (sessionid == currSessionId) { if (sessionid == currSessionId) {
// 处理结果 // 处理结果
currSessionCallback?.onFunctionCallResult( try {
convertMapToJsonObject(functionCall), getSessionCallback(sessionid)?.onFunctionCallResult(
convertMapToJsonObject(result) sessionid,
) convertMapToJsonObject(functionCall),
convertMapToJsonObject(result)
)
} catch (e: Exception) {
Log.e(TAG, "onFunctionCallResult回调异常: ${e.message}", e)
}
// 将结果发送回OpenAI继续对话 // 将结果发送回OpenAI继续对话
sendFunctionCallResultInternal( sendFunctionCallResultInternal(
sessionid= sessionid, sessionid= sessionid,
@ -843,7 +883,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
coroutineContext[Job]?.cancelChildren() coroutineContext[Job]?.cancelChildren()
// 清理其他资源 // 清理其他资源
currSessionCallback = null sessionCallbacks.clear()
currSessionId = "" currSessionId = ""
currentMessages = emptyList() currentMessages = emptyList()
toolCalls.clear() toolCalls.clear()
@ -1305,4 +1345,4 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
return "Function '$functionName' called with arguments: $arguments" return "Function '$functionName' called with arguments: $arguments"
} }
} }
} }

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

@ -50,6 +50,7 @@ class MCPSubClient(
* 连接到MCP服务器 * 连接到MCP服务器
*/ */
suspend fun connect(): Boolean = connectionMutex.withLock { suspend fun connect(): Boolean = connectionMutex.withLock {
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 1")
if (isConnected) return true if (isConnected) return true
Log.e(TAG, "[$serverId] 开始连接mcp服务器: $serverUrl") Log.e(TAG, "[$serverId] 开始连接mcp服务器: $serverUrl")
return try { return try {
@ -60,7 +61,7 @@ class MCPSubClient(
version = "1.0.0" version = "1.0.0"
) )
) )
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 2")
// 根据URL类型选择传输方式 // 根据URL类型选择传输方式
val newTransport = when { val newTransport = when {
serverUrl.startsWith("http://") || serverUrl.startsWith("https://") -> { serverUrl.startsWith("http://") || serverUrl.startsWith("https://") -> {
@ -83,15 +84,26 @@ class MCPSubClient(
return false return false
} }
} }
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 3")
transport = newTransport transport = newTransport
try {
// 连接到服务器 // 连接到服务器
client.connect(newTransport) withTimeout(10000) { // 10秒超时
client.connect(newTransport)
}
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 4")
} catch (e: TimeoutCancellationException) {
Log.e(TAG, "liwei---------Mcp [$serverId] 连接超时")
return false
} catch (e: Exception) {
Log.e(TAG, "liwei---------Mcp [$serverId] 连接异常: ${e.message}", e)
return false
}
// 获取可用工具列表 // 获取可用工具列表
try { try {
val toolsResult = client.listTools() val toolsResult = client.listTools()
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 5")
if (toolsResult != null) { if (toolsResult != null) {
availableTools.clear() availableTools.clear()
val filtered = toolsResult.tools.filter { tool -> val filtered = toolsResult.tools.filter { tool ->
@ -99,17 +111,19 @@ class MCPSubClient(
} }
Log.w(TAG, "[$serverId] [${filterTools}] 获取工具列表: ${filtered} 原始列表:${toolsResult.tools}") Log.w(TAG, "[$serverId] [${filterTools}] 获取工具列表: ${filtered} 原始列表:${toolsResult.tools}")
availableTools.addAll(filtered) availableTools.addAll(filtered)
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 6")
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "[$serverId] 获取工具列表失败: ${e.message}") Log.w(TAG, "[$serverId] 获取工具列表失败: ${e.message}")
// 即使获取工具失败,连接也可能是成功的 // 即使获取工具失败,连接也可能是成功的
} }
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 7")
mcpClient = client mcpClient = client
isConnected = true isConnected = true
retryCount = 0 retryCount = 0
currentReconnectDelay = initialReconnectDelay currentReconnectDelay = initialReconnectDelay
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 8")
// 启动心跳检测 // 启动心跳检测
// startHeartbeat() // startHeartbeat()
Log.e(TAG, "[$serverId] 连接mcp服务器成功: $serverUrl") Log.e(TAG, "[$serverId] 连接mcp服务器成功: $serverUrl")
@ -162,11 +176,23 @@ class MCPSubClient(
// 先检查连接状态 // 先检查连接状态
if (!checkConnection()) { if (!checkConnection()) {
Log.e(TAG, "无法连接到服务器,工具调用失败") Log.e(TAG, "无法连接到服务器,工具调用失败")
return null return mapOf(
"content" to listOf(mapOf(
"type" to "text",
"text" to "Tool call failed: mcp: service not connected"
)),
"isError" to true
)
} }
if (!containsTool(name)) { if (!containsTool(name)) {
Log.w(TAG, "此客户端不包含工具: $name") Log.w(TAG, "此客户端不包含工具: $name")
return null return mapOf(
"content" to listOf(mapOf(
"type" to "text",
"text" to "Tool call failed: no found tool"
)),
"isError" to true
)
} }
return try { return try {
// 创建工具调用请求 - 将Map转换为JsonObject // 创建工具调用请求 - 将Map转换为JsonObject

73
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt

@ -39,6 +39,9 @@ class MusicService : MediaBrowserServiceCompat() {
private var currentStatus = false private var currentStatus = false
private var playlist = mutableListOf<Map<String,Any>>() private var playlist = mutableListOf<Map<String,Any>>()
private var currentIndex = 0 private var currentIndex = 0
private var currentVolume: Float = 1.0f // 当前音量
private var originalVolume: Float = 1.0f // 原始音量,用于恢复
private var isVolumeReduced: Boolean = false // 是否已降低音量
fun isServiceRunning() = isRunning.get() fun isServiceRunning() = isRunning.get()
fun isPlaying() = currentStatus fun isPlaying() = currentStatus
@ -179,6 +182,8 @@ class MusicService : MediaBrowserServiceCompat() {
addAction("com.yunqiinnovation.music_service.MEDIA_CLOSE") addAction("com.yunqiinnovation.music_service.MEDIA_CLOSE")
addAction("com.yunqiinnovation.music_service.MEDIA_PREVIOUS") addAction("com.yunqiinnovation.music_service.MEDIA_PREVIOUS")
addAction("com.yunqiinnovation.music_service.MEDIA_NEXT") addAction("com.yunqiinnovation.music_service.MEDIA_NEXT")
addAction("com.yunqiinnovation.music_service.AI_SAY_START")
addAction("com.yunqiinnovation.music_service.AI_SAY_END")
} }
// Android 14+ 需要指定 flag // Android 14+ 需要指定 flag
@ -205,7 +210,7 @@ class MusicService : MediaBrowserServiceCompat() {
val songMap = intent.getSerializableExtra("song") as? Map<String, Any> val songMap = intent.getSerializableExtra("song") as? Map<String, Any>
songMap?.let { song -> songMap?.let { song ->
val songId = song["id"] ?: return@let val songId = song["id"] ?: return@let
val existingIndex = playlist.indexOfFirst { it["id"] == songId } val existingIndex = playlist.indexOfFirst { it["id"] == songId }
if (existingIndex == -1) { if (existingIndex == -1) {
playlist.add(song) playlist.add(song)
@ -213,7 +218,7 @@ class MusicService : MediaBrowserServiceCompat() {
} else { } else {
currentIndex = existingIndex currentIndex = existingIndex
} }
play(song) play(song)
} }
} }
@ -238,6 +243,10 @@ class MusicService : MediaBrowserServiceCompat() {
seekTo(position) seekTo(position)
} }
} }
"setVolume" -> {
val volume = intent.getFloatExtra("volume", 1.0f)
setVolume(volume)
}
} }
return START_STICKY return START_STICKY
} }
@ -267,6 +276,14 @@ class MusicService : MediaBrowserServiceCompat() {
Log.i(TAG, "收到 MEDIA_NEXT 广播") Log.i(TAG, "收到 MEDIA_NEXT 广播")
playNext() playNext()
} }
"com.yunqiinnovation.music_service.AI_SAY_START" -> {
Log.i(TAG, "收到 AI_SAY_START 广播 降低播放音量")
reduceVolume()
}
"com.yunqiinnovation.music_service.AI_SAY_END" -> {
Log.i(TAG, "收到 AI_SAY_START 广播 降低播放音量")
restoreVolume()
}
// 你也可以扩展支持更多指令 // 你也可以扩展支持更多指令
} }
} }
@ -427,19 +444,7 @@ class MusicService : MediaBrowserServiceCompat() {
return PendingIntent.getBroadcast(this, action.toInt(), intent, flags) return PendingIntent.getBroadcast(this, action.toInt(), intent, flags)
} }
// fun mediaButtonPendingIntent(action: Long): PendingIntent {
// val intent = Intent(this, MusicService::class.java).apply {
// this.action = Intent.ACTION_MEDIA_BUTTON
// putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, action.toKeyCode()))
// }
// val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
// } else {
// PendingIntent.FLAG_UPDATE_CURRENT
// }
// return PendingIntent.getService(this, action.toInt(), intent, flags)
// }
// 转换 PlaybackStateCompat.ACTION 到 KeyEvent
fun Long.toKeyCode(): Int = when (this) { fun Long.toKeyCode(): Int = when (this) {
PlaybackStateCompat.ACTION_PLAY -> KeyEvent.KEYCODE_MEDIA_PLAY PlaybackStateCompat.ACTION_PLAY -> KeyEvent.KEYCODE_MEDIA_PLAY
PlaybackStateCompat.ACTION_PAUSE -> KeyEvent.KEYCODE_MEDIA_PAUSE PlaybackStateCompat.ACTION_PAUSE -> KeyEvent.KEYCODE_MEDIA_PAUSE
@ -447,6 +452,43 @@ class MusicService : MediaBrowserServiceCompat() {
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS -> KeyEvent.KEYCODE_MEDIA_PREVIOUS PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS -> KeyEvent.KEYCODE_MEDIA_PREVIOUS
else -> KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE else -> KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
} }
/**
* 设置播放音量
* @param volume 音量值,范围 0.0f - 1.0f
*/
private fun setVolume(volume: Float) {
val clampedVolume = volume.coerceIn(0.0f, 1.0f)
currentVolume = clampedVolume
mediaPlayer?.setVolume(clampedVolume, clampedVolume)
Log.i(TAG, "设置音量: $clampedVolume")
}
/**
* 降低音量(AI说话时)
*/
private fun reduceVolume() {
if (!isVolumeReduced) {
originalVolume = currentVolume
val reducedVolume = (currentVolume * 0.3f).coerceIn(0.0f, 1.0f) // 降低到30%
setVolume(reducedVolume)
isVolumeReduced = true
Log.i(TAG, "AI说话开始,音量从 $originalVolume 降低到 $reducedVolume")
}
}
/**
* 恢复原始音量(AI说话结束时)
*/
private fun restoreVolume() {
if (isVolumeReduced) {
setVolume(originalVolume)
isVolumeReduced = false
Log.i(TAG, "AI说话结束,音量恢复到 $originalVolume")
}
}
/** /**
* 设置播放状态 * 设置播放状态
*/ */
@ -601,3 +643,4 @@ class MusicService : MediaBrowserServiceCompat() {
override fun onBind(intent: Intent?): IBinder? = null override fun onBind(intent: Intent?): IBinder? = null
} }

Loading…
Cancel
Save