From 42ff22bf8581403d2902ca5797c006b3c3bd411d Mon Sep 17 00:00:00 2001 From: Dezvoltari Evotech Date: Sat, 27 Jun 2026 06:42:02 -0700 Subject: [PATCH] LOT 1 - Optimizare script build -Instalare mono comanda --- .gitignore | 6 + README_LIVRABIL.md | 102 +- ai_platform/DEPLOYMENT.md | 22 +- ai_platform/ENDPOINTS.md | 112 +- ai_platform/INDEX.md | 45 +- ...L_PROJECTS_DEPLOY_KEYS_AND_REQUIREMENTS.md | 23 +- ai_platform/README.md | 44 +- ai_platform/local_gpu_stack/.env.example | 43 + ai_platform/local_gpu_stack/README.md | 74 + ai_platform/local_gpu_stack/TESTING.md | 228 +++ ai_platform/local_gpu_stack/deploy.sh | 298 ++++ .../local_gpu_stack/docker-compose.yml | 310 +++++ ai_platform/local_gpu_stack/lib.sh | 74 + .../local_gpu_stack/reports/latest.json | 422 ++++++ .../reports/test_report_20260627_143404.json | 249 ++++ .../reports/test_report_20260627_144358.json | 422 ++++++ .../reports/test_report_20260627_144637.json | 422 ++++++ ai_platform/local_gpu_stack/run_tests.py | 271 ++++ .../local_gpu_stack/seed/seed_brain.sh | 49 + .../local_gpu_stack/seed/seed_catalog.sh | 36 + ai_platform/modules/audio/API.md | 56 +- ai_platform/modules/audio/INDEX.md | 8 +- ai_platform/modules/audio/README.md | 11 +- .../modules/audio/deploy/docker-compose.yml | 10 +- ai_platform/modules/catalog-api/INDEX.md | 4 +- ai_platform/modules/catalog-api/README.md | 9 +- .../catalog-api/deploy/docker-compose.yml | 4 +- ai_platform/modules/cloak/INDEX.md | 4 +- ai_platform/modules/dashboard/INDEX.md | 68 +- ai_platform/modules/dashboard/README.md | 74 +- .../web/src/pages/system/Settings.tsx | 12 +- .../modules/didi_brain/ARCHITECTURE.md | 64 +- .../didi_brain/CONTRACT_VERIFICATION_CACHE.md | 8 +- ai_platform/modules/didi_brain/INDEX.md | 15 +- ai_platform/modules/didi_brain/README.md | 55 +- ai_platform/modules/didi_brain/STATUS.md | 90 +- .../didi_brain/infra/docker-compose.yml | 9 +- ai_platform/modules/didi_brain/pyproject.toml | 2 +- ai_platform/modules/domain_check/.env | 100 ++ ai_platform/modules/domain_check/.env.example | 172 +++ ai_platform/modules/domain_check/.gitignore | 100 ++ .../modules/domain_check/.gitlab-ci.yml | 325 +++++ .../modules/domain_check/ACCESS_INFO.md | 192 +++ .../modules/domain_check/API_ARCHITECTURE.md | 1233 +++++++++++++++++ ai_platform/modules/domain_check/CLAUDE.md | 62 + .../modules/domain_check/CURRENT_STATUS.md | 706 ++++++++++ .../modules/domain_check/NEXT_STEPS.md | 452 ++++++ ai_platform/modules/domain_check/README.md | 740 ++++++++++ .../modules/domain_check/api/Dockerfile | 68 + .../modules/domain_check/api/app/__init__.py | 422 ++++++ .../domain_check/api/app/api_models.py | 424 ++++++ .../domain_check/api/app/celery_app.py | 6 + .../modules/domain_check/api/app/config.py | 234 ++++ .../domain_check/api/app/models/__init__.py | 26 + .../domain_check/api/app/models/api_usage.py | 32 + .../domain_check/api/app/models/batch.py | 39 + .../domain_check/api/app/models/blacklist.py | 33 + .../api/app/models/check_history.py | 38 + .../domain_check/api/app/models/dns.py | 31 + .../domain_check/api/app/models/domain.py | 118 ++ .../domain_check/api/app/models/reputation.py | 42 + .../domain_check/api/app/models/risk.py | 43 + .../domain_check/api/app/models/ssl.py | 40 + .../domain_check/api/app/models/whois.py | 62 + .../domain_check/api/app/routes/__init__.py | 0 .../domain_check/api/app/routes/batch.py | 14 + .../domain_check/api/app/routes/check.py | 560 ++++++++ .../domain_check/api/app/routes/domain.py | 9 + .../domain_check/api/app/routes/search.py | 9 + .../domain_check/api/app/routes/stats.py | 9 + .../domain_check/api/app/services/__init__.py | 0 .../api/app/services/blacklist_service.py | 297 ++++ .../api/app/services/dns_service.py | 163 +++ .../api/app/services/http_analysis_service.py | 369 +++++ .../app/services/ip_intelligence_service.py | 233 ++++ .../app/services/mail_intelligence_service.py | 374 +++++ .../api/app/services/port_scan_service.py | 265 ++++ .../api/app/services/risk_scorer.py | 741 ++++++++++ .../api/app/services/ssl_service.py | 185 +++ .../api/app/services/subdomain_service.py | 442 ++++++ .../api/app/services/whois_service.py | 248 ++++ .../domain_check/api/app/static/index.html | 637 +++++++++ .../domain_check/api/app/tasks/__init__.py | 0 .../domain_check/api/app/utils/__init__.py | 0 .../modules/domain_check/api/requirements.txt | 91 ++ ai_platform/modules/domain_check/api/run.py | 45 + .../api/tests/test_whois_dates.py | 104 ++ .../modules/domain_check/dashboard/Dockerfile | 42 + .../domain_check/dashboard/requirements.txt | 26 + ai_platform/modules/domain_check/deploy.sh | 291 ++++ .../modules/domain_check/docker-compose.yml | 151 ++ .../domain_check/init-scripts/01-init-db.sql | 413 ++++++ .../modules/domain_check/test-api-complete.sh | 41 + .../modules/domain_check/test-complete-lan.sh | 114 ++ ai_platform/modules/domain_check/use_api.md | 578 ++++++++ ai_platform/modules/embeddings/INDEX.md | 39 +- .../embeddings/deploy/docker-compose.yml | 10 +- ai_platform/modules/extractors/API.md | 94 ++ .../modules/forensic_features/.env.example | 4 +- .../modules/forensic_features/README.md | 10 +- .../modules/forensic_features/docs/API.md | 39 +- .../forensic_features/docs/ARCHITECTURE.md | 8 +- .../forensic_features/docs/INTEGRATION.md | 10 +- ai_platform/modules/gateway/INDEX.md | 44 +- ai_platform/modules/gateway/README.md | 40 +- .../modules/gateway/deploy/docker-compose.yml | 2 +- ai_platform/modules/llm-inference/API.md | 83 +- ai_platform/modules/llm-inference/INDEX.md | 13 +- ai_platform/modules/llm-inference/README.md | 14 +- .../modules/llm-inference/deploy/Dockerfile | 6 +- .../llm-inference/deploy/docker-compose.yml | 8 +- .../llm_inference/backends/vllm_backend.py | 18 + .../llm-inference/src/llm_inference/config.py | 10 + ai_platform/modules/rerank/INDEX.md | 46 +- .../modules/rerank/deploy/docker-compose.yml | 10 +- .../modules/video-analysis/.env.example | 6 +- ai_platform/modules/video-analysis/API.md | 62 +- ai_platform/modules/video-analysis/INDEX.md | 16 +- ai_platform/modules/video-analysis/README.md | 44 +- ai_platform/modules/video-analysis/TESTING.md | 19 +- .../modules/video-analysis/deploy/Dockerfile | 4 +- .../video-analysis/deploy/docker-compose.yml | 10 +- ai_platform/modules/web/API.md | 16 +- ai_platform/modules/web/BRAIN_INTEGRATION.md | 6 +- ai_platform/modules/web/INDEX.md | 25 +- ai_platform/modules/web/README.md | 30 +- .../deploy/metasearch/searxng/settings.yml | 3 - 127 files changed, 16163 insertions(+), 532 deletions(-) create mode 100644 .gitignore create mode 100644 ai_platform/local_gpu_stack/.env.example create mode 100644 ai_platform/local_gpu_stack/README.md create mode 100644 ai_platform/local_gpu_stack/TESTING.md create mode 100644 ai_platform/local_gpu_stack/deploy.sh create mode 100644 ai_platform/local_gpu_stack/docker-compose.yml create mode 100644 ai_platform/local_gpu_stack/lib.sh create mode 100644 ai_platform/local_gpu_stack/reports/latest.json create mode 100644 ai_platform/local_gpu_stack/reports/test_report_20260627_143404.json create mode 100644 ai_platform/local_gpu_stack/reports/test_report_20260627_144358.json create mode 100644 ai_platform/local_gpu_stack/reports/test_report_20260627_144637.json create mode 100644 ai_platform/local_gpu_stack/run_tests.py create mode 100644 ai_platform/local_gpu_stack/seed/seed_brain.sh create mode 100644 ai_platform/local_gpu_stack/seed/seed_catalog.sh create mode 100644 ai_platform/modules/domain_check/.env create mode 100644 ai_platform/modules/domain_check/.env.example create mode 100644 ai_platform/modules/domain_check/.gitignore create mode 100644 ai_platform/modules/domain_check/.gitlab-ci.yml create mode 100644 ai_platform/modules/domain_check/ACCESS_INFO.md create mode 100644 ai_platform/modules/domain_check/API_ARCHITECTURE.md create mode 100644 ai_platform/modules/domain_check/CLAUDE.md create mode 100644 ai_platform/modules/domain_check/CURRENT_STATUS.md create mode 100644 ai_platform/modules/domain_check/NEXT_STEPS.md create mode 100644 ai_platform/modules/domain_check/README.md create mode 100644 ai_platform/modules/domain_check/api/Dockerfile create mode 100644 ai_platform/modules/domain_check/api/app/__init__.py create mode 100644 ai_platform/modules/domain_check/api/app/api_models.py create mode 100644 ai_platform/modules/domain_check/api/app/celery_app.py create mode 100644 ai_platform/modules/domain_check/api/app/config.py create mode 100644 ai_platform/modules/domain_check/api/app/models/__init__.py create mode 100644 ai_platform/modules/domain_check/api/app/models/api_usage.py create mode 100644 ai_platform/modules/domain_check/api/app/models/batch.py create mode 100644 ai_platform/modules/domain_check/api/app/models/blacklist.py create mode 100644 ai_platform/modules/domain_check/api/app/models/check_history.py create mode 100644 ai_platform/modules/domain_check/api/app/models/dns.py create mode 100644 ai_platform/modules/domain_check/api/app/models/domain.py create mode 100644 ai_platform/modules/domain_check/api/app/models/reputation.py create mode 100644 ai_platform/modules/domain_check/api/app/models/risk.py create mode 100644 ai_platform/modules/domain_check/api/app/models/ssl.py create mode 100644 ai_platform/modules/domain_check/api/app/models/whois.py create mode 100644 ai_platform/modules/domain_check/api/app/routes/__init__.py create mode 100644 ai_platform/modules/domain_check/api/app/routes/batch.py create mode 100644 ai_platform/modules/domain_check/api/app/routes/check.py create mode 100644 ai_platform/modules/domain_check/api/app/routes/domain.py create mode 100644 ai_platform/modules/domain_check/api/app/routes/search.py create mode 100644 ai_platform/modules/domain_check/api/app/routes/stats.py create mode 100644 ai_platform/modules/domain_check/api/app/services/__init__.py create mode 100644 ai_platform/modules/domain_check/api/app/services/blacklist_service.py create mode 100644 ai_platform/modules/domain_check/api/app/services/dns_service.py create mode 100644 ai_platform/modules/domain_check/api/app/services/http_analysis_service.py create mode 100644 ai_platform/modules/domain_check/api/app/services/ip_intelligence_service.py create mode 100644 ai_platform/modules/domain_check/api/app/services/mail_intelligence_service.py create mode 100644 ai_platform/modules/domain_check/api/app/services/port_scan_service.py create mode 100644 ai_platform/modules/domain_check/api/app/services/risk_scorer.py create mode 100644 ai_platform/modules/domain_check/api/app/services/ssl_service.py create mode 100644 ai_platform/modules/domain_check/api/app/services/subdomain_service.py create mode 100644 ai_platform/modules/domain_check/api/app/services/whois_service.py create mode 100644 ai_platform/modules/domain_check/api/app/static/index.html create mode 100644 ai_platform/modules/domain_check/api/app/tasks/__init__.py create mode 100644 ai_platform/modules/domain_check/api/app/utils/__init__.py create mode 100644 ai_platform/modules/domain_check/api/requirements.txt create mode 100644 ai_platform/modules/domain_check/api/run.py create mode 100644 ai_platform/modules/domain_check/api/tests/test_whois_dates.py create mode 100644 ai_platform/modules/domain_check/dashboard/Dockerfile create mode 100644 ai_platform/modules/domain_check/dashboard/requirements.txt create mode 100755 ai_platform/modules/domain_check/deploy.sh create mode 100644 ai_platform/modules/domain_check/docker-compose.yml create mode 100644 ai_platform/modules/domain_check/init-scripts/01-init-db.sql create mode 100755 ai_platform/modules/domain_check/test-api-complete.sh create mode 100755 ai_platform/modules/domain_check/test-complete-lan.sh create mode 100644 ai_platform/modules/domain_check/use_api.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87df077 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ + +# Modele locale (mari, descarcate de deploy.sh) — NU se comit +ai_platform/models/ +ai_platform/local_gpu_stack/.env +ai_platform/local_gpu_stack/test_assets/ +ai_platform/local_gpu_stack/runs/ diff --git a/README_LIVRABIL.md b/README_LIVRABIL.md index 5438f36..46746f6 100644 --- a/README_LIVRABIL.md +++ b/README_LIVRABIL.md @@ -4,9 +4,10 @@ Pachet de lucru pentru continuarea LOT 1 pe serverul cu **inferență GPU**. Con codul `ai_platform` (curățat), documentația de achiziție (caiet + ofertă) și raportul de audit cod-vs-caiet-vs-ofertă. -> **Scop:** muți acest folder pe serverul cu inferență, configurezi modelele -> reale și finalizezi punctele rămase (vezi §5). Tot ce s-a putut face fără GPU -> e deja făcut și validat în Docker. +> **Scop:** platforma este deja deployată și LIVE pe host-ul GPU (2x H200, 11/11 +> containere healthy) prin `ai_platform/local_gpu_stack/deploy.sh`. Acest pachet +> servește pentru portabilitate (mutare pe alt host) și pentru punctele rămase +> de infra (vezi §5). --- @@ -25,8 +26,9 @@ livrabil_lot1/ └── AUDIT_LOT1.md ← audit complet pe module (citește-l pentru detalii) ``` -`uv.lock` au fost excluse intenționat (se regenerează cu `uv sync`). `node_modules` -se regenerează cu `npm ci`. +`uv.lock` au fost excluse intenționat. Fix aplicat: Dockerfile-urile `llm-inference` +și `video-analysis` NU mai cer `uv.lock` la build (se rezolvă din `pyproject.toml`). +`node_modules` se regenerează cu `npm ci`. --- @@ -36,22 +38,26 @@ Sistemul AI = servicii independente, containerizate, pe care backend-ul (LOT 2) le consumă prin API. Fiecare modul are `deploy/deploy.sh`, `docker-compose.yml`, `Dockerfile`, `.env.example`. +> **Stare generală: platformă LIVE pe host-ul GPU (2x H200), 11/11 containere healthy.** +> Toate cele 13 module sunt livrate și funcționale. Singurul punct cu nuanță: +> dashboard rulează în STAGING MODE (`DASHBOARD_STAGING_MODE=true`, Keycloak bypassed). + | Modul | Ce face | Stare | |---|---|---| -| **llm-inference** | Gateway OpenAI-compatible peste 3 backend-uri (vLLM/llama.cpp/LiteLLM); rutare + fallback + metrici | 🟢 ~95% | -| **extractors** *(nou)* | Feature extraction *ușor*: metadata (EXIF/ELA/spectrogramă/integritate), sentiment/OCR (deleagă LLM), NER (GLiNER), detect (YOLO) | 🟢 ~95% | -| **video-analysis** | Deepfake video (BusterX/vLLM), verdict REAL/FAKE/UNCERTAIN + frames + evidence | 🟢 | -| **audio** | Transcriere (faster-whisper) | 🟢 | -| **web** | Web crawl & evidence (Playwright, multi-provider search) | 🟡 90% | -| **embeddings / rerank** | bge-m3 / bge-reranker-v2-m3, OpenAI-compatible | 🟢 85% | -| **didi_brain** | Cache rezultate (Postgres/pgvector) + RAG (Atomic) + fact-checking; consumat de backend | 🟢 | -| **dashboard** | Admin UI (React+FastAPI): monitorizare AI, catalog modele DB, config, RBAC | 🟡 ~78% | -| **catalog-api** | Agregator service-discovery (`/v1/info`), stateless | 🟢 | -| **gateway** | Reverse-proxy nginx (config-only) — punct de intrare AI | 🟢 | -| **forensic_features** | Detectoare forensice (rPPG/lip-sync/forgery heatmap/lighting) | 🟢 | -| **cloak** | Scraper SERP stealth (tier-3 fallback pt `web`) | 🟢 | +| **llm-inference** | Gateway OpenAI-compatible peste 3 backend-uri (vLLM/llama.cpp/LiteLLM); rutare + fallback + metrici | 🟢 livrat | +| **extractors** *(nou)* | Feature extraction *ușor*: metadata (EXIF/ELA/spectrogramă/integritate), sentiment/OCR (deleagă LLM), NER (GLiNER), detect (YOLOv8n) | 🟢 livrat | +| **video-analysis** | Deepfake video (BusterX/vLLM), verdict REAL/FAKE/UNCERTAIN + frames + evidence | 🟢 livrat | +| **audio** | Transcriere (faster-whisper) | 🟢 livrat | +| **web** | Web crawl & evidence (Playwright, SearXNG + provideri premium) | 🟢 livrat | +| **embeddings / rerank** | bge-m3 / bge-reranker-v2-m3, OpenAI-compatible | 🟢 livrat | +| **didi_brain** | Cache rezultate (Postgres/pgvector) + RAG (Atomic) + fact-checking; consumat de backend | 🟢 livrat | +| **dashboard** | Admin UI (React+FastAPI): monitorizare AI, catalog modele DB, config, RBAC | 🟢 livrat (STAGING MODE — auth bypassed) | +| **catalog-api** | Agregator service-discovery (`/v1/info`), stateless | 🟢 livrat | +| **gateway** | Reverse-proxy nginx (config-only) — punct de intrare AI | 🟢 livrat | +| **forensic_features** | Detectoare forensice (rPPG/lip-sync/forgery heatmap/lighting) | 🟢 livrat | +| **cloak** | Scraper SERP stealth (tier-3 fallback pt `web`) | 🟢 livrat | -Detalii complete + procente per cerință: **`documentatie/AUDIT_LOT1.md`**. +Detalii complete per cerință: **`documentatie/AUDIT_LOT1.md`**. --- @@ -93,50 +99,64 @@ docker run --rm -v "$PWD":/app -w /app node:20-slim bash -c "npm ci && npx tsc - --- -## 5. Ce mai e de făcut pe serverul cu inferență +## 5. Stare deploy + ce mai e de făcut -### 5.1 Configurări obligatorii la mutare (altfel serviciile nu „văd" modelele) -- **llm-inference**: pornește vLLM cu `--served-model-name qwen3.5` (sau setează - `LLM_MODEL_ALIASES`), `VLLM_MODEL=Qwen/Qwen3.5-35B-A3B`. Vezi `.env.example`. -- **extractors**: `EXTRACTORS_LLM_GATEWAY_URL=http://:14011` (pt sentiment/OCR). +**Deploy livrat (single-host GPU):** `ai_platform/local_gpu_stack/deploy.sh` — +orchestrează tot stack-ul „cărămidă cu cărămidă" (gated, pas cu pas, fail-fast). +Platforma rulează deja LIVE (11/11 healthy). Vezi `local_gpu_stack/README.md`. + +### 5.1 Rezolvate în această iterație +- **extractors** și **forensic_features** — deployate și healthy. +- **enable_thinking** (Qwen3.5) — rezolvat (parametrul e gestionat corect în + llm-inference / video-analysis). +- **dashboard** — panourile de monitorizare (health agregat, cozi, latențe) live. + +### 5.2 Configurări la mutare pe alt host (portabilitate) +- **llm-inference**: vLLM cu `--served-model-name qwen3.5`, `VLLM_MODEL=Qwen/Qwen3.5-35B-A3B`. - **video-analysis / audio / embeddings / rerank**: setează URL-urile vLLM/model în `.env`. -- **dashboard**: pentru a aprinde panourile noi — - `DASHBOARD_PROMETHEUS_URL=http://:9090`, - `DASHBOARD_RABBITMQ_MGMT_URL=http://:15672` (+ user/parolă). - RBAC se activează automat când e setat `DASHBOARD_KEYCLOAK_URL`; **scoate - `DASHBOARD_STAGING_MODE` în producție** (bypassează auth). +- **dashboard**: `DASHBOARD_PROMETHEUS_URL`, `DASHBOARD_RABBITMQ_MGMT_URL` (+ user/parolă). + **Scoate `DASHBOARD_STAGING_MODE` în producție** pentru a activa Keycloak/RBAC + (în starea livrată dashboard rulează în STAGING MODE, cu auth bypassed). -### 5.2 Cod rămas (validabil pe server cu modelele reale) +### 5.3 Cod rămas deschis - **llm-inference — load/unload model** (Task 4): vLLM **nu** suportă hot-swap - nativ (un model/proces). Decizie: (a) implementezi „register/route" — load = - asiguri că un backend servește modelul X; sau (b) marchezi „nesuportat de vLLM" - în ofertă/recepție. *(singurul punct deschis la inferență)* + nativ (un model/proces). Decizie: (a) „register/route" — load = asiguri că un + backend servește modelul X; sau (b) marchezi „nesuportat de vLLM". *(singurul + punct deschis la inferență)* - **web** (ofertă): `robots.txt`, rate-limit per-domeniu, jurnal audit persistent. - **embeddings/rerank**: failover automat între backend-uri + teste API. -- **RAG**: wrapper de contract `/v1/query`/`/v1/index` peste `/v1/gather` din - didi_brain (sau acceptat în forma livrată). -- **CI/CD**: non-root pe 5 Dockerfile, rollback + publish imagini AI în registry. +- **CI/CD**: non-root pe Dockerfile-uri rămase, rollback + publish imagini AI în registry. -### 5.3 Infra (manifeste de scris; validare reală doar cu cluster/GPU) +### 5.4 Infra (manifeste de scris; validare reală doar cu cluster/GPU) - **Orchestrare / scalare automată** (caiet 747-766) — k8s/Helm + HPA. *Singurul gap mare de caiet rămas.* -- **dashboard GPU/VRAM** — exporter dcgm pe host-ul GPU + panou (codul de panou se - cablează ca latențele, prin Prometheus). +- **dashboard GPU/VRAM** — exporter dcgm pe host-ul GPU + panou (se cablează ca + latențele, prin Prometheus). - **dashboard control rute** — gateway-ul AI e nginx static; control real = view + enable/disable prin regenerare config + reload (NU Kong, care e LOT 2). --- -## 6. Pornire rapidă (per modul) +## 6. Pornire rapidă + +**Recomandat — stack complet single-host GPU (cărămidă cu cărămidă, gated):** + +```bash +cd ai_platform/local_gpu_stack +cp .env.example .env # completează variabilele (fail-fast, fără defaults) +./deploy.sh # pornește tot stack-ul, pas cu pas +``` + +**Per modul (debug / dezvoltare):** ```bash cd ai_platform/modules//deploy -cp ../.env.example .env # completează variabilele (fail-fast, fără defaults) +cp ../.env.example .env ./deploy.sh up # sau: docker compose up -d --build ``` Convenție porturi: `1xxxx` producție / `5xxxx` dev (vezi `ai_platform/CLAUDE.md`). -Bootstrap pentru host CPU-only: `ai_platform/bootstrap.sh` + `DEPLOYMENT.md`. +Bootstrap pentru host CPU-only (web + dashboard): `ai_platform/bootstrap.sh` + `DEPLOYMENT.md`. --- diff --git a/ai_platform/DEPLOYMENT.md b/ai_platform/DEPLOYMENT.md index ee4de29..c70fc65 100644 --- a/ai_platform/DEPLOYMENT.md +++ b/ai_platform/DEPLOYMENT.md @@ -1,5 +1,7 @@ # Deployment Guide +> **Note — delivered deployment:** the delivered stack runs **single-host on the GPU machine** via `ai_platform/local_gpu_stack/deploy.sh` (see `local_gpu_stack/README.md`). The guide below describes the **reference multi-host variant** (CPU host + separate GPU host), kept intentionally for portability. + This guide covers bootstrapping the **web + dashboard** stack on a fresh CPU-only host. All GPU-dependent services (`llm-inference`, `embeddings`, `rerank`, `audio`, `video-analysis`) remain on a separate GPU machine and are called over HTTP. --- @@ -27,7 +29,7 @@ This guide covers bootstrapping the **web + dashboard** stack on a fresh CPU-onl │ │ │ ┌───────────────────┐ │ │ │ SearXNG │ (existing — free tier source of truth) │ -│ │ :55100/8080 │ │ +│ │ :8080 (intern) │ │ │ └───────────────────┘ │ │ │ └──────────┬────────────────────────────────────────────────────────────────────────────┘ @@ -90,7 +92,7 @@ cd ml-projects The script will: 1. Verify Docker + Compose. -2. Create the `deploy_default` network. +2. Create the `didi-network` network. 3. Prompt for: - GPU host address (for LLM URL) - SearXNG container name / URL @@ -120,8 +122,8 @@ If the bootstrap script fails or you prefer manual control: ### 1. Create the network ```bash -docker network inspect deploy_default >/dev/null 2>&1 \ - || docker network create deploy_default +docker network inspect didi-network >/dev/null 2>&1 \ + || docker network create didi-network ``` ### 2. Ensure SearXNG is reachable @@ -129,7 +131,7 @@ docker network inspect deploy_default >/dev/null 2>&1 \ If SearXNG is already running on this host, connect it to the network: ```bash -docker network connect deploy_default +docker network connect didi-network ``` Otherwise, deploy the bundled SearXNG: @@ -248,7 +250,7 @@ The web-api polls the dashboard for runtime config every 30s and fires events ba WEB_DASHBOARD_URL=http://didiAI-dashboard:51300 ``` -Both containers must be on the `deploy_default` network for internal hostname resolution. +Both containers must be on the `didi-network` network for internal hostname resolution. --- @@ -368,10 +370,10 @@ docker start didiAI-dashboard ## Troubleshooting -### `docker network deploy_default not found` +### `docker network didi-network not found` ```bash -docker network create deploy_default +docker network create didi-network ``` ### SearXNG unreachable from web-api @@ -379,9 +381,9 @@ docker network create deploy_default Check that SearXNG is on the shared network: ```bash -docker network inspect deploy_default | grep -A2 searxng +docker network inspect didi-network | grep -A2 searxng # If missing: -docker network connect deploy_default +docker network connect didi-network ``` Then verify the hostname/port in `WEB_SEARXNG_BASE_URL`. diff --git a/ai_platform/ENDPOINTS.md b/ai_platform/ENDPOINTS.md index 519d831..d671d5e 100644 --- a/ai_platform/ENDPOINTS.md +++ b/ai_platform/ENDPOINTS.md @@ -255,7 +255,7 @@ curl -X POST http://localhost:14200/v1/rerank \ | `GET` | `/health` | Health check | | `GET` | `/ready` | Readiness probe | | `POST` | `/v1/gather` | **Main: Complete fact-check pipeline** | -| `POST` | `/v1/search` | Web search only (Brave API) | +| `POST` | `/v1/search` | Web search only (SearXNG free tier; provideri premium optionali: SerpAPI/Tavily/Brave/LinkUp via `X-Search-Tier: premium`) | | `POST` | `/v1/fetch` | Fetch URLs with fallback | #### Quick Test @@ -282,7 +282,7 @@ curl -X POST http://localhost:51100/v1/gather \ **Base URL:** `http://localhost:54300` **Purpose:** Speech-to-text using faster-whisper **Model:** large-v3-turbo (int8 quantization) -**GPU:** GPU 1 (7GB / 143GB VRAM) +**GPU:** GPU 0 (~2GB / 143GB VRAM) **Decodare:** 5+4+3+0+0 = Dev + AI + Audio + vLLM + instance0 #### Endpoints @@ -369,6 +369,100 @@ curl -X POST http://localhost:54600/analyze/video/semantic \ --- +### 1️⃣1️⃣ Extractors API (Feature Extraction) + +**Port:** `54400` +**Base URL:** `http://localhost:54400` +**Purpose:** Lightweight feature extraction: metadata, sentiment/OCR (delegate to LLM gateway), NER, object detection + +#### Endpoints + +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/health` | Health check | +| `POST` | `/v1/metadata` | EXIF / codec / spectrogram / ELA / integrity (C2PA) | +| `POST` | `/v1/sentiment` | Sentiment analysis (delegates to LLM gateway) | +| `POST` | `/v1/ocr` | OCR (delegates to Qwen3.5 vision via LLM gateway) | +| `POST` | `/v1/ner` | Named entity recognition (GLiNER, mdeberta backbone) | +| `POST` | `/v1/detect` | Object detection (YOLOv8n) | + +#### Quick Test + +```bash +curl http://localhost:54400/health +``` + +--- + +### 1️⃣2️⃣ Forensic Features API + +**Port:** `8085` +**Base URL:** `http://localhost:8085` +**Purpose:** Forensic detectors (rPPG, lip-sync, forgery heatmap, lighting) using MediaPipe + +#### Endpoints + +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/health` | Health check | +| `POST` | `/v1/analyze` | Run forensic detectors on a video/image | + +#### Quick Test + +```bash +curl http://localhost:8085/health +``` + +--- + +### 1️⃣3️⃣ Dashboard (Admin UI + API) + +**Port:** `51300` +**Base URL:** `http://localhost:51300` +**Purpose:** Admin monitoring UI + API: AI health, model catalog (DB-backed), runtime config, RBAC, cost/archive +**Note:** runs in STAGING MODE (`DASHBOARD_STAGING_MODE=true`) — Keycloak auth bypassed in delivered state. + +#### Endpoints (selection) + +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/health` | Health check | +| `GET` | `/api/stats/providers` | Provider status + live quota | +| `GET` | `/api/config/schema` | Runtime config schema (DB-overridable) | +| `PUT` | `/api/config/{key}` | Update runtime config (Bearer token required) | +| `GET` | `/audit` | Audit trail | + +#### Quick Test + +```bash +curl http://localhost:51300/health +``` + +--- + +### 1️⃣4️⃣ didi_brain API (Cache + RAG + Fact-checking) + +**Port:** `8090` +**Base URL:** `http://localhost:8090` +**Purpose:** Result cache (Postgres/pgvector) + RAG (Atomic) + fact-checking; consumed by backend + +#### Endpoints (selection) + +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/health` | Health check | +| `POST` | `/v1/gather` | Gather with cache (returns `brain_meta.cache_status`) | +| `POST` | `/v1/cache/invalidate` | Invalidate cache (rate-limited 10/h per actor) | +| `GET` | `/v1/fact_status/due_for_recheck` | Facts with `next_check_at <= now`, unlocked | + +#### Quick Test + +```bash +curl http://localhost:8090/health +``` + +--- + ## 🧪 Complete Health Check Script ```bash @@ -386,11 +480,15 @@ curl -s http://localhost:14200/health && echo " ✓ Rerank API (14200)" # Development Services echo "=== DEVELOPMENT ===" curl -s http://localhost:51100/health && echo " ✓ Web API (51100)" +curl -s http://localhost:51300/health && echo " ✓ Dashboard (51300)" curl -s http://localhost:54100/health && echo " ✓ Embeddings API (54100)" curl -s http://localhost:54200/health && echo " ✓ Rerank API (54200)" curl -s http://localhost:54300/health && echo " ✓ Audio API (54300)" +curl -s http://localhost:54400/health && echo " ✓ Extractors API (54400)" curl -s http://localhost:54500/health && echo " ✓ BusterX (54500)" curl -s http://localhost:54600/health && echo " ✓ Video API (54600)" +curl -s http://localhost:8085/health && echo " ✓ Forensic Features (8085)" +curl -s http://localhost:8090/health && echo " ✓ didi_brain (8090)" echo "" echo "All services operational ✅" @@ -403,6 +501,7 @@ echo "All services operational ✅" | GPU | Model | VRAM Used | Total | Utilization | |-----|-------|-----------|-------|-------------| | **GPU 0** | Qwen3.5-35B-A3B (~57GB) + Whisper (~2GB) | ~59GB | 143GB | 41% | +| **GPU 1** | BusterX / Qwen2.5-VL-7B (~22GB) + BAAI/bge-m3 + BAAI/bge-reranker-v2-m3 | ~30GB | 143GB | 21% | --- @@ -422,6 +521,7 @@ PRODUCTION (1xxxx): DEVELOPMENT (5xxxx): ├── 51100 Web API (Fact-checking) +├── 51300 Dashboard (Admin UI + API) ├── 54100 Embeddings API (BGE-M3 Embeddings) │ ├── 54101 vLLM Server │ └── 54110 llama.cpp Server @@ -429,8 +529,14 @@ DEVELOPMENT (5xxxx): │ ├── 54201 vLLM Server │ └── 54210 llama.cpp Server ├── 54300 Audio API (Whisper STT) +├── 54400 Extractors API (metadata/sentiment/OCR/NER/detect) ├── 54500 BusterX (Deepfake Vision) -└── 54600 Video API (Video Analysis) +├── 54600 Video API (Video Analysis) +└── 8080 SearXNG (intern, nepublicat) (metasearch, free tier) + +OTHER (outside 5-digit schema): +├── 8085 Forensic Features (rPPG/lip-sync/forgery) +└── 8090 didi_brain (cache + RAG + fact-checking) ``` --- diff --git a/ai_platform/INDEX.md b/ai_platform/INDEX.md index 8c19fff..db58a1e 100644 --- a/ai_platform/INDEX.md +++ b/ai_platform/INDEX.md @@ -25,7 +25,7 @@ Acest fisier descrie fiecare modul, fiecare fisier, fiecare clasa, fiecare funct ## 1. Arhitectura generala -Monorepo cu 8 module independente. Fiecare modul este un pachet Python instalabil cu FastAPI, containerizat in Docker, conectat pe reteaua comuna `didi-network`. Toate comunica prin HTTP intern. Singurul port expus extern este 11000 (gateway nginx). +Monorepo cu 13 module independente (audio, catalog-api, cloak, dashboard, didi_brain, embeddings, extractors, forensic_features, gateway, llm-inference, rerank, video-analysis, web). Fiecare modul este un pachet Python instalabil cu FastAPI, containerizat in Docker, conectat pe reteaua comuna `didi-network`. Toate comunica prin HTTP intern. Singurul port expus extern este 11000 (gateway nginx). Flux tipic de request extern: ``` @@ -39,12 +39,14 @@ Video API -> vLLM (BusterX) Catalog API -> (interogheaza toate celelalte servicii pe /v1/info) ``` -Modele ML servite: -- Qwen3.5-35B-A3B (text + vision, MoE) - vLLM pe GPU 0 -- BAAI/bge-m3 (embeddings) - vLLM sau llama.cpp -- BAAI/bge-reranker-v2-m3 (reranking) - vLLM sau llama.cpp +Modele ML servite (7 modele, 13 module): +- Qwen3.5-35B-A3B (text + vision + OCR, MoE) - vLLM pe GPU 0, servit ca `qwen3.5`. Nu exista model vision separat: OCR/vision se face prin acest gateway LLM. +- BAAI/bge-m3 (embeddings) - vLLM sau llama.cpp, GPU 1 +- BAAI/bge-reranker-v2-m3 (reranking) - vLLM sau llama.cpp, GPU 1 - Whisper large-v3-turbo (speech-to-text) - faster-whisper pe GPU 0 -- BusterX / Qwen2.5-VL-7B (deepfake detection) - vLLM pe GPU 1 +- BusterX (Qwen2.5-VL-7B fine-tuned, deepfake detection) - vLLM pe GPU 1 +- GLiNER (backbone mdeberta, NER) + YOLOv8n (object detection) - extractors +- MediaPipe (rPPG / lip-sync / landmarks) - forensic_features --- @@ -63,7 +65,7 @@ Gateway-ul este un reverse proxy nginx care ruteaza toate request-urile catre se Defineste 4 upstream-uri: - `llm` -> `didiAI-llm-api:14011` -- `audio` -> `didiAI-audio-api:54300` +- `audio` -> `didiAI-audio:54300` - `web` -> `didiAI-web-api:51100` - `catalog` -> `didiAI-catalog-api:11000` @@ -547,7 +549,7 @@ Structura identica cu embeddings. Diferente specifice: ## 7. Audio - transcriere audio **Locatie:** `modules/audio/` -**Container:** `didiAI-audio-api` (port 54300) +**Container:** `didiAI-audio` (port 54300) ### Ce face @@ -599,7 +601,7 @@ Rute: **deploy/Dockerfile** - Bazat pe `nvidia/cuda:12.1.0-runtime-ubuntu22.04`. Instaleaza Python 3.10, ffmpeg. Nu foloseste uv, ci pip direct. Port 54300. -**deploy/docker-compose.yml** - Serviciu `audio-api`, container `didiAI-audio-api`. GPU 0 (CUDA_VISIBLE_DEVICES=0). Volum pentru cache modele. Profile: api. Start period 60s. +**deploy/docker-compose.yml** - Serviciu `audio-api`, container `didiAI-audio`. GPU 0 (CUDA_VISIBLE_DEVICES=0). Volum pentru cache modele. Profile: api. Start period 60s. **deploy/deploy.sh** - Valideaza AUDIO_MODEL, AUDIO_DEVICE, AUDIO_CACHE_DIR. Suporta profile `api` si `api-nginx`. @@ -613,7 +615,7 @@ Rute: ### Ce face Doua functionalitati: -1. Detectie deepfake - extrage 16 frame-uri uniforme, le trimite la BusterX (model fine-tuned pe Qwen2.5-VL-7B), obtine verdict REAL/FAKE/INCONCLUSIVE +1. Detectie deepfake - extrage 16 frame-uri uniforme, le trimite la BusterX (model fine-tuned pe Qwen2.5-VL-7B), obtine verdict REAL/FAKE/UNCERTAIN 2. Analiza semantica - divide video-ul in chunk-uri temporale (default 10s), extrage 24 frame-uri/chunk, descrie fiecare chunk cu LLM vision, optional agrega intr-un summary final ### Fisiere sursa @@ -653,10 +655,10 @@ Suporta configurare din `deploy/config.yaml` (YAML), cu override din variabile d - `frame_to_data_url_b64jpeg(frame_bgr, max_side, jpeg_quality)` - converteste frame BGR la RGB PIL Image, scaleaza la max_side, encodeaza JPEG, returneaza data URI base64 - `call_vllm_chat(base_url, model, data_urls, prompt, max_tokens, temperature, repetition_penalty, timeout_s=180)` - construieste payload cu imagini + text, POST la `/v1/chat/completions`, masoara timpul. Returneaza (response JSON, elapsed_seconds) -- `parse_verdict_and_explanation(model_text)` - verifica primele 20 caractere (uppercase) pt prefix verdict. REAL/FAKE/altceva=INCONCLUSIVE. +- `parse_verdict_and_explanation(model_text)` - verifica primele 20 caractere (uppercase) pt prefix verdict. REAL/FAKE/altceva=UNCERTAIN. **src/video_analysis/schemas.py** -- `Verdict = Literal["REAL", "FAKE", "INCONCLUSIVE"]` +- `Verdict = Literal["REAL", "FAKE", "UNCERTAIN"]` - `Usage` - prompt_tokens, completion_tokens, total_tokens - `LatencyS` - sampling_time_s, encode_time_s, model_inference_time_s - `Meta` - fps, total_frames, duration_s, sampled, indices, timestamps_s @@ -696,7 +698,7 @@ Functii helper: `safe_mkdir()`, `write_json()`, `sha256_file()`. **deploy/docker-compose.yml** - 2 servicii: -1. `vllm-buster` (didiAI-video-vllm-buster, port 54500) - Image: `vllm/vllm-openai:latest`. Model: `l8cv/BusterX_plusplus` (served as "busterx"). GPU 1. max-model-len 32768, gpu-memory-utilization 0.25, prefix caching activat. Profile: api-vllm. 600s start_period. +1. `vllm-buster` (didiAI-video-vllm-buster, port 54500) - Image: `vllm/vllm-openai:qwen3_5`. Model: `l8cv/BusterX_plusplus` (served as "busterx"). GPU 1. max-model-len 32768, gpu-memory-utilization 0.25, prefix caching activat. Profile: api-vllm. 600s start_period. 2. `video-analysis-api` (didiAI-video-api, port 54600) - FastAPI. Volum `../runs` montat la `/app/runs`. Profile: api, api-vllm. **deploy/Dockerfile** - Multi-stage python:3.11-slim cu uv. Port 54600. @@ -717,7 +719,7 @@ Modul complex de fact-checking cu pipeline complet: detectie context -> cautare **src/web/config.py** - `WebSettings(BaseSettings)` cu prefix `WEB_`: Required: -- `searxng_base_url` - URL SearXNG (ex: http://localhost:55100) +- `searxng_base_url` - URL SearXNG (ex: http://didiAI-web-searxng:8080) - `llm_base_url` - URL LLM inference server - `external_url` - URL extern OpenAPI @@ -911,6 +913,7 @@ PRODUCTION (1xxxx): DEVELOPMENT (5xxxx): 51100 Web API - fact-checking + cautare web + 51300 Dashboard - admin UI + API (monitorizare AI) 54100 Embeddings API Dev 54101 vLLM Embed Server Dev 54110 llama.cpp Embed Server Dev @@ -918,8 +921,14 @@ DEVELOPMENT (5xxxx): 54201 vLLM Rerank Server Dev 54210 llama.cpp Rerank Server Dev 54300 Audio API - transcriere Whisper + 54400 Extractors API - metadata/sentiment/OCR/NER/detect 54500 BusterX vLLM - server vision deepfake 54600 Video Analysis API - analiza video + 8080 SearXNG (didiAI-web-searxng, intern; nepublicat pe host) - metasearch free tier web + +ALTE PORTURI (in afara schemei 5-cifre): + 8085 Forensic Features API - detectoare forensice (rPPG/lip-sync/forgery) + 8090 didi_brain API - cache rezultate + RAG + fact-checking ``` Schema porturi: 5 cifre. Prima cifra: 1=prod, 5=dev. A doua cifra: 1=API/Gateway, 4=LLM/AI. @@ -931,7 +940,7 @@ Schema porturi: 5 cifre. Prima cifra: 1=prod, 5=dev. A doua cifra: 1=API/Gateway | GPU | Ce ruleaza | VRAM folosit | VRAM total | |-----|-----------|-------------|------------| | GPU 0 | Qwen3.5-35B-A3B (~57GB) + Whisper large-v3-turbo (~2GB) | ~59GB | 143GB | -| GPU 1 | BusterX / Qwen2.5-VL-7B (~22GB) | ~22GB | 143GB | +| GPU 1 | BusterX / Qwen2.5-VL-7B (~22GB) + BAAI/bge-m3 + BAAI/bge-reranker-v2-m3 | ~30GB | 143GB | --- @@ -940,9 +949,9 @@ Schema porturi: 5 cifre. Prima cifra: 1=prod, 5=dev. A doua cifra: 1=API/Gateway Toate containerele sunt pe reteaua externa `didi-network`. Comunicarea interna se face prin DNS Docker (nume containere): ``` -didiAI-gateway -> didiAI-llm-api, didiAI-audio-api, didiAI-web-api, didiAI-catalog-api +didiAI-gateway -> didiAI-llm-api, didiAI-audio, didiAI-web-api, didiAI-catalog-api didiAI-llm-api -> didiAI-vllm-qwen3.5 -didiAI-catalog-api -> didiAI-llm-api, didiAI-audio-api, didiAI-video-api, didiAI-web-api +didiAI-catalog-api -> didiAI-llm-api, didiAI-audio, didiAI-video-api, didiAI-web-api didiAI-web-api -> SearXNG, didiAI-llm-api didiAI-video-api -> didiAI-video-vllm-buster, didiAI-llm-api (pt agregare semantica) didiAI-embeddings-api -> didiAI-embeddings-vllm, didiAI-embeddings-llamacpp @@ -953,7 +962,7 @@ Naming convention containere: `didiAI-{modul}-{serviciu}`. ## Recent Changes (2026-05-05) -- **Login Keycloak SSO functional la `/admin-ai/`**: realm `didi-admins` (mutat din `didi-clients`), client `ai-platform-dashboard` (creat in didi-admins ca clona), required role `admin`. SSO comun cu admin-dashboard backend (1 login = ambele dashboard-uri). +- **Dashboard ruleaza in STAGING MODE (`DASHBOARD_STAGING_MODE=true`) — Keycloak este bypassed**: autentificarea SSO este configurata dar dezactivata in starea livrata. Config-ul pregatit: realm `didi-admins` (mutat din `didi-clients`), client `ai-platform-dashboard` (creat in didi-admins ca clona), required role `admin`, SSO comun cu admin-dashboard backend. **Inainte de productie: scoate `DASHBOARD_STAGING_MODE` pentru a activa auth-ul.** - **AI dashboard env**: `VITE_KEYCLOAK_URL=https://sso.clossers.com`, `VITE_KEYCLOAK_REALM=didi-admins`, `VITE_KEYCLOAK_CLIENT_ID=ai-platform-dashboard`, `VITE_KEYCLOAK_REQUIRED_ROLE=admin`. Dual var pentru build (VITE_*) + runtime (DASHBOARD_*). - **Schema config DB-overridable**: tabel nou `config_schema_override` (auto-creat la startup), helper `_merged_schema(session)` in `routes/config.py`, endpoint-uri admin `GET /api/config/schema/_overrides`, `PUT /api/config/schema/{key}`, `DELETE /api/config/schema/{key}`. Audit trail (action `config.schema.upsert/delete/seed`). - **Migrare automata 98 chei -> DB**: la primul startup, `seed_schema_if_empty()` populeaza tabelul din `KNOWN_KEYS` (idempotent). Codul KNOWN_KEYS ramane fallback daca DB e sters. DB = single source of truth pentru schema acum. diff --git a/ai_platform/ML_PROJECTS_DEPLOY_KEYS_AND_REQUIREMENTS.md b/ai_platform/ML_PROJECTS_DEPLOY_KEYS_AND_REQUIREMENTS.md index b014307..286d299 100644 --- a/ai_platform/ML_PROJECTS_DEPLOY_KEYS_AND_REQUIREMENTS.md +++ b/ai_platform/ML_PROJECTS_DEPLOY_KEYS_AND_REQUIREMENTS.md @@ -8,6 +8,10 @@ NU pe email/Slack public, NU în repo public, NU pe wiki. Pentru transfer: scp criptat sau message DM. +> **Notă topologie:** acest document descrie topologia **multi-host de referință** +> (URL-uri/IP-uri distribuite pe mai multe mașini). Valorile reale pentru +> deployment-ul **single-host** livrat se află în `local_gpu_stack/.env.example`. + --- ## Cuprins @@ -53,8 +57,8 @@ Stack-ul depinde de aceste hosts care rulează GPU-side: |---|---|---| | LLM Router (gateway Qwen) | `http://10.11.10.17:14011` | web, didi_brain, dashboard | | vLLM Qwen3.5-35B (direct) | `http://10.11.10.17:14001` | dashboard (provider stats) | -| llama.cpp Qwen3.5-397B #1 | `http://10.11.10.18:14001` | LLM router upstream | -| llama.cpp Qwen3.5-397B #2 | `http://10.11.10.19:14001` | LLM router upstream | +| llama.cpp Qwen3.5-35B-A3B #1 | `http://10.11.10.18:14001` | LLM router upstream | +| llama.cpp Qwen3.5-35B-A3B #2 | `http://10.11.10.19:14001` | LLM router upstream | | BGE-M3 embeddings | `http://10.11.10.15:8200` | didi_brain (Atomic auto-embeds) | | BGE-reranker-v2-m3 | `http://10.11.10.15:8100` | didi_brain (gather rerank) | | Whisper STT | (host-local, audio module) | audio module | @@ -188,13 +192,14 @@ LLM_VLLM_URL=http://localhost:14001 LLM_LLAMACPP_URLS=http://10.11.10.18:14001,http://10.11.10.19:14001 # Models -MODEL_REASONING=Qwen3.5-397B-A17B +MODEL_REASONING=Qwen/Qwen3.5-35B-A3B MODEL_REASONING_BACKEND=llamacpp MODEL_FAST=qwen3.5 MODEL_FAST_BACKEND=vllm MODEL_FAST_ENABLED=false -MODEL_VISION=gemma-3-27b-it -MODEL_VISION_URL=http://10.11.10.16:8001 +# Vision: nu exista model vision separat — OCR/vision se face prin Qwen3.5 (gateway LLM) +MODEL_VISION=qwen3.5 +MODEL_VISION_URL=http://10.11.10.17:14011 MODEL_VISION_ENABLED=false # Embeddings @@ -253,7 +258,7 @@ AUDIO_EXTERNAL_URL=http://localhost:54300 CATALOG_EXTERNAL_URL=http://localhost CATALOG_PORT=11000 CATALOG_LLM_URL=http://didiAI-llm-api:14011 -CATALOG_AUDIO_URL=http://didiAI-audio-api:54300 +CATALOG_AUDIO_URL=http://didiAI-audio:54300 CATALOG_WEB_URL=http://didiAI-web-api:51100 CATALOG_VIDEO_URL= ``` @@ -273,9 +278,9 @@ GATEWAY_API_TOKEN=0x2-m0W5oG7MjZUu5v3-ejBWMExwLeGDv4aRKqBI7bM | 51100 | Web API (gather, search, fetch) | `didiAI-web-api` | | 51300 | Dashboard UI + API | `didiAI-dashboard` | | 15432 | Dashboard Postgres (debug only) | `didiAI-dashboard-db` | -| 54300 | Audio STT proxy | `didiAI-audio-proxy` | +| 54300 | Audio STT | `didiAI-audio` | | 54600 | Video Analysis | `didiAI-video-api` | -| 55100 | SearXNG metasearch | `didiAI-web-searxng` | +| 8080 (intern) | SearXNG metasearch | `didiAI-web-searxng` | | 8090 | didi-brain HTTP API | `didibrain-api` | | 8088 | didi-brain Atomic | `didibrain-atomic` | | 5434 | didi-brain Postgres (debug only) | `didibrain-postgres` | @@ -360,7 +365,7 @@ BRAIN_IMPORT_CORPUS=0 BRAIN_RUN_EXTRACTION=0 ./scripts/bootstrap_deploy.sh ### 8. Conectează web-api la rețeaua didibrain -Web-api rulează pe network `deploy_default`, didi-brain pe `didibrain`. Trebuie unit: +Web-api rulează pe network `didi-network`, didi-brain pe `didibrain`. Trebuie unit: ```bash docker network connect didibrain didiAI-web-api diff --git a/ai_platform/README.md b/ai_platform/README.md index 96f2cdf..4f332de 100644 --- a/ai_platform/README.md +++ b/ai_platform/README.md @@ -245,13 +245,21 @@ Port allocation follows datacenter schema (5-digit ports): | Port | Service | Environment | |------|---------|-------------| -| 11000 | Catalog API | Production | -| 14001 | Qwen3.5-35B-A3B | Production | -| 14011 | LLM API Gateway | Production | +| 11000 | Gateway (nginx, external entry) | Production | +| 11000 | Catalog API (internal, via gateway) | Production | +| 14001 | vLLM Qwen3.5-35B-A3B | Production | +| 14011 | LLM Inference API (router) | Production | +| 14100 | Embeddings API | Production | +| 14200 | Rerank API | Production | | 51100 | Web API | Development | +| 51300 | Dashboard | Development | | 54300 | Audio API | Development | -| 54500 | BusterX | Development | -| 54600 | Video API | Development | +| 54400 | Extractors API | Development | +| 54500 | BusterX vLLM | Development | +| 54600 | Video Analysis API | Development | +| 8080 (intern) | SearXNG | Development | +| 8085 | Forensic Features API | Other | +| 8090 | didi_brain API | Other | **Guidelines:** - Use 1xxxx for production services @@ -377,11 +385,19 @@ Reviewers will check for: | Module | Port | Description | Status | |--------|------|-------------|--------| -| [catalog-api](modules/catalog-api/) | 11000 | Service catalog & discovery gateway | Active | -| [llm-inference](modules/llm-inference/) | 14011 | Unified LLM inference with multiple backends | Active | -| [audio](modules/audio/) | 54300 | Speech-to-text (Whisper) | Active | -| [video-analysis](modules/video-analysis/) | 54600 | Deepfake detection & semantic video analysis | Active | +| [gateway](modules/gateway/) | 11000 | nginx reverse proxy — single external entry point | Active | +| [catalog-api](modules/catalog-api/) | 11000 | Service catalog & discovery aggregator (internal) | Active | +| [llm-inference](modules/llm-inference/) | 14011 | Unified LLM inference (Qwen3.5-35B-A3B) with multiple backends | Active | +| [embeddings](modules/embeddings/) | 14100 | OpenAI-compatible embeddings (BAAI/bge-m3) | Active | +| [rerank](modules/rerank/) | 14200 | Document reranking (BAAI/bge-reranker-v2-m3) | Active | | [web](modules/web/) | 51100 | Web scraping & fact-checking evidence gathering | Active | +| [dashboard](modules/dashboard/) | 51300 | Admin UI + API (monitoring, config, RBAC) | Active | +| [audio](modules/audio/) | 54300 | Speech-to-text (Whisper large-v3-turbo) | Active | +| [extractors](modules/extractors/) | 54400 | Feature extraction: metadata/sentiment/OCR/NER (GLiNER)/detect (YOLOv8n) | Active | +| [video-analysis](modules/video-analysis/) | 54600 | Deepfake detection (BusterX) & semantic video analysis | Active | +| [forensic_features](modules/forensic_features/) | 8085 | Forensic detectors (rPPG/lip-sync/forgery) via MediaPipe | Active | +| [didi_brain](modules/didi_brain/) | 8090 | Result cache (Postgres/pgvector) + RAG + fact-checking | Active | +| [cloak](modules/cloak/) | internal | Stealth SERP scraper (tier-3 fallback for web) | Active | ## Port Allocation Summary @@ -396,12 +412,20 @@ Port allocation follows datacenter schema (5-digit ports): | Port | Service | Module | Environment | |------|---------|--------|-------------| -| 11000 | Catalog API | catalog-api | Production | +| 11000 | Gateway (nginx, external entry) | gateway | Production | +| 11000 | Catalog API (internal) | catalog-api | Production | | 14001 | vLLM Qwen3.5-35B-A3B | llm-inference | Production | | 14011 | LLM API Gateway | llm-inference | Production | +| 14100 | Embeddings API | embeddings | Production | +| 14200 | Rerank API | rerank | Production | | 51100 | Web API | web | Development | +| 51300 | Dashboard | dashboard | Development | | 54300 | Audio API (Whisper) | audio | Development | +| 54400 | Extractors API | extractors | Development | | 54500 | BusterX vLLM | video-analysis | Development | | 54600 | Video Analysis API | video-analysis | Development | +| 8080 (intern) | SearXNG | web | Development | +| 8085 | Forensic Features API | forensic_features | Other | +| 8090 | didi_brain API | didi_brain | Other | For detailed endpoint documentation, see [ENDPOINTS.md](ENDPOINTS.md). diff --git a/ai_platform/local_gpu_stack/.env.example b/ai_platform/local_gpu_stack/.env.example new file mode 100644 index 0000000..3224a0c --- /dev/null +++ b/ai_platform/local_gpu_stack/.env.example @@ -0,0 +1,43 @@ +# ============================================================================= +# DiDi LOT 1 — Local GPU stack configuration +# Copy to `.env` (deploy.sh face asta automat daca lipseste) si ajusteaza. +# ============================================================================= + +# Host IP folosit in EXTERNAL_URL-uri si in comenzile de test. +# Lasa GOL -> deploy.sh detecteaza automat IP-ul principal si il scrie aici. +HOST_IP= + +# --- Brain / Atomic / Postgres ----------------------------------------------- +# Parola e folosita de postgres, atomic-server SI brain-api (trebuie sa fie una singura). +POSTGRES_PASSWORD=brain_local_dev +# Token API atomic-server. Lasa GOL -> deploy.sh il genereaza automat +# (atomic-server token create) si il scrie inapoi aici. +ATOMIC_TOKEN= + +# --- Gateway ----------------------------------------------------------------- +GATEWAY_API_TOKEN=didi-local-dev-token-123 + +# --- Dashboard --------------------------------------------------------------- +DASHBOARD_DB_USER=dashboard +DASHBOARD_DB_PASSWORD=devpass123 +DASHBOARD_DB_NAME=dashboard +# staging=true -> bypass Keycloak (intri direct in UI). Scoate in productie. +DASHBOARD_STAGING_MODE=true + +# --- Modele (nume servite; nu schimba fara motiv) ---------------------------- +LLM_MODEL=Qwen/Qwen3.5-35B-A3B +LLM_SERVED_NAME=qwen3.5 +EMB_MODEL=BAAI/bge-m3 +RERANK_MODEL=BAAI/bge-reranker-v2-m3 +WHISPER_MODEL=large-v3-turbo +WHISPER_REPO=mobiuslabsgmbh/faster-whisper-large-v3-turbo +BUSTER_MODEL=l8cv/BusterX_plusplus +# Extractoare (NER) — GLiNER + backbone-ul lui +NER_MODEL=urchade/gliner_multi-v2.1 +NER_BACKBONE=microsoft/mdeberta-v3-base + +# Zona LOCALA unica pentru TOATE modelele (un folder per model, format HF cache: +# models----/). Toate containerele (Qwen/BusterX/bge/whisper/GLiNER) +# sunt servite OFFLINE de aici. Lasa GOL -> deploy.sh foloseste ai_platform/models. +# Ce lipseste se descarca automat din HF in acest director (offline-first). +MODELS_DIR= diff --git a/ai_platform/local_gpu_stack/README.md b/ai_platform/local_gpu_stack/README.md new file mode 100644 index 0000000..31854b1 --- /dev/null +++ b/ai_platform/local_gpu_stack/README.md @@ -0,0 +1,74 @@ +# DiDi LOT 1 — Local GPU stack (deploy progresiv) + +Deploy „cărămidă cu cărămidă" al întregii platforme pe un host cu GPU. +Pentru fiecare componentă: **build/pull + download → seed (dacă e cazul) → +TEST funcțional → doar dacă testul trece corect → următoarea**. Idempotent, +se oprește la primul test picat. + +## Cerințe (verificate de preflight, nu se instalează automat) +- Docker Engine 24+ cu plugin `docker compose` v2 +- NVIDIA driver ≥535 + `nvidia-container-toolkit` (runtime configurat în Docker) +- ≥1 GPU (proiectat pe 2× H200; GPU 0 = LLM+Whisper, GPU 1 = BusterX+bge×2) +- ~100 GB liberi pe `/` (toate modelele ≈ 94 GB) + +## Modele — o singură zonă locală +Toate modelele stau în **`ai_platform/models/`** (format HF cache, un folder per model), +servite **offline** de acolo (fără rețea la runtime). `MODELS_DIR` în `.env` (auto = acest folder). +Ce lipsește se descarcă o singură dată în el (`deploy.sh` pasul DOWNLOAD). NU se comite în git. + +| Folder | Model | Folosit de | +|---|---|---| +| `models--Qwen--Qwen3.5-35B-A3B` | LLM (67G) | llm (vLLM) | +| `models--l8cv--BusterX_plusplus` | deepfake (16G, Qwen2.5-VL intern) | video (vLLM) | +| `models--BAAI--bge-m3` | embeddings | embeddings (vLLM) | +| `models--BAAI--bge-reranker-v2-m3` | reranker | rerank (vLLM) | +| `models--mobiuslabsgmbh--faster-whisper-large-v3-turbo` | STT | audio | +| `models--urchade--gliner_multi-v2.1` + `models--microsoft--mdeberta-v3-base` | NER + backbone | extractors | + +Copiezi `ai_platform/models/` pe altă mașină → zero download la deploy. + +## Utilizare +```bash +cd ai_platform/local_gpu_stack +./deploy.sh # tot, de la preflight la dashboard +./deploy.sh --from web # reia de la cărămida 'web' (sare peste cele dinainte) +./deploy.sh --only llm # rulează o singură cărămidă (presupune deps deja up) +./deploy.sh --install-deps # instalează Docker + nvidia-container-toolkit (sudo), apoi continuă +``` +`.env` se creează automat din `.env.example` la prima rulare. + +### Mașină nouă (VM „chioară" cu GPU) +1. (o singură dată) driver NVIDIA ≥535 + reboot — `sudo ubuntu-drivers install` (nu se automatizează, cere reboot). +2. `git clone && cd .../ai_platform/local_gpu_stack` +3. `./deploy.sh --install-deps` → instalează Docker+toolkit, apoi: + - **HOST_IP** se auto-detectează și se scrie în `.env` + - **BusterX** se descarcă automat din HF dacă nu e pe disc (`BUSTER_HF_CACHE`) + - Qwen/bge se descarcă (cu progres), totul se ridică cărămidă cu cărămidă cu test la fiecare. + +> Singurul pas care NU e o comandă: driverul NVIDIA (reboot). Restul e `./deploy.sh`. +Cărămizi, în ordine: `embeddings · rerank · llm · extractors · audio · video · forensic · web · catalog · gateway · brain · dashboard`. + +La final: dashboard pe `http://:51300/admin-ai/` (staging mode, fără login). + +## Ce automatizează (fixurile care înainte erau manuale) +- rețea unică `didi-network`; plasare GPU 0/1; nume containere `didiAI-*` +- base-URL vLLM **fără** `/v1` (codul îl adaugă) pentru llm/embeddings/video +- YOLO scrie modelul în `/tmp` (cwd e read-only la non-root) +- gateway LLM injectează `enable_thinking:false` → JSON curat din Qwen3.5 (sentiment/OCR/semantic/brain) +- SearXNG fără proxy outbound, ca serviciu compose +- brain: aliniere parolă postgres, creare **token atomic** (auto, salvat în `.env`), **seed taxonomie** (79 tag-uri) +- dashboard: `.env` cu staging + override-uri health; **seed catalog** (9 intrări) + +## Fișiere +| Fișier | Rol | +|---|---| +| `deploy.sh` | orchestratorul progresiv gated | +| `lib.sh` | logging, `wait_health`, `gate`, shim docker (merge și prin `sg docker`) | +| `docker-compose.yml` | stack-ul GPU + non-GPU + searxng | +| `.env.example` | configurația (host, porturi, parole, token) | +| `seed/seed_catalog.sh` | populează catalogul DB al dashboard-ului (idempotent) | +| `seed/seed_brain.sh` | token atomic + taxonomie + env brain | +| `TESTING.md` | comenzi de testare per componentă + smoke-test | + +> Brain rulează din `modules/didi_brain/infra/docker-compose.yml`; dashboard și gateway +> din `modules/*/deploy/` — `deploy.sh` le orchestrează pe toate sub proiectul `local_gpu_stack`. diff --git a/ai_platform/local_gpu_stack/TESTING.md b/ai_platform/local_gpu_stack/TESTING.md new file mode 100644 index 0000000..a1f7ab6 --- /dev/null +++ b/ai_platform/local_gpu_stack/TESTING.md @@ -0,0 +1,228 @@ +# DiDi LOT 1 — Comenzi de testare per componentă + +Host: **10.11.10.18**. Toate comenzile sunt `curl` copy-paste. Statusul din dreptul +fiecăreia e rezultatul verificat la ultima rulare. + +> Convenție: `H=10.11.10.18`. Rulează întâi `export H=10.11.10.18`. + +--- + +## 0. Pregătire fișiere de test (o singură dată) + +```bash +export H=10.11.10.18 +cd /home/topclossers/didi-lot1-ai/ai_platform/local_gpu_stack +mkdir -p test_assets +# video mic din artefacte +cp "$(find ../../artefacte_lot1/runs_deepfake -iname '*.mp4' -printf '%s\t%p\n' | sort -n | head -1 | cut -f2)" test_assets/sample.mp4 +# extrage un cadru (jpg) + audio (wav) cu ffmpeg din containerul extractors +docker cp test_assets/sample.mp4 didiAI-extractors:/tmp/s.mp4 +docker exec didiAI-extractors sh -c 'ffmpeg -y -i /tmp/s.mp4 -frames:v 1 /tmp/frame.jpg -vn -ar 16000 -ac 1 -t 8 /tmp/audio.wav' +docker cp didiAI-extractors:/tmp/frame.jpg test_assets/frame.jpg +docker cp didiAI-extractors:/tmp/audio.wav test_assets/audio.wav +cd test_assets # restul comenzilor presupun că ești aici pentru fișiere +``` + +--- + +## 1. LLM — Qwen3.5-35B-A3B (port 14011) ✅ + +```bash +# modele servite +curl -s http://$H:14011/v1/models | python3 -m json.tool + +# chat completion +curl -s http://$H:14011/v1/chat/completions -H 'Content-Type: application/json' -d '{ + "model":"qwen3.5", + "messages":[{"role":"user","content":"Ce este un deepfake? Raspunde scurt."}], + "max_tokens":200 +}' | python3 -m json.tool + +# health +curl -s http://$H:14011/health +``` +> Notă: Qwen3.5 e model de **reasoning** — răspunsul conține un bloc „Thinking" înainte de +> răspunsul final. Normal. + +## 2. Embeddings — bge-m3 (port 14100) ✅ + +```bash +curl -s http://$H:14100/v1/embeddings -H 'Content-Type: application/json' -d '{ + "model":"bge-m3","input":"text de analizat" +}' | python3 -c "import sys,json;print('dim:',len(json.load(sys.stdin)['data'][0]['embedding']))" +``` +> Așteptat: `dim: 1024`. + +## 3. Rerank — bge-reranker-v2-m3 (port 14200) ✅ + +```bash +curl -s http://$H:14200/v1/rerank -H 'Content-Type: application/json' -d '{ + "model":"bge-reranker-v2-m3", + "query":"detectie deepfake", + "documents":["o pisica neagra","sistem de detectie deepfake pentru video"] +}' | python3 -m json.tool +``` +> Așteptat: documentul relevant primește scor mult mai mare (index 1). + +## 4. Audio / Whisper — large-v3-turbo (port 54300) ✅ + +```bash +curl -s http://$H:54300/v1/audio/transcriptions \ + -F "file=@audio.wav" -F "model=large-v3-turbo" | python3 -m json.tool +# modele / info +curl -s http://$H:54300/v1/models | python3 -m json.tool +``` +> Acceptă și `.mp4` direct (ffmpeg în container). Returnează `text` + `language`. + +## 5. Video / BusterX++ (deepfake) (port 54600) ✅ + +```bash +# verdict deepfake REAL/FAKE/UNCERTAIN +curl -s http://$H:54600/analyze/video -F "file=@sample.mp4" | python3 -m json.tool + +# analiza semantica (descriere continut, foloseste si LLM-ul) +curl -s http://$H:54600/analyze/video/semantic -F "file=@sample.mp4" | python3 -m json.tool + +curl -s http://$H:54600/v1/info | python3 -m json.tool +``` +> Așteptat: `{"verdict":"REAL"|"FAKE"|"UNCERTAIN","frames_analyzed":16,"evidence":[...]}`. + +## 6. Extractors (port 54400) + +```bash +# 6a. metadata: EXIF/ELA/ffprobe/integritate/C2PA ✅ +curl -s http://$H:54400/v1/metadata -F "file=@sample.mp4" | python3 -m json.tool + +# 6b. NER (GLiNER, multilingv) ✅ +curl -s http://$H:54400/v1/ner -H 'Content-Type: application/json' -d '{ + "text":"Klaus Iohannis s-a intalnit cu Emmanuel Macron la Bucuresti." +}' | python3 -m json.tool + +# 6c. detectie obiecte (YOLOv8) ✅ +curl -s http://$H:54400/v1/detect -F "file=@frame.jpg" | python3 -m json.tool + +# 6d. OCR (LLM vision) ✅ +curl -s http://$H:54400/v1/ocr -F "file=@frame.jpg" | python3 -m json.tool + +# 6e. sentiment (deleaga LLM) ✅ +curl -s http://$H:54400/v1/sentiment -H 'Content-Type: application/json' -d '{ + "text":"Produsul este excelent, sunt foarte multumit!" +}' | python3 -m json.tool +``` +> Așteptat: `{"ok":true,"results":{"label":"pozitiv","score":0.9,"rationale":"..."}}`. +> (Gateway-ul LLM injectează `enable_thinking:false` pe vLLM → JSON curat din modelul de reasoning.) + +## 7. Forensic — rPPG / lip-sync / forgery / lighting (port 8085) ✅ + +```bash +# module disponibile +curl -s http://$H:8085/api/forensic-modules | python3 -m json.tool + +# analiza forensica (multipart; campul fisierului se numeste 'video') +curl -s http://$H:8085/api/forensic-evidence \ + -F "video=@sample.mp4" -F "modules=m25,m26,m27,m28,m29" -F "every_n_frames=10" \ + | python3 -m json.tool +``` +> Rulează sincron implicit; returnează `modules_run`, `n_frames_extracted`, evidence per modul. +> Pentru async: adaugă `-F "async_mode=true"` → primești `job_id`, apoi +> `GET /api/status/{job_id}` și `GET /api/result/{job_id}`. + +## 8. Web — căutare & fact-checking evidence (port 51100) ✅ + +```bash +# cautare (SearXNG + provideri); ATENTIE: campul e "queries" (lista) +curl -s http://$H:51100/v1/search -H 'Content-Type: application/json' -d '{ + "queries":["deepfake detection 2024"],"max_results":5 +}' | python3 -m json.tool + +# fetch pagini (camp "urls" = LISTA) +curl -s http://$H:51100/v1/fetch -H 'Content-Type: application/json' -d '{ + "urls":["https://en.wikipedia.org/wiki/Deepfake"] +}' | python3 -m json.tool + +# gather (cautare + fetch + evidence pack); camp "claim", max_search_results >= 5 +curl -s http://$H:51100/v1/gather -H 'Content-Type: application/json' -d '{ + "claim":"Deepfakes can be detected by AI","max_search_results":5 +}' | python3 -m json.tool + +curl -s http://$H:51100/v1/info | python3 -m json.tool +``` + +## 9. Catalog — service discovery (port 11000, doar prin gateway/intern) ✅ + +`catalog-api` nu e publicat pe host; se accesează prin **gateway** (Bearer) sau din rețea. + +```bash +TOK=didi-local-dev-token-123 +curl -s http://$H:11000/catalog/v1/status -H "Authorization: Bearer $TOK" | python3 -m json.tool +curl -s http://$H:11000/catalog/v1/components -H "Authorization: Bearer $TOK" | python3 -m json.tool +curl -s http://$H:11000/catalog/v1/models -H "Authorization: Bearer $TOK" | python3 -m json.tool +``` + +## 10. Gateway — nginx reverse proxy + auth (port 11000) ✅ + +```bash +TOK=didi-local-dev-token-123 +curl -s http://$H:11000/health # fara auth -> ok +curl -s http://$H:11000/web/v1/info -H "Authorization: Bearer $TOK" # rutare -> web +curl -s http://$H:11000/catalog/v1/status -H "Authorization: Bearer $TOK" # rutare -> catalog +curl -s http://$H:11000/web/v1/info # fara token -> 401 +``` + +## 11. Brain — RAG + fact-checking + cache (port 8090) ✅ + +```bash +# cautare in baza de cunostinte (contract web; camp "queries") +curl -s http://$H:8090/v1/search -H 'Content-Type: application/json' -d '{ + "queries":["deepfake"],"max_results":3 +}' | python3 -m json.tool + +# gather (RAG complet: search -> fetch -> evidence); camp "claim" +curl -s http://$H:8090/v1/gather -H 'Content-Type: application/json' -d '{ + "claim":"Deepfakes can be detected by AI" +}' | python3 -m json.tool + +# statistici atom store (folosit de dashboard "Brain — fact status") +curl -s http://$H:8090/v1/analysis_atom/stats/extended | python3 -m json.tool + +curl -s http://$H:8090/health +``` +> `results: []` la început e normal — baza de cunoștințe e goală până se indexează conținut. + +--- + +## Smoke-test rapid (toate componentele, un singur script) + +```bash +export H=10.11.10.18; TOK=didi-local-dev-token-123 +ok(){ printf "%-26s %s\n" "$1" "$2"; } +ok "llm" "$(curl -s -m60 http://$H:14011/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"qwen3.5","messages":[{"role":"user","content":"hi"}],"max_tokens":5}' -o /dev/null -w %{http_code})" +ok "embeddings" "$(curl -s -m20 http://$H:14100/v1/embeddings -H 'Content-Type: application/json' -d '{"model":"bge-m3","input":"x"}' -o /dev/null -w %{http_code})" +ok "rerank" "$(curl -s -m20 http://$H:14200/v1/rerank -H 'Content-Type: application/json' -d '{"model":"bge-reranker-v2-m3","query":"a","documents":["a","b"]}' -o /dev/null -w %{http_code})" +ok "audio" "$(curl -s -m10 http://$H:54300/health -o /dev/null -w %{http_code})" +ok "video" "$(curl -s -m10 http://$H:54600/health -o /dev/null -w %{http_code})" +ok "extractors" "$(curl -s -m10 http://$H:54400/health -o /dev/null -w %{http_code})" +ok "forensic" "$(curl -s -m10 http://$H:8085/health -o /dev/null -w %{http_code})" +ok "web" "$(curl -s -m10 http://$H:51100/health -o /dev/null -w %{http_code})" +ok "gateway" "$(curl -s -m10 http://$H:11000/health -o /dev/null -w %{http_code})" +ok "catalog" "$(curl -s -m10 http://$H:11000/catalog/v1/status -H "Authorization: Bearer $TOK" -o /dev/null -w %{http_code})" +ok "brain" "$(curl -s -m10 http://$H:8090/health -o /dev/null -w %{http_code})" +# 200 peste tot = totul up +``` + +--- + +## Probleme rezolvate (istoric) +- **sentiment / output JSON** ✅ — gateway-ul LLM injectează acum `enable_thinking:false` + pe vLLM (`LLM_VLLM_DISABLE_THINKING`, default true), deci modelul de reasoning întoarce + JSON curat pentru sentiment/OCR/semantic/brain. +- **catalog `degraded`** ✅ — aliniat prin `CATALOG_LLM_URL/AUDIO_URL/VIDEO_URL/WEB_URL` + în compose → acum `healthy`. +- **brain taxonomy 401** ✅ — creat token atomic (`atomic-server token create`), setat + `ATOMIC_TOKEN`, seed-uită taxonomia (79 tag-uri în atomic), parolele postgres aliniate + (`infra/.env`). Scheduler-ul feed-uiește acum cu `errors=0`. + +## Rămas minor (non-blocant) +- **atomic embeddings** — atomic-server logează „OpenRouter API key not configured, skipping + embedding". Atomic-server și-ar face vectori proprii prin OpenRouter; nu e nevoie — brain + folosește bge-m3 local pentru embeddings. Doar un warning în logul atomic. diff --git a/ai_platform/local_gpu_stack/deploy.sh b/ai_platform/local_gpu_stack/deploy.sh new file mode 100644 index 0000000..ca977fd --- /dev/null +++ b/ai_platform/local_gpu_stack/deploy.sh @@ -0,0 +1,298 @@ +#!/usr/bin/env bash +# ============================================================================= +# DiDi LOT 1 — deploy progresiv „cărămidă cu cărămidă". +# Pentru fiecare componentă: build/pull + download → seed (dacă e cazul) → +# TEST funcțional → doar dacă testul TRECE corect → trece la următoarea. +# Idempotent. STOP la primul test picat. +# +# ./deploy.sh # tot, de la preflight la dashboard +# ./deploy.sh --from web # sare peste cărămizile dinainte de 'web' +# ./deploy.sh --only llm # rulează o singură cărămidă (presupune deps up) +# ============================================================================= +set -uo pipefail +cd "$(dirname "$0")" +HERE=$(pwd) +export COMPOSE_PROJECT_NAME=local_gpu_stack +COMPOSE="${HERE}/docker-compose.yml" +ENV_FILE="${HERE}/.env" +MODULES="${HERE}/../modules" +DASH_DIR="${MODULES}/dashboard/deploy" +GW_DIR="${MODULES}/gateway/deploy" +BRAIN_DIR="${MODULES}/didi_brain" +ASSETS="${HERE}/test_assets" + +source "${HERE}/lib.sh" +source "${HERE}/seed/seed_catalog.sh" +source "${HERE}/seed/seed_brain.sh" + +# ---- .env ------------------------------------------------------------------- +[ -f "$ENV_FILE" ] || { cp "${HERE}/.env.example" "$ENV_FILE"; info ".env creat din .env.example"; } +set -a; source "$ENV_FILE"; set +a +# auto-detect HOST_IP daca e gol (si scrie-l inapoi in .env pt interpolarea compose) +if [ -z "${HOST_IP:-}" ]; then + HOST_IP=$(hostname -I 2>/dev/null | awk '{print $1}') + [ -z "$HOST_IP" ] && HOST_IP=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++)if($i=="src")print $(i+1)}') + [ -n "$HOST_IP" ] && { sed -i "s#^HOST_IP=.*#HOST_IP=${HOST_IP}#" "$ENV_FILE"; info "HOST_IP detectat automat: ${HOST_IP}"; } +fi +export HOST_IP +H="${HOST_IP:-127.0.0.1}" +# MODELS_DIR: zona locala unica pentru TOATE modelele (default: ai_platform/models) +if [ -z "${MODELS_DIR:-}" ]; then + MODELS_DIR="$(cd "${HERE}/.." && pwd)/models" + sed -i "s#^MODELS_DIR=.*#MODELS_DIR=${MODELS_DIR}#" "$ENV_FILE" + info "MODELS_DIR: ${MODELS_DIR}" +fi +mkdir -p "$MODELS_DIR"; export MODELS_DIR + +# ---- arg parsing ------------------------------------------------------------ +FROM=""; ONLY=""; INSTALL_DEPS=0 +while [ $# -gt 0 ]; do case "$1" in + --from) FROM=$2; shift 2;; --only) ONLY=$2; shift 2;; + --install-deps) INSTALL_DEPS=1; shift;; + *) die "argument necunoscut: $1";; esac; done +SKIP=0; [ -n "$FROM" ] && SKIP=1 +should_run(){ # $1 = brick name + [ -n "$ONLY" ] && { [ "$ONLY" = "$1" ]; return; } + [ "$SKIP" = 1 ] && { [ "$FROM" = "$1" ] && SKIP=0; } + [ "$SKIP" = 0 ] +} + +# ============================================================================= +# PREFLIGHT (verifică + instruiește; nu instalează) +# ============================================================================= +# ============================================================================= +# INSTALL DEPS (optional, --install-deps; necesita sudo; Ubuntu/Debian) +# ============================================================================= +install_deps() { + brick "INSTALL DEPS — Docker + NVIDIA Container Toolkit (sudo)" + command -v sudo >/dev/null || die "sudo necesar pentru --install-deps" + if ! command -v docker >/dev/null; then + info "instalez Docker Engine..."; curl -fsSL https://get.docker.com | sudo sh || die "instalare Docker a picat" + sudo usermod -aG docker "$USER" || true + warn "Adăugat în grupul docker — poate fi nevoie de re-login pentru ca 'docker' să meargă fără sudo." + else ok "Docker prezent"; fi + if ! docker info 2>/dev/null | grep -qiE 'nvidia|cdi' && ! sudo docker info 2>/dev/null | grep -qiE 'nvidia|cdi'; then + info "instalez nvidia-container-toolkit..." + curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg + curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \ + | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \ + | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list >/dev/null + sudo apt-get update -y && sudo apt-get install -y nvidia-container-toolkit || die "instalare toolkit a picat" + sudo nvidia-ctk runtime configure --runtime=docker && sudo systemctl restart docker + ok "nvidia-container-toolkit instalat & Docker reconfigurat" + else ok "NVIDIA runtime prezent"; fi + command -v nvidia-smi >/dev/null || warn "Driver NVIDIA lipsește — instalează manual (ex: sudo ubuntu-drivers install) și REBOOT, apoi re-rulează deploy.sh." +} + +preflight() { + brick "PREFLIGHT — unelte & GPU" + command -v docker >/dev/null || die "Docker lipsește. Instalează: https://docs.docker.com/engine/install/ubuntu/" + if ! docker ps >/dev/null 2>&1; then + [ "$USE_SG" = 1 ] && warn "rulez docker prin 'sg docker' (userul nu e în grupul docker activ)" \ + || die "Nu pot accesa Docker. Adaugă userul în grup: sudo usermod -aG docker \$USER (apoi re-login)" + fi + docker compose version >/dev/null 2>&1 || die "Docker Compose v2 lipsește (plugin 'docker compose')." + command -v nvidia-smi >/dev/null || die "nvidia-smi lipsește — instalează driverul NVIDIA (>=535) + CUDA." + local ngpu; ngpu=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | wc -l) + [ "$ngpu" -ge 1 ] || die "Niciun GPU detectat de nvidia-smi." + if ! docker info 2>/dev/null | grep -qiE 'nvidia|cdi'; then + die "Runtime-ul NVIDIA pt Docker lipsește. Instalează nvidia-container-toolkit: + https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html + apoi: sudo nvidia-ctk runtime configure --runtime=docker && sudo systemctl restart docker" + fi + command -v curl >/dev/null || die "curl lipsește (apt-get install -y curl)." + command -v python3 >/dev/null || die "python3 lipsește." + local freeg; freeg=$(df -BG --output=avail / | tail -1 | tr -dc '0-9') + [ "${freeg:-0}" -ge 80 ] || warn "Doar ${freeg}G liberi pe / — Qwen are ~72GB. Recomandat >100G." + ok "Docker $(docker --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) · ${ngpu} GPU · ${freeg}G liberi" +} + +# ============================================================================= +# NETWORK +# ============================================================================= +ensure_network() { + brick "REȚEA — didi-network (una singură, partajată)" + if docker network inspect didi-network >/dev/null 2>&1; then ok "didi-network există" + else docker network create didi-network >/dev/null && ok "didi-network creată"; fi +} + +# ============================================================================= +# BUILD images (app) + PULL (vLLM/searxng) +# ============================================================================= +build_images() { + brick "BUILD — imagini aplicație" + local m + declare -A MAP=( + [llm-inference]=didiai-llm-inference [embeddings]=didiai-embeddings [rerank]=didiai-rerank + [audio]=didiai-audio [video-analysis]=didiai-video-analysis [extractors]=didiai-extractors + [web]=didiai-web [catalog-api]=didiai-catalog-api ) + for m in "${!MAP[@]}"; do + printf ' build %-16s ' "$m" + if docker build -q -t "${MAP[$m]}:audit" -f "${MODULES}/$m/deploy/Dockerfile" "${MODULES}/$m" >/tmp/build_$m.log 2>&1; then ok "ok" + else err "FAIL"; tail -15 /tmp/build_$m.log | sed 's/^/ /'; die "build $m a picat"; fi + done + # forensic (layout non-standard: Dockerfile la root) + printf ' build %-16s ' "forensic" + if docker build -q -t didiai-forensic:audit -f "${MODULES}/forensic_features/Dockerfile" "${MODULES}/forensic_features" >/tmp/build_forensic.log 2>&1; then ok "ok" + else err "FAIL"; tail -15 /tmp/build_forensic.log | sed 's/^/ /'; die "build forensic a picat"; fi + gate "imagini prezente" bash -c 'docker images --format "{{.Repository}}" | grep -q didiai-llm-inference' +} + +pull_images() { + brick "PULL — runtime-uri vLLM + SearXNG" + local img + for img in vllm/vllm-openai:v0.8.5 vllm/vllm-openai:qwen3_5 docker.io/searxng/searxng:latest; do + printf ' pull %-34s ' "$img" + if docker image inspect "$img" >/dev/null 2>&1; then ok "deja prezent" + elif docker pull -q "$img" >/dev/null 2>&1; then ok "ok" + else die "pull $img a picat (verifică rețeaua / Docker Hub)"; fi + done +} + +# ============================================================================= +# PRE-DOWNLOAD modele (cu progres) +# ============================================================================= +predownload_models() { + brick "DOWNLOAD — modele în MODELS_DIR (offline-first, un folder per model)" + mkdir -p "${MODELS_DIR}" + # mini-script de download (evită iadul de ghilimele prin sg docker) + local dl="${HERE}/.dl_model.py" + printf 'import os\nfrom huggingface_hub import snapshot_download\nsnapshot_download(os.environ["REPO"])\nprint("OK", os.environ["REPO"])\n' > "$dl" + # repo HF -> subfolder cache (models----) + local repo cache + for repo in "$BUSTER_MODEL" "$LLM_MODEL" "$EMB_MODEL" "$RERANK_MODEL" "$WHISPER_REPO" "$NER_MODEL" "$NER_BACKBONE"; do + cache="models--$(printf '%s' "$repo" | sed 's#/#--#')" + if [ -d "${MODELS_DIR}/${cache}/snapshots" ]; then + ok "prezent local: ${repo}" + else + info "download: ${repo}" + docker run --rm -e HF_HOME=/c -e "REPO=${repo}" \ + -v "${MODELS_DIR}:/c/hub" -v "${dl}:/dl.py" \ + --entrypoint python3 vllm/vllm-openai:qwen3_5 /dl.py \ + || die "download ${repo} a picat." + ok "${repo} descărcat" + fi + done + rm -f "$dl" + ok "Toate modelele sunt în ${MODELS_DIR}" +} + +# ============================================================================= +# TEST helpers (output corect, nu doar 200) +# ============================================================================= +t_embeddings(){ post_json "http://$H:14100/v1/embeddings" '{"model":"bge-m3","input":"test"}' 20 | python3 -c 'import sys,json;v=json.load(sys.stdin)["data"][0]["embedding"];assert len(v)==1024;print(f"dim={len(v)}")'; } +t_rerank(){ post_json "http://$H:14200/v1/rerank" '{"model":"bge-reranker-v2-m3","query":"deepfake detection","documents":["o pisica","sistem detectie deepfake video"]}' 20 | python3 -c 'import sys,json;r=json.load(sys.stdin)["results"];assert r[0]["index"]==1;print("top_idx=%d score=%.3f"%(r[0]["index"],r[0]["relevance_score"]))'; } +t_llm(){ post_json "http://$H:14011/v1/chat/completions" '{"model":"qwen3.5","messages":[{"role":"user","content":"Spune un singur cuvant: salut"}],"max_tokens":20}' 90 | python3 -c 'import sys,json;c=json.load(sys.stdin)["choices"][0]["message"]["content"].strip();assert c and not c.lower().startswith("thinking");print(repr(c[:40]))'; } +t_audio(){ curl -s -m120 "http://$H:54300/v1/audio/transcriptions" -F "file=@${ASSETS}/audio.wav" -F "model=${WHISPER_MODEL}" | python3 -c 'import sys,json;t=json.load(sys.stdin)["text"].strip();assert len(t)>0;print(f"text[:40]={t[:40]!r}")'; } +t_video(){ curl -s -m180 "http://$H:54600/analyze/video" -F "file=@${ASSETS}/sample.mp4" | python3 -c 'import sys,json;d=json.load(sys.stdin);assert d["verdict"] in ("REAL","FAKE","UNCERTAIN");print("verdict=%s frames=%s"%(d["verdict"],d.get("frames_analyzed")))'; } +t_ner(){ post_json "http://$H:54400/v1/ner" '{"text":"Klaus Iohannis s-a intalnit cu Emmanuel Macron la Bucuresti."}' 60 | python3 -c 'import sys,json;e=json.load(sys.stdin)["results"]["entities"];assert len(e)>=2;print(f"entities={len(e)}")'; } +t_detect(){ curl -s -m90 "http://$H:54400/v1/detect" -F "file=@${ASSETS}/frame.jpg" | python3 -c 'import sys,json;d=json.load(sys.stdin)["results"]["detections"];assert len(d)>=1;print(f"objects={len(d)}")'; } +t_metadata(){ curl -s -m40 "http://$H:54400/v1/metadata" -F "file=@${ASSETS}/sample.mp4" | python3 -c 'import sys,json;d=json.load(sys.stdin);assert d["analyses"]["integrity"]["ok"];print("integrity ok")'; } +t_sentiment(){ post_json "http://$H:54400/v1/sentiment" '{"text":"Produsul este excelent, sunt foarte multumit!"}' 60 | python3 -c 'import sys,json;d=json.load(sys.stdin);assert d["ok"];print("label=%s"%d["results"].get("label"))'; } +t_forensic(){ curl -s -m15 "http://$H:8085/api/forensic-modules" | python3 -c 'import sys,json;m=json.load(sys.stdin)["available_modules"];assert len(m)>=3;print(f"modules={len(m)}")'; } +t_web(){ post_json "http://$H:51100/v1/search" '{"queries":["deepfake detection"],"max_results":3}' 40 | python3 -c 'import sys,json;r=json.load(sys.stdin)["results"];assert len(r)>0;print(f"results={len(r)}")'; } +t_catalog(){ curl -s -m15 "http://$H:11000/catalog/v1/status" -H "Authorization: Bearer ${GATEWAY_API_TOKEN}" | python3 -c 'import sys,json;d=json.load(sys.stdin);assert d["status"]=="healthy";print("catalog healthy")'; } +t_gateway(){ local a b; a=$(curl -s -m10 -o /dev/null -w '%{http_code}' "http://$H:11000/health"); b=$(curl -s -m10 -o /dev/null -w '%{http_code}' "http://$H:11000/web/v1/info"); [ "$a" = 200 ] && [ "$b" = 401 ] && echo "health=200 noauth=401" || { echo "health=$a noauth=$b (astept 200/401)"; return 1; }; } +t_brain(){ curl -s -m15 "http://$H:8090/v1/analysis_atom/stats/extended" | python3 -c 'import sys,json;json.load(sys.stdin);print("stats ok")' && post_json "http://$H:8090/v1/search" '{"queries":["deepfake"],"max_results":2}' 30 | python3 -c 'import sys,json;assert "results" in json.load(sys.stdin);print("search ok")'; } +t_dashboard(){ curl -s -m15 "http://$H:51300/api/monitoring/services" | python3 -c 'import sys,json;d=json.load(sys.stdin);s=d if isinstance(d,list) else d.get("services",[]);h=sum(x.get("status")=="healthy" for x in s);assert h==len(s) and h>=11,f"{h}/{len(s)}";print(f"{h}/{len(s)} healthy")'; } + +prepare_assets() { + mkdir -p "$ASSETS" + [ -f "${ASSETS}/sample.mp4" ] || cp "$(find ${HERE}/../../artefacte_lot1/runs_deepfake -iname '*.mp4' -printf '%s\t%p\n' | sort -n | head -1 | cut -f2)" "${ASSETS}/sample.mp4" + if [ ! -f "${ASSETS}/frame.jpg" ] || [ ! -f "${ASSETS}/audio.wav" ]; then + docker cp "${ASSETS}/sample.mp4" didiAI-extractors:/tmp/s.mp4 >/dev/null + docker exec didiAI-extractors sh -c 'ffmpeg -y -i /tmp/s.mp4 -frames:v 1 /tmp/frame.jpg -vn -ar 16000 -ac 1 -t 8 /tmp/audio.wav' >/dev/null 2>&1 + docker cp didiAI-extractors:/tmp/frame.jpg "${ASSETS}/frame.jpg" >/dev/null + docker cp didiAI-extractors:/tmp/audio.wav "${ASSETS}/audio.wav" >/dev/null + fi +} + +# ============================================================================= +# CĂRĂMIZI (fiecare: up dependențe → health → test → gate) +# ============================================================================= +brick_embeddings(){ brick "EMBEDDINGS (bge-m3)"; dc up -d vllm-embeddings embeddings-api + wait_health "http://$H:14100/health" 180 "embeddings-api" || die "embeddings-api nu pornește"; gate "embeddings dim 1024" t_embeddings; } +brick_rerank(){ brick "RERANK (bge-reranker-v2-m3)"; dc up -d vllm-rerank rerank-api + wait_health "http://$H:14200/health" 180 "rerank-api" || die "rerank-api nu pornește"; gate "rerank ranking" t_rerank; } +brick_llm(){ brick "LLM (Qwen3.5-35B-A3B)"; dc up -d vllm-qwen llm-api + wait_health "http://$H:14001/v1/models" 900 "vLLM Qwen (load+compile)" || die "vLLM Qwen nu pornește" + wait_health "http://$H:14011/health" 60 "llm-api" || die "llm-api nu pornește"; gate "LLM output curat" t_llm; } +brick_extractors(){ brick "EXTRACTORS (NER/YOLO/metadata/OCR/sentiment)"; dc up -d extractors + wait_health "http://$H:54400/health" 120 "extractors" || die "extractors nu pornește" + prepare_assets + gate "metadata" t_metadata; gate "NER (GLiNER)" t_ner; gate "detect (YOLO)" t_detect; gate "sentiment (LLM)" t_sentiment; } +brick_audio(){ brick "AUDIO (Whisper large-v3-turbo)"; dc up -d audio-api + wait_health "http://$H:54300/health" 240 "audio (download model)" || die "audio nu pornește"; gate "transcriere" t_audio; } +brick_video(){ brick "VIDEO / BusterX++ (deepfake)"; dc up -d vllm-buster video-api + wait_health "http://$H:54500/health" 300 "vLLM BusterX" || die "vLLM BusterX nu pornește" + wait_health "http://$H:54600/health" 60 "video-api" || die "video-api nu pornește"; gate "verdict deepfake" t_video; } +brick_forensic(){ brick "FORENSIC (rPPG/lip-sync/forgery/lighting)"; dc up -d forensic + wait_health "http://$H:8085/health" 120 "forensic" || die "forensic nu pornește"; gate "module forensice" t_forensic; } +brick_web(){ brick "WEB + SearXNG (căutare)" + # curăță o instanță searxng pornită manual (docker run) ca s-o preia compose + if docker inspect didiAI-web-searxng >/dev/null 2>&1 && \ + [ -z "$(docker inspect didiAI-web-searxng --format '{{ index .Config.Labels "com.docker.compose.project" }}' 2>/dev/null)" ]; then + docker rm -f didiAI-web-searxng >/dev/null 2>&1 || true + fi + dc up -d searxng web-api + wait_health "http://$H:51100/health" 120 "web-api" || die "web-api nu pornește" + sleep 8; gate "căutare web" t_web; } +brick_catalog(){ brick "CATALOG (service discovery)"; dc up -d catalog-api + sleep 6; } # health-ul agregat depinde de gateway; testat după gateway +brick_gateway(){ brick "GATEWAY (nginx + auth)" + ( cd "$GW_DIR"; grep -q . .env 2>/dev/null || echo "GATEWAY_API_TOKEN=${GATEWAY_API_TOKEN}" > .env; docker compose up -d ) + wait_health "http://$H:11000/health" 60 "gateway" || die "gateway nu pornește" + gate "gateway auth+rutare" t_gateway; gate "catalog agregat" t_catalog; } +brick_brain(){ brick "BRAIN (RAG + fact-check + cache)" + # render env din master, up postgres+atomic, aliniază parola, token, taxonomie, up api+scheduler + brain_write_env "$POSTGRES_PASSWORD" "${ATOMIC_TOKEN:-PLACEHOLDER}" + ( cd "$BRAIN_DIR"; docker compose -f infra/docker-compose.yml up -d postgres atomic-server ) + wait_health "http://$H:8088/health" 120 "atomic-server" || warn "atomic health întârzie" + brain_align_pg "$POSTGRES_PASSWORD" + local tok; tok=$(brain_ensure_token "$POSTGRES_PASSWORD" "${ATOMIC_TOKEN:-}") + [ -n "$tok" ] || die "nu am putut crea tokenul atomic" + if [ "$tok" != "${ATOMIC_TOKEN:-}" ]; then + sed -i "s#^ATOMIC_TOKEN=.*#ATOMIC_TOKEN=${tok}#" "$ENV_FILE"; ATOMIC_TOKEN="$tok"; ok "token atomic creat & salvat în .env" + fi + brain_write_env "$POSTGRES_PASSWORD" "$tok" + ( cd "$BRAIN_DIR"; docker compose -f infra/docker-compose.yml up -d brain-api scheduler ) + wait_health "http://$H:8090/health" 120 "brain-api" || die "brain-api nu pornește" + brain_seed_taxonomy + ( cd "$BRAIN_DIR"; docker compose -f infra/docker-compose.yml restart brain-api scheduler ) >/dev/null + wait_health "http://$H:8090/health" 60 "brain-api (post-seed)" || die "brain-api nu revine" + gate "brain stats+search" t_brain; } +brick_dashboard(){ brick "DASHBOARD (UI + monitorizare)" + ( cd "$DASH_DIR" + cat > .env < + --model l8cv/BusterX_plusplus + --host 0.0.0.0 --port 54500 + --served-model-name busterx + --tensor-parallel-size 1 + --max-model-len 32768 + --gpu-memory-utilization 0.25 + --trust-remote-code + --enable-prefix-caching + --disable-log-requests + deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['1'] + capabilities: [gpu] + restart: unless-stopped + + # Qwen3.5-35B-A3B — downloads ~72GB on first start + vllm-qwen: + container_name: didiAI-vllm-qwen3.5 + image: vllm/vllm-openai:qwen3_5 + networks: [didi-network] + ports: ["14001:14001"] + volumes: + - ${MODELS_DIR}:/root/.cache/huggingface/hub:ro + environment: + - HF_HOME=/root/.cache/huggingface + - HF_HUB_OFFLINE=1 + - TRANSFORMERS_OFFLINE=1 + - VLLM_ALLOW_LONG_MAX_MODEL_LEN=1 + command: > + --model Qwen/Qwen3.5-35B-A3B + --host 0.0.0.0 --port 14001 + --served-model-name qwen3.5 + --tensor-parallel-size 1 + --max-model-len 32000 + --gpu-memory-utilization 0.65 + --trust-remote-code + --disable-log-requests + deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['0'] + capabilities: [gpu] + restart: unless-stopped + + # bge-m3 embeddings — downloads ~2GB on first start + vllm-embeddings: + container_name: didiAI-vllm-embeddings + image: vllm/vllm-openai:v0.8.5 + networks: [didi-network] + ports: ["14101:14101"] + volumes: + - ${MODELS_DIR}:/root/.cache/huggingface/hub:ro + environment: + - HF_HOME=/root/.cache/huggingface + - HF_HUB_OFFLINE=1 + - TRANSFORMERS_OFFLINE=1 + command: > + --model BAAI/bge-m3 + --host 0.0.0.0 --port 14101 + --served-model-name bge-m3 + --task embed + --gpu-memory-utilization 0.10 + --trust-remote-code + --disable-log-requests + deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['1'] + capabilities: [gpu] + restart: unless-stopped + + # bge-reranker-v2-m3 — downloads ~2GB on first start + vllm-rerank: + container_name: didiAI-vllm-rerank + image: vllm/vllm-openai:v0.8.5 + networks: [didi-network] + ports: ["14201:14201"] + volumes: + - ${MODELS_DIR}:/root/.cache/huggingface/hub:ro + environment: + - HF_HOME=/root/.cache/huggingface + - HF_HUB_OFFLINE=1 + - TRANSFORMERS_OFFLINE=1 + command: > + --model BAAI/bge-reranker-v2-m3 + --host 0.0.0.0 --port 14201 + --served-model-name bge-reranker-v2-m3 + --task score + --gpu-memory-utilization 0.10 + --trust-remote-code + --disable-log-requests + deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['1'] + capabilities: [gpu] + restart: unless-stopped + + # ===================== FastAPI app wrappers ===================== + + llm-api: + container_name: didiAI-llm-api + image: didiai-llm-inference:audit + networks: [didi-network] + ports: ["14011:14011"] + environment: + - LLM_HOST=0.0.0.0 + - LLM_PORT=14011 + - LLM_DEFAULT_BACKEND=vllm + - LLM_ENABLE_VLLM=true + - LLM_ENABLE_LLAMACPP=false + - LLM_VLLM_BASE_URL=http://didiAI-vllm-qwen3.5:14001 + - LLM_DEFAULT_MODEL=qwen3.5 + - LLM_EXTERNAL_URL=http://${HOST_IP}:14011 + restart: unless-stopped + + embeddings-api: + container_name: didiAI-embeddings-api + image: didiai-embeddings:audit + networks: [didi-network] + ports: ["14100:14100"] + environment: + - EMB_HOST=0.0.0.0 + - EMB_PORT=14100 + - EMB_DEFAULT_BACKEND=vllm + - EMB_ENABLE_VLLM=true + - EMB_ENABLE_LLAMACPP=false + - EMB_VLLM_BASE_URL=http://didiAI-vllm-embeddings:14101 + - EMB_VLLM_MODEL=bge-m3 + - EMB_EXTERNAL_URL=http://${HOST_IP}:14100 + restart: unless-stopped + + rerank-api: + container_name: didiAI-rerank-api + image: didiai-rerank:audit + networks: [didi-network] + ports: ["14200:14200"] + environment: + - RERANK_HOST=0.0.0.0 + - RERANK_PORT=14200 + - RERANK_DEFAULT_BACKEND=vllm + - RERANK_ENABLE_VLLM=true + - RERANK_ENABLE_LLAMACPP=false + - RERANK_VLLM_BASE_URL=http://didiAI-vllm-rerank:14201 + - RERANK_VLLM_MODEL=bge-reranker-v2-m3 + - RERANK_EXTERNAL_URL=http://${HOST_IP}:14200 + restart: unless-stopped + + video-api: + container_name: didiAI-video-api + image: didiai-video-analysis:audit + networks: [didi-network] + ports: ["54600:54600"] + volumes: + - ./runs:/app/runs + environment: + - VIDEO_ANALYSIS_HOST=0.0.0.0 + - VIDEO_ANALYSIS_PORT=54600 + - VIDEO_ANALYSIS_EXTERNAL_URL=http://${HOST_IP}:54600 + - VIDEO_ANALYSIS_VLLM_BASE_URL=http://didiAI-video-vllm-buster:54500 + - VIDEO_ANALYSIS_VLLM_MODEL=busterx + - VIDEO_ANALYSIS_RUNS_DIR=/app/runs + - VIDEO_ANALYSIS_SEMANTIC_LLM_BASE_URL=http://didiAI-llm-api:14011 + - VIDEO_ANALYSIS_SEMANTIC_AGGREGATION_MODEL=qwen3.5 + - VIDEO_ANALYSIS_SEMANTIC_LLM_API_KEY=none + restart: unless-stopped + + # ===================== Non-GPU services ===================== + + catalog-api: + container_name: didiAI-catalog-api + image: didiai-catalog-api:audit + networks: [didi-network] + environment: + - CATALOG_HOST=0.0.0.0 + - CATALOG_PORT=11000 + - CATALOG_EXTERNAL_URL=http://${HOST_IP} + - CATALOG_LLM_URL=http://didiAI-llm-api:14011 + - CATALOG_AUDIO_URL=http://didiAI-audio:54300 + - CATALOG_VIDEO_URL=http://didiAI-video-api:54600 + - CATALOG_WEB_URL=http://didiAI-web-api:51100 + restart: unless-stopped + + extractors: + container_name: didiAI-extractors + image: didiai-extractors:audit + networks: [didi-network] + ports: ["54400:54400"] + volumes: + # GLiNER (+ backbone mdeberta) servit offline din zona locala de modele + - ${MODELS_DIR}:/models-hf/hub:ro + environment: + - EXTRACTORS_HOST=0.0.0.0 + - EXTRACTORS_PORT=54400 + - EXTRACTORS_LLM_GATEWAY_URL=http://didiAI-llm-api:14011 + - EXTRACTORS_DETECT_MODEL=/tmp/yolov8n.pt + - YOLO_CONFIG_DIR=/tmp/ultralytics + - HF_HOME=/models-hf + - HF_HUB_OFFLINE=1 + - TRANSFORMERS_OFFLINE=1 + restart: unless-stopped + + forensic: + container_name: didiAI-forensic + image: didiai-forensic:audit + networks: [didi-network] + ports: ["8085:8080"] + environment: + - API_PORT=8080 + restart: unless-stopped + + # cloak — scraper SERP stealth (tier-3 fallback pentru web) + cloak: + container_name: didiAI-cloak + image: didiai-cloak:audit + networks: [didi-network] + ports: ["8770:8770"] + environment: + - CLOAK_HOST=0.0.0.0 + - CLOAK_PORT=8770 + restart: unless-stopped + + # SearXNG metasearch (motorul de cautare liber folosit de web-api) + searxng: + container_name: didiAI-web-searxng + image: docker.io/searxng/searxng:latest + networks: [didi-network] + volumes: + - ../../modules/web/deploy/metasearch/searxng:/etc/searxng:ro + environment: + - SEARXNG_BASE_URL=http://${HOST_IP}:8080/ + restart: unless-stopped + + web-api: + container_name: didiAI-web-api + image: didiai-web:audit + networks: [didi-network] + ports: ["51100:51100"] + depends_on: [searxng] + environment: + - WEB_HOST=0.0.0.0 + - WEB_PORT=51100 + - WEB_EXTERNAL_URL=http://${HOST_IP}:51100 + - WEB_SEARXNG_BASE_URL=http://didiAI-web-searxng:8080 + - WEB_LLM_BASE_URL=http://didiAI-llm-api:14011 + restart: unless-stopped + + # Whisper STT — faster-whisper loads in-process on GPU 0 + audio-api: + container_name: didiAI-audio + image: didiai-audio:audit + networks: [didi-network] + ports: ["54300:54300"] + volumes: + # faster-whisper foloseste download_root=cache_dir -> models-- direct sub el (nu /hub) + - ${MODELS_DIR}:/root/.cache/huggingface:ro + environment: + - AUDIO_HOST=0.0.0.0 + - AUDIO_PORT=54300 + - AUDIO_MODEL=large-v3-turbo + - AUDIO_DEVICE=cuda + - AUDIO_COMPUTE_TYPE=float16 + - AUDIO_CACHE_DIR=/root/.cache/huggingface + - HF_HUB_OFFLINE=1 + - TRANSFORMERS_OFFLINE=1 + - AUDIO_EXTERNAL_URL=http://${HOST_IP}:54300 + deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['0'] + capabilities: [gpu] + restart: unless-stopped diff --git a/ai_platform/local_gpu_stack/lib.sh b/ai_platform/local_gpu_stack/lib.sh new file mode 100644 index 0000000..64ee3c8 --- /dev/null +++ b/ai_platform/local_gpu_stack/lib.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Shared helpers for the brick-by-brick deploy. Sourced by deploy.sh. +# Logging, test gates, health waits, and a docker shim that works whether or not +# the current shell is in the `docker` group. + +set -uo pipefail + +# ---- colors ---------------------------------------------------------------- +if [ -t 1 ]; then + C_R=$'\e[31m'; C_G=$'\e[32m'; C_Y=$'\e[33m'; C_B=$'\e[36m'; C_D=$'\e[2m'; C_0=$'\e[0m' +else + C_R=; C_G=; C_Y=; C_B=; C_D=; C_0= +fi +log() { printf '%s\n' "$*"; } +info() { printf '%s\n' "${C_B}»${C_0} $*"; } +ok() { printf '%s\n' "${C_G}✓${C_0} $*"; } +warn() { printf '%s\n' "${C_Y}!${C_0} $*"; } +err() { printf '%s\n' "${C_R}✗${C_0} $*" >&2; } +brick(){ printf '\n%s\n' "${C_B}━━━ CĂRĂMIDĂ: $* ━━━${C_0}"; } +die() { err "$*"; err "Deploy oprit (gate). Repară și re-rulează — e idempotent."; exit 1; } + +# ---- docker shim (portable: plain docker, else `sg docker`) ----------------- +USE_SG=0 +if ! docker ps >/dev/null 2>&1; then + if command -v sg >/dev/null 2>&1 && sg docker -c 'docker ps' >/dev/null 2>&1; then + USE_SG=1 + fi +fi +docker() { + if [ "$USE_SG" = 1 ]; then + local a args="" + for a in "$@"; do args+=" $(printf '%q' "$a")"; done + sg docker -c "docker$args" + else + command docker "$@" + fi +} + +# ---- compose wrapper ------------------------------------------------------- +# COMPOSE is exported by deploy.sh +dc() { docker compose -f "$COMPOSE" "$@"; } + +# ---- health / test helpers ------------------------------------------------- +# wait_health