|
|
|
@ -38,7 +38,7 @@ interface StreamCallback { |
|
|
|
/** |
|
|
|
* AI使用量 |
|
|
|
* */ |
|
|
|
fun onUsage(sessionid: String,prompt_tokens: Int?,completion_tokens: Int?,total_tokens: Int?) |
|
|
|
fun onUsage(sessionid: String,prompt_tokens: Int?,completion_tokens: Int?,callfunctions: List<String>?) |
|
|
|
/** |
|
|
|
* 接收到新的token |
|
|
|
*/ |
|
|
|
@ -106,6 +106,13 @@ private data class ToolCallInfo( |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 会话统计信息结构 |
|
|
|
private data class SessionStats( |
|
|
|
var promptTokens: Int = 0, |
|
|
|
var completionTokens: Int = 0, |
|
|
|
val calledFunctions: MutableList<String> = mutableListOf() |
|
|
|
) |
|
|
|
|
|
|
|
/** |
|
|
|
* ChatAPI服务的Android实现 |
|
|
|
* 基于 openai-kotlin 4.0.1,与 iOS 版本接口完全一致 |
|
|
|
@ -147,6 +154,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
private var currSessionId = "" |
|
|
|
// 为每个会话维护独立的回调函数 |
|
|
|
private val sessionCallbacks: MutableMap<String, StreamCallback> = Collections.synchronizedMap(mutableMapOf()) |
|
|
|
// 会话统计信息 |
|
|
|
private val sessionStats: MutableMap<String, SessionStats> = Collections.synchronizedMap(mutableMapOf()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -165,8 +174,17 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
// 1. 取消当前的协程任务 |
|
|
|
currentStreamJob?.cancel(CancellationException("New session started")) |
|
|
|
currentStreamJob = null |
|
|
|
if (oldSessionId != ""){ |
|
|
|
val stats = sessionStats[oldSessionId] |
|
|
|
getSessionCallback(oldSessionId)?.onUsage( |
|
|
|
oldSessionId, |
|
|
|
stats?.promptTokens, |
|
|
|
stats?.completionTokens, |
|
|
|
stats?.calledFunctions?.toList() |
|
|
|
) |
|
|
|
sessionStats.remove(oldSessionId) |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 通知旧会话被中止 |
|
|
|
getSessionCallback(oldSessionId)?.onComplete(oldSessionId) // 直接完成当前会话 |
|
|
|
|
|
|
|
// 3. 清理状态 |
|
|
|
@ -174,6 +192,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
sessionCallbacks.remove(oldSessionId) |
|
|
|
toolCalls.clear() |
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "会话 $oldSessionId 已被中止") |
|
|
|
} |
|
|
|
} |
|
|
|
@ -187,6 +206,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
sessionCallbacks.remove(oldSessionId) |
|
|
|
currentStreamJob = null |
|
|
|
toolCalls.clear() |
|
|
|
sessionStats.remove(oldSessionId) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -335,6 +355,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
// val newSessionId = UUID.randomUUID().toString() |
|
|
|
currSessionId = sessionid |
|
|
|
sessionCallbacks[sessionid] = callback |
|
|
|
// 初始化会话统计 |
|
|
|
sessionStats[sessionid] = SessionStats() |
|
|
|
|
|
|
|
// 3. 转换消息并开始处理 |
|
|
|
val messageList = convertJsonArrayToList(messages) |
|
|
|
@ -496,7 +518,6 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
} |
|
|
|
|
|
|
|
toolCall.function?.let { function -> |
|
|
|
// 安全处理函数名称 - 使用 try-catch 避免 null 属性访问 |
|
|
|
try { |
|
|
|
val functionName = function.name |
|
|
|
if (!functionName.isNullOrEmpty()) { |
|
|
|
@ -532,9 +553,22 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
val promptTokens = estimatePromptTokens(chatMessages); //计算上传token |
|
|
|
val completionTokens = roughTokenCount(replyContext); //计算上传token |
|
|
|
getSessionCallback(sessionid)?.onUsage(sessionid,promptTokens,completionTokens,promptTokens+completionTokens) |
|
|
|
|
|
|
|
// 仅统计数据字节数,服务端负责计算token |
|
|
|
val promptBytes = try { |
|
|
|
val jsonArray = org.json.JSONArray() |
|
|
|
messages.forEach { msg -> |
|
|
|
jsonArray.put(convertMapToJsonObject(msg)) |
|
|
|
} |
|
|
|
jsonArray.toString().toByteArray(Charsets.UTF_8).size |
|
|
|
} catch (_: Exception) { 0 } |
|
|
|
val completionBytes = replyContext.toByteArray(Charsets.UTF_8).size |
|
|
|
// 累计到会话统计 |
|
|
|
sessionStats.getOrPut(sessionid) { SessionStats() }.apply { |
|
|
|
this.promptTokens += promptBytes |
|
|
|
this.completionTokens += completionBytes |
|
|
|
} |
|
|
|
|
|
|
|
// Log.d(TAG, "liwei-------------------------开始AI 对话 8") |
|
|
|
// 完成处理 |
|
|
|
if (sessionid == currSessionId) { |
|
|
|
@ -543,12 +577,33 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
val hasToolCalls = processToolCalls(sessionid) |
|
|
|
if (!hasToolCalls) { |
|
|
|
try { |
|
|
|
val stats = sessionStats[sessionid] |
|
|
|
getSessionCallback(sessionid)?.onUsage( |
|
|
|
sessionid, |
|
|
|
stats?.promptTokens, |
|
|
|
stats?.completionTokens, |
|
|
|
stats?.calledFunctions?.toList() |
|
|
|
) |
|
|
|
getSessionCallback(sessionid)?.onComplete(sessionid) |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "onComplete回调异常: ${e.message}", e) |
|
|
|
} |
|
|
|
clearCurrentSession() |
|
|
|
} |
|
|
|
}else{ |
|
|
|
// 会话结束,上报使用统计,再回调完成 |
|
|
|
try { |
|
|
|
val stats = sessionStats[sessionid] |
|
|
|
getSessionCallback(sessionid)?.onUsage( |
|
|
|
sessionid, |
|
|
|
stats?.promptTokens, |
|
|
|
stats?.completionTokens, |
|
|
|
stats?.calledFunctions?.toList() |
|
|
|
) |
|
|
|
getSessionCallback(sessionid)?.onComplete(sessionid) |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "onUsage回调异常: ${e.message}", e) |
|
|
|
} |
|
|
|
} |
|
|
|
// Log.d(TAG, "liwei-------------------------开始AI 对话 10") |
|
|
|
} catch (e: CancellationException) { |
|
|
|
@ -557,6 +612,13 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
} catch (e: Exception) { |
|
|
|
if (sessionid == currSessionId) { |
|
|
|
try { |
|
|
|
val stats = sessionStats[sessionid] |
|
|
|
getSessionCallback(sessionid)?.onUsage( |
|
|
|
sessionid, |
|
|
|
stats?.promptTokens, |
|
|
|
stats?.completionTokens, |
|
|
|
stats?.calledFunctions?.toList() |
|
|
|
) |
|
|
|
Log.d(TAG, "AI聊天异常 Session $sessionid 错误类型:${e::class.simpleName} 错误完整类型:${e::class.qualifiedName}") |
|
|
|
getSessionCallback(sessionid)?.onError(sessionid,1000,"流式请求失败: ${e.message}") |
|
|
|
} catch (ex: Exception) { |
|
|
|
@ -601,6 +663,12 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor |
|
|
|
"id" to firstToolCall.id |
|
|
|
) |
|
|
|
|
|
|
|
// 记录方法名称以返回给调用方 |
|
|
|
val functionName: String = firstToolCall.name |
|
|
|
if (functionName.isNotEmpty()) { |
|
|
|
sessionStats.getOrPut(sessionid) { SessionStats() }.calledFunctions.add(functionName) |
|
|
|
} |
|
|
|
|
|
|
|
// 通知上层工具调用事件 |
|
|
|
try { |
|
|
|
getSessionCallback(sessionid)?.onFunctionCall(sessionid,convertMapToJsonObject(functionCall)) |
|
|
|
|