chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
# 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.
|
||||
|
||||
## What’s Included
|
||||
|
||||
## What’s Included
|
||||
|
||||
### Chat completion parity
|
||||
- [01_basic_chat_completion.py](chat_completion/01_basic_chat_completion.py) — Minimal SK `ChatCompletionAgent` and AF `Agent` 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 session reuse and streaming prompts.
|
||||
|
||||
### Azure AI agent parity
|
||||
|
||||
### OpenAI Assistants API parity
|
||||
|
||||
OpenAI Assistants parity samples were removed alongside the deprecated Python assistants surface and are no longer
|
||||
part of this migration gallery.
|
||||
|
||||
### 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 repo’s editable packages if you are developing locally.
|
||||
- Service credentials exposed through environment variables (for example `OPENAI_API_KEY`, `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_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.
|
||||
- Sessions/conversation state are explicit in AF. When porting SK code that relies on implicit session reuse, call `agent.create_session()` and pass it into each `run` call.
|
||||
- Tools map cleanly: SK `@kernel_function` plugins translate to AF `@tool` 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.
|
||||
@@ -0,0 +1,64 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/chat_completion/01_basic_chat_completion.py
|
||||
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
"""Basic SK ChatCompletionAgent vs Agent Framework Agent.
|
||||
|
||||
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
|
||||
|
||||
from agent_framework import Agent
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
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 Agent created from OpenAIChatClient."""
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
# AF constructs a lightweight Agent backed by OpenAIChatClient.
|
||||
chat_agent = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
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())
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/chat_completion/02_chat_completion_with_tool.py
|
||||
|
||||
# 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
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
async def run_semantic_kernel() -> None:
|
||||
from semantic_kernel.agents import ChatCompletionAgent
|
||||
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()],
|
||||
)
|
||||
response = await agent.get_response("What soup can I order today?")
|
||||
print("[SK]", response.message.content)
|
||||
|
||||
|
||||
async def run_agent_framework() -> None:
|
||||
from agent_framework import Agent, tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
@tool(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 = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
name="Host",
|
||||
instructions="Answer menu questions accurately.",
|
||||
tools=[specials],
|
||||
)
|
||||
reply = await chat_agent.run("What soup can I order today?")
|
||||
print("[AF]", reply.text)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
await run_semantic_kernel()
|
||||
await run_agent_framework()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/chat_completion/03_chat_completion_thread_and_stream.py
|
||||
|
||||
# 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
|
||||
|
||||
from agent_framework import Agent
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
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 session objects are requested explicitly from the agent.
|
||||
chat_agent = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
name="Writer",
|
||||
instructions="Keep answers short and friendly.",
|
||||
)
|
||||
session = chat_agent.create_session()
|
||||
|
||||
first = await chat_agent.run(
|
||||
"Suggest a catchy headline for our product launch.",
|
||||
session=session,
|
||||
)
|
||||
print("[AF]", first.text)
|
||||
|
||||
print("[AF][stream]", end=" ")
|
||||
async for chunk in chat_agent.run(
|
||||
"Draft a 2 sentence blurb.",
|
||||
session=session,
|
||||
stream=True,
|
||||
):
|
||||
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())
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-copilotstudio",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/copilot_studio/01_basic_copilot_studio_agent.py
|
||||
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
"""Call a Copilot Studio agent with SK and Agent Framework."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
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())
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-copilotstudio",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/copilot_studio/02_copilot_studio_streaming.py
|
||||
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
"""Stream responses from Copilot Studio agents in SK and AF."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
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("Plan a day in Copenhagen for foodies.", stream=True):
|
||||
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())
|
||||
@@ -0,0 +1,61 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/openai_responses/01_basic_responses_agent.py
|
||||
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
"""Issue a basic Responses API call using SK and Agent Framework."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
async def run_semantic_kernel() -> None:
|
||||
from semantic_kernel.agents import OpenAIResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAISettings
|
||||
|
||||
openai_settings = OpenAISettings()
|
||||
assert openai_settings.responses_model_id is not None, "Responses model ID must be set in OpenAISettings"
|
||||
|
||||
client = OpenAIResponsesAgent.create_client()
|
||||
# SK response agents wrap OpenAI's hosted Responses API.
|
||||
agent = OpenAIResponsesAgent(
|
||||
ai_model_id=openai_settings.responses_model_id,
|
||||
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)
|
||||
|
||||
|
||||
async def run_agent_framework() -> None:
|
||||
from agent_framework import Agent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
# AF Agent can swap in an OpenAIChatClient directly.
|
||||
chat_agent = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
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())
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/openai_responses/02_responses_agent_with_tool.py
|
||||
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
"""Attach a lightweight function tool to the Responses API in SK and AF."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
async def run_semantic_kernel() -> None:
|
||||
from semantic_kernel.agents import OpenAIResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAISettings
|
||||
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
|
||||
|
||||
openai_settings = OpenAISettings()
|
||||
assert openai_settings.responses_model_id is not None, "Responses model ID must be set in OpenAISettings"
|
||||
|
||||
client = OpenAIResponsesAgent.create_client()
|
||||
# Plugins advertise callable tools to the Responses agent.
|
||||
agent = OpenAIResponsesAgent(
|
||||
ai_model_id=openai_settings.responses_model_id,
|
||||
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)
|
||||
|
||||
|
||||
async def run_agent_framework() -> None:
|
||||
from agent_framework import Agent, tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
@tool(name="add", description="Add two numbers")
|
||||
async def add(a: float, b: float) -> float:
|
||||
return a + b
|
||||
|
||||
chat_agent = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
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())
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/openai_responses/03_responses_agent_structured_output.py
|
||||
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
"""Request structured JSON output from the Responses API in SK and AF."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class ReleaseBrief(BaseModel):
|
||||
feature: str
|
||||
benefit: str
|
||||
launch_date: str
|
||||
|
||||
|
||||
async def run_semantic_kernel() -> None:
|
||||
from semantic_kernel.agents import OpenAIResponsesAgent
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAISettings
|
||||
|
||||
openai_settings = OpenAISettings()
|
||||
assert openai_settings.responses_model_id is not None, "Responses model ID must be set in OpenAISettings"
|
||||
|
||||
client = OpenAIResponsesAgent.create_client()
|
||||
# response_format requests schema-constrained output from the model.
|
||||
agent = OpenAIResponsesAgent(
|
||||
ai_model_id=openai_settings.responses_model_id,
|
||||
client=client,
|
||||
instructions="Return launch briefs as structured JSON.",
|
||||
name="ProductMarketer",
|
||||
text=OpenAIResponsesAgent.configure_response_format(ReleaseBrief), # type: ignore
|
||||
)
|
||||
response = await agent.get_response(
|
||||
"Draft a launch brief for the Contoso Note app.",
|
||||
)
|
||||
print("[SK]", response.message.content)
|
||||
|
||||
|
||||
async def run_agent_framework() -> None:
|
||||
from agent_framework import Agent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
chat_agent = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
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())
|
||||
@@ -0,0 +1,141 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "agent-framework-orchestrations",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/orchestrations/concurrent_basic.py
|
||||
|
||||
# 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 Agent, Message
|
||||
from agent_framework.openai import OpenAIChatCompletionClient
|
||||
from agent_framework.orchestrations import ConcurrentBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
from dotenv import load_dotenv
|
||||
from semantic_kernel.agents import ChatCompletionAgent, ConcurrentOrchestration
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.contents import ChatMessageContent
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
PROMPT = "Explain the concept of temperature from multiple scientific perspectives."
|
||||
|
||||
|
||||
######################################################################
|
||||
# Semantic Kernel orchestration path
|
||||
######################################################################
|
||||
|
||||
|
||||
def build_semantic_kernel_agents() -> list[ChatCompletionAgent]:
|
||||
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()) # type: ignore
|
||||
|
||||
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[Message]]:
|
||||
client = OpenAIChatCompletionClient(credential=AzureCliCredential())
|
||||
|
||||
physics = Agent(
|
||||
client=client,
|
||||
instructions=("You are an expert in physics. Answer questions from a physics perspective."),
|
||||
name="physics",
|
||||
)
|
||||
|
||||
chemistry = Agent(
|
||||
client=client,
|
||||
instructions=("You are an expert in chemistry. Answer questions from a chemistry perspective."),
|
||||
name="chemistry",
|
||||
)
|
||||
|
||||
workflow = ConcurrentBuilder(participants=[physics, chemistry]).build()
|
||||
|
||||
outputs: list[list[Message]] = []
|
||||
async for event in workflow.run(prompt, stream=True):
|
||||
if event.type == "output":
|
||||
outputs.append(cast(list[Message], event.data))
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
def _print_agent_framework_outputs(conversations: Sequence[Sequence[Message]]) -> 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())
|
||||
@@ -0,0 +1,291 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "agent-framework-orchestrations",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/orchestrations/group_chat.py
|
||||
|
||||
# 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 Agent, AgentResponseUpdate, Message
|
||||
from agent_framework.openai import OpenAIChatCompletionClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
from dotenv import load_dotenv
|
||||
from semantic_kernel.agents import 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
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
DISCUSSION_TOPIC = "What are the essential steps for launching a community hackathon?"
|
||||
|
||||
|
||||
######################################################################
|
||||
# Semantic Kernel orchestration path
|
||||
######################################################################
|
||||
|
||||
|
||||
def build_semantic_kernel_agents() -> list[ChatCompletionAgent]:
|
||||
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."""
|
||||
|
||||
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, max_rounds: int | None = None) -> None:
|
||||
super().__init__(max_rounds=max_rounds)
|
||||
|
||||
self._round_robin_index = 0
|
||||
self._topic = topic
|
||||
self._service = service
|
||||
|
||||
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),
|
||||
)
|
||||
return BooleanResult.model_validate_json(response.content) # type: ignore
|
||||
|
||||
@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) # type: ignore
|
||||
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) # type: ignore
|
||||
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(), # type: ignore
|
||||
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:
|
||||
client = OpenAIChatCompletionClient(credential=AzureCliCredential())
|
||||
|
||||
researcher = Agent(
|
||||
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."
|
||||
),
|
||||
client=client,
|
||||
)
|
||||
|
||||
planner = Agent(
|
||||
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."),
|
||||
client=client,
|
||||
)
|
||||
|
||||
workflow = GroupChatBuilder(
|
||||
participants=[researcher, planner],
|
||||
orchestrator_agent=Agent(client=client),
|
||||
max_rounds=8,
|
||||
intermediate_output_from=[researcher, planner],
|
||||
).build()
|
||||
|
||||
output_messages: list[Message] = []
|
||||
last_message_id: str | None = None
|
||||
async for event in workflow.run(task, stream=True):
|
||||
if event.type in ("intermediate", "output"):
|
||||
if isinstance(event.data, AgentResponseUpdate):
|
||||
if event.data.message_id != last_message_id:
|
||||
last_message_id = event.data.message_id
|
||||
print(f"{event.data.author_name}: {event.data.text}", end="")
|
||||
else:
|
||||
print(event.data.text, end="")
|
||||
else:
|
||||
output_messages.extend(cast(list[Message], event.data))
|
||||
for message in output_messages:
|
||||
print(f"[{message.author_name}] {message.text}")
|
||||
|
||||
if output_messages:
|
||||
return output_messages[-1].text
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
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())
|
||||
@@ -0,0 +1,311 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "agent-framework-orchestrations",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/orchestrations/handoff.py
|
||||
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
"""Side-by-side handoff orchestrations for Semantic Kernel and Agent Framework."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import AsyncIterable, Callable, Iterator, Sequence
|
||||
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
Message,
|
||||
WorkflowEvent,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatCompletionClient
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
from dotenv import load_dotenv
|
||||
from semantic_kernel.agents import Agent as SKAgent
|
||||
from semantic_kernel.agents import 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
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
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[SKAgent], 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[[], ChatMessageContent]:
|
||||
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: OpenAIChatCompletionClient):
|
||||
triage = Agent(
|
||||
client=client,
|
||||
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"
|
||||
),
|
||||
require_per_service_call_history_persistence=True,
|
||||
)
|
||||
refund = Agent(
|
||||
client=client,
|
||||
name="refund_agent",
|
||||
instructions=(
|
||||
"Handle refunds. Ask for order id and reason. If shipping info is needed, hand off to order_status_agent."
|
||||
),
|
||||
require_per_service_call_history_persistence=True,
|
||||
)
|
||||
status = Agent(
|
||||
client=client,
|
||||
name="order_status_agent",
|
||||
instructions=(
|
||||
"Provide order status, tracking, and timelines. If billing questions appear, hand off to refund_agent."
|
||||
),
|
||||
require_per_service_call_history_persistence=True,
|
||||
)
|
||||
returns = Agent(
|
||||
client=client,
|
||||
name="order_return_agent",
|
||||
instructions=(
|
||||
"Coordinate returns, confirm addresses, and summarize next steps. Hand off to triage_agent if unsure."
|
||||
),
|
||||
require_per_service_call_history_persistence=True,
|
||||
)
|
||||
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[WorkflowEvent]:
|
||||
requests: list[WorkflowEvent] = []
|
||||
for event in events:
|
||||
if event.type == "request_info" and isinstance(event.data, HandoffAgentUserRequest):
|
||||
requests.append(event)
|
||||
return requests
|
||||
|
||||
|
||||
def _extract_final_conversation(events: list[WorkflowEvent]) -> list[Message]:
|
||||
for event in events:
|
||||
if event.type == "output":
|
||||
return event.data
|
||||
return []
|
||||
|
||||
|
||||
async def run_agent_framework_example(initial_task: str, scripted_responses: Sequence[str]) -> str:
|
||||
client = OpenAIChatCompletionClient(credential=AzureCliCredential())
|
||||
triage, refund, status, returns = _create_af_agents(client)
|
||||
|
||||
workflow = (
|
||||
HandoffBuilder(
|
||||
name="sk_af_handoff_migration",
|
||||
participants=[triage, refund, status, returns],
|
||||
termination_condition=lambda conv: sum(1 for m in conv if m.role == "user") >= 4,
|
||||
)
|
||||
.with_start_agent(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(initial_task, stream=True))
|
||||
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: [Message(role="user", contents=[user_reply])] for request in pending}
|
||||
final_events = await _drain_events(workflow.run(stream=True, responses=responses))
|
||||
pending = _collect_handoff_requests(final_events)
|
||||
|
||||
conversation = _extract_final_conversation(final_events)
|
||||
if not conversation:
|
||||
return ""
|
||||
|
||||
# Render final transcript succinctly.
|
||||
lines: list[str] = []
|
||||
for message in conversation:
|
||||
text = message.text or ""
|
||||
if not text.strip():
|
||||
continue
|
||||
speaker = message.author_name or message.role
|
||||
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())
|
||||
@@ -0,0 +1,211 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "agent-framework-orchestrations",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/orchestrations/magentic.py
|
||||
|
||||
# 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 Agent, AgentResponseUpdate, Message
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.orchestrations import MagenticBuilder
|
||||
from dotenv import load_dotenv
|
||||
from semantic_kernel.agents import (
|
||||
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
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
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[ChatCompletionAgent | OpenAIAssistantAgent]:
|
||||
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="gpt-4o-mini-search-preview"),
|
||||
)
|
||||
|
||||
client = OpenAIAssistantAgent.create_client()
|
||||
code_interpreter_tool, code_interpreter_tool_resources = OpenAIAssistantAgent.configure_code_interpreter_tool()
|
||||
openai_settings = OpenAISettings()
|
||||
model = openai_settings.chat_model if openai_settings.chat_model else "gpt-5"
|
||||
definition = await client.beta.assistants.create( # pyright: ignore[reportDeprecated]
|
||||
model=model,
|
||||
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, # type: ignore
|
||||
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 = Agent(
|
||||
name="ResearcherAgent",
|
||||
description="Specialist in research and information gathering",
|
||||
instructions=(
|
||||
"You are a Researcher. You find information without additional computation or quantitative analysis."
|
||||
),
|
||||
client=OpenAIChatClient(model="gpt-4o-mini-search-preview"),
|
||||
)
|
||||
|
||||
# Create code interpreter tool using static method
|
||||
coder_client = OpenAIChatClient()
|
||||
code_interpreter_tool = OpenAIChatClient.get_code_interpreter_tool()
|
||||
|
||||
coder = Agent(
|
||||
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.",
|
||||
client=coder_client,
|
||||
tools=[code_interpreter_tool],
|
||||
)
|
||||
|
||||
# Create a manager agent for orchestration
|
||||
manager_agent = Agent(
|
||||
name="MagenticManager",
|
||||
description="Orchestrator that coordinates the research and coding workflow",
|
||||
instructions="You coordinate a team to complete complex tasks efficiently.",
|
||||
client=OpenAIChatClient(),
|
||||
)
|
||||
|
||||
workflow = MagenticBuilder(
|
||||
participants=[researcher, coder],
|
||||
manager_agent=manager_agent, # type: ignore
|
||||
intermediate_output_from=[researcher, coder],
|
||||
).build()
|
||||
|
||||
output_messages: list[Message] = []
|
||||
last_message_id: str | None = None
|
||||
async for event in workflow.run(prompt, stream=True):
|
||||
if event.type in ("intermediate", "output"):
|
||||
if isinstance(event.data, AgentResponseUpdate):
|
||||
if event.data.message_id != last_message_id:
|
||||
last_message_id = event.data.message_id
|
||||
print(f"{event.data.author_name}: {event.data.text}", end="")
|
||||
else:
|
||||
print(event.data.text, end="")
|
||||
else:
|
||||
output_messages.extend(cast(list[Message], event.data))
|
||||
for message in output_messages:
|
||||
print(f"[{message.author_name}] {message.text}")
|
||||
|
||||
if output_messages:
|
||||
return output_messages[-1].text
|
||||
|
||||
return None
|
||||
|
||||
|
||||
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())
|
||||
@@ -0,0 +1,146 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-openai",
|
||||
# "agent-framework-orchestrations",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/orchestrations/sequential.py
|
||||
|
||||
# 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 Agent, Message
|
||||
from agent_framework.openai import OpenAIChatCompletionClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
from dotenv import load_dotenv
|
||||
from semantic_kernel.agents import Agent as SKAgent
|
||||
from semantic_kernel.agents import ChatCompletionAgent, SequentialOrchestration
|
||||
from semantic_kernel.agents.runtime import InProcessRuntime
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.contents import ChatMessageContent
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
PROMPT = "Write a tagline for a budget-friendly eBike."
|
||||
|
||||
|
||||
######################################################################
|
||||
# Semantic Kernel orchestration path
|
||||
######################################################################
|
||||
|
||||
|
||||
def build_semantic_kernel_agents() -> list[SKAgent]:
|
||||
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[Message]:
|
||||
client = OpenAIChatCompletionClient(credential=AzureCliCredential())
|
||||
|
||||
writer = Agent(
|
||||
client=client,
|
||||
instructions=("You are a concise copywriter. Provide a single, punchy marketing sentence based on the prompt."),
|
||||
name="writer",
|
||||
)
|
||||
|
||||
reviewer = Agent(
|
||||
client=client,
|
||||
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[Message]] = []
|
||||
async for event in workflow.run(prompt, stream=True):
|
||||
if event.type == "output":
|
||||
conversation_outputs.append(cast(list[Message], 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[Message]) -> 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 == "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())
|
||||
@@ -0,0 +1,267 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-core",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/processes/fan_out_fan_in_process.py
|
||||
|
||||
# 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, handler
|
||||
from dotenv import load_dotenv
|
||||
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
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
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_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(start_executor=kickoff)
|
||||
.add_edge(kickoff, step_a)
|
||||
.add_edge(kickoff, step_b)
|
||||
.add_fan_in_edges([step_a, step_b], aggregate)
|
||||
.add_edge(aggregate, kickoff)
|
||||
.build()
|
||||
)
|
||||
|
||||
final_text: str | None = None
|
||||
async for event in workflow.run(CommonEvents.START_PROCESS, stream=True):
|
||||
if event.type == "output":
|
||||
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())
|
||||
@@ -0,0 +1,293 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "agent-framework-core",
|
||||
# "semantic-kernel",
|
||||
# ]
|
||||
# ///
|
||||
# Run with any PEP 723 compatible runner, e.g.:
|
||||
# uv run samples/semantic-kernel-migration/processes/nested_process.py
|
||||
|
||||
# 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,
|
||||
handler,
|
||||
)
|
||||
from dotenv import load_dotenv
|
||||
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)
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
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_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(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(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(initial_message, stream=True):
|
||||
if event.type == "output":
|
||||
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())
|
||||
Reference in New Issue
Block a user