cad4d5fc7c
Убрана зависимость pw.binom.agui:server из проекта — AGUI больше не нужен (см. Memory #3675; agentik переходит на свой протокол :proto и HTTP-фасад :server). Чистка: - standalone/build.gradle.kts: implementation(libs.agui.server) → удалено - gradle/libs.versions.toml: [versions] agui + agui-api/agui-client/ agui-server entries → удалены - settings.gradle.kts: убран 'AG-UI' из комментария к Nexus-репо - proto/build.gradle.kts: комментарий 'Зеркалит набор AG-UI api' → 'Полный набор KMP-целей' - proto/src/.../Agent.kt: KDoc '(замена AG-UI)' → удалено - settings.gradle.kts: то же - docs/ARCHITECTURE.md: переписан (описывал старую AGUI-centric архитектуру с AbstractAgent/SessionStore/ AgentEngine — ничего этого в коде уже нет; теперь отражает текущее состояние: :proto + :server + :standalone + LiteLlm + MCP + SqliteStores) - NATIVE-COMPATIBILITY.md: пункт 10 'agui-server KMP-готовность' → отменён (см. -); убран из 'Жёсткие блокеры' В коде не осталось ни одного обращения к AGUI. После чистки в репо больше нет ни одной зависимости от pw.binom.agui.*. Проверка: ./gradlew :server:assemble (9/9 KMP-целей) + :standalone:jvmTest 44/44 (ChatAgent 15, LlmConfig 6, McpConfig 8, McpRegistry 4, Persistence 11) — зелёные.
166 lines
9.5 KiB
Markdown
166 lines
9.5 KiB
Markdown
# Agentik — архитектура
|
|
|
|
> Полевые заметки о структуре проекта на текущий момент.
|
|
> Подробности запуска — `docs/STANDALONE.md`, чек-лист native-сборки —
|
|
> `NATIVE-COMPATIBILITY.md`, открытые вопросы — `IRC-QUESTIONS.md`.
|
|
|
|
## 1. Контекст
|
|
|
|
agentik — runtime агента. Ядро на собственном протоколе (`:proto`),
|
|
HTTP/SSE-фасад в `:server`, runtime-контейнер в `:standalone`. Раньше
|
|
фасадов было два (AG-UI + A2A); AG-UI убран как устаревший,
|
|
`a2a-server` остаётся заготовкой в `libs.versions.toml` и подключён в
|
|
`:standalone` `build.gradle.kts`, но в `Main.kt` пока не монтируется.
|
|
|
|
LLM — за интерфейсом `pw.binom.litert.LiteLlm`. Реализации:
|
|
`pw.binom.litert.openai` (HTTP/JSON поверх OpenAI-API, любая
|
|
совместимая endpoint) и `pw.binom.litert.google` (встроенный
|
|
LiteRT-LM движок для `.litertlm`/`.task` моделей).
|
|
|
|
MCP — `io.modelcontextprotocol:kotlin-sdk-client` (KMP). Подключаются
|
|
stdio + streamable-HTTP серверы, тулы оборачиваются в `LiteTool`.
|
|
|
|
## 2. Модули
|
|
|
|
```
|
|
agentik
|
|
├─ :proto KMP jvm + native. Интерфейсы Agent/Conversation, Content,
|
|
│ Message, Event, AgentEvent. @SerialName
|
|
│ дискриминаторы snake_case на проводе.
|
|
├─ :server KMP jvm + native. HTTP/SSE фасад `Route.agentikAgent(agent,
|
|
│ path = "/agentik")`. Json DTO — свой
|
|
│ Snapshot-тип, чтобы протокол оставался
|
|
│ сериализационно-чистым.
|
|
├─ :client JVM-only (пока). Ktor-клиент к фасаду `:server`,
|
|
│ подключающий удалённый агент как
|
|
│ локальный `Agent`.
|
|
└─ :standalone KMP jvm (linuxX64 — в работе). Runnable-контейнер:
|
|
SqliteStores + ChatAgent + MCP + LiteLlm,
|
|
поднимает Ktor (CIO) на AGENTIK_PORT.
|
|
```
|
|
|
|
Каталог версий — `gradle/libs.versions.toml`. Из внешних — только
|
|
`pw.binom.litert.*`, `pw.binom.a2a.*`, `app.cash.sqldelight`,
|
|
`io.ktor:*`, `io.modelcontextprotocol:kotlin-sdk-client`,
|
|
`org.jetbrains.kotlinx:*`.
|
|
|
|
## 3. `:proto` — интерфейсы
|
|
|
|
`Agent` (см. `proto/src/commonMain/.../Agent.kt`):
|
|
- `id: String`
|
|
- `createConversation(temp: Boolean): Conversation`
|
|
- `suspend getConversation(id): Conversation?`
|
|
- `suspend getConversations(offset, limit): List<Conversation>`
|
|
- `getConversations(offset = 0): Flow<Conversation>` — cold-flow paging через suspend-версию, `PAGE_SIZE = 100`.
|
|
- `events(after: Instant): Flow<AgentEvent>` — replay-free, бэкфилл через snapshot.
|
|
- `deleteConversation(id): Boolean`
|
|
|
|
`Conversation`:
|
|
- `isSupportImageInput / Output / isTemporal: Boolean`
|
|
- `updatedAt: Instant`
|
|
- `send(content: List<Content>)` — write-only, ничего не возвращает.
|
|
- `interrupt()` — отмена активного хода.
|
|
- `events(after): Flow<Event>` — live, replay-free.
|
|
- `getMessages(after, offset, limit)` + `getMessages(after): Flow<Message>` — paging.
|
|
- `rename(title)` — мутация, бампит `updatedAt`.
|
|
- `AutoCloseable` — `close()` идемпотентен.
|
|
|
|
`Content = Text(body) | Image(data, mime)`.
|
|
`Message = UserMessage | AssistantMessage | ToolCall | ToolResult | Summary | System`.
|
|
`Event = StartReasoning | StartResponse | End | AppendText | AppendImage | ToolCall | ToolResult | Error`.
|
|
`AgentEvent = Created(conversationId) | Deleted(id) | Renamed(id, title)`.
|
|
|
|
Принцип: **агент — источник истины** для транскрипта и сессий. Клиент
|
|
лишь рендерит Event-stream и кэширует историю.
|
|
|
|
## 4. `:standalone` — runtime-контейнер
|
|
|
|
Точка входа `pw.binom.agentik.standalone.MainKt`:
|
|
|
|
```
|
|
Main.kt
|
|
├─ LlmConfig.fromEnv() → LlmConfig(backend, openai, google, systemPrompt)
|
|
├─ llmConfig.createLlm() → LiteLlm (рефлексия для google)
|
|
├─ SqliteStores.open(dbPath) → ConversationStore + MessageStore + WorkingMemoryStore
|
|
├─ McpRegistry.fromConfig(...) → список NamedTool из всех MCP-серверов
|
|
├─ ChatAgent(stores, llm, llmConfig, tools)
|
|
└─ embeddedServer(CIO, port) { agentikAgent(agent, "/agentik") }
|
|
```
|
|
|
|
**`ChatAgent`** — реализация `Agent`:
|
|
- `live: Map<String, ChatConversation>` — in-memory кэш активных сессий.
|
|
- `createConversation(temp)`: для temp — только кэш; для persistent —
|
|
`upsert(conversation)` + `workingMemory.append(System(systemPrompt))`
|
|
атомарно, потом `live[id] = ChatConversation(...)`.
|
|
- `getConversation(id)`: кэш → store (cold-load). Переоткрытие восстанавливает
|
|
`LiteConversation` из `workingMemory.list(id)` (см. ниже).
|
|
- `send`/`events`/`interrupt`/`delete`/`rename` проксируются в `ChatConversation`.
|
|
|
|
**`ChatConversation`** — реализация `Conversation`:
|
|
- На каждый ход: `workingMemory.append(User)` → `liteConv.sendStreamContents(...)`
|
|
→ стрим `LiteDelta` → клиенту (AppendText / ToolCall / ToolResult).
|
|
- На завершение стрима: `messageStore.append(Assistant)` + `workingMemory.append(Assistant)`
|
|
+ `conversationStore.touch(id)`.
|
|
- Tool-loop: на `delta.toolCalls` → emit `Event.ToolCall` → execute
|
|
(`tool.invoke(argsJson)`) → emit `Event.ToolResult` →
|
|
`liteConv.addToolResult(callId, name, result)` → продолжение стрима.
|
|
- `LiteConversation` живёт **один на весь `ChatConversation`** для обоих
|
|
бэкендов: KV-cache движка сохраняется между ходами. Это контракт
|
|
litert-api, не только Google-specific.
|
|
- На `interrupt()` отменяется текущий `Job` и `liteConv.interrupt()`.
|
|
|
|
**`WorkingMemory`** — read-write контекст, который видит LLM:
|
|
- `System(text, sourceMessageId=null)` — системный промпт.
|
|
- `User/Assistant(sourceMessageId, content)` — снимки реальных сообщений.
|
|
- `compact(dropFromOrderIdx, conversationId)` — v1: DELETE rows ≥ order_idx,
|
|
summarization-вставка отложена (нужен дизайн-проработка).
|
|
|
|
**`MessageStore`** — append-only аудит. На каждый ход дописываются
|
|
`UserMessage`, `AssistantMessage`, `ToolCall`, `ToolResult`. Никаких
|
|
update/delete кроме каскада из `ConversationStore.delete`.
|
|
|
|
## 5. Слои персистентности
|
|
|
|
`SqliteStores` (jvmMain) — три интерфейса из commonMain, одна
|
|
SQLite-БД. Схема (`jvmMain/sqldelight/`):
|
|
|
|
| таблица | поля | роль |
|
|
|---|---|---|
|
|
| `conversation` | id, title, is_temporal, created_at, updated_at | карточки диалогов |
|
|
| `message` | id, conversation_id, role, payload_json, created_at | аудит-хвост |
|
|
| `working_memory` | id, conversation_id, order_idx, source_message_id, payload_json, created_at | контекст LLM |
|
|
|
|
`Content` (text/image) и per-kind payload сериализуются в
|
|
`payload_json` через `kotlinx.serialization`. Maps в каскадное удаление
|
|
(`ConversationStore.delete` — одна транзакция).
|
|
|
|
## 6. Фасады
|
|
|
|
- **`:server` (HTTP+SSE)** — основной, KMP, Ktor Route extension.
|
|
`Route.agentikAgent(agent, path = "/agentik")` монтирует весь CRUD +
|
|
live event-stream. Подробнее — `docs/STANDALONE.md`.
|
|
- **A2A (`pw.binom.a2a:server`)** — заготовка в `libs.versions.toml`,
|
|
подключён в `:standalone` для совместимости с зависимостями через
|
|
`:client`, но **не монтируется в `Main.kt`**. Если/когда понадобится —
|
|
`Route.a2aAgent(...)` (как у agui в старом дизайне).
|
|
|
|
## 7. Конфигурация (env)
|
|
|
|
| env | назначение |
|
|
|---|---|
|
|
| `AGENTIK_PORT` | порт Ktor (default `8080`) |
|
|
| `AGENTIK_DB_PATH` | путь к SQLite (default `./agentik.db`) |
|
|
| `AGENTIK_SYSTEM_PROMPT` | текст системного промпта |
|
|
| `AGENTIK_LLM_BACKEND` | `openai` (default) или `google` |
|
|
| `OPENAI_BASE_URL` / `OPENAI_API_KEY` / `OPENAI_MODEL` | для backend=openai |
|
|
| `AGENTIK_GOOGLE_MODEL_PATH` / `AGENTIK_GOOGLE_THREADS` | для backend=google |
|
|
| `AGENTIK_MCP_CONFIG` | путь к `mcp.json` в формате Claude Desktop |
|
|
|
|
## 8. Что осталось за рамками v1
|
|
|
|
- Суммаризация `WorkingMemory.compact()` (пункт `compact(dropFromOrderIdx)` пуст).
|
|
- Image input (Content.Image принимается, но `LiteContentPart.Image`
|
|
сейчас дропается в Chat-цикле с warn-логом — модель видит только текст).
|
|
- Auth на фасаде.
|
|
- A2A transport facade в `Main.kt`.
|