test
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
Python - Merge - Tests / paths-filter (push) Has been cancelled
Python - Merge - Tests / Python Tests - Core (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / Python Tests - Azure AI (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / python-integration-tests-check (push) Has been cancelled
Python - Lab Tests / paths-filter (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.14) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.14) (push) Has been cancelled
Check .md links / markdown-link-check (push) Has been cancelled

This commit is contained in:
2026-01-24 03:05:12 +11:00
parent f78f2388b3
commit 539852f81c
2584 changed files with 287471 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
# Semantic Kernel → Microsoft Agent Framework Migration Samples
This gallery helps Semantic Kernel (SK) developers move to the Microsoft Agent Framework (AF) with minimal guesswork. Each script pairs SK code with its AF equivalent so you can compare primitives, tooling, and orchestration patterns side by side while you migrate production workloads.
## Whats Included
## Whats Included
### Chat completion parity
- [01_basic_chat_completion.py](chat_completion/01_basic_chat_completion.py) — Minimal SK `ChatCompletionAgent` and AF `ChatAgent` conversation.
- [02_chat_completion_with_tool.py](chat_completion/02_chat_completion_with_tool.py) — Adds a simple tool/function call in both SDKs.
- [03_chat_completion_thread_and_stream.py](chat_completion/03_chat_completion_thread_and_stream.py) — Demonstrates thread reuse and streaming prompts.
### Azure AI agent parity
- [01_basic_azure_ai_agent.py](azure_ai_agent/01_basic_azure_ai_agent.py) — Create and run an Azure AI agent end to end.
- [02_azure_ai_agent_with_code_interpreter.py](azure_ai_agent/02_azure_ai_agent_with_code_interpreter.py) — Enable hosted code interpreter/tool execution.
- [03_azure_ai_agent_threads_and_followups.py](azure_ai_agent/03_azure_ai_agent_threads_and_followups.py) — Persist threads and follow-ups across invocations.
### OpenAI Assistants API parity
- [01_basic_openai_assistant.py](openai_assistant/01_basic_openai_assistant.py) — Baseline assistant comparison.
- [02_openai_assistant_with_code_interpreter.py](openai_assistant/02_openai_assistant_with_code_interpreter.py) — Code interpreter tool usage.
- [03_openai_assistant_function_tool.py](openai_assistant/03_openai_assistant_function_tool.py) — Custom function tooling.
### OpenAI Responses API parity
- [01_basic_responses_agent.py](openai_responses/01_basic_responses_agent.py) — Basic responses agent migration.
- [02_responses_agent_with_tool.py](openai_responses/02_responses_agent_with_tool.py) — Tool-augmented responses workflows.
- [03_responses_agent_structured_output.py](openai_responses/03_responses_agent_structured_output.py) — Structured JSON output alignment.
### Copilot Studio parity
- [01_basic_copilot_studio_agent.py](copilot_studio/01_basic_copilot_studio_agent.py) — Minimal Copilot Studio agent invocation.
- [02_copilot_studio_streaming.py](copilot_studio/02_copilot_studio_streaming.py) — Streaming responses from Copilot Studio agents.
### Orchestrations
- [sequential.py](orchestrations/sequential.py) — Step-by-step SK Team → AF `SequentialBuilder` migration.
- [concurrent_basic.py](orchestrations/concurrent_basic.py) — Concurrent orchestration parity.
- [group_chat.py](orchestrations/group_chat.py) — Group chat coordination with an LLM-backed manager in both SDKs.
- [handoff.py](orchestrations/handoff.py) - Handoff coordination between agents.
- [magentic.py](orchestrations/magentic.py) — Magentic Team orchestration vs. AF builder wiring.
### Processes
- [fan_out_fan_in_process.py](processes/fan_out_fan_in_process.py) — Fan-out/fan-in comparison between SK Process Framework and AF workflows.
- [nested_process.py](processes/nested_process.py) — Nested process orchestration vs. AF sub-workflows.
Each script is fully async and the `main()` routine runs both implementations back to back so you can observe their outputs in a single execution.
## Prerequisites
- Python 3.10 or later.
- Access to the necessary model endpoints (Azure OpenAI, OpenAI, Azure AI, Copilot Studio, etc.).
- Installed SDKs: `semantic-kernel` and the Microsoft Agent Framework (`pip install semantic-kernel agent-framework`), or the repos editable packages if you are developing locally.
- Service credentials exposed through environment variables (for example `OPENAI_API_KEY`, `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_KEY`, or Copilot Studio auth settings).
## Running Single-Agent Samples
From the repository root:
```
python samples/semantic-kernel-migration/chat_completion/01_basic_chat_completion.py
```
Every script accepts no CLI arguments and will first call the SK implementation, followed by the AF version. Adjust the prompt or credentials inside the file as necessary before running.
## Running Orchestration & Workflow Samples
Advanced comparisons are split between `samantic-kernel-migration/orchestrations` (Sequential, Concurrent, Magentic) and `samantic-kernel-migration/processes` (fan-out/fan-in, nested). You can run them directly, or isolate dependencies in a throwaway virtual environment:
```
cd samples/semantic-kernel-migration
uv venv --python 3.10 .venv-migration
source .venv-migration/bin/activate
uv pip install semantic-kernel agent-framework
uv run python orchestrations/sequential.py
uv run python processes/fan_out_fan_in_process.py
```
Swap the script path for any other workflow or process sample. Deactivate the sandbox with `deactivate` when you are finished.
## Tips for Migration
- Keep the original SK sample open while iterating on the AF equivalent; the code is intentionally formatted so you can copy/paste across SDKs.
- Threads/conversation state are explicit in AF. When porting SK code that relies on implicit thread reuse, call `agent.get_new_thread()` and pass it into each `run`/`run_stream` call.
- Tools map cleanly: SK `@kernel_function` plugins translate to AF `@ai_function` callables. Hosted tools (code interpreter, web search, MCP) are available only in AF—introduce them once parity is achieved.
- For multi-agent orchestration, AF workflows expose checkpoints and resume capabilities that SK Process/Team abstractions do not. Use the workflow samples as a blueprint when modernizing complex agent graphs.

View File

@@ -0,0 +1,51 @@
# Copyright (c) Microsoft. All rights reserved.
"""Create an Azure AI agent using both Semantic Kernel and Agent Framework.
Prerequisites:
- Azure AI agent resource with a deployed model.
- Logged-in Azure CLI or other credential supported by AzureCliCredential.
"""
import asyncio
async def run_semantic_kernel() -> None:
from azure.identity.aio import AzureCliCredential
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings
async with AzureCliCredential() as credential, AzureAIAgent.create_client(credential=credential) as client:
settings = AzureAIAgentSettings() # Reads env vars for region/deployment.
# SK builds the remote agent definition then wraps it with AzureAIAgent.
definition = await client.agents.as_agent(
model=settings.model_deployment_name,
name="Support",
instructions="Answer customer questions in one paragraph.",
)
agent = AzureAIAgent(client=client, definition=definition)
response = await agent.get_response("How do I upgrade my plan?")
print("[SK]", response.message.content)
async def run_agent_framework() -> None:
from agent_framework.azure import AzureAIAgentClient
from azure.identity.aio import AzureCliCredential
async with (
AzureCliCredential() as credential,
AzureAIAgentClient(credential=credential).as_agent(
name="Support",
instructions="Answer customer questions in one paragraph.",
) as agent,
):
# AF client returns an asynchronous context manager for remote agents.
reply = await agent.run("How do I upgrade my plan?")
print("[AF]", reply.text)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,58 @@
# Copyright (c) Microsoft. All rights reserved.
"""Enable the hosted code interpreter for Azure AI agents in SK and AF.
The Azure AI service natively executes the code interpreter tool. Provide the
resource details via AzureAIAgentSettings (SK) or environment variables consumed
by AzureAIAgentClient (AF).
"""
import asyncio
async def run_semantic_kernel() -> None:
from azure.identity.aio import AzureCliCredential
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings
async with AzureCliCredential() as credential, AzureAIAgent.create_client(credential=credential) as client:
settings = AzureAIAgentSettings()
# Register the hosted code interpreter tool with the remote agent.
definition = await client.agents.as_agent(
model=settings.model_deployment_name,
name="Analyst",
instructions="Use the code interpreter for numeric work.",
tools=[{"type": "code_interpreter"}],
)
agent = AzureAIAgent(client=client, definition=definition)
response = await agent.get_response(
"Use Python to compute 42 ** 2 and explain the result.",
)
print("[SK]", response.message.content)
async def run_agent_framework() -> None:
from agent_framework.azure import AzureAIAgentClient, HostedCodeInterpreterTool
from azure.identity.aio import AzureCliCredential
async with (
AzureCliCredential() as credential,
AzureAIAgentClient(credential=credential).as_agent(
name="Analyst",
instructions="Use the code interpreter for numeric work.",
tools=[HostedCodeInterpreterTool()],
) as agent,
):
# HostedCodeInterpreterTool mirrors the built-in Azure AI capability.
reply = await agent.run(
"Use Python to compute 42 ** 2 and explain the result.",
tool_choice="auto",
)
print("[AF]", reply.text)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,67 @@
# Copyright (c) Microsoft. All rights reserved.
"""Maintain Azure AI agent conversation state across turns in SK and AF."""
import asyncio
async def run_semantic_kernel() -> None:
from azure.identity.aio import AzureCliCredential
from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread
async with AzureCliCredential() as credential, AzureAIAgent.create_client(credential=credential) as client:
settings = AzureAIAgentSettings()
definition = await client.agents.as_agent(
model=settings.model_deployment_name,
name="Planner",
instructions="Track follow-up questions within the same thread.",
)
agent = AzureAIAgent(client=client, definition=definition)
thread: AzureAIAgentThread | None = None
# SK returns the updated AzureAIAgentThread on each response.
first = await agent.get_response("Outline the onboarding checklist.", thread=thread)
thread = first.thread
print("[SK][turn1]", first.message.content)
second = await agent.get_response(
"Highlight the items that require legal review.",
thread=thread,
)
print("[SK][turn2]", second.message.content)
if thread is not None:
print("[SK][thread-id]", thread.id)
async def run_agent_framework() -> None:
from agent_framework.azure import AzureAIAgentClient
from azure.identity.aio import AzureCliCredential
async with (
AzureCliCredential() as credential,
AzureAIAgentClient(credential=credential).as_agent(
name="Planner",
instructions="Track follow-up questions within the same thread.",
) as agent,
):
thread = agent.get_new_thread()
# AF threads are explicit and can be serialized for external storage.
first = await agent.run("Outline the onboarding checklist.", thread=thread)
print("[AF][turn1]", first.text)
second = await agent.run(
"Highlight the items that require legal review.",
thread=thread,
)
print("[AF][turn2]", second.text)
serialized = await thread.serialize()
print("[AF][thread-json]", serialized)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,46 @@
# Copyright (c) Microsoft. All rights reserved.
"""Basic SK ChatCompletionAgent vs Agent Framework ChatAgent.
Both samples expect OpenAI-compatible environment variables (OPENAI_API_KEY or
Azure OpenAI configuration). Update the prompts or client wiring to match your
model of choice before running.
"""
import asyncio
async def run_semantic_kernel() -> None:
"""Call SK's ChatCompletionAgent for a simple question."""
from semantic_kernel.agents import ChatCompletionAgent
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
# SK agent holds the thread state internally via ChatCompletionAgent.
agent = ChatCompletionAgent(
service=OpenAIChatCompletion(),
name="Support",
instructions="Answer in one sentence.",
)
response = await agent.get_response(messages="How do I reset my bike tire?")
print("[SK]", response.message.content)
async def run_agent_framework() -> None:
"""Call Agent Framework's ChatAgent created from OpenAIChatClient."""
from agent_framework.openai import OpenAIChatClient
# AF constructs a lightweight ChatAgent backed by OpenAIChatClient.
chat_agent = OpenAIChatClient().as_agent(
name="Support",
instructions="Answer in one sentence.",
)
reply = await chat_agent.run("How do I reset my bike tire?")
print("[AF]", reply.text)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,65 @@
# Copyright (c) Microsoft. All rights reserved.
"""Demonstrate SK plugins vs Agent Framework tools with a chat agent.
Configure your OpenAI or Azure OpenAI credentials before running. The example
exposes a "specials" tool that both SDKs call during the conversation.
"""
import asyncio
async def run_semantic_kernel() -> None:
from semantic_kernel.agents import ChatCompletionAgent, ChatHistoryAgentThread
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.functions import kernel_function
class SpecialsPlugin:
@kernel_function(name="specials", description="List daily specials")
def specials(self) -> str:
return "Clam chowder, Cobb salad, Chai tea"
# SK advertises tools by attaching plugin instances at construction time.
agent = ChatCompletionAgent(
service=OpenAIChatCompletion(),
name="Host",
instructions="Answer menu questions accurately.",
plugins=[SpecialsPlugin()],
)
thread = ChatHistoryAgentThread()
response = await agent.get_response(
messages="What soup can I order today?",
thread=thread,
)
print("[SK]", response.message.content)
async def run_agent_framework() -> None:
from agent_framework._tools import ai_function
from agent_framework.openai import OpenAIChatClient
@ai_function(name="specials", description="List daily specials")
async def specials() -> str:
return "Clam chowder, Cobb salad, Chai tea"
# AF tools are provided as callables on each agent instance.
chat_agent = OpenAIChatClient().as_agent(
name="Host",
instructions="Answer menu questions accurately.",
tools=[specials],
)
thread = chat_agent.get_new_thread()
reply = await chat_agent.run(
"What soup can I order today?",
thread=thread,
tool_choice="auto",
)
print("[AF]", reply.text)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,71 @@
# Copyright (c) Microsoft. All rights reserved.
"""Compare conversation threading and streaming responses for chat agents.
Both implementations reuse a conversation thread across turns and stream output
for the second turn.
"""
import asyncio
async def run_semantic_kernel() -> None:
from semantic_kernel.agents import ChatCompletionAgent, ChatHistoryAgentThread
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
# SK thread object keeps the conversation history on the agent side.
agent = ChatCompletionAgent(
service=OpenAIChatCompletion(),
name="Writer",
instructions="Keep answers short and friendly.",
)
thread = ChatHistoryAgentThread()
first = await agent.get_response(
messages="Suggest a catchy headline for our product launch.",
thread=thread,
)
print("[SK]", first.message.content)
print("[SK][stream]", end=" ")
async for update in agent.invoke_stream(
messages="Draft a 2 sentence blurb.",
thread=thread,
):
if update.message:
print(update.message.content, end="", flush=True)
print()
async def run_agent_framework() -> None:
from agent_framework.openai import OpenAIChatClient
# AF thread objects are requested explicitly from the agent.
chat_agent = OpenAIChatClient().as_agent(
name="Writer",
instructions="Keep answers short and friendly.",
)
thread = chat_agent.get_new_thread()
first = await chat_agent.run(
"Suggest a catchy headline for our product launch.",
thread=thread,
)
print("[AF]", first.text)
print("[AF][stream]", end=" ")
async for chunk in chat_agent.run_stream(
"Draft a 2 sentence blurb.",
thread=thread,
):
if chunk.text:
print(chunk.text, end="", flush=True)
print()
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,37 @@
# Copyright (c) Microsoft. All rights reserved.
"""Call a Copilot Studio agent with SK and Agent Framework."""
import asyncio
async def run_semantic_kernel() -> None:
from semantic_kernel.agents import CopilotStudioAgent
# SK agent talks to the configured Copilot Studio bot directly.
agent = CopilotStudioAgent(
name="PhysicsAgent",
instructions="Answer physics questions concisely.",
)
response = await agent.get_response("Why is the sky blue?")
print("[SK]", response.message.content)
async def run_agent_framework() -> None:
from agent_framework.microsoft import CopilotStudioAgent
# AF exposes an equivalent CopilotStudioAgent wrapper.
agent = CopilotStudioAgent(
name="PhysicsAgent",
instructions="Answer physics questions concisely.",
)
reply = await agent.run("Why is the sky blue?")
print("[AF]", reply.text)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,43 @@
# Copyright (c) Microsoft. All rights reserved.
"""Stream responses from Copilot Studio agents in SK and AF."""
import asyncio
async def run_semantic_kernel() -> None:
from semantic_kernel.agents import CopilotStudioAgent
agent = CopilotStudioAgent(
name="TourGuide",
instructions="Provide travel recommendations in short bursts.",
)
# SK streaming yields chunks with message metadata.
print("[SK][stream]", end=" ")
async for chunk in agent.invoke_stream("Plan a day in Copenhagen for foodies."):
if chunk.message:
print(chunk.message.content, end="", flush=True)
print()
async def run_agent_framework() -> None:
from agent_framework.microsoft import CopilotStudioAgent
agent = CopilotStudioAgent(
name="TourGuide",
instructions="Provide travel recommendations in short bursts.",
)
# AF streaming provides incremental AgentResponseUpdate objects.
print("[AF][stream]", end=" ")
async for update in agent.run_stream("Plan a day in Copenhagen for foodies."):
if update.text:
print(update.text, end="", flush=True)
print()
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,55 @@
# Copyright (c) Microsoft. All rights reserved.
"""Create an OpenAI Assistant using SK and Agent Framework."""
import asyncio
import os
ASSISTANT_MODEL = os.environ.get("OPENAI_ASSISTANT_MODEL", "gpt-4o-mini")
async def run_semantic_kernel() -> None:
from semantic_kernel.agents import AssistantAgentThread, OpenAIAssistantAgent
client = OpenAIAssistantAgent.create_client()
# Provision the assistant on the OpenAI Assistants service.
definition = await client.beta.assistants.create(
model=ASSISTANT_MODEL,
name="Helper",
instructions="Answer questions in one concise paragraph.",
)
agent = OpenAIAssistantAgent(client=client, definition=definition)
thread: AssistantAgentThread | None = None
response = await agent.get_response("What is the capital of Denmark?", thread=thread)
thread = response.thread
print("[SK]", response.message.content)
if thread is not None:
print("[SK][thread-id]", thread.id)
async def run_agent_framework() -> None:
from agent_framework.openai import OpenAIAssistantsClient
assistants_client = OpenAIAssistantsClient()
# AF wraps the assistant lifecycle with an async context manager.
async with assistants_client.as_agent(
name="Helper",
instructions="Answer questions in one concise paragraph.",
model=ASSISTANT_MODEL,
) as assistant_agent:
reply = await assistant_agent.run("What is the capital of Denmark?")
print("[AF]", reply.text)
follow_up = await assistant_agent.run(
"How many residents live there?",
thread=assistant_agent.get_new_thread(),
)
print("[AF][follow-up]", follow_up.text)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,55 @@
# Copyright (c) Microsoft. All rights reserved.
"""Enable the code interpreter tool for OpenAI Assistants in SK and AF."""
import asyncio
async def run_semantic_kernel() -> None:
from semantic_kernel.agents import OpenAIAssistantAgent
from semantic_kernel.connectors.ai.open_ai import OpenAISettings
client = OpenAIAssistantAgent.create_client()
code_interpreter_tool, code_interpreter_tool_resources = OpenAIAssistantAgent.configure_code_interpreter_tool()
# Enable the hosted code interpreter tool on the assistant definition.
definition = await client.beta.assistants.create(
model=OpenAISettings().chat_deployment_name,
name="CodeRunner",
instructions="Run the provided request as code and return the result.",
tools=code_interpreter_tool,
tool_resources=code_interpreter_tool_resources,
)
agent = OpenAIAssistantAgent(client=client, definition=definition)
response = await agent.get_response(
"Use Python to calculate the mean of [41, 42, 45] and explain the steps.",
)
print(f"[SK]: {response}")
async def run_agent_framework() -> None:
from agent_framework import HostedCodeInterpreterTool
from agent_framework.openai import OpenAIAssistantsClient
assistants_client = OpenAIAssistantsClient()
# AF exposes the same tool configuration via create_agent.
async with assistants_client.as_agent(
name="CodeRunner",
instructions="Use the code interpreter when calculations are required.",
model="gpt-4.1",
tools=[HostedCodeInterpreterTool()],
) as assistant_agent:
response = await assistant_agent.run(
"Use Python to calculate the mean of [41, 42, 45] and explain the steps.",
tool_choice="auto",
)
print(f"[AF]: {response.text}")
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,89 @@
# Copyright (c) Microsoft. All rights reserved.
"""Implement a function tool for OpenAI Assistants in SK and AF."""
import asyncio
import os
from typing import Any
ASSISTANT_MODEL = os.environ.get("OPENAI_ASSISTANT_MODEL", "gpt-4o-mini")
async def fake_weather_lookup(city: str, day: str) -> dict[str, Any]:
"""Pretend to call a weather service."""
return {
"city": city,
"day": day,
"forecast": "Sunny with scattered clouds",
"high_c": 22,
"low_c": 14,
}
async def run_semantic_kernel() -> None:
from semantic_kernel.agents import AssistantAgentThread, OpenAIAssistantAgent
from semantic_kernel.functions import kernel_function
class WeatherPlugin:
@kernel_function(name="get_forecast", description="Look up the forecast for a city and day.")
async def fake_weather_lookup(city: str, day: str) -> dict[str, Any]:
"""Pretend to call a weather service."""
return {
"city": city,
"day": day,
"forecast": "Sunny with scattered clouds",
"high_c": 22,
"low_c": 14,
}
client = OpenAIAssistantAgent.create_client()
# Tool schema is registered on the assistant definition.
definition = await client.beta.assistants.create(
model=ASSISTANT_MODEL,
name="WeatherHelper",
instructions="Call get_forecast to fetch weather details.",
plugins=[WeatherPlugin()],
)
agent = OpenAIAssistantAgent(client=client, definition=definition)
thread: AssistantAgentThread | None = None
response = await agent.get_response(
"What will the weather be like in Seattle tomorrow?",
thread=thread,
)
thread = response.thread
print("[SK][initial]", response.message.content)
async def run_agent_framework() -> None:
from agent_framework._tools import ai_function
from agent_framework.openai import OpenAIAssistantsClient
@ai_function(
name="get_forecast",
description="Look up the forecast for a city and day.",
)
async def get_forecast(city: str, day: str) -> dict[str, Any]:
return await fake_weather_lookup(city, day)
assistants_client = OpenAIAssistantsClient()
# AF converts the decorated function into an assistant-compatible tool.
async with assistants_client.as_agent(
name="WeatherHelper",
instructions="Call get_forecast to fetch weather details.",
model=ASSISTANT_MODEL,
tools=[get_forecast],
) as assistant_agent:
reply = await assistant_agent.run(
"What will the weather be like in Seattle tomorrow?",
tool_choice="auto",
)
print("[AF]", reply.text)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,48 @@
# Copyright (c) Microsoft. All rights reserved.
"""Issue a basic Responses API call using SK and Agent Framework."""
import asyncio
async def run_semantic_kernel() -> None:
from azure.identity import AzureCliCredential
from semantic_kernel.agents import AzureResponsesAgent
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
credential = AzureCliCredential()
try:
client = AzureResponsesAgent.create_client(credential=credential)
# SK response agents wrap Azure OpenAI's hosted Responses API.
agent = AzureResponsesAgent(
ai_model_id=AzureOpenAISettings().responses_deployment_name,
client=client,
instructions="Answer in one concise sentence.",
name="Expert",
)
response = await agent.get_response("Why is the sky blue?")
print("[SK]", response.message.content)
finally:
await credential.close()
async def run_agent_framework() -> None:
from agent_framework import ChatAgent
from agent_framework.openai import OpenAIResponsesClient
# AF ChatAgent can swap in an OpenAIResponsesClient directly.
chat_agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
instructions="Answer in one concise sentence.",
name="Expert",
)
reply = await chat_agent.run("Why is the sky blue?")
print("[AF]", reply.text)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,61 @@
# Copyright (c) Microsoft. All rights reserved.
"""Attach a lightweight function tool to the Responses API in SK and AF."""
import asyncio
async def run_semantic_kernel() -> None:
from azure.identity import AzureCliCredential
from semantic_kernel.agents import AzureResponsesAgent
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
from semantic_kernel.functions import kernel_function
class MathPlugin:
@kernel_function(name="add", description="Add two numbers")
def add(self, a: float, b: float) -> float:
return a + b
credential = AzureCliCredential()
try:
client = AzureResponsesAgent.create_client(credential=credential)
# Plugins advertise callable tools to the Responses agent.
agent = AzureResponsesAgent(
ai_model_id=AzureOpenAISettings().responses_deployment_name,
client=client,
instructions="Use the add tool when math is required.",
name="MathExpert",
plugins=[MathPlugin()],
)
response = await agent.get_response("Use add(41, 1) and explain the result.")
print("[SK]", response.message.content)
finally:
await credential.close()
async def run_agent_framework() -> None:
from agent_framework import ChatAgent
from agent_framework._tools import ai_function
from agent_framework.openai import OpenAIResponsesClient
@ai_function(name="add", description="Add two numbers")
async def add(a: float, b: float) -> float:
return a + b
chat_agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
instructions="Use the add tool when math is required.",
name="MathExpert",
# AF registers the async function as a tool at construction.
tools=[add],
)
reply = await chat_agent.run("Use add(41, 1) and explain the result.")
print("[AF]", reply.text)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,63 @@
# Copyright (c) Microsoft. All rights reserved.
"""Request structured JSON output from the Responses API in SK and AF."""
import asyncio
from pydantic import BaseModel
class ReleaseBrief(BaseModel):
feature: str
benefit: str
launch_date: str
async def run_semantic_kernel() -> None:
from azure.identity import AzureCliCredential
from semantic_kernel.agents import AzureResponsesAgent
from semantic_kernel.connectors.ai.open_ai import AzureOpenAISettings
credential = AzureCliCredential()
try:
client = AzureResponsesAgent.create_client(credential=credential)
# response_format requests schema-constrained output from the model.
agent = AzureResponsesAgent(
ai_model_id=AzureOpenAISettings().responses_deployment_name,
client=client,
instructions="Return launch briefs as structured JSON.",
name="ProductMarketer",
text=AzureResponsesAgent.configure_response_format(ReleaseBrief),
)
response = await agent.get_response(
"Draft a launch brief for the Contoso Note app.",
response_format=ReleaseBrief,
)
print("[SK]", response.message.content)
finally:
await credential.close()
async def run_agent_framework() -> None:
from agent_framework import ChatAgent
from agent_framework.openai import OpenAIResponsesClient
chat_agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
instructions="Return launch briefs as structured JSON.",
name="ProductMarketer",
)
# AF forwards the same response_format payload at invocation time.
reply = await chat_agent.run(
"Draft a launch brief for the Contoso Note app.",
options={"response_format": ReleaseBrief},
)
print("[AF]", reply.text)
async def main() -> None:
await run_semantic_kernel()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,123 @@
# Copyright (c) Microsoft. All rights reserved.
"""Side-by-side concurrent orchestrations for Agent Framework and Semantic Kernel."""
import asyncio
from collections.abc import Sequence
from typing import cast
from agent_framework import ChatMessage, ConcurrentBuilder, WorkflowOutputEvent
from agent_framework.azure import AzureOpenAIChatClient
from azure.identity import AzureCliCredential
from semantic_kernel.agents import Agent, ChatCompletionAgent, ConcurrentOrchestration
from semantic_kernel.agents.runtime import InProcessRuntime
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents import ChatMessageContent
PROMPT = "Explain the concept of temperature from multiple scientific perspectives."
######################################################################
# Semantic Kernel orchestration path
######################################################################
def build_semantic_kernel_agents() -> list[Agent]:
credential = AzureCliCredential()
physics_agent = ChatCompletionAgent(
name="PhysicsExpert",
instructions=("You are an expert in physics. Answer questions from a physics perspective."),
service=AzureChatCompletion(credential=credential),
)
chemistry_agent = ChatCompletionAgent(
name="ChemistryExpert",
instructions=("You are an expert in chemistry. Answer questions from a chemistry perspective."),
service=AzureChatCompletion(credential=credential),
)
return [physics_agent, chemistry_agent]
async def run_semantic_kernel_example(prompt: str) -> Sequence[ChatMessageContent]:
concurrent_orchestration = ConcurrentOrchestration(members=build_semantic_kernel_agents())
runtime = InProcessRuntime()
runtime.start()
try:
orchestration_result = await concurrent_orchestration.invoke(task=prompt, runtime=runtime)
final_value = await orchestration_result.get(timeout=60)
if isinstance(final_value, ChatMessageContent):
return [final_value]
if isinstance(final_value, Sequence):
return list(final_value)
return []
finally:
await runtime.stop_when_idle()
def _print_semantic_kernel_outputs(outputs: Sequence[ChatMessageContent]) -> None:
if not outputs:
print("No Semantic Kernel output.")
return
print("===== Semantic Kernel Concurrent =====")
for item in outputs:
content = item.content or ""
print(f"# {item.name}\n{content}\n")
######################################################################
# Agent Framework orchestration path
######################################################################
async def run_agent_framework_example(prompt: str) -> Sequence[list[ChatMessage]]:
chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
physics = chat_client.as_agent(
instructions=("You are an expert in physics. Answer questions from a physics perspective."),
name="physics",
)
chemistry = chat_client.as_agent(
instructions=("You are an expert in chemistry. Answer questions from a chemistry perspective."),
name="chemistry",
)
workflow = ConcurrentBuilder().participants([physics, chemistry]).build()
outputs: list[list[ChatMessage]] = []
async for event in workflow.run_stream(prompt):
if isinstance(event, WorkflowOutputEvent):
outputs.append(cast(list[ChatMessage], event.data))
return outputs
def _print_agent_framework_outputs(conversations: Sequence[Sequence[ChatMessage]]) -> None:
if not conversations:
print("No Agent Framework output.")
return
print("===== Agent Framework Concurrent =====")
for index, conversation in enumerate(conversations, start=1):
print(f"--- Conversation {index} ---")
for message in conversation:
name = message.author_name or "assistant"
print(f"[{name}] {message.text}")
print()
async def main() -> None:
agent_framework_outputs = await run_agent_framework_example(PROMPT)
_print_agent_framework_outputs(agent_framework_outputs)
semantic_kernel_outputs = await run_semantic_kernel_example(PROMPT)
_print_semantic_kernel_outputs(semantic_kernel_outputs)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,271 @@
# Copyright (c) Microsoft. All rights reserved.
"""Side-by-side group chat orchestrations for Agent Framework and Semantic Kernel."""
import asyncio
import sys
from collections.abc import Sequence
from typing import Any, cast
from agent_framework import ChatAgent, ChatMessage, GroupChatBuilder, WorkflowOutputEvent
from agent_framework.azure import AzureOpenAIChatClient, AzureOpenAIResponsesClient
from azure.identity import AzureCliCredential
from semantic_kernel.agents import Agent, ChatCompletionAgent, GroupChatOrchestration
from semantic_kernel.agents.orchestration.group_chat import (
BooleanResult,
GroupChatManager,
MessageResult,
StringResult,
)
from semantic_kernel.agents.runtime import InProcessRuntime
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.contents import AuthorRole, ChatHistory, ChatMessageContent
from semantic_kernel.functions import KernelArguments
from semantic_kernel.kernel import Kernel
from semantic_kernel.prompt_template import KernelPromptTemplate, PromptTemplateConfig
if sys.version_info >= (3, 12):
from typing import override # pragma: no cover
else:
from typing_extensions import override # pragma: no cover
DISCUSSION_TOPIC = "What are the essential steps for launching a community hackathon?"
######################################################################
# Semantic Kernel orchestration path
######################################################################
def build_semantic_kernel_agents() -> list[Agent]:
credential = AzureCliCredential()
researcher = ChatCompletionAgent(
name="Researcher",
description="Collects background information and potential resources.",
instructions=(
"Gather concise facts or considerations that help plan a community hackathon. "
"Keep your responses factual and scannable."
),
service=AzureChatCompletion(credential=credential),
)
planner = ChatCompletionAgent(
name="Planner",
description="Synthesizes an actionable plan from available notes.",
instructions=(
"Use the running conversation to draft a structured action plan. Emphasize logistics and sequencing."
),
service=AzureChatCompletion(credential=credential),
)
return [researcher, planner]
class ChatCompletionGroupChatManager(GroupChatManager):
"""Group chat manager that delegates orchestration decisions to an Azure OpenAI deployment."""
service: ChatCompletionClientBase
topic: str
termination_prompt: str = (
"You are coordinating a conversation about '{{topic}}'. "
"Decide if the discussion has produced a solid answer. "
'Respond using JSON: {"result": true|false, "reason": "..."}.'
)
selection_prompt: str = (
"You are coordinating a conversation about '{{topic}}'. "
"Choose the next participant by returning JSON with keys (result, reason). "
"The result must match one of: {{participants}}."
)
summary_prompt: str = (
"You have just finished a discussion about '{{topic}}'. "
"Summarize the plan and highlight key takeaways. Return JSON with keys (result, reason) where "
"result is the final response text."
)
def __init__(self, *, topic: str, service: ChatCompletionClientBase) -> None:
super().__init__(topic=topic, service=service)
self._round_robin_index = 0
async def _render_prompt(self, template: str, **kwargs: Any) -> str:
prompt_template = KernelPromptTemplate(prompt_template_config=PromptTemplateConfig(template=template))
return await prompt_template.render(Kernel(), arguments=KernelArguments(**kwargs))
@override
async def should_request_user_input(self, chat_history: ChatHistory) -> BooleanResult:
return BooleanResult(result=False, reason="This orchestration is fully automated.")
@override
async def should_terminate(self, chat_history: ChatHistory) -> BooleanResult:
rendered_prompt = await self._render_prompt(self.termination_prompt, topic=self.topic)
chat_history.messages.insert(
0,
ChatMessageContent(role=AuthorRole.SYSTEM, content=rendered_prompt),
)
chat_history.add_message(
ChatMessageContent(role=AuthorRole.USER, content="Decide if the discussion is complete."),
)
response = await self.service.get_chat_message_content(
chat_history,
settings=PromptExecutionSettings(response_format=BooleanResult),
)
result = BooleanResult.model_validate_json(response.content)
return result
@override
async def select_next_agent(
self,
chat_history: ChatHistory,
participant_descriptions: dict[str, str],
) -> StringResult:
rendered_prompt = await self._render_prompt(
self.selection_prompt,
topic=self.topic,
participants=", ".join(participant_descriptions.keys()),
)
chat_history.messages.insert(
0,
ChatMessageContent(role=AuthorRole.SYSTEM, content=rendered_prompt),
)
chat_history.add_message(
ChatMessageContent(role=AuthorRole.USER, content="Pick the next participant to speak."),
)
response = await self.service.get_chat_message_content(
chat_history,
settings=PromptExecutionSettings(response_format=StringResult),
)
result = StringResult.model_validate_json(response.content)
if result.result not in participant_descriptions:
raise RuntimeError(f"Unknown participant selected: {result.result}")
return result
@override
async def filter_results(self, chat_history: ChatHistory) -> MessageResult:
rendered_prompt = await self._render_prompt(self.summary_prompt, topic=self.topic)
chat_history.messages.insert(
0,
ChatMessageContent(role=AuthorRole.SYSTEM, content=rendered_prompt),
)
chat_history.add_message(
ChatMessageContent(role=AuthorRole.USER, content="Summarize the plan."),
)
response = await self.service.get_chat_message_content(
chat_history,
settings=PromptExecutionSettings(response_format=StringResult),
)
string_result = StringResult.model_validate_json(response.content)
return MessageResult(
result=ChatMessageContent(role=AuthorRole.ASSISTANT, content=string_result.result),
reason=string_result.reason,
)
async def sk_agent_response_callback(message: ChatMessageContent | Sequence[ChatMessageContent]) -> None:
if isinstance(message, ChatMessageContent):
messages: Sequence[ChatMessageContent] = [message]
elif isinstance(message, Sequence) and not isinstance(message, (str, bytes)):
messages = list(message)
else:
messages = [cast(ChatMessageContent, message)]
for item in messages:
print(f"# {item.name}\n{item.content}\n")
async def run_semantic_kernel_example(task: str) -> str:
credential = AzureCliCredential()
orchestration = GroupChatOrchestration(
members=build_semantic_kernel_agents(),
manager=ChatCompletionGroupChatManager(
topic=DISCUSSION_TOPIC,
service=AzureChatCompletion(credential=credential),
max_rounds=8,
),
agent_response_callback=sk_agent_response_callback,
)
runtime = InProcessRuntime()
runtime.start()
try:
orchestration_result = await orchestration.invoke(task=task, runtime=runtime)
final_message = await orchestration_result.get(timeout=30)
if isinstance(final_message, ChatMessageContent):
return final_message.content or ""
return str(final_message)
finally:
await runtime.stop_when_idle()
######################################################################
# Agent Framework orchestration path
######################################################################
async def run_agent_framework_example(task: str) -> str:
credential = AzureCliCredential()
researcher = ChatAgent(
name="Researcher",
description="Collects background information and potential resources.",
instructions=(
"Gather concise facts or considerations that help plan a community hackathon. "
"Keep your responses factual and scannable."
),
chat_client=AzureOpenAIChatClient(credential=credential),
)
planner = ChatAgent(
name="Planner",
description="Turns the collected notes into a concrete action plan.",
instructions=("Propose a structured action plan that accounts for logistics, roles, and timeline."),
chat_client=AzureOpenAIResponsesClient(credential=credential),
)
workflow = (
GroupChatBuilder()
.set_manager(
manager=AzureOpenAIChatClient(credential=credential).as_agent(),
display_name="Coordinator",
)
.participants(researcher=researcher, planner=planner)
.build()
)
final_response = ""
async for event in workflow.run_stream(task):
if isinstance(event, WorkflowOutputEvent):
data = event.data
if isinstance(data, list) and len(data) > 0:
# Get the final message from the conversation
final_message = data[-1]
final_response = final_message.text or "" if isinstance(final_message, ChatMessage) else str(data)
else:
final_response = str(data)
return final_response
async def main() -> None:
task = "Kick off the group discussion."
print("===== Agent Framework Group Chat =====")
af_response = await run_agent_framework_example(task)
print(af_response or "No response returned.")
print()
print("===== Semantic Kernel Group Chat =====")
sk_response = await run_semantic_kernel_example(task)
print(sk_response or "No response returned.")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,293 @@
# Copyright (c) Microsoft. All rights reserved.
"""Side-by-side handoff orchestrations for Semantic Kernel and Agent Framework."""
import asyncio
import sys
from collections.abc import AsyncIterable, Iterator, Sequence
from typing import cast
from agent_framework import (
ChatMessage,
HandoffBuilder,
HandoffUserInputRequest,
RequestInfoEvent,
WorkflowEvent,
WorkflowOutputEvent,
)
from agent_framework.azure import AzureOpenAIChatClient
from azure.identity import AzureCliCredential
from semantic_kernel.agents import Agent, ChatCompletionAgent, HandoffOrchestration, OrchestrationHandoffs
from semantic_kernel.agents.runtime import InProcessRuntime
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents import (
AuthorRole,
ChatMessageContent,
FunctionCallContent,
FunctionResultContent,
StreamingChatMessageContent,
)
from semantic_kernel.functions import kernel_function
if sys.version_info >= (3, 12):
pass # pragma: no cover
else:
pass # pragma: no cover
CUSTOMER_PROMPT = "I need help with order 12345. I want a replacement and need to know when it will arrive."
SCRIPTED_RESPONSES = [
"The item arrived damaged. I'd like a replacement shipped to the same address.",
"Great! Can you confirm the shipping cost won't be charged again?",
"Thanks for confirming!",
]
######################################################################
# Semantic Kernel orchestration path
######################################################################
class OrderStatusPlugin:
@kernel_function
def check_order_status(self, order_id: str) -> str:
return f"Order {order_id} is shipped and will arrive in 2-3 days."
class OrderRefundPlugin:
@kernel_function
def process_refund(self, order_id: str, reason: str) -> str:
return f"Refund for order {order_id} has been processed successfully (reason: {reason})."
class OrderReturnPlugin:
@kernel_function
def process_return(self, order_id: str, reason: str) -> str:
return f"Return for order {order_id} has been processed successfully (reason: {reason})."
def build_semantic_kernel_agents() -> tuple[list[Agent], OrchestrationHandoffs]:
credential = AzureCliCredential()
triage = ChatCompletionAgent(
name="TriageAgent",
description="Customer support triage specialist.",
instructions="Greet the customer, collect intent, and hand off to the right specialist.",
service=AzureChatCompletion(credential=credential),
)
refund = ChatCompletionAgent(
name="RefundAgent",
description="Handles refunds.",
instructions="Process refund requests.",
service=AzureChatCompletion(credential=credential),
plugins=[OrderRefundPlugin()],
)
order_status = ChatCompletionAgent(
name="OrderStatusAgent",
description="Looks up order status.",
instructions="Provide shipping timelines and tracking information.",
service=AzureChatCompletion(credential=credential),
plugins=[OrderStatusPlugin()],
)
order_return = ChatCompletionAgent(
name="OrderReturnAgent",
description="Handles returns.",
instructions="Coordinate order returns.",
service=AzureChatCompletion(credential=credential),
plugins=[OrderReturnPlugin()],
)
handoffs = (
OrchestrationHandoffs()
.add_many(
source_agent=triage.name,
target_agents={
refund.name: "Route refund-related requests here.",
order_status.name: "Route shipping questions here.",
order_return.name: "Route return-related requests here.",
},
)
.add(refund.name, triage.name, "Return to triage for non-refund issues.")
.add(order_status.name, triage.name, "Return to triage for non-status issues.")
.add(order_return.name, triage.name, "Return to triage for non-return issues.")
)
return [triage, refund, order_status, order_return], handoffs
_sk_new_message = True
def _sk_streaming_callback(message: StreamingChatMessageContent, is_final: bool) -> None:
"""Display SK agent messages as they stream."""
global _sk_new_message
if _sk_new_message:
print(f"{message.name}: ", end="", flush=True)
_sk_new_message = False
if message.content:
print(message.content, end="", flush=True)
for item in message.items:
if isinstance(item, FunctionCallContent):
print(f"[tool call: {item.name}({item.arguments})]", end="", flush=True)
if isinstance(item, FunctionResultContent):
print(f"[tool result: {item.result}]", end="", flush=True)
if is_final:
print()
_sk_new_message = True
def _make_sk_human_responder(script: Iterator[str]) -> callable:
def _responder() -> ChatMessageContent:
try:
user_text = next(script)
except StopIteration:
user_text = "Thanks, that's all."
print(f"[User]: {user_text}")
return ChatMessageContent(role=AuthorRole.USER, content=user_text)
return _responder
async def run_semantic_kernel_example(initial_task: str, scripted_responses: Sequence[str]) -> str:
agents, handoffs = build_semantic_kernel_agents()
response_iter = iter(scripted_responses)
orchestration = HandoffOrchestration(
members=agents,
handoffs=handoffs,
streaming_agent_response_callback=_sk_streaming_callback,
human_response_function=_make_sk_human_responder(response_iter),
)
runtime = InProcessRuntime()
runtime.start()
try:
orchestration_result = await orchestration.invoke(task=initial_task, runtime=runtime)
final_message = await orchestration_result.get(timeout=30)
if isinstance(final_message, ChatMessageContent):
return final_message.content or ""
return str(final_message)
finally:
await runtime.stop_when_idle()
######################################################################
# Agent Framework orchestration path
######################################################################
def _create_af_agents(client: AzureOpenAIChatClient):
triage = client.as_agent(
name="triage_agent",
instructions=(
"You are a customer support triage agent. Route requests:\n"
"- handoff_to_refund_agent for refunds\n"
"- handoff_to_order_status_agent for shipping/timeline questions\n"
"- handoff_to_order_return_agent for returns"
),
)
refund = client.as_agent(
name="refund_agent",
instructions=(
"Handle refunds. Ask for order id and reason. If shipping info is needed, hand off to order_status_agent."
),
)
status = client.as_agent(
name="order_status_agent",
instructions=(
"Provide order status, tracking, and timelines. If billing questions appear, hand off to refund_agent."
),
)
returns = client.as_agent(
name="order_return_agent",
instructions=(
"Coordinate returns, confirm addresses, and summarize next steps. Hand off to triage_agent if unsure."
),
)
return triage, refund, status, returns
async def _drain_events(stream: AsyncIterable[WorkflowEvent]) -> list[WorkflowEvent]:
return [event async for event in stream]
def _collect_handoff_requests(events: list[WorkflowEvent]) -> list[RequestInfoEvent]:
requests: list[RequestInfoEvent] = []
for event in events:
if isinstance(event, RequestInfoEvent) and isinstance(event.data, HandoffUserInputRequest):
requests.append(event)
return requests
def _extract_final_conversation(events: list[WorkflowEvent]) -> list[ChatMessage]:
for event in events:
if isinstance(event, WorkflowOutputEvent):
data = cast(list[ChatMessage], event.data)
return data
return []
async def run_agent_framework_example(initial_task: str, scripted_responses: Sequence[str]) -> str:
client = AzureOpenAIChatClient(credential=AzureCliCredential())
triage, refund, status, returns = _create_af_agents(client)
workflow = (
HandoffBuilder(name="sk_af_handoff_migration", participants=[triage, refund, status, returns])
.set_coordinator(triage)
.add_handoff(triage, [refund, status, returns])
.add_handoff(refund, [status, triage])
.add_handoff(status, [refund, triage])
.add_handoff(returns, triage)
.build()
)
events = await _drain_events(workflow.run_stream(initial_task))
pending = _collect_handoff_requests(events)
scripted_iter = iter(scripted_responses)
final_events = events
while pending:
try:
user_reply = next(scripted_iter)
except StopIteration:
user_reply = "Thanks, that's all."
responses = {request.request_id: user_reply for request in pending}
final_events = await _drain_events(workflow.send_responses_streaming(responses))
pending = _collect_handoff_requests(final_events)
conversation = _extract_final_conversation(final_events)
if not conversation:
return ""
# Render final transcript succinctly.
lines = []
for message in conversation:
text = message.text or ""
if not text.strip():
continue
speaker = message.author_name or message.role.value
lines.append(f"{speaker}: {text}")
return "\n".join(lines)
######################################################################
# Console entry point
######################################################################
async def main() -> None:
print("===== Agent Framework Handoff =====")
af_transcript = await run_agent_framework_example(CUSTOMER_PROMPT, SCRIPTED_RESPONSES)
print(af_transcript or "No output produced.")
print()
print("===== Semantic Kernel Handoff =====")
sk_result = await run_semantic_kernel_example(CUSTOMER_PROMPT, SCRIPTED_RESPONSES)
print(sk_result or "No output produced.")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,180 @@
# Copyright (c) Microsoft. All rights reserved.
"""Side-by-side Magentic orchestrations for Agent Framework and Semantic Kernel."""
import asyncio
from collections.abc import Sequence
from typing import cast
from agent_framework import ChatAgent, HostedCodeInterpreterTool, MagenticBuilder, WorkflowOutputEvent
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
from semantic_kernel.agents import (
Agent,
ChatCompletionAgent,
MagenticOrchestration,
OpenAIAssistantAgent,
StandardMagenticManager,
)
from semantic_kernel.agents.runtime import InProcessRuntime
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAISettings
from semantic_kernel.contents import ChatMessageContent
PROMPT = (
"I am preparing a report on the energy efficiency of different machine learning model architectures. "
"Compare the estimated training and inference energy consumption of ResNet-50, BERT-base, and GPT-2 "
"on standard datasets (e.g., ImageNet for ResNet, GLUE for BERT, WebText for GPT-2). "
"Then, estimate the CO2 emissions associated with each, assuming training on an Azure Standard_NC6s_v3 VM "
"for 24 hours. Provide tables for clarity, and recommend the most energy-efficient model per task type "
"(image classification, text classification, and text generation)."
)
######################################################################
# Semantic Kernel orchestration path
######################################################################
async def build_semantic_kernel_agents() -> list[Agent]:
research_agent = ChatCompletionAgent(
name="ResearchAgent",
description="A helpful assistant with access to web search. Ask it to perform web searches.",
instructions=(
"You are a Researcher. You find information without additional computation or quantitative analysis."
),
service=OpenAIChatCompletion(ai_model_id="gpt-4o-search-preview"),
)
client = OpenAIAssistantAgent.create_client()
code_interpreter_tool, code_interpreter_tool_resources = OpenAIAssistantAgent.configure_code_interpreter_tool()
openai_settings = OpenAISettings()
model_id = openai_settings.chat_model_id if openai_settings.chat_model_id else "gpt-5"
definition = await client.beta.assistants.create(
model=model_id,
name="CoderAgent",
description="A helpful assistant that writes and executes code to process and analyze data.",
instructions="You solve questions using code. Please provide detailed analysis and computation process.",
tools=code_interpreter_tool,
tool_resources=code_interpreter_tool_resources,
)
coder_agent = OpenAIAssistantAgent(
client=client,
definition=definition,
)
return [research_agent, coder_agent]
def sk_agent_response_callback(
message: ChatMessageContent | Sequence[ChatMessageContent],
) -> None:
if isinstance(message, ChatMessageContent):
messages: Sequence[ChatMessageContent] = [message]
elif isinstance(message, Sequence) and not isinstance(message, (str, bytes)):
messages = [item for item in message if isinstance(item, ChatMessageContent)]
else:
messages = []
for item in messages:
content = item.content or ""
print(f"**{item.name}**\n{content}\n")
async def run_semantic_kernel_example(prompt: str) -> Sequence[ChatMessageContent]:
agents = await build_semantic_kernel_agents()
magentic_orchestration = MagenticOrchestration(
members=agents,
manager=StandardMagenticManager(chat_completion_service=OpenAIChatCompletion()),
agent_response_callback=sk_agent_response_callback,
)
runtime = InProcessRuntime()
runtime.start()
try:
orchestration_result = await magentic_orchestration.invoke(task=prompt, runtime=runtime)
value = await orchestration_result.get()
if isinstance(value, ChatMessageContent):
return [value]
if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
return [item for item in value if isinstance(item, ChatMessageContent)]
return []
finally:
await runtime.stop_when_idle()
def _print_semantic_kernel_outputs(outputs: Sequence[ChatMessageContent]) -> None:
if not outputs:
print("No Semantic Kernel output.")
return
print("===== Semantic Kernel Magentic =====")
for item in outputs:
content = item.content or ""
print(f"**{item.name}**\n{content}\n")
######################################################################
# Agent Framework orchestration path
######################################################################
async def run_agent_framework_example(prompt: str) -> str | None:
researcher = ChatAgent(
name="ResearcherAgent",
description="Specialist in research and information gathering",
instructions=(
"You are a Researcher. You find information without additional computation or quantitative analysis."
),
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
)
coder = ChatAgent(
name="CoderAgent",
description="A helpful assistant that writes and executes code to process and analyze data.",
instructions="You solve questions using code. Please provide detailed analysis and computation process.",
chat_client=OpenAIResponsesClient(),
tools=HostedCodeInterpreterTool(),
)
# Create a manager agent for orchestration
manager_agent = ChatAgent(
name="MagenticManager",
description="Orchestrator that coordinates the research and coding workflow",
instructions="You coordinate a team to complete complex tasks efficiently.",
chat_client=OpenAIChatClient(),
)
workflow = (
MagenticBuilder()
.participants(researcher=researcher, coder=coder)
.with_standard_manager(agent=manager_agent)
.build()
)
final_text: str | None = None
async for event in workflow.run_stream(prompt):
if isinstance(event, WorkflowOutputEvent):
final_text = cast(str, event.data)
return final_text
def _print_agent_framework_output(result: str | None) -> None:
if result is None:
print("No Agent Framework output.")
return
print("===== Agent Framework Magentic =====")
print(result)
async def main() -> None:
agent_framework_result = await run_agent_framework_example(PROMPT)
_print_agent_framework_output(agent_framework_result)
semantic_kernel_outputs = await run_semantic_kernel_example(PROMPT)
_print_semantic_kernel_outputs(semantic_kernel_outputs)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,127 @@
# Copyright (c) Microsoft. All rights reserved.
"""Side-by-side sequential orchestrations for Agent Framework and Semantic Kernel."""
import asyncio
from collections.abc import Sequence
from typing import cast
from agent_framework import ChatMessage, Role, SequentialBuilder, WorkflowOutputEvent
from agent_framework.azure import AzureOpenAIChatClient
from azure.identity import AzureCliCredential
from semantic_kernel.agents import Agent, ChatCompletionAgent, SequentialOrchestration
from semantic_kernel.agents.runtime import InProcessRuntime
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents import ChatMessageContent
PROMPT = "Write a tagline for a budget-friendly eBike."
######################################################################
# Semantic Kernel orchestration path
######################################################################
def build_semantic_kernel_agents() -> list[Agent]:
credential = AzureCliCredential()
writer_agent = ChatCompletionAgent(
name="WriterAgent",
instructions=("You are a concise copywriter. Provide a single, punchy marketing sentence based on the prompt."),
service=AzureChatCompletion(credential=credential),
)
reviewer_agent = ChatCompletionAgent(
name="ReviewerAgent",
instructions=("You are a thoughtful reviewer. Give brief feedback on the previous assistant message."),
service=AzureChatCompletion(credential=credential),
)
return [writer_agent, reviewer_agent]
async def sk_agent_response_callback(
message: ChatMessageContent | Sequence[ChatMessageContent],
) -> None:
if isinstance(message, ChatMessageContent):
messages: Sequence[ChatMessageContent] = [message]
elif isinstance(message, Sequence) and not isinstance(message, (str, bytes)):
messages = list(message)
else:
messages = [cast(ChatMessageContent, message)]
for item in messages:
content = item.content or ""
print(f"# {item.name}\n{content}\n")
######################################################################
# Agent Framework orchestration path
######################################################################
async def run_agent_framework_example(prompt: str) -> list[ChatMessage]:
chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
writer = chat_client.as_agent(
instructions=("You are a concise copywriter. Provide a single, punchy marketing sentence based on the prompt."),
name="writer",
)
reviewer = chat_client.as_agent(
instructions=("You are a thoughtful reviewer. Give brief feedback on the previous assistant message."),
name="reviewer",
)
workflow = SequentialBuilder().participants([writer, reviewer]).build()
conversation_outputs: list[list[ChatMessage]] = []
async for event in workflow.run_stream(prompt):
if isinstance(event, WorkflowOutputEvent):
conversation_outputs.append(cast(list[ChatMessage], event.data))
return conversation_outputs[-1] if conversation_outputs else []
async def run_semantic_kernel_example(prompt: str) -> str:
sequential_orchestration = SequentialOrchestration(
members=build_semantic_kernel_agents(),
agent_response_callback=sk_agent_response_callback,
)
runtime = InProcessRuntime()
runtime.start()
try:
orchestration_result = await sequential_orchestration.invoke(task=prompt, runtime=runtime)
final_message = await orchestration_result.get(timeout=20)
if isinstance(final_message, ChatMessageContent):
return final_message.content or ""
return str(final_message)
finally:
await runtime.stop_when_idle()
def _format_conversation(conversation: list[ChatMessage]) -> None:
if not conversation:
print("No Agent Framework output.")
return
print("===== Agent Framework Sequential =====")
for index, message in enumerate(conversation, start=1):
name = message.author_name or ("assistant" if message.role == Role.ASSISTANT else "user")
print(f"{'-' * 60}\n{index:02d} [{name}]\n{message.text}")
print()
async def main() -> None:
conversation = await run_agent_framework_example(PROMPT)
_format_conversation(conversation)
print("===== Semantic Kernel Sequential =====")
final_text = await run_semantic_kernel_example(PROMPT)
print(final_text)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,254 @@
# Copyright (c) Microsoft. All rights reserved.
"""Side-by-side sample comparing Semantic Kernel Process Framework and Agent Framework workflows."""
import asyncio
import logging
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, ClassVar, cast
######################################################################
# region Agent Framework imports
######################################################################
from agent_framework import Executor, WorkflowBuilder, WorkflowContext, WorkflowOutputEvent, handler
from pydantic import BaseModel, Field
######################################################################
# region Semantic Kernel imports
######################################################################
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.functions import kernel_function
from semantic_kernel.processes.kernel_process.kernel_process_event import KernelProcessEvent
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
from semantic_kernel.processes.kernel_process.kernel_process_step_state import KernelProcessStepState
from semantic_kernel.processes.process_builder import ProcessBuilder
if TYPE_CHECKING:
from semantic_kernel.processes.kernel_process import KernelProcess
from semantic_kernel.processes.local_runtime.local_kernel_process import LocalKernelProcessContext
async def _start_local_kernel_process(
*,
process: "KernelProcess",
kernel: Kernel,
initial_event: KernelProcessEvent | str | Enum,
**kwargs: object,
) -> "LocalKernelProcessContext":
from semantic_kernel.processes.local_runtime.local_kernel_process import start as start_local_kernel_process
return await start_local_kernel_process(
process=process,
kernel=kernel,
initial_event=initial_event,
**kwargs,
)
logging.basicConfig(level=logging.WARNING)
class CommonEvents(Enum):
"""Common events for both samples."""
USER_INPUT_RECEIVED = "UserInputReceived"
COMPLETION_RESPONSE_GENERATED = "CompletionResponseGenerated"
WELCOME_DONE = "WelcomeDone"
A_STEP_DONE = "AStepDone"
B_STEP_DONE = "BStepDone"
C_STEP_DONE = "CStepDone"
START_A_REQUESTED = "StartARequested"
START_B_REQUESTED = "StartBRequested"
EXIT_REQUESTED = "ExitRequested"
START_PROCESS = "StartProcess"
######################################################################
# region Semantic Kernel Process Framework path
######################################################################
class KickOffStep(KernelProcessStep[None]):
KICK_OFF_FUNCTION: ClassVar[str] = "kick_off"
@kernel_function(name=KICK_OFF_FUNCTION)
async def print_welcome_message(self, context: KernelProcessStepContext):
await context.emit_event(process_event=CommonEvents.START_A_REQUESTED, data="Get Going A")
await context.emit_event(process_event=CommonEvents.START_B_REQUESTED, data="Get Going B")
class AStep(KernelProcessStep[None]):
@kernel_function()
async def do_it(self, context: KernelProcessStepContext):
await asyncio.sleep(1)
await context.emit_event(process_event=CommonEvents.A_STEP_DONE.value, data="I did A")
class BStep(KernelProcessStep[None]):
@kernel_function()
async def do_it(self, context: KernelProcessStepContext):
await asyncio.sleep(2)
await context.emit_event(process_event=CommonEvents.B_STEP_DONE.value, data="I did B")
class CStepState(BaseModel):
current_cycle: int = 0
class CStep(KernelProcessStep[CStepState]):
state: CStepState = Field(default_factory=CStepState)
async def activate(self, state: KernelProcessStepState[CStepState]):
self.state = state.state
@kernel_function()
async def do_it(self, context: KernelProcessStepContext, astepdata: str, bstepdata: str):
self.state.current_cycle += 1
print(f"CStep Current Cycle: {self.state.current_cycle}")
if self.state.current_cycle == 3:
print("CStep Exit Requested")
await context.emit_event(process_event=CommonEvents.EXIT_REQUESTED.value)
return
await context.emit_event(process_event=CommonEvents.C_STEP_DONE.value)
kernel = Kernel()
async def run_semantic_kernel_process_example() -> None:
kernel.add_service(OpenAIChatCompletion(service_id="default"))
process = ProcessBuilder(name="Process Framework Sample")
kickoff_step = process.add_step(step_type=KickOffStep)
step_a = process.add_step(step_type=AStep)
step_b = process.add_step(step_type=BStep)
step_c = process.add_step(step_type=CStep)
process.on_input_event(event_id=CommonEvents.START_PROCESS.value).send_event_to(target=kickoff_step)
kickoff_step.on_event(event_id=CommonEvents.START_A_REQUESTED.value).send_event_to(target=step_a)
kickoff_step.on_event(event_id=CommonEvents.START_B_REQUESTED.value).send_event_to(target=step_b)
step_a.on_event(event_id=CommonEvents.A_STEP_DONE.value).send_event_to(target=step_c, parameter_name="astepdata")
step_b.on_event(event_id=CommonEvents.B_STEP_DONE.value).send_event_to(target=step_c, parameter_name="bstepdata")
step_c.on_event(event_id=CommonEvents.C_STEP_DONE.value).send_event_to(target=kickoff_step)
step_c.on_event(event_id=CommonEvents.EXIT_REQUESTED.value).stop_process()
kernel_process: "KernelProcess" = process.build()
async with await _start_local_kernel_process(
process=kernel_process,
kernel=kernel,
initial_event=KernelProcessEvent(id=CommonEvents.START_PROCESS.value, data="Initial"),
) as process_context:
process_state = await process_context.get_executor_state()
c_step_state: KernelProcessStepState[CStepState] | None = next(
(s.state for s in process_state.steps if s.state.name == "CStep"),
None,
)
if c_step_state is None or c_step_state.state is None:
raise RuntimeError("CStep state unavailable")
assert c_step_state.state.current_cycle == 3 # nosec
print(f"Final State Check: CStepState current cycle: {c_step_state.state.current_cycle}")
######################################################################
# region Agent Framework workflow path
######################################################################
@dataclass
class StepResult:
origin: str
cycle: int
data: str
class KickOffExecutor(Executor):
def __init__(self, *, id: str = "kickoff") -> None:
super().__init__(id=id)
self._next_cycle = 0
@handler
async def handle(self, event: CommonEvents, ctx: WorkflowContext[int]) -> None:
if event not in {CommonEvents.START_PROCESS, CommonEvents.C_STEP_DONE}:
return
self._next_cycle += 1
await ctx.send_message(self._next_cycle)
class DelayedStepExecutor(Executor):
def __init__(self, *, name: str, delay_seconds: float) -> None:
super().__init__(id=name)
self._delay = delay_seconds
self._name = name
@handler
async def handle(self, cycle: int, ctx: WorkflowContext[StepResult]) -> None:
await asyncio.sleep(self._delay)
await ctx.send_message(StepResult(origin=self._name, cycle=cycle, data=f"I did {self._name.upper()[-1]}"))
class FanInExecutor(Executor):
def __init__(self, *, required_cycles: int = 3, id: str = "fanin") -> None:
super().__init__(id=id)
self._completed_cycles = 0
self._required_cycles = required_cycles
@handler
async def handle(self, results: list[StepResult], ctx: WorkflowContext[CommonEvents, str]) -> None:
if not results:
return
cycle_number = results[0].cycle
summary = ", ".join(f"{r.origin}: {r.data}" for r in results)
print(f"Cycle {cycle_number} aggregate -> {summary}")
self._completed_cycles += 1
if self._completed_cycles >= self._required_cycles:
await ctx.yield_output(f"Completed {self._completed_cycles} cycles")
return
await ctx.send_message(CommonEvents.C_STEP_DONE)
async def run_agent_framework_workflow_example() -> str | None:
kickoff = KickOffExecutor()
step_a = DelayedStepExecutor(name="step_a", delay_seconds=1)
step_b = DelayedStepExecutor(name="step_b", delay_seconds=2)
aggregate = FanInExecutor(required_cycles=3)
workflow = (
WorkflowBuilder()
.add_edge(kickoff, step_a)
.add_edge(kickoff, step_b)
.add_fan_in_edges([step_a, step_b], aggregate)
.add_edge(aggregate, kickoff)
.set_start_executor(kickoff)
.build()
)
final_text: str | None = None
async for event in workflow.run_stream(CommonEvents.START_PROCESS):
if isinstance(event, WorkflowOutputEvent):
final_text = cast(str, event.data)
return final_text
async def main() -> None:
print("===== Agent Framework Workflow =====")
af_result = await run_agent_framework_workflow_example()
if af_result:
print(af_result)
else:
print("No Agent Framework output.")
print("===== Semantic Kernel Process Framework =====")
await run_semantic_kernel_process_example()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,282 @@
# Copyright (c) Microsoft. All rights reserved.
"""Nested process comparison between Semantic Kernel Process Framework and Agent Framework sub-workflows."""
import asyncio
import logging
from collections.abc import Sequence
from dataclasses import dataclass
from enum import Enum
from typing import ClassVar, cast
######################################################################
# region Agent Framework imports
######################################################################
from agent_framework import (
Executor,
WorkflowBuilder,
WorkflowContext,
WorkflowExecutor,
WorkflowOutputEvent,
handler,
)
from pydantic import BaseModel, Field
######################################################################
# region Semantic Kernel imports
######################################################################
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.functions import kernel_function
from semantic_kernel.processes.kernel_process.kernel_process import KernelProcess
from semantic_kernel.processes.kernel_process.kernel_process_event import KernelProcessEventVisibility
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
from semantic_kernel.processes.kernel_process.kernel_process_step_state import KernelProcessStepState
from semantic_kernel.processes.local_runtime.local_kernel_process import start
from semantic_kernel.processes.process_builder import ProcessBuilder
from typing_extensions import Never
######################################################################
# endregion
######################################################################
logging.basicConfig(level=logging.WARNING)
class ProcessEvents(Enum):
START_PROCESS = "StartProcess"
START_INNER_PROCESS = "StartInnerProcess"
OUTPUT_READY_PUBLIC = "OutputReadyPublic"
OUTPUT_READY_INTERNAL = "OutputReadyInternal"
######################################################################
# region Semantic Kernel nested process path
######################################################################
class StepState(BaseModel):
last_message: str | None = None
class EchoStep(KernelProcessStep[None]):
ECHO: ClassVar[str] = "echo"
@kernel_function(name=ECHO)
async def echo(self, message: str) -> str:
print(f"[ECHO] {message}")
return message
class RepeatStep(KernelProcessStep[StepState]):
REPEAT: ClassVar[str] = "repeat"
state: StepState = Field(default_factory=StepState)
async def activate(self, state: KernelProcessStepState[StepState]):
self.state = state.state
@kernel_function(name=REPEAT)
async def repeat(
self,
message: str,
context: KernelProcessStepContext,
count: int = 2,
) -> None:
output = " ".join([message] * count)
self.state.last_message = output
print(f"[REPEAT] {output}")
await context.emit_event(
process_event=ProcessEvents.OUTPUT_READY_PUBLIC.value,
data=output,
visibility=KernelProcessEventVisibility.Public,
)
await context.emit_event(
process_event=ProcessEvents.OUTPUT_READY_INTERNAL.value,
data=output,
visibility=KernelProcessEventVisibility.Internal,
)
def _create_linear_process(name: str) -> ProcessBuilder:
process_builder = ProcessBuilder(name=name)
echo_step = process_builder.add_step(step_type=EchoStep)
repeat_step = process_builder.add_step(step_type=RepeatStep)
process_builder.on_input_event(event_id=ProcessEvents.START_PROCESS.value).send_event_to(target=echo_step)
echo_step.on_function_result(function_name=EchoStep.ECHO).send_event_to(
target=repeat_step,
parameter_name="message",
)
return process_builder
_semantic_kernel = Kernel()
async def run_semantic_kernel_nested_process() -> None:
_semantic_kernel.add_service(OpenAIChatCompletion(service_id="default"))
process_builder = _create_linear_process("Outer")
nested_process_step = process_builder.add_step_from_process(_create_linear_process("Inner"))
process_builder.steps[1].on_event(ProcessEvents.OUTPUT_READY_INTERNAL.value).send_event_to(
nested_process_step.where_input_event_is(ProcessEvents.START_PROCESS.value)
)
kernel_process = process_builder.build()
process_handle = await start(
process=kernel_process,
kernel=_semantic_kernel,
initial_event=ProcessEvents.START_PROCESS.value,
data="Test",
)
process_info = await process_handle.get_executor_state()
inner_process: KernelProcess | None = next(
(s for s in process_info.steps if s.state.name == "Inner"),
None,
)
if inner_process is None:
raise RuntimeError("Inner process state missing")
repeat_state: KernelProcessStepState[StepState] | None = next(
(s.state for s in inner_process.steps if s.state.name == "RepeatStep"),
None,
)
if repeat_state is None or repeat_state.state is None:
raise RuntimeError("RepeatStep state missing")
assert repeat_state.state.last_message == "Test Test Test Test" # nosec
######################################################################
# region Agent Framework nested workflow path
######################################################################
@dataclass
class RepeatPayload:
message: str
count: int = 2
class KickoffExecutor(Executor):
def __init__(self) -> None:
super().__init__(id="kickoff")
@handler
async def start(self, message: str, ctx: WorkflowContext[RepeatPayload]) -> None:
print(f"[OUTER] Start with message: {message}")
await ctx.send_message(RepeatPayload(message=message, count=2))
class OuterEchoExecutor(Executor):
def __init__(self) -> None:
super().__init__(id="outer_echo")
@handler
async def echo(self, payload: RepeatPayload, ctx: WorkflowContext[RepeatPayload]) -> None:
print(f"[OUTER ECHO] {payload.message}")
await ctx.send_message(payload)
class OuterRepeatExecutor(Executor):
def __init__(self, *, inner_target_id: str) -> None:
super().__init__(id="outer_repeat")
self._inner_target_id = inner_target_id
@handler
async def repeat(self, payload: RepeatPayload, ctx: WorkflowContext[RepeatPayload]) -> None:
repeated = " ".join([payload.message] * payload.count)
print(f"[OUTER REPEAT] {repeated}")
await ctx.send_message(RepeatPayload(message=repeated, count=2), target_id=self._inner_target_id)
class InnerEchoExecutor(Executor):
def __init__(self) -> None:
super().__init__(id="inner_echo")
@handler
async def echo(self, payload: RepeatPayload, ctx: WorkflowContext[RepeatPayload]) -> None:
print(f" [INNER ECHO] {payload.message}")
await ctx.send_message(payload)
class InnerRepeatExecutor(Executor):
def __init__(self) -> None:
super().__init__(id="inner_repeat")
@handler
async def repeat(self, payload: RepeatPayload, ctx: WorkflowContext[Never, str]) -> None:
repeated = " ".join([payload.message] * payload.count)
print(f" [INNER REPEAT] {repeated}")
await ctx.yield_output(repeated)
class CollectResultExecutor(Executor):
def __init__(self) -> None:
super().__init__(id="collector")
@handler
async def collect(self, result: str, ctx: WorkflowContext[Never, str]) -> None:
print(f"[COLLECTOR] Final result -> {result}")
await ctx.yield_output(result)
def _build_inner_workflow() -> WorkflowExecutor:
inner_echo = InnerEchoExecutor()
inner_repeat = InnerRepeatExecutor()
inner_workflow = WorkflowBuilder().set_start_executor(inner_echo).add_edge(inner_echo, inner_repeat).build()
return WorkflowExecutor(inner_workflow, id="inner_workflow")
async def run_agent_framework_nested_workflow(initial_message: str) -> Sequence[str]:
inner_executor = _build_inner_workflow()
kickoff = KickoffExecutor()
outer_echo = OuterEchoExecutor()
outer_repeat = OuterRepeatExecutor(inner_target_id=inner_executor.id)
collector = CollectResultExecutor()
outer_workflow = (
WorkflowBuilder()
.set_start_executor(kickoff)
.add_edge(kickoff, outer_echo)
.add_edge(outer_echo, outer_repeat)
.add_edge(outer_repeat, inner_executor)
.add_edge(inner_executor, collector)
.build()
)
results: list[str] = []
async for event in outer_workflow.run_stream(initial_message):
if isinstance(event, WorkflowOutputEvent):
results.append(cast(str, event.data))
return results
######################################################################
# endregion
######################################################################
async def main() -> None:
print("===== Agent Framework Nested Workflow =====")
af_results = await run_agent_framework_nested_workflow("Test")
for index, value in enumerate(af_results, start=1):
print(f"Result {index}: {value}")
print("\n===== Semantic Kernel Nested Process =====")
await run_semantic_kernel_nested_process()
if __name__ == "__main__":
asyncio.run(main())