Skip to content

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 так, щоб:
  2. У PROD усе працювало через один базовий URL (docker-hostи dagi-router / swapper-service).
  3. Не було прив'язки до node_id для вибору URL.
  4. У DEV/локально на Mac можна було використовувати localhost через ENV.

  5. Переконатися, що node-guardian для NODE2 коректно оновлює node_cache:

  6. Є записи з node_id = 'node-2-macbook-m4max' для router_healthy і swapper_state.
  7. Помилки логуються явно, а не тихо ковтаються.

  8. Виправити відображення моделей у Swapper Service:

  9. Якщо в Swapper реально є моделі, UI має показувати їх (назву, статус, тип, тощо).
  10. Якщо моделей немає/немає зв'язку — показувати чесний Degraded з fallback, але не плутати це з "0/0 при наявних моделях".

  11. Мінімізувати магію та дублювання логіки: конфіг через 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. Замість цього:
  4. Винести базові URL у ENV, наприклад:
    • ROUTER_BASE_URL (наприклад, http://dagi-router:9102 у проді).
    • SWAPPER_BASE_URL (наприклад, http://swapper-service:8890 у проді).
  5. Для DEV (локальний запуск на Mac без Docker) дозволити дефолт:
    • ROUTER_BASE_URL=http://localhost:9102
    • SWAPPER_BASE_URL=http://localhost:8890
  6. get_node_endpoints(node) має повертати структуру типу:

python return NodeEndpoints( router_base=f"{ROUTER_BASE_URL}", swapper_base=f"{SWAPPER_BASE_URL}", # за потреби — окремі health / metrics / models endpoints )

  1. Не прив'язувати URL до node_id. Вся різниця між нодами має відображатись у:
  2. node_cache (метрики),
  3. БД агентів (який агент до якої ноди прив'язаний).

2. Виправити get_node_swapper_detail у services/city-service/routes_city.py

  1. Переконатися, що endpoint GET /api/v1/nodes/{node_id}/swapper:
  2. Використовує get_node_endpoints для звернення до Swapper.
  3. Коректно обробляє:
    • HTTP 200 з валідною відповіддю /api/v1/models.
    • HTTP 5xx / timeout / connection error.
  4. Логіка:
  5. Якщо відповідь успішна — парсити список моделей:
    • Назва моделі.
    • Статус (loaded/failed/loading).
    • Кількість інстансів, GPU/CPU тощо (як дозволяє API Swapper).
  6. Оновлювати / читати кеш (node_cache) так, щоб UI міг показувати:
    • Загальну кількість моделей.
    • Loaded / Failed / Pending.
  7. Якщо помилка або моделі не повертаються:
    • Повернути status: "degraded" та models: [], а НЕ 404.
  8. Гарантувати, що UI завжди отримує валідний JSON:
  9. навіть якщо Swapper мертвий,
  10. без сирих трас і HTML помилок.

3. Перевірити та поправити node-guardian (особливо для NODE2)

  1. Знайти код node-guardian (швидше за все окремий сервіс / скрипт).
  2. Переконатися, що він:
  3. Читає ENV:
    • NODE_ID (для NODE2: node-2-macbook-m4max).
    • CITY_API_URL (HTTPS URL до city-service).
  4. Периодично:
    • Викликає 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": ...}).
  5. Додати нормальні лог-меседжі:
  6. На успішне оновлення.
  7. На помилки (HTTP статус, текст помилки).
  8. Перевірити, що в БД (таблиця node_cache):
  9. Після запуску guardian на NODE2 з'являються рядки з node_id = 'node-2-macbook-m4max' для router_healthy і swapper_state.

4. Сумісність з наявними фільтрами по node_id

  1. Знайти всі місця, де читається node_cache для вузла:
  2. Наприклад, get_node_status, get_node_swapper_detail, get_node_router_detail тощо.
  3. Переконатися, що фільтрація відбувається по node_id + kind:
  4. WHERE node_id = :node_id AND kind = :kind
  5. Не використовувати глобальний swapper_state без node_id, якщо вже перейшли на модель "по нодах".
  6. Якщо історично був один глобальний запис без node_id:
  7. Міграція (якщо потрібно) — або прибрати цей запис, або задовольнитися тим, що UI читає тільки записи з конкретним node_id.

5. Swapper models → UI

  1. Забезпечити, щоб бекенд повертав у UI-модель (DTO) для Swapper дані:
  2. status ("ok" | "degraded" | "down").
  3. models_total.
  4. models_loaded.
  5. models_failed.
  6. models (масив з короткою інформацією по кожній моделі).
  7. Перевірити, що фронт (Node detail page) читає ці поля й не падає, якщо масив models порожній.
  8. Якщо потрібен stub/fallback — він має відрізнятись від реального "0 моделей при піднятому Swapper'і".

Acceptance Criteria

  1. Endpoint конфіг:
  2. У .env / docker-compose є:
    • ROUTER_BASE_URL (у проді → http://dagi-router:9102).
    • SWAPPER_BASE_URL (у проді → http://swapper-service:8890).
  3. get_node_endpoints не використовує node_id для визначення URL.
  4. У DEV-режимі локальний запуск на Mac використовує localhost:9102/8890.

  5. NODE2 в UI:

  6. На сторінці НОДА2:

    • DAGI Router:
    • Показує реальний статус (Up/Down) на основі router_healthy з node_cache.
    • При живому Router статус Up без "Router недоступний".
    • Swapper Service:
    • Показує реальну кількість моделей (якщо вони є у Swapper).
    • При проблемах — Degraded, але без 404/порожніх екранiв.
  7. Node Guardian:

  8. Guardian на НОДА2 працює, логі показують регулярні оновлення.
  9. У Postgres (таблиця node_cache) є останні записи:

    • node_id = 'node-2-macbook-m4max', kind = 'router_healthy'.
    • node_id = 'node-2-macbook-m4max', kind = 'swapper_state'.
  10. Swapper models:

  11. curl <SWAPPER_BASE_URL>/api/v1/models повертає список моделей.
  12. UI Swapper Service для NODE1 показує ці моделі в таблиці (а не лише 0/0).
  13. При зупиненому Swapper:

    • UI показує Degraded або Down, але бекенд повертає валідний JSON з fallback.
  14. Без регресій:

  15. NODE1 продовжує показувати 9 агентів у DAGI Router.
  16. Інші частини nodes UI працюють як раніше (агенти, статуси, Node Guardian & Steward секція).

Пріоритет

  • Високий. Це критична частина UX для нод та діагностики стану DAGI в DAARION.city.