M1 · Your First Agent¶
Goal: create and run an agent, understand the agent loop, and see the difference between a streaming and a non-streaming response.
You'll use:
get_chat_client()(the provider switcher) andAgent.
An agent is the simplest useful unit in the Microsoft Agent Framework:
Agent = model + instructions + (optionally) tools + a loop that runs until done.
In this first lab we keep it minimal — no tools yet — so you can see the core shape clearly. Everything later in the day builds on this.
1. Choose your model — once¶
The single cell below is how every lab in this workshop gets its model. It
reads MODEL_PROVIDER from your .env and returns the right client (Foundry,
OpenAI, Anthropic, Ollama, …). You never change the code below to switch
providers — you change one line in .env.
If this import fails, revisit Setup.
# Make the workshop_utils package importable when running from docs/modules/.
import sys, pathlib
sys.path.insert(0, str(pathlib.Path.cwd().parents[1])) # repo root
from workshop_utils import get_chat_client, current_provider
print("Model provider:", current_provider())
client = get_chat_client()
print("Chat client: ", type(client).__name__)
2. Build the agent¶
An agent needs just three things: a client (the model), a name, and instructions (its system prompt / persona).
from agent_framework import Agent
agent = Agent(
client=client,
name="HelloAgent",
instructions="You are a friendly assistant. Keep your answers brief.",
)
agent
3. Run it (non-streaming)¶
agent.run(...) is an async call. In a notebook you can await it directly.
It returns the complete response once the agent is done.
result = await agent.run("What is the capital of France?")
print(result)
!!! note "What just happened?"
The framework sent your instructions + question to the model and returned the
answer. With no tools attached, the loop ran exactly one step. Add tools
(next module) and the same run() call may loop several times — calling
tools and feeding results back — before returning.
4. Run it (streaming)¶
For chat UIs you usually want tokens as they're generated. Pass stream=True
and iterate; each chunk carries a piece of text in chunk.text.
print("Agent: ", end="", flush=True)
async for chunk in agent.run("Tell me a one-sentence fun fact about octopuses.", stream=True):
if chunk.text:
print(chunk.text, end="", flush=True)
print()
5. The agent loop (the idea behind everything)¶
Every agent.run(...) executes this loop:
- Send the conversation + available tools to the model.
- If the model asks to call a tool, run it and feed the result back (go to 1).
- Otherwise, return the final answer.
┌──────────────┐ tool call ┌────────────┐
│ call model │ ─────────────► │ run tool │
└──────┬───────┘ ◄───────────── └────────────┘
│ final answer (result fed back)
▼
response
Right now there are no tools, so the loop is a single hop. In M2 you'll give the agent tools and watch the loop actually iterate.
🧪 Your turn¶
- Change the agent's
instructionsto give it a persona (e.g. "You are a pirate captain who answers in nautical metaphors") and re-run. - Ask it a multi-part question and notice it still returns in one step.
- Swap providers: stop the kernel, set a different
MODEL_PROVIDERin your.env(e.g.ollamaif you have it locally), restart, and re-run this whole notebook. The code didn't change — only the backend did. That's the whole point of the workshop's design.
✅ You built and ran your first agent. Next: give it hands. → M2 · Tools & Function Calling