Merge branch 'main' into flaky-test-report

This commit is contained in:
Giles Odigwe
2026-04-29 08:55:29 -07:00
Unverified
286 changed files with 23505 additions and 2447 deletions
@@ -0,0 +1,51 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Functional Workflow with Agents — Call agents inside @workflow
This sample shows how to call agents inside a functional workflow.
Agent calls are just regular async function calls — no special wrappers needed.
"""
import asyncio
from agent_framework import Agent, workflow
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
# <create_agents>
client = FoundryChatClient(credential=AzureCliCredential())
writer = Agent(
name="WriterAgent",
instructions="Write a short poem (4 lines max) about the given topic.",
client=client,
)
reviewer = Agent(
name="ReviewerAgent",
instructions="Review the given poem in one sentence. Is it good?",
client=client,
)
# </create_agents>
# <create_workflow>
@workflow
async def poem_workflow(topic: str) -> str:
"""Write a poem, then review it."""
poem = (await writer.run(f"Write a poem about: {topic}")).text
review = (await reviewer.run(f"Review this poem: {poem}")).text
return f"Poem:\n{poem}\n\nReview: {review}"
# </create_workflow>
async def main() -> None:
result = await poem_workflow.run("a cat learning to code")
print(result.get_outputs()[0])
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,59 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Functional Workflow Basics — Orchestrate async functions with @workflow
The functional API lets you write workflows as plain Python async functions.
No graph concepts, no edges, no executor classes — just call functions
and use native control flow (if/else, loops, asyncio.gather).
This sample builds a minimal pipeline with two steps:
1. Convert text to uppercase
2. Reverse the text
No external services are required.
"""
import asyncio
from agent_framework import workflow
# Plain async functions — no decorators needed
async def to_upper_case(text: str) -> str:
"""Convert input to uppercase."""
return text.upper()
async def reverse_text(text: str) -> str:
"""Reverse the string."""
return text[::-1]
# <create_workflow>
@workflow
async def text_workflow(text: str) -> str:
"""Uppercase the text, then reverse it."""
upper = await to_upper_case(text)
return await reverse_text(upper)
# </create_workflow>
async def main() -> None:
# <run_workflow>
result = await text_workflow.run("hello world")
print(f"Output: {result.get_outputs()}")
print(f"Final state: {result.get_final_state()}")
# </run_workflow>
"""
Expected output:
Output: ['DLROW OLLEH']
Final state: WorkflowRunState.IDLE
"""
if __name__ == "__main__":
asyncio.run(main())
@@ -12,9 +12,12 @@ from agent_framework import (
from typing_extensions import Never
"""
First Workflow Chain executors with edges
First Graph Workflow Chain executors with edges
This sample builds a minimal workflow with two steps:
The graph API gives you full control over execution topology: edges,
fan-out/fan-in, switch/case, and superstep-based checkpointing.
This sample builds a minimal graph workflow with two steps:
1. Convert text to uppercase (class-based executor)
2. Reverse the text (function-based executor)
+4 -2
View File
@@ -24,8 +24,10 @@ export FOUNDRY_MODEL="gpt-4o" # optional, defaults to gpt-4o
| 2 | [02_add_tools.py](02_add_tools.py) | Define a function tool with `@tool` and attach it to an agent. |
| 3 | [03_multi_turn.py](03_multi_turn.py) | Keep conversation history across turns with `AgentSession`. |
| 4 | [04_memory.py](04_memory.py) | Add dynamic context with a custom `ContextProvider`. |
| 5 | [05_first_workflow.py](05_first_workflow.py) | Chain executors into a workflow with edges. |
| 6 | [06_host_your_agent.py](06_host_your_agent.py) | Host a single agent with Azure Functions. |
| 5 | [05_functional_workflow_with_agents.py](05_functional_workflow_with_agents.py) | Call agents inside a functional workflow. |
| 6 | [06_functional_workflow_basics.py](06_functional_workflow_basics.py) | Write a workflow as a plain async function. |
| 7 | [07_first_graph_workflow.py](07_first_graph_workflow.py) | Chain executors into a graph workflow with edges. |
| 8 | [08_host_your_agent.py](08_host_your_agent.py) | Host a single agent with Azure Functions. |
Run any sample with:
@@ -75,11 +75,7 @@ def get_client(client_name: ClientName) -> SupportsChatGetResponse[Any]:
if client_name == "azure_openai_chat_completion":
return OpenAIChatCompletionClient(credential=AzureCliCredential())
if client_name == "foundry_chat":
return FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AzureCliCredential(),
)
return FoundryChatClient(credential=AzureCliCredential())
raise ValueError(f"Unsupported client name: {client_name}")
@@ -93,21 +89,6 @@ async def main(client_name: ClientName = "openai_chat") -> None:
print(f"Client: {client_name}")
print(f"User: {message.text}")
if isinstance(client, FoundryChatClient):
async with client:
if stream:
response_stream = client.get_response([message], stream=True, options={"tools": get_weather})
print("Assistant: ", end="")
async for chunk in response_stream:
if chunk.text:
print(chunk.text, end="")
print("")
else:
print(
f"Assistant: {await client.get_response([message], stream=False, options={'tools': get_weather})}"
)
return
if stream:
response_stream = client.get_response([message], stream=True, options={"tools": get_weather})
print("Assistant: ", end="")
@@ -24,6 +24,22 @@ The following environment variables can be configured:
| `GITHUB_COPILOT_TIMEOUT` | Request timeout in seconds | `60` |
| `GITHUB_COPILOT_LOG_LEVEL` | CLI log level | `info` |
## Observability
`GitHubCopilotAgent` has OpenTelemetry tracing built-in. To enable it, call `configure_otel_providers()` before running the agent:
```python
from agent_framework.observability import configure_otel_providers
from agent_framework.github import GitHubCopilotAgent
configure_otel_providers(enable_console_exporters=True)
async with GitHubCopilotAgent() as agent:
response = await agent.run("Hello!")
```
See the [observability samples](../../../02-agents/observability/) for full examples with OTLP exporters.
## Examples
| File | Description |
+14
View File
@@ -30,6 +30,20 @@ Once comfortable with these, explore the rest of the samples below.
## Samples Overview (by directory)
### functional
Write workflows as plain Python async functions — no graph concepts, no executor classes, no edges. Use native control flow (`if`/`else`, loops, `asyncio.gather`) for branching and parallelism.
| Sample | File | Concepts |
|---|---|---|
| Basic Pipeline | [functional/basic_pipeline.py](./functional/basic_pipeline.py) | Sequential steps as plain async functions |
| Basic Streaming Pipeline | [functional/basic_streaming_pipeline.py](./functional/basic_streaming_pipeline.py) | Stream workflow events in real time with `run(stream=True)` |
| Parallel Pipeline | [functional/parallel_pipeline.py](./functional/parallel_pipeline.py) | Fan-out/fan-in with `asyncio.gather` |
| Steps and Checkpointing | [functional/steps_and_checkpointing.py](./functional/steps_and_checkpointing.py) | `@step` decorator for per-step checkpointing and observability |
| Human-in-the-Loop Review | [functional/hitl_review.py](./functional/hitl_review.py) | HITL with `ctx.request_info()` and replay |
| Agent Integration | [functional/agent_integration.py](./functional/agent_integration.py) | Calling agents inside workflow steps |
| Naive Group Chat | [functional/naive_group_chat.py](./functional/naive_group_chat.py) | Simple round-robin group chat as a plain loop |
### agents
| Sample | File | Concepts |
@@ -26,8 +26,8 @@ Note on internal adapters:
You can safely ignore them when focusing on agent progress.
Prerequisites:
- FOUNDRY_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
- FOUNDRY_MODEL must be set to your Azure OpenAI model deployment name.
- FOUNDRY_PROJECT_ENDPOINT must be set to the Azure Foundry project endpoint.
- FOUNDRY_MODEL must be set to the model name for the Foundry chat client.
"""
@@ -68,28 +68,18 @@ async def main() -> None:
"""
Sample Output:
===== Final Conversation =====
===== Conversation =====
------------------------------------------------------------
01 [user]
Write a tagline for a budget-friendly eBike.
------------------------------------------------------------
02 [writer]
Ride farther, spend less—your affordable eBike adventure starts here.
------------------------------------------------------------
03 [reviewer]
This tagline clearly communicates affordability and the benefit of extended travel, making it
appealing to budget-conscious consumers. It has a friendly and motivating tone, though it could
be slightly shorter for more punch. Overall, a strong and effective suggestion!
===== as_agent() Conversation =====
------------------------------------------------------------
01 [writer]
Go electric, save big—your affordable ride awaits!
------------------------------------------------------------
02 [reviewer]
01 [reviewer]
Catchy and straightforward! The tagline clearly emphasizes both the electric aspect and the affordability of the
eBike. It's inviting and actionable. For even more impact, consider making it slightly shorter:
"Go electric, save big." Overall, this is an effective and appealing suggestion for a budget-friendly eBike.
Note:
`workflow.as_agent()` returns ONLY the final agent's response (the "answer") — the prior agents' work
is not included in the response. To observe intermediate agents while running as an agent, build with
`SequentialBuilder(participants=[...], intermediate_outputs=True)`; the intermediate replies are then
surfaced as `data` events and merged into the AgentResponse.
"""
@@ -0,0 +1,107 @@
# Copyright (c) Microsoft. All rights reserved.
"""Calling agents inside functional workflows.
Agent calls work inside @workflow as plain function calls — no decorator needed.
Just call the agent and use the result.
If you want per-step caching (so agent calls don't re-execute on HITL resume
or crash recovery), add @step. Since each agent call hits an LLM API (time +
money), @step is often worth it. But it's always opt-in.
This sample shows both approaches side-by-side so you can see the difference.
"""
import asyncio
from agent_framework import Agent, step, workflow
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
# ---------------------------------------------------------------------------
# Create agents
# ---------------------------------------------------------------------------
client = FoundryChatClient(credential=AzureCliCredential())
classifier_agent = Agent(
name="ClassifierAgent",
instructions=(
"Classify documents into one category: Technical, Legal, Marketing, or Scientific. "
"Reply with only the category name."
),
client=client,
)
writer_agent = Agent(
name="WriterAgent",
instructions="Summarize the given content in one sentence.",
client=client,
)
reviewer_agent = Agent(
name="ReviewerAgent",
instructions="Review the given summary in one sentence. Is it accurate and complete?",
client=client,
)
# ---------------------------------------------------------------------------
# Simplest approach: call agents directly inside the workflow.
# No @step, no wrappers — just plain function calls.
# ---------------------------------------------------------------------------
@workflow
async def simple_pipeline(document: str) -> str:
"""Process a document — agents called inline, no @step."""
classification = (await classifier_agent.run(f"Classify this document: {document}")).text
summary = (await writer_agent.run(f"Summarize: {document}")).text
review = (await reviewer_agent.run(f"Review this summary: {summary}")).text
return f"Classification: {classification}\nSummary: {summary}\nReview: {review}"
# ---------------------------------------------------------------------------
# With @step: agent results are cached. On HITL resume or checkpoint
# recovery, completed steps return their saved result instead of calling
# the LLM again. Worth it for expensive operations.
# ---------------------------------------------------------------------------
@step
async def classify_document(doc: str) -> str:
return (await classifier_agent.run(f"Classify this document: {doc}")).text
@step
async def generate_summary(doc: str) -> str:
return (await writer_agent.run(f"Summarize: {doc}")).text
@step
async def review_summary(summary: str) -> str:
return (await reviewer_agent.run(f"Review this summary: {summary}")).text
@workflow
async def cached_pipeline(document: str) -> str:
"""Same pipeline, but @step caches each agent call."""
classification = await classify_document(document)
summary = await generate_summary(document)
review = await review_summary(summary)
return f"Classification: {classification}\nSummary: {summary}\nReview: {review}"
async def main():
# Simple version — agents called inline
result = await simple_pipeline.run("This is a technical document about machine learning...")
print(result.get_outputs()[0])
# Cached version — same result, but steps won't re-execute on resume
result = await cached_pipeline.run("This is a technical document about machine learning...")
print(f"\nCached: {result.get_outputs()[0]}")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,58 @@
# Copyright (c) Microsoft. All rights reserved.
"""Basic sequential pipeline using the functional workflow API.
The simplest possible workflow: plain async functions orchestrated by @workflow.
No @step decorator needed — just write Python.
"""
import asyncio
from agent_framework import workflow
# These are plain async functions — no decorators needed.
# They run normally inside the workflow, just like any other Python function.
async def fetch_data(url: str) -> dict[str, str | int]:
"""Simulate fetching data from a URL."""
return {"url": url, "content": f"Data from {url}", "status": 200}
async def transform_data(data: dict[str, str | int]) -> str:
"""Transform raw data into a summary string."""
return f"[{data['status']}] {data['content']}"
# @workflow turns this async function into a FunctionalWorkflow object.
# Without it, this is just a normal async function. With it, you get:
# - .run() that returns a WorkflowRunResult with events and outputs
# - .run(stream=True) for streaming events in real time
# - .as_agent() to use this workflow anywhere an agent is expected
#
# The function's first parameter receives the input from .run("...").
# Add a `ctx: RunContext` parameter only if you need HITL, state, or custom events.
@workflow
async def data_pipeline(url: str) -> str:
"""A simple sequential data pipeline."""
raw = await fetch_data(url)
summary = await transform_data(raw)
# This is just a function — plain Python works between calls.
# No need to wrap every operation in a separate async function.
is_valid = len(summary) > 0 and "[200]" in summary
tag = "VALID" if is_valid else "INVALID"
# Returning a value automatically emits it as an output.
# Callers retrieve it via result.get_outputs().
return f"[{tag}] {summary}"
async def main():
# .run() is provided by @workflow — a plain async function wouldn't have it
result = await data_pipeline.run("https://example.com/api/data")
print("Output:", result.get_outputs()[0])
print("State:", result.get_final_state())
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,63 @@
# Copyright (c) Microsoft. All rights reserved.
"""Basic streaming pipeline using the functional workflow API.
Stream workflow events in real time with run(stream=True).
"""
import asyncio
from agent_framework import workflow
# Plain async functions — no decorators needed for simple helpers.
async def fetch_data(url: str) -> dict[str, str | int]:
"""Simulate fetching data from a URL."""
return {"url": url, "content": f"Data from {url}", "status": 200}
async def transform_data(data: dict[str, str | int]) -> str:
"""Transform raw data into a summary string."""
return f"[{data['status']}] {data['content']}"
async def validate_result(summary: str) -> bool:
"""Validate the transformed result."""
return len(summary) > 0 and "[200]" in summary
# @workflow enables .run(stream=True), which returns a ResponseStream
# you can iterate over with `async for`. Without @workflow, you'd just
# have a normal async function with no streaming capability.
@workflow
async def data_pipeline(url: str) -> str:
"""A simple sequential data pipeline."""
raw = await fetch_data(url)
summary = await transform_data(raw)
is_valid = await validate_result(summary)
return f"{summary} (valid={is_valid})"
async def main():
# run(stream=True) returns a ResponseStream that yields events as they
# are produced. The raw stream includes lifecycle events (started, status)
# alongside application events — filter by event.type to find what you need.
stream = data_pipeline.run("https://example.com/api/data", stream=True)
async for event in stream:
if event.type == "output":
print(f"Output: {event.data}")
# After iteration, get_final_response() returns the WorkflowRunResult
result = await stream.get_final_response()
print(f"Final state: {result.get_final_state()}")
"""
Expected output:
Output: [200] Data from https://example.com/api/data (valid=True)
Final state: WorkflowRunState.IDLE
"""
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,84 @@
# Copyright (c) Microsoft. All rights reserved.
"""Human-in-the-loop review pipeline using functional workflows.
Demonstrates ctx.request_info() for pausing the workflow to wait for
external input and resuming with run(responses={...}).
HITL works with or without @step. The difference is what happens on resume:
- Without @step: every function re-executes from the top (fine for cheap calls).
- With @step: completed functions return their saved result instantly.
This sample uses @step on write_draft() because it simulates an expensive
operation that shouldn't re-run just because the workflow was paused.
"""
import asyncio
from agent_framework import RunContext, WorkflowRunState, step, workflow
# @step saves the result. When the workflow resumes after the HITL pause,
# this returns its saved result instead of running the expensive operation again.
#
# In a real workflow you might call an agent here instead:
# @step
# async def write_draft(topic: str) -> str:
# return (await writer_agent.run(f"Write a draft about: {topic}")).text
@step
async def write_draft(topic: str) -> str:
"""Simulate writing a draft — expensive, shouldn't re-run on resume."""
print(f" write_draft executing for '{topic}'")
return f"Draft document about '{topic}': Lorem ipsum dolor sit amet..."
@step
async def revise_draft(draft: str, feedback: str) -> str:
"""Revise the draft based on feedback."""
return f"Revised: {draft[:50]}... [Applied feedback: {feedback}]"
@workflow
async def review_pipeline(topic: str, ctx: RunContext) -> str:
"""Write a draft, get human review, then revise."""
draft = await write_draft(topic)
# ctx.request_info() suspends the workflow here. The caller gets back
# a WorkflowRunResult with state IDLE_WITH_PENDING_REQUESTS and can
# inspect the pending request via result.get_request_info_events().
feedback = await ctx.request_info(
{"draft": draft, "instructions": "Please review this draft"},
response_type=str,
request_id="review_request",
)
# This only executes after the caller resumes with run(responses={...}).
# write_draft above returns its saved result (thanks to @step),
# request_info returns the provided response, and we continue here.
return await revise_draft(draft, feedback)
async def main():
# Phase 1: Run until the workflow pauses for human input
print("=== Phase 1: Initial run ===")
result1 = await review_pipeline.run("AI Safety")
# If request_info() was reached, the state is IDLE_WITH_PENDING_REQUESTS.
# If the workflow completed without hitting request_info(), it would be IDLE.
print(f"State: {(final_state := result1.get_final_state())}")
assert final_state == WorkflowRunState.IDLE_WITH_PENDING_REQUESTS
requests = result1.get_request_info_events()
print(f"Pending request: {requests[0].request_id}")
# Phase 2: Resume with the human's response
print("\n=== Phase 2: Resume with feedback ===")
print("(write_draft should NOT execute again — saved by @step)")
result2 = await review_pipeline.run(responses={"review_request": "Add more details about alignment research"})
print(f"State: {result2.get_final_state()}")
print(f"Output: {result2.get_outputs()[0]}")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,82 @@
# Copyright (c) Microsoft. All rights reserved.
"""Naive group chat using the functional workflow API.
A simple round-robin group chat where agents take turns responding.
Because it's just a function, you control the loop, the turn order,
and the termination condition with plain Python — no framework abstractions.
Compare this with the graph-based GroupChat orchestration to see how the
functional API lets you start simple and add complexity only when needed.
"""
import asyncio
from agent_framework import Agent, Message, workflow
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
# ---------------------------------------------------------------------------
# Create agents
# ---------------------------------------------------------------------------
client = FoundryChatClient(credential=AzureCliCredential())
expert = Agent(
name="PythonExpert",
instructions=(
"You are a Python expert in a group discussion. "
"Answer questions about Python and refine your answer based on feedback. "
"Keep responses concise (2-3 sentences)."
),
client=client,
)
critic = Agent(
name="Critic",
instructions=(
"You are a constructive critic in a group discussion. "
"Point out edge cases, gotchas, or missing nuances in the previous answer. "
"If the answer is solid, say so briefly."
),
client=client,
)
summarizer = Agent(
name="Summarizer",
instructions=(
"You are a summarizer in a group discussion. "
"After the discussion, provide a final concise summary that incorporates "
"the expert's answer and the critic's feedback. Keep it to 2-3 sentences."
),
client=client,
)
# ---------------------------------------------------------------------------
# A naive group chat is just a loop — no special framework needed
# ---------------------------------------------------------------------------
@workflow
async def group_chat(question: str) -> str:
"""Round-robin group chat: expert answers, critic reviews, summarizer wraps up."""
participants = [expert, critic, summarizer]
# Passing list[Message] keeps roles/authorship intact between turns,
# instead of stringifying everything into a single prompt.
conversation: list[Message] = [Message("user", [question])]
# Simple round-robin: each agent sees the full conversation so far
for agent in participants:
response = await agent.run(conversation)
conversation.extend(response.messages)
return "\n\n".join(f"{m.author_name or m.role}: {m.text}" for m in conversation)
async def main():
result = await group_chat.run("What's the difference between a list and a tuple in Python?")
print(result.get_outputs()[0])
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,66 @@
# Copyright (c) Microsoft. All rights reserved.
"""Parallel pipeline using asyncio.gather with functional workflows.
Fan-out/fan-in uses native Python concurrency via asyncio.gather.
No @step needed — still just plain async functions.
"""
import asyncio
from agent_framework import workflow
# Plain async functions — asyncio.gather handles the concurrency,
# no framework primitives needed for parallelism.
async def research_web(topic: str) -> str:
"""Simulate web research."""
await asyncio.sleep(0.05)
return f"Web results for '{topic}': 10 articles found"
async def research_papers(topic: str) -> str:
"""Simulate academic paper search."""
await asyncio.sleep(0.05)
return f"Papers on '{topic}': 3 relevant papers"
async def research_news(topic: str) -> str:
"""Simulate news search."""
await asyncio.sleep(0.05)
return f"News about '{topic}': 5 recent articles"
async def synthesize(sources: list[str]) -> str:
"""Combine research results into a summary."""
return "Research Summary:\n" + "\n".join(f" - {s}" for s in sources)
# @workflow wraps the orchestration logic so you get .run(), streaming,
# and events. The functions it calls are plain Python — no decorators
# needed just because they're inside a workflow.
@workflow
async def research_pipeline(topic: str) -> str:
"""Fan-out to three research tasks, then synthesize results."""
# asyncio.gather runs all three concurrently — this is standard Python,
# not a framework concept. Use it the same way you would anywhere else.
#
# Tip: if any of these were wrapped with @step (e.g. an expensive agent call),
# the pattern is identical — @step composes with asyncio.gather, so each
# branch is independently cached on HITL resume or checkpoint restore.
web, papers, news = await asyncio.gather(
research_web(topic),
research_papers(topic),
research_news(topic),
)
return await synthesize([web, papers, news])
async def main():
result = await research_pipeline.run("AI agents")
print(result.get_outputs()[0])
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,97 @@
# Copyright (c) Microsoft. All rights reserved.
"""Introducing @step: per-step checkpointing and observability.
The previous samples used plain functions — and that works. Workflows support
HITL (ctx.request_info) and checkpointing regardless of whether you use @step.
The difference: without @step, a resumed workflow re-executes every function
call from the top. That's fine for cheap functions. But for expensive operations
(API calls, agent runs, etc.) you don't want to pay that cost again.
@step saves each function's result so it skips re-execution on resume:
- On HITL resume, completed steps return their saved result instantly.
- On crash recovery from a checkpoint, earlier step results are restored.
- Each step emits executor_invoked/executor_completed events for observability.
@step is opt-in. Plain functions still work alongside @step in the same workflow.
"""
import asyncio
from agent_framework import InMemoryCheckpointStorage, step, workflow
# Track call counts to show which functions actually execute on resume
fetch_calls = 0
transform_calls = 0
# @step saves this function's result. On resume, it returns the saved
# result instead of re-executing — useful because this is expensive.
@step
async def fetch_data(url: str) -> dict[str, str | int]:
"""Expensive operation — @step prevents re-execution on resume."""
global fetch_calls
fetch_calls += 1
print(f" fetch_data called (call #{fetch_calls})")
return {"url": url, "content": f"Data from {url}", "status": 200}
@step
async def transform_data(data: dict[str, str | int]) -> str:
"""Another expensive operation — @step saves the result."""
global transform_calls
transform_calls += 1
print(f" transform_data called (call #{transform_calls})")
return f"[{data['status']}] {data['content']}"
# No @step — this is cheap, so it just re-runs on resume. That's fine.
async def validate_result(summary: str) -> bool:
"""Cheap validation — no @step needed."""
return len(summary) > 0 and "[200]" in summary
storage = InMemoryCheckpointStorage()
# checkpoint_storage tells @workflow where to persist step results.
# Each @step saves a checkpoint after it completes.
@workflow(checkpoint_storage=storage)
async def data_pipeline(url: str) -> str:
"""Mix of @step functions and plain functions."""
raw = await fetch_data(url)
summary = await transform_data(raw)
is_valid = await validate_result(summary)
return f"{summary} (valid={is_valid})"
async def main():
# --- Run 1: Everything executes normally ---
print("=== Run 1: Fresh execution ===")
result = await data_pipeline.run("https://example.com/api/data")
print(f"Output: {result.get_outputs()[0]}")
print(f"fetch_calls={fetch_calls}, transform_calls={transform_calls}")
# @step functions emit executor events; plain functions don't.
print("\nEvents:")
for event in result:
if event.type in ("executor_invoked", "executor_completed"):
print(f" {event.type}: {event.executor_id}")
# --- Run 2: Restore from checkpoint ---
# The workflow re-executes, but @step functions return saved results.
# Only validate_result() (no @step) actually runs again.
print("\n=== Run 2: Restored from checkpoint ===")
latest = await storage.get_latest(workflow_name="data_pipeline")
assert latest is not None
result2 = await data_pipeline.run(checkpoint_id=latest.checkpoint_id)
print(f"Output: {result2.get_outputs()[0]}")
print(f"fetch_calls={fetch_calls}, transform_calls={transform_calls}")
print("(call counts unchanged — @step results were restored from checkpoint)")
if __name__ == "__main__":
asyncio.run(main())
@@ -2,11 +2,11 @@
import asyncio
import os
from typing import Any
from agent_framework import (
Agent,
AgentExecutorResponse,
AgentResponse,
Executor,
Message,
WorkflowContext,
@@ -16,6 +16,7 @@ from agent_framework.foundry import FoundryChatClient
from agent_framework.orchestrations import SequentialBuilder
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from typing_extensions import Never
# Load environment variables from .env file
load_dotenv()
@@ -25,13 +26,14 @@ Sample: Sequential workflow mixing agents and a custom summarizer executor
This demonstrates how SequentialBuilder chains participants with a shared
conversation context (list[Message]). An agent produces content; a custom
executor appends a compact summary to the conversation. The workflow completes
after all participants have executed in sequence, and the final output contains
the complete conversation.
executor synthesizes a compact summary and yields it as the workflow's terminal
output.
Custom executor contract:
- Provide at least one @handler accepting AgentExecutorResponse and a WorkflowContext[list[Message]]
- Emit the updated conversation via ctx.send_message([...])
- Intermediate custom executors: handle the message type from the prior participant
and forward `list[Message]` via `ctx.send_message(...)` for the next participant.
- Terminator custom executors: handle the message type from the prior participant and
yield the workflow's final answer as an `AgentResponse` via `ctx.yield_output(...)`.
Prerequisites:
- FOUNDRY_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
@@ -41,27 +43,29 @@ Prerequisites:
class Summarizer(Executor):
"""Simple summarizer: consumes full conversation and appends an assistant summary."""
"""Terminator custom executor: synthesizes a one-line summary as the workflow's final answer."""
@handler
async def summarize(self, agent_response: AgentExecutorResponse, ctx: WorkflowContext[list[Message]]) -> None:
"""Append a summary message to a copy of the full conversation.
async def summarize(
self,
agent_response: AgentExecutorResponse,
ctx: WorkflowContext[Never, AgentResponse],
) -> None:
"""Yield a terminal AgentResponse containing the summary.
Note: A custom executor must be able to handle the message type from the prior participant, and produce
the message type expected by the next participant. In this case, the prior participant is an agent thus
the input is AgentExecutorResponse (an agent will be wrapped in an AgentExecutor, which produces
`AgentExecutorResponse`). If the next participant is also an agent or this is the final participant,
the output must be `list[Message]`.
The prior participant is an agent, which is wrapped in an `AgentExecutor` that
produces `AgentExecutorResponse`. As the last participant in the sequential workflow,
this executor calls `ctx.yield_output(AgentResponse(...))` so its output becomes the
workflow's terminal output (rather than being forwarded to a downstream participant).
"""
if not agent_response.full_conversation:
await ctx.send_message([Message("assistant", ["No conversation to summarize."])])
await ctx.yield_output(AgentResponse(messages=[Message("assistant", ["No conversation to summarize."])]))
return
users = sum(1 for m in agent_response.full_conversation if m.role == "user")
assistants = sum(1 for m in agent_response.full_conversation if m.role == "assistant")
summary = Message("assistant", [f"Summary -> users:{users} assistants:{assistants}"])
final_conversation = list(agent_response.full_conversation) + [summary]
await ctx.send_message(final_conversation)
await ctx.yield_output(AgentResponse(messages=[summary]))
async def main() -> None:
@@ -81,33 +85,20 @@ async def main() -> None:
summarizer = Summarizer(id="summarizer")
workflow = SequentialBuilder(participants=[content, summarizer]).build()
# 3) Run workflow and extract final conversation
# 3) Run workflow and extract the final summary
events = await workflow.run("Explain the benefits of budget eBikes for commuters.")
outputs = events.get_outputs()
if outputs:
print("===== Final Conversation =====")
messages: list[Message] | Any = outputs[0]
for i, msg in enumerate(messages, start=1):
name = msg.author_name or ("assistant" if msg.role == "assistant" else "user")
print(f"{'-' * 60}\n{i:02d} [{name}]\n{msg.text}")
print("===== Final Summary =====")
final: AgentResponse = outputs[0]
for msg in final.messages:
print(msg.text)
"""
Sample Output:
------------------------------------------------------------
01 [user]
Explain the benefits of budget eBikes for commuters.
------------------------------------------------------------
02 [content]
Budget eBikes offer commuters an affordable, eco-friendly alternative to cars and public transport.
Their electric assistance reduces physical strain and allows riders to cover longer distances quickly,
minimizing travel time and fatigue. Budget models are low-cost to maintain and operate, making them accessible
for a wider range of people. Additionally, eBikes help reduce traffic congestion and carbon emissions,
supporting greener urban environments. Overall, budget eBikes provide cost-effective, efficient, and
sustainable transportation for daily commuting needs.
------------------------------------------------------------
03 [assistant]
===== Final Summary =====
Summary -> users:1 assistants:1
"""
@@ -0,0 +1,8 @@
# Azure AI Foundry Configuration (required for a2a_server.py and a2a_agent_as_function_tools.py)
FOUNDRY_PROJECT_ENDPOINT=https://your-project.services.ai.azure.com/api/projects/your-project
FOUNDRY_MODEL=gpt-4o
# A2A Client Configuration (required for agent_with_a2a.py and a2a_agent_as_function_tools.py)
# The function-tools flow uses http://localhost:5000/ in the sample docs/output.
# Update this value if you start a different local A2A server instance on another port.
A2A_AGENT_HOST=http://localhost:5000/
+29
View File
@@ -12,6 +12,7 @@ The remaining files are supporting modules used by the server:
| File | Description |
|------|-------------|
| [`agent_framework_to_a2a.py`](agent_framework_to_a2a.py) | Exposes an agent_framework agent as an A2A-compliant server. Demonstrates how to wrap an agent_framework agent and expose it as an A2A service that other A2A clients can discover and communicate with. |
| [`agent_definitions.py`](agent_definitions.py) | Agent and AgentCard factory definitions for invoice, policy, and logistics agents. |
| [`agent_executor.py`](agent_executor.py) | Bridges the a2a-sdk `AgentExecutor` interface to Agent Framework agents. |
| [`invoice_data.py`](invoice_data.py) | Mock invoice data and tool functions for the invoice agent. |
@@ -41,8 +42,33 @@ All commands below should be run from this directory:
cd python/samples/04-hosting/a2a
```
### 0. Install Dependencies
Copy `.env.example` to `.env` and fill in your values:
```powershell
copy .env.example .env
```
**Option A — pip (standard):**
```powershell
python -m venv .venv
.venv\Scripts\Activate.ps1 # Windows
# source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt
```
**Option B — uv:**
```powershell
uv run python a2a_server.py --agent-type policy
```
### 1. Start the A2A Server
> **Note (Option A — pip users):** Replace `uv run python` with `python` in all `uv run` commands below (e.g. `python a2a_server.py ...`). `uv` is not required once the virtual environment is activated.
Pick an agent type and start the server (each in its own terminal):
```powershell
@@ -60,6 +86,9 @@ In a separate terminal (from the same directory), point the client at a running
```powershell
$env:A2A_AGENT_HOST = "http://localhost:5001/"
uv run python agent_with_a2a.py
# A2A server exposing an agent_framework agent
uv run python agent_framework_to_a2a.py
```
### 3. Run the Function Tools Sample
@@ -0,0 +1,70 @@
# Copyright (c) Microsoft. All rights reserved.
import uvicorn
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import (
AgentCapabilities,
AgentCard,
AgentSkill,
)
from agent_framework import Agent
from agent_framework.a2a import A2AExecutor
from agent_framework.openai import OpenAIChatClient
from dotenv import load_dotenv
load_dotenv()
if __name__ == "__main__":
# --8<-- [start:AgentSkill]
flight_skill = AgentSkill(
id="Flight_Booking",
name="Flight Booking",
description="Search and book flights across Europe.",
tags=["flights", "travel", "europe"],
examples=[],
)
hotel_skill = AgentSkill(
id="Hotel_Booking",
name="Hotel Booking",
description="Search and book hotels across Europe.",
tags=["hotels", "travel", "accommodation"],
examples=[],
)
# --8<-- [end:AgentSkill]
# --8<-- [start:AgentCard]
# This will be the public-facing agent card
public_agent_card = AgentCard(
name="Europe Travel Agent",
description="A helpful Europe Travel Agent that can help users search and book flights and hotels across Europe.",
url="http://localhost:9999/",
version="1.0.0",
defaultInputModes=["text"],
defaultOutputModes=["text"],
capabilities=AgentCapabilities(streaming=True),
skills=[flight_skill, hotel_skill],
)
# --8<-- [end:AgentCard]
agent = Agent(
client=OpenAIChatClient(),
name="Europe Travel Agent",
instructions="You are a helpful Europe Travel Agent. You can help users search and book flights and hotels across Europe.",
)
request_handler = DefaultRequestHandler(
agent_executor=A2AExecutor(agent),
task_store=InMemoryTaskStore(),
)
server = A2AStarletteApplication(
agent_card=public_agent_card,
http_handler=request_handler,
)
server = server.build()
# print(schemas.get_schema(server.routes))
uvicorn.run(server, host="0.0.0.0", port=9999)
@@ -0,0 +1,23 @@
# Agent Framework packages
# To use the deployed version, uncomment the lines below and comment out the local installation lines
# agent-framework-a2a
# agent-framework-foundry
# Local installation (for development and testing)
# Each package must be listed explicitly because pip doesn't resolve uv workspace sources.
# Without explicit entries, pip would fetch transitive dependencies from PyPI instead of local source.
-e ../../../packages/core # Core framework - base dependency for all packages
-e ../../../packages/foundry # Foundry support - dependency for FoundryChatClient in a2a_server.py
-e ../../../packages/a2a # A2A integration - provides A2AAgent and a2a-sdk
# Azure authentication
azure-identity
# A2A server runtime
uvicorn
# HTTP client used by A2A client samples
httpx
# Environment variable loading
python-dotenv
@@ -1,12 +1,139 @@
# Foundry Hosted Agents Samples
# Foundry Hosted Agent Samples
This directory contains samples that demonstrate how to use the Agent Framework to host agents on Foundry with different capabilities and configurations. Each sample includes a README with instructions on how to set up, run, and interact with the agent.
This directory contains samples that demonstrate how to use hosted [Agent Framework](https://github.com/microsoft/agent-framework) agents with different capabilities and configurations on Foundry using the Foundry Hosting Agent service. Each sample includes a README with instructions on how to set up, run, and interact with the agent.
Read more about Foundry Hosted Agents [here](https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/hosted-agents).
## Samples
## Environment setup
### Responses API
1. Navigate to the sample directory you want to run. For example:
| # | Sample | Description |
|---|--------|-------------|
| 1 | [Basic](responses/01_basic/) | A minimal agent demonstrating basic request/response interaction and multi-turn conversations using `previous_response_id`. |
| 2 | [Tools](responses/02_tools/) | An agent with local tools (e.g., weather lookup), demonstrating how to register and invoke custom tool functions alongside the LLM. |
| 3 | [MCP](responses/03_mcp/) | An agent connected to a remote MCP server (GitHub), demonstrating external MCP tool provider integration. |
| 4 | [Foundry Toolbox](responses/04_foundry_toolbox/) | An agent using Azure Foundry Toolbox, demonstrating toolbox provisioning and querying available tools at runtime. |
| 5 | [Workflows](responses/05_workflows/) | An agent with a multi-step orchestrated workflow, demonstrating chaining prompts through an orchestrated flow. |
| 6 | [Using deployed agent](responses/using_deployed_agent.py) | A sample demonstrating how to invoke an agent that has already been deployed to Foundry, showing how to interact with a hosted agent in code. |
### Invocations API
| # | Sample | Description |
|---|--------|-------------|
| 1 | [Basic](invocations/01_basic/) | A minimal agent demonstrating session state management via `agent_session_id` in URL params/response headers. |
| 2 | [Break Glass](invocations/02_break_glass/) | An agent demonstrating a "break glass" scenario where customizations of the API behaviors are needed, allowing for more direct control over how requests and responses are handled by the hosting layer. |
## Running the Agent Host Locally
### Using `azd`
#### Prerequisites
1. **Azure Developer CLI (`azd`)**
- [Install azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) and the AI agent extension: `azd ext install azure.ai.agents`
- Authenticated: `azd auth login`
2. **Azure Subscription**
#### Create a new project
**No cloning required**. Create a new folder, point azd at the manifest on GitHub.
```bash
mkdir hosted-agent-framework-agent && cd hosted-agent-framework-agent
# Initialize from the manifest
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/python/samples/04-hosting/foundry-hosted-agents/responses/01_basic/agent.manifest.yaml
```
Follow the instructions from `azd ai agent init` to complete the agent initialization. If you don't have an existing Foundry project and a model deployment, `azd ai agent init` will guide you through creating them.
#### Provision Azure Resources
> This step is only needed if you don't have an existing Foundry project and model deployment.
Run the following command to provision the necessary Azure resources:
```bash
azd provision
```
This will create the following Azure resources:
- A new resource group named `rg-[project_name]-dev`. In this guide, `[project_name]` will be `hosted-agent-framework-agent`.
- Within the resource group, among other resources, the most important ones are:
- A new Foundry instance
- A new Foundry project, within which a new model deployment will be created
- An Application Insights instance
- A container registry, which will be used to store the container images for the hosted agent
#### Set Environment Variables
```bash
export FOUNDRY_PROJECT_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>"
export AZURE_AI_MODEL_DEPLOYMENT_NAME="<your-model-deployment-name>"
# And any other environment variables required by the sample
```
Or in PowerShell:
```powershell
$env:FOUNDRY_PROJECT_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>"
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="<your-model-deployment-name>"
# And any other environment variables required by the sample
```
> Note: The environment variables set above are only for the current session. You will need to set them again if you open a new terminal session. if you want to set the environment variables permanently in the azd environment, you can use `azd env set <name> <value>`.
#### Running the Agent Host
```bash
azd ai agent run
```
Right now, the agent host should be running on `http://localhost:8088`
#### Invoking the Agent
Open another terminal, **navigate to the project directory**, and run the following command to invoke the agent:
```bash
azd ai agent invoke --local "Hello!"
```
Or you can in another terminal, without navigating to the project directory, run the following command to invoke the agent:
```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "Hello!"}'
```
Or in PowerShell:
```powershell
(Invoke-WebRequest -Uri http://localhost:8088/responses -Method POST -ContentType "application/json" -Body '{"input": "Hello!"}').Content
```
### Using `python`
#### Prerequisites
1. An existing Foundry project
2. A deployed model in your Foundry project
3. Azure CLI installed and authenticated
4. Python 3.10 or later
#### Running the Agent Host with Python
Clone the repository containing the sample code:
```bash
git clone https://github.com/microsoft/agent-framework.git
cd agent-framework/python/samples/04-hosting/foundry-hosted-agents/responses
```
#### Environment setup
1. Navigate to the sample directory you want to explore. Create a virtual environment:
```bash
python -m venv .venv
@@ -32,25 +159,58 @@ Read more about Foundry Hosted Agents [here](https://learn.microsoft.com/en-us/a
az login
```
## Deploying to a Docker container
Navigate to the sample directory and build the Docker image:
#### Running the Agent Host
```bash
docker build -t hosted-agent-sample .
python main.py
```
Run the container, passing in the required environment variables:
Right now, the agent host should be running on `http://localhost:8088`
#### Invoking the Agent
On another terminal, run the following command to invoke the agent:
```bash
docker run -p 8088:8088 \
-e FOUNDRY_PROJECT_ENDPOINT=<your-endpoint> \
-e MODEL_DEPLOYMENT_NAME=<your-model> \
hosted-agent-sample
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "Hello!"}'
```
The server will be available at `http://localhost:8088`. You can send requests using the same `curl` command shown above.
Or in PowerShell:
## Deploying to Foundry
```powershell
(Invoke-WebRequest -Uri http://localhost:8088/responses -Method POST -ContentType "application/json" -Body '{"input": "Hello!"}').Content
```
Follow this [guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent?tabs=bash#configure-your-agent) to deploy your agent to Foundry.
## Deploying the Agent to Foundry
Once you've tested locally, deploy to Microsoft Foundry.
### With an Existing Foundry Project
If you already have a Foundry project and the necessary Azure resources provisioned, you can skip the setup steps and proceed directly to deploying the agent.
After running `azd ai agent init -m <agent.manifest.yaml>` and following the prompts to configure your agent, you will have a project ready for deployment.
### Setting Up a New Foundry Project
Follow the steps in [Using `azd`](#using-azd) to set up the project and provision the necessary Azure resources for your Foundry deployment.
### Deploying the Agent
Once the project is setup and resources are provisioned, you can deploy the agent to Foundry by running:
```bash
azd deploy
```
> The Foundry hosting infrastructure will inject the following environment variables into your agent at runtime:
>
> - `FOUNDRY_PROJECT_ENDPOINT`: The endpoint URL for the Foundry project where the agent is deployed.
> - `AZURE_AI_MODEL_DEPLOYMENT_NAME`: The name of the model deployment in your Foundry project. This is configured during the agent initialization process with `azd ai agent init`.
> - `APPLICATIONINSIGHTS_CONNECTION_STRING`: The connection string for Application Insights to enable telemetry for your agent.
This will package your agent and deploy it to the Foundry environment, making it accessible through the Foundry project endpoint. Once it's deployed, you can also access the agent through the Foundry UI.
For the full deployment guide, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
Once deployed, learn more about how to manage deployed agents in the [official management guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/manage-hosted-agent).
@@ -1,2 +1,2 @@
FOUNDRY_PROJECT_ENDPOINT="..."
MODEL_DEPLOYMENT_NAME="..."
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
@@ -1,18 +1,26 @@
# Basic example of hosting an agent with the `invocations` API
# What this sample demonstrates
## Running the server locally
An [Agent Framework](https://github.com/microsoft/agent-framework) agent hosted using the **Invocations protocol** with session management. Unlike the Responses protocol, the Invocations protocol does **not** provide built-in server-side conversation history — this agent maintains an in-memory session store keyed by `agent_session_id`. In production, replace it with durable storage (Redis, Cosmos DB, etc.) so history survives restarts.
### Environment setup
## How It Works
Follow the instructions in the [Environment setup](../../README.md#environment-setup) section of the README in the parent directory to set up your environment and install dependencies.
### Model Integration
Run the following command to start the server:
The agent uses `FoundryChatClient` from the Agent Framework to create a Responses client from the project endpoint and model deployment. When a request arrives, the handler looks up (or creates) a session by `session_id`, runs the agent with the user message and session context, and returns the reply. The agent supports both streaming (SSE events) and non-streaming (JSON) response modes.
```bash
python main.py
```
See [main.py](main.py) for the full implementation.
### Interacting with the agent
### Agent Hosting
The agent is hosted using the [Agent Framework](https://github.com/microsoft/agent-framework) with the `InvocationsHostServer`, which provisions a REST API endpoint compatible with the Azure AI Invocations protocol.
## Running the Agent Host
Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host.
## Interacting with the agent
> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.
Send a POST request to the server with a JSON body containing a "message" field to interact with the agent. For example:
@@ -22,7 +30,7 @@ curl -X POST http://localhost:8088/invocations -i -H "Content-Type: application/
The server will respond with a JSON object containing the response text. The `-i` flag in the `curl` command includes the HTTP response headers in the output, which includes the session ID that can be used for multi-turn conversations. Here is an example of the response:
```bash
```
HTTP/1.1 200
content-length: 34
content-type: application/json
@@ -42,3 +50,7 @@ To have a multi-turn conversation with the agent, take the session ID from the r
```bash
curl -X POST http://localhost:8088/invocations?agent_session_id=9370b9d4-cd13-4436-a57f-03b843ac0e17 -i -H "Content-Type: application/json" -d '{"message": "How are you?"}'
```
## Deploying the Agent to Foundry
To host the agent on Foundry, follow the instructions in the [Deploying the Agent to Foundry](../../README.md#deploying-the-agent-to-foundry) section of the README in the parent directory.
@@ -15,9 +15,9 @@ template:
- protocol: invocations
version: 1.0.0
environment_variables:
- name: MODEL_DEPLOYMENT_NAME
value: "{{MODEL_DEPLOYMENT_NAME}}"
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
resources:
- kind: model
id: gpt-4.1-mini
name: MODEL_DEPLOYMENT_NAME
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
@@ -6,4 +6,4 @@ protocols:
version: 1.0.0
resources:
cpu: '0.25'
memory: '0.5Gi'
memory: '0.5Gi'
@@ -5,7 +5,7 @@ import os
from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import InvocationsHostServer
from azure.identity import AzureCliCredential
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv
# Load environment variables from .env file
@@ -15,8 +15,8 @@ load_dotenv()
def main():
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
agent = Agent(
@@ -1,2 +1,2 @@
FOUNDRY_PROJECT_ENDPOINT="..."
MODEL_DEPLOYMENT_NAME="..."
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
@@ -1,20 +1,26 @@
# Basic example of hosting an agent with the `invocations` API
# What this sample demonstrates
This is the same as the [01_basic](../01_basic/README.md) example, but demonstrates the "break glass" scenario where you can create your own `invoke_handler` to handle specific types of invocations. This is useful when you want to override the default behavior for certain requests or add custom processing logic.
An [Agent Framework](https://github.com/microsoft/agent-framework) agent hosted using the **Invocations protocol** with session management. Unlike the Responses protocol, the Invocations protocol does **not** provide built-in server-side conversation history — this agent maintains an in-memory session store keyed by `agent_session_id`. In production, replace it with durable storage (Redis, Cosmos DB, etc.) so history survives restarts.
## Running the server locally
## How It Works
### Environment setup
### Model Integration
Follow the instructions in the [Environment setup](../../README.md#environment-setup) section of the README in the parent directory to set up your environment and install dependencies.
The agent uses `FoundryChatClient` from the Agent Framework to create a Responses client from the project endpoint and model deployment. When a request arrives, the handler looks up (or creates) a session by `session_id`, runs the agent with the user message and session context, and returns the reply. The agent supports both streaming (SSE events) and non-streaming (JSON) response modes.
Run the following command to start the server:
See [main.py](main.py) for the full implementation.
```bash
python main.py
```
### Agent Hosting
### Interacting with the agent
The agent is hosted using the [Azure AI AgentServer Invocations SDK](https://pypi.org/project/azure-ai-agentserver-invocations/) (`InvocationAgentServerHost`), which provisions a REST API endpoint compatible with the Azure AI Invocations protocol.
## Running the Agent Host
Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host.
## Interacting with the agent
> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.
Send a POST request to the server with a JSON body containing a "message" field to interact with the agent. For example:
@@ -24,7 +30,7 @@ curl -X POST http://localhost:8088/invocations -i -H "Content-Type: application/
The server will respond with a JSON object containing the response text. The `-i` flag in the `curl` command includes the HTTP response headers in the output, which includes the session ID that can be used for multi-turn conversations. Here is an example of the response:
```bash
```
HTTP/1.1 200
content-length: 34
content-type: application/json
@@ -44,3 +50,7 @@ To have a multi-turn conversation with the agent, take the session ID from the r
```bash
curl -X POST http://localhost:8088/invocations?agent_session_id=9370b9d4-cd13-4436-a57f-03b843ac0e17 -i -H "Content-Type: application/json" -d '{"message": "How are you?"}'
```
## Deploying the Agent to Foundry
To host the agent on Foundry, follow the instructions in the [Deploying the Agent to Foundry](../../README.md#deploying-the-agent-to-foundry) section of the README in the parent directory.
@@ -15,9 +15,9 @@ template:
- protocol: invocations
version: 1.0.0
environment_variables:
- name: MODEL_DEPLOYMENT_NAME
value: "{{MODEL_DEPLOYMENT_NAME}}"
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
resources:
- kind: model
id: gpt-4.1-mini
name: MODEL_DEPLOYMENT_NAME
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
@@ -6,4 +6,4 @@ protocols:
version: 1.0.0
resources:
cpu: '0.25'
memory: '0.5Gi'
memory: '0.5Gi'
@@ -22,7 +22,7 @@ _sessions: dict[str, AgentSession] = {}
# Create the agent
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["MODEL_DEPLOYMENT_NAME"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
@@ -1,8 +0,0 @@
# Hosting agents with Foundry Hosting and the `invocations` API
This folder contains a list of samples that show how to host agents using the `invocations` API and deploy them to Foundry Hosting.
| Sample | Description |
| --- | --- |
| [01_basic](./01_basic) | A basic example of hosting an agent with the `invocations` API and carrying on a multi-turn conversation. |
| [02_break_glass](./02_break_glass) | An example of hosting an agent with the `invocations` API and a "break glass" scenario where you can create your own `invoke_handler` to handle specific types of invocations. |
@@ -1,2 +1,2 @@
FOUNDRY_PROJECT_ENDPOINT="..."
MODEL_DEPLOYMENT_NAME="..."
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
@@ -1,31 +1,39 @@
# Basic example of hosting an agent with the `responses` API
# What this sample demonstrates
This agent only contains an instruction (personal). It's the most basic agent with an LLM and no tools.
An [Agent Framework](https://github.com/microsoft/agent-framework) agent hosted using the **Responses protocol**.
## Running the server locally
## How It Works
### Environment setup
### Model Integration
Follow the instructions in the [Environment setup](../../README.md#environment-setup) section of the README in the parent directory to set up your environment and install dependencies.
The agent uses `FoundryChatClient` from the Agent Framework to create a Responses client from the project endpoint and model deployment. The agent supports both streaming (SSE events) and non-streaming (JSON) response modes.
Run the following command to start the server:
See [main.py](main.py) for the full implementation.
```bash
python main.py
```
### Agent Hosting
The agent is hosted using the [Agent Framework](https://github.com/microsoft/agent-framework) with the `ResponsesHostServer`, which provisions a REST API endpoint compatible with the OpenAI Responses protocol.
## Interacting with the agent
Send a POST request to the server with a JSON body containing a "input" field to interact with the agent. For example:
> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.
Send a POST request to the server with a JSON body containing an `"input"` field to interact with the agent. For example:
```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "Hi"}'
```
## Multi-turn conversation
The server will respond with a JSON object containing the response text and a response ID. You can use this response ID to continue the conversation in subsequent requests.
### Multi-turn conversation
To have a multi-turn conversation with the agent, include the previous response id in the request body. For example:
```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "How are you?", "previous_response_id": "REPLACE_WITH_PREVIOUS_RESPONSE_ID"}'
```
## Deploying the Agent to Foundry
To host the agent on Foundry, follow the instructions in the [Deploying the Agent to Foundry](../../README.md#deploying-the-agent-to-foundry) section of the README in the parent directory.
@@ -1,4 +1,4 @@
name: agent-framework-agent-basic
name: agent-framework-agent-basic-responses
description: >
A basic Agent Framework agent hosted by Foundry.
metadata:
@@ -9,15 +9,15 @@ metadata:
- Responses Protocol
- Streaming
template:
name: agent-framework-agent-basic
name: agent-framework-agent-basic-responses
kind: hosted
protocols:
- protocol: responses
version: 1.0.0
environment_variables:
- name: MODEL_DEPLOYMENT_NAME
value: "{{MODEL_DEPLOYMENT_NAME}}"
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
resources:
- kind: model
id: gpt-4.1-mini
name: MODEL_DEPLOYMENT_NAME
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
@@ -1,8 +1,9 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
kind: hosted
name: agent-framework-agent-basic
name: agent-framework-agent-basic-responses
protocols:
- protocol: responses
version: 1.0.0
resources:
cpu: "0.25"
memory: 0.5Gi
cpu: '0.25'
memory: '0.5Gi'
@@ -5,7 +5,7 @@ import os
from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from azure.identity import AzureCliCredential
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv
# Load environment variables from .env file
@@ -15,8 +15,8 @@ load_dotenv()
def main():
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
agent = Agent(
@@ -1,2 +0,0 @@
FOUNDRY_PROJECT_ENDPOINT="..."
MODEL_DEPLOYMENT_NAME="..."
@@ -1,27 +0,0 @@
# Basic example of hosting an agent with the `responses` API and local tools
This agent is equipped with a function tool and a local shell tool.
> We recommend deploying this sample on a local container or to Foundry Hosting because the agent has access to a local shell tool, which can run arbitrary commands on the machine.
## Running the server locally
### Environment setup
Follow the instructions in the [Environment setup](../../README.md#environment-setup) section of the README in the parent directory to set up your environment and install dependencies.
Run the following command to start the server:
```bash
python main.py
```
## Interacting with the agent
Send a POST request to the server with a JSON body containing a "input" field to interact with the agent. For example:
```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "What is the weather in Seattle?"}'
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "List the files in the current directory."}'
```
@@ -0,0 +1,2 @@
FOUNDRY_PROJECT_ENDPOINT="..."
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
@@ -0,0 +1,33 @@
# What this sample demonstrates
An [Agent Framework](https://github.com/microsoft/agent-framework) agent with **locally-defined Python tools** hosted using the **Responses protocol**. It shows how to define custom tools with the `@tool` decorator and register them with the agent so the model can call them during a conversation.
## How It Works
### Model Integration
The agent uses `FoundryChatClient` from the Agent Framework to create a Responses client from the project endpoint and model deployment. The agent supports both streaming (SSE events) and non-streaming (JSON) response modes.
See [main.py](main.py) for the full implementation.
### Agent Hosting
The agent is hosted using the [Agent Framework](https://github.com/microsoft/agent-framework) with the `ResponsesHostServer`, which provisions a REST API endpoint compatible with the OpenAI Responses protocol.
## Running the Agent Host
Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host.
## Interacting with the agent
> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.
Send a POST request to the server with a JSON body containing an `"input"` field to interact with the agent. For example:
```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "What is the weather in Seattle?"}'
```
## Deploying the Agent to Foundry
To host the agent on Foundry, follow the instructions in the [Deploying the Agent to Foundry](../../README.md#deploying-the-agent-to-foundry) section of the README in the parent directory.
@@ -1,4 +1,4 @@
name: agent-framework-agent-with-local-tools
name: agent-framework-agent-with-local-tools-responses
description: >
An Agent Framework agent with local tools hosted by Foundry.
metadata:
@@ -9,15 +9,15 @@ metadata:
- Responses Protocol
- Streaming
template:
name: agent-framework-agent-with-local-tools
name: agent-framework-agent-with-local-tools-responses
kind: hosted
protocols:
- protocol: responses
version: 1.0.0
environment_variables:
- name: MODEL_DEPLOYMENT_NAME
value: "{{MODEL_DEPLOYMENT_NAME}}"
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
resources:
- kind: model
id: gpt-4.1-mini
name: MODEL_DEPLOYMENT_NAME
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
@@ -1,5 +1,5 @@
kind: hosted
name: agent-framework-agent-with-local-tools
name: agent-framework-agent-with-local-tools-responses
protocols:
- protocol: responses
version: 1.0.0
@@ -8,7 +8,7 @@ from typing import Annotated
from agent_framework import Agent, tool
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from azure.identity import AzureCliCredential
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv
from pydantic import Field
@@ -52,8 +52,8 @@ def run_bash(command: str) -> str:
def main():
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
agent = Agent(
@@ -1,4 +1,3 @@
FOUNDRY_PROJECT_ENDPOINT="..."
MODEL_DEPLOYMENT_NAME="..."
TOOLBOX_NAME="..."
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
GITHUB_PAT="..."
@@ -0,0 +1,33 @@
# What this sample demonstrates
An [Agent Framework](https://github.com/microsoft/agent-framework) agent that connects to a **remote MCP server** (GitHub) for tool discovery and hosted using the **Responses protocol**. Instead of defining tools locally, the agent discovers and invokes tools at runtime from an MCP-compatible endpoint — in this case, the GitHub Copilot MCP server. This enables dynamic tool integration without redeployment.
## How It Works
### Model Integration
The agent uses `FoundryChatClient` from the Agent Framework to create an OpenAI-compatible Responses client. It registers a remote MCP tool pointing at `https://api.githubcopilot.com/mcp/`, authenticating with a GitHub Personal Access Token (PAT). When the model decides to call a tool, the framework forwards the call to the MCP server and returns the result to the model for the final reply.
See [main.py](main.py) for the full implementation.
### Agent Hosting
The agent is hosted using the [Agent Framework](https://github.com/microsoft/agent-framework) with the `ResponsesHostServer`, which provisions a REST API endpoint compatible with the OpenAI Responses protocol.
## Running the Agent Host
Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host.
## Interacting with the agent
> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.
Send a POST request to the server with a JSON body containing an `"input"` field to interact with the agent. For example:
```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "List all the repositories I own on GitHub."}'
```
## Deploying the Agent to Foundry
To host the agent on Foundry, follow the instructions in the [Deploying the Agent to Foundry](../../README.md#deploying-the-agent-to-foundry) section of the README in the parent directory.
@@ -1,4 +1,4 @@
name: agent-framework-agent-with-remote-mcp-tools
name: agent-framework-agent-with-remote-mcp-tools-responses
description: >
An Agent Framework agent with remote MCP tools hosted by Foundry.
metadata:
@@ -9,19 +9,17 @@ metadata:
- Responses Protocol
- Streaming
template:
name: agent-framework-agent-with-remote-mcp-tools
name: agent-framework-agent-with-remote-mcp-tools-responses
kind: hosted
protocols:
- protocol: responses
version: 1.0.0
environment_variables:
- name: MODEL_DEPLOYMENT_NAME
value: "{{MODEL_DEPLOYMENT_NAME}}"
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
- name: GITHUB_PAT
value: ${GITHUB_PAT}
- name: TOOLBOX_NAME
value: ${TOOLBOX_NAME}
resources:
- kind: model
id: gpt-4.1-mini
name: MODEL_DEPLOYMENT_NAME
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
@@ -0,0 +1,11 @@
kind: hosted
name: agent-framework-agent-with-remote-mcp-tools-responses
protocols:
- protocol: responses
version: 1.0.0
resources:
cpu: "0.25"
memory: 0.5Gi
environment_variables:
- name: GITHUB_PAT
value: ${GITHUB_PAT}
@@ -0,0 +1,56 @@
# Copyright (c) Microsoft. All rights reserved.
import logging
import os
from agent_framework import Agent, ToolTypes
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
logger = logging.getLogger(__name__)
def main():
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
github_pat = os.environ["GITHUB_PAT"]
tools: list[ToolTypes] = []
if not github_pat:
logger.warning("GITHUB_PAT environment variable is not set. The GitHub MCP tool will not get registered.")
else:
tools.append(
client.get_mcp_tool(
name="GitHub",
url="https://api.githubcopilot.com/mcp/",
headers={
"Authorization": f"Bearer {github_pat}",
},
approval_mode="never_require",
)
)
agent = Agent(
client=client,
instructions="You are a friendly assistant. Keep your answers brief.",
tools=tools,
# History will be managed by the hosting infrastructure, thus there
# is no need to store history by the service. Learn more at:
# https://developers.openai.com/api/reference/resources/responses/methods/create
default_options={"store": False},
)
server = ResponsesHostServer(agent)
server.run()
if __name__ == "__main__":
main()
@@ -1,25 +0,0 @@
# Basic example of hosting an agent with the `responses` API and a remote MCP
This agent is equipped with a GitHub MCP server and a Foundry Toolbox, which are both remote MCPs.
> Note that there are other ways to interact with Foundry toolboxes. Using it as a MCP is just one of the options.
## Running the server locally
### Environment setup
Follow the instructions in the [Environment setup](../../README.md#environment-setup) section of the README in the parent directory to set up your environment and install dependencies.
Run the following command to start the server:
```bash
python main.py
```
## Interacting with the agent
Send a POST request to the server with a JSON body containing a "input" field to interact with the agent. For example:
```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "List all the repositories I own on GitHub."}'
```
@@ -1,76 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
import os
import httpx
from agent_framework import Agent, MCPStreamableHTTPTool
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
class ToolboxAuth(httpx.Auth):
"""httpx Auth that injects a fresh bearer token on every request."""
def auth_flow(self, request: httpx.Request):
credential = AzureCliCredential()
token = credential.get_token("https://ai.azure.com/.default").token
request.headers["Authorization"] = f"Bearer {token}"
yield request
def main():
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
)
# Foundry Toolbox as a MCP tool
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
toolbox_name = os.environ["TOOLBOX_NAME"]
toolbox_endpoint = f"{project_endpoint.rstrip('/')}/toolboxes/{toolbox_name}/mcp?api-version=v1"
http_client = httpx.AsyncClient(auth=ToolboxAuth(), headers={"Foundry-Features": "Toolboxes=V1Preview"})
foundry_mcp_tool = MCPStreamableHTTPTool(
name="toolbox",
url=toolbox_endpoint,
http_client=http_client,
load_prompts=False,
)
# GitHub MCP server
github_pat = os.environ["GITHUB_PAT"]
if not github_pat:
raise ValueError(
"GITHUB_PAT environment variable must be set. Create a token at https://github.com/settings/tokens"
)
github_mcp_tool = client.get_mcp_tool(
name="GitHub",
url="https://api.githubcopilot.com/mcp/",
headers={
"Authorization": f"Bearer {github_pat}",
},
approval_mode="never_require",
)
agent = Agent(
client=client,
instructions="You are a friendly assistant. Keep your answers brief.",
tools=[foundry_mcp_tool, github_mcp_tool],
# History will be managed by the hosting infrastructure, thus there
# is no need to store history by the service. Learn more at:
# https://developers.openai.com/api/reference/resources/responses/methods/create
default_options={"store": False},
)
server = ResponsesHostServer(agent)
server.run()
if __name__ == "__main__":
main()
@@ -0,0 +1,3 @@
FOUNDRY_PROJECT_ENDPOINT="..."
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
TOOLBOX_NAME="..."
@@ -0,0 +1,43 @@
# What this sample demonstrates
An [Agent Framework](https://github.com/microsoft/agent-framework) agent that uses **Foundry Toolbox** for tool discovery and hosted using the **Responses protocol**. Foundry Toolbox is a managed tool registry in Microsoft Foundry that lets you define tools centrally and share them across agents.
## Creating a Foundry Toolbox
You can create a Foundry Toolbox by code. Refer to this sample for an example: [Foundry Toolbox CRUD Sample](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_toolboxes_crud.py).
You can also create a Foundry Toolbox in the Foundry portal. Read more about it [in the Foundry toolbox documentation](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/toolbox).
> If you set up a project with this sample and provision the resources using `azd provision`, a Foundry Toolbox will be created with the specified tools in [`agent.manifest.yaml`](agent.manifest.yaml).
## How It Works
### Model Integration
The agent uses `FoundryChatClient` from the Agent Framework to create an OpenAI-compatible Responses client. It loads a named Foundry Toolbox via `client.get_toolbox(name)` — the toolbox is a server-side bundle of tool configurations (e.g., `code_interpreter`, `web_search`) defined in the Foundry portal or by `azd provision`. Omitting `version` resolves the toolbox's current default version at runtime.
The sample then narrows the toolbox to a subset of tool types via `select_toolbox_tools(toolbox, include_types=[...])` before handing it to the agent. This demonstrates how one toolbox can be reused across agents that each expose only the tools they need — here, the agent only sees `code_interpreter` even though the toolbox also includes `web_search`.
See [main.py](main.py) for the full implementation.
### Agent Hosting
The agent is hosted using the [Agent Framework](https://github.com/microsoft/agent-framework) with the `ResponsesHostServer`, which provisions a REST API endpoint compatible with the OpenAI Responses protocol.
## Running the Agent Host
Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host.
## Interacting with the agent
> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.
Send a POST request to the server with a JSON body containing an `"input"` field to interact with the agent. For example:
```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "What tools do you have?"}'
```
## Deploying the Agent to Foundry
To host the agent on Foundry, follow the instructions in the [Deploying the Agent to Foundry](../../README.md#deploying-the-agent-to-foundry) section of the README in the parent directory.
@@ -0,0 +1,33 @@
name: agent-framework-agent-with-foundry-toolbox-responses
description: >
An Agent Framework agent with Foundry Toolbox integration.
metadata:
tags:
- Agent Framework
- AI Agent Hosting
- Azure AI AgentServer
- Responses Protocol
- Streaming
template:
name: agent-framework-agent-with-foundry-toolbox-responses
kind: hosted
protocols:
- protocol: responses
version: 1.0.0
environment_variables:
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
- name: TOOLBOX_NAME
value: "agent-tools"
resources:
- kind: model
id: gpt-4.1-mini
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
- kind: toolbox
name: agent-tools
tools:
- type: web_search
name: web_search
- type: code_interpreter
name: code_interpreter
@@ -1,5 +1,5 @@
kind: hosted
name: agent-framework-agent-with-remote-mcp-tools
name: agent-framework-agent-with-foundry-toolbox-responses
protocols:
- protocol: responses
version: 1.0.0
@@ -0,0 +1,42 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
async def main():
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
# Load the named toolbox from the Foundry project. Omitting `version`
# resolves the toolbox's current default version at runtime.
toolbox = await client.get_toolbox(os.environ["TOOLBOX_NAME"])
agent = Agent(
client=client,
instructions="You are a friendly assistant. Keep your answers brief.",
tools=toolbox,
# History will be managed by the hosting infrastructure, thus there
# is no need to store history by the service. Learn more at:
# https://developers.openai.com/api/reference/resources/responses/methods/create
default_options={"store": False},
)
server = ResponsesHostServer(agent)
await server.run_async()
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,2 @@
agent-framework
agent-framework-foundry-hosting
@@ -1,2 +0,0 @@
FOUNDRY_PROJECT_ENDPOINT="..."
MODEL_DEPLOYMENT_NAME="..."
@@ -1,23 +0,0 @@
# Basic example of hosting an agent with the `responses` API and a workflow
This sample demonstrates how to host a workflow using the `responses` API.
## Running the server locally
### Environment setup
Follow the instructions in the [Environment setup](../../README.md#environment-setup) section of the README in the parent directory to set up your environment and install dependencies.
Run the following command to start the server:
```bash
python main.py
```
## Interacting with the agent
Send a POST request to the server with a JSON body containing a "input" field to interact with the agent. For example:
```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "Create a slogan for a new electric SUV that is affordable and fun to drive."}'
```
@@ -0,0 +1,6 @@
.venv
__pycache__
*.pyc
*.pyo
*.pyd
.Python
@@ -0,0 +1,2 @@
FOUNDRY_PROJECT_ENDPOINT="..."
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
@@ -0,0 +1,16 @@
FROM python:3.12-slim
WORKDIR /app
COPY . user_agent/
WORKDIR /app/user_agent
RUN if [ -f requirements.txt ]; then \
pip install -r requirements.txt; \
else \
echo "No requirements.txt found"; \
fi
EXPOSE 8088
CMD ["python", "main.py"]
@@ -0,0 +1,43 @@
# What this sample demonstrates
An [Agent Framework](https://github.com/microsoft/agent-framework) workflow demonstrating **multi-agent chaining** and hosted using the **Responses protocol**. It shows how to use the Agent Framework's `WorkflowBuilder` to compose a pipeline of specialized agents — a slogan writer, a legal reviewer, and a formatter — that process a request sequentially. Each agent receives only the output of the previous agent, and only the final formatted result is returned to the caller.
> The workflow will be used as an agent. Read more about Agent Framework workflows in the [Agent Framework documentation](https://learn.microsoft.com/en-us/agent-framework/workflows/) and workflow as an agent in the [Workflow as an Agent documentation](https://learn.microsoft.com/en-us/agent-framework/workflows/as-agents?pivots=programming-language-python).
> This sample requires a more advanced model because the model needs to continue the conversation from an assistant message. Not all models perform well in this scenario. Tested with OpenAI's model `gpt-5.4`.
## How It Works
### Model Integration
The agent creates three specialized `Agent` instances sharing the same `FoundryChatClient`: a **writer** that generates slogans, a **legal reviewer** that ensures compliance, and a **formatter** that styles the output. Each agent is wrapped in an `AgentExecutor` with `context_mode="last_agent"` so it only sees the previous agent's output. The `WorkflowBuilder` wires them into a linear pipeline and limits the output to the formatter's result.
See [main.py](main.py) for the full implementation.
### Agent Hosting
The workflow is exposed as a single agent via `.as_agent()` and hosted using the [Agent Framework](https://github.com/microsoft/agent-framework) with the `ResponsesHostServer`, which provisions a REST API endpoint compatible with the OpenAI Responses protocol.
## Running the Agent Host
Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host.
## Interacting with the agent
> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.
Send a POST request to the server with a JSON body containing an `"input"` field to interact with the agent. For example:
```bash
curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "Create a slogan for a new electric SUV that is affordable and fun to drive."}'
```
Invoke with `azd`:
```bash
azd ai agent invoke --local "Create a slogan for a new electric SUV that is affordable and fun to drive."
```
## Deploying the Agent to Foundry
To host the agent on Foundry, follow the instructions in the [Deploying the Agent to Foundry](../../README.md#deploying-the-agent-to-foundry) section of the README in the parent directory.
@@ -1,4 +1,4 @@
name: agent-framework-workflows
name: agent-framework-workflows-responses
description: >
An Agent Framework workflow hosted by Foundry.
metadata:
@@ -9,15 +9,15 @@ metadata:
- Responses Protocol
- Streaming
template:
name: agent-framework-workflows
name: agent-framework-workflows-responses
kind: hosted
protocols:
- protocol: responses
version: 1.0.0
environment_variables:
- name: MODEL_DEPLOYMENT_NAME
value: "{{MODEL_DEPLOYMENT_NAME}}"
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
resources:
- kind: model
id: gpt-4.1-mini
name: MODEL_DEPLOYMENT_NAME
id: gpt-5.4
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
@@ -1,5 +1,5 @@
kind: hosted
name: agent-framework-workflows
name: agent-framework-workflows-responses
protocols:
- protocol: responses
version: 1.0.0
@@ -5,7 +5,7 @@ import os
from agent_framework import Agent, AgentExecutor, WorkflowBuilder
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from azure.identity import AzureCliCredential
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv
# Load environment variables from .env file
@@ -15,8 +15,8 @@ load_dotenv()
def main():
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
writer_agent = Agent(
@@ -1,11 +0,0 @@
# Hosting agents with Foundry Hosting and the `responses` API
This folder contains a list of samples that show how to host agents using the `responses` API and deploy them to Foundry Hosting.
| Sample | Description |
| --- | --- |
| [01_basic](./01_basic) | A basic example of hosting an agent with the `responses` API and carrying on a multi-turn conversation. |
| [02_local_tools](./02_local_tools) | An example of hosting an agent with the `responses` API and local tools including a function tool and a local shell tool. |
| [03_remote_mcp](./03_remote_mcp) | An example of hosting an agent with the `responses` API and remote MCPs, including a GitHub MCP server and a Foundry Toolbox. |
| [04_workflows](./04_workflows) | An example of hosting a workflow with the `responses` API. |
| [using_deployed_agent.py](./using_deployed_agent.py) | An example of how to use the deployed agent in Agent Framework. |
@@ -1,50 +1,146 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import asyncio
import os
from collections.abc import Mapping
from typing import Any, cast
from agent_framework import Agent, AgentResponse, AgentResponseUpdate, ResponseStream
from agent_framework.openai import OpenAIChatClient
from typing_extensions import Any
from agent_framework import AgentSession
from agent_framework.foundry import FoundryAgent
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import VersionRefIndicator
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
load_dotenv()
"""
This script demonstrates how to talk to a deployed agent using the OpenAIChatClient.
This sample demonstrates how to connect to the deployed basic Foundry agent with
`FoundryAgent`.
The sample uses environment variables for configuration, which can be set in a .env file or in the environment directly:
Environment variables:
FOUNDRY_PROJECT_ENDPOINT: Azure AI Foundry project endpoint.
FOUNDRY_AGENT_NAME: Hosted agent name.
FOUNDRY_AGENT_VERSION: Hosted agent version. Optional, defaults to latest if not specified.
After you deploy one of the agents in this directory, you can run this sample
to connect to it and have a conversation.
Note: The `allow_preview=True` flag is required to connect to the new hosted
agents, as this is a preview feature in Foundry.
Depending on where you have deployed your agent (local or Foundry Hosting), you may
need to change the base_url when initializing the OpenAIChatClient.
"""
async def print_streaming_response(streaming_response: ResponseStream[AgentResponseUpdate, AgentResponse[Any]]) -> None:
async for chunk in streaming_response:
if chunk.text:
print(chunk.text, end="", flush=True)
async def create_hosted_agent_session(
*,
agent: FoundryAgent,
project_client: AIProjectClient,
agent_name: str,
agent_version: str | None,
isolation_key: str,
) -> AgentSession:
"""Create a hosted-agent service session and wrap it in an AgentSession."""
create_session_kwargs: dict[str, Any] = {
"agent_name": agent_name,
"isolation_key": isolation_key,
}
resolved_agent_version = agent_version
if resolved_agent_version is None:
agent_details = await cast(Any, project_client.beta.agents).get( # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType]
agent_name=agent_name
)
versions = getattr(agent_details, "versions", None)
if not isinstance(versions, Mapping):
raise ValueError("Hosted agent details did not include a versions mapping.")
latest_version = getattr(cast(Any, versions.get("latest")), "version", None)
if not isinstance(latest_version, str) or not latest_version:
raise ValueError("Hosted agent details did not include a latest version string.")
resolved_agent_version = latest_version
create_session_kwargs["version_indicator"] = VersionRefIndicator(agent_version=resolved_agent_version)
service_session = await project_client.beta.agents.create_session(**create_session_kwargs)
agent_session_id = getattr(service_session, "agent_session_id", None)
if not isinstance(agent_session_id, str) or not agent_session_id:
raise ValueError("Hosted agent session creation did not return a non-empty agent_session_id.")
return agent.get_session(agent_session_id)
async def main() -> None:
agent = Agent(client=OpenAIChatClient(base_url="http://localhost:8088"))
session = agent.create_session()
credential = AzureCliCredential()
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
agent_name = os.environ["FOUNDRY_AGENT_NAME"]
agent_version = os.getenv("FOUNDRY_AGENT_VERSION")
isolation_key = "my-isolation-key"
# First turn
query = "Hi!"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
streaming_response = agent.run(query, session=session, stream=True)
await print_streaming_response(streaming_response)
project_client = AIProjectClient(
endpoint=project_endpoint,
credential=credential,
allow_preview=True,
)
async with (
project_client,
FoundryAgent(
project_client=project_client,
agent_name=agent_name,
agent_version=agent_version,
allow_preview=True,
) as agent,
):
session = await create_hosted_agent_session(
agent=agent,
project_client=project_client,
agent_name=agent_name,
agent_version=agent_version,
isolation_key=isolation_key,
)
# Second turn
query = "Your name is Javis. What can you do?"
print(f"\nUser: {query}")
print("Agent: ", end="", flush=True)
streaming_response = agent.run(query, session=session, stream=True)
await print_streaming_response(streaming_response)
try:
# 1. Send the first turn.
query = "Hi!"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
async for chunk in agent.run(query, session=session, stream=True):
if chunk.text:
print(chunk.text, end="", flush=True)
# Third turn
query = "What is your name?"
print(f"\nUser: {query}")
print("Agent: ", end="", flush=True)
streaming_response = agent.run(query, session=session, stream=True)
await print_streaming_response(streaming_response)
# 2. Continue the conversation with the same deployed agent session.
query = "Your name is Javis. What can you do?"
print(f"\nUser: {query}")
print("Agent: ", end="", flush=True)
async for chunk in agent.run(query, session=session, stream=True):
if chunk.text:
print(chunk.text, end="", flush=True)
# 3. Ask a follow-up question in the same session.
query = "What is your name?"
print(f"\nUser: {query}")
print("Agent: ", end="", flush=True)
async for chunk in agent.run(query, session=session, stream=True):
if chunk.text:
print(chunk.text, end="", flush=True)
finally:
if session.service_session_id is not None:
await project_client.beta.agents.delete_session(
agent_name=agent_name,
session_id=session.service_session_id,
isolation_key=isolation_key,
)
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
User: Hi!
Agent: Hello! How can I help you today?
User: Your name is Javis. What can you do?
Agent: I can answer questions and help with tasks using the instructions configured on the deployed agent.
User: What is your name?
Agent: My name is Javis.
"""
@@ -1556,9 +1556,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1569,9 +1569,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"version": "8.5.12",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
"integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==",
"dev": true,
"funding": [
{
@@ -17,7 +17,7 @@
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"typescript": "^5.4.0",
"vite": "^7.1.12"
"vite": "^7.3.2"
},
"engines": {
"node": ">=18.18",
@@ -25,9 +25,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz",
"integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
"cpu": [
"ppc64"
],
@@ -42,9 +42,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz",
"integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
"cpu": [
"arm"
],
@@ -59,9 +59,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz",
"integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
"cpu": [
"arm64"
],
@@ -76,9 +76,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz",
"integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
"cpu": [
"x64"
],
@@ -93,9 +93,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz",
"integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
"cpu": [
"arm64"
],
@@ -110,9 +110,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz",
"integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
"cpu": [
"x64"
],
@@ -127,9 +127,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz",
"integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
"cpu": [
"arm64"
],
@@ -144,9 +144,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz",
"integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
"cpu": [
"x64"
],
@@ -161,9 +161,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz",
"integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
"cpu": [
"arm"
],
@@ -178,9 +178,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz",
"integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
"cpu": [
"arm64"
],
@@ -195,9 +195,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz",
"integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
"cpu": [
"ia32"
],
@@ -212,9 +212,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz",
"integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
"cpu": [
"loong64"
],
@@ -229,9 +229,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz",
"integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
"cpu": [
"mips64el"
],
@@ -246,9 +246,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz",
"integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
"cpu": [
"ppc64"
],
@@ -263,9 +263,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz",
"integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
"cpu": [
"riscv64"
],
@@ -280,9 +280,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz",
"integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
"cpu": [
"s390x"
],
@@ -297,9 +297,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz",
"integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
"cpu": [
"x64"
],
@@ -314,9 +314,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz",
"integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
"cpu": [
"arm64"
],
@@ -331,9 +331,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz",
"integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
"cpu": [
"x64"
],
@@ -348,9 +348,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz",
"integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
"cpu": [
"arm64"
],
@@ -365,9 +365,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz",
"integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
"cpu": [
"x64"
],
@@ -382,9 +382,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz",
"integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
"cpu": [
"arm64"
],
@@ -399,9 +399,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz",
"integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
"cpu": [
"x64"
],
@@ -416,9 +416,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz",
"integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
"cpu": [
"arm64"
],
@@ -433,9 +433,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz",
"integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
"cpu": [
"ia32"
],
@@ -450,9 +450,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz",
"integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
"cpu": [
"x64"
],
@@ -1117,9 +1117,9 @@
"license": "MIT"
},
"node_modules/esbuild": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz",
"integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==",
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -1130,32 +1130,32 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.10",
"@esbuild/android-arm": "0.25.10",
"@esbuild/android-arm64": "0.25.10",
"@esbuild/android-x64": "0.25.10",
"@esbuild/darwin-arm64": "0.25.10",
"@esbuild/darwin-x64": "0.25.10",
"@esbuild/freebsd-arm64": "0.25.10",
"@esbuild/freebsd-x64": "0.25.10",
"@esbuild/linux-arm": "0.25.10",
"@esbuild/linux-arm64": "0.25.10",
"@esbuild/linux-ia32": "0.25.10",
"@esbuild/linux-loong64": "0.25.10",
"@esbuild/linux-mips64el": "0.25.10",
"@esbuild/linux-ppc64": "0.25.10",
"@esbuild/linux-riscv64": "0.25.10",
"@esbuild/linux-s390x": "0.25.10",
"@esbuild/linux-x64": "0.25.10",
"@esbuild/netbsd-arm64": "0.25.10",
"@esbuild/netbsd-x64": "0.25.10",
"@esbuild/openbsd-arm64": "0.25.10",
"@esbuild/openbsd-x64": "0.25.10",
"@esbuild/openharmony-arm64": "0.25.10",
"@esbuild/sunos-x64": "0.25.10",
"@esbuild/win32-arm64": "0.25.10",
"@esbuild/win32-ia32": "0.25.10",
"@esbuild/win32-x64": "0.25.10"
"@esbuild/aix-ppc64": "0.27.7",
"@esbuild/android-arm": "0.27.7",
"@esbuild/android-arm64": "0.27.7",
"@esbuild/android-x64": "0.27.7",
"@esbuild/darwin-arm64": "0.27.7",
"@esbuild/darwin-x64": "0.27.7",
"@esbuild/freebsd-arm64": "0.27.7",
"@esbuild/freebsd-x64": "0.27.7",
"@esbuild/linux-arm": "0.27.7",
"@esbuild/linux-arm64": "0.27.7",
"@esbuild/linux-ia32": "0.27.7",
"@esbuild/linux-loong64": "0.27.7",
"@esbuild/linux-mips64el": "0.27.7",
"@esbuild/linux-ppc64": "0.27.7",
"@esbuild/linux-riscv64": "0.27.7",
"@esbuild/linux-s390x": "0.27.7",
"@esbuild/linux-x64": "0.27.7",
"@esbuild/netbsd-arm64": "0.27.7",
"@esbuild/netbsd-x64": "0.27.7",
"@esbuild/openbsd-arm64": "0.27.7",
"@esbuild/openbsd-x64": "0.27.7",
"@esbuild/openharmony-arm64": "0.27.7",
"@esbuild/sunos-x64": "0.27.7",
"@esbuild/win32-arm64": "0.27.7",
"@esbuild/win32-ia32": "0.27.7",
"@esbuild/win32-x64": "0.27.7"
}
},
"node_modules/fsevents": {
@@ -1199,10 +1199,23 @@
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
"dev": true,
"funding": [
{
@@ -1345,19 +1358,6 @@
}
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -1373,13 +1373,13 @@
}
},
"node_modules/vite": {
"version": "7.1.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
"integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
@@ -1464,19 +1464,6 @@
"optional": true
}
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
}
}
}
@@ -22,6 +22,6 @@
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"typescript": "^5.4.0",
"vite": "^7.1.12"
"vite": "^7.3.2"
}
}
+4 -2
View File
@@ -20,8 +20,10 @@ Start with `01-get-started/` and work through the numbered files:
2. **[02_add_tools.py](./01-get-started/02_add_tools.py)** — Add function tools with `@tool`
3. **[03_multi_turn.py](./01-get-started/03_multi_turn.py)** — Multi-turn conversations with `AgentSession`
4. **[04_memory.py](./01-get-started/04_memory.py)** — Agent memory with `ContextProvider`
5. **[05_first_workflow.py](./01-get-started/05_first_workflow.py)** — Build a workflow with executors and edges
6. **[06_host_your_agent.py](./01-get-started/06_host_your_agent.py)** — Host your agent via Azure Functions
5. **[05_functional_workflow_with_agents.py](./01-get-started/05_functional_workflow_with_agents.py)** — Call agents inside a functional workflow
6. **[06_functional_workflow_basics.py](./01-get-started/06_functional_workflow_basics.py)** — Write a workflow as a plain async function
7. **[07_first_graph_workflow.py](./01-get-started/07_first_graph_workflow.py)** — Build a workflow with executors and edges
8. **[08_host_your_agent.py](./01-get-started/08_host_your_agent.py)** — Host your agent via Azure Functions
## Prerequisites