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

10 KiB
Raw Blame History

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