|
|
@ -23,6 +23,7 @@ import android.util.Log |
|
|
import org.json.JSONObject |
|
|
import org.json.JSONObject |
|
|
import android.os.Handler |
|
|
import android.os.Handler |
|
|
import android.os.Looper |
|
|
import android.os.Looper |
|
|
|
|
|
import java.util.UUID |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* ChatAPI服务异常 |
|
|
* ChatAPI服务异常 |
|
|
@ -109,10 +110,12 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
|
|
|
|
|
// 流式请求相关 |
|
|
// 流式请求相关 |
|
|
private var currentStreamJob: Job? = null |
|
|
private var currentStreamJob: Job? = null |
|
|
private var streamCallback: StreamCallback? = null |
|
|
// private var streamCallback: StreamCallback? = null |
|
|
private var currentMessages: List<Map<String, Any>> = emptyList() |
|
|
private var currentMessages: List<Map<String, Any>> = emptyList() |
|
|
private var toolCalls: MutableMap<Int, ToolCallInfo> = mutableMapOf() |
|
|
private var toolCalls: MutableMap<Int, ToolCallInfo> = mutableMapOf() |
|
|
private var isCanceled = false |
|
|
private var sessions: MutableMap<String, StreamCallback?> = mutableMapOf() |
|
|
|
|
|
private var currSessionId = "" // |
|
|
|
|
|
// private var isCanceled = false |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -124,14 +127,14 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
/** |
|
|
/** |
|
|
* 设置流式回调 |
|
|
* 设置流式回调 |
|
|
*/ |
|
|
*/ |
|
|
fun setStreamCallback(callback: StreamCallback) { |
|
|
// fun setStreamCallback(callback: StreamCallback) { |
|
|
streamCallback = callback |
|
|
// streamCallback = callback |
|
|
} |
|
|
// } |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 初始化ChatAPI服务 |
|
|
* 初始化ChatAPI服务 |
|
|
*/ |
|
|
*/ |
|
|
fun initialize(apiKey: String, baseUrl: String, model: String, mcpServer: String): Boolean { |
|
|
fun initialize(apiKey: String, baseUrl: String, model: String, visionModel: String, mcpServer: String): Boolean { |
|
|
this.apiKey = apiKey |
|
|
this.apiKey = apiKey |
|
|
if (baseUrl.isNotEmpty()) { |
|
|
if (baseUrl.isNotEmpty()) { |
|
|
this.baseUrl = baseUrl |
|
|
this.baseUrl = baseUrl |
|
|
@ -139,7 +142,9 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
if (model.isNotEmpty()) { |
|
|
if (model.isNotEmpty()) { |
|
|
this.model = model |
|
|
this.model = model |
|
|
} |
|
|
} |
|
|
|
|
|
if (visionModel.isNotEmpty()) { |
|
|
|
|
|
this.visionModel = visionModel |
|
|
|
|
|
} |
|
|
// 处理 baseUrl:移除末尾的 /chat/completions(如果存在) |
|
|
// 处理 baseUrl:移除末尾的 /chat/completions(如果存在) |
|
|
// 因为 openai-kotlin 会自动拼接 /chat/completions |
|
|
// 因为 openai-kotlin 会自动拼接 /chat/completions |
|
|
var processedBaseUrl = if (this.baseUrl.endsWith("/chat/completions")) { |
|
|
var processedBaseUrl = if (this.baseUrl.endsWith("/chat/completions")) { |
|
|
@ -258,30 +263,30 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
*/ |
|
|
*/ |
|
|
fun sendMessageStream(messages: org.json.JSONArray, callback: StreamCallback) { |
|
|
fun sendMessageStream(messages: org.json.JSONArray, callback: StreamCallback) { |
|
|
// 设置回调 |
|
|
// 设置回调 |
|
|
setStreamCallback(callback) |
|
|
// setStreamCallback(callback) |
|
|
|
|
|
|
|
|
// 转换JSONArray为List<Map<String, Any>> |
|
|
// 转换JSONArray为List<Map<String, Any>> |
|
|
val messageList = convertJsonArrayToList(messages) |
|
|
val messageList = convertJsonArrayToList(messages) |
|
|
|
|
|
currSessionId = UUID.randomUUID().toString() |
|
|
|
|
|
sessions[currSessionId] = callback |
|
|
// 调用原始方法 |
|
|
// 调用原始方法 |
|
|
sendMessageStream(messageList) |
|
|
sendMessageStream(currSessionId,messageList) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 发送消息(流式输出) |
|
|
* 发送消息(流式输出) |
|
|
*/ |
|
|
*/ |
|
|
fun sendMessageStream(messages: List<Map<String, Any>>) { |
|
|
fun sendMessageStream(sessionid:String,messages: List<Map<String, Any>>) { |
|
|
if (!isInitialized || apiKey.isEmpty() || openAI == null) { |
|
|
if (!isInitialized || apiKey.isEmpty() || openAI == null) { |
|
|
Log.e("ChatApiService", "ChatAPI服务未初始化,无法发送消息") |
|
|
Log.e("ChatApiService", "ChatAPI服务未初始化,无法发送消息") |
|
|
streamCallback?.onError(ChatApiException("ChatAPI服务未初始化")) |
|
|
sessions[sessionid]?.onError(ChatApiException("ChatAPI服务未初始化")) |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 重置状态 |
|
|
// 重置状态 |
|
|
isCanceled = false |
|
|
// isCanceled = false |
|
|
currentMessages = messages |
|
|
currentMessages = messages |
|
|
toolCalls.clear() |
|
|
toolCalls.clear() |
|
|
|
|
|
|
|
|
// 检查是否包含图片,决定使用哪个模型 |
|
|
// 检查是否包含图片,决定使用哪个模型 |
|
|
val currentModel = if (containsImage(messages)) visionModel else model |
|
|
val currentModel = if (containsImage(messages)) visionModel else model |
|
|
|
|
|
|
|
|
@ -323,7 +328,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
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}") |
|
|
val flow = openAI!!.chatCompletions(chatCompletionRequest) |
|
|
val flow = openAI!!.chatCompletions(chatCompletionRequest) |
|
|
flow |
|
|
flow |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
@ -332,7 +337,9 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
chatsFlow.collect { result -> |
|
|
chatsFlow.collect { result -> |
|
|
if (isCanceled) return@collect |
|
|
if (currSessionId != sessionid) { |
|
|
|
|
|
return@collect |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
val choice = result.choices.firstOrNull() |
|
|
val choice = result.choices.firstOrNull() |
|
|
if (choice == null) { |
|
|
if (choice == null) { |
|
|
@ -348,7 +355,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
|
|
|
|
|
// 处理普通文本内容 |
|
|
// 处理普通文本内容 |
|
|
delta.content?.let { content -> |
|
|
delta.content?.let { content -> |
|
|
streamCallback?.onToken(content) |
|
|
sessions[sessionid]?.onToken(content) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 收集工具调用信息 |
|
|
// 收集工具调用信息 |
|
|
@ -389,18 +396,20 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!isCanceled) { |
|
|
if (currSessionId == sessionid) { |
|
|
// 检查是否有工具调用需要处理 |
|
|
// 检查是否有工具调用需要处理 |
|
|
val hasToolCalls = processToolCalls() |
|
|
val hasToolCalls = processToolCalls(sessionid) |
|
|
if (!hasToolCalls) { |
|
|
if (!hasToolCalls) { |
|
|
streamCallback?.onComplete() |
|
|
sessions[sessionid]?.onComplete() |
|
|
} |
|
|
} |
|
|
|
|
|
}else{ |
|
|
|
|
|
sessions[sessionid]?.onComplete() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
if (!isCanceled) { |
|
|
// if (currSessionId == sessionid) { |
|
|
streamCallback?.onError(ChatApiException("流式请求失败: ${e.message}")) |
|
|
sessions[sessionid]?.onError(ChatApiException("流式请求失败: ${e.message}")) |
|
|
} |
|
|
// } |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -408,7 +417,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
/** |
|
|
/** |
|
|
* 处理工具调用 |
|
|
* 处理工具调用 |
|
|
*/ |
|
|
*/ |
|
|
private suspend fun processToolCalls(): Boolean { |
|
|
private suspend fun processToolCalls(sessionid: String): Boolean { |
|
|
val firstToolCall = try { |
|
|
val firstToolCall = try { |
|
|
toolCalls.values.firstOrNull { it.isValid() } |
|
|
toolCalls.values.firstOrNull { it.isValid() } |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
@ -430,11 +439,11 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
// 通知上层工具调用事件 |
|
|
// 通知上层工具调用事件 |
|
|
streamCallback?.onFunctionCall(convertMapToJsonObject(functionCall)) |
|
|
sessions[sessionid]?.onFunctionCall(convertMapToJsonObject(functionCall)) |
|
|
// 在后台队列处理工具调用 |
|
|
// 在后台队列处理工具调用 |
|
|
launch { |
|
|
launch { |
|
|
try { |
|
|
try { |
|
|
if (!isCanceled) { |
|
|
if (currSessionId == sessionid) { |
|
|
// 通过MCP客户端处理工具调用 |
|
|
// 通过MCP客户端处理工具调用 |
|
|
val functionName = firstToolCall.name |
|
|
val functionName = firstToolCall.name |
|
|
val argumentsJson = firstToolCall.arguments |
|
|
val argumentsJson = firstToolCall.arguments |
|
|
@ -471,29 +480,35 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
// 工具不存在 |
|
|
// 工具不存在 |
|
|
mapOf("context" to "Tool not found: $functionName") |
|
|
mapOf("context" to "Tool not found: $functionName") |
|
|
} |
|
|
} |
|
|
if (!isCanceled) { |
|
|
if (currSessionId == sessionid) { |
|
|
// 处理结果 |
|
|
// 处理结果 |
|
|
streamCallback?.onFunctionCallResult( |
|
|
sessions[sessionid]?.onFunctionCallResult( |
|
|
convertMapToJsonObject(functionCall), |
|
|
convertMapToJsonObject(functionCall), |
|
|
convertMapToJsonObject(result) |
|
|
convertMapToJsonObject(result) |
|
|
) |
|
|
) |
|
|
// 将结果发送回OpenAI继续对话 |
|
|
// 将结果发送回OpenAI继续对话 |
|
|
sendFunctionCallResultInternal( |
|
|
sendFunctionCallResultInternal( |
|
|
|
|
|
sessionid= sessionid, |
|
|
messages = currentMessages, |
|
|
messages = currentMessages, |
|
|
functionCall = functionCall, |
|
|
functionCall = functionCall, |
|
|
functionResult = gson.toJson(result) |
|
|
functionResult = gson.toJson(result) |
|
|
) |
|
|
) |
|
|
|
|
|
}else{ |
|
|
|
|
|
sessions[sessionid]?.onComplete() |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e("ChatApiService", "工具调用处理过程中出错: ${e.message}", e) |
|
|
Log.e("ChatApiService", "工具调用处理过程中出错: ${e.message}", e) |
|
|
if (!isCanceled) { |
|
|
if (currSessionId == sessionid) { |
|
|
val errorMessage = "工具调用处理失败: ${e.message}" |
|
|
val errorMessage = "工具调用处理失败: ${e.message}" |
|
|
sendFunctionCallResultInternal( |
|
|
sendFunctionCallResultInternal( |
|
|
|
|
|
sessionid= sessionid, |
|
|
messages = currentMessages, |
|
|
messages = currentMessages, |
|
|
functionCall = functionCall, |
|
|
functionCall = functionCall, |
|
|
functionResult = errorMessage |
|
|
functionResult = errorMessage |
|
|
) |
|
|
) |
|
|
|
|
|
}else{ |
|
|
|
|
|
sessions[sessionid]?.onComplete() |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -505,11 +520,12 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
* 发送函数调用结果(内部方法) |
|
|
* 发送函数调用结果(内部方法) |
|
|
*/ |
|
|
*/ |
|
|
private suspend fun sendFunctionCallResultInternal( |
|
|
private suspend fun sendFunctionCallResultInternal( |
|
|
|
|
|
sessionid: String, |
|
|
messages: List<Map<String, Any>>, |
|
|
messages: List<Map<String, Any>>, |
|
|
functionCall: Map<String, Any>, |
|
|
functionCall: Map<String, Any>, |
|
|
functionResult: String |
|
|
functionResult: String |
|
|
) { |
|
|
) { |
|
|
if (isCanceled) return |
|
|
if (currSessionId != sessionid) return |
|
|
|
|
|
|
|
|
val fullMessages = messages.toMutableList() |
|
|
val fullMessages = messages.toMutableList() |
|
|
|
|
|
|
|
|
@ -542,14 +558,16 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
// 重新发起对话,让AI基于工具调用结果生成回复 |
|
|
// 重新发起对话,让AI基于工具调用结果生成回复 |
|
|
sendMessageStream(fullMessages) |
|
|
sendMessageStream(sessionid,fullMessages) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 取消当前流式请求 |
|
|
* 取消当前流式请求 |
|
|
*/ |
|
|
*/ |
|
|
fun cancelCurrentStream(): Boolean { |
|
|
fun cancelCurrentStream(): Boolean { |
|
|
isCanceled = true |
|
|
sessions[currSessionId]?.onComplete() |
|
|
|
|
|
sessions.remove(currSessionId) |
|
|
|
|
|
currSessionId = "" |
|
|
currentStreamJob?.cancel() |
|
|
currentStreamJob?.cancel() |
|
|
currentStreamJob = null |
|
|
currentStreamJob = null |
|
|
return true |
|
|
return true |
|
|
@ -778,9 +796,9 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
* 聊天完成流式接口 |
|
|
* 聊天完成流式接口 |
|
|
* 与iOS版本保持一致的接口 |
|
|
* 与iOS版本保持一致的接口 |
|
|
*/ |
|
|
*/ |
|
|
fun chatCompletionStream(messages: List<Map<String, Any>>, tool: Boolean = false) { |
|
|
fun chatCompletionStream(sessionid: String,messages: List<Map<String, Any>>, tool: Boolean = false) { |
|
|
// 直接调用sendMessageStream,因为该方法已经处理了工具调用 |
|
|
// 直接调用sendMessageStream,因为该方法已经处理了工具调用 |
|
|
sendMessageStream(messages) |
|
|
sendMessageStream(sessionid,messages) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// MARK: - 工具方法 |
|
|
// MARK: - 工具方法 |
|
|
@ -893,27 +911,70 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
} |
|
|
} |
|
|
is List<*> -> { |
|
|
is List<*> -> { |
|
|
// 对于多模态内容,提取文本部分(简化处理) |
|
|
// 对于多模态内容,提取文本部分(简化处理) |
|
|
var textContent = "" |
|
|
val contentParts = mutableListOf<ContentPart>() |
|
|
|
|
|
|
|
|
content.forEach { item -> |
|
|
content.forEach { item -> |
|
|
if (item is Map<*, *>) { |
|
|
if (item is Map<*, *>) { |
|
|
val type = item["type"] as? String |
|
|
val type = item["type"] as? String |
|
|
if (type == "text") { |
|
|
when (type) { |
|
|
val text = item["text"] as? String ?: "" |
|
|
"text" -> { |
|
|
textContent += text |
|
|
val text = item["text"] as? String ?: "" |
|
|
|
|
|
contentParts.add(TextPart(text)) |
|
|
|
|
|
} |
|
|
|
|
|
"image_url" -> { |
|
|
|
|
|
val imageUrlMap = item["image_url"] as? Map<*, *> |
|
|
|
|
|
val url = imageUrlMap?.get("url") as? String ?: "" |
|
|
|
|
|
val detail = imageUrlMap?.get("detail") as? String ?: "auto" |
|
|
|
|
|
if (url.isNotEmpty()) { |
|
|
|
|
|
contentParts.add(ImagePart(url, detail)) |
|
|
|
|
|
} |
|
|
|
|
|
} // 处理其他可能的内容类型 |
|
|
|
|
|
else -> { |
|
|
|
|
|
// 记录未知类型,便于调试 |
|
|
|
|
|
Log.d("MessageParser", "Unknown content type: $type") |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
// 非Map类型的内容,记录日志 |
|
|
|
|
|
Log.d("MessageParser", "Item is not a Map: $item") |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
val finalContent = if (textContent.isEmpty()) "多媒体内容" else textContent |
|
|
// 创建消息对象 |
|
|
|
|
|
if (contentParts.isNotEmpty()) { |
|
|
val chatMessage = when (chatRole) { |
|
|
val chatMessage = when (chatRole) { |
|
|
Role.System -> ChatMessage.System(finalContent) |
|
|
Role.System -> { |
|
|
Role.User -> ChatMessage.User(finalContent) |
|
|
// 系统消息简化显示,将图片替换为[图片] |
|
|
Role.Assistant -> ChatMessage.Assistant(finalContent) |
|
|
val displayText = contentParts.joinToString { part -> |
|
|
else -> ChatMessage.User(finalContent) |
|
|
when (part) { |
|
|
|
|
|
is TextPart -> part.text // 使用TextPart |
|
|
|
|
|
is ImagePart -> "[图片]" // 使用ImagePart |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
ChatMessage.System(displayText) |
|
|
|
|
|
} |
|
|
|
|
|
Role.User -> ChatMessage.User(contentParts) |
|
|
|
|
|
Role.Assistant -> { |
|
|
|
|
|
// 将多模态内容转换为ListContent |
|
|
|
|
|
val listContent = ListContent(contentParts) |
|
|
|
|
|
// 提取文本内容作为Assistant消息的content |
|
|
|
|
|
val textContent = contentParts |
|
|
|
|
|
.filterIsInstance<TextPart>() |
|
|
|
|
|
.joinToString(separator = "") { it.text } |
|
|
|
|
|
|
|
|
|
|
|
// 调用Assistant函数,传递文本内容 |
|
|
|
|
|
ChatMessage.Assistant( |
|
|
|
|
|
content = textContent.ifEmpty { null }, |
|
|
|
|
|
toolCalls = null // 如果有工具调用,需要在这里添加 |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
else -> ChatMessage.User(contentParts) |
|
|
|
|
|
} |
|
|
|
|
|
chatMessages.add(chatMessage) |
|
|
|
|
|
} else { |
|
|
|
|
|
// 内容为空时的处理 |
|
|
|
|
|
Log.d("MessageParser", "No valid content parts found") |
|
|
} |
|
|
} |
|
|
chatMessages.add(chatMessage) |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -1035,13 +1096,13 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
*/ |
|
|
*/ |
|
|
private fun convertJsonArrayToList(jsonArray: org.json.JSONArray): List<Map<String, Any>> { |
|
|
private fun convertJsonArrayToList(jsonArray: org.json.JSONArray): List<Map<String, Any>> { |
|
|
val result = mutableListOf<Map<String, Any>>() |
|
|
val result = mutableListOf<Map<String, Any>>() |
|
|
|
|
|
// Log.e("ChatApiService", "convertJsonArrayToList 1:${jsonArray}") |
|
|
for (i in 0 until jsonArray.length()) { |
|
|
for (i in 0 until jsonArray.length()) { |
|
|
val jsonObject = jsonArray.optJSONObject(i) ?: continue |
|
|
val jsonObject = jsonArray.optJSONObject(i) ?: continue |
|
|
val map = convertJsonObjectToMap(jsonObject) |
|
|
val map = convertJsonObjectToMap(jsonObject) |
|
|
result.add(map) |
|
|
result.add(map) |
|
|
} |
|
|
} |
|
|
|
|
|
// Log.e("ChatApiService", "convertJsonArrayToList 2:${result}") |
|
|
return result |
|
|
return result |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -1123,9 +1184,9 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 通知Flutter端处理函数调用 |
|
|
// 通知Flutter端处理函数调用 |
|
|
mainHandler.post { |
|
|
// mainHandler.post { |
|
|
streamCallback?.onFunctionCall(functionCall) |
|
|
// streamCallback?.onFunctionCall(functionCall) |
|
|
} |
|
|
// } |
|
|
|
|
|
|
|
|
// 返回一个标记,表示函数已被调用 |
|
|
// 返回一个标记,表示函数已被调用 |
|
|
return "Function '$functionName' called with arguments: $arguments" |
|
|
return "Function '$functionName' called with arguments: $arguments" |
|
|
|