# core/llm_client.py import asyncio import httpx from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type from agents.registry import PROVIDERS, AGENT_REGISTRY class LLMClient: def __init__(self): self.semaphore = asyncio.Semaphore(6) # evitar saturar proveedores async def call(self, agent_key: str, messages: list, temperature: float = 0.7): agent = AGENT_REGISTRY.get(agent_key) if not agent: raise ValueError(f"Agente {agent_key} no encontrado") system_prompt = agent["role"] provider = PROVIDERS[agent["provider"]] full_messages = [{"role": "system", "content": system_prompt}] + messages @retry( stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=30), retry=retry_if_exception_type((httpx.HTTPStatusError, httpx.TimeoutException)) ) async def try_provider(base_url, model, api_key, headers): async with self.semaphore: payload = { "model": model, "messages": full_messages, "temperature": temperature, "max_tokens": 4096, "top_p": 0.92 } async with httpx.AsyncClient(timeout=90) as client: resp = await client.post( f"{base_url}/chat/completions", headers={"Authorization": f"Bearer {api_key}", **headers}, json=payload ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] # Intentar modelos del proveedor principal for model in agent.get("models", []): try: return await try_provider( provider["base_url"], model, provider["key"], provider.get("headers", {}) ) except Exception as e: print(f"Error con {model}: {e}") # Fallback a openrouter si no es el principal if agent["provider"] != "openrouter" and PROVIDERS["openrouter"]["key"]: or_p = PROVIDERS["openrouter"] for model in or_p["models"]: try: return await try_provider(or_p["base_url"], model, or_p["key"], or_p.get("headers", {})) except: continue raise Exception("Todos los proveedores fallaron") # Instancia global llm = LLMClient()