Browse Source

上传支付逻辑

newdev_shunjiawei
liwei1dao 11 months ago
parent
commit
6f367e7dbd
  1. 5
      lib/modules/agent/controllers/agent_controller.dart
  2. 89
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  3. 7
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt
  4. 5
      local_plugins/agent_service/lib/agent_service.dart
  5. 100
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt
  6. 48
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/Utils.kt

5
lib/modules/agent/controllers/agent_controller.dart

@ -1019,6 +1019,11 @@ class AgentController extends GetxController with WidgetsBindingObserver {
// } // }
} }
break; break;
case AgentServiceEventType.assistantUsages:
final sessionid = event.data['sessionid'] ?? '';
final integral = event.data['integral'] ?? 0;
User.instance.aiIntegral = integral;
break;
default: default:
break; break;
} }

89
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -205,7 +205,8 @@ object AgentService : CoroutineScope {
serverurl = config["serverurl"]?.toString() ?: ""; serverurl = config["serverurl"]?.toString() ?: "";
usertoken = config["usertoken"]?.toString() ?: ""; usertoken = config["usertoken"]?.toString() ?: "";
callingTool = config["callingTool"]?.toString() ?: ""; callingTool = config["callingTool"]?.toString() ?: "";
aiintegral = (config["aiintegral"] as? Number)?.toInt() ?: 0 aiintegral = config["aiintegral"]?.toString()?.toIntOrNull() ?: 0;
// 初始化上下文和服务 // 初始化上下文和服务
this.context = appContext.applicationContext this.context = appContext.applicationContext
@ -1007,7 +1008,6 @@ object AgentService : CoroutineScope {
// 兜底处理,直接添加原消息 // 兜底处理,直接添加原消息
addToHistoryMessages(userMessage) addToHistoryMessages(userMessage)
} }
// Log.e("ChatApiService", "liwei---------sendMessageStream messages:${messagesWithSystemPrompt}")
chatApiService.sendMessageStream( chatApiService.sendMessageStream(
sessionid, sessionid,
messagesWithSystemPrompt, messagesWithSystemPrompt,
@ -1017,16 +1017,19 @@ object AgentService : CoroutineScope {
sessionid: String, sessionid: String,
prompt_tokens: Int?, prompt_tokens: Int?,
completion_tokens: Int?, completion_tokens: Int?,
total_tokens: Int? callfunctions: List<String>?
) { ) {
try { try {
sendStatisticsEvent( // 显式构造 Map<String, Int>,避免 mapOf 推断为 Map<String, Int>
mapOf( val actions = mutableMapOf<String, Int>()
"CHAT_PROMPT_TOKENS" to (prompt_tokens ?: 0), actions["AICHAT_PROMPT_TOKENS"] = (prompt_tokens ?: 0)
"CHAT_COMPLETION_TOKENS" to (completion_tokens ?: 0), actions["AICHAT_COMPLETION_TOKENS"] = (completion_tokens ?: 0)
"CHAT_TOTAL_TOKENS" to (total_tokens ?: 0) // 累计函数调用次数:按函数名分别累加 value += 1
) callfunctions?.forEach { funcName ->
) val key = "AICHAT_FUNCTION_" + funcName
actions[key] = (actions[key] ?: 0) + 1
}
sendStatisticsEvent(actions)
}catch (e: Exception) { }catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onUsage 异常", e) Log.e(TAG, "liwei--------------- AI Call onUsage 异常", e)
} }
@ -1035,12 +1038,10 @@ object AgentService : CoroutineScope {
override fun onToken(sessionid: String, token: String) { override fun onToken(sessionid: String, token: String) {
try { try {
if (sessionid != currsessionId) { if (sessionid != currsessionId) {
// Log.d(TAG, "liwei--------------- AI Call onToken 会话过期 $sessionid-------$currsessionId") return
return
} }
responseBuilder.append(token) responseBuilder.append(token)
// Log.d(TAG, "liwei--------------- AI Call onToken:$token speakResponse:$speakResponse reply:$reply broadcast:$broadcast") if (speakResponse && reply && broadcast && token.isNotEmpty()) {
if (speakResponse && reply && broadcast && token.isNotEmpty()) {
ttsService?.speakStream(token) ttsService?.speakStream(token)
} }
if (reply && token.isNotEmpty()) { if (reply && token.isNotEmpty()) {
@ -1308,26 +1309,18 @@ object AgentService : CoroutineScope {
} }
language = languages[0] language = languages[0]
return try { return try {
Log.i(TAG, "设置支持的语言: ${languages.joinToString(", ")}") Log.i(TAG, "设置支持的语言: ${languages.joinToString(", ")}")
val asrSuccess = azureAsrHelper?.initialize(
val asrSuccess = azureAsrHelper?.initialize(
subscriptionKey = this.azureSpeechKey, subscriptionKey = this.azureSpeechKey,
region = this.azureSpeechRegion, region = this.azureSpeechRegion,
supportedLanguages = languages.toTypedArray(), supportedLanguages = languages.toTypedArray(),
audioSourceType = audioSourceType audioSourceType = audioSourceType
) ?: false ) ?: false
if (asrSuccess) { if (asrSuccess) {
recognizeCallback()
recognizeCallback()
} else { } else {
Log.w(TAG, "Azure ASR初始化失败") Log.w(TAG, "Azure ASR初始化失败")
} }
// 同时更新TTS语言设置 // 同时更新TTS语言设置
val ttsSuccess = updateTtsLanguage(languages.first()) // 使用第一个语言作为TTS语言 val ttsSuccess = updateTtsLanguage(languages.first()) // 使用第一个语言作为TTS语言
@ -2549,16 +2542,18 @@ object AgentService : CoroutineScope {
for ((key, value) in actions) { for ((key, value) in actions) {
actionsObject.put(key, value) actionsObject.put(key, value)
} }
jsonObject.put("actions", actionsObject) jsonObject.put("usagetype", 3) // 3 表示AICHAT
jsonObject.put("usages", actionsObject)
val json = jsonObject.toString() val json = jsonObject.toString()
Log.d(TAG, "统计事件发送成功,请求: $json")
val mediaType = "application/json".toMediaType() val mediaType = "application/json".toMediaType()
val body = json.toRequestBody(mediaType) val body = json.toRequestBody(mediaType)
// 构建请求 // 构建请求
val request = Request.Builder() val request = Request.Builder()
.url("$serverurl/api/home/user_pushaction") .url("$serverurl/api/home/user_usages")
.addHeader("Authorization", usertoken) .addHeader("Authorization", usertoken)
.addHeader("Content-Type", "application/json") .addHeader("Content-Type", "application/json")
.post(body) .post(body)
@ -2573,8 +2568,42 @@ object AgentService : CoroutineScope {
override fun onResponse(call: Call, response: Response) { override fun onResponse(call: Call, response: Response) {
response.use { response.use {
// 仅读取一次响应体,避免重复读取导致异常,同时统一后续处理使用
val bodyStr = response.body?.string() ?: ""
if (response.isSuccessful) { if (response.isSuccessful) {
Log.d(TAG, "统计事件发送成功") Log.d(TAG, "统计事件发送成功,响应: $bodyStr")
try {
val json = JSONObject(bodyStr)
val code = json.optInt("code", -1)
val msg = json.optString("msg", "")
val dataObj = json.optJSONObject("data")
// 解析 data.usages 为 Map<String, Int>
val dataMap = mutableMapOf<String, Int>()
val usagesObj = dataObj?.optJSONObject("usages")
if (usagesObj != null) {
val keys = usagesObj.keys()
while (keys.hasNext()) {
val k = keys.next()
dataMap[k] = usagesObj.optInt(k, 0)
}
for ((k, v) in dataMap) {
Log.d(TAG, "统计事件发送成功,响应: $k -> $v")
if(k == "3"){ //AI积分更新
aiintegral = v
// 广播一个细化事件,便于前端单独处理 AI 积分更新
sendEvent(
"assistant_usages",
mapOf(
"integral" to aiintegral
)
)
}
}
}
} catch (e: Exception) {
Log.w(TAG, "解析统计响应失败: ${e.message}")
}
} else { } else {
Log.w(TAG, "统计事件发送失败,HTTP状态码: ${response.code}") Log.w(TAG, "统计事件发送失败,HTTP状态码: ${response.code}")
} }
@ -2619,4 +2648,4 @@ object AgentService : CoroutineScope {
Log.e(TAG, "保存 _isInterrupt 缓存状态时发生异常: ${e.message}", e) Log.e(TAG, "保存 _isInterrupt 缓存状态时发生异常: ${e.message}", e)
} }
} }
} }

7
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt

@ -70,6 +70,7 @@ class BleMediaService : Service(), CoroutineScope {
private const val APP_NAVIGATION_MODE = "navigationMode" private const val APP_NAVIGATION_MODE = "navigationMode"
private const val APP_CALLING_TOOL = "callingTool" private const val APP_CALLING_TOOL = "callingTool"
private const val KEY_AI_INTAGRAL = "aiintegral" private const val KEY_AI_INTAGRAL = "aiintegral"
// 用于跟踪服务是否正在运行 // 用于跟踪服务是否正在运行
private val isRunning = AtomicBoolean(false) private val isRunning = AtomicBoolean(false)
@ -663,11 +664,13 @@ private var companionManager: BleCompanionManager? = null
private fun getConfigString(prefs: SharedPreferences, key: String, defaultValue: String): String { private fun getConfigString(prefs: SharedPreferences, key: String, defaultValue: String): String {
return prefs.getString(key, defaultValue) ?: defaultValue return prefs.getString(key, defaultValue) ?: defaultValue
} }
/**
* 从SharedPreferences获取字符串配置
*/
private fun getConfigInt(prefs: SharedPreferences, key: String, defaultValue: Int): Int { private fun getConfigInt(prefs: SharedPreferences, key: String, defaultValue: Int): Int {
return prefs.getInt(key, defaultValue) return prefs.getInt(key, defaultValue)
} }
override fun onBind(intent: Intent?): IBinder? = null override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() { override fun onDestroy() {

5
local_plugins/agent_service/lib/agent_service.dart

@ -59,6 +59,9 @@ enum AgentServiceEventType {
/// 函数调用结果 /// 函数调用结果
functionCallResult, functionCallResult,
/// AI助手使用统计
assistantUsages,
/// 图片处理中 /// 图片处理中
imageProcessing, imageProcessing,
@ -167,6 +170,8 @@ class AgentService {
return AgentServiceEventType.functionCall; return AgentServiceEventType.functionCall;
case 'function_call_result': case 'function_call_result':
return AgentServiceEventType.functionCallResult; return AgentServiceEventType.functionCallResult;
case 'assistant_usages':
return AgentServiceEventType.assistantUsages;
case 'image_processing': case 'image_processing':
return AgentServiceEventType.imageProcessing; return AgentServiceEventType.imageProcessing;
case 'image_ready': case 'image_ready':

100
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt

@ -38,7 +38,7 @@ interface StreamCallback {
/** /**
* AI使用量 * 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 * 接收到新的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实现 * ChatAPI服务的Android实现
* 基于 openai-kotlin 4.0.1,与 iOS 版本接口完全一致 * 基于 openai-kotlin 4.0.1,与 iOS 版本接口完全一致
@ -113,7 +120,7 @@ private data class ToolCallInfo(
class ChatApiService(private val context: android.content.Context? = null) : CoroutineScope { class ChatApiService(private val context: android.content.Context? = null) : CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.IO + SupervisorJob() override val coroutineContext: CoroutineContext = Dispatchers.IO + SupervisorJob()
companion object { companion object {
private const val TAG = "ChatApiService" private const val TAG = "ChatApiService"
} }
@ -133,7 +140,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
// MCP 客户端 // MCP 客户端
private var _mcpClient: MCPClient? = null private var _mcpClient: MCPClient? = null
// 公开的MCP客户端访问器 // 公开的MCP客户端访问器
val mcpClient: MCPClient? val mcpClient: MCPClient?
get() = _mcpClient get() = _mcpClient
@ -147,6 +154,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
private var currSessionId = "" private var currSessionId = ""
// 为每个会话维护独立的回调函数 // 为每个会话维护独立的回调函数
private val sessionCallbacks: MutableMap<String, StreamCallback> = Collections.synchronizedMap(mutableMapOf()) 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. 取消当前的协程任务 // 1. 取消当前的协程任务
currentStreamJob?.cancel(CancellationException("New session started")) currentStreamJob?.cancel(CancellationException("New session started"))
currentStreamJob = null currentStreamJob = null
if (oldSessionId != ""){
// 2. 通知旧会话被中止 val stats = sessionStats[oldSessionId]
getSessionCallback(oldSessionId)?.onUsage(
oldSessionId,
stats?.promptTokens,
stats?.completionTokens,
stats?.calledFunctions?.toList()
)
sessionStats.remove(oldSessionId)
}
getSessionCallback(oldSessionId)?.onComplete(oldSessionId) // 直接完成当前会话 getSessionCallback(oldSessionId)?.onComplete(oldSessionId) // 直接完成当前会话
// 3. 清理状态 // 3. 清理状态
@ -174,6 +192,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
sessionCallbacks.remove(oldSessionId) sessionCallbacks.remove(oldSessionId)
toolCalls.clear() toolCalls.clear()
Log.d(TAG, "会话 $oldSessionId 已被中止") Log.d(TAG, "会话 $oldSessionId 已被中止")
} }
} }
@ -187,6 +206,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
sessionCallbacks.remove(oldSessionId) sessionCallbacks.remove(oldSessionId)
currentStreamJob = null currentStreamJob = null
toolCalls.clear() toolCalls.clear()
sessionStats.remove(oldSessionId)
} }
/** /**
@ -335,6 +355,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
// val newSessionId = UUID.randomUUID().toString() // val newSessionId = UUID.randomUUID().toString()
currSessionId = sessionid currSessionId = sessionid
sessionCallbacks[sessionid] = callback sessionCallbacks[sessionid] = callback
// 初始化会话统计
sessionStats[sessionid] = SessionStats()
// 3. 转换消息并开始处理 // 3. 转换消息并开始处理
val messageList = convertJsonArrayToList(messages) val messageList = convertJsonArrayToList(messages)
@ -414,7 +436,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
throw IllegalStateException("OpenAI客户端未初始化") throw IllegalStateException("OpenAI客户端未初始化")
} }
// Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}") // Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}")
// 添加超时控制,10秒超时 // 添加超时控制,10秒超时
val flow = withTimeout(10.seconds) { val flow = withTimeout(10.seconds) {
openAI!!.chatCompletions(chatCompletionRequest) openAI!!.chatCompletions(chatCompletionRequest)
} }
@ -496,7 +518,6 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} }
toolCall.function?.let { function -> toolCall.function?.let { function ->
// 安全处理函数名称 - 使用 try-catch 避免 null 属性访问
try { try {
val functionName = function.name val functionName = function.name
if (!functionName.isNullOrEmpty()) { 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 // 仅统计数据字节数,服务端负责计算token
getSessionCallback(sessionid)?.onUsage(sessionid,promptTokens,completionTokens,promptTokens+completionTokens) 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") // Log.d(TAG, "liwei-------------------------开始AI 对话 8")
// 完成处理 // 完成处理
if (sessionid == currSessionId) { if (sessionid == currSessionId) {
@ -543,12 +577,33 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
val hasToolCalls = processToolCalls(sessionid) val hasToolCalls = processToolCalls(sessionid)
if (!hasToolCalls) { if (!hasToolCalls) {
try { try {
val stats = sessionStats[sessionid]
getSessionCallback(sessionid)?.onUsage(
sessionid,
stats?.promptTokens,
stats?.completionTokens,
stats?.calledFunctions?.toList()
)
getSessionCallback(sessionid)?.onComplete(sessionid) getSessionCallback(sessionid)?.onComplete(sessionid)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "onComplete回调异常: ${e.message}", e) Log.e(TAG, "onComplete回调异常: ${e.message}", e)
} }
clearCurrentSession() 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") // Log.d(TAG, "liwei-------------------------开始AI 对话 10")
} catch (e: CancellationException) { } catch (e: CancellationException) {
@ -557,6 +612,13 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} catch (e: Exception) { } catch (e: Exception) {
if (sessionid == currSessionId) { if (sessionid == currSessionId) {
try { 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}") Log.d(TAG, "AI聊天异常 Session $sessionid 错误类型:${e::class.simpleName} 错误完整类型:${e::class.qualifiedName}")
getSessionCallback(sessionid)?.onError(sessionid,1000,"流式请求失败: ${e.message}") getSessionCallback(sessionid)?.onError(sessionid,1000,"流式请求失败: ${e.message}")
} catch (ex: Exception) { } catch (ex: Exception) {
@ -586,7 +648,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
// 只处理有效的工具调用 // 只处理有效的工具调用
val validToolCalls = toolCalls.values.filter { it.isValid() } val validToolCalls = toolCalls.values.filter { it.isValid() }
if (validToolCalls.isEmpty()) { if (validToolCalls.isEmpty()) {
Log.w("ChatApiService", "[Session: $sessionid] 没有有效的工具调用(参数可能不完整)") Log.w("ChatApiService", "[Session: $sessionid] 没有有效的工具调用(参数可能不完整)")
return false return false
} }
@ -601,6 +663,12 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
"id" to firstToolCall.id "id" to firstToolCall.id
) )
// 记录方法名称以返回给调用方
val functionName: String = firstToolCall.name
if (functionName.isNotEmpty()) {
sessionStats.getOrPut(sessionid) { SessionStats() }.calledFunctions.add(functionName)
}
// 通知上层工具调用事件 // 通知上层工具调用事件
try { try {
getSessionCallback(sessionid)?.onFunctionCall(sessionid,convertMapToJsonObject(functionCall)) getSessionCallback(sessionid)?.onFunctionCall(sessionid,convertMapToJsonObject(functionCall))
@ -609,7 +677,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
} }
// 在当前协程作用域内处理工具调用,使用async确保生命周期管理 // 在当前协程作用域内处理工具调用,使用async确保生命周期管理
val toolCallDeferred = async { val toolCallDeferred = async {
try { try {
// 检查会话是否仍然有效 // 检查会话是否仍然有效
if (sessionid != currSessionId) { if (sessionid != currSessionId) {
@ -750,7 +818,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
// 不重新抛出异常,让流程正常结束 // 不重新抛出异常,让流程正常结束
} }
return true return true
} }
/** /**
@ -983,7 +1051,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
* 关闭MCP客户端 * 关闭MCP客户端
*/ */
fun closeMcpClient() { fun closeMcpClient() {
runBlocking { runBlocking {
_mcpClient?.disconnectAll() _mcpClient?.disconnectAll()
_mcpClient = null _mcpClient = null
} }
@ -1230,7 +1298,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
ChatMessage.System(displayText) ChatMessage.System(displayText)
} }
Role.User -> ChatMessage.User(contentParts) Role.User -> ChatMessage.User(contentParts)
Role.Assistant -> { Role.Assistant -> {
// 将多模态内容转换为ListContent // 将多模态内容转换为ListContent
val listContent = ListContent(contentParts) val listContent = ListContent(contentParts)
// 提取文本内容作为Assistant消息的content // 提取文本内容作为Assistant消息的content
@ -1468,4 +1536,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"
} }
} }
} }

48
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/Utils.kt

@ -8,9 +8,6 @@ import android.net.Uri
import android.provider.Settings import android.provider.Settings
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.aallam.openai.api.chat.ChatMessage
import com.aallam.openai.api.chat.TextContent
import com.aallam.openai.api.chat.ListContent
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.engine.okhttp.* import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.sse.* import io.ktor.client.plugins.sse.*
@ -166,47 +163,4 @@ fun roughTokenCount(text: String): Int {
val englishCharCount = trimmed.length - chineseCharCount val englishCharCount = trimmed.length - chineseCharCount
return chineseCharCount + (englishCharCount / 4) + 1 return chineseCharCount + (englishCharCount / 4) + 1
} }
/**
* 估算消息列表的总 token 数量(近似)
* @param messages List of message map: role: user/assistant, content: string or array
* @return 估算 token 数
*/
fun estimatePromptTokens(messages: List<ChatMessage>): Int {
var totalText = ""
for (message in messages) {
when (val content = message.messageContent) {
is TextContent? -> {
totalText += content?.content + " "
}
is ListContent -> {
// 处理多模态内容数组
content.content.forEach { item ->
if (item is Map<*, *>) {
val type = item["type"] as? String
if (type == "text") {
val text = item["text"] as? String ?: ""
totalText += text + " "
}else if(type == "image_url"){
val text = item["image_url"] as? String ?: ""
totalText += text + " "
}
}
}
}
}
}
return roughTokenCount(totalText)
}
//fun estimatePromptTokens(messages: List<ChatMessage>): Int {
// var tokens = 0
// for (msg in messages) {
// tokens += 4 // 每条消息结构开销
// tokens += roughTokenCount(msg.role.role.lowercase()) // ChatRole 枚举转小写字符串
// tokens += roughTokenCount(msg.content ?: "")
// }
// tokens += 2 // 结尾 assistant 回复结构开销
// return tokens
//}
Loading…
Cancel
Save