standalone v1: dual-backend (openai + litert-google) with SQLDelight dual-log persistence
Replace EchoProtoAgent / EchoAgent / EchoA2aHandler placeholders with a real
stateful agent on top of SQLite (SQLDelight 2.3.2) and litert-api v6.
persistence (commonMain):
- ConversationStore / MessageStore / WorkingMemoryStore — three narrow
interfaces, all operations suspend, AutoCloseable.
- MessageRecord sealed: UserMessage / AssistantMessage (Body subtype),
ToolCall / ToolResult (audit-only), Summary / System (working-memory-only
synthetic). Snake-case @SerialName discriminators.
- WorkingMemoryEntry sealed: System / User(sourceMessageId) /
Assistant(sourceMessageId); sourceMessageId is null for System.
- Two-table dual-log model: append-only message audit + mutable
working_memory with monotonic order_idx.
SQLite (jvmMain):
- SQLDelight schema + SqliteConversationStore / SqliteMessageStore /
SqliteWorkingMemoryStore under src/jvmMain/sqldelight/.
- SqliteStores.open(path) / inMemory(); Schema.create gated on
sqlite_master probe for idempotency.
- All payload_json is the MessageRecord encoded as JSON; subtype-specific
fields avoid migrations.
agent (jvmMain):
- ChatAgent — stateful proto.Agent with live in-memory cache, lock-protected,
AgentEvent bus (Created/Deleted).
- ChatConversation — long-lived LiteConversation handle; created lazily on
first send from working_memory (system + initial messages), reused across
all subsequent turns (REQUIRED for litert-google KV-cache).
- Per turn: append User to audit + WM → sendStreamContents (wrapped in
transformWhile for litert-google-jvm 0.16.1 isDone workaround) → emit
AppendText deltas → append Assistant to audit + WM + touch conversation.
- isClosed flag so getConversation reconstructs after close.
llm (jvmMain):
- LlmConfig data class with LlmBackend enum (OPENAI / GOOGLE); fromEnv
parses AGENTIK_LLM_BACKEND and dispatches to backend-specific config.
- OpenAI: litert-openai, OpenAI-compatible endpoint, validated
baseUrl/apiKey/model.
- Google: litert-google (reflection-resolved pw.binom.litert.google
factory) on top of litertlm-jvm 0.16.1 native engine;
visionBackend/audioBackend = null (LiteRT-LM 0.16.1 binds encoder
graph even with null backend, but a model lacking encoder crashes;
null is the correct "don't bind" signal).
- foldSystemIntoFirstUser (default true for GOOGLE) folds system prompt
into the first user message to avoid chat template alternation issues.
build:
- Add sqldelight plugin + runtime + sqlite-driver + coroutines-extensions
to gradle/libs.versions.toml.
- litert-openai: implementation; litert-google: runtimeOnly (resolved via
reflection at runtime).
- KMP jvm executable via @OptIn(ExperimentalKotlinGradlePluginApi) +
jvm { binaries { executable { mainClass.set("...MainKt") } } }.
tests (jvmTest): 30 passing
- PersistenceTest (11): conversation upsert/list/cascade-delete/rename/
touch; message audit append/list; working-memory order preservation;
image-content payload roundtrip.
- ChatAgentTest (14): system-prompt seeding; persistent vs temp
persistence across SqliteStores reopen; multi-turn audit + WM growth;
interrupt of in-flight slow send; agentEvents Created/Deleted flow;
closed-conv reconstruct via getConversation.
- LlmConfigTest (6): env happy path, defaults, missing fields throw.
smoke tested e2e:
- openai backend against real llm.binom.pw/v1 (myopenai/local/codding)
— multi-turn dialogue persisted, kill -9 + restart survives.
- google backend against gemma-4-E2B-it.litertlm — multi-turn
("Hello there!" → "2 + 2 = 4"), KV-cache survives across turns,
SSE start→append_text*→end cleanly closes.
docs/STANDALONE.md updated for v1 architecture, dual-backend env table,
long-lived LiteConversation invariant, and litert-google-jvm 0.16.1
isDone-stream workaround.
This commit is contained in:
@@ -6,11 +6,14 @@ kotlinx-datetime = "0.8.0"
|
||||
ktor = "3.1.3"
|
||||
agui = "0.1.0"
|
||||
a2a = "1.0.0-SNAPSHOT"
|
||||
litert = "6"
|
||||
sqldelight = "2.3.2"
|
||||
|
||||
[plugins]
|
||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
|
||||
|
||||
[libraries]
|
||||
# --- AG-UI (pw.binom.agui) — KMP: jvm + native ---
|
||||
@@ -23,6 +26,16 @@ a2a-shared = { module = "pw.binom.a2a:shared", version.ref = "a2a" }
|
||||
a2a-client = { module = "pw.binom.a2a:client", version.ref = "a2a" }
|
||||
a2a-server = { module = "pw.binom.a2a:server", version.ref = "a2a" }
|
||||
|
||||
# --- litert-kmp (pw.binom.litert) — universal LLM wrapper ---
|
||||
litert-api = { module = "pw.binom.litert:litert-api", version.ref = "litert" }
|
||||
litert-openai = { module = "pw.binom.litert:litert-openai-jvm", version.ref = "litert" }
|
||||
litert-google = { module = "pw.binom.litert:litert-google", version.ref = "litert" }
|
||||
|
||||
# --- SQLDelight (app.cash.sqldelight) — KMP SQLite, JDBC driver ---
|
||||
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" }
|
||||
sqldelight-sqlite-driver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
|
||||
sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
|
||||
|
||||
# --- Ktor (сервер, JVM) ---
|
||||
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
|
||||
ktor-server-sse = { module = "io.ktor:ktor-server-sse", version.ref = "ktor" }
|
||||
@@ -32,6 +45,9 @@ ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx
|
||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
||||
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
||||
ktor-server-test-host = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" }
|
||||
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
|
||||
# --- commons ---
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
||||
|
||||
Reference in New Issue
Block a user