fix(nodes): Normalize Router/Swapper endpoints and fix NODE2 display
Major changes: - Normalize get_node_endpoints to use ENV vars (ROUTER_BASE_URL, SWAPPER_BASE_URL) - Remove node_id-based URL selection logic - Add fallback direct API call in get_node_swapper_detail - Fix Swapper API endpoint (/models instead of /api/v1/models) - Add router_healthy and router_version to node_heartbeat fallback - Add ENV vars to docker-compose for Router/Swapper URLs Documentation: - Add TASK_PHASE_NODE2_ROUTER_SWAPPER_FIX.md with full task description - Add NODE2_GUARDIAN_SETUP.md with setup instructions This fixes: - Swapper models not showing for NODE1 and NODE2 - DAGI Router agents not showing for NODE2 - Router/Swapper showing as Down/Degraded when they're actually up
This commit is contained in:
183
docs/tasks/TASK_PHASE_NODE2_ROUTER_SWAPPER_FIX.md
Normal file
183
docs/tasks/TASK_PHASE_NODE2_ROUTER_SWAPPER_FIX.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# TASK_PHASE_NODE2_ROUTER_SWAPPER_FIX — Router / Swapper / Node Guardian
|
||||
|
||||
## Контекст
|
||||
|
||||
- У DAARION.city є дві ноди:
|
||||
- NODE1 — основний сервер (docker-compose, daji-router, swapper-service тощо).
|
||||
- NODE2 — MacBook Pro M4 Max (`node-2-macbook-m4max`, IP: `192.168.1.33`).
|
||||
|
||||
- UI `https://daarion.space/nodes/node/...` показує:
|
||||
- Для NODE1:
|
||||
- Swapper Service: `Degraded`, моделей 0/0, `No models found`.
|
||||
- DAGI Router: `Up`, 9 агентів.
|
||||
- Для NODE2:
|
||||
- Swapper Service: `Degraded`, моделей 0/0, `No models found`.
|
||||
- DAGI Router: `Down`, `Router недоступний`.
|
||||
|
||||
- Cursor раніше змінював `get_node_endpoints` так, що:
|
||||
- Для NODE1 використовувалися docker URLs (`http://dagi-router:9102`, `http://swapper-service:8890`).
|
||||
- Для NODE2 — `http://localhost:9102`, `http://localhost:8890`, з визначенням по `node_id`.
|
||||
- Це працює локально на Mac, але в прод-оточенні `city-service` крутиться в docker на NODE1, і `localhost` для нього — це контейнер, а не MacBook або DAGI Router.
|
||||
|
||||
- Стани в UI беруться не напряму з Router/Swapper, а з таблиці `node_cache` (метрики, які пушить `node-guardian`).
|
||||
|
||||
## Ціль
|
||||
|
||||
1. Стандартизувати визначення endpoint'ів для DAGI Router і Swapper так, щоб:
|
||||
- У PROD усе працювало через один базовий URL (docker-hostи `dagi-router` / `swapper-service`).
|
||||
- Не було прив'язки до `node_id` для вибору URL.
|
||||
- У DEV/локально на Mac можна було використовувати `localhost` через ENV.
|
||||
|
||||
2. Переконатися, що `node-guardian` для NODE2 коректно оновлює `node_cache`:
|
||||
- Є записи з `node_id = 'node-2-macbook-m4max'` для `router_healthy` і `swapper_state`.
|
||||
- Помилки логуються явно, а не тихо ковтаються.
|
||||
|
||||
3. Виправити відображення моделей у Swapper Service:
|
||||
- Якщо в Swapper реально є моделі, UI має показувати їх (назву, статус, тип, тощо).
|
||||
- Якщо моделей немає/немає зв'язку — показувати чесний `Degraded` з fallback, але не плутати це з "0/0 при наявних моделях".
|
||||
|
||||
4. Мінімізувати магію та дублювання логіки: конфіг через ENV, один контракт між `city-service`, `node-guardian` і `DAGI Router / Swapper`.
|
||||
|
||||
---
|
||||
|
||||
## Архітектурні принципи
|
||||
|
||||
- **Один DAGI Router + один Swapper в проді** (на NODE1) обслуговує всі ноди.
|
||||
- `city-service` завжди ходить до Router/Swapper через **базовий URL з ENV**, без умов по `node_id`.
|
||||
- `node-guardian`:
|
||||
- Викликає Router/Swapper.
|
||||
- Перетворює результати в метрики `node_cache` (`router_healthy`, `swapper_state`, можливо інші).
|
||||
- Маркує ці записи конкретним `node_id`, що відповідає ноді, де стоїть guardian.
|
||||
|
||||
---
|
||||
|
||||
## Завдання
|
||||
|
||||
### 1. Нормалізувати `get_node_endpoints` у `services/city-service/repo_city.py`
|
||||
|
||||
1. Знайти реалізацію `get_node_endpoints`.
|
||||
2. Прибрати логіку, яка підміняє URL на `localhost` на основі `node_id` (наприклад, `if "node-2" in node_id` тощо).
|
||||
3. Замість цього:
|
||||
- Винести базові URL у ENV, наприклад:
|
||||
- `ROUTER_BASE_URL` (наприклад, `http://dagi-router:9102` у проді).
|
||||
- `SWAPPER_BASE_URL` (наприклад, `http://swapper-service:8890` у проді).
|
||||
- Для DEV (локальний запуск на Mac без Docker) дозволити дефолт:
|
||||
- `ROUTER_BASE_URL=http://localhost:9102`
|
||||
- `SWAPPER_BASE_URL=http://localhost:8890`
|
||||
4. `get_node_endpoints(node)` має повертати структуру типу:
|
||||
|
||||
```python
|
||||
return NodeEndpoints(
|
||||
router_base=f"{ROUTER_BASE_URL}",
|
||||
swapper_base=f"{SWAPPER_BASE_URL}",
|
||||
# за потреби — окремі health / metrics / models endpoints
|
||||
)
|
||||
```
|
||||
|
||||
5. Не прив'язувати URL до `node_id`. Вся різниця між нодами має відображатись у:
|
||||
* `node_cache` (метрики),
|
||||
* БД агентів (який агент до якої ноди прив'язаний).
|
||||
|
||||
### 2. Виправити `get_node_swapper_detail` у `services/city-service/routes_city.py`
|
||||
|
||||
1. Переконатися, що endpoint `GET /api/v1/nodes/{node_id}/swapper`:
|
||||
* Використовує `get_node_endpoints` для звернення до Swapper.
|
||||
* Коректно обробляє:
|
||||
* HTTP 200 з валідною відповіддю `/api/v1/models`.
|
||||
* HTTP 5xx / timeout / connection error.
|
||||
2. Логіка:
|
||||
* Якщо відповідь успішна — парсити список моделей:
|
||||
* Назва моделі.
|
||||
* Статус (loaded/failed/loading).
|
||||
* Кількість інстансів, GPU/CPU тощо (як дозволяє API Swapper).
|
||||
* Оновлювати / читати кеш (`node_cache`) так, щоб UI міг показувати:
|
||||
* Загальну кількість моделей.
|
||||
* Loaded / Failed / Pending.
|
||||
* Якщо помилка або моделі не повертаються:
|
||||
* Повернути `status: "degraded"` та `models: []`, а НЕ 404.
|
||||
3. Гарантувати, що UI завжди отримує валідний JSON:
|
||||
* навіть якщо Swapper мертвий,
|
||||
* без сирих трас і HTML помилок.
|
||||
|
||||
### 3. Перевірити та поправити `node-guardian` (особливо для NODE2)
|
||||
|
||||
1. Знайти код `node-guardian` (швидше за все окремий сервіс / скрипт).
|
||||
2. Переконатися, що він:
|
||||
* Читає ENV:
|
||||
* `NODE_ID` (для NODE2: `node-2-macbook-m4max`).
|
||||
* `CITY_API_URL` (HTTPS URL до city-service).
|
||||
* Периодично:
|
||||
* Викликає Router health endpoint (через `ROUTER_BASE_URL` або відповідний URL з ENV).
|
||||
* Викликає Swapper `/api/v1/models` або health endpoint.
|
||||
* Пушить у `node_cache` записи:
|
||||
* `router_healthy` з payload (`{"ok": true/false, "latency_ms": ...}`).
|
||||
* `swapper_state` з payload (`{"models_total": X, "models_loaded": Y, "models_failed": Z, "raw": ...}`).
|
||||
3. Додати нормальні лог-меседжі:
|
||||
* На успішне оновлення.
|
||||
* На помилки (HTTP статус, текст помилки).
|
||||
4. Перевірити, що в БД (таблиця `node_cache`):
|
||||
* Після запуску guardian на NODE2 з'являються рядки з `node_id = 'node-2-macbook-m4max'` для `router_healthy` і `swapper_state`.
|
||||
|
||||
### 4. Сумісність з наявними фільтрами по `node_id`
|
||||
|
||||
1. Знайти всі місця, де читається `node_cache` для вузла:
|
||||
* Наприклад, `get_node_status`, `get_node_swapper_detail`, `get_node_router_detail` тощо.
|
||||
2. Переконатися, що фільтрація відбувається по `node_id` + `kind`:
|
||||
* `WHERE node_id = :node_id AND kind = :kind`
|
||||
3. Не використовувати глобальний `swapper_state` без `node_id`, якщо вже перейшли на модель "по нодах".
|
||||
4. Якщо історично був один глобальний запис без `node_id`:
|
||||
* Міграція (якщо потрібно) — або прибрати цей запис, або задовольнитися тим, що UI читає тільки записи з конкретним `node_id`.
|
||||
|
||||
### 5. Swapper models → UI
|
||||
|
||||
1. Забезпечити, щоб бекенд повертав у UI-модель (DTO) для Swapper дані:
|
||||
* `status` (`"ok" | "degraded" | "down"`).
|
||||
* `models_total`.
|
||||
* `models_loaded`.
|
||||
* `models_failed`.
|
||||
* `models` (масив з короткою інформацією по кожній моделі).
|
||||
2. Перевірити, що фронт (Node detail page) читає ці поля й не падає, якщо масив `models` порожній.
|
||||
3. Якщо потрібен stub/fallback — він має відрізнятись від реального "0 моделей при піднятому Swapper'і".
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. **Endpoint конфіг**:
|
||||
* У `.env` / docker-compose є:
|
||||
* `ROUTER_BASE_URL` (у проді → `http://dagi-router:9102`).
|
||||
* `SWAPPER_BASE_URL` (у проді → `http://swapper-service:8890`).
|
||||
* `get_node_endpoints` не використовує `node_id` для визначення URL.
|
||||
* У DEV-режимі локальний запуск на Mac використовує `localhost:9102/8890`.
|
||||
|
||||
2. **NODE2 в UI**:
|
||||
* На сторінці НОДА2:
|
||||
* DAGI Router:
|
||||
* Показує реальний статус (`Up/Down`) на основі `router_healthy` з `node_cache`.
|
||||
* При живому Router статус `Up` без "Router недоступний".
|
||||
* Swapper Service:
|
||||
* Показує реальну кількість моделей (якщо вони є у Swapper).
|
||||
* При проблемах — `Degraded`, але без 404/порожніх екранiв.
|
||||
|
||||
3. **Node Guardian**:
|
||||
* Guardian на НОДА2 працює, логі показують регулярні оновлення.
|
||||
* У Postgres (таблиця `node_cache`) є останні записи:
|
||||
* `node_id = 'node-2-macbook-m4max'`, `kind = 'router_healthy'`.
|
||||
* `node_id = 'node-2-macbook-m4max'`, `kind = 'swapper_state'`.
|
||||
|
||||
4. **Swapper models**:
|
||||
* `curl <SWAPPER_BASE_URL>/api/v1/models` повертає список моделей.
|
||||
* UI Swapper Service для NODE1 показує ці моделі в таблиці (а не лише `0/0`).
|
||||
* При зупиненому Swapper:
|
||||
* UI показує `Degraded` або `Down`, але бекенд повертає валідний JSON з fallback.
|
||||
|
||||
5. **Без регресій**:
|
||||
* NODE1 продовжує показувати 9 агентів у DAGI Router.
|
||||
* Інші частини `nodes` UI працюють як раніше (агенти, статуси, Node Guardian & Steward секція).
|
||||
|
||||
---
|
||||
|
||||
## Пріоритет
|
||||
|
||||
* Високий. Це критична частина UX для нод та діагностики стану DAGI в DAARION.city.
|
||||
|
||||
Reference in New Issue
Block a user