|
23 | 23 | import os |
24 | 24 | import sys |
25 | 25 |
|
26 | | -from agent_framework import Agent |
| 26 | +from pydantic import Field |
| 27 | + |
| 28 | +from agent_framework import Agent, AgentResponseUpdate, tool |
27 | 29 | from agent_framework.openai import OpenAIChatClient |
28 | 30 | from agent_framework.orchestrations import HandoffBuilder |
29 | 31 | from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider |
30 | 32 | from dotenv import load_dotenv |
31 | | -from rich.logging import RichHandler |
| 33 | +from rich.console import Console |
32 | 34 |
|
33 | | -log_handler = RichHandler(show_path=False, rich_tracebacks=True, show_level=False) |
34 | | -logging.basicConfig(level=logging.WARNING, handlers=[log_handler], force=True, format="%(message)s") |
35 | | -logger = logging.getLogger(__name__) |
36 | | -logger.setLevel(logging.INFO) |
| 35 | +logging.basicConfig(level=logging.WARNING) |
| 36 | +console = Console() |
37 | 37 |
|
38 | 38 | load_dotenv(override=True) |
39 | 39 | API_HOST = os.getenv("API_HOST", "github") |
|
60 | 60 | ) |
61 | 61 |
|
62 | 62 |
|
| 63 | +# ── Herramientas ──────────────────────────────────────────────────────────── |
| 64 | + |
| 65 | + |
| 66 | +@tool |
| 67 | +def initiate_return( |
| 68 | + order_id: str = Field(description="The order ID to return"), |
| 69 | + reason: str = Field(description="Reason for the return"), |
| 70 | +) -> str: |
| 71 | + """Inicia una devolución de producto y genera una etiqueta de envío prepagada.""" |
| 72 | + return ( |
| 73 | + f"Devolución iniciada para el pedido {order_id} (motivo: {reason}). " |
| 74 | + f"La etiqueta de devolución RL-{order_id}-2026 fue enviada por correo al cliente. " |
| 75 | + "Por favor, deja el paquete en cualquier punto de envío dentro de 14 días." |
| 76 | + ) |
| 77 | + |
| 78 | + |
| 79 | +@tool |
| 80 | +def process_refund( |
| 81 | + order_id: str = Field(description="The order ID to refund"), |
| 82 | + amount: str = Field(description="Refund amount in USD"), |
| 83 | +) -> str: |
| 84 | + """Procesa un reembolso al método de pago original del cliente.""" |
| 85 | + return ( |
| 86 | + f"Reembolso de ${amount} para el pedido {order_id} procesado. " |
| 87 | + "Aparecerá en el método de pago original en 5-10 días hábiles. " |
| 88 | + f"Número de confirmación: RF-{order_id}-2026." |
| 89 | + ) |
| 90 | + |
| 91 | + |
63 | 92 | # ── Agentes ─────────────────────────────────────────────────────────────── |
64 | 93 |
|
65 | 94 | triage_agent = Agent( |
66 | 95 | client=client, |
67 | 96 | name="agente_triaje", |
68 | 97 | instructions=( |
69 | | - "Eres un agente de triage de soporte al cliente. Saluda al cliente, entiende su problema " |
70 | | - "y haz handoff al especialista correcto: agente_pedidos para temas de pedidos y " |
| 98 | + "Eres un agente de triage de soporte al cliente. Reconoce brevemente el problema del cliente " |
| 99 | + "y haz handoff de inmediato al especialista correcto: agente_pedidos para temas de pedidos, " |
71 | 100 | "agente_devoluciones para devoluciones. No puedes gestionar reembolsos directamente. " |
72 | | - "Cuando el caso esté resuelto, di 'Goodbye!' (Goodbye/Adiós) para terminar la sesión." |
| 101 | + "NO pidas detalles adicionales al cliente — el especialista se encargará. " |
| 102 | + "Cuando el caso esté resuelto, di '¡Adiós!' para terminar la sesión." |
73 | 103 | ), |
74 | 104 | ) |
75 | 105 |
|
|
86 | 116 | client=client, |
87 | 117 | name="agente_devoluciones", |
88 | 118 | instructions=( |
89 | | - "Atiendes devoluciones de productos. Ayuda al cliente a iniciar una devolución. " |
90 | | - "Si también quiere un reembolso, haz handoff a agente_reembolsos. " |
| 119 | + "Atiendes devoluciones de productos. Usa la herramienta initiate_return con la información proporcionada " |
| 120 | + "para crear la devolución — NO pidas detalles adicionales al cliente. " |
| 121 | + "Si también quiere un reembolso, haz handoff a agente_reembolsos después de iniciar la devolución. " |
91 | 122 | "De lo contrario, haz handoff de vuelta a agente_triaje al terminar." |
92 | 123 | ), |
| 124 | + tools=[initiate_return], |
93 | 125 | ) |
94 | 126 |
|
95 | 127 | refund_agent = Agent( |
96 | 128 | client=client, |
97 | 129 | name="agente_reembolsos", |
98 | 130 | instructions=( |
99 | | - "Procesas reembolsos por artículos devueltos. Confirma los detalles del reembolso y avísale " |
100 | | - "al cliente cuándo puede esperar el dinero de vuelta. Haz handoff a agente_triaje al terminar." |
| 131 | + "Procesas reembolsos por artículos devueltos. Usa la herramienta process_refund para emitir el reembolso " |
| 132 | + "con la información ya proporcionada — NO pidas detalles adicionales al cliente. " |
| 133 | + "Si el monto exacto no se conoce, usa una estimación razonable basada en el contexto. " |
| 134 | + "Confirma el resultado y haz handoff a agente_triaje al terminar." |
101 | 135 | ), |
| 136 | + tools=[process_refund], |
102 | 137 | ) |
103 | 138 |
|
104 | 139 | # ── Construye el workflow de handoff con reglas explícitas ───────────────── |
|
108 | 143 | name="handoff_soporte_cliente", |
109 | 144 | participants=[triage_agent, order_agent, return_agent, refund_agent], |
110 | 145 | termination_condition=lambda conversation: ( |
111 | | - len(conversation) > 0 and "goodbye" in conversation[-1].text.lower() |
| 146 | + len(conversation) > 0 and "adiós" in conversation[-1].text.lower() |
112 | 147 | ), |
113 | 148 | ) |
114 | 149 | .with_start_agent(triage_agent) |
|
126 | 161 |
|
127 | 162 | async def main() -> None: |
128 | 163 | """Ejecuta un workflow de soporte con handoff y reglas explícitas de ruteo.""" |
129 | | - request = "Quiero devolver una chamarra que compré la semana pasada y recibir un reembolso." |
130 | | - logger.info("Solicitud: %s\n", request) |
131 | | - |
132 | | - result = await workflow.run(request) |
133 | | - # El workflow de handoff devuelve la conversación completa como list[Message] |
134 | | - for output in result.get_outputs(): |
135 | | - if isinstance(output, list): |
136 | | - print(output[-1].text) |
| 164 | + request = ( |
| 165 | + "Quiero devolver una chamarra que compré la semana pasada y recibir un reembolso. " |
| 166 | + "Pedido #12345, es una chamarra azul impermeable de senderismo, talla M, llegó con el cierre roto. " |
| 167 | + "Pagué $89.99 y quiero el reembolso a mi tarjeta de crédito." |
| 168 | + ) |
| 169 | + console.print(f"[bold]Solicitud:[/bold] {request}\n") |
| 170 | + |
| 171 | + current_agent = None |
| 172 | + |
| 173 | + async for event in workflow.run(request, stream=True): |
| 174 | + if event.type == "handoff_sent": |
| 175 | + console.print( |
| 176 | + f"\n🔀 [bold yellow]Handoff:[/bold yellow] {event.data.source} → {event.data.target}\n" |
| 177 | + ) |
| 178 | + |
| 179 | + elif event.type == "output" and isinstance(event.data, AgentResponseUpdate): |
| 180 | + if event.executor_id != current_agent: |
| 181 | + current_agent = event.executor_id |
| 182 | + console.print(f"\n🤖 [bold cyan]{current_agent}[/bold cyan]") |
| 183 | + console.print(event.data.text, end="") |
137 | 184 |
|
138 | 185 | if async_credential: |
139 | 186 | await async_credential.close() |
|
0 commit comments