From 1cae048a7f8ca25995b3452ea673343fe560b6b1 Mon Sep 17 00:00:00 2001 From: gcw_4spBpAfv Date: Tue, 10 Mar 2026 19:00:32 +0800 Subject: [PATCH] llm testing --- .../com/digitalperson/Live2DChatActivity.kt | 423 ++++++------------ .../com/digitalperson/config/AppConfig.kt | 9 + .../interaction/UserMemoryStore.kt | 12 +- .../onboard_testing/FaceRecognitionTest.kt | 213 +++++++++ .../onboard_testing/LLMSummaryTest.kt | 122 +++++ 5 files changed, 482 insertions(+), 297 deletions(-) create mode 100644 app/src/main/java/com/digitalperson/onboard_testing/FaceRecognitionTest.kt create mode 100644 app/src/main/java/com/digitalperson/onboard_testing/LLMSummaryTest.kt diff --git a/app/src/main/java/com/digitalperson/Live2DChatActivity.kt b/app/src/main/java/com/digitalperson/Live2DChatActivity.kt index 41a4cdf..c5fcb42 100644 --- a/app/src/main/java/com/digitalperson/Live2DChatActivity.kt +++ b/app/src/main/java/com/digitalperson/Live2DChatActivity.kt @@ -42,6 +42,7 @@ import com.digitalperson.data.AppDatabase import com.digitalperson.data.entity.ChatMessage import com.digitalperson.interaction.ConversationBufferMemory import com.digitalperson.interaction.ConversationSummaryMemory + import java.io.File import android.graphics.BitmapFactory import org.json.JSONObject @@ -56,6 +57,9 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import com.digitalperson.onboard_testing.FaceRecognitionTest +import com.digitalperson.onboard_testing.LLMSummaryTest + class Live2DChatActivity : AppCompatActivity() { companion object { private const val TAG_ACTIVITY = "Live2DChatActivity" @@ -116,6 +120,9 @@ class Live2DChatActivity : AppCompatActivity() { private var lastFaceIdentityId: String? = null private var lastFaceRecognizedName: String? = null + private lateinit var faceRecognitionTest: FaceRecognitionTest + private lateinit var llmSummaryTest: LLMSummaryTest + override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, @@ -400,6 +407,8 @@ class Live2DChatActivity : AppCompatActivity() { // 显示本地 LLM 开关,并同步状态 uiManager.showLLMSwitch(false) } + + } /** @@ -457,12 +466,21 @@ class Live2DChatActivity : AppCompatActivity() { } // 测试人脸识别(延迟执行,确保所有组件初始化完成) -// ioScope.launch { -// kotlinx.coroutines.delay(10000) // 等待3秒,确保所有组件初始化完成 -// runOnUiThread { -// runFaceRecognitionTest() -// } -// } + if (AppConfig.OnboardTesting.FACE_REGONITION) { + faceRecognitionTest = FaceRecognitionTest(this) + faceRecognitionTest.setFaceDetectionPipeline(faceDetectionPipeline) + CoroutineScope(Dispatchers.IO).launch { + kotlinx.coroutines.delay(10000) + runOnUiThread { + faceRecognitionTest.runTest("http://192.168.1.19:5000/api/face_test_images") { message -> + Log.i(AppConfig.TAG, message) + uiManager.appendToUi("\n$message\n") + } + } + } + } + + } /** @@ -479,185 +497,8 @@ class Live2DChatActivity : AppCompatActivity() { .show() } - /** - * 运行人脸识别相似度测试 - * 使用网络服务器上的测试图片 - */ - private fun runFaceRecognitionTest() { - Log.i(TAG_ACTIVITY, "Starting face recognition test...") - uiManager.appendToUi("\n[测试] 开始人脸识别相似度测试...\n") - - // 从服务器获取目录下的所有图片文件列表 - ioScope.launch { - try { - val imageUrls = fetchImageListFromServer("http://192.168.1.19:5000/api/face_test_images") - - if (imageUrls.isEmpty()) { - Log.e(AppConfig.TAG, "No images found in server directory") - runOnUiThread { - uiManager.appendToUi("\n[测试] 服务器目录中没有找到图片文件\n") - } - return@launch - } - - Log.i(AppConfig.TAG, "[测试]Found ${imageUrls.size} images: $imageUrls") - runOnUiThread { - uiManager.appendToUi("\n[测试] 发现 ${imageUrls.size} 张测试图片\n") - } - - val bitmaps = mutableListOf>() - - // 下载所有图片 - for (url in imageUrls) { - Log.d(AppConfig.TAG, "[测试]Downloading test image: $url") - val bitmap = downloadImage(url) - if (bitmap != null) { - val fileName = url.substringAfterLast("/") - bitmaps.add(fileName to bitmap) - Log.d(AppConfig.TAG, "[测试]Downloaded image $fileName successfully") - } else { - Log.e(AppConfig.TAG, "[测试]Failed to download image: $url") - } - } - - if (bitmaps.size < 2) { - Log.e(AppConfig.TAG, "[测试]Not enough test images downloaded") - runOnUiThread { - uiManager.appendToUi("\n[测试] 测试图片下载失败,无法进行测试\n") - } - return@launch - } - - // 对所有图片两两比较 - Log.i(AppConfig.TAG, "[测试]Starting similarity comparison for ${bitmaps.size} images...") - for (i in 0 until bitmaps.size) { - for (j in i + 1 until bitmaps.size) { - val (fileName1, bitmap1) = bitmaps[i] - val (fileName2, bitmap2) = bitmaps[j] - - Log.d(AppConfig.TAG, "[测试]Comparing $fileName1 with $fileName2") - - // 检测人脸 - val face1 = detectFace(bitmap1) - val face2 = detectFace(bitmap2) - - Log.d(AppConfig.TAG, "[测试]Face detection result: face1=$face1, face2=$face2") - - if (face1 != null && face2 != null) { - // 计算相似度 - Log.d(AppConfig.TAG, "[测试]Detected faces, calculating similarity...") - val similarity = faceDetectionPipeline?.getRecognizer()?.testSimilarityBetween( - bitmap1, face1, bitmap2, face2 - ) - val similarityRaw = faceDetectionPipeline?.getRecognizer()?.run { - val emb1 = extractEmbedding(bitmap1, face1) - val emb2 = extractEmbedding(bitmap2, face2) - if (emb1.isNotEmpty() && emb2.isNotEmpty()) { - var dot = 0f - var n1 = 0f - var n2 = 0f - for (k in emb1.indices) { - dot += emb1[k] * emb2[k] - n1 += emb1[k] * emb1[k] - n2 += emb2[k] * emb2[k] - } - if (n1 > 1e-12f && n2 > 1e-12f) { - (dot / (kotlin.math.sqrt(n1) * kotlin.math.sqrt(n2))).coerceIn(-1f, 1f) - } else -1f - } else -1f - } - - Log.d(AppConfig.TAG, "[测试]Similarity result: $similarity") - - if (similarity != null && similarity >= 0) { - val message = "[测试] 图片 $fileName1 与 $fileName2 的相似度: $similarity" - val compareMessage = "[测试] 对齐后=$similarity, 原始裁剪=$similarityRaw" - Log.i(AppConfig.TAG, message) - Log.i(AppConfig.TAG, compareMessage) - runOnUiThread { - uiManager.appendToUi("\n$message\n") - uiManager.appendToUi("$compareMessage\n") - } - } else { - Log.w(AppConfig.TAG, "[测试]Failed to calculate similarity: $similarity") - runOnUiThread { - uiManager.appendToUi("\n[测试] 计算相似度失败: $similarity\n") - } - } - } else { - val message = "[测试] 无法检测到人脸: $fileName1 或 $fileName2" - Log.w(AppConfig.TAG, message) - runOnUiThread { - uiManager.appendToUi("\n$message\n") - } - } - } - } - - Log.i(AppConfig.TAG, "[测试]Face recognition test completed") - runOnUiThread { - uiManager.appendToUi("\n[测试] 人脸识别相似度测试完成\n") - } - - } catch (e: Exception) { - Log.e(AppConfig.TAG, "Error during face recognition test: ${e.message}", e) - runOnUiThread { - uiManager.appendToUi("\n[测试] 测试过程中发生错误: ${e.message}\n") - } - } - } - } - - /** - * 从服务器获取目录下的图片文件列表 - * 调用 API 接口获取图片列表 - */ - private fun fetchImageListFromServer(apiUrl: String): List { - val imageUrls = mutableListOf() - - return try { - // 调用 API 接口 - val connection = java.net.URL(apiUrl).openConnection() as java.net.HttpURLConnection - connection.requestMethod = "GET" - connection.connectTimeout = 10000 - connection.readTimeout = 10000 - connection.setRequestProperty("Accept", "application/json") - - try { - val responseCode = connection.responseCode - if (responseCode == 200) { - connection.inputStream.use { input -> - val content = input.bufferedReader().use { it.readText() } - Log.d(AppConfig.TAG, "API response: $content") - - // 解析 JSON 响应 - val jsonObject = org.json.JSONObject(content) - val imagesArray = jsonObject.getJSONArray("images") - - // 构建完整的图片 URL - val baseUrl = apiUrl.replace("/api/face_test_images", "/shared_files/face_test") - for (i in 0 until imagesArray.length()) { - val fileName = imagesArray.getString(i) - val fullUrl = "$baseUrl/$fileName" - imageUrls.add(fullUrl) - Log.d(AppConfig.TAG, "Added image URL: $fullUrl") - } - } - } else { - Log.e(AppConfig.TAG, "API request failed with code: $responseCode") - } - } finally { - connection.disconnect() - } - - imageUrls - } catch (e: Exception) { - Log.e(AppConfig.TAG, "Failed to fetch image list: ${e.message}", e) - // 如果获取失败,返回空列表 - emptyList() - } - } - + + /** * 检查 URL 是否存在 */ @@ -674,87 +515,8 @@ class Live2DChatActivity : AppCompatActivity() { false } } - - /** - * 从网络下载图片 - */ - private fun downloadImage(url: String): Bitmap? { - return try { - // 使用与大模型相同的下载方式 - val tempFile = File(cacheDir, "temp_test_image_${System.currentTimeMillis()}.jpg") - val success = FileHelper.downloadTestImage(url, tempFile) - - if (success && tempFile.exists()) { - val bitmap = BitmapFactory.decodeFile(tempFile.absolutePath) - tempFile.delete() // 删除临时文件 - bitmap - } else { - Log.e(AppConfig.TAG, "Failed to download image: $url") - null - } - } catch (e: Exception) { - Log.e(AppConfig.TAG, "Failed to download image: ${e.message}", e) - null - } - } - - /** - * 检测图片中的人脸 - */ - private fun detectFace(bitmap: Bitmap): FaceBox? { - Log.d(AppConfig.TAG, "[测试]Detecting face in bitmap: ${bitmap.width}x${bitmap.height}") - return try { - val engine = RetinaFaceEngineRKNN() - Log.d(AppConfig.TAG, "[测试]Initializing RetinaFace engine...") - if (engine.initialize(applicationContext)) { - Log.d(AppConfig.TAG, "[测试]RetinaFace engine initialized successfully") - val raw = engine.detect(bitmap) - Log.d(AppConfig.TAG, "[测试]Face detection result: ${raw.joinToString(", ")}") - engine.release() - - if (raw.isNotEmpty()) { - val stride = when { - raw.size % 15 == 0 -> 15 - raw.size % 5 == 0 -> 5 - else -> 0 - } - - Log.d(AppConfig.TAG, "[测试]Stride: $stride, raw size: ${raw.size}") - - if (stride > 0) { - val faceCount = raw.size / stride - Log.d(AppConfig.TAG, "[测试]Detected $faceCount faces") - if (faceCount > 0) { - val i = 0 - val lm = if (stride >= 15) raw.copyOfRange(i + 5, i + 15) else null - val hasLm = lm?.all { it >= 0f } == true - - val faceBox = FaceBox( - left = raw[i], - top = raw[i + 1], - right = raw[i + 2], - bottom = raw[i + 3], - score = raw[i + 4], - hasLandmarks = hasLm, - landmarks = if (hasLm) lm else null - ) - Log.d(AppConfig.TAG, "[测试]Created face box: $faceBox") - return faceBox - } - } - } else { - Log.w(AppConfig.TAG, "[测试]No faces detected in bitmap") - } - } else { - Log.e(AppConfig.TAG, "[测试]Failed to initialize RetinaFace engine") - } - null - } catch (e: Exception) { - Log.e(AppConfig.TAG, "[测试]Failed to detect face: ${e.message}", e) - null - } - } - + + private fun createAsrCallback() = object : AsrManager.AsrCallback { override fun onAsrStarted() { currentTrace?.markASRStart() @@ -1247,27 +1009,30 @@ class Live2DChatActivity : AppCompatActivity() { Log.d(AppConfig.TAG, "Generated conversation summary for $activeUserId: $summary") } - // 使用 conversationBufferMemory 的对话记录提取用户信息 + // 使用多角度提问方式提取用户信息 val dialogue = messages.joinToString("\n") { "${it.role}: ${it.content}" } - requestLocalProfileExtraction(dialogue) { raw -> + requestMultiAngleProfileExtraction(dialogue) { profileData -> try { - val json = parseFirstJsonObject(raw) - val name = json.optString("name", "").trim().ifBlank { null } - val age = json.optString("age", "").trim().ifBlank { null } - val gender = json.optString("gender", "").trim().ifBlank { null } - val hobbies = json.optString("hobbies", "").trim().ifBlank { null } - val summary = json.optString("summary", "").trim().ifBlank { null } - if (name != null) { - userMemoryStore.updateDisplayName(activeUserId, name) - } - userMemoryStore.updateProfile(activeUserId, age, gender, hobbies, summary) - - // 清空已处理的对话记录 - conversationBufferMemory.clear(activeUserId) - - runOnUiThread { - uiManager.appendToUi("\n[Memory] 已更新用户画像: $activeUserId\n") + val nameToUpdate = profileData["name"]?.trim()?.ifBlank { null } + val ageToUpdate = profileData["age"]?.trim()?.ifBlank { null } + val genderToUpdate = profileData["gender"]?.trim()?.ifBlank { null } + val hobbiesToUpdate = profileData["hobbies"]?.trim()?.ifBlank { null } + val summaryToUpdate = profileData["summary"]?.trim()?.ifBlank { null } + Log.d(TAG_LLM, "profileData: $profileData") + if (nameToUpdate != null || ageToUpdate != null || genderToUpdate != null || hobbiesToUpdate != null || summaryToUpdate != null) { + if (nameToUpdate != null) { + userMemoryStore.updateDisplayName(activeUserId, nameToUpdate) + Log.i(TAG_LLM, "Updated display name to $nameToUpdate") + } + userMemoryStore.updateProfile(activeUserId, ageToUpdate, genderToUpdate, hobbiesToUpdate, summaryToUpdate) + + // 清空已处理的对话记录 + conversationBufferMemory.clear(activeUserId) + + runOnUiThread { + uiManager.appendToUi("\n[Memory] 已更新用户画像: $activeUserId\n") + } } } catch (e: Exception) { Log.w(TAG_LLM, "Profile parse failed: ${e.message}") @@ -1275,27 +1040,77 @@ class Live2DChatActivity : AppCompatActivity() { } } - private fun requestLocalProfileExtraction(dialogue: String, onResult: (String) -> Unit) { + private fun requestMultiAngleProfileExtraction(dialogue: String, onResult: (Map) -> Unit) { try { val local = llmManager if (local == null) { - onResult("{}") + onResult(emptyMap()) return } - localThoughtSilentMode = true - pendingLocalProfileCallback = onResult - Log.i(TAG_LLM, "Routing profile extraction to LOCAL") - local.generateResponseWithSystem( - "你是信息抽取器。仅输出JSON对象,不要其他文字。字段为name,age,gender,hobbies,summary。", - "请从以下对话提取用户信息,未知填空字符串,注意不需要:\n$dialogue" + + val questions = listOf( + "请从对话中提取用户的姓名,只返回姓名,如果没有提到姓名,请返回未知", + "请从对话中提取用户的年龄,只返回年龄,如果没有提到年龄,请返回未知", + "请从对话中提取用户的性别,只返回性别,如果没有提到性别,请返回未知", + "请从对话中提取用户的爱好,只返回爱好,如果没有提到爱好,请返回未知", + "请总结对话,只返回总结的内容" ) + + var completed = 0 + val results = mutableMapOf() + + questions.forEach { question -> + val prompt = buildMultiAnglePrompt(dialogue, question) + local.generate(prompt) { answer -> + val processedAnswer = processProfileAnswer(answer) + + when { + question.contains("姓名") -> results["name"] = processedAnswer + question.contains("年龄") -> results["age"] = processedAnswer + question.contains("性别") -> results["gender"] = processedAnswer + question.contains("爱好") -> results["hobbies"] = processedAnswer + question.contains("总结") -> results["summary"] = processedAnswer + } + + completed++ + + if (completed == questions.size) { + onResult(results) + } + } + } } catch (e: Exception) { - pendingLocalProfileCallback = null - localThoughtSilentMode = false - Log.e(TAG_LLM, "requestLocalProfileExtraction failed: ${e.message}", e) - onResult("{}") + Log.e(TAG_LLM, "requestMultiAngleProfileExtraction failed: ${e.message}", e) + onResult(emptyMap()) } } + + private fun buildMultiAnglePrompt(dialogue: String, question: String): String { + return """ + 请根据以下对话回答问题: + + 对话内容: + $dialogue + + 问题:$question + + 回答: + """.trimIndent() + } + + private fun processProfileAnswer(answer: String): String { + var processed = answer.replace("<", "").replace(">", "") + if (processed.contains("unknown", ignoreCase = true) || + processed.contains("null", ignoreCase = true) || + processed.contains("未知")) { + return "" + } + if (processed.contains(":")) { + processed = processed.substringAfter(":").trim() + } + processed = processed.replace(".", "").trim() + return processed + } private fun parseFirstJsonObject(text: String): JSONObject { val raw = text.trim() @@ -1363,6 +1178,22 @@ class Live2DChatActivity : AppCompatActivity() { }) Log.i(TAG_LLM, "LOCAL memory LLM initialized") useLocalLLM = true + + if (AppConfig.OnboardTesting.LOCAL_LLM_SUMMARY) { + llmSummaryTest = LLMSummaryTest(this) + ioScope.launch { + kotlinx.coroutines.delay(5000) // 等待5秒,确保LLMManager初始化完成 + runOnUiThread { + if (llmManager != null) { + llmSummaryTest.setLLMManager(llmManager!!) + llmSummaryTest.runTest { message -> + Log.i(AppConfig.TAG, message) + uiManager.appendToUi("\n$message\n") + } + } + } + } + } } catch (e: Exception) { Log.e(AppConfig.TAG, "Failed to initialize LLM: ${e.message}", e) Log.e(TAG_LLM, "LOCAL init failed: ${e.message}", e) diff --git a/app/src/main/java/com/digitalperson/config/AppConfig.kt b/app/src/main/java/com/digitalperson/config/AppConfig.kt index 2be3602..9dcef1e 100644 --- a/app/src/main/java/com/digitalperson/config/AppConfig.kt +++ b/app/src/main/java/com/digitalperson/config/AppConfig.kt @@ -90,4 +90,13 @@ object AppConfig { // 模型文件大小估计(字节) const val MODEL_SIZE_ESTIMATE = 500L * 1024 * 1024 // 500MB } + + object OnboardTesting { + // 测试人脸识别 + const val FACE_REGONITION = false + // 测试本地大模型 + const val LOCAL_LLM_SUMMARY = false + + + } } diff --git a/app/src/main/java/com/digitalperson/interaction/UserMemoryStore.kt b/app/src/main/java/com/digitalperson/interaction/UserMemoryStore.kt index 13df14c..58b842a 100644 --- a/app/src/main/java/com/digitalperson/interaction/UserMemoryStore.kt +++ b/app/src/main/java/com/digitalperson/interaction/UserMemoryStore.kt @@ -52,6 +52,16 @@ class UserMemoryStore(context: Context) { fun updateDisplayName(userId: String, displayName: String?) { if (displayName.isNullOrBlank()) return upsertUserSeen(userId, displayName) + + val now = System.currentTimeMillis() + + val existing = memoryCache[userId] + if (existing != null) { + memoryCache[userId] = existing.copy( + displayName = displayName ?: existing.displayName, + lastSeenAt = now + ) + } } fun updateThought(userId: String, thought: String) { @@ -73,7 +83,7 @@ class UserMemoryStore(context: Context) { fun updateProfile(userId: String, age: String?, gender: String?, hobbies: String?, summary: String?) { GlobalScope.launch(Dispatchers.IO) { - upsertUserSeen(userId, null) + val now = System.currentTimeMillis() userMemoryDao.updateProfile(userId, age, gender, hobbies, summary, now) diff --git a/app/src/main/java/com/digitalperson/onboard_testing/FaceRecognitionTest.kt b/app/src/main/java/com/digitalperson/onboard_testing/FaceRecognitionTest.kt new file mode 100644 index 0000000..a52467e --- /dev/null +++ b/app/src/main/java/com/digitalperson/onboard_testing/FaceRecognitionTest.kt @@ -0,0 +1,213 @@ +package com.digitalperson.onboard_testing + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Log +import com.digitalperson.config.AppConfig +import com.digitalperson.engine.RetinaFaceEngineRKNN +import com.digitalperson.face.FaceBox +import com.digitalperson.face.FaceDetectionPipeline +import com.digitalperson.face.FaceRecognizer +import com.digitalperson.util.FileHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.File + +class FaceRecognitionTest(private val context: Context) { + + private lateinit var faceDetectionPipeline: FaceDetectionPipeline + private lateinit var faceRecognizer: FaceRecognizer + + fun setFaceDetectionPipeline(pipeline: FaceDetectionPipeline) { + this.faceDetectionPipeline = pipeline + this.faceRecognizer = pipeline.getRecognizer() + } + + fun runTest(apiUrl: String, onResult: (String) -> Unit) { + CoroutineScope(Dispatchers.IO).launch { + try { + val imageUrls = fetchImageListFromServer(apiUrl) + + if (imageUrls.isEmpty()) { + onResult("[测试] 服务器目录中没有找到图片文件") + return@launch + } + + onResult("[测试] 发现 ${imageUrls.size} 张测试图片") + + val bitmaps = mutableListOf>() + + // 下载所有图片 + for (url in imageUrls) { + val bitmap = downloadImage(url) + if (bitmap != null) { + val fileName = url.substringAfterLast("/") + bitmaps.add(fileName to bitmap) + onResult("[测试] 下载图片 $fileName 成功") + } else { + onResult("[测试] 下载图片 $url 失败") + } + } + + if (bitmaps.size < 2) { + onResult("[测试] 测试图片下载失败,无法进行测试") + return@launch + } + + // 对所有图片两两比较(只比较不同图片) + for (i in 0 until bitmaps.size) { + for (j in i + 1 until bitmaps.size) { + val (fileName1, bitmap1) = bitmaps[i] + val (fileName2, bitmap2) = bitmaps[j] + + // 检测人脸 + val face1 = detectFace(bitmap1) + val face2 = detectFace(bitmap2) + + if (face1 != null && face2 != null) { + // 计算相似度 + val similarity = faceRecognizer.testSimilarityBetween( + bitmap1, face1, bitmap2, face2 + ) + + if (similarity >= 0) { + onResult("[测试] 图片 $fileName1 与 $fileName2 的相似度: $similarity") + } else { + onResult("[测试] 计算相似度失败: $similarity") + } + } else { + onResult("[测试] 无法检测到人脸: $fileName1 或 $fileName2") + } + } + } + + onResult("[测试] 人脸识别相似度测试完成") + + } catch (e: Exception) { + onResult("[测试] 测试过程中发生错误: ${e.message}") + } + } + } + + private fun fetchImageListFromServer(apiUrl: String): List { + val imageUrls = mutableListOf() + + return try { + // 调用 API 接口 + val connection = java.net.URL(apiUrl).openConnection() as java.net.HttpURLConnection + connection.requestMethod = "GET" + connection.connectTimeout = 10000 + connection.readTimeout = 10000 + connection.setRequestProperty("Accept", "application/json") + + try { + val responseCode = connection.responseCode + if (responseCode == 200) { + connection.inputStream.use { input -> + val content = input.bufferedReader().use { it.readText() } + Log.d(AppConfig.TAG, "API response: $content") + + // 解析 JSON 响应 + val jsonObject = org.json.JSONObject(content) + val imagesArray = jsonObject.getJSONArray("images") + + // 构建完整的图片 URL + val baseUrl = apiUrl.replace("/api/face_test_images", "/shared_files/face_test") + for (i in 0 until imagesArray.length()) { + val fileName = imagesArray.getString(i) + val fullUrl = "$baseUrl/$fileName" + imageUrls.add(fullUrl) + Log.d(AppConfig.TAG, "Added image URL: $fullUrl") + } + } + } else { + Log.e(AppConfig.TAG, "API request failed with code: $responseCode") + } + } finally { + connection.disconnect() + } + + imageUrls + } catch (e: Exception) { + Log.e(AppConfig.TAG, "Failed to fetch image list: ${e.message}", e) + // 如果获取失败,返回空列表 + emptyList() + } + } + + private fun downloadImage(url: String): Bitmap? { + return try { + // 使用与大模型相同的下载方式 + val tempFile = File(context.cacheDir, "temp_test_image_${System.currentTimeMillis()}.jpg") + val success = FileHelper.downloadTestImage(url, tempFile) + + if (success && tempFile.exists()) { + val bitmap = BitmapFactory.decodeFile(tempFile.absolutePath) + tempFile.delete() // 删除临时文件 + bitmap + } else { + Log.e(AppConfig.TAG, "Failed to download image: $url") + null + } + } catch (e: Exception) { + Log.e(AppConfig.TAG, "Failed to download image: ${e.message}", e) + null + } + } + + private fun detectFace(bitmap: Bitmap): FaceBox? { + Log.d(AppConfig.TAG, "[测试]Detecting face in bitmap: ${bitmap.width}x${bitmap.height}") + return try { + val engine = RetinaFaceEngineRKNN() + Log.d(AppConfig.TAG, "[测试]Initializing RetinaFace engine...") + if (engine.initialize(context)) { + Log.d(AppConfig.TAG, "[测试]RetinaFace engine initialized successfully") + val raw = engine.detect(bitmap) + Log.d(AppConfig.TAG, "[测试]Face detection result: ${raw.joinToString(", ")}") + engine.release() + + if (raw.isNotEmpty()) { + val stride = when { + raw.size % 15 == 0 -> 15 + raw.size % 5 == 0 -> 5 + else -> 0 + } + + Log.d(AppConfig.TAG, "[测试]Stride: $stride, raw size: ${raw.size}") + + if (stride > 0) { + val faceCount = raw.size / stride + Log.d(AppConfig.TAG, "[测试]Detected $faceCount faces") + if (faceCount > 0) { + val i = 0 + val lm = if (stride >= 15) raw.copyOfRange(i + 5, i + 15) else null + val hasLm = lm?.all { it >= 0f } == true + + val faceBox = FaceBox( + left = raw[i], + top = raw[i + 1], + right = raw[i + 2], + bottom = raw[i + 3], + score = raw[i + 4], + hasLandmarks = hasLm, + landmarks = if (hasLm) lm else null + ) + Log.d(AppConfig.TAG, "[测试]Created face box: $faceBox") + return faceBox + } + } + } else { + Log.w(AppConfig.TAG, "[测试]No faces detected in bitmap") + } + } else { + Log.e(AppConfig.TAG, "[测试]Failed to initialize RetinaFace engine") + } + null + } catch (e: Exception) { + Log.e(AppConfig.TAG, "[测试]Failed to detect face: ${e.message}", e) + null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/digitalperson/onboard_testing/LLMSummaryTest.kt b/app/src/main/java/com/digitalperson/onboard_testing/LLMSummaryTest.kt new file mode 100644 index 0000000..b17ab1d --- /dev/null +++ b/app/src/main/java/com/digitalperson/onboard_testing/LLMSummaryTest.kt @@ -0,0 +1,122 @@ +package com.digitalperson.onboard_testing + +import android.content.Context +import android.util.Log +import com.digitalperson.config.AppConfig +import com.digitalperson.llm.LLMManager +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class LLMSummaryTest(private val context: Context) { + + private lateinit var llmManager: LLMManager + + fun setLLMManager(manager: LLMManager) { + this.llmManager = manager + } + + fun runTest(onResult: (String) -> Unit) { + CoroutineScope(Dispatchers.IO).launch { + try { + onResult("[测试] 开始本地大模型总结能力测试...") + + // 测试用例 1: 简单对话总结 + val testCase1 = listOf( + "助手: 下午好,小朋友,请问你叫什么名字?", + "用户: 你好,我是张三", + "助手: 你好张三,有什么可以帮你的吗?", + "用户: 我想了解一下今天的天气", + "助手: 今天天气晴朗,温度25度,适合户外活动" + ) + + // 测试用例 2: 复杂对话总结 + val testCase2 = listOf( + "用户: 我想学习编程", + "助手: 好的,你想学习哪种编程语言?", + "用户: 我对Python感兴趣", + "助手: Python是一种很好的入门语言,你可以从基础语法开始学习", + "用户: 有什么推荐的学习资源吗?", + "助手: 你可以看官方文档,或者一些在线教程,比如Codecademy或Coursera" + ) + + // 测试用例 3: 多轮对话总结 + val testCase3 = listOf( + "用户: 你能帮我做一个番茄炒蛋吗?", + "助手: 当然可以,需要准备番茄、鸡蛋、盐、糖等材料", + "用户: 具体步骤是什么?", + "助手: 1. 番茄切块,鸡蛋打散;2. 热锅倒油,炒鸡蛋;3. 加入番茄翻炒;4. 加盐糖调味", + "用户: 需要多长时间?", + "助手: 大约15-20分钟" + ) + + // 执行测试 + testMultiAngleSummary("简单对话", testCase1, onResult) + testMultiAngleSummary("复杂对话", testCase2, onResult) + testMultiAngleSummary("多轮对话", testCase3, onResult) + + onResult("[测试] 本地大模型总结能力测试完成") + + } catch (e: Exception) { + onResult("[测试] 测试过程中发生错误: ${e.message}") + } + } + } + + private fun testMultiAngleSummary(testName: String, messages: List, onResult: (String) -> Unit) { + try { + onResult("[测试] 测试 $testName 多角度总结...") + + val questions = listOf( + "请从对话中提取用户的姓名,只返回姓名,如果没有提到姓名,请返回未知", + "请总结对话,只返回总结的内容", + "请从对话中提取用户的性别,只返回性别,如果没有提到性别,请返回未知" + ) + + var completed = 0 + val results = mutableMapOf() + + questions.forEach { question -> + val prompt = buildMultiAnglePrompt(messages, question) + llmManager.generate(prompt) { answer -> + results[question] = answer + completed++ + + if (completed == questions.size) { + // 所有问题都回答完成 + val summary = buildFinalSummary(results) + onResult("[测试] $testName 多角度总结结果: $summary") + } + } + } + + } catch (e: Exception) { + onResult("[测试] $testName 测试失败: ${e.message}") + } + } + + private fun buildMultiAnglePrompt(messages: List, question: String): String { + val messageText = messages.joinToString("\n") + return """ + 请根据以下对话回答问题: + + 对话内容: + $messageText + + 问题:$question + + 回答: + """.trimIndent() + } + + private fun buildFinalSummary(results: Map): String { + val summary = StringBuilder() + results.forEach { (question, answer) -> + summary.append("${question.substringAfter("提取")}: $answer\n") + } + return summary.toString() + } + + +} \ No newline at end of file