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>
164 lines
34 KiB
Markdown
164 lines
34 KiB
Markdown
# Ревью кодовой базы 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/локального сервера (60–90 строк каждый).
|
||
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 — их пропуск занижал расход на 30–44%), и 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 000–2 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 уже «оплачено» реальным недоучётом 30–44% расходов.
|
||
|
||
### 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; `(prompt−cached)·in + cached·cachedIn + (completion+reasoning)·out`.
|
||
- **Резервирование** (`store.go Reserve/Settle/ReleaseReservation/RefundRequest`): до вызова книжится оценка максимальной стоимости включённого маршрута (`reserveEstimate`), потолок проверяется по `committed + reserved` под advisory-lock на день (иначе burst конкурентных запросов проскочил бы потолок — деньги ложатся только после ответа); после ответа Settle атомарно снимает резерв и книжит факт по компонентам. Отдельно: возврат слота запроса без возврата денег (2xx оплачен, даже если ответ не доставлен), refund квоты grounding при пустом результате.
|
||
- **`telemetry.go` + миграции v3–v8**: одна строка `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 000–2 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, ожидание по 10–120 с), SQLite/Postgres, очереди, стриминг; продукт коммерческий, деплой должен быть дешёвым.
|
||
|
||
**Go — рекомендация.**
|
||
- ~2–2.5k строк готовой, проверенной прод-нагрузкой инфраструктуры из этого репозитория (транспорт, retry, фейловер на локальную GPU, прайсинг, ledger, телеметрия, trace) — 3–6 недель работы, которые не надо повторять и повторно отлаживать (недоучёт 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 или общих знаний и помечены «(не проверено)» — перед использованием сверить онлайн.
|