🚀 Fleet AHV — Sơ đồ & Chuẩn đặt tên
Cập nhật 11/07/2026. Nguồn chuẩn: /home/claudeproxy/Claude/ahv_fleet_runbook.md trên orchestrator. Giờ hệ thống toàn fleet = Asia/Ho_Chi_Minh (+7); riêng DB PostgreSQL giữ UTC nội bộ.
Chuẩn đặt tên (BẮT BUỘC)
- Node =
n<octet-cuối-IP>— 1:1 với IP, không bao giờ nhầm. Node mới .200 → n200. - Backend =
{tính-năng}-{node}(external =ext-{provider}). Token tính năng: tryon, gen, swap, upscale, video, remove, rembg, scan, nsfw. - Category (API user gọi — KHÔNG đổi): try_on, ahv_image, faceswap, faceswap_video, upscale, remove_object, remove_background, scan_objects, nsfw_check + external.
Sơ đồ node hiện tại (5 node GPU + orchestrator)
| Node | IP | GPU | Backend đang chạy | Ghi chú |
|---|---|---|---|---|
| n131 | 103.253.21.131 | 48G RTX PRO 5000 | tryon-131(6), gen-131, video-131(8), swap-131, upscale-131, swapprem-131 | Workhorse; node duy nhất đủ VRAM cho premium video (pilot) |
| n81 | 103.253.21.81 | 24G RTX PRO 4000 | tryon-81(2), gen-81, swapprem-81 | FLUX-ONLY (12/07: fsimage+fsvideo stopped — backend disabled thì service phải tắt; bật video-81 = start faceswap-video TRƯỚC) |
| n239 | 103.253.21.239 | 24G RTX PRO 4000 | tryon-239(1), gen-239, video-239(2), swapprem-239(OFF) | Node nguồn rsync; fsimage stopped 12/07 (mồ côi 8.2G gây OOM tryon) |
| n183 | 178.63.96.183 | 20G (Hetzner) | remove-183(:2031 diffusion), swap-183, upscale-183, rembg/scan/nsfw-183(:2026) | Node phụ trợ — KHÔNG chạy flux |
| n22 | 161.248.81.22 | 24G RTX PRO 4000 | tryon-22(2), swapprem-22 | Vào trận 11/07 giữa sự cố quá tải; đủ 4 stack trên disk |
| Orchestrator | 15.235.200.66 | — | FastAPI :2022, PostgreSQL, JOB_WORKERS=80×4 | SPOF — cần dự phòng khi scale |
Nguyên tắc thiết kế: (1) mọi node đủ mọi stack trên DISK — cái gì "nóng" trên VRAM do vận hành quyết theo tải; (2) thêm tính năng = thêm 1 stack + 1 category backend, không sửa kiến trúc; (3) thêm node = chạy onboard script; (4) mọi chuyển dịch kiểu "bật mới song song → verify job thật → tắt cũ", không gián đoạn.
Quy tắc VRAM & Xếp hình
- flux (FLUX.2-klein-9B NF4, phục vụ cả /tryon và /generate trên :1994) load-peak 14.7G, steady ~13G.
- fs-image 8.8G; video 9.5G; diffusion remove ~3.5G; upscale nằm trong fs-image.
- Node 24G: CHỈ flux + phụ ≤5G. flux + fs-image = OOM (thiếu 300MB — đã đo thật). Node 20G: không chạy flux. Chỉ n131 48G ôm được nhiều.
- Thứ tự bật trên node ghép: flux load TRƯỚC (cần peak), dịch vụ khác SAU.
- Mọi node flux PHẢI có drop-in
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True(chống VRAM phình → 503). - Video (inswapper) slot = free_VRAM/1.5G VÀ ≤3-4/node-24G (giới hạn phiên NVENC) — quá slot = lỗi "NVENC stream encode failed" / ONNX init OOM (đo thật 11-12/07).
- Service bloat theo giờ chạy (flux >20G, fsvideo phình ~2-8h/lần khi tải nặng) → restart node đó lúc vắng khi free <2G.
- Backend enabled ⇔ service running — backend tắt thì TẮT service (mồ côi ăn VRAM: n81 15.5G + n239 8.2G phát hiện 12/07); backend bật thì service PHẢI chạy (video-81 4 job fail 12/07).
- KHÔNG sync
config.pygiữa các node (BASE_URL per-node — sự cố 12/07: 76 job link 404 trong 40 phút). Chỉ sync code (server.py/pipeline.py). - KHÔNG tải file lớn (model) trên node đang production — bão hòa băng thông làm HTTP/SSH treo (sự cố 11h15 12/07). Tải lúc drain/khuya hoặc --limit-rate.
- GPU 100% util thì nâng slots là VÔ ÍCH (thrash) — chỉ có thêm node hoặc kịch bản ưu tiên.
Lệnh vận hành nhanh (từ orchestrator)
# Backend API (basic auth admin:***)
GET/POST/PUT http://127.0.0.1:2022/admin/api/backends # POST=thêm, PUT=sửa {category,name,...}
# Bật tính năng X trên node N
systemctl start <unit> → ufw allow port → curl test → POST backend slots nhỏ → theo dõi job thật → tăng slots
# Tắt an toàn: PUT enabled=false TRƯỚC (drain) → systemctl stop
# Restart flux 1 node an toàn (flux :1994 phục vụ CẢ tryon + gen!):
# disable cả tryon-X lẫn gen-X → drain ~20s → restart → health 200 → enable lại
- KHÔNG restart orchestrator lúc tải cao (fail job đang chạy) — đổi env qua systemd drop-in, restart lúc khuya vắng.
- SSH mọi node: key
gpu_admin(password login đã TẮT trên n22, nên tắt dần các node cũ). Key tạm onboard phải xoá sau khi xong. - Admin panel Server: danh sách node ở
/opt/ai-orchestrator-2022/collect_server_metrics.py— thêm node mới vào SERVERS.
⚡ Playbook sự cố (đã kiểm chứng thực chiến)
1. try_on quá tải (khách kêu chậm)
Ngưỡng vào kịch bản: p50>58s & p95>90s & running>90 building 2 nhịp — HOẶC khách kêu + p50>60s kéo dài. Lưu ý: p50/p95 vọt nhưng running thấp (<30) = burst tự tan (mẫu ~20h18 hằng đêm), KHÔNG can thiệp.
BẬC 1: PUT enabled=false gen-81 + gen-239 # dồn GPU n81/n239 cho try_on
BẬC 2: PUT enabled=false swap-131 # dồn n131 (faceswap vẫn chạy qua swap-183)
TAN (p50<40 & running<25): bật lại NGƯỢC bậc: swap-131 → gen-239 → gen-81
Thực chiến 10-11/07: p50 94s→29.5s trong ~90 phút (BẬC 1+2 + đưa n22 vào cấp tốc), 0 job mất.
2. 503 hàng loạt cùng 1 backend (≥3/nhịp)
= OOM node đó. Xem nvidia-smi; flux phình VRAM → restart flux node đó (quy trình drain ở trên); kiểm expandable_segments.
3. Link ảnh 404
Nguyên nhân kinh điển: (a) BASE_URL node trỏ sai IP (rsync đè config.py — bài học n22); (b) retention xoá sớm (đã chuẩn 24h mọi node). File thật luôn nằm trên node XỬ LÝ — cứu link đã phát bằng cách rsync files/ sang node có IP trong link.
4. disconnect/502 lẻ tẻ
1 lần đơn lẻ = transient, bỏ qua sau khi probe health OK. CÙNG backend ≥2/15 phút → probe, chết/chậm → restart service node đó.
5. Video quá tải / lỗi NVENC-ONNX
Video demand >25 running + video-131 lỗi → bật video-81: ssh n81 systemctl start faceswap-video TRƯỚC → PUT video-81 enabled slots 3. Node nào NVENC/ONNX >3/5phút → giảm slot node đó (free/1.5G) hoặc restart faceswap-video node đó (drain→restart→re-enable). Hết đỉnh → tắt video-81 (PUT off + stop service) trả VRAM cho try_on. Thực chiến 11/07: demand 75 → 3 node slot-theo-VRAM → 0 fail.
6. Job kẹt / metric running phồng ở đỉnh
Watchdog 900s chạy trễ dưới tải (coroutine đói — mục Audit 6/7). Job không leak vĩnh viễn (recover lúc boot). Fix triệt để cần sửa server.py + restart orchestrator lúc vắng (đang chờ duyệt).
Onboard node GPU mới
bash /home/claudeproxy/Claude/onboard_node.sh <IP> # (node đã nhận key gpu_admin)
Script tự làm: rsync 4 stack từ n239, copy systemd units, libnvinfer, set BASE_URL/FSV_PUBLIC_BASE=IP mới, expandable_segments, ufw. SAU KHI rsync ALL_DONE — 2 việc BẮT BUỘC (bài học n22 10-11/07):
- Chạy lại bước set BASE_URL — rsync ĐÈ config.py bằng bản node nguồn → link sẽ trỏ sai IP.
- Kiểm Python: venv build cho python3.10; node OS mới (3.12) phải
apt install python3.10(deadsnakes) +ln -sf /usr/bin/python3.10 venv/bin/python3.
Rồi: start flux → health 200 → POST backend tryon-<octet> slots 2 → theo dõi job thật (link đúng IP!) → xoá key tạm onboard_tmp → thêm vào SERVERS admin panel → tắt password SSH.
Giám sát & Ngưỡng
Triết lý: im lặng khi xanh (LowNoise), chỉ báo khi: vượt ngưỡng/sự cố đã tự xử lý xong/cần quyết định. 1 chuỗi giám sát DUY NHẤT (token-dedup file .ahv_monitor_token, mỗi nhịp rotate — chuỗi lạ tự chết im lặng). Nhịp thích ứng: đỉnh 15-25 phút, xanh 50-55 phút.
- Khung tải VN (+7): sáng 7-9h, trưa 11-14h, tối 19-22h nặng nhất (+burst ~20h18 tự tan), volume đang TĂNG từng ngày kể cả khuya.
- Lỗi INPUT — KHÔNG tính sự cố hệ thống: no-face, mask trống, 400/404, NSFW_BLOCKED, "Failed to download image" (storage 5.223.78.252 xoá input ngay — lỗi try_on nhiều nhất), "image file is truncated".
- Đọc node lỗi bằng field
backendtrong error JSON (tên chuẩn) — ĐỪNG tin text "VN2 try-on" (label cũ hardcode). - DB timestamp = UTC (giờ máy = VN+7). Node xử lý ≠ node lưu file.
📚 Bài học sự cố (đọc trước khi vận hành)
| Ngày | Sự cố | Bài học |
|---|---|---|
| 04-05/07 | remove_object LaMa → diffusion | Nâng cấp kiểu song song + fallback; đo A/B 10 case trước khi chốt |
| 06/07 | Link 404 hàng loạt | 2 lỗi chồng nhau: retention 1h quá ngắn (→24h) + BASE_URL .81 sai. Fix cả gốc lẫn onboard script |
| 06/07 | Audit 2 vòng | Bug latent (FSV_PUBLIC_BASE .183, flux .183 còn enabled) phải quét chủ động, đừng chờ nổ |
| 10-11/07 | try_on quá tải kỷ lục (p50 108s, p95 912s) | 3 node bão hòa thật sự → BẬC 1+2 + n22 cấp tốc. 3 bug khi onboard gấp: rsync đè BASE_URL, python 3.12 vs venv 3.10, log DONE giả do kill vòng lặp (luôn dùng exit-code marker) |
| 11/07 | Video SPOF: drain n131 test → 305 job video "no backend" | Video giờ ≥2 node (video-131+video-239); TRƯỚC khi rút node khỏi category 1-node phải bật dự phòng; test GPU ban ngày chỉ khi có node gánh |
| 11-12/07 | Video đỉnh trưa 70+ concurrent, NVENC/ONNX fail | Slot video = free/1.5G và ≤3-4/node (NVENC session limit); fsvideo bloat theo giờ → restart lúc vắng |
| 12/07 | Premium ảnh deploy — 3 bug tự bắt trong audit | (a) KHÔNG sync config.py giữa node (BASE_URL — 76 job 404/40min); (b) service mồ côi ăn VRAM 2 node (n81 15.5G, n239 8.2G → OOM tryon-239); (c) backend enabled ⇔ service running. Tải model lớn trên node production làm nghẽn băng thông (HTTP/SSH treo ~10min) |
| 09-10/07 | Đánh giá FLUX 4B vs 9B | NF4 trên card này: 4B KHÔNG nhanh hơn 9B (nghẽn băng thông), chất lượng thấp hơn → gen/try_on giữ 9B; 4B chỉ đáng cho head_swap (VRAM 4.2G); giảm bước 8→4 mới là đòn bẩy tốc độ thật (distilled) |
Việc treo chờ quyết: mở bán premium video (pilot xong, cần node 48G riêng nếu số lượng), watchdog fix, retention bảng tasks, label "VN2" hardcode, cache input chống download-fail, orchestrator dự phòng (SPOF). ĐÃ XONG 12/07: premium ảnh 9B (live), video redundancy 2 node.
AHV AI Orchestrator v2.0
Hệ thống điều phối AI đa tác vụ – quản lý hàng đợi, phân phối backend GPU, webhook callback, và giám sát thời gian thực.
Stack
Python 3.10+ · FastAPI · Uvicorn (uvloop + httptools) · PostgreSQL (psycopg async) · Systemd
Architecture
Job Processing Flow
- Client gửi
POST /v1/jobsvớitask,payload,webhook_url - Job lưu DB với status
queued, trả vềjob_id+predict_time(EMA) - Webhook phase 1: gửi event
queued - Worker lấy job, chọn backend GPU (round-robin + semaphore)
- Webhook phase 2: gửi event
started - Backend xử lý xong → trả kết quả
- Webhook phase 3: gửi event
finishedvới URL + meta - EMA predictor tự học từ duration thực tế
Installation & Setup
Requirements
- Python 3.10+
- PostgreSQL 14+
- Nginx (reverse proxy, SSL)
- Linux with Systemd
Quick Start
cd /opt/ai-orchestrator-2022
pip install -r requirements.txt
# Create database
sudo -u postgres createdb ahv_orchestrator
sudo -u postgres psql -c "CREATE USER ahv_user WITH PASSWORD 'ahv_pass_2026';"
sudo -u postgres psql -c "GRANT ALL ON DATABASE ahv_orchestrator TO ahv_user;"
# Run (development)
uvicorn server:app --host 0.0.0.0 --port 2022 --reload
# Run (production)
sudo systemctl start ai-orchestrator-2022
sudo systemctl enable ai-orchestrator-2022
Service Management
sudo systemctl status ai-orchestrator-2022 # View status
sudo systemctl restart ai-orchestrator-2022 # Restart (~2s)
sudo systemctl reload ai-orchestrator-2022 # Zero-downtime reload
journalctl -u ai-orchestrator-2022 -f # Live logs
Environment Variables
Database
| Variable | Default | Description |
|---|---|---|
DB_HOST | localhost | PostgreSQL host |
DB_PORT | 5432 | PostgreSQL port |
DB_NAME | ahv_orchestrator | Database name |
DB_USER | ahv_user | Database user |
DB_PASSWORD | ahv_pass_2026 | Database password |
API & Auth
| Variable | Default | Description |
|---|---|---|
API_KEY | anhyeuem | API key for clients |
ADMIN_PASS | Anhyeuem | Admin panel password (Basic Auth) |
Timeout & Performance
| Variable | Default | Description |
|---|---|---|
REQUEST_TIMEOUT | 300 | Backend call timeout (seconds) |
CONNECT_TIMEOUT | 15 | Connection timeout (seconds) |
JOB_MAX_RUNTIME | 900 | Max job runtime before watchdog kills it |
MAX_QUEUE_SIZE | 1000 | Maximum queue capacity |
JOB_WORKERS | 40 | Concurrent worker count |
Webhook
| Variable | Default | Description |
|---|---|---|
WEBHOOK_TIMEOUT | 12 | Webhook delivery timeout (seconds) |
WEBHOOK_RETRIES | 3 | Max retry attempts |
WEBHOOK_BACKOFF_BASE | 0.6 | Exponential backoff base (seconds) |
WEBHOOK_SECRET | "" | HMAC-SHA256 signing secret |
EMA Predictor
| Variable | Default | Description |
|---|---|---|
PREDICT_EMA_ALPHA | 0.25 | Learning rate (0–1, higher = more responsive) |
PREDICT_MIN_SEC | 3 | Minimum predicted time (seconds) |
Database: Table jobs
Main table storing all job records. Each API call creates one row.
| Column | Type | Description |
|---|---|---|
job_id | TEXT PK | UUID unique identifier |
task | TEXT indexed | Task type: faceswap, try_on, ahv_image, ... |
payload | JSON | Client-provided parameters |
webhook_url | TEXT | Callback URL |
status | TEXT indexed | queued · running · succeeded · failed · rejected |
progress | INTEGER | 0–100 |
result | JSON | Success result: {url, filename, meta} |
error | JSON | Error details: {code, message} |
created_at | TIMESTAMP | Job creation time |
started_at | TIMESTAMP | Processing start time |
finished_at | TIMESTAMP | Completion time |
duration_sec | FLOAT | Actual processing duration (seconds) |
predict_time | INTEGER | Predicted duration from EMA (seconds) |
eta_utc | TIMESTAMP | Estimated completion time |
attempts | INTEGER | Retry attempt count |
expires_at | TIMESTAMP | Auto-delete after this time |
Indexes: idx_jobs_created, idx_jobs_expires, idx_jobs_status_created
Database: Table ema_predictions
Stores Exponential Moving Average values for time prediction per task variant.
| Column | Type | Description |
|---|---|---|
key | TEXT PK | Task variant key (e.g. faceswap:up1:ms1080) |
value | FLOAT | Current EMA estimate (seconds) |
count | INTEGER | Number of samples learned |
updated_at | TIMESTAMP | Last update time |
EMA Formula: new = alpha × observed + (1 - alpha) × old. Alpha = 0.25 means 25% weight on new observations.
Database: Table webhook_logs
Tracks failed webhook deliveries for debugging. Auto-cleaned after 7 days.
| Column | Type | Description |
|---|---|---|
id | SERIAL PK | Auto-increment ID |
job_id | TEXT indexed | Related job |
event | TEXT | queued · started · finished |
url | TEXT | Webhook URL that failed |
attempt | INTEGER | Which retry attempt |
status_code | INTEGER | HTTP status code returned |
error | TEXT | Error message |
created_at | TIMESTAMP | Log timestamp |
API: Jobs
POST /v1/jobs — Create Job
Creates a new async processing job.
curl -X POST http://15.235.200.66:2022/v1/jobs \
-H "Content-Type: application/json" \
-H "X-API-Key: anhyeuem" \
-d '{
"task": "faceswap",
"payload": {
"target_url": "https://example.com/photo.jpg",
"source_url": "https://example.com/face.jpg"
},
"webhook_url": "https://your-server.com/webhook"
}'
Response
{
"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "queued",
"predict_time": 10,
"eta_utc": "2026-06-14T10:00:10Z"
}
GET /v1/jobs/{job_id} — Get Job Status
Success Response
{
"job_id": "a1b2c3d4-...",
"task": "faceswap",
"status": "succeeded",
"progress": 100,
"result": {
"url": "http://15.235.200.66:2022/uploads/result.jpg",
"filename": "result.jpg",
"meta": {"width": 1080, "height": 1920, "backend": "FSVN1"}
},
"duration_sec": 3.5
}
Error Response
{
"job_id": "a1b2c3d4-...",
"status": "failed",
"error": {"code": 502, "message": "Backend timeout after 300s"}
}
API: Upload & NSFW
POST /upload — Upload File
curl -X POST http://15.235.200.66:2022/upload -F "file=@photo.jpg"
{"url": "http://15.235.200.66:2022/uploads/abc123.jpg", "filename": "abc123.jpg"}
POST /v1/nsfw-check — NSFW Check
curl -X POST http://15.235.200.66:2022/v1/nsfw-check \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/photo.jpg", "strict": "medium"}'
{"status": "ok", "nsfw_score": 0.02, "is_nsfw": false, "threshold": 0.5, "label": "safe"}
API: Health & Diagnostics
| Endpoint | Description |
|---|---|
GET /healthz | {"ok": true, "status": "healthy", "queue_size": 5} |
GET /diag/backends | All backend nodes: slots, in-flight, enabled, last_error |
GET /diag/queue | {"queued": 10, "running": 5} |
GET /stats | Recent jobs list (with ?limit= and ?task= filter) |
GET /daily-stats | Daily aggregation (timezone-aware via ?tz=) |
GET /events | SSE stream for realtime dashboard updates |
API: Admin Statistics
Auth Required: All admin endpoints require Basic Auth (username: admin, password: ADMIN_PASS)
| Endpoint | Description |
|---|---|
GET /admin/stats/overview | Summary: total jobs, success rate, avg duration, queue status |
GET /admin/stats/hourly | Hourly traffic breakdown + avg response time |
GET /admin/stats/by-kind | Job distribution by task type |
GET /admin/stats/daily-trend | 7-day trend (stacked area chart data) |
GET /admin/stats/top-users | Top API users by webhook URL |
GET /admin/stats/top-errors | Most common error messages |
GET /admin/stats/backend-perf | Per-backend performance metrics |
GET /admin/stats/export | Export jobs data (?fmt=json or csv) |
GET /admin/stats/live | Live counters: queued, running, succeeded, failed |
GET /admin/stats/webhook-failures | Failed webhook delivery logs |
DELETE /admin/stats/webhook-failures | Purge all webhook failure logs |
Query params: ?tz=Asia/Ho_Chi_Minh, ?days=7, ?limit=20
API: Backend Management
| Endpoint | Method | Body |
|---|---|---|
/admin/api/backends | GET | — |
/admin/api/backends | POST | {"category", "name", "url", "slots", "enabled"} |
/admin/api/backends | PUT | {"category", "name", "url", "slots", "enabled", "new_name"} |
/admin/api/backends | DELETE | {"category", "name"} |
/admin/api/retry | GET / POST | Get/set retry counts per task type |
/admin/api/tryon-settings | GET / POST | Virtual Try-On default settings |
/admin/api/cx-settings | GET / POST | CX Image model settings |
Tasks: Faceswap & Upscale
faceswap
Swap a face onto a target image. Timeout: 180s
Required: target_url, source_url
faceswap_multi
Swap multiple faces. Timeout: 180s
Required: target_url, source_urls[]
faceswap_video
Swap face in video, preserves audio. Timeout: 600s
Required: target_url, source_url
upscale
AI image enhancement + upscaling. Timeout: 300s
Required: target_url or image_url
Optional Parameters
| Param | Default | Description |
|---|---|---|
max_side | 1080 | Max output dimension (px) |
max_faces | 0 (all) | Limit number of faces to swap |
upscale | 1 | Enable upscaling (0/1) |
restore_face | 1 | Enable face restoration (0/1) |
sharpen | 0.12 | Sharpening strength (0–1) |
NSFW Protection: All face tasks automatically run NSFW check. Violations result in status: "rejected" with code NSFW_BLOCKED.
Tasks: Image Generation
ahv_image
Smart-routed text-to-image / image-to-image. Timeout: 300s
Backends: VN2 GPU, CX API, FlowProxy (auto-selected)
gg_image
Google Imagen-4 via FlowProxy. Timeout: 300s
Async polling: submit → poll → download
cx_image
CX/GPT Image API. Timeout: 120s
Direct API call with auto-upload
minimax_image
MiniMax image generation. Timeout: 120s
Supports I2I with image_url
Payload
| Param | Required | Description |
|---|---|---|
prompt | Yes | Text description of desired image |
image_url | No | Source image for image-to-image |
width / height | No | Output dimensions |
aspect_ratio | No | 1:1, 16:9, 9:16 |
model | No | Model override (backend-specific) |
steps | No | Inference steps |
guidance | No | Guidance scale |
seed | No | Random seed for reproducibility |
ahv_image routing: VN2 + no image_url → retries non-VN2. VN2 + image_url → VN2 GPU I2I. CX backend → CX API. FlowProxy → Imagen-4.
Tasks: Video Generation
ahv_video
Image-to-video via Google Veo / FlowProxy. Timeout: 600s
Required: prompt
minimax_video
MiniMax text/image-to-video. Timeout: 600s
Required: prompt
Payload
| Param | Required | Description |
|---|---|---|
prompt | Yes | Video description |
image_url | No | Source image (for I2V) |
duration | No | Video duration (default: 6s) |
aspect_ratio | No | 16:9, 9:16, 1:1 |
fps | No | Frames per second |
Tasks: Virtual Try-On
try_on
Virtual clothing try-on. Timeout: 300s
Smart routing: VN2 (sync) / PiAPI (async) / CX / FlowProxy
Payload
| Param | Required | Description |
|---|---|---|
person_image_url | Yes | Photo of the person (aliases: model_input, person_image) |
garment_image_url | Yes | Photo of garment (aliases: dress_input, garment_image) |
top_url | No | Upper garment (for split top/bottom) |
bottom_url | No | Lower garment (for split top/bottom) |
prompt | No | Additional instructions |
Smart Routing:
VN2 (port 1994) → Direct sync call (300s timeout)
PiAPI/Kling → Async submit + polling
CX → Multi-image prompt approach
FlowProxy → Imagen-4 with try-on prompt
Tasks: Image Tools
remove_object
Remove objects from image. Timeout: 120s
image_url, prompt (describe object)
scan_objects
Detect & label objects. Timeout: 60s
image_url
remove_background
Auto background removal. Timeout: 60s
image_url
nsfw_check
Content moderation check. Timeout: 30s
image_url, strict (low/medium/high)
Backend System
Multi-backend Architecture
Each task type has a category with multiple nodes (GPU servers):
Category: faceswap
├── FSVN1 (30 slots) → http://103.253.21.239:1993/faceswap
└── FSVN48G (30 slots) → http://178.63.96.183:1993/faceswap
Category: try_on
├── TryOn-VN2 (3 slots) → http://103.253.21.239:1994/tryon
└── TryOn-HZ1 (3 slots) → http://178.63.96.183:1994/tryon
Selection Algorithm
- Iterate enabled backends in order
- Pick first backend with free semaphore slot
- If all busy → pick least-loaded backend
- Disabled backends → skipped entirely
Backend Class Properties
| Property | Type | Description |
|---|---|---|
name | str | Display name (e.g. "FSVN1") |
url | str | GPU endpoint URL |
slots | int | Max concurrent requests |
enabled | bool | Active/inactive toggle |
api_key | str | Per-node API key (MiniMax, CX, ...) |
sem | Semaphore | Runtime concurrency limiter |
total_done | int | Jobs processed (live counter) |
last_error | str | Most recent error (diagnostics) |
Hot Reload: Add/edit/delete backends via Admin UI or API — no service restart needed. Config persists in backends_config.json.
Webhook System
3-Phase Event Flow
Success Payload
{
"job_id": "a1b2c3d4-...",
"task": "faceswap",
"status": "succeeded",
"event": "finished",
"progress": 100,
"url": "http://15.235.200.66:2022/uploads/result.jpg",
"filename": "result.jpg",
"meta": {"backend": "FSVN1", "width": 1080, "height": 1920, "duration_sec": 3.5}
}
Error Payload
{
"job_id": "a1b2c3d4-...",
"status": "failed",
"event": "finished",
"error": {"code": 502, "message": "Backend timeout after 300s"}
}
NSFW Rejected Payload
{
"job_id": "a1b2c3d4-...",
"status": "rejected",
"event": "finished",
"error": {"code": "NSFW_BLOCKED", "message": "Content violates policy"}
}
Retry & Security
| Feature | Detail |
|---|---|
| Retry count | 3 attempts |
| Backoff | Exponential: 0.6s → 1.2s → 2.4s (+ random jitter) |
| Signature | X-Signature: sha256=<hex> (HMAC-SHA256, if WEBHOOK_SECRET set) |
| Failure logging | Saved to webhook_logs table, viewable in Admin UI |
| Auto cleanup | Logs older than 7 days automatically deleted |
Auth & Security
API Key Authentication
| Path Pattern | Auth Required |
|---|---|
/, /public/*, /upload, /healthz | No |
/stats, /events, /tasks | No |
/v1/jobs, /v1/nsfw-check | API Key (X-API-Key header) |
/admin/api/* | Basic Auth (admin) |
CORS
All origins allowed (*) with credentials support.
NSFW Protection
Auto-applied to: faceswap, faceswap_video, upscale, try_on.
Violation → status: "rejected", code: NSFW_BLOCKED
Web Interfaces
Landing Page
Product overview, features, pricing, code examples
Dashboard
Realtime monitoring via SSE, traffic charts, job list
Admin Panel
Backend management, retry config, stats, webhook logs
API Demo
Test all APIs directly in browser with live preview
API Docs
Complete API reference with examples & error codes
Wiki
This page — full system documentation
File Structure
/opt/ai-orchestrator-2022/
├── server.py # FastAPI app: routes, DB, queue, webhook, auth, SSE (~1800 lines)
├── workers.py # Task dispatch: backend calls, normalize responses (~1370 lines)
├── handlers.py # Helpers: NSFW, webhook retry, EMA, payload builders (~620 lines)
├── backends.py # Backend registry: config, node management, selection (~430 lines)
├── backends_config.json # Backend node config (persists across restarts)
├── .env # Local environment overrides
├── requirements.txt # Python dependencies
├── README.md # Project documentation
│
├── public/
│ ├── index.html # Landing page
│ ├── dashboard.html # Realtime monitoring dashboard
│ ├── admin.html # Admin panel (backend management)
│ ├── webhook-demo.html # API testing tool
│ ├── docs-webhook.html # API documentation
│ ├── wiki.html # System wiki (this page)
│ └── uploads/ # Uploaded files directory
│
└── Systemd: /etc/systemd/system/ai-orchestrator-2022.service
File Responsibilities
| File | Lines | Role |
|---|---|---|
server.py | ~1800 | Main app: FastAPI routes, SQLAlchemy models, asyncio job queue, webhook delivery, auth middleware, SSE events, admin stats endpoints, watchdog |
workers.py | ~1370 | Task dispatch: route task → backend GPU, HTTP calls, response normalization, async polling (PiAPI, FlowProxy, MiniMax) |
handlers.py | ~620 | Helpers: NSFW detection, webhook retry with backoff, EMA time prediction, payload validation & building |
backends.py | ~430 | Backend management: load/save config, Backend class, round-robin selection, semaphore concurrency control |
Deployment & Operations
Systemd Service Configuration
[Service]
ExecStart=uvicorn server:app --host 0.0.0.0 --port 2022
--workers 4 --loop uvloop --http httptools
--timeout-keep-alive 75 --limit-concurrency 2000
--backlog 8192 --proxy-headers
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=1
TimeoutStopSec=10
KillSignal=SIGINT
LimitNOFILE=65535
Performance Tuning
| Optimization | Detail |
|---|---|
| uvloop + httptools | ~10% throughput improvement over stdlib |
| 4 Uvicorn workers | Multi-core CPU utilization |
| 40 Job workers | 40 concurrent job processing |
| DB pool | size=20, max_overflow=40 |
| Graceful reload | systemctl reload = zero-downtime (SIGHUP) |
| Fast restart | ~2 seconds (SIGINT + TimeoutStopSec=10) |
| CTE queries | Admin stats use Common Table Expressions |
Automatic Maintenance
| Mechanism | Interval | Action |
|---|---|---|
| Stuck job watchdog | Every 30s | Jobs running > 900s → force fail |
| Expired job cleanup | Every 60s | Jobs past expires_at → delete |
| Webhook log cleanup | Every 60s | Logs older than 7 days → delete |
Monitoring Commands
# Health check
curl http://15.235.200.66:2022/healthz
# Queue status
curl http://15.235.200.66:2022/diag/queue
# Backend health
curl http://15.235.200.66:2022/diag/backends
# Live logs
journalctl -u ai-orchestrator-2022 -f
Developer Guide
⚠ Mandatory Dev Rules (BẮT BUỘC)
Áp dụng cho mọi việc triển khai tính năng / dev / bugfix. Không được báo "pass"/"xong" nếu chưa làm đủ:
- Dùng Superpowers để triển khai (brainstorming → writing-plans → TDD → code-review → verification-before-completion).
- Audit + review + stress test thật 2 VÒNG mới được báo pass — hai vòng độc lập, có bằng chứng (output thật), không tự nhận pass khi chưa chạy kiểm chứng.
- Nếu có giao diện (UI): phải test bằng trình duyệt thật (Playwright/Chromium) trên cả mobile lẫn desktop thật.
- Mọi test phải mô phỏng như người dùng thật — đi đúng luồng người dùng, không mock cho qua.
Lý do: ưu tiên độ ổn định và trải nghiệm người dùng tuyệt đối — "người dùng là quan trọng nhất".
Adding a New Backend Node
- Go to Admin Panel → select category
- Click "Add Backend"
- Enter: name, URL, slots, API key (if needed)
- Enable → backend is ready immediately (no restart needed)
Adding a New Task Type
Step 1: Add dispatch function in workers.py
async def _dispatch_new_task(payload: dict) -> dict:
"""Call backend, return normalized result."""
prompt = payload.get("prompt")
if not prompt:
raise HTTPException(400, detail="prompt required")
backend = pick_backend("new_task")
async with backend.sem:
async with httpx.AsyncClient(timeout=300) as client:
resp = await client.post(backend.url, json={"prompt": prompt})
data = resp.json()
return {"ok": True, "url": data["result_url"], "meta": {"backend": backend.name}}
Step 2: Register in dispatch_task()
# In workers.py, inside dispatch_task()
if task == "new_task":
return await _dispatch_new_task(payload)
Step 3: Add fallback estimate in server.py
PREDICT_FALLBACKS = {
...
"new_task": 30, # default prediction: 30 seconds
}
Step 4: Add backend category & configure nodes via Admin UI
Processing Flow
Coding Conventions
| Convention | Detail |
|---|---|
| Normalized response | All tasks return: {ok, url, filename, meta, error} |
| Semaphore guard | Always use async with backend.sem: for concurrency |
| Error handling | Use BackendHTTPException(502, message, backend_name) |
| Async polling | Pattern: submit → poll every 3s → download → re-upload |
| NSFW check | Add at start of dispatch for user-facing image tasks |
| Retry | Only on 502 errors (backend issues), never on 4xx (client errors) |
Tip: Test new task types via API Demo page before deploying to production.