# /home/Autopartes/pos/services/ai_chat.py """AI Chat service using OpenRouter for parts lookup assistance.""" import requests import json from config import OPENROUTER_API_KEY OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions" # ⚠️ SOLO MODELOS GRATUITOS — No cambiar a modelos de pago. # El modelo DEBE terminar en ":free" para garantizar costo $0. # Alternativas gratuitas: "meta-llama/llama-4-scout:free", "google/gemma-3-27b-it:free" MODEL = "qwen/qwen3-6b-preview:free" def _validate_model(model_id): """Ensure only free models are used. Raises if model is not free.""" if not model_id.endswith(':free'): raise ValueError(f"BLOQUEADO: Solo se permiten modelos gratuitos (:free). Modelo '{model_id}' no es gratuito.") SYSTEM_PROMPT = """Eres un asistente de refaccionaria automotriz mexicana. Tu trabajo es ayudar a encontrar autopartes. IMPORTANTE: Responde SIEMPRE en formato JSON valido con esta estructura: { "message": "Tu respuesta al usuario en español", "search_query": "termino de busqueda EN INGLES para el catalogo", "vehicle": {"brand": "TOYOTA", "model": "Corolla", "year": 2020} } Reglas OBLIGATORIAS: 1. "search_query" SIEMPRE debe tener un valor cuando el usuario menciona una parte. NUNCA dejes null si el usuario pide algo. 2. "search_query" debe estar EN INGLES porque el catalogo TecDoc tiene nombres en ingles. Traducciones comunes: - Balatas/Pastillas de freno = "Brake Pad" - Discos de freno = "Brake Disc" - Amortiguador = "Shock Absorber" - Filtro de aceite = "Oil Filter" - Filtro de aire = "Air Filter" - Bujias = "Spark Plug" - Banda serpentina = "V-Belt" o "Serpentine Belt" - Bomba de agua = "Water Pump" - Alternador = "Alternator" - Radiador = "Radiator" - Sensor de oxigeno = "Oxygen Sensor" - Terminal de direccion = "Tie Rod End" - Bomba de gasolina = "Fuel Pump" - Clutch/Embrague = "Clutch Kit" - Mofle/Escape = "Exhaust" - Inyector = "Injector" 3. "vehicle" extrae marca, modelo y ano. La marca en MAYUSCULAS. 4. Nombres mexicanos: Tsuru = TSURU, Aveo = AVEO, Jetta = JETTA, Pointer = POINTER, Chevy = CORSA, Vocho = BEETLE. 5. No preguntes mas info si ya puedes buscar. Si el usuario dice "balatas para Tsuru 2015", busca directo. 6. "message" es breve y directo: "Buscando balatas para Nissan Tsuru 2015..." """ def chat(user_message, conversation_history=None): """Send a message to the AI and get a response with search suggestions.""" _validate_model(MODEL) # Block paid models messages = [{"role": "system", "content": SYSTEM_PROMPT}] if conversation_history: messages.extend(conversation_history) messages.append({"role": "user", "content": user_message}) try: resp = requests.post( OPENROUTER_URL, headers={ "Authorization": f"Bearer {OPENROUTER_API_KEY}", "Content-Type": "application/json", }, json={ "model": MODEL, "messages": messages, "max_tokens": 500, "temperature": 0.3, }, timeout=15, ) resp.raise_for_status() data = resp.json() content = data["choices"][0]["message"]["content"] # Try to parse JSON response try: # Handle markdown-wrapped JSON (```json ... ```) stripped = content.strip() if stripped.startswith("```"): lines = stripped.split("\n") # Remove first and last lines (``` markers) json_str = "\n".join(lines[1:-1]) parsed = json.loads(json_str) else: parsed = json.loads(stripped) return parsed except (json.JSONDecodeError, IndexError): return {"message": content, "search_query": None, "vehicle": None} except Exception as e: return { "message": f"Error de conexion: {str(e)}", "search_query": None, "vehicle": None, }