Browse Source
在MCPClient中新增ToolsSystemFunctionHandler,用于注册web_search函数。 该函数允许通过外部API进行网络搜索,需要传入服务器地址和用户token。 修改AgentService、ChatApiService和MCPClient的初始化方法以传递这些参数。newdev_shunjiawei
4 changed files with 233 additions and 6 deletions
@ -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…
Reference in new issue