elmarcito commited on
Commit
3cbc846
Β·
verified Β·
1 Parent(s): 2be6704

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -273
app.py CHANGED
@@ -1,9 +1,11 @@
1
  """
2
  πŸš€ NVIDIA AI Multi-Model Space
3
- - Tutti i modelli NVIDIA NIM
4
- - Memoria breve e lunga
5
- - API REST GET/POST + SSE streaming
6
  - Server MCP integrato
 
 
7
  """
8
 
9
  import os
@@ -12,15 +14,18 @@ import time
12
  import uuid
13
  import base64
14
  import asyncio
15
- import hashlib
16
  from typing import Optional, AsyncGenerator
17
 
18
  import gradio as gr
19
  import httpx
20
  from dotenv import load_dotenv
21
  from fastapi import FastAPI, Request, Query, HTTPException
22
- from fastapi.responses import StreamingResponse, JSONResponse, HTMLResponse
 
 
23
  from fastapi.middleware.cors import CORSMiddleware
 
24
  from sse_starlette.sse import EventSourceResponse
25
  from pydantic import BaseModel, Field
26
 
@@ -389,6 +394,11 @@ app.add_middleware(
389
  allow_headers=["*"],
390
  )
391
 
 
 
 
 
 
392
 
393
  # ── Pydantic Models ───────────────────────────────────────────
394
 
@@ -433,15 +443,42 @@ class MemoryRequest(BaseModel):
433
  summary: Optional[str] = None
434
 
435
 
436
- # ── API Endpoints ──────────────────────────────────────────────
 
 
437
 
438
- @app.get("/")
439
  async def root():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  return {
441
  "service": "πŸš€ NVIDIA AI Multi-Model Space",
442
  "version": "2.0.0",
443
  "models_available": len(NVIDIA_MODELS),
444
  "endpoints": {
 
 
445
  "docs": "/docs",
446
  "api_docs_html": "/api-docs",
447
  "models": "/v1/models",
@@ -466,6 +503,10 @@ async def health():
466
  }
467
 
468
 
 
 
 
 
469
  @app.get("/v1/models")
470
  async def list_models(category: Optional[str] = None):
471
  """Lista tutti i modelli disponibili, filtrabile per categoria"""
@@ -490,7 +531,9 @@ async def get_model_info(model_id: str):
490
  return {"model_id": model_id, **NVIDIA_MODELS[model_id]}
491
 
492
 
493
- # ── Chat POST (non-streaming) ─────────────────────────────────
 
 
494
 
495
  @app.post("/v1/chat")
496
  async def chat_post(req: ChatRequest):
@@ -503,7 +546,6 @@ async def chat_post(req: ChatRequest):
503
  if req.stream:
504
  return await _stream_response(req, session_id)
505
 
506
- # Costruisci contesto con memoria
507
  if req.use_memory:
508
  memory.add_short(session_id, "user", req.message)
509
  messages = memory.get_full_context(session_id, req.system_prompt)
@@ -536,8 +578,6 @@ async def chat_post(req: ChatRequest):
536
  raise HTTPException(500, detail=str(e))
537
 
538
 
539
- # ── Chat GET ───────────────────────────────────────────────────
540
-
541
  @app.get("/v1/chat/get")
542
  async def chat_get(
543
  message: str = Query(..., description="Messaggio utente"),
@@ -569,8 +609,6 @@ async def chat_get(
569
  return await chat_post(req)
570
 
571
 
572
- # ── Chat SSE Stream ───────────────────────────────────────────
573
-
574
  @app.post("/v1/chat/stream")
575
  async def chat_stream_post(req: ChatRequest):
576
  """Endpoint dedicato per streaming SSE via POST"""
@@ -619,7 +657,6 @@ async def _stream_response(req: ChatRequest, session_id: str):
619
  async def event_generator():
620
  full_response = []
621
  try:
622
- # Evento di inizio
623
  yield {
624
  "event": "start",
625
  "data": json.dumps({
@@ -642,12 +679,10 @@ async def _stream_response(req: ChatRequest, session_id: str):
642
  "data": json.dumps({"token": chunk})
643
  }
644
 
645
- # Salva in memoria
646
  complete = "".join(full_response)
647
  if req.use_memory:
648
  memory.add_short(session_id, "assistant", complete)
649
 
650
- # Evento di fine
651
  yield {
652
  "event": "done",
653
  "data": json.dumps({
@@ -667,7 +702,9 @@ async def _stream_response(req: ChatRequest, session_id: str):
667
  return EventSourceResponse(event_generator())
668
 
669
 
670
- # ── Memory Management ─────────────────────────────────────────
 
 
671
 
672
  @app.post("/v1/memory")
673
  async def manage_memory(req: MemoryRequest):
@@ -830,7 +867,6 @@ async def mcp_endpoint(request: Request):
830
  "error": {"code": code, "message": message}
831
  })
832
 
833
- # ── Initialize ──
834
  if method == "initialize":
835
  return _mcp_response({
836
  "protocolVersion": "2024-11-05",
@@ -845,11 +881,9 @@ async def mcp_endpoint(request: Request):
845
  }
846
  })
847
 
848
- # ── Tools List ──
849
  elif method == "tools/list":
850
  return _mcp_response({"tools": MCP_TOOLS})
851
 
852
- # ── Tools Call ──
853
  elif method == "tools/call":
854
  tool_name = params.get("name", "")
855
  tool_args = params.get("arguments", {})
@@ -862,7 +896,6 @@ async def mcp_endpoint(request: Request):
862
  except Exception as e:
863
  return _mcp_error(-32000, str(e))
864
 
865
- # ── Resources List ──
866
  elif method == "resources/list":
867
  return _mcp_response({
868
  "resources": [
@@ -881,7 +914,6 @@ async def mcp_endpoint(request: Request):
881
  ]
882
  })
883
 
884
- # ── Resources Read ──
885
  elif method == "resources/read":
886
  uri = params.get("uri", "")
887
  if uri == "nvidia://models":
@@ -1010,19 +1042,7 @@ async def api_docs_page():
1010
  .param-table { width: 100%; border-collapse: collapse; margin: 10px 0; }
1011
  .param-table th, .param-table td { padding: 10px; border: 1px solid var(--border); text-align: left; }
1012
  .param-table th { background: var(--code-bg); color: var(--accent); }
1013
- .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.8em; margin: 2px; }
1014
- .badge-llm { background: #1a5276; color: #85c1e9; }
1015
- .badge-code { background: #1e4620; color: #82e0aa; }
1016
- .badge-vision { background: #4a235a; color: #d7bde2; }
1017
- .badge-reasoning { background: #7d3c00; color: #f0b27a; }
1018
- .tab-container { margin: 20px 0; }
1019
- .tabs { display: flex; gap: 5px; margin-bottom: -1px; }
1020
- .tab { padding: 10px 20px; background: var(--code-bg); border: 1px solid var(--border); border-bottom: none; border-radius: 8px 8px 0 0; cursor: pointer; color: #888; }
1021
- .tab.active { background: var(--card); color: var(--accent); border-color: var(--accent); }
1022
- .tab-content { display: none; background: var(--card); border: 1px solid var(--border); border-radius: 0 8px 8px 8px; padding: 20px; }
1023
- .tab-content.active { display: block; }
1024
  .note { background: #1a3a1a; border-left: 4px solid var(--accent); padding: 15px; margin: 15px 0; border-radius: 0 8px 8px 0; }
1025
- .warning { background: #3a2a1a; border-left: 4px solid #f0b27a; padding: 15px; margin: 15px 0; border-radius: 0 8px 8px 0; }
1026
  a { color: var(--accent); }
1027
  </style>
1028
  </head>
@@ -1034,163 +1054,56 @@ async def api_docs_page():
1034
 
1035
  <div class="note">
1036
  <strong>Base URL:</strong> <code>https://YOUR-SPACE.hf.space</code><br>
1037
- <strong>Autenticazione:</strong> API key NVIDIA pre-configurata (default) oppure fornire la propria.
1038
  </div>
1039
 
1040
- <h2>πŸ“‹ Endpoints Rapidi</h2>
1041
  <div class="endpoint">
1042
  <table class="param-table">
1043
  <tr><th>Metodo</th><th>Endpoint</th><th>Descrizione</th></tr>
1044
- <tr><td><span class="method get">GET</span></td><td><code>/v1/models</code></td><td>Lista tutti i modelli</td></tr>
 
 
 
1045
  <tr><td><span class="method get">GET</span></td><td><code>/v1/chat/get</code></td><td>Chat via GET</td></tr>
1046
  <tr><td><span class="method post">POST</span></td><td><code>/v1/chat</code></td><td>Chat via POST</td></tr>
1047
- <tr><td><span class="method get">GET</span></td><td><code>/v1/chat/stream</code></td><td>Chat streaming SSE via GET</td></tr>
1048
- <tr><td><span class="method post">POST</span></td><td><code>/v1/chat/stream</code></td><td>Chat streaming SSE via POST</td></tr>
1049
  <tr><td><span class="method post">POST</span></td><td><code>/v1/memory</code></td><td>Gestione memoria</td></tr>
1050
- <tr><td><span class="method get">GET</span></td><td><code>/v1/memory/{session_id}</code></td><td>Stats memoria sessione</td></tr>
1051
  <tr><td><span class="method post">POST</span></td><td><code>/v1/mcp</code></td><td>Server MCP (JSON-RPC)</td></tr>
1052
  </table>
1053
  </div>
1054
 
1055
- <h2>πŸ’¬ Chat API</h2>
1056
-
1057
- <h3>POST /v1/chat</h3>
1058
  <div class="endpoint">
1059
- <span class="method post">POST</span> <span class="url">/v1/chat</span>
1060
- <p>Chat completo con supporto memoria e streaming opzionale.</p>
1061
-
1062
- <pre><code># cURL - Chat semplice
1063
- curl -X POST https://YOUR-SPACE.hf.space/v1/chat \\
1064
  -H "Content-Type: application/json" \\
1065
  -d '{
1066
  "message": "Spiegami la relativit&agrave;",
1067
  "model": "meta/llama-3.1-405b-instruct",
1068
  "session_id": "user-123",
1069
- "temperature": 0.7,
1070
  "use_memory": true
1071
  }'</code></pre>
1072
-
1073
- <pre><code># Python
1074
- import requests
1075
-
1076
- resp = requests.post("https://YOUR-SPACE.hf.space/v1/chat", json={
1077
- "message": "Ciao! Come funziona la memoria?",
1078
- "model": "meta/llama-3.1-405b-instruct",
1079
- "session_id": "my-session",
1080
- "use_memory": True
1081
- })
1082
- print(resp.json()["response"])</code></pre>
1083
-
1084
- <pre><code># JavaScript (fetch)
1085
- const resp = await fetch('https://YOUR-SPACE.hf.space/v1/chat', {
1086
- method: 'POST',
1087
- headers: {'Content-Type': 'application/json'},
1088
- body: JSON.stringify({
1089
- message: 'Hello!',
1090
- model: 'meta/llama-3.1-405b-instruct',
1091
- session_id: 'js-session'
1092
- })
1093
- });
1094
- const data = await resp.json();
1095
- console.log(data.response);</code></pre>
1096
  </div>
1097
 
1098
- <h3>GET /v1/chat/get</h3>
1099
  <div class="endpoint">
1100
- <span class="method get">GET</span> <span class="url">/v1/chat/get?message=...&amp;model=...</span>
1101
- <p>Chat via query parameters β€” comodo per test rapidi.</p>
1102
- <pre><code># Browser o cURL
1103
- curl "https://YOUR-SPACE.hf.space/v1/chat/get?message=Ciao&model=meta/llama-3.1-8b-instruct&session_id=test"</code></pre>
1104
- </div>
1105
-
1106
- <h2>πŸ“‘ Streaming SSE</h2>
1107
- <div class="endpoint">
1108
- <span class="method get">GET</span> <span class="url">/v1/chat/stream?message=...</span><br>
1109
- <span class="method post">POST</span> <span class="url">/v1/chat/stream</span>
1110
- <p>Risposta in tempo reale via Server-Sent Events.</p>
1111
-
1112
- <p><strong>Eventi SSE:</strong></p>
1113
- <ul>
1114
- <li><code>event: start</code> β€” Inizio generazione (contiene session_id, model)</li>
1115
- <li><code>event: token</code> β€” Singolo token generato</li>
1116
- <li><code>event: done</code> β€” Fine (contiene risposta completa + stats memoria)</li>
1117
- <li><code>event: error</code> β€” Errore</li>
1118
- </ul>
1119
-
1120
- <pre><code># JavaScript - EventSource (GET)
1121
  const es = new EventSource(
1122
- 'https://YOUR-SPACE.hf.space/v1/chat/stream?message=Raccontami+una+storia&session_id=sse-demo'
1123
  );
1124
-
1125
- es.addEventListener('token', (e) => {
1126
- const {token} = JSON.parse(e.data);
1127
- process.stdout.write(token); // o document.body.innerHTML += token;
1128
- });
1129
-
1130
- es.addEventListener('done', (e) => {
1131
- const {full_response, memory_stats} = JSON.parse(e.data);
1132
- console.log('\\n--- DONE ---');
1133
- console.log('Stats:', memory_stats);
1134
- es.close();
1135
  });
 
1136
 
1137
- es.addEventListener('error', (e) => {
1138
- console.error('Error:', e.data);
1139
- es.close();
1140
- });</code></pre>
1141
-
1142
- <pre><code># Python - SSE streaming con httpx
1143
- import httpx
1144
- import json
1145
-
1146
- with httpx.stream('GET', 'https://YOUR-SPACE.hf.space/v1/chat/stream',
1147
- params={'message': 'Ciao!', 'session_id': 'py-sse'}) as r:
1148
- for line in r.iter_lines():
1149
- if line.startswith('data: '):
1150
- data = json.loads(line[6:])
1151
- if 'token' in data:
1152
- print(data['token'], end='', flush=True)</code></pre>
1153
-
1154
- <pre><code># cURL - SSE streaming
1155
- curl -N "https://YOUR-SPACE.hf.space/v1/chat/stream?message=Ciao&session_id=curl-test"</code></pre>
1156
- </div>
1157
-
1158
- <h2>🧠 Memoria API</h2>
1159
- <div class="endpoint">
1160
- <p>Il sistema di memoria gestisce automaticamente:</p>
1161
- <ul>
1162
- <li><strong>Memoria Breve:</strong> ultimi 20 messaggi della sessione attiva (in RAM)</li>
1163
- <li><strong>Memoria Lunga:</strong> messaggi precedenti salvati in SQLite con limite token</li>
1164
- <li><strong>Riassunti:</strong> possibilit&agrave; di salvare riassunti di sessione</li>
1165
- </ul>
1166
-
1167
- <pre><code># Statistiche memoria
1168
- curl https://YOUR-SPACE.hf.space/v1/memory/my-session
1169
-
1170
- # Cronologia completa
1171
- curl https://YOUR-SPACE.hf.space/v1/memory/my-session/history
1172
-
1173
- # Pulisci memoria breve
1174
- curl -X POST https://YOUR-SPACE.hf.space/v1/memory \\
1175
- -H "Content-Type: application/json" \\
1176
- -d '{"session_id": "my-session", "action": "clear_short"}'
1177
-
1178
- # Elimina tutta la sessione
1179
- curl -X DELETE https://YOUR-SPACE.hf.space/v1/sessions/my-session
1180
-
1181
- # Salva riassunto
1182
- curl -X POST https://YOUR-SPACE.hf.space/v1/memory \\
1183
- -H "Content-Type: application/json" \\
1184
- -d '{"session_id": "my-session", "action": "save_summary", "summary": "L utente sta imparando Python"}'</code></pre>
1185
  </div>
1186
 
1187
- <h2>πŸ”Œ Server MCP</h2>
1188
  <div class="endpoint">
1189
- <span class="method post">POST</span> <span class="url">/v1/mcp</span>
1190
- <p>Endpoint MCP (Model Context Protocol) per integrazioni con Claude Desktop, Cursor, etc.</p>
1191
-
1192
- <pre><code># Configurazione MCP per Claude Desktop / Cursor
1193
- // In mcp_settings.json:
1194
  {
1195
  "mcpServers": {
1196
  "nvidia-ai": {
@@ -1199,78 +1112,6 @@ curl -X POST https://YOUR-SPACE.hf.space/v1/memory \\
1199
  }
1200
  }
1201
  }</code></pre>
1202
-
1203
- <pre><code># Test MCP - Initialize
1204
- curl -X POST https://YOUR-SPACE.hf.space/v1/mcp \\
1205
- -H "Content-Type: application/json" \\
1206
- -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
1207
-
1208
- # Test MCP - List Tools
1209
- curl -X POST https://YOUR-SPACE.hf.space/v1/mcp \\
1210
- -H "Content-Type: application/json" \\
1211
- -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
1212
-
1213
- # Test MCP - Call Tool
1214
- curl -X POST https://YOUR-SPACE.hf.space/v1/mcp \\
1215
- -H "Content-Type: application/json" \\
1216
- -d '{
1217
- "jsonrpc":"2.0","id":3,
1218
- "method":"tools/call",
1219
- "params":{
1220
- "name":"nvidia_chat",
1221
- "arguments":{
1222
- "message":"What is quantum computing?",
1223
- "model":"meta/llama-3.1-70b-instruct",
1224
- "session_id":"mcp-test"
1225
- }
1226
- }
1227
- }'</code></pre>
1228
-
1229
- <h3>Tools MCP Disponibili:</h3>
1230
- <table class="param-table">
1231
- <tr><th>Tool</th><th>Descrizione</th></tr>
1232
- <tr><td><code>nvidia_chat</code></td><td>Chat con qualsiasi modello NVIDIA + memoria</td></tr>
1233
- <tr><td><code>nvidia_chat_stream</code></td><td>Info per streaming SSE</td></tr>
1234
- <tr><td><code>list_models</code></td><td>Lista modelli (filtrabile per categoria)</td></tr>
1235
- <tr><td><code>memory_stats</code></td><td>Statistiche memoria sessione</td></tr>
1236
- <tr><td><code>memory_clear</code></td><td>Pulisci memoria (short/all)</td></tr>
1237
- <tr><td><code>get_conversation_history</code></td><td>Cronologia completa conversazione</td></tr>
1238
- </table>
1239
- </div>
1240
-
1241
- <h2>πŸ€– Modelli Disponibili</h2>
1242
- <div class="endpoint">
1243
- <p>
1244
- <span class="badge badge-llm">LLM</span>
1245
- <span class="badge badge-code">Code</span>
1246
- <span class="badge badge-vision">Vision</span>
1247
- <span class="badge badge-reasoning">Reasoning</span>
1248
- e altri...
1249
- </p>
1250
- <pre><code># Lista tutti i modelli
1251
- curl https://YOUR-SPACE.hf.space/v1/models
1252
-
1253
- # Filtra per categoria
1254
- curl "https://YOUR-SPACE.hf.space/v1/models?category=Code"
1255
-
1256
- # Info singolo modello
1257
- curl https://YOUR-SPACE.hf.space/v1/models/meta/llama-3.1-405b-instruct</code></pre>
1258
- </div>
1259
-
1260
- <h2>πŸ“ Schema Parametri Chat</h2>
1261
- <div class="endpoint">
1262
- <table class="param-table">
1263
- <tr><th>Parametro</th><th>Tipo</th><th>Default</th><th>Descrizione</th></tr>
1264
- <tr><td><code>message</code></td><td>string</td><td><em>richiesto</em></td><td>Messaggio dell'utente</td></tr>
1265
- <tr><td><code>model</code></td><td>string</td><td>meta/llama-3.1-405b-instruct</td><td>ID modello NVIDIA</td></tr>
1266
- <tr><td><code>session_id</code></td><td>string</td><td>auto-generated</td><td>ID sessione per memoria</td></tr>
1267
- <tr><td><code>system_prompt</code></td><td>string</td><td>"Sei un assistente AI..."</td><td>System prompt</td></tr>
1268
- <tr><td><code>temperature</code></td><td>float</td><td>0.7</td><td>Creativit&agrave; (0.0 - 2.0)</td></tr>
1269
- <tr><td><code>max_tokens</code></td><td>int</td><td>4096</td><td>Max token risposta</td></tr>
1270
- <tr><td><code>top_p</code></td><td>float</td><td>0.9</td><td>Nucleus sampling</td></tr>
1271
- <tr><td><code>use_memory</code></td><td>bool</td><td>true</td><td>Usa sistema memoria</td></tr>
1272
- <tr><td><code>stream</code></td><td>bool</td><td>false</td><td>Abilita SSE streaming</td></tr>
1273
- </table>
1274
  </div>
1275
 
1276
  </div>
@@ -1280,7 +1121,7 @@ curl https://YOUR-SPACE.hf.space/v1/models/meta/llama-3.1-405b-instruct</code></
1280
 
1281
 
1282
  # ══════════════════════════════════════════════════════════════
1283
- # GRADIO UI
1284
  # ══════════════════════════════════════════════════════════════
1285
 
1286
  def get_model_choices():
@@ -1315,10 +1156,11 @@ async def gradio_chat(
1315
  else:
1316
  messages = [{"role": "system", "content": system_prompt}]
1317
  for h in history:
1318
- if h["role"] == "user":
1319
- messages.append({"role": "user", "content": h["content"]})
1320
- elif h["role"] == "assistant":
1321
- messages.append({"role": "assistant", "content": h["content"]})
 
1322
  messages.append({"role": "user", "content": message})
1323
 
1324
  full_response = ""
@@ -1341,9 +1183,8 @@ async def gradio_chat(
1341
 
1342
  def get_memory_info(session_id: str):
1343
  if not session_id:
1344
- return "⚠️ Inserisci un Session ID"
1345
- stats = memory.get_stats(session_id)
1346
- return json.dumps(stats, indent=2, ensure_ascii=False)
1347
 
1348
 
1349
  def clear_session_memory(session_id: str):
@@ -1353,8 +1194,6 @@ def clear_session_memory(session_id: str):
1353
  return f"πŸ—‘οΈ Memoria sessione '{session_id}' eliminata"
1354
 
1355
 
1356
- # ── Build Gradio Interface ────────────────────────────────────
1357
-
1358
  with gr.Blocks(
1359
  title="πŸš€ NVIDIA AI Multi-Model Space",
1360
  theme=gr.themes.Soft(
@@ -1372,7 +1211,7 @@ with gr.Blocks(
1372
  # πŸš€ NVIDIA AI Multi-Model Space
1373
  **Chat con tutti i modelli NVIDIA NIM** β€’ Memoria Breve/Lunga β€’ API REST β€’ SSE Streaming β€’ MCP Server
1374
 
1375
- πŸ“– [Documentazione API](/api-docs) &nbsp;|&nbsp; πŸ”§ [OpenAPI Swagger](/docs) &nbsp;|&nbsp; πŸ”Œ [MCP Endpoint](/v1/mcp)
1376
  """)
1377
 
1378
  with gr.Tab("πŸ’¬ Chat"):
@@ -1382,7 +1221,6 @@ with gr.Blocks(
1382
  height=600,
1383
  type="messages",
1384
  show_copy_button=True,
1385
- avatar_images=(None, "https://upload.wikimedia.org/wikipedia/sco/thumb/2/21/Nvidia_logo.svg/200px-Nvidia_logo.svg.png"),
1386
  placeholder="Seleziona un modello e inizia a chattare..."
1387
  )
1388
  msg = gr.Textbox(
@@ -1425,7 +1263,6 @@ with gr.Blocks(
1425
 
1426
  mem_output = gr.JSON(label="πŸ“Š Memoria", visible=True)
1427
 
1428
- # Chat handler
1429
  msg.submit(
1430
  gradio_chat,
1431
  inputs=[msg, chatbot, model, system_prompt, temperature, max_tokens, session_id, use_memory],
@@ -1458,8 +1295,7 @@ with gr.Blocks(
1458
  with gr.Tab("🧠 Memoria"):
1459
  gr.Markdown("""
1460
  ## 🧠 Gestione Memoria
1461
- Il sistema gestisce automaticamente:
1462
- - **Memoria Breve**: ultimi 20 messaggi (configurabile)
1463
  - **Memoria Lunga**: messaggi piΓΉ vecchi salvati in SQLite
1464
  - **Riassunti**: riassunti di sessione persistenti
1465
  """)
@@ -1470,7 +1306,7 @@ with gr.Blocks(
1470
  stats_btn = gr.Button("πŸ“Š Mostra Statistiche")
1471
  clear_mem_btn = gr.Button("πŸ—‘οΈ Elimina Sessione", variant="stop")
1472
 
1473
- mem_display = gr.Textbox(label="Risultato", lines=10, interactive=False)
1474
 
1475
  stats_btn.click(get_memory_info, inputs=[mem_session], outputs=[mem_display])
1476
  clear_mem_btn.click(clear_session_memory, inputs=[mem_session], outputs=[mem_display])
@@ -1479,15 +1315,13 @@ with gr.Blocks(
1479
  gr.Markdown("""
1480
  ## πŸ“– Documentazione API
1481
 
1482
- πŸ‘‰ **[Apri documentazione completa](/api-docs)** (pagina HTML interattiva)
1483
-
1484
- πŸ‘‰ **[OpenAPI / Swagger UI](/docs)** (auto-generata da FastAPI)
1485
 
1486
- ---
1487
 
1488
  ### Quick Start
1489
 
1490
- #### Chat via GET (test veloce)
1491
  ```
1492
  GET /v1/chat/get?message=Ciao&model=meta/llama-3.1-8b-instruct&session_id=test
1493
  ```
@@ -1504,24 +1338,12 @@ with gr.Blocks(
1504
  curl -N "/v1/chat/stream?message=Raccontami+una+storia"
1505
  ```
1506
 
1507
- #### MCP (Model Context Protocol)
1508
  ```bash
1509
  curl -X POST /v1/mcp \\
1510
  -H "Content-Type: application/json" \\
1511
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
1512
  ```
1513
-
1514
- ### Configurazione MCP per Claude Desktop / Cursor
1515
- ```json
1516
- {
1517
- "mcpServers": {
1518
- "nvidia-ai": {
1519
- "url": "https://YOUR-SPACE.hf.space/v1/mcp",
1520
- "transport": "http"
1521
- }
1522
- }
1523
- }
1524
- ```
1525
  """)
1526
 
1527
 
@@ -1532,12 +1354,6 @@ with gr.Blocks(
1532
  app = gr.mount_gradio_app(app, demo, path="/ui")
1533
 
1534
 
1535
- # Redirect root HTML to UI for browser visits
1536
- @app.get("/app", response_class=HTMLResponse)
1537
- async def redirect_to_ui():
1538
- return '<html><head><meta http-equiv="refresh" content="0;url=/ui"></head></html>'
1539
-
1540
-
1541
  if __name__ == "__main__":
1542
  import uvicorn
1543
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  """
2
  πŸš€ NVIDIA AI Multi-Model Space
3
+ - Tutti i modelli NVIDIA NIM (35+)
4
+ - Memoria breve (RAM) e lunga (SQLite)
5
+ - API REST GET/POST + SSE Streaming
6
  - Server MCP integrato
7
+ - UI Web moderna alla root
8
+ - Interfaccia Gradio su /ui
9
  """
10
 
11
  import os
 
14
  import uuid
15
  import base64
16
  import asyncio
17
+ from pathlib import Path
18
  from typing import Optional, AsyncGenerator
19
 
20
  import gradio as gr
21
  import httpx
22
  from dotenv import load_dotenv
23
  from fastapi import FastAPI, Request, Query, HTTPException
24
+ from fastapi.responses import (
25
+ StreamingResponse, JSONResponse, HTMLResponse, FileResponse
26
+ )
27
  from fastapi.middleware.cors import CORSMiddleware
28
+ from fastapi.staticfiles import StaticFiles
29
  from sse_starlette.sse import EventSourceResponse
30
  from pydantic import BaseModel, Field
31
 
 
394
  allow_headers=["*"],
395
  )
396
 
397
+ # Mount statici se la cartella esiste
398
+ STATIC_DIR = Path(__file__).parent / "static"
399
+ if STATIC_DIR.exists():
400
+ app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
401
+
402
 
403
  # ── Pydantic Models ───────────────────────────────────────────
404
 
 
443
  summary: Optional[str] = None
444
 
445
 
446
+ # ══════════════════════════════════════════════════════════════
447
+ # ROOT UI + INFO
448
+ # ══════════════════════════════════════════════════════════════
449
 
450
+ @app.get("/", response_class=HTMLResponse)
451
  async def root():
452
+ """Serve la UI web principale (static/index.html)"""
453
+ ui_file = STATIC_DIR / "index.html"
454
+ if ui_file.exists():
455
+ return FileResponse(str(ui_file))
456
+ # Fallback se manca il file
457
+ return HTMLResponse("""
458
+ <!DOCTYPE html><html><head><title>NVIDIA AI</title></head>
459
+ <body style="font-family:sans-serif;background:#0a0e14;color:#e4e6eb;padding:40px">
460
+ <h1>πŸš€ NVIDIA AI Multi-Model</h1>
461
+ <p>UI non trovata (static/index.html mancante).</p>
462
+ <ul>
463
+ <li><a href="/ui" style="color:#76b900">Interfaccia Gradio</a></li>
464
+ <li><a href="/docs" style="color:#76b900">Swagger UI</a></li>
465
+ <li><a href="/api-docs" style="color:#76b900">API Docs HTML</a></li>
466
+ <li><a href="/info" style="color:#76b900">Info JSON</a></li>
467
+ </ul>
468
+ </body></html>
469
+ """)
470
+
471
+
472
+ @app.get("/info")
473
+ async def info():
474
+ """Info del servizio (JSON)"""
475
  return {
476
  "service": "πŸš€ NVIDIA AI Multi-Model Space",
477
  "version": "2.0.0",
478
  "models_available": len(NVIDIA_MODELS),
479
  "endpoints": {
480
+ "ui": "/",
481
+ "gradio": "/ui",
482
  "docs": "/docs",
483
  "api_docs_html": "/api-docs",
484
  "models": "/v1/models",
 
503
  }
504
 
505
 
506
+ # ══════════════════════════════════════════════════════════════
507
+ # MODELS
508
+ # ══════════════════════════════════════════════════════════════
509
+
510
  @app.get("/v1/models")
511
  async def list_models(category: Optional[str] = None):
512
  """Lista tutti i modelli disponibili, filtrabile per categoria"""
 
531
  return {"model_id": model_id, **NVIDIA_MODELS[model_id]}
532
 
533
 
534
+ # ══════════════════════════════════════════════════════════════
535
+ # CHAT ENDPOINTS
536
+ # ══════════════════════════════════════════════════════════════
537
 
538
  @app.post("/v1/chat")
539
  async def chat_post(req: ChatRequest):
 
546
  if req.stream:
547
  return await _stream_response(req, session_id)
548
 
 
549
  if req.use_memory:
550
  memory.add_short(session_id, "user", req.message)
551
  messages = memory.get_full_context(session_id, req.system_prompt)
 
578
  raise HTTPException(500, detail=str(e))
579
 
580
 
 
 
581
  @app.get("/v1/chat/get")
582
  async def chat_get(
583
  message: str = Query(..., description="Messaggio utente"),
 
609
  return await chat_post(req)
610
 
611
 
 
 
612
  @app.post("/v1/chat/stream")
613
  async def chat_stream_post(req: ChatRequest):
614
  """Endpoint dedicato per streaming SSE via POST"""
 
657
  async def event_generator():
658
  full_response = []
659
  try:
 
660
  yield {
661
  "event": "start",
662
  "data": json.dumps({
 
679
  "data": json.dumps({"token": chunk})
680
  }
681
 
 
682
  complete = "".join(full_response)
683
  if req.use_memory:
684
  memory.add_short(session_id, "assistant", complete)
685
 
 
686
  yield {
687
  "event": "done",
688
  "data": json.dumps({
 
702
  return EventSourceResponse(event_generator())
703
 
704
 
705
+ # ══════════════════════════════════════════════════════════════
706
+ # MEMORY MANAGEMENT
707
+ # ══════════════════════════════════════════════════════════════
708
 
709
  @app.post("/v1/memory")
710
  async def manage_memory(req: MemoryRequest):
 
867
  "error": {"code": code, "message": message}
868
  })
869
 
 
870
  if method == "initialize":
871
  return _mcp_response({
872
  "protocolVersion": "2024-11-05",
 
881
  }
882
  })
883
 
 
884
  elif method == "tools/list":
885
  return _mcp_response({"tools": MCP_TOOLS})
886
 
 
887
  elif method == "tools/call":
888
  tool_name = params.get("name", "")
889
  tool_args = params.get("arguments", {})
 
896
  except Exception as e:
897
  return _mcp_error(-32000, str(e))
898
 
 
899
  elif method == "resources/list":
900
  return _mcp_response({
901
  "resources": [
 
914
  ]
915
  })
916
 
 
917
  elif method == "resources/read":
918
  uri = params.get("uri", "")
919
  if uri == "nvidia://models":
 
1042
  .param-table { width: 100%; border-collapse: collapse; margin: 10px 0; }
1043
  .param-table th, .param-table td { padding: 10px; border: 1px solid var(--border); text-align: left; }
1044
  .param-table th { background: var(--code-bg); color: var(--accent); }
 
 
 
 
 
 
 
 
 
 
 
1045
  .note { background: #1a3a1a; border-left: 4px solid var(--accent); padding: 15px; margin: 15px 0; border-radius: 0 8px 8px 0; }
 
1046
  a { color: var(--accent); }
1047
  </style>
1048
  </head>
 
1054
 
1055
  <div class="note">
1056
  <strong>Base URL:</strong> <code>https://YOUR-SPACE.hf.space</code><br>
1057
+ <strong>Autenticazione:</strong> API key NVIDIA pre-configurata (default)
1058
  </div>
1059
 
1060
+ <h2>πŸ“‹ Endpoints</h2>
1061
  <div class="endpoint">
1062
  <table class="param-table">
1063
  <tr><th>Metodo</th><th>Endpoint</th><th>Descrizione</th></tr>
1064
+ <tr><td><span class="method get">GET</span></td><td><code>/</code></td><td>UI Web principale</td></tr>
1065
+ <tr><td><span class="method get">GET</span></td><td><code>/ui</code></td><td>Interfaccia Gradio</td></tr>
1066
+ <tr><td><span class="method get">GET</span></td><td><code>/docs</code></td><td>Swagger UI</td></tr>
1067
+ <tr><td><span class="method get">GET</span></td><td><code>/v1/models</code></td><td>Lista modelli</td></tr>
1068
  <tr><td><span class="method get">GET</span></td><td><code>/v1/chat/get</code></td><td>Chat via GET</td></tr>
1069
  <tr><td><span class="method post">POST</span></td><td><code>/v1/chat</code></td><td>Chat via POST</td></tr>
1070
+ <tr><td><span class="method get">GET</span></td><td><code>/v1/chat/stream</code></td><td>SSE streaming via GET</td></tr>
1071
+ <tr><td><span class="method post">POST</span></td><td><code>/v1/chat/stream</code></td><td>SSE streaming via POST</td></tr>
1072
  <tr><td><span class="method post">POST</span></td><td><code>/v1/memory</code></td><td>Gestione memoria</td></tr>
 
1073
  <tr><td><span class="method post">POST</span></td><td><code>/v1/mcp</code></td><td>Server MCP (JSON-RPC)</td></tr>
1074
  </table>
1075
  </div>
1076
 
1077
+ <h2>πŸ’¬ Chat</h2>
 
 
1078
  <div class="endpoint">
1079
+ <pre><code>curl -X POST https://YOUR-SPACE.hf.space/v1/chat \\
 
 
 
 
1080
  -H "Content-Type: application/json" \\
1081
  -d '{
1082
  "message": "Spiegami la relativit&agrave;",
1083
  "model": "meta/llama-3.1-405b-instruct",
1084
  "session_id": "user-123",
 
1085
  "use_memory": true
1086
  }'</code></pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1087
  </div>
1088
 
1089
+ <h2>πŸ“‘ SSE Streaming</h2>
1090
  <div class="endpoint">
1091
+ <pre><code># JavaScript EventSource
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1092
  const es = new EventSource(
1093
+ '/v1/chat/stream?message=Ciao&session_id=demo'
1094
  );
1095
+ es.addEventListener('token', (e) =&gt; {
1096
+ console.log(JSON.parse(e.data).token);
 
 
 
 
 
 
 
 
 
1097
  });
1098
+ es.addEventListener('done', (e) =&gt; es.close());</code></pre>
1099
 
1100
+ <pre><code># cURL
1101
+ curl -N "https://YOUR-SPACE.hf.space/v1/chat/stream?message=Ciao"</code></pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1102
  </div>
1103
 
1104
+ <h2>πŸ”Œ MCP Server</h2>
1105
  <div class="endpoint">
1106
+ <pre><code># Config Claude Desktop / Cursor
 
 
 
 
1107
  {
1108
  "mcpServers": {
1109
  "nvidia-ai": {
 
1112
  }
1113
  }
1114
  }</code></pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1115
  </div>
1116
 
1117
  </div>
 
1121
 
1122
 
1123
  # ══════════════════════════════════════════════════════════════
1124
+ # GRADIO UI (secondaria, su /ui)
1125
  # ══════════════════════════════════════════════════════════════
1126
 
1127
  def get_model_choices():
 
1156
  else:
1157
  messages = [{"role": "system", "content": system_prompt}]
1158
  for h in history:
1159
+ if isinstance(h, dict):
1160
+ if h.get("role") == "user":
1161
+ messages.append({"role": "user", "content": h["content"]})
1162
+ elif h.get("role") == "assistant":
1163
+ messages.append({"role": "assistant", "content": h["content"]})
1164
  messages.append({"role": "user", "content": message})
1165
 
1166
  full_response = ""
 
1183
 
1184
  def get_memory_info(session_id: str):
1185
  if not session_id:
1186
+ return {"error": "Inserisci un Session ID"}
1187
+ return memory.get_stats(session_id)
 
1188
 
1189
 
1190
  def clear_session_memory(session_id: str):
 
1194
  return f"πŸ—‘οΈ Memoria sessione '{session_id}' eliminata"
1195
 
1196
 
 
 
1197
  with gr.Blocks(
1198
  title="πŸš€ NVIDIA AI Multi-Model Space",
1199
  theme=gr.themes.Soft(
 
1211
  # πŸš€ NVIDIA AI Multi-Model Space
1212
  **Chat con tutti i modelli NVIDIA NIM** β€’ Memoria Breve/Lunga β€’ API REST β€’ SSE Streaming β€’ MCP Server
1213
 
1214
+ 🏠 [UI Web Principale](/) &nbsp;|&nbsp; πŸ“– [API Docs HTML](/api-docs) &nbsp;|&nbsp; πŸ”§ [Swagger UI](/docs) &nbsp;|&nbsp; πŸ”Œ [MCP Endpoint](/v1/mcp)
1215
  """)
1216
 
1217
  with gr.Tab("πŸ’¬ Chat"):
 
1221
  height=600,
1222
  type="messages",
1223
  show_copy_button=True,
 
1224
  placeholder="Seleziona un modello e inizia a chattare..."
1225
  )
1226
  msg = gr.Textbox(
 
1263
 
1264
  mem_output = gr.JSON(label="πŸ“Š Memoria", visible=True)
1265
 
 
1266
  msg.submit(
1267
  gradio_chat,
1268
  inputs=[msg, chatbot, model, system_prompt, temperature, max_tokens, session_id, use_memory],
 
1295
  with gr.Tab("🧠 Memoria"):
1296
  gr.Markdown("""
1297
  ## 🧠 Gestione Memoria
1298
+ - **Memoria Breve**: ultimi 20 messaggi (in RAM)
 
1299
  - **Memoria Lunga**: messaggi piΓΉ vecchi salvati in SQLite
1300
  - **Riassunti**: riassunti di sessione persistenti
1301
  """)
 
1306
  stats_btn = gr.Button("πŸ“Š Mostra Statistiche")
1307
  clear_mem_btn = gr.Button("πŸ—‘οΈ Elimina Sessione", variant="stop")
1308
 
1309
+ mem_display = gr.JSON(label="Risultato")
1310
 
1311
  stats_btn.click(get_memory_info, inputs=[mem_session], outputs=[mem_display])
1312
  clear_mem_btn.click(clear_session_memory, inputs=[mem_session], outputs=[mem_display])
 
1315
  gr.Markdown("""
1316
  ## πŸ“– Documentazione API
1317
 
1318
+ πŸ‘‰ **[Apri documentazione completa](/api-docs)**
 
 
1319
 
1320
+ πŸ‘‰ **[OpenAPI / Swagger UI](/docs)**
1321
 
1322
  ### Quick Start
1323
 
1324
+ #### Chat via GET
1325
  ```
1326
  GET /v1/chat/get?message=Ciao&model=meta/llama-3.1-8b-instruct&session_id=test
1327
  ```
 
1338
  curl -N "/v1/chat/stream?message=Raccontami+una+storia"
1339
  ```
1340
 
1341
+ #### MCP
1342
  ```bash
1343
  curl -X POST /v1/mcp \\
1344
  -H "Content-Type: application/json" \\
1345
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
1346
  ```
 
 
 
 
 
 
 
 
 
 
 
 
1347
  """)
1348
 
1349
 
 
1354
  app = gr.mount_gradio_app(app, demo, path="/ui")
1355
 
1356
 
 
 
 
 
 
 
1357
  if __name__ == "__main__":
1358
  import uvicorn
1359
  uvicorn.run(app, host="0.0.0.0", port=7860)