Files
microdao-daarion/docs/tasks/TASK_PHASE_NODE2_ROUTER_SWAPPER_FIX.md
Apple f95810e8a7 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
2025-12-02 03:13:01 -08:00

11 KiB
Raw Blame History

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) має повертати структуру типу:

    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.