feat(agent): hierarchical executor with path resolve, runtime probe, quiet UI
Make Zed Agent closer to Cursor: deterministic DevOps path index, live Traefik port probe before blind edits, stop-after-edit, and quieter Russian progress.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
# EventHub AI Router — Zed gateway + LiteLLM on IFT
|
||||
|
||||
OpenAI-compatible endpoint for [Zed](https://zed.dev) with automatic tier/lane routing (Novita AI), optional VLESS VPN, Grafana metrics, and LiteLLM Admin UI.
|
||||
OpenAI-compatible endpoint for [Zed](https://zed.dev) with tier/lane routing, optional **hierarchical** orchestration (**Qwen3.8-Max** plan → cheap **Qwen3-Coder-30B** workers/executor → conditional **DeepSeek V3.2** verify), GigaChat classifier, VLESS VPN, Grafana, LiteLLM Admin UI.
|
||||
|
||||
Цель: качество правок близко к Cursor при экономии бюджета — дорогие модели только на plan/hard/verify, рутина на дешёвом coder.
|
||||
|
||||
## URLs (IFT, Calentiq TLS)
|
||||
|
||||
@@ -11,123 +13,146 @@ OpenAI-compatible endpoint for [Zed](https://zed.dev) with automatic tier/lane r
|
||||
| **Grafana** | `https://grafana.ift.calentiq.com` |
|
||||
| Legacy (self-signed) | `https://ai-router.ift.eventhub.local/v1` |
|
||||
|
||||
## Zed settings.json
|
||||
## Клиент (канон): Zed Agent → AiRouter
|
||||
|
||||
**Primary:** встроенный Zed Agent (OpenAI-compatible), не Claude Code / Codex ACP — те обходят Novita-бюджет.
|
||||
|
||||
Пример `settings.json` (Zed 2026 openai-compatible provider):
|
||||
|
||||
```json
|
||||
{
|
||||
"openai": {
|
||||
"api_url": "https://ai-router.ift.calentiq.com/v1",
|
||||
"api_key": "<ROUTER_API_KEY>"
|
||||
"language_models": {
|
||||
"openai_compatible": {
|
||||
"AiRouter": {
|
||||
"api_url": "https://ai-router.ift.calentiq.com/v1",
|
||||
"available_models": [
|
||||
{
|
||||
"name": "smart-router",
|
||||
"display_name": "EventHub smart-router",
|
||||
"max_tokens": 16384,
|
||||
"max_output_tokens": 4096,
|
||||
"capabilities": {
|
||||
"tools": true,
|
||||
"images": false,
|
||||
"parallel_tool_calls": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"assistant": {
|
||||
"default_model": { "provider": "openai", "model": "smart-router" }
|
||||
"agent": {
|
||||
"default_model": {
|
||||
"provider": "AiRouter",
|
||||
"model": "smart-router"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
API key: `ROUTER_API_KEY` в Agent Settings / provider UI.
|
||||
|
||||
Checklist:
|
||||
|
||||
- модель `smart-router`, `capabilities.tools: true`
|
||||
- профиль **Write** (tools on) для правок файлов
|
||||
- Ask без tools → text hierarchical / lane routing
|
||||
|
||||
Optional headers / metadata:
|
||||
|
||||
- `X-AI-Quality: auto|economy|balanced|max` — `max` поднимает hard на Qwen3.8-Max
|
||||
- `X-AI-Orchestrate: off|auto|force` — hierarchical (`force` always; `auto` на COMPLEX/REASONING; `economy` quality пропускает hierarchical)
|
||||
|
||||
### Запасной клиент (тот же AiRouter)
|
||||
|
||||
Если native Zed Agent loop хрупкий: **OpenCode** или **Cline** с `base_url=https://ai-router.ift.calentiq.com/v1` и тем же ключом — иерархия бюджета сохраняется. Claude Code / Codex ACP в Zed — **не** primary (свой биллинг).
|
||||
|
||||
### Zed Agent Write + hierarchical
|
||||
|
||||
Если в запросе есть `tools` (профиль **Write**), gateway **не** подменяет agent-цикл текстовым синтезом:
|
||||
|
||||
1. **Max**-планировщик строит план (`paths` / `edit_goal` / `constraints`) → при `plan_confirm: true` ждёт **утвердить / правка / отмена**
|
||||
2. После `ok` — **Qwen3-Coder-30B** executor получает `tools` и отвечает **`tool_calls`** (Zed сам правит файлы)
|
||||
3. Пока в истории есть `role: tool` / `tool_calls` — только executor; при повторных fail edit → escalate на DeepSeek (без synthetic tool_calls)
|
||||
|
||||
Чат без tools (Ask / Minimal) по-прежнему может идти через полный text hierarchical.
|
||||
|
||||
### Progress в Zed (hierarchical)
|
||||
|
||||
При `stream: true` (дефолт Zed) gateway сразу стримит блок **«Ход выполнения»**: план → волны workers → verify → синтез, затем `---` и финальный ответ. Конфиг: `hierarchical.stream_progress` / `progress_in_content` в `config/orchestration.yaml`.
|
||||
|
||||
## Provider profiles (`PRIMARY_PROVIDER`)
|
||||
|
||||
| Profile | Keys | VPN | Role |
|
||||
|---------|------|-----|------|
|
||||
| **`hybrid`** (default) | `NOVITA_API_KEY` | workers direct | Novita workers + **Novita** plan/verify |
|
||||
| **`openrouter`** | `OPENROUTER_API_KEY` | **VPN** | Reserve (`:free` / non-Anthropic); Claude/OpenAI/Google may be region-blocked |
|
||||
|
||||
GigaChat (`GIGACHAT_CREDENTIALS`) — classifier. Optional: Groq / Gemini / Grok / Anthropic (если биллинг заработает).
|
||||
|
||||
```bash
|
||||
bash scripts/switch-provider.sh hybrid
|
||||
bash scripts/deploy.sh
|
||||
# VPN нужен только для OpenRouter / Grok / Anthropic — не для Novita hierarchical
|
||||
```
|
||||
|
||||
## Hierarchical flow (COMPLEX / force)
|
||||
|
||||
1. **`novita-planner`** (**Qwen3.8-Max**) — JSON subtasks (`simple` | `medium_code` | `hard`) + `paths` / `edit_goal`
|
||||
2. **Workers / agent executor**: `simple` → Llama (text only); `medium_code` → **Qwen3-Coder-30B**; `hard` → **DeepSeek V3.2**; `quality=max` hard → **Qwen3.8-Max**
|
||||
3. Checks; **`novita-verifier`** (DeepSeek V3.2) только на fail / hard / `quality=max`
|
||||
4. **Synthesize** (text path) на `a-medium-code`
|
||||
|
||||
> Anthropic / OpenRouter-Claude недоступны при RU billing — plan/verify идут через Novita.
|
||||
|
||||
Response `x_router_meta.mode=hierarchical` / `hierarchical_agent` with `worker_calls`, `executor_model`, `role_cost`, `verify_skipped`, etc.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Gateway** (`router/`) — vision/OCR split, tier classification, lanes A/B/C orchestration, Redis session
|
||||
- **LiteLLM** — named models `a-*` / `b-*` / `c-*`, Auto Router v2 fallback (`smart-router-internal`)
|
||||
- **PostgreSQL** — LiteLLM Admin UI, spend logs
|
||||
- **Redis** — response cache + gateway session context
|
||||
- **VPN** — `vless-proxy` replicas=0 by default; `scripts/vpn-enable.sh`
|
||||
- **Gateway** (`router/`) — classify, lanes A/B/C, hierarchical runner, Redis session
|
||||
- **LiteLLM** — named models + Auto Router (`smart-router-internal`)
|
||||
- **PostgreSQL** — spend logs / Admin UI
|
||||
- **Redis** — cache + session
|
||||
- **VPN** — `vless-proxy` (Anthropic, OpenRouter, Grok)
|
||||
|
||||
Config sources:
|
||||
|
||||
- `config/routing_rules.yaml` — tier keywords
|
||||
- `config/model_matrix.yaml` — tier × lane → Novita models
|
||||
- `config/orchestration.yaml` — start lanes, escalation, budget caps
|
||||
Config: `config/providers.yaml`, `orchestration.yaml`, `routing_rules.yaml`, `model_matrix.yaml`.
|
||||
|
||||
## Deploy on IFT
|
||||
|
||||
```bash
|
||||
git clone git.sabilin.com/eventhub/EventHubAiRouter /opt/ai-router-stack
|
||||
cd /opt/ai-router-stack
|
||||
cp .env.example .env # fill NOVITA_API_KEY, keys, POSTGRES_PASSWORD, LITELLM_SALT_KEY
|
||||
cp .env.example .env # NOVITA_API_KEY; PRIMARY_PROVIDER=hybrid
|
||||
bash scripts/deploy.sh
|
||||
bash scripts/smoke-test.sh
|
||||
sudo bash scripts/install-audit-cron.sh # daily Novita audit 03:00 MSK
|
||||
SMOKE_HIERARCHICAL=1 bash scripts/smoke-test.sh
|
||||
```
|
||||
|
||||
Prerequisites: Docker Swarm, external network `eventhub-ift-net`, Traefik routes in EventHubDevOps.
|
||||
|
||||
### Swarm secrets
|
||||
|
||||
Created automatically by `deploy.sh` from `.env`: `novita_api_key`, `litellm_master_key`, `litellm_salt_key`, `router_api_key`, `postgres_password`, optional `groq_api_key`, `gemini_api_key`, `vless_conf`.
|
||||
Swarm secrets: `novita_api_key`, optional `anthropic_api_key` / `openrouter_api_key`, `gigachat_credentials`, …
|
||||
|
||||
**`LITELLM_SALT_KEY`** — generate once, never change after first deploy.
|
||||
|
||||
## VPN
|
||||
|
||||
```bash
|
||||
bash scripts/vpn-enable.sh # scale vless + watchdog, set HTTP_PROXY on litellm
|
||||
bash scripts/vpn-enable.sh
|
||||
bash scripts/vpn-disable.sh
|
||||
```
|
||||
|
||||
Novita always direct (`NO_PROXY=api.novita.ai`).
|
||||
| Provider | Host | Route |
|
||||
|----------|------|-------|
|
||||
| Novita | `api.novita.ai` | **direct** (NO_PROXY) |
|
||||
| Groq / Gemini / GigaChat | … | **direct** |
|
||||
| **Anthropic** | `api.anthropic.com` | **VPN** (App unavailable without) |
|
||||
| **OpenRouter** | `openrouter.ai` | **VPN** |
|
||||
| **xAI Grok** | `api.x.ai` | **VPN** |
|
||||
|
||||
## Observability
|
||||
Canon: `scripts/no-proxy-default.sh`.
|
||||
|
||||
- Prometheus scrape: `litellm:4000/metrics`, `ai-router:8000/metrics` (via `eventhub-ift-net`)
|
||||
- Grafana: vendored LiteLLM v2 dashboard + custom `gateway-tier-lane.json` (EventHubDevOps)
|
||||
- Uptime Kuma monitors — see table in plan / configure manually
|
||||
## LLM classifier (GigaChat)
|
||||
|
||||
`CLASSIFIER_MODE=hybrid|heuristic|llm` — see `config/orchestration.yaml`. Freemium GigaChat for personal test.
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| `deploy.sh` | secrets + build + stack deploy |
|
||||
| `smoke-test.sh` | health, classify, chat max_tokens=16 |
|
||||
| `sync-routing-config.sh` | regen `litellm_config.generated.yaml` |
|
||||
| `audit-novita-pricing.sh` | daily model catalog check |
|
||||
| `vendor-litellm-dashboards.sh` | fetch Grafana JSON → EventHubDevOps |
|
||||
| `vpn-enable.sh` / `vpn-disable.sh` | VPN toggle |
|
||||
|
||||
## Quality modes
|
||||
|
||||
Header `X-AI-Quality: auto|economy|balanced|max` or `metadata.quality_mode` in request body. Default: `auto` (start lane A/B by tier, escalate on retry/5xx).
|
||||
|
||||
## LLM classifier (GigaChat, optional)
|
||||
|
||||
When `CLASSIFIER_MODE=hybrid` (default) and `GIGACHAT_CREDENTIALS` is set:
|
||||
|
||||
1. **Heuristic** classify first (0 cost)
|
||||
2. If `confidence < 0.6` → one call to **GigaChat-2-Lite** via LiteLLM (`gigachat-classifier`)
|
||||
3. Lane orchestration unchanged (A/B/C, Redis, budget)
|
||||
|
||||
| `CLASSIFIER_MODE` | Behavior |
|
||||
|-------------------|----------|
|
||||
| `heuristic` | Keywords only (no GigaChat) |
|
||||
| `hybrid` | GigaChat only on low confidence |
|
||||
| `llm` | Always GigaChat for text (except vision) |
|
||||
|
||||
Freemium GigaChat — для личного некомmercial теста; prod — юр. тариф Сбера.
|
||||
|
||||
Setup: [developers.sber.ru](https://developers.sber.ru/docs/ru/gigachat/quickstart/ind-create-project) → Authorization key → `.env` `GIGACHAT_CREDENTIALS`.
|
||||
|
||||
Response field: `x_router_meta.classifier_source` = `heuristic` | `gigachat` | `heuristic_fallback`.
|
||||
|
||||
## Response metadata
|
||||
|
||||
Each chat response includes `x_router_meta`:
|
||||
|
||||
```json
|
||||
{
|
||||
"tier": "MEDIUM_OPS",
|
||||
"lane": "A",
|
||||
"model": "a-medium-ops",
|
||||
"escalation_level": 0,
|
||||
"quality_mode": "auto"
|
||||
}
|
||||
```
|
||||
|
||||
## Local build
|
||||
|
||||
```bash
|
||||
docker build -f router/Dockerfile -t ai-router-gateway:local .
|
||||
```
|
||||
|
||||
## CI
|
||||
|
||||
Gitea Actions: `.gitea/workflows/ci.yml` — build gateway image, sync config check.
|
||||
See `scripts/` — `deploy.sh`, `smoke-test.sh`, `gen-litellm-config.py`, `switch-provider.sh`, VPN helpers.
|
||||
|
||||
Reference in New Issue
Block a user