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
11 KiB
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 агентів.
- Swapper Service:
- Для NODE2:
- Swapper Service:
Degraded, моделей 0/0,No models found. - DAGI Router:
Down,Router недоступний.
- Swapper Service:
- Для NODE1:
-
Cursor раніше змінював
get_node_endpointsтак, що:- Для NODE1 використовувалися docker URLs (
http://dagi-router:9102,http://swapper-service:8890). - Для NODE2 —
http://localhost:9102,http://localhost:8890, з визначенням поnode_id.
- Для NODE1 використовувалися docker URLs (
-
Це працює локально на Mac, але в прод-оточенні
city-serviceкрутиться в docker на NODE1, іlocalhostдля нього — це контейнер, а не MacBook або DAGI Router. -
Стани в UI беруться не напряму з Router/Swapper, а з таблиці
node_cache(метрики, які пушитьnode-guardian).
Ціль
-
Стандартизувати визначення endpoint'ів для DAGI Router і Swapper так, щоб:
- У PROD усе працювало через один базовий URL (docker-hostи
dagi-router/swapper-service). - Не було прив'язки до
node_idдля вибору URL. - У DEV/локально на Mac можна було використовувати
localhostчерез ENV.
- У PROD усе працювало через один базовий URL (docker-hostи
-
Переконатися, що
node-guardianдля NODE2 коректно оновлюєnode_cache:- Є записи з
node_id = 'node-2-macbook-m4max'дляrouter_healthyіswapper_state. - Помилки логуються явно, а не тихо ковтаються.
- Є записи з
-
Виправити відображення моделей у Swapper Service:
- Якщо в Swapper реально є моделі, UI має показувати їх (назву, статус, тип, тощо).
- Якщо моделей немає/немає зв'язку — показувати чесний
Degradedз fallback, але не плутати це з "0/0 при наявних моделях".
-
Мінімізувати магію та дублювання логіки: конфіг через 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
-
Знайти реалізацію
get_node_endpoints. -
Прибрати логіку, яка підміняє URL на
localhostна основіnode_id(наприклад,if "node-2" in node_idтощо). -
Замість цього:
- Винести базові URL у ENV, наприклад:
ROUTER_BASE_URL(наприклад,http://dagi-router:9102у проді).SWAPPER_BASE_URL(наприклад,http://swapper-service:8890у проді).
- Для DEV (локальний запуск на Mac без Docker) дозволити дефолт:
ROUTER_BASE_URL=http://localhost:9102SWAPPER_BASE_URL=http://localhost:8890
- Винести базові URL у ENV, наприклад:
-
get_node_endpoints(node)має повертати структуру типу:return NodeEndpoints( router_base=f"{ROUTER_BASE_URL}", swapper_base=f"{SWAPPER_BASE_URL}", # за потреби — окремі health / metrics / models endpoints ) -
Не прив'язувати URL до
node_id. Вся різниця між нодами має відображатись у:node_cache(метрики),- БД агентів (який агент до якої ноди прив'язаний).
2. Виправити get_node_swapper_detail у services/city-service/routes_city.py
- Переконатися, що endpoint
GET /api/v1/nodes/{node_id}/swapper:- Використовує
get_node_endpointsдля звернення до Swapper. - Коректно обробляє:
- HTTP 200 з валідною відповіддю
/api/v1/models. - HTTP 5xx / timeout / connection error.
- HTTP 200 з валідною відповіддю
- Використовує
- Логіка:
- Якщо відповідь успішна — парсити список моделей:
- Назва моделі.
- Статус (loaded/failed/loading).
- Кількість інстансів, GPU/CPU тощо (як дозволяє API Swapper).
- Оновлювати / читати кеш (
node_cache) так, щоб UI міг показувати:- Загальну кількість моделей.
- Loaded / Failed / Pending.
- Якщо помилка або моделі не повертаються:
- Повернути
status: "degraded"таmodels: [], а НЕ 404.
- Повернути
- Якщо відповідь успішна — парсити список моделей:
- Гарантувати, що UI завжди отримує валідний JSON:
- навіть якщо Swapper мертвий,
- без сирих трас і HTML помилок.
3. Перевірити та поправити node-guardian (особливо для NODE2)
- Знайти код
node-guardian(швидше за все окремий сервіс / скрипт). - Переконатися, що він:
- Читає 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": ...}).
- Викликає Router health endpoint (через
- Читає ENV:
- Додати нормальні лог-меседжі:
- На успішне оновлення.
- На помилки (HTTP статус, текст помилки).
- Перевірити, що в БД (таблиця
node_cache):- Після запуску guardian на NODE2 з'являються рядки з
node_id = 'node-2-macbook-m4max'дляrouter_healthyіswapper_state.
- Після запуску guardian на NODE2 з'являються рядки з
4. Сумісність з наявними фільтрами по node_id
- Знайти всі місця, де читається
node_cacheдля вузла:- Наприклад,
get_node_status,get_node_swapper_detail,get_node_router_detailтощо.
- Наприклад,
- Переконатися, що фільтрація відбувається по
node_id+kind:WHERE node_id = :node_id AND kind = :kind
- Не використовувати глобальний
swapper_stateбезnode_id, якщо вже перейшли на модель "по нодах". - Якщо історично був один глобальний запис без
node_id:- Міграція (якщо потрібно) — або прибрати цей запис, або задовольнитися тим, що UI читає тільки записи з конкретним
node_id.
- Міграція (якщо потрібно) — або прибрати цей запис, або задовольнитися тим, що UI читає тільки записи з конкретним
5. Swapper models → UI
- Забезпечити, щоб бекенд повертав у UI-модель (DTO) для Swapper дані:
status("ok" | "degraded" | "down").models_total.models_loaded.models_failed.models(масив з короткою інформацією по кожній моделі).
- Перевірити, що фронт (Node detail page) читає ці поля й не падає, якщо масив
modelsпорожній. - Якщо потрібен stub/fallback — він має відрізнятись від реального "0 моделей при піднятому Swapper'і".
Acceptance Criteria
-
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.
- У
-
NODE2 в UI:
- На сторінці НОДА2:
- DAGI Router:
- Показує реальний статус (
Up/Down) на основіrouter_healthyзnode_cache. - При живому Router статус
Upбез "Router недоступний".
- Показує реальний статус (
- Swapper Service:
- Показує реальну кількість моделей (якщо вони є у Swapper).
- При проблемах —
Degraded, але без 404/порожніх екранiв.
- DAGI Router:
- На сторінці НОДА2:
-
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'.
-
Swapper models:
curl <SWAPPER_BASE_URL>/api/v1/modelsповертає список моделей.- UI Swapper Service для NODE1 показує ці моделі в таблиці (а не лише
0/0). - При зупиненому Swapper:
- UI показує
DegradedабоDown, але бекенд повертає валідний JSON з fallback.
- UI показує
-
Без регресій:
- NODE1 продовжує показувати 9 агентів у DAGI Router.
- Інші частини
nodesUI працюють як раніше (агенти, статуси, Node Guardian & Steward секція).
Пріоритет
- Високий. Це критична частина UX для нод та діагностики стану DAGI в DAARION.city.