Skip to content

Commit ba2c48a

Browse files
committed
Add YAML and Jinja2 support to agents; refactor agent method names
- Updated `requirements.txt` to include `pyyaml` and `jinja2`. - Refactored `AgentNode` class in `base.py` to change method name from `handle` to `__call__`. - Enhanced `FAQAgent` and `WizardAgent` to load configurations from YAML files and utilize Jinja2 for prompt rendering. - Improved prompt generation in `supervisor.py` and `faq.py` for better context handling and response generation.
1 parent fd314d9 commit ba2c48a

File tree

14 files changed

+552
-150
lines changed

14 files changed

+552
-150
lines changed

app/agents/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ class AgentNode(ABC):
1717
name – short identifier used as the graph node key (e.g. "faq").
1818
description – natural-language purpose used by the supervisor to route
1919
user messages to the right agent.
20-
handle() – async entry point that LangGraph invokes.
20+
__call__() – async entry point that LangGraph invokes.
2121
"""
2222

2323
name: str
2424
description: str
2525

2626
@abstractmethod
27-
async def handle(self, state: ConversationState) -> ConversationState:
27+
async def __call__(self, state: ConversationState) -> ConversationState:
2828
"""Process the current conversation state and return the updated state."""
2929
...

app/agents/config/faq.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: faq
2+
description: >-
3+
Responde preguntas sobre Ithaka: programas (Fellows, minor),
4+
cursos, costos, campus, convocatorias, requisitos, actividades,
5+
información general y preguntas frecuentes. Incluye cualquier
6+
consulta informativa como '¿qué es...?', '¿cómo funciona...?',
7+
'¿cuándo es...?', '¿dónde queda...?', contacto, etc.
8+
9+
model:
10+
temperature_contextual: 0.0
11+
temperature_no_results: 0.0
12+
max_tokens_contextual: 400
13+
max_tokens_no_results: 200
14+
15+
system_prompts:
16+
contextual: "Eres el asistente virtual oficial de Ithaka, centro de emprendimiento de la UCU. Respondes consultas de manera amigable y precisa."
17+
no_results: "Eres el asistente de Ithaka. Ayuda al usuario incluso cuando no tienes información específica."

app/agents/config/supervisor.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
model:
2+
temperature: 0.0
3+
max_tokens: 10

app/agents/config/wizard.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name: wizard
2+
description: >-
3+
Guía al usuario a través del proceso de postulación/aplicación:
4+
postular una idea o proyecto, inscripción, formulario de
5+
postulación, emprendimiento, incubadora, startup. También maneja
6+
comandos de navegación del formulario como 'volver' o 'cancelar'.

app/agents/faq.py

Lines changed: 47 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

55
import logging
66
import os
7+
from pathlib import Path
78
from typing import Any
89

910
import numpy as np
11+
import yaml
12+
from jinja2 import Environment, FileSystemLoader
1013
from langchain_core.messages import AIMessage
1114
from openai import AsyncOpenAI
1215

@@ -17,6 +20,10 @@
1720

1821
logger = logging.getLogger(__name__)
1922

23+
_AGENTS_DIR = Path(__file__).parent
24+
_config = yaml.safe_load((_AGENTS_DIR / "config" / "faq.yaml").read_text())
25+
_prompts = Environment(loader=FileSystemLoader(str(_AGENTS_DIR / "prompts")), keep_trailing_newline=True)
26+
2027

2128
def to_serializable(obj):
2229
if isinstance(obj, np.floating):
@@ -31,14 +38,8 @@ def to_serializable(obj):
3138
class FAQAgent(AgentNode):
3239
"""Agente para responder preguntas frecuentes usando base vectorial"""
3340

34-
name = "faq"
35-
description = (
36-
"Responde preguntas sobre Ithaka: programas (Fellows, minor), "
37-
"cursos, costos, campus, convocatorias, requisitos, actividades, "
38-
"información general y preguntas frecuentes. Incluye cualquier "
39-
"consulta informativa como '¿qué es...?', '¿cómo funciona...?', "
40-
"'¿cuándo es...?', '¿dónde queda...?', contacto, etc."
41-
)
41+
name: str = _config["name"]
42+
description: str = _config["description"]
4243

4344
def __init__(self):
4445
api_key = os.getenv("OPENAI_API_KEY")
@@ -51,7 +52,7 @@ def __init__(self):
5152
self.similarity_threshold = float(
5253
os.getenv("SIMILARITY_THRESHOLD", "0.4"))
5354

54-
async def handle(self, state: ConversationState) -> ConversationState:
55+
async def __call__(self, state: ConversationState) -> ConversationState:
5556
"""Procesa una consulta FAQ del usuario"""
5657

5758
user_message = [m.content for m in state["messages"] if m.type == "human"][-1]
@@ -138,132 +139,77 @@ async def _generate_contextual_response(
138139
"""Genera una respuesta contextualizada basada en FAQs similares"""
139140

140141
try:
141-
# Preparar contexto de FAQs
142142
faq_context = ""
143143
for i, faq in enumerate(similar_faqs, 1):
144-
faq_context += f"""
145-
FAQ {i} (similitud: {faq['similarity']:.2f}):
146-
Pregunta: {faq['question']}
147-
Respuesta: {faq['answer']}
148-
"""
144+
faq_context += (
145+
f"\nFAQ {i} (similitud: {faq['similarity']:.2f}):\n"
146+
f"Pregunta: {faq['question']}\n"
147+
f"Respuesta: {faq['answer']}\n"
148+
)
149149

150-
prompt = f"""
151-
Eres el asistente virtual inteligente de Ithaka (centro de emprendimiento UCU). Tu misión es ayudar de la manera más útil posible.
152-
153-
CONSULTA DEL USUARIO:
154-
"{user_query}"
155-
156-
INFORMACIÓN RELEVANTE ENCONTRADA:
157-
{faq_context}
158-
159-
INSTRUCCIONES INTELIGENTES:
160-
1. **Flexibilidad**: Interpreta la intención aunque haya errores de tipeo ("corsos" = "cursos", "ithaka" mal escrito, etc.)
161-
2. **Contextualidad**: Si preguntan sobre temas relacionados a emprendimiento/universidad, conecta con lo que ofrece Ithaka
162-
3. **Inteligencia**: Aunque la pregunta no sea exacta, infiere qué información necesita (ej: "qué hacen" → explica programas y servicios)
163-
4. **Completitud**: Da información útil incluso si no hay coincidencia perfecta
164-
5. **Natural**: Responde conversacionalmente, como si fueras un consejero experto
165-
6. **Proactivo**: Sugiere recursos adicionales y próximos pasos
166-
7. **Amigable**: Termina invitando a hacer más preguntas
167-
8. **Postulación directa**: Si la intención del usuario es postularse (ej: "quiero postularme", "quiero inscribirme", "quiero postular mi idea"), no preguntes "¿te gustaría saber más sobre eso?". En su lugar, ofrece iniciar la postulación directamente con una frase clara como "Si querés, iniciamos tu postulación ahora mismo."
168-
169-
CONTEXTO ITHAKA:
170-
- Centro de emprendimiento de la Universidad Católica del Uruguay
171-
- Ofrece: cursos, minor de emprendimiento, programa Fellows, incubadora
172-
- Todo gratuito para comunidad UCU
173-
- Abierto también a emprendedores externos
174-
- Foco en innovación, emprendimiento e impacto social
175-
176-
RESPUESTA INTELIGENTE:
177-
"""
150+
prompt = _prompts.get_template("faq_contextual.j2").render(
151+
user_query=user_query,
152+
faq_context=faq_context,
153+
)
178154

155+
model_cfg = _config["model"]
179156
response = await self.client.chat.completions.create(
180157
model=self.model,
181158
messages=[
182-
{
183-
"role": "system",
184-
"content": "Eres el asistente virtual oficial de Ithaka, centro de emprendimiento de la UCU. Respondes consultas de manera amigable y precisa."
185-
},
186-
{"role": "user", "content": prompt}
159+
{"role": "system", "content": _config["system_prompts"]["contextual"]},
160+
{"role": "user", "content": prompt},
187161
],
188-
temperature=0.3,
189-
max_tokens=400
162+
temperature=model_cfg["temperature_contextual"],
163+
max_tokens=model_cfg["max_tokens_contextual"],
190164
)
191165

192166
return response.choices[0].message.content
193167

194168
except Exception as e:
195169
logger.error(f"Error generating contextual response: {e}")
196170

197-
# Respuesta básica usando la FAQ más similar
198171
best_faq = similar_faqs[0] if similar_faqs else None
199172
if best_faq:
200-
return f"""
201-
Basándome en tu consulta, creo que esto te puede ayudar:
202-
203-
**{best_faq['question']}**
204-
205-
{best_faq['answer']}
206-
207-
¿Esto responde a tu pregunta o necesitas información adicional?
208-
"""
173+
return (
174+
f"Basándome en tu consulta, creo que esto te puede ayudar:\n\n"
175+
f"**{best_faq['question']}**\n\n"
176+
f"{best_faq['answer']}\n\n"
177+
f"¿Esto responde a tu pregunta o necesitas información adicional?"
178+
)
209179

210180
return "Lo siento, no pude procesar tu consulta correctamente. ¿Podrías reformularla?"
211181

212182
async def _generate_no_results_response(self, user_query: str) -> str:
213183
"""Genera respuesta cuando no se encuentran FAQs relevantes"""
214184

215185
try:
216-
prompt = f"""
217-
El usuario preguntó: "{user_query}"
218-
219-
Aunque no encuentro FAQs específicas que coincidan exactamente, soy el asistente inteligente de Ithaka y puedo ayudar.
220-
221-
CONTEXTO ITHAKA:
222-
- Centro de emprendimiento de la Universidad Católica del Uruguay
223-
- Programas: Minor de emprendimiento, Programa Fellows, cursos electivos
224-
- Servicios: Incubadora de startups, mentorías, capacitaciones
225-
- Todo gratuito para comunidad UCU, abierto a emprendedores externos
226-
- Campus: Montevideo, Maldonado, Salto
227-
- Foco: Innovación, emprendimiento, impacto social
228-
229-
GENERA UNA RESPUESTA INTELIGENTE QUE:
230-
1. **Interprete la intención**: Aunque la pregunta tenga errores o sea vaga, infiere qué necesita
231-
2. **Proporcione valor**: Da información útil sobre Ithaka basándose en el contexto
232-
3. **Sea proactiva**: Sugiere programas/servicios que podrían interesarle
233-
4. **Mantenga conversación**: Invita a hacer preguntas más específicas
234-
5. **Corrige sutilmente**: Si hay errores de tipeo, usa las palabras correctas en tu respuesta
235-
6. **Postulación directa**: Si el usuario quiere postularse, ofrece iniciar la postulación de forma directa y no uses la frase "¿te gustaría saber más sobre eso?".
236-
237-
Respuesta útil e inteligente:
238-
"""
186+
prompt = _prompts.get_template("faq_no_results.j2").render(
187+
user_query=user_query,
188+
)
239189

190+
model_cfg = _config["model"]
240191
response = await self.client.chat.completions.create(
241192
model=self.model,
242193
messages=[
243-
{
244-
"role": "system",
245-
"content": "Eres el asistente de Ithaka. Ayuda al usuario incluso cuando no tienes información específica."
246-
},
247-
{"role": "user", "content": prompt}
194+
{"role": "system", "content": _config["system_prompts"]["no_results"]},
195+
{"role": "user", "content": prompt},
248196
],
249-
temperature=0.5,
250-
max_tokens=200
197+
temperature=model_cfg["temperature_no_results"],
198+
max_tokens=model_cfg["max_tokens_no_results"],
251199
)
252200

253201
return response.choices[0].message.content
254202

255203
except Exception as e:
256204
logger.error(f"Error generating no results response: {e}")
257-
return """
258-
No encontré información específica sobre tu consulta en nuestras FAQs.
259-
260-
Te sugiero:
261-
• Contactar directamente al equipo de Ithaka
262-
• Revisar nuestro sitio web oficial
263-
• Seguirnos en redes sociales para estar al día
264-
265-
¿Hay algo más sobre emprendimiento o nuestros programas en lo que pueda ayudarte?
266-
"""
205+
return (
206+
"No encontré información específica sobre tu consulta en nuestras FAQs.\n\n"
207+
"Te sugiero:\n"
208+
"• Contactar directamente al equipo de Ithaka\n"
209+
"• Revisar nuestro sitio web oficial\n"
210+
"• Seguirnos en redes sociales para estar al día\n\n"
211+
"¿Hay algo más sobre emprendimiento o nuestros programas en lo que pueda ayudarte?"
212+
)
267213

268214

269215
# Instancia global del agente
@@ -272,4 +218,4 @@ async def _generate_no_results_response(self, user_query: str) -> str:
272218

273219
async def handle_faq_query(state: ConversationState) -> ConversationState:
274220
"""Función wrapper para LangGraph"""
275-
return await faq_agent.handle(state)
221+
return await faq_agent(state)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Eres el asistente virtual inteligente de Ithaka (centro de emprendimiento UCU). Tu misión es ayudar de la manera más útil posible.
2+
3+
CONSULTA DEL USUARIO:
4+
"{{ user_query }}"
5+
6+
INFORMACIÓN RELEVANTE ENCONTRADA:
7+
{{ faq_context }}
8+
9+
INSTRUCCIONES INTELIGENTES:
10+
1. **Flexibilidad**: Interpreta la intención aunque haya errores de tipeo ("corsos" = "cursos", "ithaka" mal escrito, etc.)
11+
2. **Contextualidad**: Si preguntan sobre temas relacionados a emprendimiento/universidad, conecta con lo que ofrece Ithaka
12+
3. **Inteligencia**: Aunque la pregunta no sea exacta, infiere qué información necesita (ej: "qué hacen" → explica programas y servicios)
13+
4. **Completitud**: Da información útil incluso si no hay coincidencia perfecta
14+
5. **Natural**: Responde conversacionalmente, como si fueras un consejero experto
15+
6. **Proactivo**: Sugiere recursos adicionales y próximos pasos
16+
7. **Amigable**: Termina invitando a hacer más preguntas
17+
8. **Postulación directa**: Si la intención del usuario es postularse (ej: "quiero postularme", "quiero inscribirme", "quiero postular mi idea"), no preguntes "¿te gustaría saber más sobre eso?". En su lugar, ofrece iniciar la postulación directamente con una frase clara como "Si querés, iniciamos tu postulación ahora mismo."
18+
19+
CONTEXTO ITHAKA:
20+
- Centro de emprendimiento de la Universidad Católica del Uruguay
21+
- Ofrece: cursos, minor de emprendimiento, programa Fellows, incubadora
22+
- Todo gratuito para comunidad UCU
23+
- Abierto también a emprendedores externos
24+
- Foco en innovación, emprendimiento e impacto social
25+
26+
RESPUESTA INTELIGENTE:
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
El usuario preguntó: "{{ user_query }}"
2+
3+
Aunque no encuentro FAQs específicas que coincidan exactamente, soy el asistente inteligente de Ithaka y puedo ayudar.
4+
5+
CONTEXTO ITHAKA:
6+
- Centro de emprendimiento de la Universidad Católica del Uruguay
7+
- Programas: Minor de emprendimiento, Programa Fellows, cursos electivos
8+
- Servicios: Incubadora de startups, mentorías, capacitaciones
9+
- Todo gratuito para comunidad UCU, abierto a emprendedores externos
10+
- Campus: Montevideo, Maldonado, Salto
11+
- Foco: Innovación, emprendimiento, impacto social
12+
13+
GENERA UNA RESPUESTA INTELIGENTE QUE:
14+
1. **Interprete la intención**: Aunque la pregunta tenga errores o sea vaga, infiere qué necesita
15+
2. **Proporcione valor**: Da información útil sobre Ithaka basándose en el contexto
16+
3. **Sea proactiva**: Sugiere programas/servicios que podrían interesarle
17+
4. **Mantenga conversación**: Invita a hacer preguntas más específicas
18+
5. **Corrige sutilmente**: Si hay errores de tipeo, usa las palabras correctas en tu respuesta
19+
6. **Postulación directa**: Si el usuario quiere postularse, ofrece iniciar la postulación de forma directa y no uses la frase "¿te gustaría saber más sobre eso?".
20+
21+
Respuesta útil e inteligente:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Eres el asistente virtual oficial de Ithaka, centro de emprendimiento de la Universidad Católica del Uruguay. Respondes consultas de manera amigable y precisa.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Eres un router que analiza la intención del usuario y elige el agente más adecuado.
2+
3+
AGENTES DISPONIBLES:
4+
{{ agents_block }}
5+
6+
{% if context %}
7+
CONTEXTO (mensajes recientes de la conversación):
8+
{{ context }}
9+
10+
{% endif %}
11+
MENSAJE ACTUAL DEL USUARIO:
12+
"{{ message }}"
13+
14+
INSTRUCCIONES:
15+
- Elige el agente cuya descripción mejor coincida con lo que el usuario quiere hacer.
16+
- Considera el contexto completo de la conversación.
17+
- No uses heurísticas por palabras clave: interpreta la intención por el contexto.
18+
- Si el usuario quiere iniciar postulación o responde afirmativamente a una invitación a iniciarla, elige wizard.
19+
- Responde ÚNICAMENTE con el nombre del agente: {{ valid_names }}.
20+
- No agregues explicación ni puntuación, solo el nombre.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Eres un router experto. Respondes únicamente con el nombre del agente elegido, sin explicación.

0 commit comments

Comments
 (0)