Browse Source

Merge branch 'new_dev' of github.com:deepcloud2048/deep_voice into new_dev

weicu
lxm 8 months ago
parent
commit
c3f3f34f4c
  1. 4
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  2. 8
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt
  3. 14
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPClient.kt
  4. 213
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ToolsSystemFunctionHandler.kt

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

@ -294,7 +294,9 @@ object AgentService : CoroutineScope {
config["openaiModel"]?.toString() ?: "",
config["openaivisionModel"]?.toString() ?: "",
config["mcpServer"]?.toString() ?: "",
config["musicServiceType"]?.toString() ?: ""
config["musicServiceType"]?.toString() ?: "",
serverurl,
usertoken,
)
Log.e(TAG, "liwei -----------读取服务地址和用户token: $serverurl $usertoken")

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

@ -219,7 +219,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
/**
* 初始化ChatAPI服务
*/
fun initialize(apiKey: String, baseUrl: String, model: String, visionModel: String, mcpServer: String,musicType: String = "AUTO"): Boolean {
fun initialize(apiKey: String, baseUrl: String, model: String, visionModel: String, mcpServer: String,musicType: String = "AUTO",serverhUrl: String = "",token: String = ""): Boolean {
this.apiKey = apiKey
if (baseUrl.isNotEmpty()) {
this.baseUrl = baseUrl
@ -257,7 +257,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
mcpConfigJson = mcpServer
// 异步初始化MCP客户端
launch {
initializeMcpClient(mcpServer,musicType)
initializeMcpClient(mcpServer,musicType,serverhUrl,token)
// initializeMcpClient("{}")
}
@ -941,9 +941,9 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
/**
* 初始化MCP客户端
*/
fun initializeMcpClient(serverUrl: String,musicType: String = "AUTO"): Boolean {
fun initializeMcpClient(serverUrl: String,musicType: String = "AUTO",searchUrl: String = "",token: String = ""): Boolean {
if (_mcpClient == null) {
_mcpClient = MCPClient(context,musicType)
_mcpClient = MCPClient(context,musicType,searchUrl,token)
}
// 直接使用类的CoroutineScope启动协程

14
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPClient.kt

@ -31,7 +31,7 @@ interface FunctionHandler {
* MCP客户端
* 与 iOS 版本 MCPClient 功能对等
*/
class MCPClient(private val context: Context? = null,musicType: String = "AUTO") : AutoCloseable {
class MCPClient(private val context: Context? = null,musicType: String = "AUTO",searchUrl: String = "",token: String = "") : AutoCloseable {
companion object {
private const val TAG = "MCPClient"
@ -54,6 +54,7 @@ class MCPClient(private val context: Context? = null,musicType: String = "AUTO")
init {
initializeSystemFunctions()
initializeToolsFunctions(searchUrl,token)
Log.d(TAG, "初始化音乐服务: $musicType")
when (musicType) {
"WY" -> initializeMusiceFunctions() //网易
@ -81,6 +82,17 @@ class MCPClient(private val context: Context? = null,musicType: String = "AUTO")
Log.w(TAG, "Failed to initialize system functions", e)
}
}
/**
* 初始化系统函数
*/
private fun initializeToolsFunctions(searchUrl: String = "",token: String = "") {
try {
val handler = ToolsSystemFunctionHandler(context,searchUrl,token)
handler.registerAllFunctions(this)
} catch (e: Exception) {
Log.w(TAG, "Failed to initialize system functions", e)
}
}
/**
* 初始化音乐函数

213
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ToolsSystemFunctionHandler.kt

@ -0,0 +1,213 @@
package com.yunqiinnovation.chat_api
import android.content.Context
import android.content.Intent
import android.util.Log
import com.yunqiinnovation.music_service.MusicSingleton
import org.json.JSONObject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import androidx.core.content.ContextCompat
import kotlinx.coroutines.launch
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.json.JSONArray
import java.io.IOException
import java.util.concurrent.TimeUnit
/**
* QQ音乐系统功能处理器
* 负责注册QQ音乐相关的MCP函数
*/
class ToolsSystemFunctionHandler(private val context: Context? = null,private val searchUrl:String,private val token:String) {
companion object {
const val TAG = "ToolsSystem"
}
fun registerAllFunctions(client: MCPClient) {
try {
client.registerLocalFunction(
"web_search",
"通过搜索网络搜索回复用户相关问题,例如今日新闻、百科、医疗、股票、汇率等等",
mapOf(
"type" to "object",
"properties" to mapOf(
"keyword" to mapOf(
"type" to "string",
"description" to "搜索关键词,例如今日新闻、今日人民币汇率等等"
),
"count" to mapOf(
"type" to "integer",
"description" to "返回的搜索结果数量,默认值为5",
"default" to 5
)
),
"required" to listOf("keyword")
),
SearchSearchHandler(context,searchUrl,token)
)
Log.d(TAG, "音乐所有功能注册完成")
} catch (e: Exception) {
Log.e(TAG, "注册音乐功能失败: ${e.message}", e)
}
}
}
// 搜索系统函数处理类
private class SearchSearchHandler(private val context: Context?,private val searchUrl:String,private val token:String) : FunctionHandler {
companion object {
const val TAG = "SearchSearchHandler"
}
private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
/**
* 搜索网络内容
*/
fun webSearch(
keyword: String,
count: Int = 0,
callback: (Result<List<Map<String, Any?>>>) -> Unit
) {
// 使用协程异步处理,避免阻塞主线程
coroutineScope.launch {
try {
// 配置带超时的 OkHttpClient
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
// 构建搜索请求的 JSON 数据
val jsonObject = JSONObject().apply {
put("keywords", keyword)
put("count", count)
}
val json = jsonObject.toString()
val mediaType = "application/json".toMediaType()
val body = json.toRequestBody(mediaType)
// 构建请求
val request = Request.Builder()
.url("$searchUrl/api/home/aitools_bochasearch")
.addHeader("Authorization", token)
.addHeader("Content-Type", "application/json")
.post(body)
.build()
Log.d("SearchSearchHandler", "发送搜索请求: $json")
// 使用异步调用
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e(TAG, "搜索歌曲请求失败: ${e.message}", e)
callback(Result.failure(e))
}
override fun onResponse(call: Call, response: Response) {
response.use {
try {
if (response.isSuccessful) {
val responseBody = response.body?.string()
Log.d(TAG, "搜索响应: $responseBody")
if (responseBody != null) {
val jsonResponse = JSONObject(responseBody)
val results = parseResultsFromResponse(jsonResponse)
callback(Result.success(results))
} else {
callback(Result.failure(Exception("响应体为空")))
}
} else {
val errorMsg = "搜索失败,HTTP状态码: ${response.code}"
Log.w(TAG, errorMsg)
callback(Result.failure(Exception(errorMsg)))
}
} catch (e: Exception) {
Log.e(TAG, "解析搜索响应时发生异常: ${e.message}", e)
callback(Result.failure(e))
}
}
}
})
} catch (e: Exception) {
Log.e(TAG, "搜索歌曲时发生异常: ${e.message}", e)
callback(Result.failure(e))
}
}
}
/**
* 解析服务器响应中的歌曲列表
*/
private fun parseResultsFromResponse(jsonResponse: JSONObject): List<Map<String, Any?>> {
val results = mutableListOf<Map<String, Any?>>()
try {
val data = jsonResponse.optJSONObject("data")
val resultsArray = data?.optJSONArray("results")
resultsArray?.let { array ->
for (i in 0 until array.length()) {
val songObj = array.getJSONObject(i)
results.add(mapOf(
"role" to songObj.optLong("role"),
"type" to songObj.optString("type"),
"content_type" to songObj.optString("content_type"),
"content" to songObj.optString("content"),
))
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to parse songs from response", e)
}
return results
}
override suspend fun handle(arguments: Map<String, Any>): String {
return try {
val keyword = arguments["keyword"] as? String
?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}"
val count = arguments["count"] as? Int ?: 5 // 默认搜索歌曲
// 使用 suspendCoroutine 将回调转换为协程
suspendCoroutine { continuation ->
webSearch(keyword, count) { searchResult ->
val result = if (searchResult.isSuccess) {
val datas = searchResult.getOrNull() ?: emptyList()
val songsJson = datas.joinToString(",") { song ->
val resultMap = song as Map<String, Any?>
// 添加调试日志
Log.d("SearchSearchHandler", "原始数据: $resultMap")
// 修正字段名映射
val role = resultMap["role"] ?: 0
val type = resultMap["type"] ?: "unknown"
val content_type = resultMap["content_type"] ?: "unknown"
val content = resultMap["content"] ?: "unknown"
"{\"role\": $role, \"type\": \"$type\", \"content_type\": \"$content_type\", \"content\": \"$content\"}"
}
"{\"success\": true, \"iscard\": true, \"broadcast\": true, \"message\": \"搜索成功,共找到数据:${datas.size}\", \"data\": [$songsJson]}"
} else {
val error = searchResult.exceptionOrNull()
"{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}"
}
// 恢复协程执行
continuation.resume(result)
}
}
} catch (e: Exception) {
Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e)
"{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}"
}
}
}
Loading…
Cancel
Save