File size: 6,644 Bytes
c46013c
 
 
 
 
 
 
 
bb78940
 
c46013c
 
bb78940
c46013c
 
 
bb78940
c46013c
 
bb78940
 
c46013c
bb78940
 
b9f1124
bb78940
b9f1124
 
b144b5f
 
 
b9f1124
 
 
b144b5f
bb78940
 
 
 
 
 
 
 
 
 
 
 
 
 
c46013c
b9f1124
bb78940
 
 
 
c46013c
b9f1124
 
 
 
bb78940
 
 
 
 
b9f1124
 
 
 
bb78940
 
 
 
 
 
 
 
 
 
b9f1124
 
f7c8bab
db446f6
bb78940
db446f6
bb78940
 
 
b9f1124
 
 
 
 
 
 
 
 
 
c46013c
bb78940
 
c46013c
bb78940
 
 
 
 
 
 
b9f1124
bf76179
b9f1124
 
 
bf76179
bb78940
b9f1124
bb78940
 
b9f1124
 
bb78940
 
 
 
 
 
 
b9f1124
 
 
bb78940
b9f1124
 
 
c46013c
bb78940
 
c46013c
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# 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"]
        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]
                print(f"Intentando Gemini con modelo: {model_name}")

                try:
                    model = genai.GenerativeModel(model_name)
                except Exception as e:
                    print(f"Modelo {model_name} fallΓ³: {str(e)}")
                    model_name = "gemini-2.5-flash"  # fallback seguro
                    print(f"Usando fallback: {model_name}")
                    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}
                )
                print(f"Gemini respondiΓ³ exitosamente con {model_name}")
                return response.text

            except ImportError:
                raise ImportError("Instala google-generativeai: pip install google-generativeai")
            except Exception as e:
                print(f"Error completo en Gemini ({model_name}): {str(e)}")
                if hasattr(e, 'response'):
                    print(f"Status: {e.response.status_code}")
                    print(f"Body: {e.response.text[:500]}")
                raise

        elif provider["type"] == "openai_compat":
            # ── Soporte para OpenRouter, Groq, etc. ───────────────────────────────
            @retry(
                stop=stop_after_attempt(6),  # un intento mΓ‘s
                wait=wait_exponential(multiplier=2, min=5, max=120),  # espera progresiva: 5s β†’ 10s β†’ 20s β†’ 40s β†’ 80s β†’ 120s
                retry=retry_if_exception_type((httpx.HTTPStatusError, httpx.TimeoutException)),
                before_sleep=lambda retry_state: print(f"Reintento {retry_state.attempt_number} despuΓ©s de {retry_state.outcome.exception()}")
            )
            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
                    }
                    print(f"Intentando {provider['name']} con modelo {model}")
                    async with httpx.AsyncClient(timeout=120) as client:  # timeout mΓ‘s largo
                        url = f"{base_url}/chat/completions"  # ← Β‘esto es lo que falta!
                        print(f"URL completa: {url}")
                        resp = await client.post(
                            url,
                            headers={"Authorization": f"Bearer {api_key}", **headers},
                            json=payload
                        )
                        try:
                            resp.raise_for_status()
                        except httpx.HTTPStatusError as exc:
                            print(f"HTTP Error {resp.status_code} con {model}")
                            print(f"Response body: {resp.text[:500]}")
                            raise exc

                        content = resp.json()["choices"][0]["message"]["content"]
                        print(f"Γ‰xito con {model}")
                        return content

            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:
                    print(f"Error completo con {model}: {str(e)}")
                    if hasattr(e, 'response') and e.response is not None:
                        print(f"  β†’ Status: {e.response.status_code}")
                        print(f"  β†’ Body: {e.response.text[:500]}")
                        print(f"  β†’ Headers: {dict(e.response.headers)}")
                    last_err = e

            # Fallback a OpenRouter
            if agent["provider"] != "openrouter" and PROVIDERS["openrouter"]["key"]:
                or_p = PROVIDERS["openrouter"]
                print("Probando fallback a OpenRouter...")
                for model in or_p["models"][:3]:  # solo los primeros 3 para no saturar
                    try:
                        return await try_provider(
                            or_p["base_url"],
                            model,
                            or_p["key"],
                            or_p.get("headers", {})
                        )
                    except Exception as e:
                        print(f"Fallback fallΓ³ con {model}: {str(e)}")
                        last_err = e

            error_msg = f"Todos los proveedores fallaron. Último error: {last_err}"
            print(error_msg)
            raise Exception(error_msg)

        else:
            raise ValueError(f"Tipo de proveedor desconocido: {provider['type']}")

# Instancia global
llm = LLMClient()