mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: (samples): adopt AzureOpenAIResponsesClient, reorganize orchestration examples, and fix workflow/orchestration bugs (#3873)
* adopt AzureOpenAIResponsesClient, reorganize orchestration examples, and fix workflow/orchestration bugs * Updates * add comment
This commit is contained in:
committed by
GitHub
Unverified
parent
8457533c69
commit
1b10b051fd
@@ -0,0 +1,129 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
AgentResponseUpdate,
|
||||
Message,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Group Chat with Agent-Based Manager
|
||||
|
||||
What it does:
|
||||
- Demonstrates the new set_manager() API for agent-based coordination
|
||||
- Manager is a full Agent with access to tools, context, and observability
|
||||
- Coordinates a researcher and writer agent to solve tasks collaboratively
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for AzureOpenAIResponsesClient
|
||||
"""
|
||||
|
||||
ORCHESTRATOR_AGENT_INSTRUCTIONS = """
|
||||
You coordinate a team conversation to solve the user's task.
|
||||
|
||||
Guidelines:
|
||||
- Start with Researcher to gather information
|
||||
- Then have Writer synthesize the final answer
|
||||
- Only finish after both have contributed meaningfully
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Create a chat client using Azure OpenAI and Azure CLI credentials for all agents
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Orchestrator agent that manages the conversation
|
||||
# Note: This agent (and the underlying chat client) must support structured outputs.
|
||||
# The group chat workflow relies on this to parse the orchestrator's decisions.
|
||||
# `response_format` is set internally by the GroupChat workflow when the agent is invoked.
|
||||
orchestrator_agent = Agent(
|
||||
name="Orchestrator",
|
||||
description="Coordinates multi-agent collaboration by selecting speakers",
|
||||
instructions=ORCHESTRATOR_AGENT_INSTRUCTIONS,
|
||||
client=client,
|
||||
)
|
||||
|
||||
# Participant agents
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
description="Collects relevant background information",
|
||||
instructions="Gather concise facts that help a teammate answer the question.",
|
||||
client=client,
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
description="Synthesizes polished answers from gathered information",
|
||||
instructions="Compose clear and structured answers using any notes provided.",
|
||||
client=client,
|
||||
)
|
||||
|
||||
# Build the group chat workflow
|
||||
# termination_condition: stop after 4 assistant messages
|
||||
# (The agent orchestrator will intelligently decide when to end before this limit but just in case)
|
||||
# intermediate_outputs=True: Enable intermediate outputs to observe the conversation as it unfolds
|
||||
# (Intermediate outputs will be emitted as WorkflowOutputEvent events)
|
||||
workflow = (
|
||||
GroupChatBuilder(
|
||||
participants=[researcher, writer],
|
||||
termination_condition=lambda messages: sum(1 for msg in messages if msg.role == "assistant") >= 4,
|
||||
intermediate_outputs=True,
|
||||
orchestrator_agent=orchestrator_agent,
|
||||
)
|
||||
# Set a hard termination condition: stop after 4 assistant messages
|
||||
# The agent orchestrator will intelligently decide when to end before this limit but just in case
|
||||
.with_termination_condition(lambda messages: sum(1 for msg in messages if msg.role == "assistant") >= 4)
|
||||
.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)
|
||||
|
||||
# Track current speaker for readable streaming output.
|
||||
pending_speaker: str | None = None
|
||||
current_speaker: str | None = None
|
||||
async for event in workflow.run(task, stream=True):
|
||||
if event.type != "output":
|
||||
continue
|
||||
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
if data.author_name:
|
||||
pending_speaker = data.author_name
|
||||
if not data.text:
|
||||
continue
|
||||
|
||||
speaker = data.author_name or pending_speaker or "assistant"
|
||||
if speaker != current_speaker:
|
||||
if current_speaker is not None:
|
||||
print("\n")
|
||||
print(f"{speaker}:", end=" ", flush=True)
|
||||
current_speaker = speaker
|
||||
print(data.text, end="", flush=True)
|
||||
continue
|
||||
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[Message], data)
|
||||
print("\n" + "=" * 80)
|
||||
print("\nFinal Conversation Transcript:\n")
|
||||
for message in outputs:
|
||||
print(f"{message.author_name or message.role}: {message.text}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from typing import Annotated, cast
|
||||
|
||||
from agent_framework import (
|
||||
Content,
|
||||
Message,
|
||||
WorkflowEvent,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder, GroupChatState
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Group Chat Workflow with Tool Approval Requests
|
||||
|
||||
This sample demonstrates how to use GroupChatBuilder with tools that require human
|
||||
approval before execution. A group of specialized agents collaborate on a task, and
|
||||
sensitive tool calls trigger human-in-the-loop approval.
|
||||
|
||||
This sample works as follows:
|
||||
1. A GroupChatBuilder workflow is created with multiple specialized agents.
|
||||
2. A selector function determines which agent speaks next based on conversation state.
|
||||
3. Agents collaborate on a software deployment task.
|
||||
4. When the deployment agent tries to deploy to production, it triggers an approval request.
|
||||
5. The sample simulates human approval and the workflow completes.
|
||||
|
||||
Purpose:
|
||||
Show how tool call approvals integrate with multi-agent group chat workflows where
|
||||
different agents have different levels of tool access.
|
||||
|
||||
Demonstrate:
|
||||
- Using set_select_speakers_func with agents that have approval-required tools.
|
||||
- Handling request_info events (type='request_info') in group chat scenarios.
|
||||
- Multi-round group chat with tool approval interruption and resumption.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- OpenAI or Azure OpenAI configured with the required environment variables.
|
||||
- Basic familiarity with GroupChatBuilder and streaming workflow events.
|
||||
"""
|
||||
|
||||
|
||||
# 1. Define tools for different agents
|
||||
# NOTE: approval_mode="never_require" is for sample brevity.
|
||||
# Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py
|
||||
# and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def run_tests(test_suite: Annotated[str, "Name of the test suite to run"]) -> str:
|
||||
"""Run automated tests for the application."""
|
||||
return f"Test suite '{test_suite}' completed: 47 passed, 0 failed, 0 skipped"
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def check_staging_status() -> str:
|
||||
"""Check the current status of the staging environment."""
|
||||
return "Staging environment: Healthy, Version 2.3.0 deployed, All services running"
|
||||
|
||||
|
||||
@tool(approval_mode="always_require")
|
||||
def deploy_to_production(
|
||||
version: Annotated[str, "The version to deploy"],
|
||||
components: Annotated[str, "Comma-separated list of components to deploy"],
|
||||
) -> str:
|
||||
"""Deploy specified components to production. Requires human approval."""
|
||||
return f"Production deployment complete: Version {version}, Components: {components}"
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def create_rollback_plan(version: Annotated[str, "The version being deployed"]) -> str:
|
||||
"""Create a rollback plan for the deployment."""
|
||||
return (
|
||||
f"Rollback plan created for version {version}: "
|
||||
"Automated rollback to v2.2.0 if health checks fail within 5 minutes"
|
||||
)
|
||||
|
||||
|
||||
# 2. Define the speaker selector function
|
||||
def select_next_speaker(state: GroupChatState) -> str:
|
||||
"""Select the next speaker based on the conversation flow.
|
||||
|
||||
This simple selector follows a predefined flow:
|
||||
1. QA Engineer runs tests
|
||||
2. DevOps Engineer checks staging and creates rollback plan
|
||||
3. DevOps Engineer deploys to production (triggers approval)
|
||||
"""
|
||||
if not state.conversation:
|
||||
raise RuntimeError("Conversation is empty; cannot select next speaker.")
|
||||
|
||||
if len(state.conversation) == 1:
|
||||
return "QAEngineer" # First speaker
|
||||
|
||||
return "DevOpsEngineer" # Subsequent speakers
|
||||
|
||||
|
||||
async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str, Content] | None:
|
||||
"""Process events from the workflow stream to capture human feedback requests."""
|
||||
requests: dict[str, Content] = {}
|
||||
async for event in stream:
|
||||
if event.type == "request_info" and isinstance(event.data, Content):
|
||||
# We are only expecting tool approval requests in this sample
|
||||
requests[event.request_id] = event.data
|
||||
elif event.type == "output":
|
||||
# The output of the workflow comes from the orchestrator and it's a list of messages
|
||||
print("\n" + "=" * 60)
|
||||
print("Workflow summary:")
|
||||
outputs = cast(list[Message], event.data)
|
||||
for msg in outputs:
|
||||
speaker = msg.author_name or msg.role
|
||||
print(f"[{speaker}]: {msg.text}")
|
||||
|
||||
responses: dict[str, Content] = {}
|
||||
if requests:
|
||||
for request_id, request in requests.items():
|
||||
if request.type == "function_approval_request":
|
||||
print("\n[APPROVAL REQUIRED]")
|
||||
print(f" Tool: {request.function_call.name}") # type: ignore
|
||||
print(f" Arguments: {request.function_call.arguments}") # type: ignore
|
||||
print(f"Simulating human approval for: {request.function_call.name}") # type: ignore
|
||||
# Create approval response
|
||||
responses[request_id] = request.to_function_approval_response(approved=True)
|
||||
|
||||
return responses if responses else None
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# 3. Create specialized agents
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
qa_engineer = client.as_agent(
|
||||
name="QAEngineer",
|
||||
instructions=(
|
||||
"You are a QA engineer responsible for running tests before deployment. "
|
||||
"Run the appropriate test suites and report results clearly."
|
||||
),
|
||||
tools=[run_tests],
|
||||
)
|
||||
|
||||
devops_engineer = client.as_agent(
|
||||
name="DevOpsEngineer",
|
||||
instructions=(
|
||||
"You are a DevOps engineer responsible for deployments. First check staging "
|
||||
"status and create a rollback plan, then proceed with production deployment. "
|
||||
"Always ensure safety measures are in place before deploying."
|
||||
),
|
||||
tools=[check_staging_status, create_rollback_plan, deploy_to_production],
|
||||
)
|
||||
|
||||
# 4. Build a group chat workflow with the selector function
|
||||
# max_rounds=4: Set a hard limit to 4 rounds
|
||||
# First round: QAEngineer speaks
|
||||
# Second round: DevOpsEngineer speaks (check staging + create rollback)
|
||||
# Third round: DevOpsEngineer speaks with an approval request (deploy to production)
|
||||
# Fourth round: DevOpsEngineer speaks again after approval
|
||||
workflow = GroupChatBuilder(
|
||||
participants=[qa_engineer, devops_engineer],
|
||||
max_rounds=4,
|
||||
selection_func=select_next_speaker,
|
||||
).build()
|
||||
|
||||
# 5. Start the workflow
|
||||
print("Starting group chat workflow for software deployment...")
|
||||
print(f"Agents: {[qa_engineer.name, devops_engineer.name]}")
|
||||
print("-" * 60)
|
||||
|
||||
# Initiate the first run of the workflow.
|
||||
# Runs are not isolated; state is preserved across multiple calls to run.
|
||||
stream = workflow.run(
|
||||
"We need to deploy version 2.4.0 to production. Please coordinate the deployment.", stream=True
|
||||
)
|
||||
|
||||
pending_responses = await process_event_stream(stream)
|
||||
while pending_responses is not None:
|
||||
# Run the workflow until there is no more human feedback to provide,
|
||||
# in which case this workflow completes.
|
||||
stream = workflow.run(stream=True, responses=pending_responses)
|
||||
pending_responses = await process_event_stream(stream)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
Starting group chat workflow for software deployment...
|
||||
Agents: QA Engineer, DevOps Engineer
|
||||
------------------------------------------------------------
|
||||
|
||||
[QAEngineer]: Running the integration test suite to verify the application
|
||||
before deployment... Test suite 'integration' completed: 47 passed, 0 failed.
|
||||
All tests passing - ready for deployment.
|
||||
|
||||
[DevOpsEngineer]: Checking staging environment status... Staging is healthy
|
||||
with version 2.3.0. Creating rollback plan for version 2.4.0... Rollback plan
|
||||
created with automated rollback to v2.2.0 if health checks fail.
|
||||
|
||||
[APPROVAL REQUIRED]
|
||||
Tool: deploy_to_production
|
||||
Arguments: {"version": "2.4.0", "components": "api,web,worker"}
|
||||
|
||||
============================================================
|
||||
Human review required for production deployment!
|
||||
In a real scenario, you would review the deployment details here.
|
||||
Simulating approval for demo purposes...
|
||||
============================================================
|
||||
|
||||
[DevOpsEngineer]: Production deployment complete! Version 2.4.0 has been
|
||||
successfully deployed with components: api, web, worker.
|
||||
|
||||
------------------------------------------------------------
|
||||
Deployment workflow completed successfully!
|
||||
All agents have finished their tasks.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
AgentResponseUpdate,
|
||||
Message,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder
|
||||
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:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for AzureOpenAIResponsesClient
|
||||
"""
|
||||
|
||||
|
||||
def _get_chat_client() -> AzureOpenAIResponsesClient:
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
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 = Agent(
|
||||
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.
|
||||
""",
|
||||
client=_get_chat_client(),
|
||||
)
|
||||
|
||||
farmer = Agent(
|
||||
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)
|
||||
""",
|
||||
client=_get_chat_client(),
|
||||
)
|
||||
|
||||
developer = Agent(
|
||||
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)
|
||||
""",
|
||||
client=_get_chat_client(),
|
||||
)
|
||||
|
||||
teacher = Agent(
|
||||
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)
|
||||
""",
|
||||
client=_get_chat_client(),
|
||||
)
|
||||
|
||||
activist = Agent(
|
||||
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)
|
||||
""",
|
||||
client=_get_chat_client(),
|
||||
)
|
||||
|
||||
spiritual_leader = Agent(
|
||||
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)
|
||||
""",
|
||||
client=_get_chat_client(),
|
||||
)
|
||||
|
||||
artist = Agent(
|
||||
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)
|
||||
""",
|
||||
client=_get_chat_client(),
|
||||
)
|
||||
|
||||
immigrant = Agent(
|
||||
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)
|
||||
""",
|
||||
client=_get_chat_client(),
|
||||
)
|
||||
|
||||
doctor = Agent(
|
||||
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)
|
||||
""",
|
||||
client=_get_chat_client(),
|
||||
)
|
||||
|
||||
# termination_condition: stop after 10 assistant messages
|
||||
# intermediate_outputs=True: Enable intermediate outputs to observe the conversation as it unfolds
|
||||
# (Intermediate outputs will be emitted as WorkflowOutputEvent events)
|
||||
workflow = (
|
||||
GroupChatBuilder(
|
||||
participants=[farmer, developer, teacher, activist, spiritual_leader, artist, immigrant, doctor],
|
||||
termination_condition=lambda messages: sum(1 for msg in messages if msg.role == "assistant") >= 10,
|
||||
intermediate_outputs=True,
|
||||
orchestrator_agent=moderator,
|
||||
)
|
||||
.with_termination_condition(lambda messages: sum(1 for msg in messages if msg.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")
|
||||
|
||||
# Track current speaker for readable streaming output.
|
||||
pending_speaker: str | None = None
|
||||
current_speaker: str | None = None
|
||||
async for event in workflow.run(f"Please begin the discussion on: {topic}", stream=True):
|
||||
if event.type != "output":
|
||||
continue
|
||||
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
if data.author_name:
|
||||
pending_speaker = data.author_name
|
||||
if not data.text:
|
||||
continue
|
||||
|
||||
speaker = data.author_name or pending_speaker or "assistant"
|
||||
if speaker != current_speaker:
|
||||
if current_speaker is not None:
|
||||
print("\n")
|
||||
print(f"{speaker}:", end=" ", flush=True)
|
||||
current_speaker = speaker
|
||||
print(data.text, end="", flush=True)
|
||||
continue
|
||||
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[Message], data)
|
||||
print("\n" + "=" * 80)
|
||||
print("\nFinal Conversation Transcript:\n")
|
||||
for message in outputs:
|
||||
print(f"{message.author_name or message.role}: {message.text}\n")
|
||||
|
||||
"""
|
||||
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
|
||||
================================================================================
|
||||
|
||||
[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?
|
||||
|
||||
[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.
|
||||
|
||||
[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.
|
||||
|
||||
[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?
|
||||
|
||||
[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?
|
||||
|
||||
[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?
|
||||
|
||||
================================================================================
|
||||
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())
|
||||
@@ -0,0 +1,174 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""
|
||||
Sample: Request Info with GroupChatBuilder
|
||||
|
||||
This sample demonstrates using the `.with_request_info()` method to pause a
|
||||
GroupChatBuilder workflow BEFORE specific participants speak. By using the
|
||||
`agents=` filter parameter, you can target only certain participants rather
|
||||
than pausing before every turn.
|
||||
|
||||
Purpose:
|
||||
Show how to use the request info API with selective filtering to pause before
|
||||
specific participants speak, allowing human input to steer their response.
|
||||
|
||||
Demonstrate:
|
||||
- Configuring request info with `.with_request_info(agents=[...])`
|
||||
- Using agent filtering to reduce interruptions
|
||||
- Steering agent behavior with pre-agent human input
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables
|
||||
- Authentication via azure-identity (run az login before executing)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
AgentExecutorResponse,
|
||||
Message,
|
||||
WorkflowEvent,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import AgentRequestInfoResponse, GroupChatBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
|
||||
async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str, AgentRequestInfoResponse] | None:
|
||||
"""Process events from the workflow stream to capture human feedback requests."""
|
||||
|
||||
requests: dict[str, AgentExecutorResponse] = {}
|
||||
async for event in stream:
|
||||
if event.type == "request_info" and isinstance(event.data, AgentExecutorResponse):
|
||||
requests[event.request_id] = event.data
|
||||
|
||||
if event.type == "output":
|
||||
# The output of the workflow comes from the orchestrator and it's a list of messages
|
||||
print("\n" + "=" * 60)
|
||||
print("DISCUSSION COMPLETE")
|
||||
print("=" * 60)
|
||||
print("Final discussion summary:")
|
||||
# To make the type checker happy, we cast event.data to the expected type
|
||||
outputs = cast(list[Message], event.data)
|
||||
for msg in outputs:
|
||||
speaker = msg.author_name or msg.role
|
||||
print(f"[{speaker}]: {msg.text}")
|
||||
|
||||
responses: dict[str, AgentRequestInfoResponse] = {}
|
||||
if requests:
|
||||
for request_id, request in requests.items():
|
||||
# Display pre-agent context for human input
|
||||
print("\n" + "-" * 40)
|
||||
print("INPUT REQUESTED")
|
||||
print(
|
||||
f"Agent {request.executor_id} just responded with: '{request.agent_response.text}'. "
|
||||
"Please provide your feedback."
|
||||
)
|
||||
print("-" * 40)
|
||||
if request.full_conversation:
|
||||
print("Conversation context:")
|
||||
recent = (
|
||||
request.full_conversation[-2:] if len(request.full_conversation) > 2 else request.full_conversation
|
||||
)
|
||||
for msg in recent:
|
||||
name = msg.author_name or msg.role
|
||||
text = (msg.text or "")[:150]
|
||||
print(f" [{name}]: {text}...")
|
||||
print("-" * 40)
|
||||
|
||||
# Get human input to steer the agent
|
||||
user_input = input(f"Feedback for {request.executor_id} (or 'skip' to approve): ") # noqa: ASYNC250
|
||||
if user_input.lower() == "skip":
|
||||
user_input = AgentRequestInfoResponse.approve()
|
||||
else:
|
||||
user_input = AgentRequestInfoResponse.from_strings([user_input])
|
||||
|
||||
responses[request_id] = user_input
|
||||
|
||||
return responses if responses else None
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create agents for a group discussion
|
||||
optimist = client.as_agent(
|
||||
name="optimist",
|
||||
instructions=(
|
||||
"You are an optimistic team member. You see opportunities and potential "
|
||||
"in ideas. Engage constructively with the discussion, building on others' "
|
||||
"points while maintaining a positive outlook. Keep responses to 2-3 sentences."
|
||||
),
|
||||
)
|
||||
|
||||
pragmatist = client.as_agent(
|
||||
name="pragmatist",
|
||||
instructions=(
|
||||
"You are a pragmatic team member. You focus on practical implementation "
|
||||
"and realistic timelines. Sometimes you disagree with overly optimistic views. "
|
||||
"Keep responses to 2-3 sentences."
|
||||
),
|
||||
)
|
||||
|
||||
creative = client.as_agent(
|
||||
name="creative",
|
||||
instructions=(
|
||||
"You are a creative team member. You propose innovative solutions and "
|
||||
"think outside the box. You may suggest alternatives to conventional approaches. "
|
||||
"Keep responses to 2-3 sentences."
|
||||
),
|
||||
)
|
||||
|
||||
# Orchestrator coordinates the discussion
|
||||
orchestrator = client.as_agent(
|
||||
name="orchestrator",
|
||||
instructions=(
|
||||
"You are a discussion manager coordinating a team conversation between participants. "
|
||||
"Your job is to select who speaks next.\n\n"
|
||||
"RULES:\n"
|
||||
"1. Rotate through ALL participants - do not favor any single participant\n"
|
||||
"2. Each participant should speak at least once before any participant speaks twice\n"
|
||||
"3. Continue for at least 5 rounds before ending the discussion\n"
|
||||
"4. Do NOT select the same participant twice in a row"
|
||||
),
|
||||
)
|
||||
|
||||
# Build workflow with request info enabled
|
||||
# Using agents= filter to only pause before pragmatist speaks (not every turn)
|
||||
# max_rounds=6: Limit to 6 rounds
|
||||
workflow = (
|
||||
GroupChatBuilder(
|
||||
participants=[optimist, pragmatist, creative],
|
||||
max_rounds=6,
|
||||
orchestrator_agent=orchestrator,
|
||||
)
|
||||
.with_request_info(agents=[pragmatist]) # Only pause before pragmatist speaks
|
||||
.build()
|
||||
)
|
||||
|
||||
# Initiate the first run of the workflow.
|
||||
# Runs are not isolated; state is preserved across multiple calls to run.
|
||||
stream = workflow.run(
|
||||
"Discuss how our team should approach adopting AI tools for productivity. "
|
||||
"Consider benefits, risks, and implementation strategies.",
|
||||
stream=True,
|
||||
)
|
||||
|
||||
pending_responses = await process_event_stream(stream)
|
||||
while pending_responses is not None:
|
||||
# Run the workflow until there is no more human feedback to provide,
|
||||
# in which case this workflow completes.
|
||||
stream = workflow.run(stream=True, responses=pending_responses)
|
||||
pending_responses = await process_event_stream(stream)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
AgentResponseUpdate,
|
||||
Message,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder, GroupChatState
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Group Chat with a round-robin speaker selector
|
||||
|
||||
What it does:
|
||||
- Demonstrates the selection_func parameter for GroupChat orchestration
|
||||
- Uses a pure Python function to control speaker selection based on conversation state
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for AzureOpenAIResponsesClient
|
||||
"""
|
||||
|
||||
|
||||
def round_robin_selector(state: GroupChatState) -> str:
|
||||
"""A round-robin selector function that picks the next speaker based on the current round index."""
|
||||
|
||||
participant_names = list(state.participants.keys())
|
||||
return participant_names[state.current_round % len(participant_names)]
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Create a chat client using Azure OpenAI and Azure CLI credentials for all agents
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Participant agents
|
||||
expert = Agent(
|
||||
name="PythonExpert",
|
||||
instructions=(
|
||||
"You are an expert in Python in a workgroup. "
|
||||
"Your job is to answer Python related questions and refine your answer "
|
||||
"based on feedback from all the other participants."
|
||||
),
|
||||
client=client,
|
||||
)
|
||||
|
||||
verifier = Agent(
|
||||
name="AnswerVerifier",
|
||||
instructions=(
|
||||
"You are a programming expert in a workgroup. "
|
||||
f"Your job is to review the answer provided by {expert.name} and point "
|
||||
"out statements that are technically true but practically dangerous."
|
||||
"If there is nothing woth pointing out, respond with 'The answer looks good to me.'"
|
||||
),
|
||||
client=client,
|
||||
)
|
||||
|
||||
clarifier = Agent(
|
||||
name="AnswerClarifier",
|
||||
instructions=(
|
||||
"You are an accessibility expert in a workgroup. "
|
||||
f"Your job is to review the answer provided by {expert.name} and point "
|
||||
"out jargons or complex terms that may be difficult for a beginner to understand."
|
||||
"If there is nothing worth pointing out, respond with 'The answer looks clear to me.'"
|
||||
),
|
||||
client=client,
|
||||
)
|
||||
|
||||
skeptic = Agent(
|
||||
name="Skeptic",
|
||||
instructions=(
|
||||
"You are a devil's advocate in a workgroup. "
|
||||
f"Your job is to review the answer provided by {expert.name} and point "
|
||||
"out caveats, exceptions, and alternative perspectives."
|
||||
"If there is nothing worth pointing out, respond with 'I have no further questions.'"
|
||||
),
|
||||
client=client,
|
||||
)
|
||||
|
||||
# Build the group chat workflow
|
||||
# termination_condition: stop after 6 messages (user task + one full rounds + 1)
|
||||
# One round is expert -> verifier -> clarifier -> skeptic, after which the expert gets to respond again.
|
||||
# This will end the conversation after the expert has spoken 2 times (one iteration loop)
|
||||
# Note: it's possible that the expert gets it right the first time and the other participants
|
||||
# have nothing to add, but for demo purposes we want to see at least one full round of interaction.
|
||||
# intermediate_outputs=True: Enable intermediate outputs to observe the conversation as it unfolds
|
||||
# (Intermediate outputs will be emitted as WorkflowOutputEvent events)
|
||||
workflow = (
|
||||
GroupChatBuilder(
|
||||
participants=[expert, verifier, clarifier, skeptic],
|
||||
termination_condition=lambda conversation: len(conversation) >= 6,
|
||||
intermediate_outputs=True,
|
||||
selection_func=round_robin_selector,
|
||||
)
|
||||
# Set a hard termination condition: stop after 6 messages (user task + one full rounds + 1)
|
||||
# One round is expert -> verifier -> clarifier -> skeptic, after which the expert gets to respond again.
|
||||
# This will end the conversation after the expert has spoken 2 times (one iteration loop)
|
||||
# Note: it's possible that the expert gets it right the first time and the other participants
|
||||
# have nothing to add, but for demo purposes we want to see at least one full round of interaction.
|
||||
.with_termination_condition(lambda conversation: len(conversation) >= 6)
|
||||
.build()
|
||||
)
|
||||
|
||||
task = "How does Python’s Protocol differ from abstract base classes?"
|
||||
|
||||
print("\nStarting Group Chat with round-robin speaker selector...\n")
|
||||
print(f"TASK: {task}\n")
|
||||
print("=" * 80)
|
||||
|
||||
# Keep track of the last response to format output nicely in streaming mode
|
||||
last_response_id: str | None = None
|
||||
async for event in workflow.run(task, stream=True):
|
||||
if event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
rid = data.response_id
|
||||
if rid != last_response_id:
|
||||
if last_response_id is not None:
|
||||
print("\n")
|
||||
print(f"{data.author_name}:", end=" ", flush=True)
|
||||
last_response_id = rid
|
||||
print(data.text, end="", flush=True)
|
||||
elif event.type == "output":
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[Message], event.data)
|
||||
print("\n" + "=" * 80)
|
||||
print("\nFinal Conversation Transcript:\n")
|
||||
for message in outputs:
|
||||
print(f"{message.author_name or message.role}: {message.text}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import Agent
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Group Chat Orchestration
|
||||
|
||||
What it does:
|
||||
- Demonstrates the generic GroupChatBuilder with a agent orchestrator directing two agents.
|
||||
- The orchestrator coordinates a researcher (chat completions) and a writer (responses API) to solve a task.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for `AzureOpenAIResponsesClient`.
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
researcher = Agent(
|
||||
name="Researcher",
|
||||
description="Collects relevant background information.",
|
||||
instructions="Gather concise facts that help a teammate answer the question.",
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
description="Synthesizes a polished answer using the gathered notes.",
|
||||
instructions="Compose clear and structured answers using any notes provided.",
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
# intermediate_outputs=True: Enable intermediate outputs to observe the conversation as it unfolds
|
||||
# (Intermediate outputs will be emitted as WorkflowOutputEvent events)
|
||||
workflow = GroupChatBuilder(
|
||||
participants=[researcher, writer],
|
||||
intermediate_outputs=True,
|
||||
orchestrator_agent=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="Orchestrator",
|
||||
instructions="You coordinate a team conversation to solve the user's task.",
|
||||
),
|
||||
).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"Input: {task}\n")
|
||||
|
||||
try:
|
||||
workflow_agent = workflow.as_agent(name="GroupChatWorkflowAgent")
|
||||
agent_result = await workflow_agent.run(task)
|
||||
|
||||
if agent_result.messages:
|
||||
# The output should contain a message from the researcher, a message from the writer,
|
||||
# and a final synthesized answer from the orchestrator.
|
||||
print("\n===== as_agent() Transcript =====")
|
||||
for i, msg in enumerate(agent_result.messages, start=1):
|
||||
role_value = getattr(msg.role, "value", msg.role)
|
||||
speaker = msg.author_name or role_value
|
||||
print(f"{'-' * 50}\n{i:02d} [{speaker}]\n{msg.text}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Workflow execution failed: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user