Files
admin/.tasks/llm-web-proxy.md

106 lines
10 KiB
Markdown
Raw 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.
# llm-web-proxy
## Goal
Кастомный LLM-провайдер (локальный, Windows) для pi и Claude Code: **web-чаты вместо API**. Основа — код omniroute (executor'ы + трансляторы), вытащенный и завёрнутый в минимальный сервер. Решает боль текущих комбо omniroute: quality validation режет стримы, 503 chat_admission_busy, оверкилл Next.js+sqlite+дашборд ради 2 комбо.
Заказчик: vitya. Сессия дизайна 2026-08-22 (.wiki/concepts/omniroute-combo-deepseek-web-tuning.md).
## Почему (контекст)
- pi ходит в omniroute (localhost:20128) → combo `deepseek-v4-flash-web` / `deepseek-v4-pro-web` → провайдер `deepseek-web` (chat.deepseek.com через userToken, 3 аккаунта: megan/yulia/vitya).
- Веб-чат DeepSeek **не имеет нативного function calling** — omniroute эмулирует: `tools[]` → текстовый контракт `<tool>{"name":"...","arguments":{...}}</tool>` в промпт → парсит ответ обратно в `tool_calls`.
- Существующий `llm-router-failover-proxy` (служба, порт 8787) — **не подходит**: пасsthrough для API-провайдеров (routerai/официальный DS), нет web-протокола, нет эмуляции тулов, нет `/v1/messages`.
- Диагноз вылетов в omniroute: (1) `system_message` override режет скилы pi, (2) `tool_filter_regex` режет тулы, (3) search-режим генерит DEEP_SEARCH-мусор, (4) quality validation (`isStreamingUpstreamError`) режет стрим на `type:error`, (5) 503 = capacity веб-чата. Всё, кроме 503, — следствие оверкилла; в кастомном провайдере этих слоёв просто не будет.
## Архитектура
```
pi ── /v1/chat/completions (OpenAI) ──┐
Claude Code ── /v1/messages (Anthropic) ──┴──▶ llm-web-proxy (localhost:8788)
│ 1. detect format (openai | claude)
│ 2. round-robin: combo → account (конфиг)
│ 3. web executor: auth(PoW) → session → SSE
│ 4. эмуляция тулов: <tool>-контракт → tool_calls
│ 5. ретраи/cooldown на 429/5xx/timeout
chat.deepseek.com (и др. web-чаты)
```
### Комбо (конфиг, один на провайдера — НЕ смешивать)
- `deepseek-flash-web` → deepseek-web аккаунты (megan/yulia/vitya), модель `deepseek-v4-flash-think` (search ВЫКЛЮЧЕН — он ломал тулы)
- `deepseek-pro-web` → те же аккаунты, `deepseek-v4-pro`
- позже: `qwen-web`, `gemini-web`, `kimi-web`, `grok-web` — отдельные комбо, свои executors
## Что берём из omniroute (готовое, работает)
Из docker-контейнера `omniroute` (`/app/`), образ `diegosouzapw/omniroute:latest`:
1. `open-sse/executors/deepseek-web.ts` (1147 строк) — auth userToken→accessToken, PoW-челлендж, создание/переиспользование сессий, SSE-парсинг фрагментов (THINK/ANSWER), эмуляция тулов. **Взять целиком**, удалить лишнее (persistSession уже есть).
2. `open-sse/translator/deepseekWebTools.ts` (486 строк) + `open-sse/translator/webTools.ts` — парсеры зоопарка `<tool>`-форматов. Взять целиком.
3. `open-sse/translator/request/claude-to-openai.ts` + `open-sse/translator/response/openai-to-claude.ts` — конвертация Anthropic ⇄ OpenAI (для Claude Code).
4. Зависимости: `@omniroute/open-sse` имеет свои импорты — вытаскивать вместе с нужными модулями (`streamHelpers`, `tokenAccounting` и т.п.) либо скопировать файлы в свой пакет и починить импорты. **Решение исполнителя**, но НЕ переписывать логику.
## Что пишем сами (мало)
- `src/server.js` — HTTP: `POST /v1/chat/completions` (OpenAI) + `POST /v1/messages` (Anthropic, с SSE keepalive `event: ping` — Claude Code рвёт соединение при долгом промпте) + `GET /health`.
- `src/router.js` — round-robin по аккаунтам + cooldown/circuit (паттерн llm-router-proxy `src/router.js` — уже написан, работает).
- `config/web-providers.json` — комбо → аккаунты → модель. Ключи НЕ в конфиге: userToken'ы из `~/.pi/agent/auth.json` или отдельного файла (формат на усмотрение, gitignore).
- **Логирование — первоклассное требование** (см. ниже).
## Логирование (обязательно, для диагностики агентом)
Файл-лог, **одна строка = одно событие**, grep-парсится: `[TS] [TAG] event=... key=value`.
| Событие | Что пишем | Зачем |
|---|---|---|
| `req` | alias, аккаунт, модель, msgs, tools, stream | какой аккаунт крутит round-robin |
| `tool-prompt` | tools=N, bytes контракта | контракт раздулся → модель теряет формат |
| `raw-reply` | **первые ~500 символов сырого ответа модели** | видно `<tool>`-блоки, DEEP_SEARCH-мусор, reasoning, пустоту — корень 90% проблем |
| `parse` | calls=N, имена тулов, аргументы валидны | парсер нашёл/потерял вызовы, `arguments:{}` |
| `upstream` | статус, код (401/429/503), PoW, таймаут, ретрай N | лимиты, баны |
| `session` | created/reused/deleted, persist | эффективность persistSession |
| `stream` | complete/truncated/error, байты, длительность | обрыв стрима |
| `failover` | упавший апстрим → куда ушёл | round-robin честен |
Правила: тела user-сообщений НЕ логируем (только размеры); `raw-reply` обрезанный; ротация (`.1`, `.2`); пишем в stdout + файл (winsw).
## Требования
1. **OpenAI-совместимый** `/v1/chat/completions` — pi подключается одним провайдером в `~/.pi/agent/models.json`.
2. **Anthropic-совместимый** `/v1/messages` — Claude Code подключается через `ANTHROPIC_BASE_URL=http://127.0.0.1:8788` (+ свой API-ключ, любой).
3. **Tool-calls работают** в обоих форматах: несколько последовательных инструмент-раундов (как pi вызывает тулы; Claude Code — `tool_use` блоки).
4. **thinking/reasoning** deepseek не ломается (reasoning_content для pi, `thinking`-блоки для Claude Code).
5. **Failover**: 429/5xx/таймаут/сеть → следующий аккаунт в комбо; cooldown + circuit-breaker на упавший аккаунт.
6. **persistSession: true** — переиспользование сессии (меньше 503 chat_admission_busy).
7. **БЕЗ quality validation** — этого слоя в omniroute нет (это и была боль).
8. **Логирование** — по спеке выше; агент должен по логу восстанавливать цепочку запроса.
9. **Конфиг** — JSON: комбо → упорядоченные аккаунты (baseUrl/executor, userToken-ref, model, timeout), router-параметры.
10. **Тесты**: TDD — round-robin, failover, парсинг `<tool>` (валид/зоопарк форматов), claude⇄openai конвертация, стриминг.
## Acceptance
- [ ] pi → `/v1/chat/completions` (model=deepseek-flash-web): стриминг, tool-calls в несколько раундов, reasoning не ломается.
- [ ] Claude Code → `/v1/messages`: подключается, тулы (`tool_use`) работают, SSE keepalive не даёт оборвать соединение.
- [ ] Failover доказан тестом: аккаунт недоступен (503/таймаут) → запрос обслуживает следующий; cooldown работает.
- [ ] Лог: по `raw-reply` + `parse` видно каждый tool-call модели; тела пользователя отсутствуют.
- [ ] Инструкция pi-интеграции (models.json) + Claude Code (env) + запуск/остановка (winsw-сервис, паттерн llm-router).
- [ ] Деплой: локально, Windows, winsw-сервис `llm-web-proxy`, живой smoke.
## Деплой
Локально на Windows: Node-процесс + winsw-сервис (паттерн agents-task-runner / llm-router уже обкатан). Ключи не уходят с машины. Порт 8788 (не конфликтует с llm-router :8787).
## Обязательные скилы — вызвать до начала работы
- invoke `tdd-criteria` — до написания кода
- invoke `using-tasks` — статус задачи
- invoke `project-discipline` — коммиты/пуши
- invoke `using-wiki` после закрытия — обновить концепт
**TDD:** да — failover/приоритет/парсинг/конвертация это чистые юнит-кейсы; тест-харнесс обязателен (мок-апстрим).
**Разрешения:** интерны: да | автопуш: да
**Weight:** needs-claude
**Notify:** OpeItcLoc03/admin