feat: история общения (SQLite) + Koog LLM-клиент в app-phone
SQLite (голый SQLiteOpenHelper, без ORM — как решили): - sessions (id, title, created_at, updated_at) + messages (role, content, tool_call_id, tool_calls JSON, attachments JSON, created_at) по утверждённой схеме; FK-каскад; CHECK на роли. - ChatDb + ChatDao (createSession/listSessions/rename/touch/delete, addMessage, messagesForSession), доступ через PhoneApp.chatDao. - Unit: ModelsTest (роли, JSON tool_calls/attachments). androidTest: ChatDaoTest — CRUD-круг + каскад — OK (3 tests) на эмуляторе. Koog (JetBrains) — базовый LLM-клиент, без агентного слоя: - ai.koog:prompt-executor-openai-client:1.1.1 + http-client-okhttp - LlmClient: OpenAILLMClient(apiKey, baseUrl=llm.binom.pw) → chat(история) - BuildConfig LLM_API_KEY/LLM_MODEL из local.properties (вне git); llm_debug smoke (--ez llm_debug true) — проверен на эмуляторе: реальный ответ от llm.binom.pw (deepseek-v4-flash). - ЗАГЛУШКА Coroutines_jvmKt: дефект публикации Koog 1.1.1 — utils-android не включает jvm-класс, который зовёт OkHttpKoogHttpClient (без неё NoClassDefFoundError на первом вызове). Сигнатура сверена с utils-jvm. Убрать после фикса JetBrains.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package pw.binom.viewmate.phone.agent
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
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 ChatDaoTest {
|
||||
|
||||
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 sessionLifecycleWithMessages() {
|
||||
val sessionId = dao.createSession()
|
||||
assertTrue(sessionId > 0)
|
||||
|
||||
val userMsgId = dao.addMessage(sessionId, ChatRole.USER, "включи Матрицу")
|
||||
val assistantMsgId = dao.addMessage(
|
||||
sessionId,
|
||||
ChatRole.ASSISTANT,
|
||||
"",
|
||||
toolCalls = listOf(ToolCall("c1", "play_movie", "{}")),
|
||||
)
|
||||
val toolMsgId = dao.addMessage(sessionId, ChatRole.TOOL, "", toolCallId = "c1")
|
||||
|
||||
// порядок по id и совпадение ролей/полей
|
||||
val messages = dao.messagesForSession(sessionId)
|
||||
assertEquals(listOf(userMsgId, assistantMsgId, toolMsgId), messages.map { it.id })
|
||||
assertEquals(3, messages.size)
|
||||
|
||||
assertEquals(ChatRole.USER, messages[0].role)
|
||||
assertEquals("включи Матрицу", messages[0].content)
|
||||
assertTrue(messages[0].toolCalls.isEmpty())
|
||||
|
||||
assertEquals(ChatRole.ASSISTANT, messages[1].role)
|
||||
assertEquals(listOf(ToolCall("c1", "play_movie", "{}")), messages[1].toolCalls)
|
||||
|
||||
assertEquals(ChatRole.TOOL, messages[2].role)
|
||||
assertEquals("c1", messages[2].toolCallId)
|
||||
|
||||
// список сессий: созданная есть
|
||||
assertEquals(sessionId, dao.listSessions().single().id)
|
||||
|
||||
// каскадное удаление сообщений вместе с сессией
|
||||
dao.deleteSession(sessionId)
|
||||
assertTrue(dao.messagesForSession(sessionId).isEmpty())
|
||||
assertTrue(dao.listSessions().none { it.id == sessionId })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addMessageTouchesSessionUpdatedAt() {
|
||||
val sessionId = dao.createSession()
|
||||
val before = dao.listSessions().single().updatedAt
|
||||
|
||||
dao.addMessage(sessionId, ChatRole.USER, "привет")
|
||||
|
||||
val after = dao.listSessions().single().updatedAt
|
||||
assertTrue("updated_at должен обновиться после addMessage", after >= before)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun renameSessionChangesTitle() {
|
||||
val sessionId = dao.createSession("первая")
|
||||
dao.renameSession(sessionId, "Матрица")
|
||||
|
||||
val session = dao.listSessions().single()
|
||||
assertEquals("Матрица", session.title)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user