Browse Source
- 在DBMCPServer模型中添加type字段以区分传输类型 - 实现CustomStreamableHttpClientTransport用于HTTP/JSON传输 - 移除ToolsSystemFunctionHandler及相关搜索工具集成 - 优化MCP客户端初始化,根据type选择SSE或HTTP传输 - 修复iOS端工具调用ID生成逻辑,提升兼容性newdev_shunjiawei
11 changed files with 448 additions and 279 deletions
@ -1,213 +0,0 @@ |
|||||
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…
Reference in new issue