11 KiB
OmniRoute: настройка web-комбо под агентскую работу
Диагностика и настройка комбо deepseek-v4-flash-web / deepseek-v4-pro-web (провайдер deepseek-web = веб-чат chat.deepseek.com через userToken, 3 аккаунта megan/yulia/vitya). Сессия 2026-08-22.
Архитектура
pi (клиент) → omniroute (localhost:20128, Next.js + sqlite + MCP) → combo → deepseek-web executor → chat.deepseek.com/api/v0/chat/completion
- Веб-чат DeepSeek не имеет нативного function calling — omniroute эмулирует: сериализует
tools[]в текстовый контракт<tool>{"name":"...","arguments":{...}}</tool>в промпт, модель пишет блоки текстом, парсер (open-sse/translator/deepseekWebTools.ts, 486 строк) разбирает зоопарк форматов обратно в OpenAItool_calls. toolCalling: "emulated"вsrc/shared/constants/providers/web-cookie.ts— маркер эмуляции (claude-web ="none", grok-web = нативныйtrue).- Executor:
open-sse/executors/deepseek-web.ts(1147 строк): userToken→accessToken, PoW-челлендж, создание/удаление сессий, SSE-парсинг фрагментов (THINK/ANSWER), эмуляция тулов. - Управление: REST
http://127.0.0.1:20128/api/combos(GET список, PUT /api/combos/[id]). MCP-тулы omniroute в pi-сессии отваливаются сUnknown Mcp-Session-Id— работать через HTTP напрямую.
Найденные причины вылетов агента (по убыванию важности)
1. system_message override убивает скилы pi ⭐
applyComboAgentMiddleware (open-sse/services/comboAgentMiddleware.ts) удаляет ВСЕ system-сообщения от pi (скилы, AGENTS.md, инструкции) и ставит свой system_message комбо. Модель физически не видит скилы.
Фикс: PUT /api/combos/[id] с "system_message": "" — пустая строка = override не применяется (trim()-проверка), промпт pi проходит.
2. tool_filter_regex режет инструменты ⭐
Было ^(bash|computer)$ — из 339 тулов pi оставалось 2, модель слепая (не могла звать read/write/edit/MCP).
Фикс: ^(bash|read|write|edit|grep|glob|list|describe_image|mcp__[a-z0-9_]+)$ — базовые + все MCP (filesystem, projects_meta, interns, books_ops...). Проверено: 339→25 тулов, все имена матчатся.
3. search-режим модели ломает tool-calling ⭐
deepseek-v4-flash-think-search (суффикс -search) включает веб-поиск чата: при URL модель сама лезет «читать» (сообщение «Link reading is unavailable in Expert Mode» — это веб-чат, не pi), генерит токены DEEP_SEARCH/SEARCHING в стриме → валидатор качества режет → Upstream response failed quality validation: streaming upstream error.
Фикс: модель в комбо → deepseek-v4-flash-think (без search; thinking остаётся).
4. streaming upstream error — валидатор качества
open-sse/services/combo/validateQuality.ts isStreamingUpstreamError(): SSE-фрейм с eventType === "error" / parsed.error != null / type:"error" до контента → стрим режется, все 3 аккаунта round-robin фейлятся → «All models failed». Через API не настраивается — патч кода.
5. 503 chat_admission_busy — лимит веб-чата DeepSeek
Перегрузка их серверов / слишком частые запросы (агентный цикл pi = серия быстрых запросов). Не настройка omniroute, а capacity провайдера.
Фикс: config.maxRetries: 4, config.retryDelayMs: 4000 (было 1–2 / 1500–2000). Также qwen-web падал по DNS (EAI_AGAIN chat.qwen.ai) — сетевой глюк, не ключ.
6. Пустые tool_calls (arguments: {}, name: undefined)
Модель иногда генерит <tool>{"name":"bash","arguments":{}}</tool> без аргументов → pi-валидация падает (command: must have required properties command). Следствие длинного контекста + слабого контракта. Сокращение тулов (фикс №2) снижает частоту.
Что работает после фиксов (проверено тестами)
- Эмуляция tool-calling исправна: тест с 1 тулом и с 25 тулами (stream=true и false) возвращает корректные
tool_calls({"name":"read","arguments":{"path":...}}). - То, что выглядит как «модель пишет read("...") текстом» — это
reasoning_content(мысли модели), pi их показывает, реальный tool_call при этом корректный. - Скиллы видны после удаления system_message.
Web-провайдеры с tool calling (для кастомного провайдера)
Из кода omniroute (web-cookie.ts + executors). Эмуляция ("emulated") у: deepseek-web, qwen-web, gemini-web, chatgpt-web, kimi-web, t3-web, perplexity-web, blackbox-web, muse-spark-web, inner-ai, adapta-web. Нативный (true): grok-web. "none": claude-web (не стоит скрейпить).
Бесплатные без подписки: deepseek-web (✅ есть), qwen-web (✅ ключ есть, DNS-глюк), gemini-web (Google-аккаунт), kimi-web (свободный лимит), grok-web (free-тир).
План кастомного провайдера (идея, не реализовано)
OmniRoute для 2 комбо — overkill (Next.js + sqlite + дашборд + MCP). Идея: вытащить готовые executor/deepseek-web.ts + translator/deepseekWebTools.ts из omniroute (они работают), завернуть в минимальный OpenAI-совместимый сервер:
POST /v1/chat/completions+GET /v1/models(pi подключается как провайдер)- round-robin по аккаунтам из конфига (отдельное комбо на провайдера — не смешивать в одном)
- без quality validation (убирает все streaming upstream error)
persistSession: true(переиспользование сессии → меньше 503)- ~300 строк обвязки + 2 готовых файла
- Плюс: поддержать qwen-web / gemini-web / kimi-web / grok-web как отдельные комбо (один парсер webTools.ts, разные executors)
Ключевые файлы omniroute
src/shared/constants/providers/web-cookie.ts— реестр web-провайдеров, toolCallingopen-sse/executors/deepseek-web.ts— executor веб-чата DeepSeekopen-sse/translator/deepseekWebTools.ts+webTools.ts— эмуляция/парсинг туловopen-sse/services/combo/validateQuality.ts— валидатор качества (режет стримы)open-sse/services/comboAgentMiddleware.ts— system_message override + tool_filter_regexsrc/shared/validation/schemas/combo.ts—updateComboSchema(PUT-поля)- Данные: docker volume
omniroute-data→/app/data/storage.sqlite, логи/app/data/logs/application/app.log, call_logs/app/data/call_logs/
Связанное
- admin-infra-project — контекст стека
- omniroute-session-2026-08-22 — хронология сессии (sources)
qwen-web: стена baxia-WAF (2026-08-22, llm-web-proxy подзадача)
Попытка добавить qwen-web в llm-web-proxy упёрлась в анти-бот:
- Симптом: completion возвращает HTTP 200 с JSON-эневелопом
{"ret":["FAIL_SYS_USER_VALIDATE","RGV587_ERROR::SM::哎哟喂,被挤爆啦,请稍后重试"],"data":{"url":".../_____tmd_____/punish?x5secdata=...&action=captcha"}}— слайдер-капча Alibaba baxia-WAF. RGV587 = punish-flow, требует решить капчу в браузере (получить x5secdata). - Проверено (все — капча): Node-fetch с полным свежим cookie-jar (включая token/cna/ssxmod_itna), со всеми браузерными заголовками (sec-ch-ua, sec-fetch-*, Accept: text/event-stream), со статическим bx-umidtoken / без него. Даже настоящий Chrome (CDP :9222, залогиненная сессия, капча разгадана вручную): chats/new проходит (браузерный фингерпринт), но chat/completions виснет → капча возвращается.
- Вывод: капча привязана к device-фингерпринту запроса (bx-umidtoken минтится JS из sg-wum.alibaba.com), не к cookie. Прямой API-scraping из Node — мёртвая ветка.
- Зацепка: «из под omniroute работает» — в контейнере omniroute лежит рабочая версия
open-sse/executors/qwen-web.ts(≠ GitHub main, который портировали в lwp). Сравнить и/или взять подход оттуда (возможно решённый punish-flow / живой браузерный путь). Ключ/сессия qwen хранится в omniroute DB (provider_connections, зашифрованenc.v1.+ STORAGE_ENCRYPTION_KEY из/app/data/server.env). - Статус: подзадача [llm-web-proxy-qwen-web] 🟡 paused, next-сессия — юзер покажет рабочий подход.
- 2026-08-22 вечер: находка-подтверждение. Аккаунт ForgetMeAI (FreeQwenApi, 212⭐) гоняет qwen через браузер и работает: чат-запрос исполняется внутри страницы (
page.evaluate(fetch(...))) — запрос несёт настоящий bx-umidtoken + canvas-фингерпринт + same-origin → baxia-WAF пропускает. Плюс тяжёлый stealth-спуфинг (navigator/canvas-noise/задержки событий). Вывод: Node-fetch-путь мёртв окончательно, рабочий сценарий = браузерный executor (puppeteer/playwright). Референс: forgetmeai-free-web-chat-proxies + lwpdocs/reference/forgetmeai/.