vfven commited on
Commit
a0f3f23
·
verified ·
1 Parent(s): 3186461

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +140 -18
main.py CHANGED
@@ -61,8 +61,8 @@ DEFAULT_AGENTS = [
61
  "5. Si hay frontend_dev en el equipo, TÚ haces servidor/backend, él hace HTML.\n"
62
  "6. Si la tarea no requiere backend → responde: {\"skip\":\"no backend needed\"}"
63
  ),
64
- "models":["qwen/qwen3-4b:free","meta-llama/llama-3.3-70b-instruct:free",
65
- "mistralai/mistral-small-3.1-24b-instruct:free","google/gemma-3-12b-it:free"]},
66
  {"key":"frontend_dev","name":"Frontend","provider":"openrouter",
67
  "role":(
68
  "Eres desarrollador frontend senior. REGLAS ABSOLUTAS:\n"
@@ -71,8 +71,8 @@ DEFAULT_AGENTS = [
71
  "3. Si la tarea NO requiere frontend → responde: {\"skip\":\"no frontend needed\"}\n"
72
  "4. Entrega siempre HTML completo y funcional con los estilos incluidos."
73
  ),
74
- "models":["meta-llama/llama-3.3-70b-instruct:free","mistralai/mistral-small-3.1-24b-instruct:free",
75
- "qwen/qwen3-4b:free","google/gemma-3-12b-it:free"]},
76
  {"key":"analyst","name":"Analyst","provider":"openrouter",
77
  "role":(
78
  "Eres analista de negocios. REGLAS:\n"
@@ -80,8 +80,8 @@ DEFAULT_AGENTS = [
80
  "2. NUNCA describas imágenes ni hagas trabajo de otros agentes.\n"
81
  "3. Si la tarea no requiere análisis → responde: {\"skip\":\"no analysis needed\"}"
82
  ),
83
- "models":["meta-llama/llama-3.3-70b-instruct:free","mistralai/mistral-small-3.1-24b-instruct:free",
84
- "google/gemma-3-27b-it:free","qwen/qwen3-4b:free"]},
85
  {"key":"writer","name":"Writer","provider":"openrouter",
86
  "role":(
87
  "Eres redactor experto. Escribe SOLO contenido real y extenso (500+ palabras). "
@@ -89,8 +89,8 @@ DEFAULT_AGENTS = [
89
  "Secciones: ## Resumen Ejecutivo, ### Introducción, ### Desarrollo, "
90
  "### Hallazgos, ### Conclusiones, ### Recomendaciones"
91
  ),
92
- "models":["meta-llama/llama-3.3-70b-instruct:free","mistralai/mistral-small-3.1-24b-instruct:free",
93
- "qwen/qwen3-4b:free","google/gemma-3-12b-it:free"]},
94
  {"key":"image_agent","name":"ImageAgent","provider":"gemini",
95
  "role":(
96
  "Cuando se te pida imágenes, responde SOLO con: "
@@ -117,6 +117,87 @@ async def call_compat(base_url,model,system,user,key,headers):
117
  r.raise_for_status()
118
  return r.json()["choices"][0]["message"]["content"]
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  def is_rate_limit(err: str) -> bool:
121
  e = err.lower()
122
  return any(x in e for x in ["429","rate limit","quota","resource exhausted","too many requests","ratelimit"])
@@ -144,12 +225,12 @@ async def call_llm(agent, task):
144
  if OPENROUTER_API_KEY and agent["provider"] != "openrouter":
145
  or_prov = PROVIDERS["openrouter"]
146
  for m in [
 
 
147
  "meta-llama/llama-3.3-70b-instruct:free",
148
  "mistralai/mistral-small-3.1-24b-instruct:free",
149
  "qwen/qwen3-4b:free",
150
- "google/gemma-3-12b-it:free",
151
  "qwen/qwen-2.5-72b-instruct:free",
152
- "microsoft/phi-4-reasoning-plus:free",
153
  "deepseek/deepseek-r1-distill-llama-70b:free",
154
  ]:
155
  try:
@@ -518,25 +599,66 @@ async def run_mission(request:Request):
518
 
519
 
520
 
 
 
 
521
  @app.post("/api/chat")
522
  async def chat_with_agent(request: Request):
523
- body = await request.json()
524
- agent_key = body.get("agent", "").strip()
525
- message = body.get("message", "").strip()
 
 
 
526
  if not agent_key or not message:
527
  return JSONResponse({"error": "agent and message required"}, status_code=400)
528
  if agent_key not in agent_registry:
529
  return JSONResponse({"error": f"Agent '{agent_key}' not found"}, status_code=404)
530
- from datetime import datetime as _dt
531
- _today = _dt.now().strftime("%A %d de %B de %Y, %H:%M")
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  agent = dict(agent_registry[agent_key])
533
- agent["role"] = f"HOY ES: {_today}. " + agent["role"]
 
534
  try:
535
- response = await call_llm(agent, message)
536
- return JSONResponse({"success": True, "agent": agent_key, "response": response, "model": agent["models"][0] if agent.get("models") else ""})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  except Exception as e:
538
  return JSONResponse({"error": str(e)}, status_code=500)
539
 
 
 
 
 
 
540
  @app.get("/api/archive")
541
  async def list_archive():
542
  files = []
 
61
  "5. Si hay frontend_dev en el equipo, TÚ haces servidor/backend, él hace HTML.\n"
62
  "6. Si la tarea no requiere backend → responde: {\"skip\":\"no backend needed\"}"
63
  ),
64
+ "models":["google/gemma-3-27b-it:free","google/gemma-3-12b-it:free",
65
+ "meta-llama/llama-3.3-70b-instruct:free","mistralai/mistral-small-3.1-24b-instruct:free"]},
66
  {"key":"frontend_dev","name":"Frontend","provider":"openrouter",
67
  "role":(
68
  "Eres desarrollador frontend senior. REGLAS ABSOLUTAS:\n"
 
71
  "3. Si la tarea NO requiere frontend → responde: {\"skip\":\"no frontend needed\"}\n"
72
  "4. Entrega siempre HTML completo y funcional con los estilos incluidos."
73
  ),
74
+ "models":["google/gemma-3-12b-it:free","google/gemma-3-27b-it:free",
75
+ "meta-llama/llama-3.3-70b-instruct:free","mistralai/mistral-small-3.1-24b-instruct:free"]},
76
  {"key":"analyst","name":"Analyst","provider":"openrouter",
77
  "role":(
78
  "Eres analista de negocios. REGLAS:\n"
 
80
  "2. NUNCA describas imágenes ni hagas trabajo de otros agentes.\n"
81
  "3. Si la tarea no requiere análisis → responde: {\"skip\":\"no analysis needed\"}"
82
  ),
83
+ "models":["google/gemma-3-27b-it:free","google/gemma-3-12b-it:free",
84
+ "meta-llama/llama-3.3-70b-instruct:free","mistralai/mistral-small-3.1-24b-instruct:free"]},
85
  {"key":"writer","name":"Writer","provider":"openrouter",
86
  "role":(
87
  "Eres redactor experto. Escribe SOLO contenido real y extenso (500+ palabras). "
 
89
  "Secciones: ## Resumen Ejecutivo, ### Introducción, ### Desarrollo, "
90
  "### Hallazgos, ### Conclusiones, ### Recomendaciones"
91
  ),
92
+ "models":["google/gemma-3-12b-it:free","google/gemma-3-27b-it:free",
93
+ "meta-llama/llama-3.3-70b-instruct:free","mistralai/mistral-small-3.1-24b-instruct:free"]},
94
  {"key":"image_agent","name":"ImageAgent","provider":"gemini",
95
  "role":(
96
  "Cuando se te pida imágenes, responde SOLO con: "
 
117
  r.raise_for_status()
118
  return r.json()["choices"][0]["message"]["content"]
119
 
120
+
121
+ async def call_compat_multiturn(base_url, model, system, messages, key, extra_headers):
122
+ """OpenAI-compatible chat with full message history for multi-turn conversations."""
123
+ h = {"Authorization": f"Bearer {key}", "Content-Type": "application/json", **extra_headers}
124
+ payload = {
125
+ "model": model,
126
+ "messages": [{"role": "system", "content": system}] + messages,
127
+ "max_tokens": 2048,
128
+ "temperature": 0.6,
129
+ }
130
+ async with httpx.AsyncClient(timeout=90) as c:
131
+ r = await c.post(base_url, json=payload, headers=h)
132
+ r.raise_for_status()
133
+ return r.json()["choices"][0]["message"]["content"]
134
+
135
+ async def call_gemini_multiturn(model, system, messages, key):
136
+ """Gemini multi-turn conversation."""
137
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={key}"
138
+ # Convert messages to Gemini format
139
+ contents = []
140
+ for m in messages:
141
+ role = "user" if m["role"] == "user" else "model"
142
+ contents.append({"role": role, "parts": [{"text": m["content"]}]})
143
+ # Prepend system as first user message if contents start with model
144
+ full_system = system + "\n\n" + (contents[0]["parts"][0]["text"] if contents and contents[0]["role"] == "user" else "")
145
+ if contents and contents[0]["role"] == "user":
146
+ contents[0]["parts"][0]["text"] = full_system
147
+ payload = {
148
+ "contents": contents,
149
+ "generationConfig": {"maxOutputTokens": 2048, "temperature": 0.6},
150
+ }
151
+ async with httpx.AsyncClient(timeout=90) as c:
152
+ r = await c.post(url, json=payload)
153
+ r.raise_for_status()
154
+ return r.json()["candidates"][0]["content"]["parts"][0]["text"]
155
+
156
+ async def call_llm_multiturn(agent, messages):
157
+ """Multi-turn LLM call with full conversation history. Cascades through providers."""
158
+ system = agent["role"]
159
+ last_err = None
160
+
161
+ # 1. Primary provider
162
+ p = PROVIDERS[agent["provider"]]
163
+ for m in agent["models"]:
164
+ try:
165
+ if p["type"] == "gemini":
166
+ return await call_gemini_multiturn(m, system, messages, p["key"])
167
+ else:
168
+ return await call_compat_multiturn(p["base_url"], m, system, messages,
169
+ p["key"], p.get("headers", {}))
170
+ except Exception as e:
171
+ last_err = str(e)
172
+ if is_rate_limit(last_err):
173
+ break
174
+
175
+ # 2. OpenRouter fallback (Gemma 3 first)
176
+ if OPENROUTER_API_KEY and agent["provider"] != "openrouter":
177
+ or_prov = PROVIDERS["openrouter"]
178
+ for m in ["google/gemma-3-27b-it:free", "google/gemma-3-12b-it:free",
179
+ "meta-llama/llama-3.3-70b-instruct:free",
180
+ "mistralai/mistral-small-3.1-24b-instruct:free"]:
181
+ try:
182
+ return await call_compat_multiturn(or_prov["base_url"], m, system, messages,
183
+ or_prov["key"], or_prov.get("headers", {}))
184
+ except Exception as e:
185
+ last_err = str(e)
186
+ if is_rate_limit(last_err):
187
+ break
188
+
189
+ # 3. Groq fallback
190
+ if GROQ_API_KEY and agent["provider"] != "groq":
191
+ groq = PROVIDERS["groq"]
192
+ for m in ["llama-3.1-8b-instant", "gemma2-9b-it"]:
193
+ try:
194
+ return await call_compat_multiturn(groq["base_url"], m, system, messages,
195
+ GROQ_API_KEY, {})
196
+ except Exception as e:
197
+ last_err = str(e)
198
+
199
+ raise Exception(f"All providers exhausted. Last: {last_err}")
200
+
201
  def is_rate_limit(err: str) -> bool:
202
  e = err.lower()
203
  return any(x in e for x in ["429","rate limit","quota","resource exhausted","too many requests","ratelimit"])
 
225
  if OPENROUTER_API_KEY and agent["provider"] != "openrouter":
226
  or_prov = PROVIDERS["openrouter"]
227
  for m in [
228
+ "google/gemma-3-27b-it:free",
229
+ "google/gemma-3-12b-it:free",
230
  "meta-llama/llama-3.3-70b-instruct:free",
231
  "mistralai/mistral-small-3.1-24b-instruct:free",
232
  "qwen/qwen3-4b:free",
 
233
  "qwen/qwen-2.5-72b-instruct:free",
 
234
  "deepseek/deepseek-r1-distill-llama-70b:free",
235
  ]:
236
  try:
 
599
 
600
 
601
 
602
+ # In-memory chat sessions: {session_id: [{role, content}]}
603
+ chat_sessions: dict = {}
604
+
605
  @app.post("/api/chat")
606
  async def chat_with_agent(request: Request):
607
+ body = await request.json()
608
+ agent_key = body.get("agent", "").strip()
609
+ message = body.get("message", "").strip()
610
+ session_id = body.get("session_id", agent_key) # default: one session per agent
611
+ clear = body.get("clear", False)
612
+
613
  if not agent_key or not message:
614
  return JSONResponse({"error": "agent and message required"}, status_code=400)
615
  if agent_key not in agent_registry:
616
  return JSONResponse({"error": f"Agent '{agent_key}' not found"}, status_code=404)
617
+
618
+ # Reset history if requested
619
+ if clear:
620
+ chat_sessions[session_id] = []
621
+
622
+ # Init session
623
+ if session_id not in chat_sessions:
624
+ chat_sessions[session_id] = []
625
+
626
+ # Build messages list (keep last 20 turns = 40 messages to stay within context)
627
+ history = chat_sessions[session_id][-40:]
628
+ history.append({"role": "user", "content": message})
629
+
630
+ # Inject today's date into agent role
631
+ _today = datetime.now().strftime("%A %d de %B de %Y, %H:%M")
632
  agent = dict(agent_registry[agent_key])
633
+ agent["role"] = f"HOY ES: {_today}. Eres {agent['name']}. " + agent["role"]
634
+
635
  try:
636
+ response = await call_llm_multiturn(agent, history)
637
+
638
+ # Save turn to session
639
+ chat_sessions[session_id].append({"role": "user", "content": message})
640
+ chat_sessions[session_id].append({"role": "assistant", "content": response})
641
+
642
+ # Keep sessions from growing too large (max 100 messages)
643
+ if len(chat_sessions[session_id]) > 100:
644
+ chat_sessions[session_id] = chat_sessions[session_id][-80:]
645
+
646
+ used_model = agent["models"][0] if agent.get("models") else ""
647
+ return JSONResponse({
648
+ "success": True,
649
+ "agent": agent_key,
650
+ "response": response,
651
+ "model": used_model,
652
+ "turn": len(chat_sessions[session_id]) // 2,
653
+ })
654
  except Exception as e:
655
  return JSONResponse({"error": str(e)}, status_code=500)
656
 
657
+ @app.delete("/api/chat/{session_id}")
658
+ async def clear_chat_session(session_id: str):
659
+ chat_sessions.pop(session_id, None)
660
+ return {"success": True}
661
+
662
  @app.get("/api/archive")
663
  async def list_archive():
664
  files = []