textmachine/docs/research/10-vojo-ai-bot-review.md
Claude (backend session) 9bb00d49f2 Initial commit: documentation, eval polygon, backend step 0 verdict
TextMachine project repository (AI translation of literary books).
Includes: v2 architecture decisions, MVP plan, research 01-12,
polygon experiments 01-03, backend-session revalidation verdict
(03-implementation-notes.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 06:50:59 +03:00

164 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Ревью кодовой базы vojo/apps/ai-bot (Go) и оценка переиспользования для TextMachine
Дата: 2026-07-04. Источник: локальный код `/home/ubuntu/projects/vojo/apps/ai-bot` (интернет не использовался; все утверждения о внешних API — из комментариев кода и помечены при необходимости «(не проверено)»).
## TL;DR
1. **Кодовая база зрелая и на удивление хорошо спроектирована для «бота»**: ~10 600 строк Go (из них ~3 850 — тесты, 36%), 125+ тест-функций, минимум зависимостей (pgx, goldmark, bluemonday, yaml — всё), Go 1.25, статическая CGO-free сборка в distroless.
2. **Ядро LLM-инфраструктуры провайдеро-нейтрально и переносимо почти как есть**: интерфейс `LLMClient` + нейтральные типы (`llm.go`, 83 строки), общий OpenAI-совместимый HTTP-транспорт с retry/backoff и самолечением 400-ошибок (`httpllm.go`), тонкие адаптеры xAI/Gemini/локального сервера (6090 строк каждый).
3. **Failover «локальная GPU → облако» уже написан и оттестирован** (`failover.go`): circuit breaker + активные health-пробы `GET /models`, анти-flapping, «терминальный 4xx не маскируется облаком». Это ровно сценарий TextMachine (8GB VRAM дома + облачные API).
4. **Учёт денег — самая ценная часть**: таблица цен per-model (`pricing.go`), `CostBreakdown` по компонентам, биллинг по фактическому usage API (включая reasoning-токены, которые xAI считает ПОВЕРХ completion_tokens — их пропуск занижал расход на 3044%), и ledger в Postgres с **резервированием бюджета до вызова и settle после** (TOCTOU-защита потолка расходов через advisory lock).
5. **Телеметрия production-уровня**: `request_log` (43 колонки, 8 идемпотентных миграций) — маршрут, $/компонент, латентность по стадиям, degrade-причины, фидбек пользователя; trace_id (W3C/OTel-форма) через `context` + обёртка slog-хендлера. Пишется асинхронно, никогда не роняет ответ.
6. **Паттерн «чистое ядро решений + оффлайн-реплей»** (`internal/routedecide` + `cmd/routereval`): роутинг вынесен в пакет без I/O, и golden-set гоняется через ТОТ ЖЕ код, что и прод, со свипом порогов. Прямо переносится на QA-оценку перевода в TextMachine.
7. **Чего нет**: стриминга (везде `stream:false`), нативного Anthropic-адаптера, tool-calling (кроме web_search), мультимодальности, batch API, очередей/durable jobs, пакетной структуры (почти всё в `package main`). Это и есть основной объём дописывания.
8. **Рекомендация по языку — Go.** Реально переиспользуемых ~2 0002 500 строк проверенного кода + идеальное соответствие задаче (тысячи параллельных HTTP-вызовов, один статический бинарь, дешёвая конкурентность). C++/userver не даёт ни переиспользования, ни скорости разработки Claude-ом; Python/TS проигрывают на долгоживущем конкурентном оркестраторе как продукте.
---
## 1. Что это за система
`ai-bot` — Matrix-бот (Synapse appservice), отвечающий через xAI Grok, с опциональным каскадом: двухслойный роутер (regex + дешёвый Gemini-классификатор) выбирает маршрут (`trivial_direct` / `grok_direct` / `web_then_grok` / `reason_then_grok` / `project_then_grok`), любой сбой любого слоя деградирует к прямому вызову Grok («никогда молчание»). Есть web-grounding (два провайдера: xAI web_search и Gemini native google_search с верификацией цитат), локальный LLM-бэкенд (ollama/llama.cpp за SSH-туннелем) как бесплатная «первая нога» с прозрачным фейловером в облако, жёсткий дневной потолок расходов в USD и полная телеметрия.
Объём: ~6 760 строк не-тестового кода. Крупнейшие файлы: `bot.go` (1 273 — Matrix-логика), `config.go` (695), `store.go` (620), `cascade.go` (562).
## 2. (a) Архитектура: адаптеры, интерфейс, роутинг/каскад/фейловер
### 2.1 Провайдеро-нейтральный шов (`llm.go`)
Весь бизнес-код зависит от одного интерфейса и нейтральных типов:
```go
type LLMClient interface {
Complete(ctx context.Context, req LLMRequest) (*LLMResponse, error)
}
```
- `LLMRequest`: Model, Messages (role/content — только текст), MaxTokens, Temperature, Tools, ReasoningEffort, ConvID (хинт prompt-кэша, уходит заголовком), JSONOnly (`response_format: json_object`).
- `LLMResponse`: Text, Usage, **Model — модель, которая ФАКТИЧЕСКИ ответила** (может отличаться от запрошенной при фейловере; биллинг и телеметрия следуют за ответившей, иначе бесплатный локальный ответ книжится по облачной цене), ProviderRequestID.
- `Usage`: PromptTokens, CachedTokens (подмножество prompt), CompletionTokens, **ReasoningTokens**с явно задокументированной семантикой: у xAI reasoning-токены аддитивны к completion_tokens и биллятся по output-ставке; у провайдеров с subset-семантикой (ollama, OpenAI-спека) адаптер обязан оставлять 0, чтобы не задвоить биллинг. Это тонкое место, которое в vojo уже «оплачено» реальным недоучётом 3044% расходов.
### 2.2 Общий транспорт (`httpllm.go`, 299 строк)
Один HTTP-клиент для всех OpenAI-совместимых провайдеров: сборка `/chat/completions`, классификация ошибок (429/5xx/сетевые = retryable с экспоненциальным backoff 0.5s→8s + jitter; прочие 4xx = терминальные), per-attempt дедлайн 60s внутри общего бюджета запроса, типизированная `httpStatusError` (фейловер классифицирует ошибки структурно, не по тексту). Умные детали: 2xx с пустым контентом — успех (вызов оплачен, деньги книжатся); самолечение `reasoning_effort`-несовместимости — модель, вернувшая 400 на параметр, запоминается, параметр срезается и запрос повторяется один раз, WARN один раз.
### 2.3 Адаптеры
- `provider_xai.go` (66 строк) — маппинг нейтральных типов на wire, заголовок `x-grok-conv-id`.
- `provider_gemini.go` (240 строк) — два лица: OpenAI-compat `Complete` + нативный v1beta `generateContent` c `google_search` и **verify-gate по цитатам** (нет groundingChunks ⇒ ответ не заземлён ⇒ ошибка ⇒ деградация). Ключ — в заголовке `x-goog-api-key`, а не в query (иначе `*url.Error` утёк бы секретом в лог), запрет редиректов как анти-exfil.
- `provider_local.go` (91 строка) — адаптер любого OpenAI-совместимого локального сервера (ollama/llama.cpp/vLLM/LM Studio): своя модель (не имена каскада), своя температура (request-температура перебивает Modelfile), свой max_tokens (у ollama лимит покрывает thinking+ответ вместе, в отличие от xAI), пустой ключ = без Authorization.
### 2.4 Роутинг и каскад
- **Layer-0** — бесплатные regex-эвристики RU+EN (`internal/routedecide`, чистый пакет без I/O): freshness-слова → web, приветствия/арифметика → trivial, «lookup-intent» — мягкий хинт.
- **Layer-1** — Gemini-классификатор с эпистемологическим промптом (не «тема», а «навредит ли ответ по памяти»): JSON-вердикт `needs_web/verifiable/entity_obscure/time_sensitive/trivial/about_project/search_query/confidence`, суб-дедлайн 4s, любой сбой → вердикт Layer-0.
- **Combine** (чистая функция с параметризуемыми порогами) сводит оба слоя; порядок веток даёт атрибуцию `web_decided_by` для аналитики. Классификатору не доверяют слепо: trivial требует согласия обоих слоёв, needs_web — порога уверенности и «verifiable».
- **Каскад** (`cascade.go`): диспетчеризация по маршруту, каждая ветка при ошибке деградирует к `genGrokDirect` с «честными» хеджами (staleness-каведж для recency-промаха, abstain-инструкция для промаха проверяемого факта — чтобы модель не выдала уверенную галлюцинацию). Частично исполненный каскад книжит фактически потраченное.
- **Фейловер** (`failover.go`): декоратор над `LLMClient` — локальная нога под своим таймаутом, на transport/5xx/timeout/429 → облако в том же запросе + trip брейкера; восстановление только по пробам (2 подряд после request-trip, анти-flapping); терминальный 4xx локальной ноги (неверный тег модели) падает громко, облаком не маскируется; одноразовый WARN «локальный бэкенд ни разу не поднялся с boot».
Оценка: это учебниково-правильная композиция декораторов над одним интерфейсом. Каскад, правда, «зашит» под конкретные 5 маршрутов чат-бота — для TextMachine логика маршрутов (переводчик→редактор→судья) будет иной, но каркас «маршрут → исполнение → деградация → учёт» переносится.
## 3. (b) Телеметрия и учёт токенов/стоимости — готовность к переиспользованию: высокая
- **`pricing.go`**: `ModelPrice{InputPerM, CachedPerM, OutputPerM}` в `Config.Prices` (паттерн LiteLLM), `priceFor(model)` с fallback на дефолтную модель (никогда $0 — «$0 ослепил бы потолок»); `CostBreakdown{Token, Grounding, WebTool, Router, GroundingFee}` с вычисляемым `Total()`.
- **`computeUSD`** (bot.go): цена по фактическому usage API; `(promptcached)·in + cached·cachedIn + (completion+reasoning)·out`.
- **Резервирование** (`store.go Reserve/Settle/ReleaseReservation/RefundRequest`): до вызова книжится оценка максимальной стоимости включённого маршрута (`reserveEstimate`), потолок проверяется по `committed + reserved` под advisory-lock на день (иначе burst конкурентных запросов проскочил бы потолок — деньги ложатся только после ответа); после ответа Settle атомарно снимает резерв и книжит факт по компонентам. Отдельно: возврат слота запроса без возврата денег (2xx оплачен, даже если ответ не доставлен), refund квоты grounding при пустом результате.
- **`telemetry.go` + миграции v3v8**: одна строка `request_log` на запрос — маршрут, источник решения, confidence, JSONB `models`/`stage_ms`, токены (включая reasoning), $ по 5 компонентам, latency, degrade-причина (стабильные токены для GROUP BY), сигналы классификатора, цитаты, `prompt_version` (хэш ВСЕЙ поведенческой поверхности промптов — системного, классификатора, хеджей), `reply_event_id` + эмодзи-фидбек пользователя как сигнал качества. Запись асинхронна (`safego` с recover), отказ пишет WARN и никогда не роняет ответ; text-поля — только под отдельным флагом (privacy by default); ретеншн-трим раз в 200 записей.
- **`trace.go` + `logging.go`**: trace_id — 16 случайных байт в hex (форма W3C/OTel), кладётся в `context` один раз на запрос; `contextHandler` — обёртка slog, автоматически штампующая trace_id на каждую строку `*Context`-логов (с корректным re-wrap в WithAttrs/WithGroup — типовая ошибка обёрток тут учтена). Тела LLM-обменов логируются только по allowlist пользователей И на DEBUG, без URL/заголовков (ключ не утечёт).
Для TextMachine это переиспользуется процентов на 80: понадобится (1) добавить в `Usage`/`ModelPrice` **стоимость записи в кэш** (у Anthropic cache write дороже input — 1.25x/2x в зависимости от TTL (не проверено, сверить с актуальным прайсом)), (2) заменить ось учёта «пользователь/день» на «проект/книга/глава/роль агента», (3) добавить в `request_log` аналоги: chapter_id, agent_role, счёт итераций редактуры.
## 4. (c) Хранилище (`store.go`)
Postgres через `pgx/v5` (pool MaxConns=4), таймаут 10s на любую операцию. Схема — 8 версионированных идемпотентных миграций под advisory-lock (`schema_version` + `CREATE TABLE/ADD COLUMN IF NOT EXISTS`), самонакатываются при старте. Таблицы:
- `processed_txn` / `processed_event` — дедуп с LRU-обрезкой (5k/20k записей): идемпотентность «at most once» через `INSERT … ON CONFLICT DO NOTHING` + `RowsAffected`.
- `spend(date, mxid, requests, usd, router_usd, grounding_usd, webtool_usd, reserved_usd)` — дневной ledger с резервированием.
- `request_log` — аналитика (43 колонки, см. выше), индексы по ts и reply_event_id.
- `grounding_count` — дневная квота grounding: check-and-increment одним стейтментом (`INSERT … ON CONFLICT … WHERE n < cap RETURNING`), гонки невозможны.
- `warned_encrypted` — Matrix-специфика.
Контента сообщений в БД нет (by design). Качество SQL высокое: атомарные однооператорные апдейты с `GREATEST(0, …)` против отрицательных значений, комментарии объясняют каждое решение о блокировках. Для TextMachine схема почти вся не подходит по смыслу (нужны проекты/главы/глоссарий/память), но **паттерны** (миграции, advisory-lock, атомарные квоты, reserve/settle) — прямо в перенос.
## 5. (d) Качество кода и тесты
**Качество — заметно выше среднего.** Плотнейшие пояснительные комментарии с обоснованием каждого решения и отсылками к инцидентам («вот из-за этого была многоминутная тишина»), fail-fast валидация конфига (несовместимые флаги отказывают в старте), секреты через `*_FILE`, никакой lock не держится через сетевые вызовы, панк-recovery вокруг фоновых горутин, privacy-by-default в логах и телеметрии, prompt-injection-споттинг (`<DATA>`-маркеры вокруг веб-дайджеста, OWASP LLM01). Зависимости — 5 прямых. Минусы: почти всё в `package main` (кроме `internal/routedecide`) — как библиотека не импортируется, только копируется; конфиг — один «толстый» структ на 60+ полей; нет CI-конфига в каталоге; `rand.Intn` для jitter (без seed — в Go 1.20+ ок).
**Тесты: 17 файлов, ~3 850 строк, 125+ тест-функций.** `cascade_test.go` — 29 тестов (деградации, частичный биллинг, хеджи), `store_test.go` — 19 (включая конкурентную гарантию per-user cap и durability через рестарт; требуют Postgres через `AI_BOT_TEST_DATABASE_URL`, иначе skip), `failover_test.go` — 12 (брейкер, терминальные 4xx, пустой ответ локальной ноги), `config_test.go` — 15, транспорт — через `httptest` с реальными HTTP-раундтрипами, LLM — через `fakeLLM`-дублёры интерфейса. README заявляет `go vet`/`gofmt` clean; в текущем окружении Go toolchain не установлен, прогнать `go test` не удалось (не проверено), но структура тестов и golden-set-харнесс говорят о рабочей дисциплине.
Отдельно ценен **`cmd/routereval`** — оффлайн-реплей golden-набора через прод-функции решения с confusion matrix и метриками (misroute, false-web, trivial-leak, «lie-metric»), со свипом порогов флагами. Это готовый шаблон для eval-гейта качества перевода.
## 6. (e) Что конкретно переиспользовать, что переписать
### Брать почти как есть (адаптация < 20%)
| Артефакт | Строк | Что менять |
|---|---|---|
| `llm.go` — типы + `LLMClient` | 83 | + стриминг (интерфейс `CompleteStream` или каналы), + multi-part content, + TopP/StopSequences, + системные блоки |
| `httpllm.go` — транспорт+retry | 299 | + поддержка SSE-стриминга; вынести `max_tokens``max_completion_tokens` маппинг per-provider (новые OpenAI-модели требуют второй вариант (не проверено)) |
| `failover.go` — локально-облачный декоратор | 234 | почти ничего; probe URL и пороги — уже конфиг |
| `provider_local.go` | 91 | ничего существенного |
| `pricing.go` + `computeUSD` | ~70 | + CacheWritePerM, + тиры по длине контекста (Gemini >200k (не проверено)) |
| `trace.go` + `logging.go` | 180 | ничего; это идиома userver, знакомая владельцу |
| `store.go`: механизм миграций, reserve/settle, атомарные квоты | ~200 | схему таблиц — под домен TextMachine |
| `telemetry.go` — паттерн async request_log | 154 | колонки под пайплайн перевода |
| Паттерн `routedecide` + `routereval` | ~470 | сам код не нужен, нужен паттерн «чистое ядро + golden-реплей» |
| `config.go` — env-парсинг, fail-fast, `*_FILE`-секреты, Summary с redaction | ~400 | поля под TextMachine |
Итого прямо переносимого проверенного кода: **~2 0002 500 строк** — это и есть весь «скучный» инфраструктурный риск LLM-бэкенда (retry, деньги, трейсинг, фейловер), уже отлаженный на проде.
### Писать заново
1. **Пакетная структура**: разнести `package main` на `pkg/llm`, `pkg/pricing`, `pkg/ledger`, `pkg/telemetry` и т.д.
2. **Anthropic-адаптер (нативный Messages API)**: система блоков, `cache_control` (эксплицитный кэш — критично для экономики TextMachine: system-промпт + глоссарий + резюме предыдущих глав кэшируются), tool_use, thinking-блоки. OpenAI-compat слоя vojo недостаточно.
3. **Стриминг** — в vojo отсутствует полностью; для IDE-фронтенда обязателен.
4. **Batch API** (Anthropic/OpenAI 50% стоимости (не проверено, сверить)) — для оффлайн-перевода глав это главный рычаг себестоимости; в vojo концепции нет.
5. **Оркестратор пайплайна**: у vojo модель конкурентности — «одна горутина на комнату + in-memory буферы + single-flight»; рестарт теряет in-flight работу. TextMachine нужны **durable jobs** (глава = job, стадии = переводчик→редактор→судья, resume после падения, ретраи, приоритеты) — очередь поверх Postgres (напр. river) или своя таблица jobs; это новый код.
6. **Домен**: память/глоссарий/консистентность имён, сегментация текста, диффы редактур, чекпоинты книги — ничего этого в vojo нет и быть не могло.
7. **Хранилище контента**: vojo принципиально не хранит текст; TextMachine — наоборот, текст и есть данные (главы, варианты, память). SQLite (modernc.org/sqlite, CGO-free) для локального режима + тот же SQL-слой на Postgres для продакшена.
8. **18+/цензура**: маршрутизация «этот фрагмент не пройдёт у провайдера X → локальная abliterated-модель / другой провайдер» — в vojo есть только зачаток (abliterated Qwen3 как локальная модель и фейловер), политику придётся строить самим.
## 7. (f) Выбор языка бэкенда: Go vs C++/userver vs Rust vs Python/TS
Вводные: код пишет в основном Claude; владелец лучше всего знает C++/userver; профиль нагрузки — I/O-bound оркестратор (сотни-тысячи параллельных HTTP-вызовов к LLM, ожидание по 10120 с), SQLite/Postgres, очереди, стриминг; продукт коммерческий, деплой должен быть дешёвым.
**Go — рекомендация.**
- ~22.5k строк готовой, проверенной прод-нагрузкой инфраструктуры из этого репозитория (транспорт, retry, фейловер на локальную GPU, прайсинг, ledger, телеметрия, trace) — 36 недель работы, которые не надо повторять и повторно отлаживать (недоучёт reasoning-токенов, TOCTOU потолка, flapping брейкера — всё уже поймано).
- Goroutines + context — идеальная модель для «тысяча параллельных вызовов с дедлайнами и отменой»; в кодовой базе видно, что она уже освоена правильно (никаких lock-через-I/O, single-flight, safego).
- Claude генерирует Go стабильно и дёшево в ревью: язык маленький, ошибки видны компилятором, нет UB, рефакторинги безопасны. Один статический бинарь → деплой и себестоимость инфраструктуры минимальны.
- SQLite без CGO (modernc), pgx, SSE — всё есть; официальные SDK Anthropic/OpenAI для Go существуют (anthropic-sdk-go (не проверено, актуальность)), а собственный тонкий транспорт уже написан.
**C++/userver — не рекомендуется**, несмотря на экспертизу владельца.
- Ноль переиспользования этой базы; экосистема LLM-клиентов/JSON/SDK несравнимо тоньше.
- Главный аргумент «владелец знает язык» ослаблен собственной вводной: код пишет Claude. Для Claude C++ — самый дорогой язык поддержки: UB, время сборки, управление зависимостями (userver и его окружение — тяжёлая сборка), а корпус userver-кода в обучающих данных мал — качество генерации будет заметно ниже, чем на Go/Python/TS.
- Производительность C++ здесь не монетизируется: узкое место — ожидание LLM API, а не CPU. Знание userver останется полезным на уровне ревью идиом (trace-id через контекст в vojo прямо назван «the userver idiom» — концепции совпадают).
**Rust — второй по адекватности, но минусы перевешивают**: безопасность и скорость не нужны в этом профиле, компиляция и borrow-checker замедляют итерации (а итераций в исследовательском продукте будет много), переиспользования нет. Оправдан был бы, если бы планировался embedded-движок или WASM-ядро.
**Python — оставить для оффлайн-евалов и экспериментов с промптами** (богатейшая экосистема, ноутбуки), но не для ядра продукта: долгоживущий конкурентный сервис с очередями на asyncio + деплой с зависимостями + слабее типизация = дороже сопровождение и хуже маржа на инфраструктуре.
**TypeScript — единственная реальная альтернатива Go**: первоклассные SDK всех провайдеров, один язык с будущим IDE-фронтендом, event-loop нормально держит I/O-bound нагрузку. Против: нет переиспользования этой Go-базы, слабее модель отмены/дедлайнов (AbortController против context), рантайм тяжелее, у монолитных бэкендов на Node дисциплина типов и конкурентности требует больше ревью. Если бы Go-базы не существовало, выбор между TS и Go был бы спорным; с ней — нет.
**Итог: Go для бэкенда-оркестратора; Python допустим как вспомогательный слой евалов; TypeScript — для фронтенд-IDE позже.**
## Выводы для TextMachine
1. **Стартовать бэкенд на Go, выпилив из vojo/ai-bot ядро в пакеты**: `llm.go`+`httpllm.go`+адаптеры+`failover.go``pkg/llm`; `pricing.go`+`computeUSD`+reserve/settle → `pkg/ledger`; `trace.go`+`logging.go``pkg/obs`; механизм миграций → `pkg/store`. Это снимает главный инфраструктурный риск в первые же дни.
2. **Первым новым кодом писать Anthropic-нативный адаптер** с `cache_control` и thinking-блоками — экономика художественного перевода живёт на кэшировании длинного контекста (глоссарий, память книги, стиль-гайд) и на дешёвых моделях с редкой эскалацией; каркас каскада/деградации из `cascade.go` — образец.
3. **Скопировать дисциплину денег целиком**: биллинг по usage из ответа API, per-model таблица цен, резервирование до вызова, потолок на проект/день, `request_log` per-агент/per-глава. Для продукта с маржой это не «телеметрия», а ядро юнит-экономики; у vojo это уже добито до цента (сверка с `cost_in_usd_ticks`).
4. **Повторить паттерн `routedecide`/`routereval` для качества перевода**: чистое ядро решений (эскалация к дорогой модели, вердикты судьи) + golden-set реплей со свипом порогов — тот же «offline-eval gate перед включением слоя», который vojo применяет к роутеру, TextMachine применит к качеству перевода.
5. **Фейловер локальной GPU уже готов**: `provider_local.go` + `failover.go` покрывают сценарий «8GB VRAM дома обслуживает дешёвые роли (черновой перевод, классификация), облако — фейловер и дорогие роли»; семантика max_tokens (thinking внутри лимита у ollama) и температурных override уже учтена. Для 18+ контента локальная нога дополнительно снимает риск цензуры провайдера.
6. **Не тащить**: Matrix-слой, каскад чат-маршрутов, схему `spend`/`request_log` буквально, in-memory модель конкурентности. Оркестрацию глав строить как durable jobs в Postgres/SQLite с resume — этого в vojo нет, и это основная новая разработка вместе с банком памяти/глоссарием.
7. **Учесть две ловушки биллинга из опыта vojo**: (а) reasoning-токены — аддитивные у xAI, subset у ollama/OpenAI-спеки, у Anthropic thinking внутри output (не проверено) — семантику фиксировать per-adapter; (б) неизвестная модель никогда не должна стоить $0 (fallback на дефолтную цену).
## Источники
Все — локальные файлы, прочитаны 2026-07-04:
- `/home/ubuntu/projects/vojo/apps/ai-bot/README.md` — обзор, статус верификации, флаги.
- `/home/ubuntu/projects/vojo/apps/ai-bot/llm.go`, `httpllm.go`, `provider_xai.go`, `provider_gemini.go`, `provider_local.go` — интерфейс и адаптеры.
- `/home/ubuntu/projects/vojo/apps/ai-bot/router.go`, `cascade.go`, `failover.go`, `web.go`, `internal/routedecide/routedecide.go`, `cmd/routereval/main.go` — роутинг/каскад/фейловер/eval.
- `/home/ubuntu/projects/vojo/apps/ai-bot/pricing.go`, `telemetry.go`, `trace.go`, `logging.go`, `store.go`, `config.go`, `main.go`, `bot.go` (фрагменты) — деньги, телеметрия, хранилище, конфиг.
- Тесты: `cascade_test.go`, `store_test.go`, `failover_test.go`, `httpllm_test.go`, `config_test.go` и др. (17 файлов, ~3 849 строк).
- `go.mod` — Go 1.25.0; зависимости: pgx/v5 v5.9.2, bluemonday, goldmark, x/net, yaml.v3.
Утверждения о внешних API (цены Anthropic cache write, batch 50%, `max_completion_tokens`, actual-модели) в этом документе взяты из комментариев кода vojo или общих знаний и помечены «(не проверено)» — перед использованием сверить онлайн.