|
|
@ -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" |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |