mission-control-v2 / core /llm_client.py
vfven's picture
Update core/llm_client.py
b144b5f verified
Raw
History Blame
4.99 kB
# 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) # mΓ‘ximo 6 llamadas simultΓ‘neas
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")
provider = PROVIDERS[agent["provider"]]
system_prompt = agent["role"]
# Mensajes completos (system + history)
full_messages = [{"role": "system", "content": system_prompt}] + messages
if provider["type"] == "gemini":
# ── Soporte para Google Gemini ───────────────────────────────────────
try:
import google.generativeai as genai
genai.configure(api_key=provider["key"])
model_name = agent["models"][0]
try:
model = genai.GenerativeModel(model_name)
except Exception as e:
print(f"Modelo {model_name} fallΓ³: {e}")
model_name = "gemini-2.5-flash" # fallback duro
model = genai.GenerativeModel(model_name)
# Convertir formato OpenAI β†’ Gemini
gemini_messages = []
for msg in full_messages:
role = "user" if msg["role"] in ("system", "user") else "model"
gemini_messages.append({
"role": role,
"parts": [{"text": msg["content"]}]
})
response = await asyncio.to_thread(
model.generate_content,
gemini_messages,
generation_config={"temperature": temperature, "max_output_tokens": 4096}
)
return response.text
except ImportError:
raise ImportError("Instala google-generativeai: pip install google-generativeai")
except Exception as e:
print(f"Error en Gemini ({model_name}): {str(e)}")
raise
elif provider["type"] == "openai_compat":
# ── Soporte para OpenRouter, Groq, etc. ───────────────────────────────
@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
last_err = None
for model in agent.get("models", []):
try:
return await try_provider(
provider["base_url"],
model,
provider["key"],
provider.get("headers", {})
)
except Exception as e:
last_err = e
print(f"Error con {model}: {str(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(f"Todos los proveedores fallaron. Último error: {last_err}")
else:
raise ValueError(f"Tipo de proveedor desconocido: {provider['type']}")
# Instancia global
llm = LLMClient()