Skip to content

Commit 3835975

Browse files
authored
Merge pull request #31 from pamelafox/advtweaks
Improve console output for handoff and magentic workflows
2 parents 3b348ae + 1f2a751 commit 3835975

File tree

6 files changed

+188
-61
lines changed

6 files changed

+188
-61
lines changed

examples/spanish/workflow_handoffbuilder.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,10 @@
2626
from agent_framework.orchestrations import HandoffBuilder
2727
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
2828
from dotenv import load_dotenv
29-
from rich.logging import RichHandler
29+
from rich.console import Console
3030

31-
log_handler = RichHandler(show_path=False, rich_tracebacks=True, show_level=False)
32-
logging.basicConfig(level=logging.WARNING, handlers=[log_handler], force=True, format="%(message)s")
33-
logger = logging.getLogger(__name__)
34-
logger.setLevel(logging.INFO)
31+
logging.basicConfig(level=logging.WARNING)
32+
console = Console()
3533

3634
load_dotenv(override=True)
3735
API_HOST = os.getenv("API_HOST", "github")
@@ -136,19 +134,21 @@ async def main() -> None:
136134
# ── Ejecuta ──────────────────────────────────────────────────────
137135

138136
prompt = "Escribe un post de LinkedIn sobre desplegar agentes Python en Azure Container Apps."
139-
logger.info("Prompt: %s\n", prompt)
137+
console.print(f"[bold]Prompt:[/bold] {prompt}\n")
140138

141139
current_agent = None
142140

143141
async for event in workflow.run(prompt, stream=True):
144142
if event.type == "handoff_sent":
145-
logger.info("\n[Handoff] %s -> %s\n", event.data.source, event.data.target)
143+
console.print(
144+
f"\n🔀 [bold yellow]Handoff:[/bold yellow] {event.data.source}{event.data.target}\n"
145+
)
146146

147147
elif event.type == "output" and isinstance(event.data, AgentResponseUpdate):
148148
if event.executor_id != current_agent:
149149
current_agent = event.executor_id
150-
print(f"\n--- {current_agent} ---")
151-
print(event.data.text, end="", flush=True)
150+
console.print(f"\n🤖 [bold cyan]{current_agent}[/bold cyan]")
151+
console.print(event.data.text, end="")
152152

153153
if async_credential:
154154
await async_credential.close()

examples/spanish/workflow_handoffbuilder_rules.py

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@
2323
import os
2424
import sys
2525

26-
from agent_framework import Agent
26+
from pydantic import Field
27+
28+
from agent_framework import Agent, AgentResponseUpdate, tool
2729
from agent_framework.openai import OpenAIChatClient
2830
from agent_framework.orchestrations import HandoffBuilder
2931
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
3032
from dotenv import load_dotenv
31-
from rich.logging import RichHandler
33+
from rich.console import Console
3234

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()
3737

3838
load_dotenv(override=True)
3939
API_HOST = os.getenv("API_HOST", "github")
@@ -60,16 +60,46 @@
6060
)
6161

6262

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+
6392
# ── Agentes ───────────────────────────────────────────────────────────────
6493

6594
triage_agent = Agent(
6695
client=client,
6796
name="agente_triaje",
6897
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, "
71100
"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."
73103
),
74104
)
75105

@@ -86,19 +116,24 @@
86116
client=client,
87117
name="agente_devoluciones",
88118
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. "
91122
"De lo contrario, haz handoff de vuelta a agente_triaje al terminar."
92123
),
124+
tools=[initiate_return],
93125
)
94126

95127
refund_agent = Agent(
96128
client=client,
97129
name="agente_reembolsos",
98130
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."
101135
),
136+
tools=[process_refund],
102137
)
103138

104139
# ── Construye el workflow de handoff con reglas explícitas ─────────────────
@@ -108,7 +143,7 @@
108143
name="handoff_soporte_cliente",
109144
participants=[triage_agent, order_agent, return_agent, refund_agent],
110145
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()
112147
),
113148
)
114149
.with_start_agent(triage_agent)
@@ -126,14 +161,26 @@
126161

127162
async def main() -> None:
128163
"""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="")
137184

138185
if async_credential:
139186
await async_credential.close()

examples/spanish/workflow_magenticone.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,21 @@ def handle_stream_event(event: WorkflowEvent, last_message_id: str | None) -> st
116116
console.print(event.data, end="")
117117
return last_message_id
118118

119+
# Un participante terminó — muestra su salida
120+
if event.type == "executor_completed" and isinstance(event.data, list) and event.data:
121+
parts = [msg.text for msg in event.data if isinstance(msg, AgentResponseUpdate) and msg.text]
122+
if parts:
123+
full_text = "".join(parts)
124+
console.print(
125+
Panel(
126+
Markdown(full_text),
127+
title=f"🤖 {event.executor_id}",
128+
border_style="cyan",
129+
padding=(1, 2),
130+
)
131+
)
132+
return last_message_id
133+
119134
if event.type == "magentic_orchestrator":
120135
console.print()
121136
emoji = "✅" if event.data.event_type.name == "PROGRESS_LEDGER_UPDATED" else "🧭"

examples/workflow_handoffbuilder.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@
2525
from agent_framework.orchestrations import HandoffBuilder
2626
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
2727
from dotenv import load_dotenv
28-
from rich.logging import RichHandler
28+
from rich.console import Console
2929

30-
log_handler = RichHandler(show_path=False, rich_tracebacks=True, show_level=False)
31-
logging.basicConfig(level=logging.WARNING, handlers=[log_handler], force=True, format="%(message)s")
32-
logger = logging.getLogger(__name__)
33-
logger.setLevel(logging.INFO)
30+
logging.basicConfig(level=logging.WARNING)
31+
console = Console()
3432

3533
load_dotenv(override=True)
3634
API_HOST = os.getenv("API_HOST", "github")
@@ -135,19 +133,21 @@ async def main() -> None:
135133
# ── Run ───────────────────────────────────────────────────────────
136134

137135
prompt = "Write a LinkedIn post about deploying Python agents on Azure Container Apps."
138-
logger.info("Prompt: %s\n", prompt)
136+
console.print(f"[bold]Prompt:[/bold] {prompt}\n")
139137

140138
current_agent = None
141139

142140
async for event in workflow.run(prompt, stream=True):
143141
if event.type == "handoff_sent":
144-
logger.info("\n[Handoff] %s -> %s\n", event.data.source, event.data.target)
142+
console.print(
143+
f"\n🔀 [bold yellow]Handoff:[/bold yellow] {event.data.source}{event.data.target}\n"
144+
)
145145

146146
elif event.type == "output" and isinstance(event.data, AgentResponseUpdate):
147147
if event.executor_id != current_agent:
148148
current_agent = event.executor_id
149-
print(f"\n--- {current_agent} ---")
150-
print(event.data.text, end="", flush=True)
149+
console.print(f"\n🤖 [bold cyan]{current_agent}[/bold cyan]")
150+
console.print(event.data.text, end="")
151151

152152
if async_credential:
153153
await async_credential.close()

examples/workflow_handoffbuilder_rules.py

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@
2323
import os
2424
import sys
2525

26-
from agent_framework import Agent
26+
from pydantic import Field
27+
28+
from agent_framework import Agent, AgentResponseUpdate, tool
2729
from agent_framework.openai import OpenAIChatClient
2830
from agent_framework.orchestrations import HandoffBuilder
2931
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
3032
from dotenv import load_dotenv
31-
from rich.logging import RichHandler
33+
from rich.console import Console
3234

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()
3737

3838
load_dotenv(override=True)
3939
API_HOST = os.getenv("API_HOST", "github")
@@ -60,16 +60,46 @@
6060
)
6161

6262

63+
# ── Tools ──────────────────────────────────────────────────────────────────
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+
"""Initiate a product return and generate a prepaid shipping label."""
72+
return (
73+
f"Return initiated for order {order_id} (reason: {reason}). "
74+
f"Return label RL-{order_id}-2026 has been emailed to the customer. "
75+
"Please drop off at any carrier location within 14 days."
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+
"""Process a refund to the customer's original payment method."""
85+
return (
86+
f"Refund of ${amount} for order {order_id} has been processed. "
87+
"It will appear on the original payment method within 5-10 business days. "
88+
f"Refund confirmation number: RF-{order_id}-2026."
89+
)
90+
91+
6392
# ── Agents ─────────────────────────────────────────────────────────────────
6493

6594
triage_agent = Agent(
6695
client=client,
6796
name="triage_agent",
6897
instructions=(
69-
"You are a customer-support triage agent. Greet the customer, understand their issue, "
70-
"and hand off to the right specialist: order_agent for order inquiries, "
98+
"You are a customer-support triage agent. Briefly acknowledge the customer's issue "
99+
"and immediately hand off to the right specialist: order_agent for order inquiries, "
71100
"return_agent for returns. You cannot handle refunds directly. "
72-
"When the conversation is resolved, say 'Goodbye!' to end the session."
101+
"Do NOT ask the customer for additional details — the specialist will handle it. "
102+
"When the conversation is fully resolved (all agents have completed their tasks), say 'Goodbye!' to end the session."
73103
),
74104
)
75105

@@ -86,19 +116,24 @@
86116
client=client,
87117
name="return_agent",
88118
instructions=(
89-
"You handle product returns. Help the customer initiate a return. "
90-
"If they also want a refund, hand off to refund_agent. "
119+
"You handle product returns. Use the initiate_return tool with the information provided "
120+
"to create the return — do NOT ask the customer for extra details. "
121+
"If they also want a refund, hand off to refund_agent after initiating the return. "
91122
"Otherwise, hand off back to triage_agent when done."
92123
),
124+
tools=[initiate_return],
93125
)
94126

95127
refund_agent = Agent(
96128
client=client,
97129
name="refund_agent",
98130
instructions=(
99-
"You process refunds for returned items. Confirm the refund details and let the "
100-
"customer know when to expect the money back. Hand off to triage_agent when done."
131+
"You process refunds for returned items. Use the process_refund tool to issue the refund "
132+
"using the information already provided — do NOT ask the customer for extra details. "
133+
"If the exact amount is unknown, use a reasonable estimate based on context. "
134+
"Confirm the result and hand off to triage_agent when done."
101135
),
136+
tools=[process_refund],
102137
)
103138

104139
# ── Build the handoff workflow with explicit routing rules ─────────────────
@@ -126,14 +161,26 @@
126161

127162
async def main() -> None:
128163
"""Run a customer support handoff workflow with explicit routing rules."""
129-
request = "I want to return a jacket I bought last week and get a refund."
130-
logger.info("Request: %s\n", request)
131-
132-
result = await workflow.run(request)
133-
# The handoff workflow outputs the full conversation as list[Message]
134-
for output in result.get_outputs():
135-
if isinstance(output, list):
136-
print(output[-1].text)
164+
request = (
165+
"I want to return a jacket I bought last week and get a refund. "
166+
"Order #12345, it's a blue waterproof hiking jacket, size M, and it arrived with a torn zipper. "
167+
"I paid $89.99 and I'd like the refund back to my credit card."
168+
)
169+
console.print(f"[bold]Request:[/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="")
137184

138185
if async_credential:
139186
await async_credential.close()

0 commit comments

Comments
 (0)