llm testing

This commit is contained in:
gcw_4spBpAfv
2026-03-10 19:00:32 +08:00
parent ec1f7d2e72
commit 1cae048a7f
5 changed files with 482 additions and 297 deletions

View File

@@ -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<String>,
@@ -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<Pair<String, Bitmap>>()
// 下载所有图片
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<String> {
val imageUrls = mutableListOf<String>()
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<String, String>) -> 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<String, String>()
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)

View File

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

View File

@@ -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)

View File

@@ -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<Pair<String, Bitmap>>()
// 下载所有图片
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<String> {
val imageUrls = mutableListOf<String>()
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
}
}
}

View File

@@ -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<String>, onResult: (String) -> Unit) {
try {
onResult("[测试] 测试 $testName 多角度总结...")
val questions = listOf(
"请从对话中提取用户的姓名,只返回姓名,如果没有提到姓名,请返回未知",
"请总结对话,只返回总结的内容",
"请从对话中提取用户的性别,只返回性别,如果没有提到性别,请返回未知"
)
var completed = 0
val results = mutableMapOf<String, String>()
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<String>, question: String): String {
val messageText = messages.joinToString("\n")
return """
请根据以下对话回答问题:
对话内容:
$messageText
问题:$question
回答:
""".trimIndent()
}
private fun buildFinalSummary(results: Map<String, String>): String {
val summary = StringBuilder()
results.forEach { (question, answer) ->
summary.append("${question.substringAfter("提取")}: $answer\n")
}
return summary.toString()
}
}