Skip to content

Commit 7f32d2a

Browse files
committed
Add new workflows
1 parent 7429074 commit 7f32d2a

10 files changed

Lines changed: 1040 additions & 32 deletions
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""Análisis concurrente multi-experto usando orquestación ConcurrentBuilder.
2+
3+
Demuestra: ConcurrentBuilder(participants=[...]) para ejecutar 3 agentes
4+
especialistas en paralelo sobre el mismo prompt del usuario, y luego recopilar
5+
la salida agregada por defecto (lista combinada de mensajes).
6+
7+
Cada participante recibe el prompt original de forma independiente y se ejecuta
8+
concurrentemente. El agregador por defecto fusiona todas las conversaciones de
9+
los agentes en una sola lista de mensajes.
10+
11+
Contraste con workflow_agents_sequential.py, donde los agentes se ejecutan uno
12+
tras otro y cada uno ve la conversación completa hasta ese punto.
13+
14+
Referencia:
15+
https://learn.microsoft.com/en-us/agent-framework/workflows/orchestrations/concurrent?pivots=programming-language-python
16+
17+
Ejecutar:
18+
uv run examples/spanish/workflow_agents_concurrent.py
19+
uv run examples/spanish/workflow_agents_concurrent.py --devui (abre DevUI en http://localhost:8105)
20+
"""
21+
22+
import asyncio
23+
import logging
24+
import os
25+
import sys
26+
27+
from agent_framework import Agent, Message
28+
from agent_framework.openai import OpenAIChatClient
29+
from agent_framework.orchestrations import ConcurrentBuilder
30+
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
31+
from dotenv import load_dotenv
32+
from rich.logging import RichHandler
33+
34+
log_handler = RichHandler(show_path=False, rich_tracebacks=True, show_level=False)
35+
logging.basicConfig(level=logging.WARNING, handlers=[log_handler], force=True, format="%(message)s")
36+
logger = logging.getLogger(__name__)
37+
logger.setLevel(logging.INFO)
38+
39+
load_dotenv(override=True)
40+
API_HOST = os.getenv("API_HOST", "github")
41+
42+
# Configura el cliente de chat según el proveedor de API
43+
async_credential = None
44+
if API_HOST == "azure":
45+
async_credential = DefaultAzureCredential()
46+
token_provider = get_bearer_token_provider(async_credential, "https://cognitiveservices.azure.com/.default")
47+
client = OpenAIChatClient(
48+
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT']}/openai/v1/",
49+
api_key=token_provider,
50+
model_id=os.environ["AZURE_OPENAI_CHAT_DEPLOYMENT"],
51+
)
52+
elif API_HOST == "github":
53+
client = OpenAIChatClient(
54+
base_url="https://models.github.ai/inference",
55+
api_key=os.environ["GITHUB_TOKEN"],
56+
model_id=os.getenv("GITHUB_MODEL", "openai/gpt-4o-mini"),
57+
)
58+
else:
59+
client = OpenAIChatClient(
60+
api_key=os.environ["OPENAI_API_KEY"], model_id=os.environ.get("OPENAI_MODEL", "gpt-4o-mini")
61+
)
62+
63+
# Tres agentes especialistas — cada uno aporta una perspectiva diferente al mismo prompt
64+
researcher = Agent(
65+
client=client,
66+
name="Investigador",
67+
instructions=(
68+
"Eres un experto en investigación de mercado y productos. "
69+
"Dado un prompt, proporciona información concisa, factual, oportunidades y riesgos. "
70+
"Limita tu análisis a un párrafo."
71+
),
72+
)
73+
74+
marketer = Agent(
75+
client=client,
76+
name="Mercadólogo",
77+
instructions=(
78+
"Eres un estratega creativo de marketing. "
79+
"Elabora una propuesta de valor atractiva y mensajes dirigidos alineados con el prompt. "
80+
"Limita tu respuesta a un párrafo."
81+
),
82+
)
83+
84+
legal = Agent(
85+
client=client,
86+
name="Legal",
87+
instructions=(
88+
"Eres un revisor cauteloso de asuntos legales y cumplimiento normativo. "
89+
"Destaca restricciones, advertencias y preocupaciones de política basadas en el prompt. "
90+
"Limita tu respuesta a un párrafo."
91+
),
92+
)
93+
94+
# Construye el workflow concurrente — los tres agentes se ejecutan en paralelo
95+
workflow = ConcurrentBuilder(participants=[researcher, marketer, legal]).build()
96+
97+
98+
async def main():
99+
prompt = "Estamos lanzando una nueva bicicleta eléctrica económica para viajeros urbanos."
100+
logger.info("Prompt: %s", prompt)
101+
result = await workflow.run(prompt)
102+
outputs = result.get_outputs()
103+
104+
for conversation in outputs:
105+
logger.info("===== Conversación agregada =====")
106+
messages: list[Message] = conversation
107+
for index, message in enumerate(messages, start=1):
108+
author = message.author_name or ("assistant" if message.role == "assistant" else "user")
109+
logger.info("%02d [%s]\n%s", index, author, message.text)
110+
111+
if async_credential:
112+
await async_credential.close()
113+
114+
115+
if __name__ == "__main__":
116+
if "--devui" in sys.argv:
117+
from agent_framework.devui import serve
118+
119+
serve(entities=[workflow], port=8105, auto_open=True)
120+
else:
121+
asyncio.run(main())
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
"""Ejemplo de orquestación MagenticOne con el setup de OpenAIChatClient de este repo.
2+
3+
Este ejemplo muestra cómo un manager Magentic coordina tres especialistas para
4+
crear un plan de viaje, con salida en streaming y eventos del ledger de orquestación.
5+
6+
Run:
7+
uv run examples/spanish/workflow_magenticone.py
8+
"""
9+
10+
import asyncio
11+
import json
12+
import os
13+
from typing import cast
14+
15+
from agent_framework import Agent, AgentResponseUpdate, Message, WorkflowEvent
16+
from agent_framework.openai import OpenAIChatClient
17+
from agent_framework.orchestrations import MagenticBuilder, MagenticProgressLedger
18+
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
19+
from dotenv import load_dotenv
20+
from rich.console import Console
21+
from rich.markdown import Markdown
22+
from rich.panel import Panel
23+
24+
# Configura el cliente OpenAI según el entorno
25+
load_dotenv(override=True)
26+
API_HOST = os.getenv("API_HOST", "github")
27+
28+
async_credential = None
29+
if API_HOST == "azure":
30+
async_credential = DefaultAzureCredential()
31+
token_provider = get_bearer_token_provider(async_credential, "https://cognitiveservices.azure.com/.default")
32+
client = OpenAIChatClient(
33+
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT']}/openai/v1/",
34+
api_key=token_provider,
35+
model_id=os.environ["AZURE_OPENAI_CHAT_DEPLOYMENT"],
36+
)
37+
elif API_HOST == "github":
38+
client = OpenAIChatClient(
39+
base_url="https://models.github.ai/inference",
40+
api_key=os.environ["GITHUB_TOKEN"],
41+
model_id=os.getenv("GITHUB_MODEL", "openai/gpt-4.1-mini"),
42+
)
43+
else:
44+
client = OpenAIChatClient(
45+
api_key=os.environ["OPENAI_API_KEY"], model_id=os.environ.get("OPENAI_MODEL", "gpt-4.1-mini")
46+
)
47+
48+
console = Console()
49+
50+
51+
local_agent = Agent(
52+
client=client,
53+
instructions=(
54+
"Sugieres actividades locales auténticas e interesantes o lugares para visitar, "
55+
"usando cualquier contexto que provea la persona usuaria u otros agentes."
56+
),
57+
name="local_agent",
58+
description="Specialist in local activities and places.",
59+
)
60+
61+
language_agent = Agent(
62+
client=client,
63+
instructions=(
64+
"Revisas planes de viaje y das recomendaciones prácticas para retos de idioma "
65+
"y comunicación en el destino. Si ya está bien cubierto, menciónalo con una razón clara."
66+
),
67+
name="language_agent",
68+
description="Specialist in language and communication advice.",
69+
)
70+
71+
travel_summary_agent = Agent(
72+
client=client,
73+
instructions=(
74+
"Sintetizas las sugerencias y recomendaciones de los demás agentes en un plan completo. "
75+
"Haz suposiciones razonables si faltan detalles. "
76+
"No hagas preguntas de seguimiento a la persona usuaria. "
77+
"No pidas confirmaciones ni permisos. "
78+
"YOUR FINAL RESPONSE MUST BE THE COMPLETE PLAN."
79+
),
80+
name="travel_summary_agent",
81+
description="Specialist in travel-plan synthesis.",
82+
)
83+
84+
manager_agent = Agent(
85+
client=client,
86+
name="manager_agent",
87+
description="Magentic manager that coordinates specialists.",
88+
instructions=(
89+
"Coordinas especialistas para resolver tareas complejas de forma eficiente. "
90+
"La persona usuaria no está disponible para preguntas de seguimiento. "
91+
"Si falta información, elige suposiciones sensatas y continúa. "
92+
"Asegúrate de terminar con un plan final completo."
93+
),
94+
)
95+
96+
magentic_workflow = MagenticBuilder(
97+
participants=[local_agent, language_agent, travel_summary_agent],
98+
manager_agent=manager_agent,
99+
max_round_count=10,
100+
max_stall_count=1,
101+
max_reset_count=1,
102+
).build()
103+
104+
105+
def handle_stream_event(event: WorkflowEvent, last_message_id: str | None) -> str | None:
106+
"""Renderiza un evento del stream y regresa el último message id actualizado."""
107+
if event.type == "output" and isinstance(event.data, AgentResponseUpdate):
108+
message_id = event.data.message_id
109+
if message_id != last_message_id:
110+
if last_message_id is not None:
111+
console.print()
112+
console.print(f"🤖 {event.executor_id}:", end=" ")
113+
last_message_id = message_id
114+
console.print(event.data, end="")
115+
return last_message_id
116+
117+
if event.type == "magentic_orchestrator":
118+
console.print()
119+
emoji = "✅" if event.data.event_type.name == "PROGRESS_LEDGER_UPDATED" else "🧭"
120+
121+
if isinstance(event.data.content, MagenticProgressLedger):
122+
rendered_content = json.dumps(event.data.content.to_dict(), indent=2)
123+
console.print(
124+
Panel(
125+
rendered_content,
126+
title=f"{emoji} Orchestrator: {event.data.event_type.name}",
127+
border_style="bold yellow",
128+
padding=(1, 2),
129+
)
130+
)
131+
elif hasattr(event.data.content, "text"):
132+
console.print(
133+
Panel(
134+
Markdown(event.data.content.text),
135+
title=f"{emoji} Orchestrator: {event.data.event_type.name}",
136+
border_style="bold green",
137+
padding=(1, 2),
138+
)
139+
)
140+
else:
141+
console.print(
142+
Panel(
143+
Markdown(str(event.data.content)),
144+
title=f"{emoji} Orchestrator: {event.data.event_type.name}",
145+
border_style="bold green",
146+
padding=(1, 2),
147+
)
148+
)
149+
150+
return last_message_id
151+
152+
153+
def print_final_result(output_event: WorkflowEvent | None) -> None:
154+
"""Imprime el plan final a partir del evento de salida del workflow."""
155+
if output_event is None:
156+
return
157+
158+
output_messages = cast(list[Message], output_event.data)
159+
console.print(
160+
Panel(
161+
Markdown(output_messages[-1].text),
162+
title="🌍 Plan de viaje final",
163+
border_style="bold green",
164+
padding=(1, 2),
165+
)
166+
)
167+
168+
169+
async def main() -> None:
170+
"""Ejecuta el workflow Magentic con salida en streaming."""
171+
task = (
172+
"Planea un viaje de medio día en Costa Rica para una familia con dos hijos de 6 y 9 años, "
173+
"hospedada en San José, con presupuesto medio. "
174+
"Entrega un itinerario completo con horarios, supuestos de transporte, costos estimados, "
175+
"recomendación de comida y consejos prácticos de idioma."
176+
)
177+
console.print(f"[bold]Tarea:[/bold] {task}\n")
178+
179+
last_message_id: str | None = None
180+
output_event: WorkflowEvent | None = None
181+
182+
async for event in magentic_workflow.run(task, stream=True):
183+
last_message_id = handle_stream_event(event, last_message_id)
184+
if event.type == "output" and not isinstance(event.data, AgentResponseUpdate):
185+
output_event = event
186+
187+
print_final_result(output_event)
188+
189+
if async_credential:
190+
await async_credential.close()
191+
192+
193+
if __name__ == "__main__":
194+
asyncio.run(main())

0 commit comments

Comments
 (0)