feat: потоковый локальный ассистент — фраза → LLM → ответ на очки
- agent/Assistant.kt: оркестратор диалога (SQLite-история + Koog), таймаут 30с, сессия одна живая, история — последние 20 сообщений - ChatDao.latestSessionId(); SYSTEM_PROMPT нейтральный (TODO: персона) - PhoneApp: очередь фраз (Channel UNLIMITED), assistantLoop (THINKING/ShowText/IDLE), ensureAssistant ленивый; ключ/модель из BuildConfig (local.properties) - Очки: assistantText StateFlow, ShowText → оверлей (белый текст речи + серый ответ), оверлей остаётся после Click до новой сессии - AssistantTest (androidTest, 3 теста: сессия/история/таймаут) — OK (6 tests всего) - README: строка про ассистента
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package pw.binom.viewmate.phone.agent
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AssistantTest {
|
||||
|
||||
private lateinit var dao: ChatDao
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context.deleteDatabase("assistant.db")
|
||||
dao = ChatDao(ChatDb(context))
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
context.deleteDatabase("assistant.db")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstPhraseCreatesSessionAndTwoMessages() = runBlocking {
|
||||
val histories = mutableListOf<List<ChatMessage>>()
|
||||
val fake: suspend (List<ChatMessage>, String?) -> String = { history, _ ->
|
||||
histories += history
|
||||
"Привет! Чем помочь?"
|
||||
}
|
||||
val assistant = Assistant(chatDao = dao, chat = fake, timeoutMs = 1_000)
|
||||
|
||||
val answer = assistant.process("привет")
|
||||
|
||||
// ответ = то, что вернул фейк
|
||||
assertEquals("Привет! Чем помочь?", answer)
|
||||
|
||||
// создана сессия, в ней 2 сообщения (user + assistant)
|
||||
val sessionId = dao.latestSessionId()
|
||||
assertTrue("сессия должна создаться", sessionId != null)
|
||||
val messages = dao.messagesForSession(sessionId!!)
|
||||
assertEquals(2, messages.size)
|
||||
assertEquals(ChatRole.USER, messages[0].role)
|
||||
assertEquals("привет", messages[0].content)
|
||||
assertEquals(ChatRole.ASSISTANT, messages[1].role)
|
||||
assertEquals("Привет! Чем помочь?", messages[1].content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun secondPhraseReusesSessionAndFakeGetsHistory() = runBlocking {
|
||||
val histories = mutableListOf<List<ChatMessage>>()
|
||||
val fake: suspend (List<ChatMessage>, String?) -> String = { history, _ ->
|
||||
histories += history
|
||||
"ответ #${history.size}"
|
||||
}
|
||||
val assistant = Assistant(chatDao = dao, chat = fake, timeoutMs = 1_000)
|
||||
|
||||
assistant.process("первый")
|
||||
val session1 = dao.latestSessionId()
|
||||
assistant.process("второй")
|
||||
val session2 = dao.latestSessionId()
|
||||
|
||||
// сессия та же
|
||||
assertEquals(session1, session2)
|
||||
|
||||
// фейк получил историю из 2+ сообщений (user1 + assistant1 + user2)
|
||||
assertTrue("фейк получил историю из 2+ сообщений", histories.last().size >= 2)
|
||||
|
||||
// в сессии 4 сообщения
|
||||
assertEquals(4, dao.messagesForSession(session1!!).size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun timeoutReturnsMessageAndOnlyUserMessagePersisted() = runBlocking {
|
||||
val fake: suspend (List<ChatMessage>, String?) -> String = { _, _ ->
|
||||
delay(150)
|
||||
"опоздал"
|
||||
}
|
||||
val assistant = Assistant(chatDao = dao, chat = fake, timeoutMs = 100)
|
||||
|
||||
val answer = assistant.process("вопрос")
|
||||
|
||||
assertEquals("Ассистент не ответил (таймаут)", answer)
|
||||
|
||||
// в БД ТОЛЬКО user-сообщение — assistant не пишется
|
||||
val sessionId = dao.latestSessionId()
|
||||
val messages = dao.messagesForSession(sessionId!!)
|
||||
assertEquals(1, messages.size)
|
||||
assertEquals(ChatRole.USER, messages[0].role)
|
||||
assertEquals("вопрос", messages[0].content)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user