| """
|
| π NVIDIA AI Multi-Model Space v3.1
|
| - 38 modelli NVIDIA NIM VERIFICATI e FUNZIONANTI
|
| - Auto-adattamento messaggi per modelli senza 'system role'
|
| - Memoria breve (RAM) e lunga (SQLite)
|
| - API REST GET/POST + SSE Streaming
|
| - Server MCP integrato
|
| - Test parallelo async ultra-veloce
|
| """
|
|
|
| import os
|
| import json
|
| import time
|
| import uuid
|
| import base64
|
| import asyncio
|
| from pathlib import Path
|
| from typing import Optional, AsyncGenerator
|
|
|
| import gradio as gr
|
| import httpx
|
| from dotenv import load_dotenv
|
| from fastapi import FastAPI, Request, Query, HTTPException
|
| from fastapi.responses import (
|
| StreamingResponse, JSONResponse, HTMLResponse, FileResponse
|
| )
|
| from fastapi.middleware.cors import CORSMiddleware
|
| from fastapi.staticfiles import StaticFiles
|
| from sse_starlette.sse import EventSourceResponse
|
| from pydantic import BaseModel, Field, ConfigDict
|
|
|
| from memory_manager import MemoryManager
|
|
|
| load_dotenv()
|
|
|
|
|
|
|
|
|
|
|
| def _decode_key() -> str:
|
| b64 = os.getenv(
|
| "NVIDIA_API_KEY_B64",
|
| "bnZhcGktUXdVV29UMlJPSlR4X2luTWtraWVSeF9sTl9TdkxRM2FiNnBVYTFmREg4c1hOYUFST0tyWWhDM3NWRU5MYU03dg=="
|
| )
|
| return base64.b64decode(b64).decode("utf-8")
|
|
|
| NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY") or _decode_key()
|
| NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1"
|
| DEFAULT_MODEL = os.getenv("DEFAULT_MODEL", "meta/llama-3.2-3b-instruct")
|
|
|
| HTTP_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
|
| HTTP_TIMEOUT = httpx.Timeout(120.0, connect=10.0)
|
|
|
|
|
|
|
|
|
|
|
| NVIDIA_MODELS = {
|
|
|
|
|
|
|
| "meta/llama-3.2-3b-instruct": {
|
| "name": "β‘ LLaMA 3.2 3B Instruct",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π₯ FASTEST β 153ms β DEFAULT"
|
| },
|
| "nvidia/nemotron-mini-4b-instruct": {
|
| "name": "β‘ Nemotron Mini 4B",
|
| "category": "LLM",
|
| "context": 8192,
|
| "description": "Ultra-fast 4B β 186ms"
|
| },
|
| "meta/llama-3.2-11b-vision-instruct": {
|
| "name": "β‘ LLaMA 3.2 11B Vision",
|
| "category": "Vision",
|
| "context": 131072,
|
| "description": "Fast vision-language β 196ms"
|
| },
|
| "microsoft/phi-4-mini-instruct": {
|
| "name": "β‘ Phi 4 Mini Instruct",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π Latest Phi 4 Mini β 206ms"
|
| },
|
| "google/gemma-2-2b-it": {
|
| "name": "β‘ Gemma 2 2B",
|
| "category": "LLM",
|
| "context": 8192,
|
| "description": "Compact Gemma β 212ms (no system role)"
|
| },
|
| "nvidia/llama-3.1-nemotron-nano-8b-v1": {
|
| "name": "β‘ Nemotron Nano 8B v1",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "Efficient Nemotron β 239ms"
|
| },
|
| "stockmark/stockmark-2-100b-instruct": {
|
| "name": "β‘ Stockmark 2 100B",
|
| "category": "LLM",
|
| "context": 32768,
|
| "description": "Japanese specialist 100B β 261ms"
|
| },
|
| "upstage/solar-10.7b-instruct": {
|
| "name": "β‘ Solar 10.7B",
|
| "category": "LLM",
|
| "context": 4096,
|
| "description": "Upstage Solar β 261ms"
|
| },
|
| "meta/llama-3.1-70b-instruct": {
|
| "name": "β‘ LLaMA 3.1 70B Instruct",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "High-quality 70B β 263ms"
|
| },
|
| "sarvamai/sarvam-m": {
|
| "name": "β‘ Sarvam M",
|
| "category": "LLM",
|
| "context": 32768,
|
| "description": "Indian multilingual β 285ms"
|
| },
|
|
|
|
|
|
|
|
|
| "nvidia/ising-calibration-1-35b-a3b": {
|
| "name": "π Ising Calibration 35B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "Calibration model β 331ms"
|
| },
|
| "nvidia/nemotron-nano-12b-v2-vl": {
|
| "name": "π Nemotron Nano 12B v2 VL",
|
| "category": "Vision",
|
| "context": 131072,
|
| "description": "Vision Nemotron 12B β 334ms"
|
| },
|
| "mistralai/mistral-small-4-119b-2603": {
|
| "name": "π Mistral Small 4 119B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "Mistral Small 4 β 357ms"
|
| },
|
| "qwen/qwen3-next-80b-a3b-instruct": {
|
| "name": "π Qwen3 Next 80B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π Latest Qwen 3 β 371ms"
|
| },
|
| "google/gemma-3n-e2b-it": {
|
| "name": "π Gemma 3n E2B",
|
| "category": "LLM",
|
| "context": 8192,
|
| "description": "Gemma 3 Nano β 380ms (no system role)"
|
| },
|
| "mistralai/mistral-nemotron": {
|
| "name": "π Mistral Nemotron",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "NVIDIA-Mistral collab β 383ms"
|
| },
|
| "meta/llama-3.1-8b-instruct": {
|
| "name": "π LLaMA 3.1 8B Instruct",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "Popular 8B β 404ms"
|
| },
|
| "minimaxai/minimax-m2.7": {
|
| "name": "π MiniMax M2.7",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "MiniMax model β 409ms"
|
| },
|
| "nvidia/nemotron-3-nano-30b-a3b": {
|
| "name": "π Nemotron 3 Nano 30B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π Nemotron 3 MoE β 410ms"
|
| },
|
| "meta/llama-3.2-90b-vision-instruct": {
|
| "name": "π LLaMA 3.2 90B Vision",
|
| "category": "Vision",
|
| "context": 131072,
|
| "description": "Large vision-language β 419ms"
|
| },
|
| "mistralai/mistral-medium-3.5-128b": {
|
| "name": "π Mistral Medium 3.5 128B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "Mistral Medium 3.5 β 419ms"
|
| },
|
|
|
|
|
|
|
|
|
| "qwen/qwen3.5-122b-a10b": {
|
| "name": "πͺ Qwen 3.5 122B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π₯ Qwen 3.5 MoE β 505ms"
|
| },
|
| "nvidia/nemotron-3-super-120b-a12b": {
|
| "name": "πͺ Nemotron 3 Super 120B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π₯ Nemotron 3 Super MoE β 556ms"
|
| },
|
| "mistralai/mixtral-8x7b-instruct-v0.1": {
|
| "name": "πͺ Mixtral 8x7B",
|
| "category": "LLM",
|
| "context": 32768,
|
| "description": "Classic MoE β 685ms"
|
| },
|
| "nvidia/nemotron-3-ultra-550b-a55b": {
|
| "name": "πͺ Nemotron 3 Ultra 550B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π MASSIVE 550B MoE β 707ms"
|
| },
|
| "deepseek-ai/deepseek-v4-flash": {
|
| "name": "πͺ DeepSeek V4 Flash",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π Fast DeepSeek V4 β 712ms"
|
| },
|
| "abacusai/dracarys-llama-3.1-70b-instruct": {
|
| "name": "πͺ Dracarys LLaMA 3.1 70B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "AbacusAI fine-tuned 70B β 774ms"
|
| },
|
| "google/diffusiongemma-26b-a4b-it": {
|
| "name": "πͺ DiffusionGemma 26B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "Diffusion-based Gemma β 843ms (no system role)"
|
| },
|
|
|
|
|
|
|
|
|
| "moonshotai/kimi-k2.6": {
|
| "name": "π’ Kimi K2.6",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π Moonshot Kimi β 1.12s"
|
| },
|
| "z-ai/glm-5.2": {
|
| "name": "π’ GLM 5.2",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π Zhipu GLM 5.2 β 1.5s"
|
| },
|
| "deepseek-ai/deepseek-v4-pro": {
|
| "name": "π’ DeepSeek V4 Pro",
|
| "category": "Reasoning",
|
| "context": 131072,
|
| "description": "π₯ Advanced reasoning β 1.7s"
|
| },
|
| "meta/llama-3.2-1b-instruct": {
|
| "name": "π’ LLaMA 3.2 1B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "Ultra-lightweight β 1.9s"
|
| },
|
| "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": {
|
| "name": "π’ Nemotron 3 Nano Omni (Reasoning)",
|
| "category": "Reasoning",
|
| "context": 131072,
|
| "description": "π§ Reasoning model β 2.6s"
|
| },
|
| "minimaxai/minimax-m3": {
|
| "name": "π’ MiniMax M3",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π Latest MiniMax β 3.6s"
|
| },
|
| "mistralai/ministral-14b-instruct-2512": {
|
| "name": "π’ Ministral 14B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "Mistral Ministral β 4.6s"
|
| },
|
| "nvidia/llama-3.1-nemotron-nano-vl-8b-v1": {
|
| "name": "π’ Nemotron Nano VL 8B",
|
| "category": "Vision",
|
| "context": 131072,
|
| "description": "Vision Nemotron β 5.9s"
|
| },
|
| "mistralai/mistral-large-3-675b-instruct-2512": {
|
| "name": "π’ Mistral Large 3 675B",
|
| "category": "LLM",
|
| "context": 131072,
|
| "description": "π MASSIVE 675B β 8.2s"
|
| },
|
|
|
|
|
|
|
|
|
| "nvidia/riva-translate-4b-instruct-v1.1": {
|
| "name": "π Riva Translate 4B v1.1",
|
| "category": "Translation",
|
| "context": 8192,
|
| "description": "NVIDIA translation β 178ms"
|
| },
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| MODELS_NO_SYSTEM_ROLE = {
|
|
|
| "google/gemma-2-2b-it",
|
| "google/gemma-3n-e2b-it",
|
| "google/diffusiongemma-26b-a4b-it",
|
|
|
| }
|
|
|
|
|
| def normalize_messages(messages: list[dict], model: str) -> list[dict]:
|
| """
|
| Adatta i messaggi al modello:
|
| - Se il modello non supporta 'system', converte il system prompt
|
| come prefisso del primo messaggio user.
|
| - Rimuove messaggi vuoti.
|
| """
|
| if not messages:
|
| return messages
|
|
|
|
|
| if model not in MODELS_NO_SYSTEM_ROLE:
|
| return messages
|
|
|
|
|
| system_parts = []
|
| non_system = []
|
| for m in messages:
|
| if m.get("role") == "system":
|
| content = (m.get("content") or "").strip()
|
| if content:
|
| system_parts.append(content)
|
| else:
|
| non_system.append(m)
|
|
|
| if not system_parts:
|
| return non_system if non_system else messages
|
|
|
|
|
| system_text = "\n\n".join(system_parts)
|
| prepended = False
|
| for i, m in enumerate(non_system):
|
| if m.get("role") == "user":
|
| non_system[i] = {
|
| "role": "user",
|
| "content": f"[System instructions]\n{system_text}\n\n[User message]\n{m.get('content', '')}"
|
| }
|
| prepended = True
|
| break
|
|
|
| if not prepended:
|
|
|
| non_system.insert(0, {"role": "user", "content": system_text})
|
|
|
| return non_system
|
|
|
|
|
|
|
|
|
|
|
|
|
| memory = MemoryManager(
|
| max_short=int(os.getenv("MAX_SHORT_MEMORY", 20)),
|
| max_long_tokens=int(os.getenv("MAX_LONG_MEMORY_TOKENS", 50000))
|
| )
|
|
|
|
|
|
|
|
|
|
|
|
|
| def _is_system_role_error(err_text: str) -> bool:
|
| """Rileva errori dovuti a system role non supportato"""
|
| if not err_text:
|
| return False
|
| err_lower = err_text.lower()
|
| keywords = [
|
| "system role not supported",
|
| "does not support 'system'",
|
| "does not support system",
|
| "system role is not supported",
|
| "role 'system' not allowed",
|
| ]
|
| return any(k in err_lower for k in keywords)
|
|
|
|
|
| async def nvidia_chat_stream(
|
| messages: list[dict],
|
| model: str = DEFAULT_MODEL,
|
| temperature: float = 0.7,
|
| max_tokens: int = 4096,
|
| top_p: float = 0.9,
|
| ) -> AsyncGenerator[str, None]:
|
| if not model or not str(model).strip():
|
| model = DEFAULT_MODEL
|
|
|
|
|
| original_messages = messages
|
| messages = normalize_messages(messages, model)
|
|
|
| headers = {
|
| "Authorization": f"Bearer {NVIDIA_API_KEY}",
|
| "Content-Type": "application/json",
|
| "Accept": "text/event-stream",
|
| }
|
| payload = {
|
| "model": model,
|
| "messages": messages,
|
| "temperature": temperature,
|
| "max_tokens": max_tokens,
|
| "top_p": top_p,
|
| "stream": True,
|
| }
|
|
|
| async with httpx.AsyncClient(timeout=HTTP_TIMEOUT, limits=HTTP_LIMITS) as client:
|
| async with client.stream(
|
| "POST",
|
| f"{NVIDIA_BASE_URL}/chat/completions",
|
| headers=headers, json=payload,
|
| ) as response:
|
|
|
|
|
| if response.status_code != 200:
|
| err = (await response.aread()).decode(errors="ignore")[:500]
|
| if _is_system_role_error(err) and model not in MODELS_NO_SYSTEM_ROLE:
|
| print(f"[AUTO-FIX] Aggiunto {model} a MODELS_NO_SYSTEM_ROLE")
|
| MODELS_NO_SYSTEM_ROLE.add(model)
|
|
|
| payload["messages"] = normalize_messages(original_messages, model)
|
| async with client.stream(
|
| "POST",
|
| f"{NVIDIA_BASE_URL}/chat/completions",
|
| headers=headers, json=payload,
|
| ) as retry_response:
|
| if retry_response.status_code != 200:
|
| err2 = (await retry_response.aread()).decode(errors="ignore")[:500]
|
| raise Exception(f"NVIDIA API {retry_response.status_code}: {err2}")
|
| async for line in retry_response.aiter_lines():
|
| if line.startswith("data: "):
|
| data = line[6:]
|
| if data.strip() == "[DONE]":
|
| return
|
| try:
|
| chunk = json.loads(data)
|
| delta = chunk["choices"][0].get("delta", {})
|
| if "content" in delta and delta["content"]:
|
| yield delta["content"]
|
| except (json.JSONDecodeError, KeyError, IndexError):
|
| continue
|
| return
|
|
|
|
|
| if response.status_code == 404:
|
| raise Exception(f"Modello '{model}' non disponibile (404)")
|
| raise Exception(f"NVIDIA API {response.status_code}: {err}")
|
|
|
|
|
| async for line in response.aiter_lines():
|
| if line.startswith("data: "):
|
| data = line[6:]
|
| if data.strip() == "[DONE]":
|
| break
|
| try:
|
| chunk = json.loads(data)
|
| delta = chunk["choices"][0].get("delta", {})
|
| if "content" in delta and delta["content"]:
|
| yield delta["content"]
|
| except (json.JSONDecodeError, KeyError, IndexError):
|
| continue
|
|
|
|
|
| async def nvidia_chat(
|
| messages: list[dict],
|
| model: str = DEFAULT_MODEL,
|
| temperature: float = 0.7,
|
| max_tokens: int = 4096,
|
| top_p: float = 0.9,
|
| client: Optional[httpx.AsyncClient] = None,
|
| ) -> str:
|
| if not model or not str(model).strip():
|
| model = DEFAULT_MODEL
|
|
|
|
|
| original_messages = messages
|
| messages = normalize_messages(messages, model)
|
|
|
| headers = {
|
| "Authorization": f"Bearer {NVIDIA_API_KEY}",
|
| "Content-Type": "application/json",
|
| }
|
|
|
| async def _do_request(c: httpx.AsyncClient, msgs: list[dict]) -> httpx.Response:
|
| return await c.post(
|
| f"{NVIDIA_BASE_URL}/chat/completions",
|
| headers=headers,
|
| json={
|
| "model": model,
|
| "messages": msgs,
|
| "temperature": temperature,
|
| "max_tokens": max_tokens,
|
| "top_p": top_p,
|
| "stream": False,
|
| },
|
| )
|
|
|
| async def _call(c: httpx.AsyncClient) -> str:
|
| resp = await _do_request(c, messages)
|
|
|
|
|
| if resp.status_code != 200:
|
| err_body = resp.text[:500]
|
| if _is_system_role_error(err_body) and model not in MODELS_NO_SYSTEM_ROLE:
|
| print(f"[AUTO-FIX] Aggiunto {model} a MODELS_NO_SYSTEM_ROLE")
|
| MODELS_NO_SYSTEM_ROLE.add(model)
|
|
|
| retry_msgs = normalize_messages(original_messages, model)
|
| resp = await _do_request(c, retry_msgs)
|
| if resp.status_code == 200:
|
| return resp.json()["choices"][0]["message"]["content"]
|
|
|
| if resp.status_code == 404:
|
| raise Exception(f"Modello '{model}' non disponibile")
|
| raise Exception(f"NVIDIA API {resp.status_code}: {resp.text[:500]}")
|
|
|
| try:
|
| return resp.json()["choices"][0]["message"]["content"]
|
| except (KeyError, IndexError, TypeError):
|
| raise Exception(f"Risposta NVIDIA non valida per {model}: {resp.text[:200]}")
|
|
|
| if client:
|
| return await _call(client)
|
| else:
|
| async with httpx.AsyncClient(timeout=HTTP_TIMEOUT, limits=HTTP_LIMITS) as c:
|
| return await _call(c)
|
|
|
|
|
|
|
|
|
|
|
|
|
| app = FastAPI(
|
| title="π NVIDIA AI Multi-Model API",
|
| description=f"{len(NVIDIA_MODELS)} modelli verificati e funzionanti",
|
| version="3.1.0",
|
| )
|
|
|
| app.add_middleware(
|
| CORSMiddleware,
|
| allow_origins=["*"], allow_credentials=True,
|
| allow_methods=["*"], allow_headers=["*"],
|
| )
|
|
|
| STATIC_DIR = Path(__file__).parent / "static"
|
| if STATIC_DIR.exists():
|
| app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
|
|
|
|
| class ChatRequest(BaseModel):
|
| model_config = ConfigDict(
|
| json_schema_extra={
|
| "example": {
|
| "message": "Ciao!",
|
| "model": DEFAULT_MODEL,
|
| "session_id": "test",
|
| }
|
| }
|
| )
|
| message: str
|
| model: str = Field(default=DEFAULT_MODEL)
|
| session_id: Optional[str] = None
|
| system_prompt: str = "Sei un assistente AI utile. Rispondi in italiano se l'utente scrive in italiano."
|
| temperature: float = Field(default=0.7, ge=0.0, le=2.0)
|
| max_tokens: int = Field(default=4096, ge=1, le=16384)
|
| top_p: float = Field(default=0.9, ge=0.0, le=1.0)
|
| use_memory: bool = True
|
| stream: bool = False
|
|
|
|
|
| class MemoryRequest(BaseModel):
|
| session_id: str
|
| action: str
|
| summary: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
| @app.get("/", response_class=HTMLResponse)
|
| async def root():
|
| ui_file = STATIC_DIR / "index.html"
|
| if ui_file.exists():
|
| return FileResponse(str(ui_file))
|
| return HTMLResponse("""
|
| <html><body style="font-family:sans-serif;background:#0a0e14;color:#e4e6eb;padding:40px">
|
| <h1>π NVIDIA AI Multi-Model</h1>
|
| <ul>
|
| <li><a href="/ui" style="color:#76b900">Gradio</a></li>
|
| <li><a href="/docs" style="color:#76b900">Swagger</a></li>
|
| <li><a href="/api-docs" style="color:#76b900">API Docs</a></li>
|
| </ul>
|
| </body></html>
|
| """)
|
|
|
|
|
| @app.get("/info")
|
| async def info():
|
| return {
|
| "service": "π NVIDIA AI Multi-Model",
|
| "version": "3.1.0",
|
| "default_model": DEFAULT_MODEL,
|
| "models_available": len(NVIDIA_MODELS),
|
| "models_no_system_role": sorted(MODELS_NO_SYSTEM_ROLE),
|
| "all_models_verified": True,
|
| "last_verified": "2026-07-04",
|
| }
|
|
|
|
|
| @app.get("/v1/health")
|
| async def health():
|
| return {
|
| "status": "healthy",
|
| "timestamp": time.time(),
|
| "models_count": len(NVIDIA_MODELS),
|
| "default_model": DEFAULT_MODEL,
|
| "active_sessions": len(memory.short_memory),
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| @app.get("/v1/models")
|
| async def list_models(category: Optional[str] = None):
|
| models = {}
|
| for mid, mi in NVIDIA_MODELS.items():
|
| if category and mi["category"].lower() != category.lower():
|
| continue
|
| models[mid] = mi
|
| return {
|
| "models": models,
|
| "total": len(models),
|
| "categories": sorted(set(m["category"] for m in NVIDIA_MODELS.values())),
|
| "default": DEFAULT_MODEL,
|
| "models_no_system_role": sorted(MODELS_NO_SYSTEM_ROLE),
|
| }
|
|
|
|
|
| @app.get("/v1/models/{model_id:path}")
|
| async def get_model_info(model_id: str):
|
| if model_id not in NVIDIA_MODELS:
|
| raise HTTPException(404, f"Modello '{model_id}' non nel catalogo")
|
| return {
|
| "model_id": model_id,
|
| **NVIDIA_MODELS[model_id],
|
| "supports_system_role": model_id not in MODELS_NO_SYSTEM_ROLE,
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| @app.get("/v1/debug/test-model")
|
| async def debug_test_model(model: str = Query(DEFAULT_MODEL)):
|
| """Testa un modello con system + user (come nella UI)"""
|
| try:
|
| start = time.time()
|
| response = await nvidia_chat(
|
| messages=[
|
| {"role": "system", "content": "You are a helpful assistant."},
|
| {"role": "user", "content": "Say 'ok'."},
|
| ],
|
| model=model, max_tokens=10,
|
| )
|
| return {
|
| "status": "β
OK",
|
| "model": model,
|
| "response": response,
|
| "elapsed_ms": round((time.time() - start) * 1000),
|
| "supports_system_role": model not in MODELS_NO_SYSTEM_ROLE,
|
| }
|
| except Exception as e:
|
| return {"status": "β ERROR", "model": model, "error": str(e)}
|
|
|
|
|
| @app.get("/v1/debug/no-system-models")
|
| async def debug_no_system_models():
|
| """Lista modelli che non supportano il role 'system'"""
|
| return {
|
| "models_no_system_role": sorted(MODELS_NO_SYSTEM_ROLE),
|
| "total": len(MODELS_NO_SYSTEM_ROLE),
|
| "note": "Questi modelli ricevono automaticamente il system_prompt come prefisso del primo user message."
|
| }
|
|
|
|
|
| async def _test_single(mid: str, client: httpx.AsyncClient, sem: asyncio.Semaphore) -> dict:
|
| async with sem:
|
| start = time.time()
|
| try:
|
|
|
| resp = await nvidia_chat(
|
| messages=[
|
| {"role": "system", "content": "You are a helpful assistant."},
|
| {"role": "user", "content": "Say 'ok'"},
|
| ],
|
| model=mid, max_tokens=5, client=client,
|
| )
|
| return {
|
| "model": mid, "status": "β
",
|
| "elapsed_ms": round((time.time() - start) * 1000),
|
| "preview": (resp or "")[:30],
|
| "supports_system_role": mid not in MODELS_NO_SYSTEM_ROLE,
|
| }
|
| except Exception as e:
|
| return {
|
| "model": mid, "status": "β",
|
| "elapsed_ms": round((time.time() - start) * 1000),
|
| "error": str(e)[:150],
|
| }
|
|
|
|
|
| @app.get("/v1/debug/test-all-parallel")
|
| async def debug_test_all_parallel(concurrency: int = Query(15, ge=1, le=50)):
|
| """β‘ Test parallelo async di tutti i modelli del catalogo"""
|
| start = time.time()
|
| testable = list(NVIDIA_MODELS.keys())
|
| sem = asyncio.Semaphore(concurrency)
|
| async with httpx.AsyncClient(timeout=HTTP_TIMEOUT, limits=HTTP_LIMITS) as client:
|
| tasks = [_test_single(m, client, sem) for m in testable]
|
| results = await asyncio.gather(*tasks)
|
| working = [r for r in results if r["status"] == "β
"]
|
| broken = [r for r in results if r["status"] == "β"]
|
| working.sort(key=lambda x: x["elapsed_ms"])
|
| return {
|
| "summary": {
|
| "total_tested": len(results),
|
| "working": len(working),
|
| "broken": len(broken),
|
| "total_time_seconds": round(time.time() - start, 2),
|
| "models_no_system_role": sorted(MODELS_NO_SYSTEM_ROLE),
|
| },
|
| "working_models_by_speed": working,
|
| "broken_models": broken,
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| @app.post("/v1/chat")
|
| async def chat_post(req: ChatRequest):
|
| sid = req.session_id or str(uuid.uuid4())
|
| if req.stream:
|
| return await _stream_response(req, sid)
|
|
|
| if req.use_memory:
|
| memory.add_short(sid, "user", req.message)
|
| messages = memory.get_full_context(sid, req.system_prompt)
|
| else:
|
| messages = [
|
| {"role": "system", "content": req.system_prompt},
|
| {"role": "user", "content": req.message},
|
| ]
|
|
|
| try:
|
| response = await nvidia_chat(
|
| messages=messages, model=req.model,
|
| temperature=req.temperature, max_tokens=req.max_tokens, top_p=req.top_p,
|
| )
|
| if req.use_memory:
|
| memory.add_short(sid, "assistant", response)
|
| return {
|
| "response": response, "model": req.model, "session_id": sid,
|
| "memory_stats": memory.get_stats(sid) if req.use_memory else None,
|
| "timestamp": time.time(),
|
| }
|
| except Exception as e:
|
| raise HTTPException(500, detail=str(e))
|
|
|
|
|
| @app.get("/v1/chat/get")
|
| async def chat_get(
|
| message: str = Query(...),
|
| model: str = Query(DEFAULT_MODEL),
|
| session_id: Optional[str] = Query(None),
|
| system_prompt: str = Query("Sei un assistente AI utile."),
|
| temperature: float = Query(0.7),
|
| max_tokens: int = Query(4096),
|
| use_memory: bool = Query(True),
|
| stream: bool = Query(False),
|
| ):
|
| req = ChatRequest(
|
| message=message, model=model, session_id=session_id,
|
| system_prompt=system_prompt, temperature=temperature,
|
| max_tokens=max_tokens, use_memory=use_memory, stream=stream,
|
| )
|
| if stream:
|
| return await _stream_response(req, session_id or str(uuid.uuid4()))
|
| return await chat_post(req)
|
|
|
|
|
| @app.post("/v1/chat/stream")
|
| async def chat_stream_post(req: ChatRequest):
|
| req.stream = True
|
| return await _stream_response(req, req.session_id or str(uuid.uuid4()))
|
|
|
|
|
| @app.get("/v1/chat/stream")
|
| async def chat_stream_get(
|
| message: str = Query(...),
|
| model: str = Query(DEFAULT_MODEL),
|
| session_id: Optional[str] = Query(None),
|
| system_prompt: str = Query("Sei un assistente AI utile."),
|
| temperature: float = Query(0.7),
|
| max_tokens: int = Query(4096),
|
| use_memory: bool = Query(True),
|
| ):
|
| req = ChatRequest(
|
| message=message, model=model, session_id=session_id,
|
| system_prompt=system_prompt, temperature=temperature,
|
| max_tokens=max_tokens, use_memory=use_memory, stream=True,
|
| )
|
| return await _stream_response(req, session_id or str(uuid.uuid4()))
|
|
|
|
|
| async def _stream_response(req: ChatRequest, session_id: str):
|
| if req.use_memory:
|
| memory.add_short(session_id, "user", req.message)
|
| messages = memory.get_full_context(session_id, req.system_prompt)
|
| else:
|
| messages = [
|
| {"role": "system", "content": req.system_prompt},
|
| {"role": "user", "content": req.message},
|
| ]
|
|
|
| async def event_generator():
|
| full = []
|
| try:
|
| yield {"event": "start", "data": json.dumps({
|
| "session_id": session_id, "model": req.model, "timestamp": time.time(),
|
| "supports_system_role": req.model not in MODELS_NO_SYSTEM_ROLE,
|
| })}
|
| async for chunk in nvidia_chat_stream(
|
| messages=messages, model=req.model,
|
| temperature=req.temperature, max_tokens=req.max_tokens, top_p=req.top_p,
|
| ):
|
| full.append(chunk)
|
| yield {"event": "token", "data": json.dumps({"token": chunk})}
|
|
|
| complete = "".join(full)
|
| if req.use_memory and complete:
|
| memory.add_short(session_id, "assistant", complete)
|
| yield {"event": "done", "data": json.dumps({
|
| "full_response": complete, "session_id": session_id,
|
| "memory_stats": memory.get_stats(session_id) if req.use_memory else None,
|
| "timestamp": time.time(),
|
| })}
|
| except Exception as e:
|
| yield {"event": "error", "data": json.dumps({"error": str(e)})}
|
|
|
| return EventSourceResponse(event_generator())
|
|
|
|
|
|
|
|
|
|
|
|
|
| @app.post("/v1/memory")
|
| async def manage_memory(req: MemoryRequest):
|
| if req.action == "get_stats":
|
| return memory.get_stats(req.session_id)
|
| elif req.action == "clear_short":
|
| memory.clear_short(req.session_id)
|
| return {"status": "cleared"}
|
| elif req.action == "clear_all":
|
| memory.delete_session(req.session_id)
|
| return {"status": "deleted"}
|
| elif req.action == "save_summary":
|
| if not req.summary:
|
| raise HTTPException(400, "summary required")
|
| memory.save_session_summary(req.session_id, req.summary)
|
| return {"status": "saved"}
|
| raise HTTPException(400, f"Unknown action: {req.action}")
|
|
|
|
|
| @app.get("/v1/memory/{session_id}")
|
| async def get_memory_stats(session_id: str):
|
| return memory.get_stats(session_id)
|
|
|
|
|
| @app.get("/v1/memory/{session_id}/history")
|
| async def get_memory_history(session_id: str):
|
| return {
|
| "session_id": session_id,
|
| "short_memory": memory.get_short(session_id),
|
| "long_context": memory.get_long_context(session_id),
|
| "stats": memory.get_stats(session_id),
|
| }
|
|
|
|
|
| @app.get("/v1/sessions")
|
| async def list_sessions():
|
| return {"sessions": memory.list_sessions()}
|
|
|
|
|
| @app.delete("/v1/sessions/{session_id}")
|
| async def delete_session(session_id: str):
|
| memory.delete_session(session_id)
|
| return {"status": "deleted", "session_id": session_id}
|
|
|
|
|
|
|
|
|
|
|
|
|
| MCP_TOOLS = [
|
| {
|
| "name": "nvidia_chat",
|
| "description": "Chat with any verified NVIDIA NIM model with memory (auto-adapts system role)",
|
| "inputSchema": {
|
| "type": "object",
|
| "properties": {
|
| "message": {"type": "string"},
|
| "model": {"type": "string", "default": DEFAULT_MODEL},
|
| "session_id": {"type": "string"},
|
| "system_prompt": {"type": "string", "default": "You are a helpful AI assistant."},
|
| "temperature": {"type": "number", "default": 0.7},
|
| "max_tokens": {"type": "integer", "default": 4096},
|
| "use_memory": {"type": "boolean", "default": True},
|
| },
|
| "required": ["message"]
|
| }
|
| },
|
| {
|
| "name": "multi_model_compare",
|
| "description": "Send same message to multiple models in PARALLEL and compare",
|
| "inputSchema": {
|
| "type": "object",
|
| "properties": {
|
| "message": {"type": "string"},
|
| "models": {"type": "array", "items": {"type": "string"}, "minItems": 2, "maxItems": 6},
|
| "system_prompt": {"type": "string", "default": "You are helpful."},
|
| "max_tokens": {"type": "integer", "default": 512},
|
| },
|
| "required": ["message", "models"]
|
| }
|
| },
|
| {
|
| "name": "list_models",
|
| "description": "List all verified NVIDIA models",
|
| "inputSchema": {
|
| "type": "object",
|
| "properties": {"category": {"type": "string"}}
|
| }
|
| },
|
| {
|
| "name": "memory_stats",
|
| "description": "Memory stats for session",
|
| "inputSchema": {
|
| "type": "object",
|
| "properties": {"session_id": {"type": "string"}},
|
| "required": ["session_id"]
|
| }
|
| },
|
| {
|
| "name": "memory_clear",
|
| "description": "Clear memory",
|
| "inputSchema": {
|
| "type": "object",
|
| "properties": {
|
| "session_id": {"type": "string"},
|
| "clear_type": {"type": "string", "enum": ["short", "all"], "default": "short"}
|
| },
|
| "required": ["session_id"]
|
| }
|
| },
|
| {
|
| "name": "get_conversation_history",
|
| "description": "Get full history for session",
|
| "inputSchema": {
|
| "type": "object",
|
| "properties": {"session_id": {"type": "string"}},
|
| "required": ["session_id"]
|
| }
|
| }
|
| ]
|
|
|
|
|
| @app.post("/v1/mcp")
|
| async def mcp_endpoint(request: Request):
|
| body = await request.json()
|
| method = body.get("method", "")
|
| params = body.get("params", {})
|
| req_id = body.get("id", 1)
|
|
|
| def _ok(r): return JSONResponse({"jsonrpc": "2.0", "id": req_id, "result": r})
|
| def _err(c, m): return JSONResponse({"jsonrpc": "2.0", "id": req_id, "error": {"code": c, "message": m}})
|
|
|
| if method == "initialize":
|
| return _ok({
|
| "protocolVersion": "2024-11-05",
|
| "serverInfo": {"name": "nvidia-ai", "version": "3.1.0"},
|
| "capabilities": {
|
| "tools": {"listChanged": False},
|
| "resources": {"subscribe": False, "listChanged": False}
|
| }
|
| })
|
| elif method == "tools/list":
|
| return _ok({"tools": MCP_TOOLS})
|
| elif method == "tools/call":
|
| try:
|
| result = await _execute_mcp_tool(params.get("name", ""), params.get("arguments", {}))
|
| return _ok({"content": [{"type": "text", "text": json.dumps(result, ensure_ascii=False)}]})
|
| except Exception as e:
|
| return _err(-32000, str(e))
|
| elif method == "resources/list":
|
| return _ok({
|
| "resources": [
|
| {"uri": "nvidia://models", "name": "Models", "mimeType": "application/json"},
|
| {"uri": "nvidia://sessions", "name": "Sessions", "mimeType": "application/json"},
|
| ]
|
| })
|
| elif method == "resources/read":
|
| uri = params.get("uri", "")
|
| if uri == "nvidia://models":
|
| return _ok({"contents": [{"uri": uri, "mimeType": "application/json",
|
| "text": json.dumps(NVIDIA_MODELS, ensure_ascii=False)}]})
|
| elif uri == "nvidia://sessions":
|
| return _ok({"contents": [{"uri": uri, "mimeType": "application/json",
|
| "text": json.dumps(memory.list_sessions())}]})
|
| return _err(-32602, f"Unknown resource: {uri}")
|
| return _err(-32601, f"Method not found: {method}")
|
|
|
|
|
| async def _execute_mcp_tool(name: str, args: dict) -> dict:
|
| if name == "nvidia_chat":
|
| sid = args.get("session_id") or str(uuid.uuid4())
|
| use_mem = args.get("use_memory", True)
|
| message = args["message"]
|
| model = args.get("model", DEFAULT_MODEL)
|
| sysp = args.get("system_prompt", "You are a helpful AI assistant.")
|
|
|
| if use_mem:
|
| memory.add_short(sid, "user", message)
|
| messages = memory.get_full_context(sid, sysp)
|
| else:
|
| messages = [{"role": "system", "content": sysp}, {"role": "user", "content": message}]
|
|
|
| response = await nvidia_chat(
|
| messages=messages, model=model,
|
| temperature=args.get("temperature", 0.7),
|
| max_tokens=args.get("max_tokens", 4096),
|
| )
|
| if use_mem:
|
| memory.add_short(sid, "assistant", response)
|
| return {"response": response, "model": model, "session_id": sid,
|
| "memory_stats": memory.get_stats(sid)}
|
|
|
| elif name == "multi_model_compare":
|
| message = args["message"]
|
| models = args["models"]
|
| sysp = args.get("system_prompt", "You are helpful.")
|
| max_tok = args.get("max_tokens", 512)
|
| messages = [{"role": "system", "content": sysp}, {"role": "user", "content": message}]
|
|
|
| async with httpx.AsyncClient(timeout=HTTP_TIMEOUT, limits=HTTP_LIMITS) as client:
|
| tasks = [nvidia_chat(messages=messages, model=m, max_tokens=max_tok, client=client) for m in models]
|
| responses = await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
| results = {}
|
| for m, r in zip(models, responses):
|
| if isinstance(r, Exception):
|
| results[m] = {"error": str(r)}
|
| else:
|
| results[m] = {"response": r, "name": NVIDIA_MODELS.get(m, {}).get("name", m)}
|
| return {"message": message, "results": results}
|
|
|
| elif name == "list_models":
|
| category = args.get("category")
|
| models = {}
|
| for mid, mi in NVIDIA_MODELS.items():
|
| if category and mi["category"].lower() != category.lower():
|
| continue
|
| models[mid] = mi
|
| return {"models": models, "total": len(models)}
|
|
|
| elif name == "memory_stats":
|
| return memory.get_stats(args["session_id"])
|
|
|
| elif name == "memory_clear":
|
| sid = args["session_id"]
|
| if args.get("clear_type", "short") == "all":
|
| memory.delete_session(sid)
|
| else:
|
| memory.clear_short(sid)
|
| return {"status": "cleared", "session_id": sid}
|
|
|
| elif name == "get_conversation_history":
|
| sid = args["session_id"]
|
| return {
|
| "session_id": sid,
|
| "short_memory": memory.get_short(sid),
|
| "long_context": memory.get_long_context(sid),
|
| "stats": memory.get_stats(sid),
|
| }
|
| raise ValueError(f"Unknown tool: {name}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| @app.get("/api-docs", response_class=HTMLResponse)
|
| async def api_docs_page():
|
| return f"""
|
| <!DOCTYPE html><html><head><meta charset="UTF-8"><title>API Docs</title>
|
| <style>
|
| body{{font-family:sans-serif;background:#0a0a0a;color:#e0e0e0;padding:20px;max-width:1200px;margin:auto}}
|
| h1,h2{{color:#76b900}} pre{{background:#16213e;padding:15px;border-radius:8px;overflow-x:auto}}
|
| code{{color:#7ec8e3}} table{{border-collapse:collapse;width:100%}}
|
| th,td{{padding:8px;border:1px solid #333;text-align:left}}
|
| th{{background:#16213e;color:#76b900}}
|
| </style></head><body>
|
| <h1>π NVIDIA AI Multi-Model API v3.1</h1>
|
| <p><b>{len(NVIDIA_MODELS)} modelli VERIFICATI</b> + auto-adattamento system role</p>
|
|
|
| <h2>π¬ Chat POST</h2>
|
| <pre>curl -X POST /v1/chat -H "Content-Type: application/json" \\
|
| -d '{{"message":"Ciao!","model":"{DEFAULT_MODEL}"}}'</pre>
|
|
|
| <h2>π‘ SSE Streaming</h2>
|
| <pre>curl -N "/v1/chat/stream?message=Ciao&model={DEFAULT_MODEL}"</pre>
|
|
|
| <h2>β‘ Test Parallelo</h2>
|
| <pre>curl "/v1/debug/test-all-parallel?concurrency=15"</pre>
|
|
|
| <h2>π― Modelli senza system role (auto-adattati)</h2>
|
| <pre>curl "/v1/debug/no-system-models"</pre>
|
|
|
| <h2>π MCP</h2>
|
| <pre>curl -X POST /v1/mcp -H "Content-Type: application/json" \\
|
| -d '{{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{{}}}}'</pre>
|
|
|
| </body></html>
|
| """
|
|
|
|
|
|
|
|
|
|
|
|
|
| def get_model_choices():
|
| return [
|
| (f"{mi['name']} [{mi['category']}]", mid)
|
| for mid, mi in NVIDIA_MODELS.items()
|
| ]
|
|
|
|
|
| async def gradio_chat(message, history, model, system_prompt, temperature, max_tokens, session_id, use_memory):
|
| if not message.strip():
|
| yield ""
|
| return
|
|
|
| if not model or not str(model).strip():
|
| model = DEFAULT_MODEL
|
| if not session_id:
|
| session_id = str(uuid.uuid4())[:8]
|
|
|
| if use_memory:
|
| memory.add_short(session_id, "user", message)
|
| messages = memory.get_full_context(session_id, system_prompt)
|
| else:
|
| messages = [{"role": "system", "content": system_prompt}]
|
| for h in history:
|
| if isinstance(h, dict):
|
| messages.append({"role": h.get("role"), "content": h.get("content", "")})
|
| messages.append({"role": "user", "content": message})
|
|
|
| full = ""
|
| try:
|
| async for chunk in nvidia_chat_stream(
|
| messages=messages, model=model,
|
| temperature=temperature, max_tokens=max_tokens,
|
| ):
|
| full += chunk
|
| yield full
|
| except Exception as e:
|
| full = f"β {str(e)}"
|
| yield full
|
|
|
| if use_memory and full and not full.startswith("β"):
|
| memory.add_short(session_id, "assistant", full)
|
|
|
|
|
| def get_memory_info(sid):
|
| return memory.get_stats(sid) if sid else {"error": "Session ID missing"}
|
|
|
|
|
| def clear_session_memory(sid):
|
| if not sid: return "β οΈ Session ID missing"
|
| memory.delete_session(sid)
|
| return f"ποΈ '{sid}' deleted"
|
|
|
|
|
| with gr.Blocks(
|
| title="π NVIDIA AI",
|
| theme=gr.themes.Soft(primary_hue="green", secondary_hue="blue"),
|
| css=".gradio-container{max-width:1400px !important;} footer{display:none !important;}"
|
| ) as demo:
|
| gr.Markdown(f"""
|
| # π NVIDIA AI Multi-Model v3.1
|
| **{len(NVIDIA_MODELS)} modelli VERIFICATI** + auto-adattamento system role β’ Memoria β’ SSE β’ MCP
|
|
|
| π [UI](/) | π [Docs](/api-docs) | π§ [Swagger](/docs) | π [MCP](/v1/mcp)
|
|
|
| **Default:** `{DEFAULT_MODEL}` (β‘ 153ms)
|
| """)
|
|
|
| with gr.Tab("π¬ Chat"):
|
| with gr.Row():
|
| with gr.Column(scale=3):
|
| chatbot = gr.Chatbot(height=600, type="messages", show_copy_button=True)
|
| msg = gr.Textbox(placeholder="Scrivi...", show_label=False, container=False)
|
| with gr.Column(scale=1):
|
| model = gr.Dropdown(choices=get_model_choices(), value=DEFAULT_MODEL, label="π€ Modello")
|
| session_id = gr.Textbox(value="default", label="π Session ID")
|
| system_prompt = gr.Textbox(
|
| value="Sei un assistente AI utile. Rispondi in italiano.",
|
| label="π System Prompt", lines=3,
|
| )
|
| temperature = gr.Slider(0.0, 2.0, value=0.7, step=0.1, label="π‘οΈ Temp")
|
| max_tokens = gr.Slider(256, 16384, value=4096, step=256, label="π Tokens")
|
| use_memory = gr.Checkbox(value=True, label="π§ Memoria")
|
| with gr.Row():
|
| clear_btn = gr.Button("ποΈ", size="sm")
|
| mem_btn = gr.Button("π", size="sm")
|
| mem_output = gr.JSON(label="π Memoria")
|
|
|
| msg.submit(gradio_chat,
|
| inputs=[msg, chatbot, model, system_prompt, temperature, max_tokens, session_id, use_memory],
|
| outputs=[chatbot]).then(lambda: "", outputs=[msg])
|
| clear_btn.click(lambda: [], outputs=[chatbot])
|
| mem_btn.click(get_memory_info, inputs=[session_id], outputs=[mem_output])
|
|
|
| with gr.Tab("π€ Modelli"):
|
| gr.Markdown(f"### π Catalogo β {len(NVIDIA_MODELS)} modelli verificati")
|
| data = [[mi["category"], mi["name"], mid, mi["context"], mi["description"],
|
| "β" if mid in MODELS_NO_SYSTEM_ROLE else "β
"]
|
| for mid, mi in NVIDIA_MODELS.items()]
|
| gr.Dataframe(
|
| headers=["Cat", "Nome", "ID", "Context", "Descrizione", "System Role"],
|
| value=data, interactive=False, wrap=True
|
| )
|
|
|
| with gr.Tab("π Test Parallelo"):
|
| gr.Markdown("### β‘ Verifica live tutti i modelli in parallelo (con system prompt)")
|
| test_btn = gr.Button("π Testa tutti i modelli", variant="primary")
|
| test_output = gr.JSON(label="Risultati")
|
|
|
| async def run_test():
|
| testable = list(NVIDIA_MODELS.keys())
|
| sem = asyncio.Semaphore(15)
|
| start = time.time()
|
| async with httpx.AsyncClient(timeout=HTTP_TIMEOUT, limits=HTTP_LIMITS) as client:
|
| tasks = [_test_single(m, client, sem) for m in testable]
|
| results = await asyncio.gather(*tasks)
|
| working = sorted([r for r in results if r["status"] == "β
"], key=lambda x: x["elapsed_ms"])
|
| broken = [r for r in results if r["status"] == "β"]
|
| return {
|
| "β±οΈ tempo_sec": round(time.time() - start, 2),
|
| "β
funzionanti": len(working),
|
| "β non_funzionanti": len(broken),
|
| "totale": len(results),
|
| "models_no_system_role_detected": sorted(MODELS_NO_SYSTEM_ROLE),
|
| "working_by_speed": working,
|
| "broken": broken,
|
| }
|
| test_btn.click(run_test, outputs=[test_output])
|
|
|
| with gr.Tab("π§ Memoria"):
|
| with gr.Row():
|
| mem_session = gr.Textbox(label="Session ID", value="default")
|
| with gr.Column():
|
| stats_btn = gr.Button("π Stats")
|
| clear_mem_btn = gr.Button("ποΈ Elimina", variant="stop")
|
| mem_display = gr.JSON(label="Risultato")
|
| stats_btn.click(get_memory_info, inputs=[mem_session], outputs=[mem_display])
|
| clear_mem_btn.click(clear_session_memory, inputs=[mem_session], outputs=[mem_display])
|
|
|
| with gr.Tab("π― System Role"):
|
| gr.Markdown(f"""
|
| ## π― Auto-adattamento System Role
|
|
|
| Alcuni modelli (come **Gemma 2**, **Gemma 3n**, **DiffusionGemma**) non supportano il ruolo `system`.
|
|
|
| Questo Space **rileva automaticamente** l'errore e converte il `system_prompt` come **prefisso** del primo messaggio user.
|
|
|
| ### π Modelli attualmente in auto-adattamento:
|
| """)
|
| no_sys_display = gr.JSON(label="Modelli senza system role")
|
|
|
| def get_no_sys():
|
| return {
|
| "models_no_system_role": sorted(MODELS_NO_SYSTEM_ROLE),
|
| "total": len(MODELS_NO_SYSTEM_ROLE),
|
| "note": "Aggiornato dinamicamente quando viene rilevato un errore 'system role not supported'"
|
| }
|
|
|
| refresh_btn = gr.Button("π Aggiorna lista")
|
| refresh_btn.click(get_no_sys, outputs=[no_sys_display])
|
| demo.load(get_no_sys, outputs=[no_sys_display])
|
|
|
| with gr.Tab("π Docs"):
|
| gr.Markdown("""
|
| ## π Documentazione
|
|
|
| - **[API HTML Docs](/api-docs)**
|
| - **[Swagger UI](/docs)**
|
| - **[Info JSON](/info)**
|
|
|
| ### Endpoints principali:
|
| - `GET /v1/chat/get?message=Ciao` β Chat rapida
|
| - `POST /v1/chat` β Chat JSON
|
| - `GET /v1/chat/stream` β SSE streaming
|
| - `POST /v1/mcp` β MCP JSON-RPC
|
| - `GET /v1/debug/test-all-parallel` β Test parallelo
|
| - `GET /v1/debug/no-system-models` β Modelli senza system role
|
| """)
|
|
|
|
|
| app = gr.mount_gradio_app(app, demo, path="/ui")
|
|
|
|
|
| if __name__ == "__main__":
|
| import uvicorn
|
| uvicorn.run(app, host="0.0.0.0", port=7860) |