standalone: persist turn errors so polling clients see them
Event.Error уже эмитился в live-SSE, но если клиент подключился
после провала хода (или опрашивает историю через getMessages
вместо SSE), он видел только user-сообщение без следа, что ход
провалился. Это и нужно было поправить.
* :proto
- Message.Error(id, message, code?, date) — терминальная
персистентная проекция Event.Error. Audit-only; в live-стриме
по-прежнему приходит Event.Error.
* :standalone
- MessageRecord.Error с тем же контрактом.
- SqliteMessageStore: kind "error", JSON-payload {message, code};
encoding-ошибки round-trip покрыты тестом (с code и без).
- ChatConversation.failTurn(message, code?): пишет MessageRecord.Error
в audit (кроме temp-диалогов), затем эмитит Event.Error + Event.End.
Все три exit-точки из runTurn (пустой parts, init-catch,
stream-catch) теперь через failTurn.
- ChatConversation: при ошибке стрима живой LiteConversation
сбрасывается — следующий send пересоберёт его из working_memory.
Раньше оставляли битую инстанцию вопреки KDoc класса.
- toProto: маппит MessageRecord.Error → Message.Error.
* docs
- STANDALONE.md: добавлен Message.Error в таблицу dual-log + абзац
про персист ошибок (audit/working_memory, сброс liteConv).
- ARCHITECTURE.md: Message.Error в сигнатуре Message, failTurn
в ChatConversation, Message.Error в audit log.
- Поправлен пример describe() в доке тулов (реальный формат —
OpenAI-style function wrapper, а не голый JSON Schema).
Тесты:
- ChatAgentTest: LLM failure → Event.Error + Event.End + Error
record в audit + backfill через getMessages.
- PersistenceTest: MessageRecord.Error round-trip (с code и без).
- :standalone jvmTest 70 (было 69).
E2E: неверный model id (HTTP 400) → polling GET /messages теперь
возвращает user_message + error (без assistant_message).
This commit is contained in:
@@ -66,7 +66,7 @@ agentik
|
||||
- `AutoCloseable` — `close()` идемпотентен.
|
||||
|
||||
`Content = Text(body) | Image(data, mime)`.
|
||||
`Message = UserMessage | AssistantMessage | ToolCall | ToolResult | Summary | System`.
|
||||
`Message = UserMessage | AssistantMessage | ToolCall | ToolResult | Error`.
|
||||
`Event = StartReasoning | StartResponse | End | AppendText | AppendImage | ToolCall | ToolResult | Error`.
|
||||
`AgentEvent = Created(conversationId) | Deleted(id) | Renamed(id, title)`.
|
||||
|
||||
@@ -104,6 +104,9 @@ Main.kt
|
||||
- Tool-loop: на `delta.toolCalls` → emit `Event.ToolCall` → execute
|
||||
(`tool.invoke(argsJson)`) → emit `Event.ToolResult` →
|
||||
`liteConv.addToolResult(callId, name, result)` → продолжение стрима.
|
||||
- На ошибку хода (init/стрим LLM): `failTurn` → `messageStore.append(Error)`
|
||||
(audit; в working_memory не пишется) + `Event.Error` + `Event.End`, живой
|
||||
`LiteConversation` сбрасывается и пересобирается на следующем `send`.
|
||||
- `LiteConversation` живёт **один на весь `ChatConversation`** для обоих
|
||||
бэкендов: KV-cache движка сохраняется между ходами. Это контракт
|
||||
litert-api, не только Google-specific.
|
||||
@@ -116,7 +119,7 @@ Main.kt
|
||||
summarization-вставка отложена (нужен дизайн-проработка).
|
||||
|
||||
**`MessageStore`** — append-only аудит. На каждый ход дописываются
|
||||
`UserMessage`, `AssistantMessage`, `ToolCall`, `ToolResult`. Никаких
|
||||
`UserMessage`, `AssistantMessage`, `ToolCall`, `ToolResult`, `Error`. Никаких
|
||||
update/delete кроме каскада из `ConversationStore.delete`.
|
||||
|
||||
## 5. Слои персистентности
|
||||
|
||||
+4
-2
@@ -166,13 +166,15 @@ fun main() {
|
||||
|
||||
| таблица | назначение | мутации |
|
||||
|---|---|---|
|
||||
| `message` | append-only audit log. Все user/assistant/tool-call/tool-result сообщения. Никогда не редактируется (кроме каскадного `DELETE` при удалении диалога). | только `INSERT` |
|
||||
| `message` | append-only audit log. Все user/assistant/tool-call/tool-result/error сообщения. Никогда не редактируется (кроме каскадного `DELETE` при удалении диалога). | только `INSERT` |
|
||||
| `working_memory` | mutable LLM-контекст. System-prompt + текущая история + (в v2) суммаризации. | `INSERT`, `compact(dropFromIdx, summary)` |
|
||||
|
||||
Маппинг `:proto.Message ↔ MessageRecord` живёт в `ChatConversation.kt` (`toProto`/`toStorage`) — сами `MessageRecord` намеренно НЕ зависят от `:proto`, чтобы можно было сменить транспорт без миграции таблиц.
|
||||
|
||||
Подробный контракт — в комментариях к `MessageRecord.kt` и `WorkingMemoryEntry.kt`.
|
||||
|
||||
**Ошибки хода персистятся.** Если ход провалился (LLM/движок недоступны — например, HTTP 400 от endpoint'а), `ChatConversation.failTurn` пишет терминальную запись `MessageRecord.Error` в audit и эмитит `Event.Error` + `Event.End`. Благодаря audit-записи ошибка видна не только подписчику live-SSE, но и клиенту, который делает backfill через `getMessages` (polling/переподключение): в истории будет `Message.Error(id, message, code?)`, а для этого user-сообщения не будет `AssistantMessage`. При ошибке стрима живой `LiteConversation` сбрасывается — следующий `send` пересоберёт его из `working_memory`. В working_memory `Error` не пишется (модель не должна видеть ошибки прошлых ходов).
|
||||
|
||||
### `MessageStore`
|
||||
|
||||
```kotlin
|
||||
@@ -259,7 +261,7 @@ suspend fun touch(id: String, now: Instant)
|
||||
// 1) In-agent tool (нативный LiteTool):
|
||||
val echoTool = object : LiteTool {
|
||||
override fun describe(): String =
|
||||
"""{"type":"object","properties":{"x":{"type":"string"}},"required":["x"]}"""
|
||||
"""{"type":"function","function":{"name":"echo","description":"Echo a string","parameters":{"type":"object","properties":{"x":{"type":"string"}},"required":["x"]}}}"""
|
||||
override fun invoke(args: String): String = "echoed: $args"
|
||||
}
|
||||
val tools = listOf(NamedTool("echo", echoTool))
|
||||
|
||||
Reference in New Issue
Block a user