mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
[BREAKING] Python: Standardize orchestration outputs as list of ChatMessage. Allow agent as group chat manager. (#2291)
* Standardize orchestration outputs as list of chatmessage. Add chat options to group chat prompt manager * refactor group chat * Improve group chat manager * README Update * Cleanup * Add comment * More cleanup * Standardize termination condition for group chat * Improvements on termination logic * Fix tests * Fix new line * PR feedback * Update ChatKit based on OpenAI type change * Raise error if response format is not expected type * Only one starting executor required. Add tests. * Add magentic start executor test
This commit is contained in:
committed by
GitHub
Unverified
parent
ed53ba158b
commit
907d79ab3c
@@ -92,7 +92,8 @@ For observability samples in Agent Framework, see the [observability getting sta
|
||||
| Concurrent Orchestration (Default Aggregator) | [orchestration/concurrent_agents.py](./orchestration/concurrent_agents.py) | Fan-out to multiple agents; fan-in with default aggregator returning combined ChatMessages |
|
||||
| Concurrent Orchestration (Custom Aggregator) | [orchestration/concurrent_custom_aggregator.py](./orchestration/concurrent_custom_aggregator.py) | Override aggregator via callback; summarize results with an LLM |
|
||||
| Concurrent Orchestration (Custom Agent Executors) | [orchestration/concurrent_custom_agent_executors.py](./orchestration/concurrent_custom_agent_executors.py) | Child executors own ChatAgents; concurrent fan-out/fan-in via ConcurrentBuilder |
|
||||
| Group Chat Orchestration with Prompt Based Manager | [orchestration/group_chat_prompt_based_manager.py](./orchestration/group_chat_prompt_based_manager.py) | LLM Manager-directed conversation using GroupChatBuilder |
|
||||
| Group Chat with Agent Manager | [orchestration/group_chat_agent_manager.py](./orchestration/group_chat_agent_manager.py) | Agent-based manager using `set_manager()` to select next speaker |
|
||||
| Group Chat Philosophical Debate | [orchestration/group_chat_philosophical_debate.py](./orchestration/group_chat_philosophical_debate.py) | Agent manager moderates long-form, multi-round debate across diverse participants |
|
||||
| Group Chat with Simple Function Selector | [orchestration/group_chat_simple_selector.py](./orchestration/group_chat_simple_selector.py) | Group chat with a simple function selector for next speaker |
|
||||
| Handoff (Simple) | [orchestration/handoff_simple.py](./orchestration/handoff_simple.py) | Single-tier routing: triage agent routes to specialists, control returns to user after each specialist response |
|
||||
| Handoff (Specialist-to-Specialist) | [orchestration/handoff_specialist_to_specialist.py](./orchestration/handoff_specialist_to_specialist.py) | Multi-tier routing: specialists can hand off to other specialists using `.add_handoff()` fluent API |
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
AgentRunUpdateEvent,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
GroupChatBuilder,
|
||||
Role,
|
||||
WorkflowOutputEvent,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
"""
|
||||
Sample: Group Chat with Agent-Based Manager
|
||||
|
||||
What it does:
|
||||
- Demonstrates the new set_manager() API for agent-based coordination
|
||||
- Manager is a full ChatAgent with access to tools, context, and observability
|
||||
- Coordinates a researcher and writer agent to solve tasks collaboratively
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured for OpenAIChatClient
|
||||
"""
|
||||
|
||||
|
||||
def _get_chat_client() -> AzureOpenAIChatClient:
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Create coordinator agent with structured output for speaker selection
|
||||
# Note: response_format is enforced to ManagerSelectionResponse by set_manager()
|
||||
coordinator = ChatAgent(
|
||||
name="Coordinator",
|
||||
description="Coordinates multi-agent collaboration by selecting speakers",
|
||||
instructions="""
|
||||
You coordinate a team conversation to solve the user's task.
|
||||
|
||||
Review the conversation history and select the next participant to speak.
|
||||
|
||||
Guidelines:
|
||||
- Start with Researcher to gather information
|
||||
- Then have Writer synthesize the final answer
|
||||
- Only finish after both have contributed meaningfully
|
||||
- Allow for multiple rounds of information gathering if needed
|
||||
""",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
researcher = ChatAgent(
|
||||
name="Researcher",
|
||||
description="Collects relevant background information",
|
||||
instructions="Gather concise facts that help a teammate answer the question.",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
writer = ChatAgent(
|
||||
name="Writer",
|
||||
description="Synthesizes polished answers from gathered information",
|
||||
instructions="Compose clear and structured answers using any notes provided.",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
workflow = (
|
||||
GroupChatBuilder()
|
||||
.set_manager(coordinator, display_name="Orchestrator")
|
||||
.with_termination_condition(lambda messages: sum(1 for msg in messages if msg.role == Role.ASSISTANT) >= 2)
|
||||
.participants([researcher, writer])
|
||||
.build()
|
||||
)
|
||||
|
||||
task = "What are the key benefits of using async/await in Python? Provide a concise summary."
|
||||
|
||||
print("\nStarting Group Chat with Agent-Based Manager...\n")
|
||||
print(f"TASK: {task}\n")
|
||||
print("=" * 80)
|
||||
|
||||
final_conversation: list[ChatMessage] = []
|
||||
last_executor_id: str | None = None
|
||||
async for event in workflow.run_stream(task):
|
||||
if isinstance(event, AgentRunUpdateEvent):
|
||||
eid = event.executor_id
|
||||
if eid != last_executor_id:
|
||||
if last_executor_id is not None:
|
||||
print()
|
||||
print(f"{eid}:", end=" ", flush=True)
|
||||
last_executor_id = eid
|
||||
print(event.data, end="", flush=True)
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
final_conversation = cast(list[ChatMessage], event.data)
|
||||
|
||||
if final_conversation and isinstance(final_conversation, list):
|
||||
print("\n\n" + "=" * 80)
|
||||
print("FINAL CONVERSATION")
|
||||
print("=" * 80)
|
||||
for msg in final_conversation:
|
||||
author = getattr(msg, "author_name", "Unknown")
|
||||
text = getattr(msg, "text", str(msg))
|
||||
print(f"\n[{author}]")
|
||||
print(text)
|
||||
print("-" * 80)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+408
@@ -0,0 +1,408 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
AgentRunUpdateEvent,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
GroupChatBuilder,
|
||||
Role,
|
||||
WorkflowOutputEvent,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
"""
|
||||
Sample: Philosophical Debate with Agent-Based Manager
|
||||
|
||||
What it does:
|
||||
- Creates a diverse group of agents representing different global perspectives
|
||||
- Uses an agent-based manager to guide a philosophical discussion
|
||||
- Demonstrates longer, multi-round discourse with natural conversation flow
|
||||
- Manager decides when discussion has reached meaningful conclusion
|
||||
|
||||
Topic: "What does a good life mean to you personally?"
|
||||
|
||||
Participants represent:
|
||||
- Farmer from Southeast Asia (tradition, sustainability, land connection)
|
||||
- Software Developer from United States (innovation, technology, work-life balance)
|
||||
- History Teacher from Eastern Europe (legacy, learning, cultural continuity)
|
||||
- Activist from South America (social justice, environmental rights)
|
||||
- Spiritual Leader from Middle East (morality, community service)
|
||||
- Artist from Africa (creative expression, storytelling)
|
||||
- Immigrant Entrepreneur from Asia in Canada (tradition + adaptation)
|
||||
- Doctor from Scandinavia (public health, equity, societal support)
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured for OpenAIChatClient
|
||||
"""
|
||||
|
||||
|
||||
def _get_chat_client() -> AzureOpenAIChatClient:
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Create debate moderator with structured output for speaker selection
|
||||
# Note: Participant names and descriptions are automatically injected by the orchestrator
|
||||
moderator = ChatAgent(
|
||||
name="Moderator",
|
||||
description="Guides philosophical discussion by selecting next speaker",
|
||||
instructions="""
|
||||
You are a thoughtful moderator guiding a philosophical discussion on the topic handed to you by the user.
|
||||
|
||||
Your participants bring diverse global perspectives. Select speakers strategically to:
|
||||
- Create natural conversation flow and responses to previous points
|
||||
- Ensure all voices are heard throughout the discussion
|
||||
- Build on themes and contrasts that emerge
|
||||
- Allow for respectful challenges and counterpoints
|
||||
- Guide toward meaningful conclusions
|
||||
|
||||
Select speakers who can:
|
||||
1. Respond directly to points just made
|
||||
2. Introduce fresh perspectives when needed
|
||||
3. Bridge or contrast different viewpoints
|
||||
4. Deepen the philosophical exploration
|
||||
|
||||
Finish when:
|
||||
- Multiple rounds have occurred (at least 6-8 exchanges)
|
||||
- Key themes have been explored from different angles
|
||||
- Natural conclusion or synthesis has emerged
|
||||
- Diminishing returns in new insights
|
||||
|
||||
In your final_message, provide a brief synthesis highlighting key themes that emerged.
|
||||
""",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
farmer = ChatAgent(
|
||||
name="Farmer",
|
||||
description="A rural farmer from Southeast Asia",
|
||||
instructions="""
|
||||
You're a farmer from Southeast Asia. Your life is deeply connected to land and family.
|
||||
You value tradition and sustainability. You are in a philosophical debate.
|
||||
|
||||
Share your perspective authentically. Feel free to:
|
||||
- Challenge other participants respectfully
|
||||
- Build on points others have made
|
||||
- Use concrete examples from your experience
|
||||
- Keep responses thoughtful but concise (2-4 sentences)
|
||||
""",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
developer = ChatAgent(
|
||||
name="Developer",
|
||||
description="An urban software developer from the United States",
|
||||
instructions="""
|
||||
You're a software developer from the United States. Your life is fast-paced and technology-driven.
|
||||
You value innovation, freedom, and work-life balance. You are in a philosophical debate.
|
||||
|
||||
Share your perspective authentically. Feel free to:
|
||||
- Challenge other participants respectfully
|
||||
- Build on points others have made
|
||||
- Use concrete examples from your experience
|
||||
- Keep responses thoughtful but concise (2-4 sentences)
|
||||
""",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
teacher = ChatAgent(
|
||||
name="Teacher",
|
||||
description="A retired history teacher from Eastern Europe",
|
||||
instructions="""
|
||||
You're a retired history teacher from Eastern Europe. You bring historical and philosophical
|
||||
perspectives to discussions. You value legacy, learning, and cultural continuity.
|
||||
You are in a philosophical debate.
|
||||
|
||||
Share your perspective authentically. Feel free to:
|
||||
- Challenge other participants respectfully
|
||||
- Build on points others have made
|
||||
- Use concrete examples from history or your teaching experience
|
||||
- Keep responses thoughtful but concise (2-4 sentences)
|
||||
""",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
activist = ChatAgent(
|
||||
name="Activist",
|
||||
description="A young activist from South America",
|
||||
instructions="""
|
||||
You're a young activist from South America. You focus on social justice, environmental rights,
|
||||
and generational change. You are in a philosophical debate.
|
||||
|
||||
Share your perspective authentically. Feel free to:
|
||||
- Challenge other participants respectfully
|
||||
- Build on points others have made
|
||||
- Use concrete examples from your activism
|
||||
- Keep responses thoughtful but concise (2-4 sentences)
|
||||
""",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
spiritual_leader = ChatAgent(
|
||||
name="SpiritualLeader",
|
||||
description="A spiritual leader from the Middle East",
|
||||
instructions="""
|
||||
You're a spiritual leader from the Middle East. You provide insights grounded in religion,
|
||||
morality, and community service. You are in a philosophical debate.
|
||||
|
||||
Share your perspective authentically. Feel free to:
|
||||
- Challenge other participants respectfully
|
||||
- Build on points others have made
|
||||
- Use examples from spiritual teachings or community work
|
||||
- Keep responses thoughtful but concise (2-4 sentences)
|
||||
""",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
artist = ChatAgent(
|
||||
name="Artist",
|
||||
description="An artist from Africa",
|
||||
instructions="""
|
||||
You're an artist from Africa. You view life through creative expression, storytelling,
|
||||
and collective memory. You are in a philosophical debate.
|
||||
|
||||
Share your perspective authentically. Feel free to:
|
||||
- Challenge other participants respectfully
|
||||
- Build on points others have made
|
||||
- Use examples from your art or cultural traditions
|
||||
- Keep responses thoughtful but concise (2-4 sentences)
|
||||
""",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
immigrant = ChatAgent(
|
||||
name="Immigrant",
|
||||
description="An immigrant entrepreneur from Asia living in Canada",
|
||||
instructions="""
|
||||
You're an immigrant entrepreneur from Asia living in Canada. You balance tradition with adaptation.
|
||||
You focus on family success, risk, and opportunity. You are in a philosophical debate.
|
||||
|
||||
Share your perspective authentically. Feel free to:
|
||||
- Challenge other participants respectfully
|
||||
- Build on points others have made
|
||||
- Use examples from your immigrant and entrepreneurial journey
|
||||
- Keep responses thoughtful but concise (2-4 sentences)
|
||||
""",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
doctor = ChatAgent(
|
||||
name="Doctor",
|
||||
description="A doctor from Scandinavia",
|
||||
instructions="""
|
||||
You're a doctor from Scandinavia. Your perspective is shaped by public health, equity,
|
||||
and structured societal support. You are in a philosophical debate.
|
||||
|
||||
Share your perspective authentically. Feel free to:
|
||||
- Challenge other participants respectfully
|
||||
- Build on points others have made
|
||||
- Use examples from healthcare and societal systems
|
||||
- Keep responses thoughtful but concise (2-4 sentences)
|
||||
""",
|
||||
chat_client=_get_chat_client(),
|
||||
)
|
||||
|
||||
workflow = (
|
||||
GroupChatBuilder()
|
||||
.set_manager(moderator, display_name="Moderator")
|
||||
.participants([farmer, developer, teacher, activist, spiritual_leader, artist, immigrant, doctor])
|
||||
.with_termination_condition(lambda messages: sum(1 for msg in messages if msg.role == Role.ASSISTANT) >= 10)
|
||||
.build()
|
||||
)
|
||||
|
||||
topic = "What does a good life mean to you personally?"
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("PHILOSOPHICAL DEBATE: Perspectives on a Good Life")
|
||||
print("=" * 80)
|
||||
print(f"\nTopic: {topic}")
|
||||
print("\nParticipants:")
|
||||
print(" - Farmer (Southeast Asia)")
|
||||
print(" - Developer (United States)")
|
||||
print(" - Teacher (Eastern Europe)")
|
||||
print(" - Activist (South America)")
|
||||
print(" - SpiritualLeader (Middle East)")
|
||||
print(" - Artist (Africa)")
|
||||
print(" - Immigrant (Asia → Canada)")
|
||||
print(" - Doctor (Scandinavia)")
|
||||
print("\n" + "=" * 80)
|
||||
print("DISCUSSION BEGINS")
|
||||
print("=" * 80 + "\n")
|
||||
|
||||
final_conversation: list[ChatMessage] = []
|
||||
current_speaker: str | None = None
|
||||
|
||||
async for event in workflow.run_stream(f"Please begin the discussion on: {topic}"):
|
||||
if isinstance(event, AgentRunUpdateEvent):
|
||||
speaker_id = event.executor_id.replace("groupchat_agent:", "")
|
||||
|
||||
if speaker_id != current_speaker:
|
||||
if current_speaker is not None:
|
||||
print("\n")
|
||||
print(f"[{speaker_id}]", flush=True)
|
||||
current_speaker = speaker_id
|
||||
|
||||
print(event.data, end="", flush=True)
|
||||
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
final_conversation = cast(list[ChatMessage], event.data)
|
||||
|
||||
print("\n\n" + "=" * 80)
|
||||
print("DISCUSSION SUMMARY")
|
||||
print("=" * 80)
|
||||
|
||||
if final_conversation and isinstance(final_conversation, list) and final_conversation:
|
||||
final_msg = final_conversation[-1]
|
||||
if hasattr(final_msg, "author_name") and final_msg.author_name == "Moderator":
|
||||
print(f"\n{final_msg.text}")
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
================================================================================
|
||||
PHILOSOPHICAL DEBATE: Perspectives on a Good Life
|
||||
================================================================================
|
||||
|
||||
Topic: What does a good life mean to you personally?
|
||||
|
||||
Participants:
|
||||
- Farmer (Southeast Asia)
|
||||
- Developer (United States)
|
||||
- Teacher (Eastern Europe)
|
||||
- Activist (South America)
|
||||
- SpiritualLeader (Middle East)
|
||||
- Artist (Africa)
|
||||
- Immigrant (Asia → Canada)
|
||||
- Doctor (Scandinavia)
|
||||
|
||||
================================================================================
|
||||
DISCUSSION BEGINS
|
||||
================================================================================
|
||||
|
||||
[Moderator]
|
||||
{"selected_participant":"Farmer","instruction":"Please start by sharing what living a good life means to you,
|
||||
especially from your perspective living in a rural area in Southeast Asia.","finish":false,"final_message":null}
|
||||
|
||||
[Farmer]
|
||||
To me, a good life is deeply intertwined with the rhythm of the land and the nurturing of relationships with my
|
||||
family and community. It means cultivating crops that respect our environment, ensuring sustainability for future
|
||||
generations, and sharing meals made from our harvests around the dinner table. The joy found in everyday
|
||||
tasks—planting rice or tending to our livestock—creates a sense of fulfillment that cannot be measured by material
|
||||
wealth. It's the simple moments, like sharing stories with my children under the stars, that truly define a good
|
||||
life. What good is progress if it isolates us from those we love and the land that sustains us?
|
||||
|
||||
[Moderator]
|
||||
{"selected_participant":"Developer","instruction":"Given the insights shared by the Farmer, please discuss what a
|
||||
good life means to you as a software developer in an urban setting in the United States and how it might contrast
|
||||
with or complement the Farmer's view.","finish":false,"final_message":null}
|
||||
|
||||
[Developer]
|
||||
As a software developer in an urban environment, a good life for me hinges on the intersection of innovation,
|
||||
creativity, and balance. It's about having the freedom to explore new technologies that can solve real-world
|
||||
problems while ensuring that my work doesn't encroach on my personal life. For instance, I value remote work
|
||||
flexibility, which allows me to maintain connections with family and friends, similar to how the Farmer values
|
||||
community. While our lifestyles may differ markedly, both of us seek fulfillment—whether through meaningful work or
|
||||
rich personal experiences. The challenge is finding harmony between technological progress and preserving the
|
||||
intimate human connections that truly enrich our lives.
|
||||
|
||||
[Moderator]
|
||||
{"selected_participant":"SpiritualLeader","instruction":"Reflect on both the Farmer's and Developer's perspectives
|
||||
and share your view of what constitutes a good life, particularly from your spiritual and cultural standpoint in
|
||||
the Middle East.","finish":false,"final_message":null}
|
||||
|
||||
[SpiritualLeader]
|
||||
From my spiritual perspective, a good life embodies a balance between personal fulfillment and service to others,
|
||||
rooted in compassion and community. In our teachings, we emphasize that true happiness comes from helping those in
|
||||
need and fostering strong connections with our families and neighbors. Whether it's the Farmer nurturing the earth
|
||||
or the Developer creating tools to enhance lives, both contribute to the greater good. The essence of a good life
|
||||
lies in our intentions and actions—finding ways to serve our communities, spread kindness, and live harmoniously
|
||||
with those around us. Ultimately, as we align our personal beliefs with our communal responsibilities, we cultivate
|
||||
a richness that transcends material wealth.
|
||||
|
||||
[Moderator]
|
||||
{"selected_participant":"Activist","instruction":"Add to the discussion by sharing your perspective on what a good
|
||||
life entails, particularly from your background as a young activist in South America.","finish":false,
|
||||
"final_message":null}
|
||||
|
||||
[Activist]
|
||||
As a young activist in South America, a good life for me is about advocating for social justice and environmental
|
||||
sustainability. It means living in a society where everyone's rights are respected and where marginalized voices,
|
||||
particularly those of Indigenous communities, are amplified. I see a good life as one where we work collectively to
|
||||
dismantle oppressive systems—such as deforestation and inequality—while nurturing our planet. For instance, through
|
||||
my activism, I've witnessed the transformative power of community organizing, where collective efforts lead to real
|
||||
change, like resisting destructive mining practices that threaten our rivers and lands. A good life, therefore, is
|
||||
not just lived for oneself but is deeply tied to the well-being of our communities and the health of our
|
||||
environment. How can we, regardless of our backgrounds, collaborate to foster these essential changes?
|
||||
|
||||
[Moderator]
|
||||
{"selected_participant":"Teacher","instruction":"Considering the views shared so far, tell us how your experience
|
||||
as a retired history teacher from Eastern Europe shapes your understanding of a good life, perhaps reflecting on
|
||||
lessons from the past and their impact on present-day life choices.","finish":false,"final_message":null}
|
||||
|
||||
[Teacher]
|
||||
As a retired history teacher from Eastern Europe, my understanding of a good life is deeply rooted in the lessons
|
||||
drawn from history and the struggle for freedom and dignity. Historical events, such as the fall of the Iron
|
||||
Curtain, remind us of the profound importance of liberty and collective resilience. A good life, therefore, is about
|
||||
cherishing our freedoms and working towards a society where everyone has a voice, much as my students and I
|
||||
discussed the impacts of totalitarian regimes. Additionally, I believe it involves fostering cultural continuity,
|
||||
where we honor our heritage while embracing progressive values. We must learn from the past—especially the
|
||||
consequences of neglecting empathy and solidarity—so that we can cultivate a future that values every individual's
|
||||
contributions to the rich tapestry of our shared humanity. How can we ensure that the lessons of history inform a
|
||||
more compassionate and just society moving forward?
|
||||
|
||||
[Moderator]
|
||||
{"selected_participant":"Artist","instruction":"Expound on the themes and perspectives discussed so far by sharing
|
||||
how, as an artist from Africa, you define a good life and how art plays a role in that vision.","finish":false,
|
||||
"final_message":null}
|
||||
|
||||
[Artist]
|
||||
As an artist from Africa, I define a good life as one steeped in cultural expression, storytelling, and the
|
||||
celebration of our collective memories. Art is a powerful medium through which we capture our histories, struggles,
|
||||
and triumphs, creating a tapestry that connects generations. For instance, in my work, I often draw from folktales
|
||||
and traditional music, weaving narratives that reflect the human experience, much like how the retired teacher
|
||||
emphasizes learning from history. A good life involves not only personal fulfillment but also the responsibility to
|
||||
share our narratives and use our creativity to inspire change, whether addressing social injustices or environmental
|
||||
issues. It's in this interplay of art and activism that we can transcend individual existence and contribute to a
|
||||
collective good, fostering empathy and understanding among diverse communities. How can we harness art to bridge
|
||||
differences and amplify marginalized voices in our pursuit of a good life?
|
||||
|
||||
[Moderator]
|
||||
{"selected_participant":null,"instruction":null,"finish":true,"final_message":"As our discussion unfolds, several
|
||||
key themes have gracefully emerged, reflecting the richness of diverse perspectives on what constitutes a good life.
|
||||
From the rural farmer's integration with the land to the developer's search for balance between technology and
|
||||
personal connection, each viewpoint validates that fulfillment, at its core, transcends material wealth. The
|
||||
spiritual leader and the activist highlight the importance of community and social justice, while the history
|
||||
teacher and the artist remind us of the lessons and narratives that shape our cultural and personal identities.
|
||||
|
||||
Ultimately, the good life seems to revolve around meaningful relationships, honoring our legacies while striving for
|
||||
progress, and nurturing both our inner selves and external communities. This dialogue demonstrates that despite our
|
||||
varied backgrounds and experiences, the quest for a good life binds us together, urging cooperation and empathy in
|
||||
our shared human journey."}
|
||||
|
||||
================================================================================
|
||||
DISCUSSION SUMMARY
|
||||
================================================================================
|
||||
|
||||
As our discussion unfolds, several key themes have gracefully emerged, reflecting the richness of diverse
|
||||
perspectives on what constitutes a good life. From the rural farmer's integration with the land to the developer's
|
||||
search for balance between technology and personal connection, each viewpoint validates that fulfillment, at its
|
||||
core, transcends material wealth. The spiritual leader and the activist highlight the importance of community and
|
||||
social justice, while the history teacher and the artist remind us of the lessons and narratives that shape our
|
||||
cultural and personal identities.
|
||||
|
||||
Ultimately, the good life seems to revolve around meaningful relationships, honoring our legacies while striving for
|
||||
progress, and nurturing both our inner selves and external communities. This dialogue demonstrates that despite our
|
||||
varied backgrounds and experiences, the quest for a good life binds us together, urging cooperation and empathy in
|
||||
our shared human journey.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from agent_framework import AgentRunUpdateEvent, ChatAgent, GroupChatBuilder, WorkflowOutputEvent
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
"""
|
||||
Sample: Group Chat Orchestration (manager-directed)
|
||||
|
||||
What it does:
|
||||
- Demonstrates the generic GroupChatBuilder with a language-model manager directing two agents.
|
||||
- The manager coordinates a researcher (chat completions) and a writer (responses API) to solve a task.
|
||||
- Uses the default group chat orchestration pipeline shared with Magentic.
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured for `OpenAIChatClient` and `OpenAIResponsesClient`.
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
researcher = ChatAgent(
|
||||
name="Researcher",
|
||||
description="Collects relevant background information.",
|
||||
instructions="Gather concise facts that help a teammate answer the question.",
|
||||
chat_client=OpenAIChatClient(model_id="gpt-4o-mini"),
|
||||
)
|
||||
|
||||
writer = ChatAgent(
|
||||
name="Writer",
|
||||
description="Synthesizes a polished answer using the gathered notes.",
|
||||
instructions="Compose clear and structured answers using any notes provided.",
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
)
|
||||
|
||||
workflow = (
|
||||
GroupChatBuilder()
|
||||
.set_prompt_based_manager(chat_client=OpenAIChatClient(), display_name="Coordinator")
|
||||
.participants(researcher=researcher, writer=writer)
|
||||
.build()
|
||||
)
|
||||
|
||||
task = "Outline the core considerations for planning a community hackathon, and finish with a concise action plan."
|
||||
|
||||
print("\nStarting Group Chat Workflow...\n")
|
||||
print(f"TASK: {task}\n")
|
||||
|
||||
final_response = None
|
||||
last_executor_id: str | None = None
|
||||
async for event in workflow.run_stream(task):
|
||||
if isinstance(event, AgentRunUpdateEvent):
|
||||
# Handle the streaming agent update as it's produced
|
||||
eid = event.executor_id
|
||||
if eid != last_executor_id:
|
||||
if last_executor_id is not None:
|
||||
print()
|
||||
print(f"{eid}:", end=" ", flush=True)
|
||||
last_executor_id = eid
|
||||
print(event.data, end="", flush=True)
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
final_response = getattr(event.data, "text", str(event.data))
|
||||
|
||||
if final_response:
|
||||
print("=" * 60)
|
||||
print("FINAL RESPONSE")
|
||||
print("=" * 60)
|
||||
print(final_response)
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+12
-8
@@ -2,8 +2,9 @@
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import ChatAgent, GroupChatBuilder, GroupChatStateSnapshot, WorkflowOutputEvent
|
||||
from agent_framework import ChatAgent, ChatMessage, GroupChatBuilder, GroupChatStateSnapshot, WorkflowOutputEvent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
@@ -12,7 +13,7 @@ logging.basicConfig(level=logging.INFO)
|
||||
Sample: Group Chat with Simple Speaker Selector Function
|
||||
|
||||
What it does:
|
||||
- Demonstrates the select_speakers() API for GroupChat orchestration
|
||||
- Demonstrates the set_select_speakers_func() API for GroupChat orchestration
|
||||
- Uses a pure Python function to control speaker selection based on conversation state
|
||||
- Alternates between researcher and writer agents in a simple round-robin pattern
|
||||
- Shows how to access conversation history, round index, and participant metadata
|
||||
@@ -84,7 +85,7 @@ async def main() -> None:
|
||||
# 2. Dict form - explicit names: .participants(researcher=researcher, writer=writer)
|
||||
workflow = (
|
||||
GroupChatBuilder()
|
||||
.select_speakers(select_next_speaker, display_name="Orchestrator")
|
||||
.set_select_speakers_func(select_next_speaker, display_name="Orchestrator")
|
||||
.participants([researcher, writer]) # Uses agent.name for participant names
|
||||
.build()
|
||||
)
|
||||
@@ -97,11 +98,14 @@ async def main() -> None:
|
||||
|
||||
async for event in workflow.run_stream(task):
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
final_message = event.data
|
||||
author = getattr(final_message, "author_name", "Unknown")
|
||||
text = getattr(final_message, "text", str(final_message))
|
||||
print(f"\n[{author}]\n{text}\n")
|
||||
print("-" * 80)
|
||||
conversation = cast(list[ChatMessage], event.data)
|
||||
if isinstance(conversation, list):
|
||||
print("\n===== Final Conversation =====\n")
|
||||
for msg in conversation:
|
||||
author = getattr(msg, "author_name", "Unknown")
|
||||
text = getattr(msg, "text", str(msg))
|
||||
print(f"[{author}]\n{text}\n")
|
||||
print("-" * 80)
|
||||
|
||||
print("\nWorkflow completed.")
|
||||
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
MAGENTIC_EVENT_TYPE_AGENT_DELTA,
|
||||
MAGENTIC_EVENT_TYPE_ORCHESTRATOR,
|
||||
AgentRunUpdateEvent,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
HostedCodeInterpreterTool,
|
||||
MagenticAgentDeltaEvent,
|
||||
MagenticAgentMessageEvent,
|
||||
MagenticBuilder,
|
||||
MagenticFinalResultEvent,
|
||||
MagenticOrchestratorMessageEvent,
|
||||
WorkflowOutputEvent,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
@@ -97,35 +98,30 @@ async def main() -> None:
|
||||
try:
|
||||
output: str | None = None
|
||||
async for event in workflow.run_stream(task):
|
||||
if isinstance(event, MagenticOrchestratorMessageEvent):
|
||||
print(f"\n[ORCH:{event.kind}]\n\n{getattr(event.message, 'text', '')}\n{'-' * 26}")
|
||||
elif isinstance(event, MagenticAgentDeltaEvent):
|
||||
if last_stream_agent_id != event.agent_id or not stream_line_open:
|
||||
if stream_line_open:
|
||||
print()
|
||||
print(f"\n[STREAM:{event.agent_id}]: ", end="", flush=True)
|
||||
last_stream_agent_id = event.agent_id
|
||||
stream_line_open = True
|
||||
if event.text:
|
||||
print(event.text, end="", flush=True)
|
||||
elif isinstance(event, MagenticAgentMessageEvent):
|
||||
if stream_line_open:
|
||||
print(" (final)")
|
||||
stream_line_open = False
|
||||
print()
|
||||
msg = event.message
|
||||
if msg is not None:
|
||||
response_text = (msg.text or "").replace("\n", " ")
|
||||
print(f"\n[AGENT:{event.agent_id}] {msg.role.value}\n\n{response_text}\n{'-' * 26}")
|
||||
elif isinstance(event, MagenticFinalResultEvent):
|
||||
print("\n" + "=" * 50)
|
||||
print("FINAL RESULT:")
|
||||
print("=" * 50)
|
||||
if event.message is not None:
|
||||
print(event.message.text)
|
||||
print("=" * 50)
|
||||
if isinstance(event, AgentRunUpdateEvent):
|
||||
props = event.data.additional_properties if event.data else None
|
||||
event_type = props.get("magentic_event_type") if props else None
|
||||
|
||||
if event_type == MAGENTIC_EVENT_TYPE_ORCHESTRATOR:
|
||||
kind = props.get("orchestrator_message_kind", "") if props else ""
|
||||
text = event.data.text if event.data else ""
|
||||
print(f"\n[ORCH:{kind}]\n\n{text}\n{'-' * 26}")
|
||||
elif event_type == MAGENTIC_EVENT_TYPE_AGENT_DELTA:
|
||||
agent_id = props.get("agent_id", event.executor_id) if props else event.executor_id
|
||||
if last_stream_agent_id != agent_id or not stream_line_open:
|
||||
if stream_line_open:
|
||||
print()
|
||||
print(f"\n[STREAM:{agent_id}]: ", end="", flush=True)
|
||||
last_stream_agent_id = agent_id
|
||||
stream_line_open = True
|
||||
if event.data and event.data.text:
|
||||
print(event.data.text, end="", flush=True)
|
||||
elif event.data and event.data.text:
|
||||
print(event.data.text, end="", flush=True)
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
output = str(event.data) if event.data is not None else None
|
||||
output_messages = cast(list[ChatMessage], event.data)
|
||||
if output_messages:
|
||||
output = output_messages[-1].text
|
||||
|
||||
if stream_line_open:
|
||||
print()
|
||||
|
||||
Reference in New Issue
Block a user