From your first ChatAgent with Microsoft Agent Framework, to a production multi-agent workflow on Microsoft Foundry, with load balancing.
0 / 10 completed
Level 1 Β· Foundations
Your first ChatAgent
Goal: install Microsoft Agent Framework and create your first agent connected to a model hosted on Microsoft Foundry.
Microsoft Agent Framework (GA since April 2026) is the official convergence of Semantic Kernel and AutoGen into a single production SDK, available in Python and .NET. ChatAgent is its main abstraction: an agent that uses a "chat client" to talk to any model, including those deployed on Microsoft Foundry.
1. Install the dependencies
TERMINAL
pip install agent-framework agent-framework-azure-ai azure-identity
az login # authenticate against your Azure subscription
2. Your first agent
agent.py
import asyncio
from agent_framework import ChatAgent
from agent_framework.azure import AzureAIAgentClient
from azure.identity.aio import DefaultAzureCredential
async def main():
async with DefaultAzureCredential() as cred:
async with ChatAgent(
chat_client=AzureAIAgentClient(async_credential=cred),
name="assistant",
instructions="Answer briefly and clearly.",
) as agent:
response = await agent.run("What is an AI agent, in one sentence?")
print(response.text)
asyncio.run(main())
AzureAIAgentClient connects directly to a Microsoft Foundry project β the model, authentication, and endpoint are already managed on the Azure side, you just reference the project.
Challenge
Run the same agent with agent-framework devui to inspect the execution in the local visual debugger.
Level 2 Β· Personality
Instructions and model parameters
Goal: control the agent's behavior with instructions and generation parameters.
agent.py
agent = ChatAgent(
chat_client=AzureAIAgentClient(async_credential=cred),
name="support",
instructions=(
"You are a technical support assistant. Answer in English, "
"briefly and with numbered steps. If you don't know something, say so."
),
temperature=0.3,
max_tokens=400,
)
response = await agent.run("The app crashes when I open the camera")
The temperature and max_tokens parameters are passed directly to ChatAgent, with no need to configure a separate model object.
Challenge
Write instructions for an agent that only answers questions about a specific domain and politely declines any other question.
Level 3 Β· Tools
Tools: plain Python functions
Goal: give the agent tools by passing Python functions directly in tools.
agent.py
def current_weather(location: str) -> str:
"""Returns the current reported weather for a given location."""
data = {"Guatemala City": "22Β°C, partly cloudy"}
return data.get(location, "no data for that location")
agent = ChatAgent(
chat_client=AzureAIAgentClient(async_credential=cred),
name="weather-assistant",
instructions="You help look up the weather in cities.",
tools=current_weather, # also accepts a list: tools=[current_weather, other_tool]
)
response = await agent.run("What's the weather like in Guatemala City?")
Just like in other modern frameworks, the function's docstring and type hints are what the model uses to decide when and how to call it β there's no need to hand-write a JSON schema.
Challenge
Add HostedCodeInterpreterTool() to the tools list and ask the agent to compute basic statistics for a list of numbers.
Level 4 Β· Memory
Threads: conversational memory
Goal: keep context across turns using an explicit conversation thread.
In Agent Framework, each stateful conversation lives in a thread β you create it once and pass it to every agent.run() call so the agent remembers previous turns.
chat_with_memory.py
thread = agent.get_new_thread()
r1 = await agent.run("I'm looking for a laptop for graphic design", thread=thread)
r2 = await agent.run("Which is the cheapest of the ones you mentioned?", thread=thread)
print(r2.text) # the agent remembers the laptops mentioned in r1
Each thread is independent β that's what lets you serve several users in parallel with the same agent object, simply by creating a separate thread per user or session.
Challenge
Simulate two different users with two separate threads and confirm their conversations don't mix.
Level 5 Β· Structured output
Typed responses with Pydantic
Goal: force the agent to return a validated object instead of free-form text.
agent.py
from pydantic import BaseModel
class SupportTicket(BaseModel):
category: str
urgency: str
summary: str
agent = ChatAgent(
chat_client=AzureAIAgentClient(async_credential=cred),
name="classifier",
instructions="Classify the incoming support ticket.",
response_format=SupportTicket,
)
result = await agent.run("The app crashes when uploading a profile photo, it's urgent")
ticket: SupportTicket = result.value
print(ticket.category, ticket.urgency, ticket.summary)
Challenge
Define a Pydantic model to extract the rating, sentiment, and whether a specific problem is mentioned from a product review.
Level 6 Β· Streaming
run_stream for live responses
Goal: show the agent's response in real time, token by token.
stream.py
async for update in agent.run_stream("Explain what RAG is in 3 steps", thread=thread):
if update.text:
print(update.text, end="", flush=True)
ChatAgent supports both streaming and non-streaming responses with the same base API β you swap run() for run_stream() without restructuring the rest of the code.
Challenge
Wrap run_stream in a FastAPI endpoint with StreamingResponse for a live chat.
Level 7 Β· Multi-agent
Several specialized ChatAgents
Goal: create independent specialized agents as a first step before orchestrating them.
Before building a full workflow, the first multi-agent step is simply to have several ChatAgent instances with different instructions, each good at one thing β you'll handle composing them with a workflow in the next level.
specialists.py
sales_agent = ChatAgent(
chat_client=AzureAIAgentClient(async_credential=cred),
name="sales",
instructions="Specialist in pricing and product questions.",
)
support_agent = ChatAgent(
chat_client=AzureAIAgentClient(async_credential=cred),
name="support",
instructions="Specialist in technical errors and bugs.",
)
# An agent can use another agent as a tool, just like in other frameworks:
async def ask_sales(question: str) -> str:
"""Delegate a sales question to the sales specialist."""
r = await sales_agent.run(question)
return r.text
Challenge
Add ask_sales as a tool for a third "front desk" agent and confirm it delegates correctly.
Level 8 Β· Orchestrator
Workflows: graph-based orchestration
Goal: build an explicit orchestrator with WorkflowBuilder, Agent Framework's graph-based workflow system.
For predictable multi-agent orchestration in production, Agent Framework provides graph-based workflows β inherited from the "Semantic Kernel" side of the merger β where each agent is a node and the edges define the flow of data between them, with built-in patterns for sequential, concurrent, and handoff orchestration.
orchestrator.py
from agent_framework import WorkflowBuilder
supervisor = ChatAgent(
chat_client=AzureAIAgentClient(async_credential=cred),
name="supervisor",
instructions="Analyze the message and decide which specialist to send it to.",
)
workflow = (
WorkflowBuilder()
.add_node(supervisor)
.add_node(sales_agent)
.add_node(support_agent)
.add_edge(supervisor, sales_agent, condition=lambda r: "price" in r.text.lower())
.add_edge(supervisor, support_agent, condition=lambda r: "error" in r.text.lower())
.set_start(supervisor)
.build()
)
result = await workflow.run("I was charged twice for the subscription")
This gives you explicit, traceable control over routing (as opposed to letting a single agent "decide on its own" with tools) β key when you need to audit why the system made each decision.
Challenge
Add a third billing node with its own routing condition, and test an ambiguous message to see how the tie is resolved.
Level 9 Β· Production
DevUI, telemetry, and guardrails
Goal: visually debug the workflow and trace its execution with OpenTelemetry.
TERMINAL
agent-framework devui
# Local debugger in the browser: shows execution traces,
# message flow between agents, tool calls, and routing decisions in real time
Native telemetry via OpenTelemetry β every workflow step, every tool call, is traced and exportable to your observability backend
Inherited Semantic Kernel middleware: you can intercept every model call for logging, rate limiting, or content validation
Strongly typed state management β the state flowing between workflow nodes is validated, not a free-form dictionary
Challenge
Add middleware that logs to a structured log how long each node in the Level 8 workflow took to respond.
Level 10 Β· Final architecture
Microsoft Foundry Agent Service + load balancing
Goal: deploy the workflow as hosted agents on Microsoft Foundry, scaled and load-balanced.
Microsoft Foundry Agent Service is Azure's hosted layer for running agents in production without managing your own compute infrastructure β but when you need to control horizontal scaling yourself (for example, for workflows with custom logic like Level 8), you host it on Azure Container Apps or App Service behind a load balancer.
With this you have the full path: from a few-line ChatAgent in Level 1, to a production multi-agent workflow on Azure, with queues, balanced replicas, and end-to-end telemetry.
Final challenge
Package the support specialist from Level 7 as a container, deploy it with 2 replicas on Azure Container Apps, and put it behind Application Gateway.