vfven commited on
Commit
d677097
·
verified ·
1 Parent(s): 550e14f

Upload 6 files

Browse files
agents/specialized/analyst.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agents/specialized/analyst.py
2
+ from agents.specialized.base import build_result, call_llm
3
+
4
+ ROLE = """
5
+ Eres analista de negocios. REGLAS:
6
+ 1. Solo haz lo que el manager delegó: revisar documentos, evaluar viabilidad, analizar riesgos.
7
+ 2. NUNCA describas imágenes ni hagas trabajo de otros agentes.
8
+ 3. Si la tarea no requiere análisis → responde: {"skip":"no analysis needed"}
9
+ """
10
+
11
+
12
+ async def run(task: str, context: dict = None) -> dict:
13
+ result = build_result("analyst")
14
+ try:
15
+ result["response"] = await call_llm("analyst", ROLE, task, context)
16
+ except Exception as e:
17
+ result["success"] = False
18
+ result["error"] = str(e)
19
+ return result
agents/specialized/backend_dev.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agents/specialized/backend_dev.py
2
+ import re
3
+ import json
4
+ from agents.specialized.base import build_result, call_llm
5
+ from core.file_builder import build_excel
6
+
7
+ ROLE = """
8
+ Eres programador backend senior. REGLAS ABSOLUTAS:
9
+ 1. Entrega SOLO el código pedido, sin explicaciones innecesarias.
10
+ 2. Para excel/planilla → responde con EXCEL_TEMPLATE:{"title":"...","sheet_name":"...","headers":[...],"sample_rows":[[...],[...]]}
11
+ 3. Python → entrega código Python puro y funcional.
12
+ 4. Groovy/Jenkins → entrega el script completo.
13
+ 5. Si hay frontend_dev en el equipo, TÚ haces servidor/backend, él hace HTML.
14
+ 6. Si la tarea no requiere backend → responde: {"skip":"no backend needed"}
15
+ """
16
+
17
+
18
+ async def run(task: str, context: dict = None) -> dict:
19
+ result = build_result("backend_dev")
20
+ try:
21
+ response = await call_llm("backend_dev", ROLE, task, context)
22
+ result["response"] = response
23
+
24
+ # Detectar si el agente generó un EXCEL_TEMPLATE
25
+ match = re.search(r'EXCEL_TEMPLATE:\s*(\{.*\})', response, re.DOTALL | re.IGNORECASE)
26
+ if match:
27
+ try:
28
+ excel_data = json.loads(match.group(1))
29
+ file_path = build_excel(task, excel_data)
30
+ if file_path:
31
+ result["file_path"] = file_path.name
32
+ result["file_type"] = "xlsx"
33
+ result["response"] = response.split("EXCEL_TEMPLATE:")[0].strip()
34
+ except Exception as e:
35
+ result["file_error"] = str(e)
36
+
37
+ except Exception as e:
38
+ result["success"] = False
39
+ result["error"] = str(e)
40
+
41
+ return result
agents/specialized/base.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agents/specialized/base.py
2
+ from core.llm_client import llm
3
+ from utils.prompts import get_today_prefix, get_chat_style
4
+
5
+
6
+ def build_result(agent_key: str) -> dict:
7
+ """Estructura base de resultado para cualquier agente."""
8
+ return {
9
+ "agent": agent_key,
10
+ "response": "",
11
+ "success": True,
12
+ "file_path": None,
13
+ "file_type": None,
14
+ "image_urls": [],
15
+ "queries": None,
16
+ }
17
+
18
+
19
+ async def call_llm(agent_key: str, role: str, task: str, context: dict = None) -> str:
20
+ """Llama al LLM con el rol, tarea y contexto dados."""
21
+ today = get_today_prefix()
22
+ full_prompt = today + role + get_chat_style() + f"\n\nTarea asignada: {task}"
23
+
24
+ if context:
25
+ full_prompt += "\n\nContexto de otros agentes:\n" + "\n".join(
26
+ [f"[{k.upper()}] {v[:300]}..." for k, v in context.items()]
27
+ )
28
+
29
+ messages = [{"role": "user", "content": full_prompt}]
30
+ return await llm.call(agent_key, messages, temperature=0.45)
agents/specialized/frontend_dev.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agents/specialized/frontend_dev.py
2
+ from agents.specialized.base import build_result, call_llm
3
+
4
+ ROLE = """
5
+ Eres desarrollador frontend senior. REGLAS ABSOLUTAS:
6
+ 1. Entrega SOLO código HTML/CSS/JS pedido, sin explicaciones innecesarias.
7
+ 2. Si hay backend_dev, TÚ haces HTML/interfaz, él hace servidor/lógica.
8
+ 3. Si la tarea NO requiere frontend → responde: {"skip":"no frontend needed"}
9
+ 4. Entrega siempre HTML completo y funcional con los estilos incluidos.
10
+ """
11
+
12
+
13
+ async def run(task: str, context: dict = None) -> dict:
14
+ result = build_result("frontend_dev")
15
+ try:
16
+ result["response"] = await call_llm("frontend_dev", ROLE, task, context)
17
+ except Exception as e:
18
+ result["success"] = False
19
+ result["error"] = str(e)
20
+ return result
agents/specialized/image_agent.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agents/specialized/image_agent.py
2
+ import os
3
+ import re
4
+ import json
5
+ import base64
6
+ import requests
7
+ from pathlib import Path
8
+ from datetime import datetime
9
+ from agents.specialized.base import build_result, call_llm
10
+ from agents.registry import AGENT_REGISTRY
11
+
12
+ ROLE = """
13
+ Eres el agente de imágenes.
14
+ Cuando se te pida generar, crear, mostrar o visualizar una imagen, responde SOLO con un JSON:
15
+ {"image_queries": ["término en inglés 1", "término en inglés 2", "término en inglés 3"]}
16
+ Los términos deben ser específicos, detallados y en inglés para obtener mejores resultados.
17
+ Si la tarea no es sobre imágenes, responde: {"skip":"no image task"}
18
+ """
19
+
20
+ DOCS_DIR = Path("data/docs")
21
+
22
+
23
+ async def generate_with_gemini(queries: list, model_name: str) -> list:
24
+ # ⚠️ pendiente de implementar con Gemini imagen real
25
+ return []
26
+
27
+
28
+ async def generate_with_hf(queries: list) -> list:
29
+ urls = []
30
+ for prompt in queries[:2]:
31
+ response = requests.post(
32
+ "https://router.huggingface.co/v1/images/generations",
33
+ headers={
34
+ "Authorization": f"Bearer {os.getenv('HF_API_TOKEN')}",
35
+ "Content-Type": "application/json",
36
+ },
37
+ json={
38
+ "model": "black-forest-labs/FLUX.1-schnell",
39
+ "prompt": prompt,
40
+ "size": "1024x1024",
41
+ },
42
+ )
43
+
44
+ if response.status_code == 200:
45
+ data = response.json()
46
+
47
+ # Algunos modelos devuelven b64_json, otros url directa
48
+ item = data["data"][0]
49
+ if "url" in item:
50
+ urls.append(item["url"])
51
+ elif "b64_json" in item:
52
+ image_bytes = base64.b64decode(item["b64_json"])
53
+ DOCS_DIR.mkdir(parents=True, exist_ok=True)
54
+ file_name = f"hf_image_{datetime.now().timestamp()}.png"
55
+ file_path = DOCS_DIR / file_name
56
+ with open(file_path, "wb") as f:
57
+ f.write(image_bytes)
58
+ urls.append(f"/docs/{file_name}")
59
+ else:
60
+ print("HF image error:", response.text)
61
+
62
+ return urls
63
+
64
+
65
+ async def run(task: str, context: dict = None) -> dict:
66
+ result = build_result("image_agent")
67
+ try:
68
+ response = await call_llm("image_agent", ROLE, task, context)
69
+ result["response"] = response
70
+
71
+ agent_config = AGENT_REGISTRY.get("image_agent", {})
72
+ provider = agent_config.get("provider")
73
+ model_name = agent_config.get("models", [None])[0]
74
+
75
+ print("USANDO PROVIDER:", provider)
76
+ print("USANDO MODELO:", model_name)
77
+
78
+ match = re.search(r'\{.*"image_queries".*\}', response, re.DOTALL)
79
+
80
+ if match:
81
+ try:
82
+ data = json.loads(match.group(0))
83
+ queries = data.get("image_queries", [])
84
+ result["queries"] = queries
85
+ result["response"] = "Ideas de imágenes generadas:\n" + "\n".join(queries)
86
+
87
+ image_urls = []
88
+
89
+ # Intento 1: provider principal
90
+ try:
91
+ if provider == "gemini":
92
+ image_urls = await generate_with_gemini(queries, model_name)
93
+ elif provider == "huggingface":
94
+ image_urls = await generate_with_hf(queries)
95
+ except Exception as e:
96
+ print("Provider principal falló:", str(e))
97
+
98
+ # Fallback a HF
99
+ if not image_urls:
100
+ try:
101
+ print("Intentando fallback HF...")
102
+ image_urls = await generate_with_hf(queries)
103
+ except Exception as e:
104
+ print("Fallback HF falló:", str(e))
105
+
106
+ # Fallback visual final
107
+ if not image_urls:
108
+ image_urls = ["https://picsum.photos/400/300"]
109
+
110
+ result["image_urls"] = image_urls
111
+ result["response"] += "\n\nImagen generada ✔" if image_urls else "\n\n⚠️ No se pudo generar imagen"
112
+
113
+ except Exception as e:
114
+ result["response"] = f"Error procesando JSON: {str(e)}"
115
+ else:
116
+ result["response"] = "No se detectaron instrucciones claras para imágenes."
117
+
118
+ except Exception as e:
119
+ result["success"] = False
120
+ result["error"] = str(e)
121
+
122
+ return result
agents/specialized/writer.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agents/specialized/writer.py
2
+ from agents.specialized.base import build_result, call_llm
3
+ from core.file_builder import build_docx
4
+
5
+ ROLE = """
6
+ Eres redactor experto. Escribe SOLO contenido real y extenso (500+ palabras).
7
+ Sin placeholders. Usa ## para secciones y ### para subsecciones.
8
+ Secciones: ## Resumen Ejecutivo, ### Introducción, ### Desarrollo,
9
+ ### Hallazgos, ### Conclusiones, ### Recomendaciones
10
+ """
11
+
12
+
13
+ async def run(task: str, context: dict = None) -> dict:
14
+ result = build_result("writer")
15
+ try:
16
+ response = await call_llm("writer", ROLE, task, context)
17
+ result["response"] = response
18
+
19
+ # Si la respuesta es suficientemente larga, generar un .docx
20
+ if len(response.strip()) > 300:
21
+ title = task[:60].strip('"') or "Informe generado"
22
+ try:
23
+ file_path = build_docx(title, response, images=None)
24
+ if file_path:
25
+ result["file_path"] = file_path.name
26
+ result["file_type"] = "docx"
27
+ except Exception as e:
28
+ result["file_error"] = str(e)
29
+
30
+ except Exception as e:
31
+ result["success"] = False
32
+ result["error"] = str(e)
33
+
34
+ return result