Delta0723 commited on
Commit
a6028fd
·
verified ·
1 Parent(s): 4eccc2b

Upload 2 files

Browse files
Files changed (2) hide show
  1. api.py +370 -0
  2. inference_techmind.py +245 -0
api.py ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ TechMind PRO - Backend API
3
+ FastAPI + Rate Limiting + Stripe
4
+ """
5
+
6
+ from fastapi import FastAPI, HTTPException, Depends, Header
7
+ from fastapi.middleware.cors import CORSMiddleware
8
+ from pydantic import BaseModel, EmailStr
9
+ import sqlite3
10
+ from datetime import datetime, timedelta
11
+ from typing import Optional
12
+ import os
13
+ import hashlib
14
+ import secrets
15
+
16
+ # Importar función de inferencia
17
+ from inference_techmind import generar_respuesta_api
18
+
19
+ # =========================================================
20
+ # CONFIGURACIÓN
21
+ # =========================================================
22
+ app = FastAPI(
23
+ title="TechMind API",
24
+ description="API para TechMind PRO",
25
+ version="1.0.0"
26
+ )
27
+
28
+ # CORS (permite requests desde tu frontend)
29
+ app.add_middleware(
30
+ CORSMiddleware,
31
+ allow_origins=[
32
+ "https://techmind-landing.vercel.app",
33
+ "http://localhost:3000",
34
+ "http://localhost:8000"
35
+ ],
36
+ allow_credentials=True,
37
+ allow_methods=["*"],
38
+ allow_headers=["*"],
39
+ )
40
+
41
+ # Límites
42
+ FREE_DAILY_LIMIT = 10
43
+ PRO_DAILY_LIMIT = 999999
44
+
45
+ # =========================================================
46
+ # MODELOS PYDANTIC
47
+ # =========================================================
48
+ class QueryRequest(BaseModel):
49
+ pregunta: str
50
+ api_key: Optional[str] = None
51
+
52
+ class QueryResponse(BaseModel):
53
+ success: bool
54
+ respuesta: Optional[str] = None
55
+ tiempo_generacion: Optional[float] = None
56
+ requests_restantes: Optional[int] = None
57
+ error: Optional[str] = None
58
+
59
+ class SignupRequest(BaseModel):
60
+ email: EmailStr
61
+
62
+ class APIKeyResponse(BaseModel):
63
+ api_key: str
64
+ plan: str
65
+ expires_at: str
66
+
67
+ # =========================================================
68
+ # BASE DE DATOS
69
+ # =========================================================
70
+ def init_db():
71
+ """Inicializa base de datos SQLite"""
72
+ conn = sqlite3.connect('techmind.db')
73
+ c = conn.cursor()
74
+
75
+ # Tabla de API keys
76
+ c.execute('''
77
+ CREATE TABLE IF NOT EXISTS api_keys (
78
+ api_key TEXT PRIMARY KEY,
79
+ email TEXT NOT NULL,
80
+ plan TEXT NOT NULL,
81
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
82
+ expires_at TIMESTAMP,
83
+ is_active BOOLEAN DEFAULT 1,
84
+ stripe_customer_id TEXT,
85
+ stripe_subscription_id TEXT
86
+ )
87
+ ''')
88
+
89
+ # Tabla de requests diarios
90
+ c.execute('''
91
+ CREATE TABLE IF NOT EXISTS daily_requests (
92
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
93
+ user_id TEXT NOT NULL,
94
+ date TEXT NOT NULL,
95
+ count INTEGER DEFAULT 0,
96
+ UNIQUE(user_id, date)
97
+ )
98
+ ''')
99
+
100
+ conn.commit()
101
+ conn.close()
102
+
103
+ init_db()
104
+
105
+ def get_db():
106
+ """Obtiene conexión a DB"""
107
+ conn = sqlite3.connect('techmind.db')
108
+ conn.row_factory = sqlite3.Row
109
+ return conn
110
+
111
+ # =========================================================
112
+ # RATE LIMITING
113
+ # =========================================================
114
+ def check_rate_limit(api_key: Optional[str] = None) -> tuple[bool, int, str]:
115
+ """
116
+ Verifica límite de requests
117
+
118
+ Returns:
119
+ (puede_continuar, requests_restantes, user_id)
120
+ """
121
+
122
+ conn = get_db()
123
+ c = conn.cursor()
124
+
125
+ # Identificar usuario
126
+ if api_key:
127
+ # Usuario Pro
128
+ c.execute("SELECT plan FROM api_keys WHERE api_key = ? AND is_active = 1", (api_key,))
129
+ result = c.fetchone()
130
+
131
+ if not result:
132
+ conn.close()
133
+ return False, 0, "invalid"
134
+
135
+ user_id = api_key
136
+ limit = PRO_DAILY_LIMIT
137
+ else:
138
+ # Usuario Free (identificar por IP o session)
139
+ # En producción usarías request.client.host
140
+ user_id = "free_user"
141
+ limit = FREE_DAILY_LIMIT
142
+
143
+ # Contar requests hoy
144
+ today = datetime.now().strftime("%Y-%m-%d")
145
+
146
+ c.execute("""
147
+ SELECT count FROM daily_requests
148
+ WHERE user_id = ? AND date = ?
149
+ """, (user_id, today))
150
+
151
+ result = c.fetchone()
152
+ count = result["count"] if result else 0
153
+
154
+ if count >= limit:
155
+ conn.close()
156
+ return False, 0, user_id
157
+
158
+ # Incrementar contador
159
+ c.execute("""
160
+ INSERT INTO daily_requests (user_id, date, count)
161
+ VALUES (?, ?, 1)
162
+ ON CONFLICT(user_id, date)
163
+ DO UPDATE SET count = count + 1
164
+ """, (user_id, today))
165
+
166
+ conn.commit()
167
+ conn.close()
168
+
169
+ requests_restantes = limit - count - 1
170
+
171
+ return True, requests_restantes, user_id
172
+
173
+ # =========================================================
174
+ # ENDPOINTS
175
+ # =========================================================
176
+
177
+ @app.get("/")
178
+ async def root():
179
+ """Health check"""
180
+ return {
181
+ "status": "ok",
182
+ "service": "TechMind API",
183
+ "version": "1.0.0"
184
+ }
185
+
186
+ @app.post("/api/query", response_model=QueryResponse)
187
+ async def query_techmind(request: QueryRequest):
188
+ """
189
+ Endpoint principal para hacer preguntas a TechMind
190
+ """
191
+
192
+ # Validar pregunta
193
+ if not request.pregunta or len(request.pregunta.strip()) < 3:
194
+ raise HTTPException(400, "Pregunta demasiado corta")
195
+
196
+ if len(request.pregunta) > 500:
197
+ raise HTTPException(400, "Pregunta demasiado larga (máx 500 caracteres)")
198
+
199
+ # Verificar rate limit
200
+ puede_continuar, requests_restantes, user_id = check_rate_limit(request.api_key)
201
+
202
+ if not puede_continuar:
203
+ if user_id == "invalid":
204
+ raise HTTPException(401, "API key inválida")
205
+ else:
206
+ raise HTTPException(
207
+ 429,
208
+ f"Límite diario alcanzado. Upgrade a Pro para consultas ilimitadas."
209
+ )
210
+
211
+ # Generar respuesta
212
+ try:
213
+ resultado = generar_respuesta_api(request.pregunta)
214
+
215
+ if not resultado["success"]:
216
+ raise HTTPException(500, resultado.get("error", "Error generando respuesta"))
217
+
218
+ return QueryResponse(
219
+ success=True,
220
+ respuesta=resultado["respuesta"],
221
+ tiempo_generacion=resultado["tiempo_generacion"],
222
+ requests_restantes=requests_restantes
223
+ )
224
+
225
+ except Exception as e:
226
+ raise HTTPException(500, f"Error: {str(e)}")
227
+
228
+ @app.post("/api/signup", response_model=APIKeyResponse)
229
+ async def signup_free(request: SignupRequest):
230
+ """
231
+ Registro gratuito (genera API key)
232
+ """
233
+
234
+ conn = get_db()
235
+ c = conn.cursor()
236
+
237
+ # Verificar si email ya existe
238
+ c.execute("SELECT api_key FROM api_keys WHERE email = ?", (request.email,))
239
+ existing = c.fetchone()
240
+
241
+ if existing:
242
+ conn.close()
243
+ raise HTTPException(400, "Email ya registrado")
244
+
245
+ # Generar API key
246
+ api_key = f"tm_free_{secrets.token_urlsafe(32)}"
247
+
248
+ # Guardar en DB
249
+ c.execute("""
250
+ INSERT INTO api_keys (api_key, email, plan, is_active)
251
+ VALUES (?, ?, 'free', 1)
252
+ """, (api_key, request.email))
253
+
254
+ conn.commit()
255
+ conn.close()
256
+
257
+ return APIKeyResponse(
258
+ api_key=api_key,
259
+ plan="free",
260
+ expires_at="never"
261
+ )
262
+
263
+ @app.get("/api/stats")
264
+ async def get_stats(api_key: str = Header(...)):
265
+ """
266
+ Obtiene estadísticas de uso
267
+ """
268
+
269
+ conn = get_db()
270
+ c = conn.cursor()
271
+
272
+ # Verificar API key
273
+ c.execute("""
274
+ SELECT email, plan, created_at
275
+ FROM api_keys
276
+ WHERE api_key = ? AND is_active = 1
277
+ """, (api_key,))
278
+
279
+ user = c.fetchone()
280
+
281
+ if not user:
282
+ conn.close()
283
+ raise HTTPException(401, "API key inválida")
284
+
285
+ # Obtener requests hoy
286
+ today = datetime.now().strftime("%Y-%m-%d")
287
+
288
+ c.execute("""
289
+ SELECT count FROM daily_requests
290
+ WHERE user_id = ? AND date = ?
291
+ """, (api_key, today))
292
+
293
+ result = c.fetchone()
294
+ requests_hoy = result["count"] if result else 0
295
+
296
+ # Obtener total histórico
297
+ c.execute("""
298
+ SELECT SUM(count) as total FROM daily_requests
299
+ WHERE user_id = ?
300
+ """, (api_key,))
301
+
302
+ result = c.fetchone()
303
+ requests_total = result["total"] if result and result["total"] else 0
304
+
305
+ conn.close()
306
+
307
+ limit = PRO_DAILY_LIMIT if user["plan"] == "pro" else FREE_DAILY_LIMIT
308
+
309
+ return {
310
+ "email": user["email"],
311
+ "plan": user["plan"],
312
+ "requests_hoy": requests_hoy,
313
+ "requests_total": requests_total,
314
+ "limite_diario": limit,
315
+ "requests_restantes": limit - requests_hoy
316
+ }
317
+
318
+ # =========================================================
319
+ # WEBHOOK STRIPE (para después)
320
+ # =========================================================
321
+ @app.post("/webhook/stripe")
322
+ async def stripe_webhook():
323
+ """
324
+ Webhook para procesar pagos de Stripe
325
+ TODO: Implementar cuando configures Stripe
326
+ """
327
+ return {"status": "pending_implementation"}
328
+
329
+ # =========================================================
330
+ # ADMIN (opcional - para ti)
331
+ # =========================================================
332
+ @app.get("/admin/users")
333
+ async def list_users(admin_key: str = Header(...)):
334
+ """
335
+ Lista todos los usuarios (solo admin)
336
+ """
337
+
338
+ # Contraseña admin simple (cambiar en producción)
339
+ if admin_key != "admin_techmind_2025":
340
+ raise HTTPException(403, "No autorizado")
341
+
342
+ conn = get_db()
343
+ c = conn.cursor()
344
+
345
+ c.execute("""
346
+ SELECT email, plan, created_at, is_active
347
+ FROM api_keys
348
+ ORDER BY created_at DESC
349
+ LIMIT 100
350
+ """)
351
+
352
+ users = [dict(row) for row in c.fetchall()]
353
+ conn.close()
354
+
355
+ return {"users": users, "total": len(users)}
356
+
357
+ # =========================================================
358
+ # STARTUP
359
+ # =========================================================
360
+ @app.on_event("startup")
361
+ async def startup_event():
362
+ """Ejecutar al iniciar el servidor"""
363
+ print("🚀 TechMind API iniciada")
364
+ print("📊 Base de datos: techmind.db")
365
+ print("🔒 CORS habilitado para: techmind-landing.vercel.app")
366
+ print()
367
+
368
+ if __name__ == "__main__":
369
+ import uvicorn
370
+ uvicorn.run(app, host="0.0.0.0", port=8000)
inference_techmind.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ TechMind PRO - Script de Inferencia Optimizado
3
+ Para usar después del entrenamiento
4
+ """
5
+
6
+ import torch
7
+ from transformers import AutoTokenizer, AutoModelForCausalLM
8
+ from peft import PeftModel
9
+ import time
10
+
11
+ # =========================================================
12
+ # CONFIGURACIÓN
13
+ # =========================================================
14
+ BASE_MODEL = "EleutherAI/gpt-j-6B"
15
+ LORA_PATH = "/workspace/TechMind/lora_final_pro" # Modelo de FASE 2
16
+
17
+ SYSTEM_PROMPT = (
18
+ "Eres TechMind, experto en redes y ciberseguridad. "
19
+ "Responde SIEMPRE en español claro y técnico, con pasos y comandos cuando aplique.\n"
20
+ )
21
+
22
+ # =========================================================
23
+ # CARGAR MODELO (solo una vez)
24
+ # =========================================================
25
+ print("🧩 Cargando TechMind PRO...")
26
+ print(f"📁 Modelo: {LORA_PATH}")
27
+
28
+ tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
29
+ if tokenizer.pad_token is None:
30
+ tokenizer.pad_token = tokenizer.eos_token
31
+
32
+ print("🔧 Cargando GPT-J 6B...")
33
+ model = AutoModelForCausalLM.from_pretrained(
34
+ BASE_MODEL,
35
+ torch_dtype=torch.float16,
36
+ device_map="auto",
37
+ load_in_8bit=True # Reduce RAM
38
+ )
39
+
40
+ print("🔗 Aplicando LoRA...")
41
+ model = PeftModel.from_pretrained(model, LORA_PATH)
42
+ model.eval()
43
+
44
+ print("✅ TechMind PRO listo\n")
45
+
46
+ # =========================================================
47
+ # FUNCIÓN DE INFERENCIA
48
+ # =========================================================
49
+ def ask_techmind(
50
+ pregunta: str,
51
+ max_tokens: int = 400,
52
+ temperature: float = 0.6,
53
+ top_p: float = 0.9
54
+ ) -> dict:
55
+ """
56
+ Genera respuesta de TechMind
57
+
58
+ Args:
59
+ pregunta: Pregunta del usuario
60
+ max_tokens: Longitud máxima de respuesta
61
+ temperature: Creatividad (0.1=conservador, 1.0=creativo)
62
+ top_p: Diversidad de vocabulario
63
+
64
+ Returns:
65
+ dict con 'respuesta', 'tiempo' y 'tokens'
66
+ """
67
+
68
+ # Construir prompt
69
+ prompt = f"{SYSTEM_PROMPT}Pregunta: {pregunta}\nRespuesta: "
70
+
71
+ # Tokenizar
72
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
73
+
74
+ # Medir tiempo
75
+ start_time = time.time()
76
+
77
+ # Generar
78
+ with torch.no_grad():
79
+ outputs = model.generate(
80
+ **inputs,
81
+ max_new_tokens=max_tokens,
82
+ temperature=temperature,
83
+ top_p=top_p,
84
+ do_sample=True,
85
+ pad_token_id=tokenizer.eos_token_id,
86
+ eos_token_id=tokenizer.eos_token_id,
87
+ repetition_penalty=1.15,
88
+ no_repeat_ngram_size=3
89
+ )
90
+
91
+ elapsed_time = time.time() - start_time
92
+
93
+ # Decodificar
94
+ respuesta_completa = tokenizer.decode(outputs[0], skip_special_tokens=True)
95
+
96
+ # Limpiar (quitar prompt)
97
+ if "Respuesta:" in respuesta_completa:
98
+ respuesta = respuesta_completa.split("Respuesta:", 1)[-1].strip()
99
+ else:
100
+ respuesta = respuesta_completa
101
+
102
+ # Calcular tokens
103
+ tokens_generados = len(outputs[0]) - len(inputs["input_ids"][0])
104
+
105
+ return {
106
+ "respuesta": respuesta,
107
+ "tiempo": round(elapsed_time, 2),
108
+ "tokens": tokens_generados
109
+ }
110
+
111
+ # =========================================================
112
+ # MODO INTERACTIVO
113
+ # =========================================================
114
+ def modo_interactivo():
115
+ """Chat interactivo con TechMind"""
116
+
117
+ print("=" * 70)
118
+ print("🧠 TECHMIND PRO - Modo Interactivo")
119
+ print("=" * 70)
120
+ print("Escribe 'salir' para terminar")
121
+ print("Escribe 'ajustes' para cambiar parámetros")
122
+ print()
123
+
124
+ # Parámetros por defecto
125
+ config = {
126
+ "max_tokens": 400,
127
+ "temperature": 0.6,
128
+ "top_p": 0.9
129
+ }
130
+
131
+ while True:
132
+ try:
133
+ pregunta = input("👤 Tú: ").strip()
134
+
135
+ if pregunta.lower() in ["salir", "exit", "quit"]:
136
+ print("👋 ¡Hasta luego!")
137
+ break
138
+
139
+ if pregunta.lower() == "ajustes":
140
+ print("\n⚙️ Ajustes actuales:")
141
+ print(f" max_tokens: {config['max_tokens']}")
142
+ print(f" temperature: {config['temperature']}")
143
+ print(f" top_p: {config['top_p']}")
144
+ print()
145
+ continue
146
+
147
+ if not pregunta:
148
+ continue
149
+
150
+ # Generar respuesta
151
+ print("🤖 TechMind: ", end="", flush=True)
152
+
153
+ resultado = ask_techmind(
154
+ pregunta,
155
+ max_tokens=config["max_tokens"],
156
+ temperature=config["temperature"],
157
+ top_p=config["top_p"]
158
+ )
159
+
160
+ print(resultado["respuesta"])
161
+ print(f"\n⏱️ {resultado['tiempo']}s | 📊 {resultado['tokens']} tokens\n")
162
+
163
+ except KeyboardInterrupt:
164
+ print("\n👋 ¡Hasta luego!")
165
+ break
166
+
167
+ except Exception as e:
168
+ print(f"\n❌ Error: {e}\n")
169
+
170
+ # =========================================================
171
+ # EJEMPLOS DE USO
172
+ # =========================================================
173
+ def ejemplos():
174
+ """Muestra ejemplos de uso"""
175
+
176
+ print("\n" + "=" * 70)
177
+ print("📝 EJEMPLOS DE USO")
178
+ print("=" * 70)
179
+
180
+ preguntas_ejemplo = [
181
+ "¿Cómo configuro OSPF área 0 en un router Cisco?",
182
+ "Dame comandos para diagnosticar packet loss",
183
+ "¿Qué es BGP y cuándo debo usarlo?"
184
+ ]
185
+
186
+ for i, pregunta in enumerate(preguntas_ejemplo, 1):
187
+ print(f"\n{i}. Pregunta: {pregunta}")
188
+ print("-" * 70)
189
+
190
+ resultado = ask_techmind(pregunta, max_tokens=300)
191
+
192
+ print(f"Respuesta: {resultado['respuesta']}")
193
+ print(f"⏱️ {resultado['tiempo']}s | 📊 {resultado['tokens']} tokens")
194
+ print()
195
+
196
+ # =========================================================
197
+ # FUNCIÓN PARA API (FastAPI/Flask)
198
+ # =========================================================
199
+ def generar_respuesta_api(pregunta: str) -> dict:
200
+ """
201
+ Función optimizada para usar en API web
202
+
203
+ Returns:
204
+ dict con respuesta y metadata
205
+ """
206
+
207
+ try:
208
+ resultado = ask_techmind(
209
+ pregunta,
210
+ max_tokens=400,
211
+ temperature=0.6
212
+ )
213
+
214
+ return {
215
+ "success": True,
216
+ "respuesta": resultado["respuesta"],
217
+ "tiempo_generacion": resultado["tiempo"],
218
+ "tokens_generados": resultado["tokens"]
219
+ }
220
+
221
+ except Exception as e:
222
+ return {
223
+ "success": False,
224
+ "error": str(e)
225
+ }
226
+
227
+ # =========================================================
228
+ # MAIN
229
+ # =========================================================
230
+ if __name__ == "__main__":
231
+ import sys
232
+
233
+ if len(sys.argv) > 1:
234
+ # Modo comando: python inference_techmind.py "tu pregunta"
235
+ pregunta = " ".join(sys.argv[1:])
236
+ resultado = ask_techmind(pregunta)
237
+ print(resultado["respuesta"])
238
+
239
+ elif "--ejemplos" in sys.argv or "-e" in sys.argv:
240
+ # Modo ejemplos
241
+ ejemplos()
242
+
243
+ else:
244
+ # Modo interactivo por defecto
245
+ modo_interactivo()