Merge branch 'main' into copilot/move-workflow-and-agent-samples

This commit is contained in:
Shawn Henry
2026-04-02 04:21:26 +00:00
Unverified
467 changed files with 12233 additions and 7020 deletions
+2 -2
View File
@@ -3,7 +3,7 @@
import asyncio
from typing import Any
from agent_framework import Agent, AgentSession, BaseContextProvider, SessionContext
from agent_framework import Agent, AgentSession, ContextProvider, SessionContext
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
@@ -17,7 +17,7 @@ responses — the name persists across turns via the session.
# <context_provider>
class UserMemoryProvider(BaseContextProvider):
class UserMemoryProvider(ContextProvider):
"""A context provider that remembers user info in session state."""
DEFAULT_SOURCE_ID = "user_memory"
+14 -4
View File
@@ -9,6 +9,7 @@ This folder contains examples for direct chat client usage patterns.
| [`built_in_chat_clients.py`](built_in_chat_clients.py) | Consolidated sample for built-in chat clients. Uses `get_client()` to create the selected client and pass it to `main()`. |
| [`chat_response_cancellation.py`](chat_response_cancellation.py) | Demonstrates how to cancel chat responses during streaming, showing proper cancellation handling and cleanup. |
| [`custom_chat_client.py`](custom_chat_client.py) | Demonstrates how to create custom chat clients by extending the `BaseChatClient` class. Shows a `EchoingChatClient` implementation and how to integrate it with `Agent` using the `as_agent()` method. |
| [`require_per_service_call_history_persistence.py`](require_per_service_call_history_persistence.py) | Compares two otherwise identical `FoundryChatClient` agents with `store=False`; the only difference is whether `require_per_service_call_history_persistence` is enabled, and only the run without it stores the synthesized tool result when middleware terminates the loop early. |
## Selecting a built-in client
@@ -35,13 +36,22 @@ Example:
uv run samples/02-agents/chat_client/built_in_chat_clients.py
```
The `require_per_service_call_history_persistence.py` sample uses `FoundryChatClient`, so set the usual Foundry settings first and sign in with the Azure CLI:
```bash
export FOUNDRY_PROJECT_ENDPOINT="https://<your-project>.services.ai.azure.com/api/projects/<project-name>"
export FOUNDRY_MODEL="<your-model-deployment-name>"
az login
uv run samples/02-agents/chat_client/require_per_service_call_history_persistence.py
```
## Environment Variables
Depending on the selected client, set the appropriate environment variables:
**For Azure OpenAI clients (`azure_openai_responses` and `azure_openai_chat_completion`):**
- `AZURE_OPENAI_ENDPOINT`: Your Azure OpenAI endpoint
- `AZURE_OPENAI_DEPLOYMENT_NAME`: The Azure OpenAI deployment used by the sample
- `AZURE_OPENAI_MODEL`: The Azure OpenAI deployment used by the sample
- `AZURE_OPENAI_API_VERSION` (optional): Azure OpenAI API version override
- `AZURE_OPENAI_API_KEY` (optional): Azure OpenAI API key if you are not using `AzureCliCredential`
@@ -56,13 +66,13 @@ Depending on the selected client, set the appropriate environment variables:
**For Anthropic client (`anthropic`):**
- `ANTHROPIC_API_KEY`: Your Anthropic API key
- `ANTHROPIC_CHAT_MODEL_ID`: The Anthropic model ID (for example, `claude-sonnet-4-5`)
- `ANTHROPIC_CHAT_MODEL`: The Anthropic model to use (for example, `claude-sonnet-4-5`)
**For Ollama client (`ollama`):**
- `OLLAMA_HOST`: Ollama server URL (defaults to `http://localhost:11434` if unset)
- `OLLAMA_MODEL_ID`: Ollama model name (for example, `mistral`, `qwen2.5:8b`)
- `OLLAMA_MODEL`: Ollama model name (for example, `mistral`, `qwen2.5:8b`)
**For Bedrock client (`bedrock`):**
- `BEDROCK_CHAT_MODEL_ID`: Bedrock model ID (for example, `anthropic.claude-3-5-sonnet-20240620-v1:0`)
- `BEDROCK_CHAT_MODEL`: Bedrock model ID (for example, `anthropic.claude-3-5-sonnet-20240620-v1:0`)
- `BEDROCK_REGION`: AWS region (defaults to `us-east-1` if unset)
- AWS credentials via standard environment variables (for example, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`)
@@ -22,31 +22,29 @@ This sample demonstrates how to run the same prompt flow against different built
chat clients using a single `get_client` factory.
Select one of these client names:
- openai_responses
- openai_chat
- openai_chat_completion
- anthropic
- ollama
- bedrock
- azure_openai_responses
- azure_openai_chat
- azure_openai_chat_completion
- foundry_chat
"""
ClientName = Literal[
"openai_responses",
"openai_chat",
"openai_chat_completion",
"anthropic",
"ollama",
"bedrock",
"azure_openai_responses",
"azure_openai_chat",
"azure_openai_chat_completion",
"foundry_chat",
]
# NOTE: approval_mode="never_require" is for sample brevity.
# Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -62,7 +60,7 @@ def get_client(client_name: ClientName) -> SupportsChatGetResponse[Any]:
from agent_framework.anthropic import AnthropicClient
from agent_framework.ollama import OllamaChatClient
if client_name == "openai_responses":
if client_name == "openai_chat":
return OpenAIChatClient()
if client_name == "openai_chat_completion":
return OpenAIChatCompletionClient()
@@ -72,7 +70,7 @@ def get_client(client_name: ClientName) -> SupportsChatGetResponse[Any]:
return OllamaChatClient()
if client_name == "bedrock":
return BedrockChatClient()
if client_name == "azure_openai_responses":
if client_name == "azure_openai_chat":
return OpenAIChatClient(credential=AzureCliCredential())
if client_name == "azure_openai_chat_completion":
return OpenAIChatCompletionClient(credential=AzureCliCredential())
@@ -86,7 +84,7 @@ def get_client(client_name: ClientName) -> SupportsChatGetResponse[Any]:
raise ValueError(f"Unsupported client name: {client_name}")
async def main(client_name: ClientName = "openai_responses") -> None:
async def main(client_name: ClientName = "openai_chat") -> None:
"""Run a basic prompt using a selected built-in client."""
client = get_client(client_name)
@@ -122,7 +120,7 @@ async def main(client_name: ClientName = "openai_responses") -> None:
if __name__ == "__main__":
asyncio.run(main("openai_responses"))
asyncio.run(main("openai_chat"))
"""
@@ -24,7 +24,7 @@ async def main() -> None:
Creates a task for the chat request, waits briefly, then cancels it to show proper cleanup.
Configuration:
- OpenAI model ID: Use "model_id" parameter or "OPENAI_MODEL" environment variable
- OpenAI model ID: Use "model" parameter or "OPENAI_MODEL" environment variable
- OpenAI API key: Use "api_key" parameter or "OPENAI_API_KEY" environment variable
"""
client = FoundryChatClient(credential=AzureCliCredential())
@@ -102,7 +102,7 @@ class EchoingChatClient(BaseChatClient[OptionsT]):
response = ChatResponse(
messages=[response_message],
model_id="echo-model-v1",
model="echo-model-v1",
response_id=f"echo-resp-{random.randint(1000, 9999)}",
)
@@ -120,7 +120,7 @@ class EchoingChatClient(BaseChatClient[OptionsT]):
contents=[Content.from_text(char)],
role="assistant",
response_id=f"echo-stream-resp-{random.randint(1000, 9999)}",
model_id="echo-model-v1",
model="echo-model-v1",
)
await asyncio.sleep(stream_delay_seconds)
@@ -0,0 +1,194 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable
from typing import Annotated
from agent_framework import (
Agent,
FunctionInvocationContext,
FunctionMiddleware,
InMemoryHistoryProvider,
Message,
MiddlewareTermination,
)
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from pydantic import Field
"""
Compare Foundry agents with and without per-service-call chat history persistence.
This sample runs two otherwise identical Foundry agents with ``store=False`` so
history stays local for both runs.
The sample adds a function middleware that raises ``MiddlewareTermination``
immediately after the tool runs, so the request stops before a second model
call.
That early termination is the important difference:
- Without per-service-call chat history persistence, the synthesized tool result is
still written to local history.
- With ``require_per_service_call_history_persistence=True``, that synthesized tool result is
not written to local history.
The per-service-call persistence case matches service-side storage behavior. When a terminated
request never sends the tool result back to the service, that result also never
becomes part of the service-managed history.
"""
# Load environment variables from .env file
load_dotenv()
def lookup_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Return a deterministic weather result for the requested location."""
return f"The weather in {location} is sunny."
class TerminateAfterToolMiddleware(FunctionMiddleware):
"""Stop the tool loop after the first tool finishes."""
async def process(
self,
context: FunctionInvocationContext,
call_next: Callable[[], Awaitable[None]],
) -> None:
"""Run the tool, then terminate the loop with that tool result."""
await call_next()
raise MiddlewareTermination(result=context.result)
def _describe_message(message: Message) -> str:
"""Render one stored message in a compact, readable format."""
parts: list[str] = []
for content in message.contents:
if content.type == "text" and content.text:
parts.append(content.text)
elif content.type == "function_call":
parts.append(f"function_call -> {content.name}({content.arguments})")
elif content.type == "function_result":
parts.append(f"function_result -> {content.result}")
else:
parts.append(content.type)
return f"{message.role}: {' | '.join(parts)}"
def _includes_tool_result(messages: list[Message]) -> bool:
"""Return whether any stored message contains a tool result."""
return any(content.type == "function_result" for message in messages for content in message.contents)
async def main() -> None:
"""Run both comparison scenarios."""
print("=== require_per_service_call_history_persistence when middleware terminates the tool loop ===\n")
# 1. Create one Foundry chat client that both agents will share.
client = FoundryChatClient(credential=AzureCliCredential())
query = "What is the weather in Seattle, and should I bring sunglasses?"
# 2. Create and run the agent without per-service-call persistence.
agent_without_persistence = Agent(
client=client,
instructions=(
"You are a weather assistant. Call lookup_weather exactly once before answering "
"any weather question, then summarize the tool result in one short paragraph."
),
tools=[lookup_weather],
context_providers=[InMemoryHistoryProvider()],
middleware=[TerminateAfterToolMiddleware()],
default_options={"tool_choice": "required", "store": False},
)
session_without_persistence = agent_without_persistence.create_session()
await agent_without_persistence.run(
query,
session=session_without_persistence,
)
stored_messages_without_persistence = session_without_persistence.state[InMemoryHistoryProvider.DEFAULT_SOURCE_ID][
"messages"
]
print("=== Without per-service-call persistence ===")
print("Loop terminated immediately after the tool finished.")
print(f"Stored synthesized tool result: {_includes_tool_result(stored_messages_without_persistence)}")
print("Stored history:")
for index, message in enumerate(stored_messages_without_persistence, start=1):
print(f" {index}. {_describe_message(message)}")
print()
# 3. Create and run the agent with per-service-call persistence enabled.
agent_with_persistence = Agent(
client=client,
instructions=(
"You are a weather assistant. Call lookup_weather exactly once before answering "
"any weather question, then summarize the tool result in one short paragraph."
),
tools=[lookup_weather],
context_providers=[InMemoryHistoryProvider()],
middleware=[TerminateAfterToolMiddleware()],
require_per_service_call_history_persistence=True,
default_options={"tool_choice": "required", "store": False},
)
session_with_persistence = agent_with_persistence.create_session()
await agent_with_persistence.run(
query,
session=session_with_persistence,
)
stored_messages_with_persistence = session_with_persistence.state[InMemoryHistoryProvider.DEFAULT_SOURCE_ID][
"messages"
]
print("=== With per-service-call persistence ===")
print("Loop terminated immediately after the tool finished.")
print(f"Stored synthesized tool result: {_includes_tool_result(stored_messages_with_persistence)}")
print("Stored history:")
for index, message in enumerate(stored_messages_with_persistence, start=1):
print(f" {index}. {_describe_message(message)}")
print()
# 4. Summarize the effect of the flag.
print(
"Both runs used FoundryChatClient with store=False and terminated right after the tool. "
"Without per-service-call persistence, local history still stored the synthesized tool result. "
"With per-service-call persistence, local history stopped at the assistant function-call message instead, "
"which matches service-side storage because the terminated tool result is never sent back to the service."
)
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
=== require_per_service_call_history_persistence when middleware terminates the tool loop ===
=== Without per-service-call persistence ===
Loop terminated immediately after the tool finished.
Stored synthesized tool result: True
Stored history:
1. user: What is the weather in Seattle, and should I bring sunglasses?
2. assistant: function_call -> lookup_weather({"location":"Seattle"})
3. tool: function_result -> The weather in Seattle is sunny.
=== With per-service-call persistence ===
Loop terminated immediately after the tool finished.
Stored synthesized tool result: False
Stored history:
1. user: What is the weather in Seattle, and should I bring sunglasses?
2. assistant: function_call -> lookup_weather({"location":"Seattle"})
Both runs used FoundryChatClient with store=False and terminated right after
the tool. Without per-service-call persistence, local history still stored the
synthesized tool result. With per-service-call persistence, local history
stopped at the assistant function-call message instead, which matches
service-side storage because the terminated tool result is never sent back to
the service.
"""
@@ -11,7 +11,7 @@
import asyncio
from typing import Any
import tiktoken
import tiktoken # type: ignore
from agent_framework import (
Message,
TokenizerProtocol,
@@ -33,9 +33,9 @@ Key components:
class TiktokenTokenizer(TokenizerProtocol):
"""TokenizerProtocol implementation backed by tiktoken's o200k_base (gpt-4.1 and up default) encoding."""
def __init__(self, *, encoding_name: str = "o200k_base", model_name: str | None = None) -> None:
if model_name is not None:
self._encoding = tiktoken.encoding_for_model(model_name)
def __init__(self, *, encoding_name: str = "o200k_base", model: str | None = None) -> None:
if model is not None:
self._encoding = tiktoken.encoding_for_model(model)
else:
self._encoding: Any = tiktoken.get_encoding(encoding_name)
@@ -0,0 +1,28 @@
# Context Provider Samples
These samples demonstrate how to use context providers to enrich agent conversations with external knowledge — from custom logic to Azure AI Search (RAG) and memory services.
## Samples
| File / Folder | Description |
|---------------|-------------|
| [`simple_context_provider.py`](simple_context_provider.py) | Implement a custom context provider by extending `BaseContextProvider` to extract and inject structured user information across turns. |
| [`azure_ai_foundry_memory.py`](azure_ai_foundry_memory.py) | Use `FoundryMemoryProvider` to add semantic memory — automatically retrieves, searches, and stores memories via Azure AI Foundry. |
| [`azure_ai_search/`](azure_ai_search/) | Retrieval Augmented Generation (RAG) with Azure AI Search in semantic and agentic modes. See its own [README](azure_ai_search/README.md). |
| [`mem0/`](mem0/) | Memory-powered context using the Mem0 integration (open-source and managed). See its own [README](mem0/README.md). |
| [`redis/`](redis/) | Redis-backed context providers for conversation memory and sessions. See its own [README](redis/README.md). |
## Prerequisites
**For `simple_context_provider.py`:**
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `FOUNDRY_MODEL`: Model deployment name
- Azure CLI authentication (`az login`)
**For `azure_ai_foundry_memory.py`:**
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `FOUNDRY_MODEL`: Chat/responses model deployment name
- `AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME`: Embedding model deployment name (e.g., `text-embedding-ada-002`)
- Azure CLI authentication (`az login`)
See each subfolder's README for provider-specific prerequisites.
@@ -33,7 +33,7 @@ rather than chat history. The memory store is deleted at the end of the run.
Prerequisites:
1. Set FOUNDRY_PROJECT_ENDPOINT environment variable
2. Set FOUNDRY_MODEL for the chat/responses model
3. Set AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME for the embedding model
3. Set AZURE_OPENAI_EMBEDDING_MODEL for the embedding model
4. Deploy both a chat model (e.g. gpt-4) and an embedding model (e.g. text-embedding-3-small)
"""
load_dotenv()
@@ -55,7 +55,7 @@ async def main() -> None:
)
memory_store_definition = MemoryStoreDefaultDefinition(
chat_model=os.environ["FOUNDRY_MODEL"],
embedding_model=os.environ["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"],
embedding_model=os.environ["AZURE_OPENAI_EMBEDDING_MODEL"],
options=options,
)
print(f"Creating memory store '{memory_store_name}'...")
@@ -100,7 +100,7 @@ async def main() -> None:
credential=AzureCliCredential() if not search_key else None,
mode="agentic",
azure_openai_resource_url=azure_openai_resource_url,
model_model=model_deployment,
model_deployment_name=model_deployment,
# Optional: Configure retrieval behavior
knowledge_base_output_mode="extractive_data", # or "answer_synthesis"
retrieval_reasoning_effort="minimal", # or "medium", "low"
@@ -110,13 +110,12 @@ async def main() -> None:
# Create agent with search context provider
async with (
search_provider,
FoundryChatClient(
project_endpoint=project_endpoint,
model_model=model_deployment,
credential=AzureCliCredential(),
) as client,
Agent(
client=client,
client=FoundryChatClient(
project_endpoint=project_endpoint,
model=model_deployment,
credential=AzureCliCredential(),
),
name="SearchAgent",
instructions=(
"You are a helpful assistant with advanced reasoning capabilities. "
@@ -32,7 +32,7 @@ Prerequisites:
- AZURE_SEARCH_INDEX_NAME: Your search index name
- FOUNDRY_PROJECT_ENDPOINT: Your Azure AI Foundry project endpoint
- FOUNDRY_MODEL: Your model deployment name (e.g., "gpt-4o")
- AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: (Optional) Your Azure OpenAI embedding deployment for hybrid search
- AZURE_OPENAI_EMBEDDING_MODEL: (Optional) Your Azure OpenAI embedding deployment for hybrid search
- AZURE_OPENAI_ENDPOINT: (Optional) Your Azure OpenAI resource URL, required if using Azure OpenAI embeddings
"""
@@ -56,7 +56,7 @@ async def main() -> None:
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
model_deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o")
openai_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT")
embedding_deployment = os.environ.get("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME")
embedding_deployment = os.environ.get("AZURE_OPENAI_EMBEDDING_MODEL")
embedding_client = None
if openai_endpoint and embedding_deployment:
@@ -85,13 +85,12 @@ async def main() -> None:
# Create agent with search context provider
async with (
search_provider,
FoundryChatClient(
project_endpoint=project_endpoint,
model_model=model_deployment,
credential=credential,
) as client,
Agent(
client=client,
client=FoundryChatClient(
project_endpoint=project_endpoint,
model=model_deployment,
credential=credential,
),
name="SearchAgent",
instructions=(
"You are a helpful assistant. Use the provided context from the "
@@ -9,7 +9,7 @@ This folder contains examples demonstrating how to use the Mem0 context provider
| File | Description |
|------|-------------|
| [`mem0_basic.py`](mem0_basic.py) | Basic example of using Mem0 context provider to store and retrieve user preferences across different conversation threads. |
| [`mem0_sessions.py`](mem0_sessions.py) | Advanced example demonstrating different thread scoping strategies with Mem0. Covers global thread scope (memories shared across all operations), per-operation thread scope (memories isolated per thread), and multiple agents with different memory configurations for personal vs. work contexts. |
| [`mem0_sessions.py`](mem0_sessions.py) | Example demonstrating different memory scoping strategies with Mem0. Covers user-scoped memory (memories shared across all sessions for the same user), agent-scoped memory (memories isolated per agent), and multiple agents with different memory configurations for personal vs. work contexts. |
| [`mem0_oss.py`](mem0_oss.py) | Example of using the Mem0 Open Source self-hosted version as the context provider. Demonstrates setup and configuration for local deployment. |
## Prerequisites
@@ -40,16 +40,8 @@ Set the following environment variables:
### Memory Scoping
The Mem0 context provider supports different scoping strategies:
The Mem0 context provider supports scoping via identifiers:
- **Global Scope** (`scope_to_per_operation_thread_id=False`): Memories are shared across all conversation threads
- **Thread Scope** (`scope_to_per_operation_thread_id=True`): Memories are isolated per conversation thread
### Memory Association
Mem0 records can be associated with different identifiers:
- `user_id`: Associate memories with a specific user
- `agent_id`: Associate memories with a specific agent
- `thread_id`: Associate memories with a specific conversation thread
- `application_id`: Associate memories with an application context
- **User scope** (`user_id`): Associate memories with a specific user, shared across all sessions
- **Agent scope** (`agent_id`): Isolate memories per agent persona
- **Application scope** (`application_id`): Associate memories with an application context
@@ -31,7 +31,7 @@ def retrieve_company_report(company_code: str, detailed: bool) -> str:
async def main() -> None:
"""Example of memory usage with Mem0 context provider."""
print("=== Mem0 Context Provider Example ===")
# Each record in Mem0 should be associated with agent_id or user_id or application_id or thread_id.
# Each record in Mem0 should be associated with agent_id or user_id or application_id.
# In this example, we associate Mem0 records with user_id.
user_id = str(uuid.uuid4())
# For Azure authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
@@ -57,12 +57,16 @@ async def main() -> None:
# Now tell the agent the company code and the report format that you want to use
# and it should be able to invoke the tool and return the report.
query = "I always work with CNTS and I always want a detailed report format. Please remember and retrieve it."
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
# Mem0 processes and indexes memories asynchronously.
# Wait for memories to be indexed before querying in a new thread.
# In production, consider implementing retry logic or using Mem0's
# eventual consistency handling instead of a fixed delay.
print("Waiting for memories to be processed...")
await asyncio.sleep(12) # Empirically determined delay for Mem0 indexing
await asyncio.sleep(15) # Empirically determined delay for Mem0 indexing
print("\nRequest within a new session:")
# Create a new session for the agent.
# The new session has no context of the previous conversation.
@@ -70,7 +74,10 @@ async def main() -> None:
# Since we have the mem0 component in the session, the agent should be able to
# retrieve the company report without asking for clarification, as it will
# be able to remember the user preferences from Mem0 component.
query = "Please retrieve my company report"
print(f"User: {query}")
result = await agent.run(query, session=session)
print(f"Agent: {result}")
if __name__ == "__main__":
@@ -32,7 +32,7 @@ def retrieve_company_report(company_code: str, detailed: bool) -> str:
async def main() -> None:
"""Example of memory usage with local Mem0 OSS context provider."""
print("=== Mem0 Context Provider Example ===")
# Each record in Mem0 should be associated with agent_id or user_id or application_id or thread_id.
# Each record in Mem0 should be associated with agent_id or user_id or application_id.
# In this example, we associate Mem0 records with user_id.
user_id = str(uuid.uuid4())
# For Azure authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
@@ -1,7 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import uuid
from agent_framework import Agent, tool
from agent_framework.foundry import FoundryChatClient
@@ -27,103 +26,84 @@ def get_user_preferences(user_id: str) -> str:
return preferences.get(user_id, "No specific preferences found")
async def example_global_thread_scope() -> None:
"""Example 1: Global thread_id scope (memories shared across all operations)."""
print("1. Global Thread Scope Example:")
async def example_user_scoped_memory() -> None:
"""Example 1: User-scoped memory (memories shared across all sessions for the same user)."""
print("1. User-Scoped Memory Example:")
print("-" * 40)
global_thread_id = str(uuid.uuid4())
user_id = "user123"
async with (
AzureCliCredential() as credential,
Agent(
client=FoundryChatClient(credential=credential),
name="GlobalMemoryAssistant",
name="UserMemoryAssistant",
instructions="You are an assistant that remembers user preferences across conversations.",
tools=get_user_preferences,
context_providers=[
Mem0ContextProvider(
source_id="mem0",
user_id=user_id,
thread_id=global_thread_id,
scope_to_per_operation_thread_id=False, # Share memories across all sessions
)
],
) as global_agent,
) as user_agent,
):
# Store some preferences in the global scope
# Store some preferences
query = "Remember that I prefer technical responses with code examples when discussing programming."
print(f"User: {query}")
result = await global_agent.run(query)
result = await user_agent.run(query)
print(f"Agent: {result}\n")
# Create a new session - but memories should still be accessible due to global scope
new_session = global_agent.create_session()
# Create a new session - memories should still be accessible via user_id scoping
new_session = user_agent.create_session()
query = "What do you know about my preferences?"
print(f"User (new session): {query}")
result = await global_agent.run(query, session=new_session)
result = await user_agent.run(query, session=new_session)
print(f"Agent: {result}\n")
async def example_per_operation_thread_scope() -> None:
"""Example 2: Per-operation thread scope (memories isolated per session).
async def example_agent_scoped_memory() -> None:
"""Example 2: Agent-scoped memory (memories isolated per agent_id).
Note: When scope_to_per_operation_thread_id=True, the provider is bound to a single session
throughout its lifetime. Use the same session object for all operations with that provider.
Note: Use different agent_id values to isolate memories between different
agent personas, even when the user_id is the same.
"""
print("2. Per-Operation Thread Scope Example:")
print("2. Agent-Scoped Memory Example:")
print("-" * 40)
user_id = "user123"
async with (
AzureCliCredential() as credential,
Agent(
client=FoundryChatClient(credential=credential),
name="ScopedMemoryAssistant",
instructions="You are an assistant with thread-scoped memory.",
instructions="You are an assistant with agent-scoped memory.",
tools=get_user_preferences,
context_providers=[
Mem0ContextProvider(
source_id="mem0",
user_id=user_id,
scope_to_per_operation_thread_id=True, # Isolate memories per session
agent_id="scoped_assistant",
)
],
) as scoped_agent,
):
# Create a specific session for this scoped provider
dedicated_session = scoped_agent.create_session()
# Store some information in the dedicated session
query = "Remember that for this conversation, I'm working on a Python project about data analysis."
print(f"User (dedicated session): {query}")
result = await scoped_agent.run(query, session=dedicated_session)
query = (
"Remember that I'm working on a Python project about data analysis "
"and I prefer using pandas and matplotlib."
)
print(f"User: {query}")
result = await scoped_agent.run(query)
print(f"Agent: {result}\n")
# Test memory retrieval in the same dedicated session
query = "What project am I working on?"
print(f"User (same dedicated session): {query}")
result = await scoped_agent.run(query, session=dedicated_session)
print(f"Agent: {result}\n")
# Store more information in the same session
query = "Also remember that I prefer using pandas and matplotlib for this project."
print(f"User (same dedicated session): {query}")
result = await scoped_agent.run(query, session=dedicated_session)
print(f"Agent: {result}\n")
# Test comprehensive memory retrieval
new_session = scoped_agent.create_session()
query = "What do you know about my current project and preferences?"
print(f"User (same dedicated session): {query}")
result = await scoped_agent.run(query, session=dedicated_session)
print(f"User (new session): {query}")
result = await scoped_agent.run(query, session=new_session)
print(f"Agent: {result}\n")
async def example_multiple_agents() -> None:
"""Example 3: Multiple agents with different thread configurations."""
print("3. Multiple Agents with Different Thread Configurations:")
"""Example 3: Multiple agents with different memory configurations."""
print("3. Multiple Agents with Different Memory Configurations:")
print("-" * 40)
agent_id_1 = "agent_personal"
@@ -178,11 +158,11 @@ async def example_multiple_agents() -> None:
async def main() -> None:
"""Run all Mem0 thread management examples."""
print("=== Mem0 Thread Management Example ===\n")
"""Run all Mem0 memory management examples."""
print("=== Mem0 Memory Management Example ===\n")
await example_global_thread_scope()
await example_per_operation_thread_scope()
await example_user_scoped_memory()
await example_agent_scoped_memory()
await example_multiple_agents()
@@ -11,7 +11,7 @@ This folder contains an example demonstrating how to use the Redis context provi
| [`azure_redis_conversation.py`](azure_redis_conversation.py) | Demonstrates conversation persistence with RedisHistoryProvider and Azure Redis with Azure AD (Entra ID) authentication using credential provider. |
| [`redis_basics.py`](redis_basics.py) | Shows standalone provider usage and agent integration. Demonstrates writing messages to Redis, retrieving context via fulltext or hybrid vector search, and persisting preferences across threads. Also includes a simple tool example whose outputs are remembered. |
| [`redis_conversation.py`](redis_conversation.py) | Simple example showing conversation persistence with RedisContextProvider using traditional connection string authentication. |
| [`redis_sessions.py`](redis_sessions.py) | Demonstrates thread scoping. Includes: (1) global thread scope with a fixed `thread_id` shared across operations; (2) peroperation thread scope where `scope_to_per_operation_thread_id=True` binds memory to a single thread for the provider's lifetime; and (3) multiple agents with isolated memory via different `agent_id` values. |
| [`redis_sessions.py`](redis_sessions.py) | Demonstrates memory scoping strategies. Includes: (1) global memory scope with `application_id`, `agent_id`, and `user_id` shared across operations; (2) hybrid vector search using a custom OpenAI vectorizer for richer context retrieval; and (3) multiple agents with isolated memory via different `agent_id` values. |
## Prerequisites
@@ -61,8 +61,7 @@ The provider supports both fulltext only and hybrid vector search:
- Set `vectorizer_choice` to `"openai"` or `"hf"` to enable embeddings and hybrid search.
- When using a vectorizer, also set `vector_field_name` (e.g., `"vector"`).
- Partition fields for scoping memory: `application_id`, `agent_id`, `user_id`, `thread_id`.
- Thread scoping: `scope_to_per_operation_thread_id=True` isolates memory per operation thread.
- Partition fields for scoping memory: `application_id`, `agent_id`, `user_id`.
- Index management: `index_name`, `overwrite_redis_index`, `drop_redis_index`.
## What the example does
@@ -104,8 +103,8 @@ You should see the agent responses and, when using embeddings, context retrieved
### Memory scoping
- Global scope: set `application_id`, `agent_id`, `user_id`, or `thread_id` on the provider to filter memory.
- Peroperation thread scope: set `scope_to_per_operation_thread_id=True` to isolate memory to the current thread created by the framework.
- Global scope: set `application_id`, `agent_id`, or `user_id` on the provider to filter memory.
- Agent isolation: use different `agent_id` values to keep memories separated for different agent personas.
### Hybrid vector search (optional)
@@ -118,7 +117,7 @@ You should see the agent responses and, when using embeddings, context retrieved
## Troubleshooting
- Ensure at least one of `application_id`, `agent_id`, `user_id`, or `thread_id` is set; the provider requires a scope.
- Ensure at least one of `application_id`, `agent_id`, or `user_id` is set; the provider requires a scope.
- Verify `FOUNDRY_PROJECT_ENDPOINT` and `FOUNDRY_MODEL` are set for the chat client.
- If using embeddings, verify `OPENAI_API_KEY` is set and reachable.
- Make sure Redis exposes RediSearch (Redis Stack image or managed service with search enabled).
@@ -30,9 +30,10 @@ from agent_framework.foundry import FoundryChatClient
from agent_framework.redis import RedisHistoryProvider
from azure.identity import AzureCliCredential
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
from dotenv import load_dotenv
from redis.credentials import CredentialProvider
# Copyright (c) Microsoft. All rights reserved.
load_dotenv()
class AzureCredentialProvider(CredentialProvider):
@@ -100,7 +100,7 @@ def search_flights(origin_airport_code: str, destination_airport_code: str, deta
def create_chat_client() -> FoundryChatClient:
"""Create an Azure OpenAI Responses client using a Foundry project endpoint."""
"""Create a FoundryChatClient using a Foundry project endpoint."""
return FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
@@ -1,17 +1,18 @@
# Copyright (c) Microsoft. All rights reserved.
"""Redis Context Provider: Thread scoping examples
"""Redis Context Provider: Memory scoping examples
This sample demonstrates how conversational memory can be scoped when using the
Redis context provider. It covers three scenarios:
1) Global thread scope
- Provide a fixed thread_id to share memories across operations/threads.
1) Global memory scope
- Use application_id, agent_id, and user_id to share memories across
all operations/sessions.
2) Per-operation thread scope
- Enable scope_to_per_operation_thread_id to bind the provider to a single
thread for the lifetime of that provider instance. Use the same thread
object for reads/writes with that provider.
2) Hybrid vector search
- Use a custom OpenAI vectorizer with the provider for hybrid vector search.
Demonstrates combining full-text and semantic search for richer context
retrieval.
3) Multiple agents with isolated memory
- Use different agent_id values to keep memories separated for different
@@ -23,7 +24,7 @@ Requirements:
- Optionally an OpenAI API key for the chat client in this demo
Run:
python redis_threads.py
python redis_sessions.py
"""
import asyncio
@@ -33,10 +34,12 @@ from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from agent_framework.redis import RedisContextProvider
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from redisvl.extensions.cache.embeddings import EmbeddingsCache
from redisvl.utils.vectorize import OpenAITextVectorizer
# Copyright (c) Microsoft. All rights reserved.
# Load environment variables from .env file
load_dotenv()
# Default Redis URL for local Redis Stack.
@@ -47,7 +50,7 @@ REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379")
# Please set OPENAI_API_KEY to use the OpenAI vectorizer.
# For chat responses, also set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL.
def create_chat_client() -> FoundryChatClient:
"""Create an Azure OpenAI Responses client using a Foundry project endpoint."""
"""Create a FoundryChatClient using a Foundry project endpoint."""
return FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
@@ -55,9 +58,9 @@ def create_chat_client() -> FoundryChatClient:
)
async def example_global_thread_scope() -> None:
"""Example 1: Global thread_id scope (memories shared across all operations)."""
print("1. Global Thread Scope Example:")
async def example_global_memory_scope() -> None:
"""Example 1: Global memory scope (memories shared across all operations)."""
print("1. Global Memory Scope Example:")
print("-" * 40)
client = create_chat_client()
@@ -99,13 +102,13 @@ async def example_global_thread_scope() -> None:
await provider.redis_index.delete()
async def example_per_operation_thread_scope() -> None:
"""Example 2: Per-operation thread scope (memories isolated per session).
async def example_hybrid_vector_search() -> None:
"""Example 2: Hybrid vector search with custom vectorizer.
Note: When scope_to_per_operation_thread_id=True, the provider is bound to a single session
throughout its lifetime. Use the same session object for all operations with that provider.
Demonstrates using a custom OpenAI vectorizer for hybrid vector search,
combining full-text and semantic search for richer context retrieval.
"""
print("2. Per-Operation Thread Scope Example:")
print("2. Hybrid Vector Search Example:")
print("-" * 40)
client = create_chat_client()
@@ -131,36 +134,33 @@ async def example_per_operation_thread_scope() -> None:
agent = Agent(
client=client,
name="ScopedMemoryAssistant",
instructions="You are an assistant with thread-scoped memory.",
name="HybridSearchAssistant",
instructions="You are an assistant with hybrid vector search for richer context retrieval.",
context_providers=[provider],
)
# Create a specific session for this scoped provider
dedicated_session = agent.create_session()
# Store some information in the dedicated session
# Store some information
query = "Remember that for this conversation, I'm working on a Python project about data analysis."
print(f"User (dedicated session): {query}")
result = await agent.run(query, session=dedicated_session)
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
# Test memory retrieval in the same dedicated session
# Test memory retrieval via hybrid search
query = "What project am I working on?"
print(f"User (same dedicated session): {query}")
result = await agent.run(query, session=dedicated_session)
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
# Store more information in the same session
# Store more information
query = "Also remember that I prefer using pandas and matplotlib for this project."
print(f"User (same dedicated session): {query}")
result = await agent.run(query, session=dedicated_session)
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
# Test comprehensive memory retrieval
query = "What do you know about my current project and preferences?"
print(f"User (same dedicated session): {query}")
result = await agent.run(query, session=dedicated_session)
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
# Clean up the Redis index
@@ -168,8 +168,8 @@ async def example_per_operation_thread_scope() -> None:
async def example_multiple_agents() -> None:
"""Example 3: Multiple agents with different thread configurations (isolated via agent_id) but within 1 index."""
print("3. Multiple Agents with Different Thread Configurations:")
"""Example 3: Multiple agents with different memory configurations (isolated via agent_id) but within 1 index."""
print("3. Multiple Agents with Different Memory Configurations:")
print("-" * 40)
client = create_chat_client()
@@ -247,9 +247,9 @@ async def example_multiple_agents() -> None:
async def main() -> None:
print("=== Redis Thread Scoping Examples ===\n")
await example_global_thread_scope()
await example_per_operation_thread_scope()
print("=== Redis Memory Scoping Examples ===\n")
await example_global_memory_scope()
await example_hybrid_vector_search()
await example_multiple_agents()
@@ -5,7 +5,7 @@ import os
from contextlib import suppress
from typing import Any
from agent_framework import Agent, AgentSession, BaseContextProvider, SessionContext, SupportsChatGetResponse
from agent_framework import Agent, AgentSession, ContextProvider, SessionContext, SupportsChatGetResponse
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
@@ -20,7 +20,7 @@ class UserInfo(BaseModel):
age: int | None = None
class UserInfoMemory(BaseContextProvider):
class UserInfoMemory(ContextProvider):
DEFAULT_SOURCE_ID = "user_info_memory"
def __init__(self, source_id: str = DEFAULT_SOURCE_ID, *, client: SupportsChatGetResponse, **kwargs: Any):
@@ -50,9 +50,11 @@ class UserInfoMemory(BaseContextProvider):
# Use the chat client to extract structured information
result = await self._chat_client.get_response(
messages=request_messages, # type: ignore
instructions="Extract the user's name and age from the message if present. "
"If not present return nulls.",
options={"response_format": UserInfo},
options={
"instructions": "Extract the user's name and age from the message if present. "
"If not present return nulls.",
"response_format": UserInfo,
},
)
# Update user info with extracted data
@@ -0,0 +1,28 @@
# Conversation & Session Management Samples
These samples demonstrate different approaches to managing conversation history and session state in Agent Framework.
## Samples
| File | Description |
|------|-------------|
| [`suspend_resume_session.py`](suspend_resume_session.py) | Suspend and resume conversation sessions, comparing service-managed sessions (Azure AI Foundry) with in-memory sessions (OpenAI). |
| [`custom_history_provider.py`](custom_history_provider.py) | Implement a custom history provider by extending `BaseHistoryProvider`, enabling conversation persistence in your preferred storage backend. |
| [`redis_history_provider.py`](redis_history_provider.py) | Use Redis as a history provider for persistent conversation history storage across sessions. |
## Prerequisites
**For `suspend_resume_session.py`:**
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint (service-managed session)
- `FOUNDRY_MODEL`: The Foundry model deployment name
- `OPENAI_API_KEY`: Your OpenAI API key (in-memory session)
- Azure CLI authentication (`az login`)
**For `custom_history_provider.py`:**
- `OPENAI_API_KEY`: Your OpenAI API key
**For `redis_history_provider.py`:**
- `OPENAI_API_KEY`: Your OpenAI API key
- A running Redis server — default URL is `redis://localhost:6379`
- Override via the `REDIS_URL` environment variable for remote or authenticated instances
- Quickstart with Docker: `docker run -d --name redis-stack -p 6379:6379 redis/redis-stack-server:latest`
@@ -4,7 +4,7 @@ import asyncio
from collections.abc import Sequence
from typing import Any
from agent_framework import Agent, AgentSession, BaseHistoryProvider, Message
from agent_framework import Agent, AgentSession, HistoryProvider, Message
from agent_framework.openai import OpenAIChatClient
from dotenv import load_dotenv
@@ -20,7 +20,7 @@ preferred storage solution (database, file system, etc.).
"""
class CustomHistoryProvider(BaseHistoryProvider):
class CustomHistoryProvider(HistoryProvider):
"""Implementation of custom history provider.
In real applications, this can be an implementation of relational database or vector store."""
@@ -4,6 +4,7 @@ import asyncio
from agent_framework import Agent, AgentSession
from agent_framework.foundry import FoundryChatClient
from agent_framework.openai import OpenAIChatCompletionClient
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
@@ -62,7 +63,7 @@ async def suspend_resume_in_memory_session() -> None:
# OpenAI Chat Client is used as an example here,
# other chat clients can be used as well.
agent = Agent(
client=FoundryChatClient(),
client=OpenAIChatCompletionClient(),
name="MemoryBot",
instructions="You are a helpful assistant that remembers our conversation.",
)
@@ -68,7 +68,7 @@ Illustrates a basic agent using Azure OpenAI with structured responses.
**Key concepts**: Azure OpenAI integration, credential management, structured outputs
### 5. **OpenAI Responses Agent** ([`openai_responses_agent.py`](./openai_responses_agent.py))
### 5. **OpenAI Responses Agent** ([`openai_agent.py`](./openai_agent.py))
Demonstrates the simplest possible agent using OpenAI directly.
@@ -159,7 +159,7 @@ agent_factory = AgentFactory(
"MyProvider": {
"package": "my_custom_module",
"name": "MyCustomChatClient",
"model_id_field": "model_id",
"model_field": "model",
}
}
)
@@ -176,7 +176,7 @@ agent = agent_factory.create_agent_from_yaml_path(Path("custom_provider.yaml"))
This allows you to extend the declarative framework with custom chat client implementations. The mapping requires:
- **package**: The Python package/module to import from
- **name**: The class name of your SupportsChatGetResponse implementation
- **model_id_field**: The constructor parameter name that accepts the value of the `model.id` field from the YAML
- **model_field**: The constructor parameter name that accepts the value of the `model.id` field from the YAML
You can reference your custom provider using either `Provider.ApiType` format or just `Provider` in your YAML configuration, as long as it matches the registered mapping.
@@ -18,7 +18,7 @@ Prerequisites:
- `pip install agent-framework-foundry agent-framework-declarative --pre`
- Set the following environment variables in a .env file or your environment:
- FOUNDRY_PROJECT_ENDPOINT
- AZURE_OPENAI_MODEL
- FOUNDRY_MODEL
"""
@@ -31,7 +31,7 @@ instructions: Specialized diagnostic and issue detection agent for systems with
description: A agent that performs diagnostics on systems and can escalate issues when critical errors are detected.
model:
id: =Env.AZURE_OPENAI_MODEL
id: =Env.FOUNDRY_MODEL
"""
# create the agent from the yaml
async with (
@@ -9,6 +9,20 @@ from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
"""
This sample demonstrates creating an agent from a declarative YAML file specification.
It uses a MCP server to connect to the Microsoft Learn content and a FoundryChatClient.
The yaml also has some chat options set, such as temperature and topP.
These options do not work with newer OpenAI models, so ensure to use a compatible model such as gpt-4o-mini.
Environment variables:
- FOUNDRY_PROJECT_ENDPOINT: The endpoint URL for the Foundry project.
- FOUNDRY_MODEL: The model ID to use for the agent, make sure it is compatible with the chat options specified in
the yaml, or remove the options.
"""
async def main():
"""Create an agent from a declarative yaml specification and run it."""
@@ -14,11 +14,8 @@ async def main():
# get the path
current_path = Path(__file__).parent
yaml_path = current_path.parent.parent.parent.parent / "declarative-agents" / "agent-samples" / "openai" / "OpenAIResponses.yaml"
# load the yaml from the path
with yaml_path.open("r") as f:
yaml_str = f.read()
# create the agent from the yaml
agent = AgentFactory(safe_mode=False).create_agent_from_yaml(yaml_str)
agent = AgentFactory(safe_mode=False).create_agent_from_yaml_path(yaml_path)
# use the agent
response = await agent.run("Why is the sky blue, answer in Dutch?")
# Use response.value with try/except for safe parsing
@@ -0,0 +1,15 @@
# Shared configuration for samples/02-agents/devui
# Used by in_memory_mode.py, main.py, and as a fallback for discovered samples.
# Run `az login` before starting Azure-backed samples.
# Microsoft Foundry samples
FOUNDRY_PROJECT_ENDPOINT=https://your-project.services.ai.azure.com
FOUNDRY_MODEL=gpt-4o
# Azure OpenAI workflow sample
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_RESPONSES_MODEL=gpt-4o
# Optional fallback env name also supported by workflow_with_agents/workflow.py:
AZURE_OPENAI_MODEL=gpt-4o
# Optional if you need to override the default API version:
AZURE_OPENAI_API_VERSION=2024-10-21
+78 -30
View File
@@ -16,76 +16,124 @@ DevUI is a sample application that provides:
## Quick Start
### Option 1: In-Memory Mode (Simplest)
### Option 1: In-Memory Mode (Programmatic Registration)
Run a single sample directly. This demonstrates how to wrap agents and workflows programmatically without needing a directory structure:
Run a single sample directly. This demonstrates how to register agents and workflows in code without using DevUI's directory discovery.
This sample uses Azure AI Foundry. Before running it:
1. Copy `.env.example` in this folder to `.env`, or export the same values in your shell
2. Set `FOUNDRY_PROJECT_ENDPOINT` and `FOUNDRY_MODEL`
3. Run `az login`
Then start the sample:
```bash
cd python/samples/02-agents/devui
python in_memory_mode.py
```
This opens your browser at http://localhost:8090 with pre-configured agents and a basic workflow.
This opens your browser at http://localhost:8090 with two Foundry-backed agents and a simple text transformation workflow.
### Option 2: Directory Discovery
### Option 2: Directory Discovery with Shared Root `.env`
Launch DevUI to discover all samples in this folder:
Run the folder-level launcher to load `samples/02-agents/devui/.env` and then start DevUI with directory discovery for this folder:
```bash
cd python/samples/02-agents/devui
devui
python main.py
```
This starts the server at http://localhost:8080 with all agents and workflows available.
This starts the server at http://localhost:8080 with all discoverable agents and workflows available. The root `.env` acts as shared fallback configuration for discovered samples.
### Option 3: Directory Discovery with the `devui` CLI
If you prefer the CLI directly, you can still launch DevUI from this folder:
```bash
cd python/samples/02-agents/devui
devui .
```
DevUI discovery checks for a sample-specific `.env` first and then falls back to `.env` in `samples/02-agents/devui/`.
## Sample Structure
Each agent/workflow follows a strict structure required by DevUI's discovery system:
DevUI discovers samples from Python packages that export either `agent` or `workflow`.
Typical agent layout:
```
agent_name/
├── __init__.py # Must export: agent = Agent(...)
├── __init__.py # Must export: agent = ...
├── agent.py # Agent implementation
└── .env.example # Example environment variables
└── .env.example # Optional example environment variables
```
Typical workflow layout:
```
workflow_name/
├── __init__.py # Must export: workflow = ...
├── workflow.py # Workflow implementation
├── workflow.yaml # Optional declarative definition
└── .env.example # Optional example environment variables
```
## Available Samples
### Agents
| Sample | Description | Features | Required Environment Variables |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| [**weather_agent_azure/**](weather_agent_azure/) | Weather agent using Azure OpenAI with API key authentication | Azure OpenAI integration, function calling, mock weather tools | `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_DEPLOYMENT_NAME`, `AZURE_OPENAI_ENDPOINT` |
| [**foundry_agent/**](foundry_agent/) | Weather agent using Azure AI Agent (Foundry) with Azure CLI authentication (run `az login` first) | Azure AI Agent integration, Azure CLI authentication, mock weather tools | `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL` |
| Sample | What it demonstrates | Required keys / auth |
| ------ | -------------------- | -------------------- |
| [**agent_weather/**](agent_weather/) | A richer Foundry-backed weather agent that shows chat middleware, function middleware, tool calling, and an approval-required tool alongside auto-approved tools. | `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL`, plus Azure CLI auth via `az login` |
| [**agent_foundry/**](agent_foundry/) | A minimal Foundry-backed weather agent with current weather and forecast tools. Use this when you want the smallest possible directory-discovered agent sample. | `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL`, plus Azure CLI auth via `az login` |
### Workflows
| Sample | Description | Features | Required Environment Variables |
| -------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| [**declarative/**](declarative/) | Declarative YAML workflow with conditional branching | YAML-based workflow definition, conditional logic, no Python code required | None - uses mock data |
| [**workflow_agents/**](workflow_agents/) | Content review workflow with agents as executors | Agents as workflow nodes, conditional routing based on structured outputs, quality-based paths (Writer -> Reviewer -> Editor/Publisher) | `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_DEPLOYMENT_NAME`, `AZURE_OPENAI_ENDPOINT` |
| [**spam_workflow/**](spam_workflow/) | 5-step email spam detection workflow with branching logic | Sequential execution, conditional branching (spam vs. legitimate), multiple executors, mock spam detection | None - uses mock data |
| [**fanout_workflow/**](fanout_workflow/) | Advanced data processing workflow with parallel execution | Fan-out/fan-in patterns, complex state management, multi-stage processing (validation -> transformation -> quality assurance) | None - uses mock data |
| Sample | What it demonstrates | Required keys / auth |
| ------ | -------------------- | -------------------- |
| [**workflow_declarative/**](workflow_declarative/) | A YAML-defined workflow loaded through `WorkflowFactory`, with nested age-based branching and no model client code. | None |
| [**workflow_with_agents/**](workflow_with_agents/) | A content review workflow that uses agents as executors and routes based on structured review output (`Writer -> Reviewer -> Editor/Publisher -> Summarizer`). | `AZURE_OPENAI_ENDPOINT`, plus `AZURE_OPENAI_RESPONSES_MODEL` or `AZURE_OPENAI_MODEL`; Azure CLI auth via `az login`; `AZURE_OPENAI_API_VERSION` is optional |
| [**workflow_spam/**](workflow_spam/) | A multi-step spam detection workflow with human-in-the-loop approval, branching for spam vs. legitimate messages, and a final reporting step. | None |
| [**workflow_fanout/**](workflow_fanout/) | A larger fan-out/fan-in data processing workflow with parallel validation, multiple transformations, QA, aggregation, and demo failure toggles. | None |
### Standalone Examples
| Sample | Description | Features |
| ------------------------------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| [**in_memory_mode.py**](in_memory_mode.py) | Demonstrates programmatic entity registration without directory structure | In-memory agent and workflow registration, multiple entities served from a single file, includes basic workflow, simplest way to get started |
| Sample | What it demonstrates | Required keys / auth |
| ------ | -------------------- | -------------------- |
| [**in_memory_mode.py**](in_memory_mode.py) | Registers multiple entities directly in Python: two Foundry-backed agents plus a simple workflow, all served from one file without directory discovery. | `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL`, plus Azure CLI auth via `az login` |
## Environment Variables
Each sample that requires API keys includes a `.env.example` file. To use:
For samples that require external services:
1. Copy `.env.example` to `.env` in the same directory
2. Fill in your actual API keys
3. DevUI automatically loads `.env` files from entity directories
1. Copy `.env.example` to `.env`
2. Fill in the required values
3. Run `az login` for samples that use Azure CLI authentication
Directory discovery checks `.env` files in this order:
1. The entity directory itself, for example `agent_weather/.env`
2. The root DevUI samples folder, `samples/02-agents/devui/.env`
That means the root `.env.example` can hold shared defaults for multiple samples, while a sample-specific `.env` can override those values when needed.
`in_memory_mode.py` and `main.py` both load `.env` from `samples/02-agents/devui/`, so the root `.env.example` in this folder is the right starting point for both commands.
Alternatively, set environment variables globally:
```bash
export OPENAI_API_KEY="your-key-here"
export OPENAI_CHAT_MODEL="gpt-4o"
# Foundry-backed samples
export FOUNDRY_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com"
export FOUNDRY_MODEL="gpt-4o"
# Azure OpenAI workflow_with_agents sample
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com"
export AZURE_OPENAI_RESPONSES_MODEL="gpt-4o"
export AZURE_OPENAI_MODEL="gpt-4o"
az login
```
## Using DevUI with Your Own Agents
@@ -145,7 +193,7 @@ curl http://localhost:8080/v1/entities
## Troubleshooting
**Missing API keys**: Check your `.env` files or environment variables.
**Missing credentials or settings**: Check your `.env` files, confirm the required variables for the sample you are running, and make sure `az login` has completed for Azure-authenticated samples.
**Import errors**: Make sure you've installed the devui package:
@@ -0,0 +1,5 @@
# Azure AI Foundry Configuration
# Make sure to run 'az login' before starting devui
FOUNDRY_PROJECT_ENDPOINT=https://your-project.services.ai.azure.com
FOUNDRY_MODEL=gpt-4o
@@ -53,7 +53,7 @@ agent = Agent(
name="FoundryWeatherAgent",
client=FoundryChatClient(
project_endpoint=os.environ.get("FOUNDRY_PROJECT_ENDPOINT"),
model_model=os.environ.get("FOUNDRY_MODEL"),
model=os.environ.get("FOUNDRY_MODEL"),
credential=AzureCliCredential(),
),
instructions="""
@@ -0,0 +1,5 @@
# Azure AI Foundry Configuration
# Make sure to run 'az login' before starting devui
FOUNDRY_PROJECT_ENDPOINT=https://your-project.services.ai.azure.com
FOUNDRY_MODEL=gpt-4o
@@ -22,6 +22,7 @@ from agent_framework import (
)
from agent_framework.foundry import FoundryChatClient
from agent_framework_devui import register_cleanup
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file
@@ -145,7 +146,7 @@ def send_email(
# Agent instance following Agent Framework conventions
agent = Agent(
name="AzureWeatherAgent",
name="WeatherAgent",
description="A helpful agent that provides weather information and forecasts",
instructions="""
You are a weather assistant. You can provide current weather information
@@ -153,7 +154,9 @@ agent = Agent(
weather information when asked.
""",
client=FoundryChatClient(
api_key=os.environ.get("AZURE_OPENAI_API_KEY", ""),
project_endpoint=os.environ.get("FOUNDRY_PROJECT_ENDPOINT"),
model=os.environ.get("FOUNDRY_MODEL"),
credential=AzureCliCredential(),
),
tools=[get_weather, get_forecast, send_email],
middleware=[security_filter_middleware, atlantis_location_filter_middleware],
@@ -164,7 +167,7 @@ register_cleanup(agent, cleanup_resources)
def main():
"""Launch the Azure weather agent in DevUI."""
"""Launch the Weather Agent in DevUI."""
import logging
from agent_framework.devui import serve
@@ -173,9 +176,9 @@ def main():
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)
logger.info("Starting Azure Weather Agent")
logger.info("Starting Weather Agent")
logger.info("Available at: http://localhost:8090")
logger.info("Entity ID: agent_AzureWeatherAgent")
logger.info("Entity ID: agent_WeatherAgent")
# Launch server with the agent
serve(entities=[agent], port=8090, auto_open=True)
@@ -1,15 +0,0 @@
# Azure OpenAI Responses API Configuration
# The Responses API supports PDF uploads, images, and other multimodal content.
# Requires api-version 2025-03-01-preview or later.
# Option 1: Use API key authentication
AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
# Option 2: Use Azure CLI authentication (run 'az login' first)
# No API key needed - just leave AZURE_OPENAI_API_KEY unset
# Required: Azure OpenAI endpoint with Responses API support
AZURE_OPENAI_ENDPOINT=https://your-resource.cognitiveservices.azure.com/
# Required: Deployment name (must support Responses API)
FOUNDRY_MODEL=gpt-4.1-mini
@@ -1,6 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
"""Azure Responses Agent sample for DevUI."""
from .agent import agent
__all__ = ["agent"]
@@ -1,128 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
"""Sample agent using Azure OpenAI Responses API for Agent Framework DevUI.
This agent uses the Responses API which supports:
- PDF file uploads
- Image uploads
- Audio inputs
- And other multimodal content
The Chat Completions API (FoundryChatClient) does NOT support PDF uploads.
Use this agent when you need to process documents or other file types.
Required environment variables:
- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint
- FOUNDRY_MODEL: Deployment name for Responses API
(falls back to FOUNDRY_MODEL if not set)
- AZURE_OPENAI_API_KEY: Your API key (or use Azure CLI auth)
"""
import logging
import os
from typing import Annotated
from agent_framework import Agent, tool
from agent_framework.foundry import FoundryChatClient
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
logger = logging.getLogger(__name__)
# Get deployment name - try responses-specific env var first, fall back to chat deployment
_deployment_name = os.environ.get(
"FOUNDRY_MODEL",
os.environ.get("FOUNDRY_MODEL", ""),
)
# Get endpoint - try responses-specific env var first, fall back to default
_endpoint = os.environ.get(
"AZURE_OPENAI_RESPONSES_ENDPOINT",
os.environ.get("AZURE_OPENAI_ENDPOINT", ""),
)
def analyze_content(
query: Annotated[str, "What to analyze or extract from the uploaded content"],
) -> str:
"""Analyze uploaded content based on the user's query.
This is a placeholder - the actual analysis is done by the model
when processing the uploaded files.
"""
return f"Analyzing content for: {query}"
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def summarize_document(
length: Annotated[str, "Desired summary length: 'brief', 'medium', or 'detailed'"] = "medium",
) -> str:
"""Generate a summary of the uploaded document."""
return f"Generating {length} summary of the document..."
@tool(approval_mode="never_require")
def extract_key_points(
max_points: Annotated[int, "Maximum number of key points to extract"] = 5,
) -> str:
"""Extract key points from the uploaded document."""
return f"Extracting up to {max_points} key points..."
# Agent using Azure OpenAI Responses API (supports PDF uploads!)
agent = Agent(
name="AzureResponsesAgent",
description="An agent that can analyze PDFs, images, and other documents using Azure OpenAI Responses API",
instructions="""
You are a helpful document analysis assistant. You can:
1. Analyze uploaded PDF documents and extract information
2. Summarize document contents
3. Answer questions about uploaded files
4. Extract key points and insights
When a user uploads a file, carefully analyze its contents and provide
helpful, accurate information based on what you find.
For PDFs, you can read and understand the text, tables, and structure.
For images, you can describe what you see and extract any text.
""",
client=FoundryChatClient(
model=_deployment_name,
endpoint=_endpoint,
api_version="2025-03-01-preview", # Required for Responses API
),
tools=[summarize_document, extract_key_points],
)
def main():
"""Launch the Azure Responses agent in DevUI."""
from agent_framework_devui import serve
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger.info("=" * 60)
logger.info("Starting Azure Responses Agent")
logger.info("=" * 60)
logger.info("")
logger.info("This agent uses the Azure OpenAI Responses API which supports:")
logger.info(" - PDF file uploads")
logger.info(" - Image uploads")
logger.info(" - Audio inputs")
logger.info("")
logger.info("Try uploading a PDF and asking questions about it!")
logger.info("")
logger.info("Required environment variables:")
logger.info(" - AZURE_OPENAI_ENDPOINT")
logger.info(" - FOUNDRY_MODEL")
logger.info(" - AZURE_OPENAI_API_KEY (or use Azure CLI auth)")
logger.info("")
serve(entities=[agent], port=8090, auto_open=True)
if __name__ == "__main__":
main()
@@ -1,6 +0,0 @@
# Azure AI Foundry Configuration
# Get your credentials from Azure AI Foundry portal
# Make sure to run 'az login' before starting devui
FOUNDRY_PROJECT_ENDPOINT=https://your-project.api.azureml.ms
FOUNDRY_MODEL=gpt-4o
@@ -20,6 +20,7 @@ from agent_framework import (
)
from agent_framework.devui import serve
from agent_framework.foundry import FoundryChatClient
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
from typing_extensions import Never
@@ -80,14 +81,13 @@ def main():
# Create Azure OpenAI chat client
client = FoundryChatClient(
api_key=os.environ.get("AZURE_OPENAI_API_KEY"),
model=os.environ["FOUNDRY_MODEL"],
endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
api_version=os.environ.get("AZURE_OPENAI_API_VERSION", "2024-10-21"),
project_endpoint=os.environ.get("FOUNDRY_PROJECT_ENDPOINT"),
credential=AzureCliCredential(),
)
# Create agents
weather_agent = Agent(
weather_assistant = Agent(
name="weather-assistant",
description="Provides weather information and time",
instructions=(
@@ -120,7 +120,7 @@ def main():
)
# Collect entities for serving
entities = [weather_agent, simple_agent, basic_workflow]
entities = [weather_assistant, simple_agent, basic_workflow]
logger.info("Starting DevUI on http://localhost:8090")
logger.info("Entities available:")
+32
View File
@@ -0,0 +1,32 @@
# Copyright (c) Microsoft. All rights reserved.
"""Launch DevUI with folder discovery for the samples in this directory.
This sample demonstrates:
- Loading a shared root `.env` file for the DevUI samples folder
- Starting DevUI in directory discovery mode for this folder
- Using root-level settings as fallbacks for discovered samples
"""
from pathlib import Path
from agent_framework.devui import serve
from dotenv import load_dotenv
def main() -> None:
"""Load the root .env file and launch DevUI with folder discovery."""
samples_dir = Path(__file__).resolve().parent
# 1. Load shared defaults for the samples in this folder.
load_dotenv(samples_dir / ".env")
# 2. Start DevUI and discover entities from this directory.
serve(entities_dir=str(samples_dir), auto_open=True)
if __name__ == "__main__":
main()
# Sample output:
# Starting Agent Framework DevUI on 127.0.0.1:8080
@@ -1,6 +0,0 @@
# Azure OpenAI API Configuration
# Get your credentials from Azure Portal
AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
@@ -1,7 +0,0 @@
# Azure OpenAI API Configuration
# Get your credentials from Azure Portal
AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_API_VERSION=2024-10-21
@@ -0,0 +1,9 @@
# Azure OpenAI configuration for the Responses-based workflow sample
# This sample uses Azure CLI auth, so run `az login` before starting DevUI.
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_RESPONSES_MODEL=gpt-4o
# Optional fallback env name also supported by the client:
# AZURE_OPENAI_MODEL=gpt-4o
# Optional if you need to override the default API version:
AZURE_OPENAI_API_VERSION=2024-10-21
@@ -18,7 +18,8 @@ import os
from typing import Any
from agent_framework import Agent, AgentExecutorResponse, WorkflowBuilder
from agent_framework.foundry import FoundryChatClient
from agent_framework.openai import OpenAIChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from pydantic import BaseModel
@@ -62,8 +63,13 @@ def is_approved(message: Any) -> bool:
return True
# Create Azure OpenAI chat client
client = FoundryChatClient(api_key=os.environ.get("AZURE_OPENAI_API_KEY", ""))
# Create Azure OpenAI Responses chat client
client = OpenAIChatClient(
model=os.environ.get("AZURE_OPENAI_RESPONSES_MODEL") or os.environ.get("AZURE_OPENAI_MODEL"),
azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
api_version=os.environ.get("AZURE_OPENAI_API_VERSION"),
credential=AzureCliCredential(),
)
# Create Writer agent - generates content
writer = Agent(
@@ -12,7 +12,7 @@ import asyncio
import pathlib
from agent_framework import Content
from agent_framework_azure_ai import AzureAIInferenceEmbeddingClient
from agent_framework.azure import AzureAIInferenceEmbeddingClient
from dotenv import load_dotenv
load_dotenv()
@@ -24,12 +24,16 @@ Azure AI Inference embedding client with the Cohere-embed-v3-english model.
Images are passed as ``Content`` objects created with ``Content.from_data()``.
Prerequisites:
Set the following environment variables or add them to a .env file:
- AZURE_AI_INFERENCE_ENDPOINT: Your Azure AI model inference endpoint URL
Deploy an embedding model in Azure AI Inference that supports image inputs, such as Cohere-embed-v3-english.
The details page for that model, has a target URI and a Key, which should be set in environment variables or a .env
file as follows, the target URI should append the `/models` path:
- AZURE_AI_INFERENCE_ENDPOINT: Your Azure AI model inference endpoint URL, for instance:
https://<apim-instance>.azure-api.net/<foundry-instance>/models
- AZURE_AI_INFERENCE_API_KEY: Your API key
- AZURE_AI_INFERENCE_EMBEDDING_MODEL_ID: The text embedding model name
- AZURE_AI_INFERENCE_EMBEDDING_MODEL: The text embedding model name
(e.g. "text-embedding-3-small")
- AZURE_AI_INFERENCE_IMAGE_EMBEDDING_MODEL_ID: The image embedding model name
- AZURE_AI_INFERENCE_IMAGE_EMBEDDING_MODEL: The image embedding model name
(e.g. "Cohere-embed-v3-english")
"""
@@ -45,7 +49,7 @@ async def main() -> None:
result = await client.get_embeddings([image_content])
print(f"Image embedding dimensions: {result[0].dimensions}")
print(f"First 5 values: {result[0].vector[:5]}")
print(f"Model: {result[0].model_id}")
print(f"Model: {result[0].model}")
print(f"Usage: {result.usage}")
print()
@@ -73,15 +77,17 @@ if __name__ == "__main__":
"""
Sample output (using Cohere-embed-v3-english):
Sample output (using deployment: Cohere-embed-v3-english, which is Cohere's "embed-english-v3.0-image" model):
Image embedding dimensions: 1024
First 5 values: [0.023, -0.045, 0.067, -0.089, 0.011]
Model: Cohere-embed-v3-english
Usage: {'prompt_tokens': 1, 'total_tokens': 1}
First 5 values: [0.029159546, -0.007926941, -0.0032978058, -0.0030403137, -0.012786865]
Model: embed-english-v3.0-image
Usage: {'input_token_count': 1000, 'output_token_count': 0}
Image+text (separate) results:
Text embedding dimensions: 1536
First 5 values: [-0.019439403, 0.015791258, 0.012358093, 0.0028533707, -0.01649483]
Image embedding dimensions: 1024
First 5 values: [0.029159546, -0.007926941, -0.0032978058, -0.0030403137, -0.012786865]
Document embedding dimensions: 1024
First 5 values: [0.029159546, -0.007926941, -0.0032978058, -0.0030403137, -0.012786865]
"""
@@ -14,7 +14,7 @@ from dotenv import load_dotenv
Prerequisites:
Set the following environment variables or add them to a local ``.env`` file:
- ``AZURE_OPENAI_ENDPOINT``: Your Azure OpenAI endpoint URL
- ``AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME``: The embedding deployment name
- ``AZURE_OPENAI_EMBEDDING_MODEL``: The embedding deployment name
- ``AZURE_OPENAI_API_VERSION``: Optional API version override
Sign in with ``az login`` before running the sample.
@@ -27,7 +27,7 @@ async def main() -> None:
"""Generate embeddings with Azure OpenAI."""
async with AzureCliCredential() as credential:
client = OpenAIEmbeddingClient(
model=os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"),
model=os.getenv("AZURE_OPENAI_EMBEDDING_MODEL"),
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
credential=credential,
@@ -41,7 +41,7 @@ def response_matches_expected(response: str, expected_output: str) -> float:
async def main() -> None:
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "gpt-4o"),
model=os.environ.get("FOUNDRY_MODEL", "gpt-4o"),
credential=AzureCliCredential(),
)
@@ -13,8 +13,8 @@ This folder contains focused middleware samples for `Agent`, chat clients, tools
| [`exception_handling_with_middleware.py`](./exception_handling_with_middleware.py) | Shows how middleware can handle failures and recover cleanly. |
| [`function_based_middleware.py`](./function_based_middleware.py) | Shows function-based agent and function middleware. |
| [`middleware_termination.py`](./middleware_termination.py) | Demonstrates stopping a middleware pipeline early. |
| [`override_result_with_middleware.py`](./override_result_with_middleware.py) | Shows how middleware can replace the normal result. |
| [`runtime_context_delegation.py`](./runtime_context_delegation.py) | Demonstrates delegating work with runtime context data. |
| [`override_result_with_middleware.py`](./override_result_with_middleware.py) | Shows how middleware can replace regular and streaming results, then post-process the final response. |
| [`runtime_context_delegation.py`](./runtime_context_delegation.py) | Demonstrates delegating arguments with runtime context data. |
| [`session_behavior_middleware.py`](./session_behavior_middleware.py) | Shows how middleware interacts with session-backed runs. |
| [`shared_state_middleware.py`](./shared_state_middleware.py) | Demonstrates sharing mutable state across middleware invocations. |
| [`usage_tracking_middleware.py`](./usage_tracking_middleware.py) | Demonstrates one chat middleware function that tracks per-call usage in non-streaming and streaming tool-loop runs. |
@@ -81,7 +81,7 @@ async def weather_override_middleware(context: ChatContext, call_next: Callable[
role="assistant",
)
context.result = ResponseStream(_override_stream())
context.result = ResponseStream(_override_stream(), finalizer=ChatResponse.from_updates)
else:
# For non-streaming: just replace with a new message
current_text = context.result.text if isinstance(context.result, ChatResponse) else ""
@@ -99,12 +99,17 @@ async def validate_weather_middleware(context: ChatContext, call_next: Callable[
return
if context.stream and isinstance(context.result, ResponseStream):
result_stream = context.result
def _append_validation_note(response: ChatResponse) -> ChatResponse:
response.messages.append(Message(role="assistant", text=validation_note))
return response
async def _validated_stream() -> AsyncIterable[ChatResponseUpdate]:
async for update in result_stream:
yield update
yield ChatResponseUpdate(
contents=[Content.from_text(text=validation_note)],
role="assistant",
)
context.result.with_finalizer(_append_validation_note)
context.result = ResponseStream(_validated_stream(), finalizer=ChatResponse.from_updates)
elif isinstance(context.result, ChatResponse):
context.result.messages.append(Message(role="assistant", text=validation_note))
@@ -118,11 +123,11 @@ async def agent_cleanup_middleware(context: AgentContext, call_next: Callable[[]
validation_note = "Validation: weather data verified."
state = {"found_prefix": False}
state = {"found_prefix": False, "found_validation": False}
def _sanitize(response: AgentResponse) -> AgentResponse:
found_prefix = state["found_prefix"]
found_validation = False
found_validation = state["found_validation"]
cleaned_messages: list[Message] = []
for message in response.messages:
@@ -141,12 +146,14 @@ async def agent_cleanup_middleware(context: AgentContext, call_next: Callable[[]
found_prefix = True
text = text.replace("Weather Advisory:", "")
text = re.sub(r"\[\d+\]\s*", "", text)
text = re.sub(r"\[\d+\]\s*", "", text).strip()
if not text:
continue
cleaned_messages.append(
Message(
role=message.role,
text=text.strip(),
text=text,
author_name=message.author_name,
message_id=message.message_id,
additional_properties=message.additional_properties,
@@ -166,19 +173,30 @@ async def agent_cleanup_middleware(context: AgentContext, call_next: Callable[[]
if context.stream and isinstance(context.result, ResponseStream):
def _clean_update(update: AgentResponseUpdate) -> AgentResponseUpdate:
cleaned_contents: list[Content] = []
for content in update.contents or []:
if not content.text:
cleaned_contents.append(content)
continue
text = content.text
if "Weather Advisory:" in text:
state["found_prefix"] = True
text = text.replace("Weather Advisory:", "")
if validation_note in text:
state["found_validation"] = True
text = text.replace(validation_note, "").strip()
if not text:
continue
text = re.sub(r"\[\d+\]\s*", "", text)
content.text = text
cleaned_contents.append(content)
update.contents = cleaned_contents
return update
context.result.with_transform_hook(_clean_update)
context.result.with_finalizer(_sanitize)
context.result.with_result_hook(_sanitize)
elif isinstance(context.result, AgentResponse):
context.result = _sanitize(context.result)
@@ -6,6 +6,7 @@ from typing import Annotated
from agent_framework import Agent, FunctionInvocationContext, function_middleware, tool
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from pydantic import Field
@@ -43,6 +44,13 @@ Key Concepts:
- MiddlewareTypes: Intercepts function calls to access/modify kwargs
- Closure: Functions capturing variables from outer scope
- kwargs Propagation: Automatic forwarding of runtime context through delegation chains
Environment Setup:
- Configure Azure credentials (e.g., via Azure CLI)
- Run `az login` to authenticate
- Set FOUNDRY_PROJECT_ENDPOINT to your Azure AI Foundry project endpoint
- Set FOUNDRY_MODEL to the model deployment name (for example: gpt-4o)
"""
@@ -85,7 +93,7 @@ class SessionContextContainer:
runtime_context = SessionContextContainer()
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production.
@tool(approval_mode="never_require")
async def send_email(
to: Annotated[str, Field(description="Recipient email address")],
@@ -149,7 +157,7 @@ async def pattern_1_single_agent_with_closure() -> None:
print("Use case: Single agent with multiple tools sharing runtime context")
print()
client = FoundryChatClient(model="gpt-4o-mini")
client = FoundryChatClient(credential=AzureCliCredential())
# Create agent with both tools and shared context via middleware
communication_agent = Agent(
@@ -177,9 +185,11 @@ async def pattern_1_single_agent_with_closure() -> None:
result1 = await communication_agent.run(
user_query,
# Runtime context passed as kwargs
api_token="sk-test-token-xyz-789",
user_id="user-12345",
session_metadata={"tenant": "acme-corp", "region": "us-west"},
function_invocation_kwargs={
"api_token": "sk-test-token-xyz-789",
"user_id": "user-12345",
"session_metadata": {"tenant": "acme-corp", "region": "us-west"},
},
)
print(f"\nAgent: {result1.text}")
@@ -195,9 +205,11 @@ async def pattern_1_single_agent_with_closure() -> None:
result2 = await communication_agent.run(
user_query2,
# Different runtime context for this request
api_token="sk-prod-token-abc-456",
user_id="user-67890",
session_metadata={"tenant": "store-inc", "region": "eu-central"},
function_invocation_kwargs={
"api_token": "sk-prod-token-abc-456",
"user_id": "user-67890",
"session_metadata": {"tenant": "store-inc", "region": "eu-central"},
},
)
print(f"\nAgent: {result2.text}")
@@ -215,9 +227,11 @@ async def pattern_1_single_agent_with_closure() -> None:
result3 = await communication_agent.run(
user_query3,
api_token="sk-dev-token-def-123",
user_id="user-11111",
session_metadata={"tenant": "dev-team", "region": "us-east"},
function_invocation_kwargs={
"api_token": "sk-dev-token-def-123",
"user_id": "user-11111",
"session_metadata": {"tenant": "dev-team", "region": "us-east"},
},
)
print(f"\nAgent: {result3.text}")
@@ -234,7 +248,9 @@ async def pattern_1_single_agent_with_closure() -> None:
result4 = await communication_agent.run(
user_query4,
# Missing api_token - tools should handle gracefully
user_id="user-22222",
function_invocation_kwargs={
"user_id": "user-22222",
},
)
print(f"\nAgent: {result4.text}")
@@ -295,7 +311,7 @@ async def pattern_2_hierarchical_with_kwargs_propagation() -> None:
print(f"[SMSAgent] Received runtime context: {list(context.kwargs.keys())}")
await call_next()
client = FoundryChatClient(model="gpt-4o-mini")
client = FoundryChatClient(credential=AzureCliCredential())
# Create specialized sub-agents
email_agent = Agent(
@@ -341,9 +357,11 @@ async def pattern_2_hierarchical_with_kwargs_propagation() -> None:
print("Test: Send email with runtime context\n")
await coordinator.run(
"Send an email to john@example.com with subject 'Meeting' and body 'See you at 2pm'",
api_token="secret-token-abc",
user_id="user-999",
tenant_id="tenant-acme",
function_invocation_kwargs={
"api_token": "secret-token-abc",
"user_id": "user-999",
"tenant_id": "tenant-acme",
},
)
print(f"\n[Verification] EmailAgent received kwargs keys: {list(email_agent_kwargs.keys())}")
@@ -400,7 +418,7 @@ async def pattern_3_hierarchical_with_middleware() -> None:
auth_middleware = AuthContextMiddleware()
client = FoundryChatClient(model="gpt-4o-mini")
client = FoundryChatClient(credential=AzureCliCredential())
# Sub-agent with validation middleware
protected_agent = Agent(
@@ -428,16 +446,20 @@ async def pattern_3_hierarchical_with_middleware() -> None:
print("Test 1: Valid token\n")
await coordinator.run(
"Execute operation: backup_database",
api_token="valid-token-xyz-789",
user_id="admin-123",
function_invocation_kwargs={
"api_token": "valid-token-xyz-789",
"user_id": "admin-123",
},
)
# Test with invalid token
print("\nTest 2: Invalid token\n")
await coordinator.run(
"Execute operation: delete_records",
api_token="invalid-token-bad",
user_id="user-456",
function_invocation_kwargs={
"api_token": "invalid-token-bad",
"user_id": "user-456",
},
)
print(f"\n[Validation Summary] Validated tokens: {len(auth_middleware.validated_tokens)}")
@@ -32,8 +32,8 @@ Set the following environment variables before running the examples:
**For Azure OpenAI:**
- `AZURE_OPENAI_ENDPOINT`: Your Azure OpenAI endpoint
- `AZURE_OPENAI_DEPLOYMENT_NAME`: The name of your Azure OpenAI chat model deployment
- `AZURE_OPENAI_DEPLOYMENT_NAME`: The name of your Azure OpenAI responses model deployment
- `AZURE_OPENAI_MODEL`: The name of your Azure OpenAI chat model deployment
- `AZURE_OPENAI_MODEL`: The name of your Azure OpenAI responses model deployment
Optionally for Azure OpenAI:
- `AZURE_OPENAI_API_VERSION`: The API version to use (default is `2024-10-21`)
@@ -23,7 +23,7 @@ async def test_image() -> None:
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option. Requires AZURE_OPENAI_ENDPOINT and FOUNDRY_MODEL
# environment variables to be set.
# Alternatively, you can pass deployment_name explicitly:
# Alternatively, you can pass model explicitly:
# client = FoundryChatClient(credential=AzureCliCredential(), model="your-deployment-name")
client = FoundryChatClient(credential=AzureCliCredential())
image_uri = create_sample_image()
@@ -32,7 +32,7 @@ async def test_image() -> None:
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option. Requires AZURE_OPENAI_ENDPOINT and FOUNDRY_MODEL
# environment variables to be set.
# Alternatively, you can pass deployment_name explicitly:
# Alternatively, you can pass model explicitly:
# client = FoundryChatClient(credential=AzureCliCredential(), model="your-deployment-name")
client = FoundryChatClient(credential=AzureCliCredential())
@@ -6,7 +6,7 @@ import struct
from pathlib import Path
from agent_framework import Content, Message
from agent_framework.foundry import FoundryChatClient
from agent_framework.openai import OpenAIChatClient, OpenAIChatCompletionClient
from dotenv import load_dotenv
# Load environment variables from .env file
@@ -14,6 +14,13 @@ load_dotenv()
ASSETS_DIR = Path(__file__).resolve().parents[2] / "shared" / "sample_assets"
"""
Leverage multimodel capabilities of different models.
Uses the OpenAIChatClient and OpenAIChatCompletionClient to demonstrate multimodal input handling with the gpt-4o and gpt-4o-audio-preview models, respectively. The sample includes demonstrations for image, audio, and PDF inputs, showcasing how to create appropriate Content objects and send them in messages to the chat clients.
"""
def load_sample_pdf() -> bytes:
"""Read the bundled sample PDF for tests."""
@@ -46,7 +53,7 @@ def create_sample_audio() -> str:
async def test_image() -> None:
"""Test image analysis with OpenAI."""
client = FoundryChatClient(model="gpt-4o")
client = OpenAIChatClient(model="gpt-4o")
image_uri = create_sample_image()
message = Message(
@@ -63,7 +70,7 @@ async def test_image() -> None:
async def test_audio() -> None:
"""Test audio analysis with OpenAI."""
client = FoundryChatClient(model="gpt-4o-audio-preview")
client = OpenAIChatCompletionClient(model="gpt-4o-audio-preview-2025-06-03")
audio_uri = create_sample_audio()
message = Message(
@@ -80,7 +87,7 @@ async def test_audio() -> None:
async def test_pdf() -> None:
"""Test PDF document analysis with OpenAI."""
client = FoundryChatClient(model="gpt-4o")
client = OpenAIChatClient(model="gpt-4o")
pdf_bytes = load_sample_pdf()
message = Message(
@@ -8,11 +8,12 @@ from typing import Annotated
from agent_framework import Message, tool
from agent_framework.foundry import FoundryChatClient
from agent_framework.observability import enable_instrumentation
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from opentelemetry._logs import set_logger_provider
from opentelemetry.metrics import set_meter_provider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor, ConsoleLogExporter
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor, ConsoleLogRecordExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricExporter, PeriodicExportingMetricReader
from opentelemetry.sdk.resources import Resource
@@ -37,7 +38,7 @@ def setup_logging():
# Create and set a global logger provider for the application.
logger_provider = LoggerProvider(resource=resource)
# Log processors are initialized with an exporter which is responsible
logger_provider.add_log_record_processor(BatchLogRecordProcessor(ConsoleLogExporter()))
logger_provider.add_log_record_processor(BatchLogRecordProcessor(ConsoleLogRecordExporter()))
# Sets the global default logger provider
set_logger_provider(logger_provider)
# Create a logging handler to write logging records, in OTLP format, to the exporter.
@@ -115,11 +116,15 @@ async def run_chat_client() -> None:
2 spans with gen_ai.operation.name=execute_tool
"""
client = FoundryChatClient()
client = FoundryChatClient(credential=AzureCliCredential())
message = "What's the weather in Amsterdam and in Paris?"
print(f"User: {message}")
print("Assistant: ", end="")
async for chunk in client.get_response([Message(role="user", text=message)], tools=get_weather, stream=True):
async for chunk in client.get_response(
[Message(role="user", text=message)],
stream=True,
options={"tools": [get_weather]},
):
if chunk.text:
print(chunk.text, end="")
print("")
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Annotated
from agent_framework import Message, tool
from agent_framework.foundry import FoundryChatClient
from agent_framework.observability import get_tracer
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from opentelemetry.trace import SpanKind
from opentelemetry.trace.span import format_trace_id
@@ -90,12 +91,19 @@ async def run_chat_client(client: "SupportsChatGetResponse", stream: bool = Fals
print(f"User: {message}")
if stream:
print("Assistant: ", end="")
async for chunk in client.get_response([Message(role="user", text=message)], tools=get_weather, stream=True):
async for chunk in client.get_response(
[Message(role="user", text=message)],
stream=True,
options={"tools": [get_weather]},
):
if chunk.text:
print(chunk.text, end="")
print("")
else:
response = await client.get_response([Message(role="user", text=message)], tools=get_weather)
response = await client.get_response(
[Message(role="user", text=message)],
options={"tools": [get_weather]},
)
print(f"Assistant: {response}")
@@ -103,7 +111,7 @@ async def main() -> None:
with get_tracer().start_as_current_span("Zero Code", kind=SpanKind.CLIENT) as current_span:
print(f"Trace ID: {format_trace_id(current_span.get_span_context().trace_id)}")
client = FoundryChatClient()
client = FoundryChatClient(credential=AzureCliCredential())
await run_chat_client(client, stream=True)
await run_chat_client(client, stream=False)
@@ -7,6 +7,7 @@ from typing import Annotated
from agent_framework import Agent, tool
from agent_framework.foundry import FoundryChatClient
from agent_framework.observability import configure_otel_providers, get_tracer
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from opentelemetry.trace import SpanKind
from opentelemetry.trace.span import format_trace_id
@@ -18,6 +19,12 @@ load_dotenv()
"""
This sample shows how you can observe an agent in Agent Framework by using the
same observability setup function.
Pre-requisites:
- A Foundry project
- An observability backend to receive traces and metrics (for example, a local or remote
OpenTelemetry Collector, another OTLP-compatible backend, or console exporters enabled
via environment variables).
"""
@@ -47,7 +54,7 @@ async def main():
print(f"Trace ID: {format_trace_id(current_span.get_span_context().trace_id)}")
agent = Agent(
client=FoundryChatClient(),
client=FoundryChatClient(credential=AzureCliCredential()),
tools=get_weather,
name="WeatherAgent",
instructions="You are a weather assistant.",
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Annotated, Literal
from agent_framework import Message, tool
from agent_framework.foundry import FoundryChatClient
from agent_framework.observability import configure_otel_providers, get_tracer
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from opentelemetry import trace
from opentelemetry.trace.span import format_trace_id
@@ -24,8 +25,9 @@ This sample shows how you can configure observability of an application via the
When you run this sample with an OTLP endpoint or an Application Insights connection string,
you should see traces, logs, and metrics in the configured backend.
If no OTLP endpoint or Application Insights connection string is configured, the sample will
output traces, logs, and metrics to the console.
Pre-requisites:
- A Foundry project
- A local OpenTelemetry Collector instance to receive the traces and metrics.
"""
# Load environment variables from .env file
@@ -78,13 +80,18 @@ async def run_chat_client(client: "SupportsChatGetResponse", stream: bool = Fals
if stream:
print("Assistant: ", end="")
async for chunk in client.get_response(
[Message(role="user", text=message)], tools=get_weather, stream=True
[Message(role="user", text=message)],
stream=True,
options={"tools": [get_weather]},
):
if chunk.text:
print(chunk.text, end="")
print("")
else:
response = await client.get_response([Message(role="user", text=message)], tools=get_weather)
response = await client.get_response(
[Message(role="user", text=message)],
options={"tools": [get_weather]},
)
print(f"Assistant: {response}")
@@ -101,7 +108,7 @@ async def run_tool() -> None:
with get_tracer().start_as_current_span("Scenario: AI Function", kind=trace.SpanKind.CLIENT):
print("Running scenario: AI Function")
weather = await get_weather.invoke(location="Amsterdam")
print(f"Weather in Amsterdam:\n{weather}")
print(f"Weather in Amsterdam:\n{weather[-1]}")
async def main(scenario: Literal["client", "client_stream", "tool", "all"] = "all"):
@@ -114,7 +121,7 @@ async def main(scenario: Literal["client", "client_stream", "tool", "all"] = "al
with get_tracer().start_as_current_span("Sample Scenarios", kind=trace.SpanKind.CLIENT) as current_span:
print(f"Trace ID: {format_trace_id(current_span.get_span_context().trace_id)}")
client = FoundryChatClient()
client = FoundryChatClient(credential=AzureCliCredential())
# Scenarios where telemetry is collected in the SDK, from the most basic to the most complex.
if scenario == "tool" or scenario == "all":
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Annotated, Literal
from agent_framework import Message, tool
from agent_framework.foundry import FoundryChatClient
from agent_framework.observability import configure_otel_providers, get_tracer
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from opentelemetry import trace
from opentelemetry.trace.span import format_trace_id
@@ -27,6 +28,10 @@ and allows you to add multiple exporters programmatically.
For standard OTLP setup, it's recommended to use environment variables (see configure_otel_providers_with_env_var.py).
Use this approach when you need custom exporter configuration beyond what environment variables provide.
Pre-requisites:
- A Foundry project
- A local OpenTelemetry Collector instance to receive the traces and metrics.
"""
# Load environment variables from .env file
@@ -79,13 +84,18 @@ async def run_chat_client(client: "SupportsChatGetResponse", stream: bool = Fals
if stream:
print("Assistant: ", end="")
async for chunk in client.get_response(
[Message(role="user", text=message)], stream=True, tools=get_weather
[Message(role="user", text=message)],
stream=True,
options={"tools": [get_weather]},
):
if chunk.text:
print(chunk.text, end="")
print("")
else:
response = await client.get_response([Message(role="user", text=message)], tools=get_weather)
response = await client.get_response(
[Message(role="user", text=message)],
options={"tools": [get_weather]},
)
print(f"Assistant: {response}")
@@ -102,7 +112,7 @@ async def run_tool() -> None:
with get_tracer().start_as_current_span("Scenario: AI Function", kind=trace.SpanKind.CLIENT):
print("Running scenario: AI Function")
weather = await get_weather.invoke(location="Amsterdam")
print(f"Weather in Amsterdam:\n{weather}")
print(f"Weather in Amsterdam:\n{weather[-1]}")
async def main(scenario: Literal["client", "client_stream", "tool", "all"] = "all"):
@@ -153,7 +163,7 @@ async def main(scenario: Literal["client", "client_stream", "tool", "all"] = "al
with get_tracer().start_as_current_span("Sample Scenarios", kind=trace.SpanKind.CLIENT) as current_span:
print(f"Trace ID: {format_trace_id(current_span.get_span_context().trace_id)}")
client = FoundryChatClient()
client = FoundryChatClient(credential=AzureCliCredential())
# Scenarios where telemetry is collected in the SDK, from the most basic to the most complex.
if scenario == "tool" or scenario == "all":
@@ -1,7 +1,7 @@
# Bedrock Examples
This folder contains examples demonstrating how to use AWS Bedrock models with the Agent Framework. The sample
uses `BEDROCK_CHAT_MODEL_ID`, `BEDROCK_REGION`, and AWS credentials (`AWS_ACCESS_KEY_ID`,
uses `BEDROCK_CHAT_MODEL`, `BEDROCK_REGION`, and AWS credentials (`AWS_ACCESS_KEY_ID`,
`AWS_SECRET_ACCESS_KEY`, optional `AWS_SESSION_TOKEN`).
## Examples
@@ -12,6 +12,6 @@ uses `BEDROCK_CHAT_MODEL_ID`, `BEDROCK_REGION`, and AWS credentials (`AWS_ACCESS
## Environment Variables
- `BEDROCK_CHAT_MODEL_ID`: Bedrock model ID (for example, `anthropic.claude-3-5-sonnet-20240620-v1:0`)
- `BEDROCK_CHAT_MODEL`: Bedrock model ID (for example, `anthropic.claude-3-5-sonnet-20240620-v1:0`)
- `BEDROCK_REGION`: AWS region (defaults to `us-east-1` if unset)
- AWS credentials via standard variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, optional `AWS_SESSION_TOKEN`)
@@ -17,7 +17,7 @@ Bedrock Chat Client Example
This sample demonstrates using `BedrockChatClient` with an agent and a simple tool.
Environment variables used:
- `BEDROCK_CHAT_MODEL_ID`
- `BEDROCK_CHAT_MODEL`
- `BEDROCK_REGION` (defaults to `us-east-1` if unset)
- AWS credentials via standard variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`,
optional `AWS_SESSION_TOKEN`)
@@ -28,14 +28,14 @@ This folder contains examples demonstrating how to use Anthropic's Claude models
### Anthropic Client
- `ANTHROPIC_API_KEY`: Your Anthropic API key (get one from [Anthropic Console](https://console.anthropic.com/))
- `ANTHROPIC_CHAT_MODEL_ID`: The Claude model to use (e.g., `claude-haiku-4-5`, `claude-sonnet-4-5-20250929`)
- `ANTHROPIC_CHAT_MODEL`: The Claude model to use (e.g., `claude-haiku-4-5`, `claude-sonnet-4-5-20250929`)
### Foundry
- `ANTHROPIC_FOUNDRY_API_KEY`: Your Foundry Anthropic API key
- `ANTHROPIC_FOUNDRY_RESOURCE`: Your Foundry resource name (for example `my-foundry-resource`)
- `ANTHROPIC_FOUNDRY_BASE_URL`: Optional full Foundry Anthropic base URL alternative to `ANTHROPIC_FOUNDRY_RESOURCE`
- `ANTHROPIC_CHAT_MODEL_ID`: The Claude model to use in Foundry (e.g., `claude-haiku-4-5`)
- `ANTHROPIC_CHAT_MODEL`: The Claude model to use in Foundry (e.g., `claude-haiku-4-5`)
### Claude Agent
@@ -35,7 +35,7 @@ async def non_streaming_example() -> None:
print("=== Non-streaming Response Example ===")
agent = Agent(
client=AnthropicClient(model_id="claude-sonnet-4-5-20250929"),
client=AnthropicClient(model="claude-sonnet-4-5-20250929"),
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
@@ -52,7 +52,7 @@ async def streaming_example() -> None:
print("=== Streaming Response Example ===")
agent = Agent(
client=AnthropicClient(model_id="claude-sonnet-4-5-20250929"),
client=AnthropicClient(model="claude-sonnet-4-5-20250929"),
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
@@ -2,8 +2,7 @@
import asyncio
from agent_framework import Agent
from agent_framework.anthropic import AnthropicClient
from anthropic import AsyncAnthropicFoundry
from agent_framework.foundry import AnthropicFoundryClient
from dotenv import load_dotenv
# Load environment variables from .env file
@@ -27,14 +26,14 @@ To use the Foundry integration ensure you have the following environment variabl
- ANTHROPIC_FOUNDRY_BASE_URL
Optional alternative to ANTHROPIC_FOUNDRY_RESOURCE. Should be something like
https://<your-resource-name>.services.ai.azure.com/anthropic/
- ANTHROPIC_CHAT_MODEL_ID
- ANTHROPIC_CHAT_MODEL
Should be something like claude-haiku-4-5
"""
async def main() -> None:
"""Example of streaming response (get results as they are generated)."""
client = AnthropicClient(anthropic_client=AsyncAnthropicFoundry())
client = AnthropicFoundryClient()
# Create MCP tool configuration using instance method
mcp_tool = client.get_mcp_tool(
@@ -29,7 +29,7 @@ async def main() -> None:
client = AnthropicClient[AnthropicChatOptions](additional_beta_flags=["skills-2025-10-02"])
# List Anthropic-managed Skills
skills = await client.anthropic_client.beta.skills.list(source="anthropic", betas=["skills-2025-10-02"])
skills = await client.anthropic_client.beta.skills.list(source="anthropic", betas=["skills-2025-10-02"]) # type: ignore
for skill in skills.data:
print(f"{skill.source}: {skill.id} (version: {skill.latest_version})")
@@ -81,7 +81,7 @@ async def main() -> None:
# Since I'm using the pptx skill, the files will be PowerPoint presentations
print("Generated files:")
for idx, file in enumerate(files):
file_content = await client.anthropic_client.beta.files.download(
file_content = await client.anthropic_client.beta.files.download( # type: ignore
file_id=file.file_id, betas=["files-api-2025-04-14"]
)
with open(Path(__file__).parent / f"python_programming-{idx}.pptx", "wb") as f:
@@ -26,7 +26,7 @@ This folder contains Azure-backed samples for the generic OpenAI clients in
Set these before running the Azure provider samples:
- `AZURE_OPENAI_ENDPOINT`
- `AZURE_OPENAI_DEPLOYMENT_NAME`
- `AZURE_OPENAI_MODEL`
Optionally, you can also set:
@@ -38,7 +38,7 @@ async def non_streaming_example() -> None:
agent = Agent(
client=OpenAIChatCompletionClient(
model=os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME"),
model=os.getenv("AZURE_OPENAI_MODEL"),
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
credential=AzureCliCredential(),
@@ -60,7 +60,7 @@ async def streaming_example() -> None:
agent = Agent(
client=OpenAIChatCompletionClient(
model=os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME"),
model=os.getenv("AZURE_OPENAI_MODEL"),
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
credential=AzureCliCredential(),
@@ -41,7 +41,7 @@ async def main() -> None:
# authentication option.
agent = Agent(
client=OpenAIChatCompletionClient(
model=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"],
model=os.environ["AZURE_OPENAI_MODEL"],
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
credential=AzureCliCredential(),
),
@@ -38,7 +38,7 @@ async def non_streaming_example() -> None:
agent = Agent(
client=OpenAIChatClient(
model=os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME"),
model=os.getenv("AZURE_OPENAI_MODEL"),
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
credential=AzureCliCredential(),
@@ -60,7 +60,7 @@ async def streaming_example() -> None:
agent = Agent(
client=OpenAIChatClient(
model=os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME"),
model=os.getenv("AZURE_OPENAI_MODEL"),
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
credential=AzureCliCredential(),
@@ -45,4 +45,4 @@ This folder contains Azure AI Foundry and Foundry Local samples for Agent Framew
### Environment Variables
- `FOUNDRY_LOCAL_MODEL_ID`: Optional model alias/ID to use by default when `model_id` is not passed to `FoundryLocalClient`.
- `FOUNDRY_LOCAL_MODEL`: Optional model alias/ID to use by default when `model` is not passed to `FoundryLocalClient`.
@@ -51,7 +51,7 @@ async def handle_approvals_with_session(query: str, agent: "SupportsAgentRun", s
"""Here we let the session deal with the previous responses, and we just rerun with the approval."""
from agent_framework import Message
result = await agent.run(query, session=session, store=True)
result = await agent.run(query, session=session, options={"store": True})
while len(result.user_input_requests) > 0:
new_input: list[Any] = []
for user_input_needed in result.user_input_requests:
@@ -66,7 +66,7 @@ async def handle_approvals_with_session(query: str, agent: "SupportsAgentRun", s
contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")],
)
)
result = await agent.run(new_input, session=session, store=True)
result = await agent.run(new_input, session=session, options={"store": True})
return result
@@ -40,8 +40,8 @@ Set the following environment variables:
- `OLLAMA_HOST`: The base URL for your Ollama server (optional, defaults to `http://localhost:11434`)
- Example: `export OLLAMA_HOST="http://localhost:11434"`
- `OLLAMA_MODEL_ID`: The model name to use
- Example: `export OLLAMA_MODEL_ID="qwen2.5:8b"`
- `OLLAMA_MODEL`: The model name to use
- Example: `export OLLAMA_MODEL="qwen2.5:8b"`
- Must be a model you have pulled with Ollama
### For OpenAI Client with Ollama (`ollama_with_openai_chat_client.py`)
@@ -17,7 +17,7 @@ This sample demonstrates implementing a Ollama agent with basic tool usage.
Ensure to install Ollama and have a model running locally before running the sample
Not all Models support function calling, to test function calling try llama3.2 or qwen3:4b
Set the model to use via the OLLAMA_MODEL_ID environment variable or modify the code below.
Set the model to use via the OLLAMA_MODEL environment variable or modify the code below.
https://ollama.com/
"""
@@ -16,7 +16,7 @@ This sample demonstrates implementing a Ollama agent with reasoning.
Ensure to install Ollama and have a model running locally before running the sample
Not all Models support reasoning, to test reasoning try qwen3:8b
Set the model to use via the OLLAMA_MODEL_ID environment variable or modify the code below.
Set the model to use via the OLLAMA_MODEL environment variable or modify the code below.
https://ollama.com/
"""
@@ -17,7 +17,7 @@ This sample demonstrates using the native Ollama Chat Client directly.
Ensure to install Ollama and have a model running locally before running the sample.
Not all Models support function calling, to test function calling try llama3.2
Set the model to use via the OLLAMA_MODEL_ID environment variable or modify the code below.
Set the model to use via the OLLAMA_MODEL environment variable or modify the code below.
https://ollama.com/
"""
@@ -16,7 +16,7 @@ This sample demonstrates implementing a Ollama agent with multimodal input capab
Ensure to install Ollama and have a model running locally before running the sample
Not all Models support multimodal input, to test multimodal input try gemma3:4b
Set the model to use via the OLLAMA_MODEL_ID environment variable or modify the code below.
Set the model to use via the OLLAMA_MODEL environment variable or modify the code below.
https://ollama.com/
"""
@@ -28,7 +28,7 @@ code_defined_skill/
Set the required environment variables in a `.env` file (see `python/.env.example`):
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `AZURE_OPENAI_DEPLOYMENT_NAME`: The name of your model deployment (defaults to `gpt-4o-mini`)
- `AZURE_OPENAI_MODEL`: The name of your model deployment (defaults to `gpt-4o-mini`)
### Authentication
@@ -48,7 +48,7 @@ file_based_skill/
Set the required environment variables in a `.env` file (see `python/.env.example`):
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `AZURE_OPENAI_DEPLOYMENT_NAME`: The name of your model deployment (defaults to `gpt-4o-mini`)
- `AZURE_OPENAI_MODEL`: The name of your model deployment (defaults to `gpt-4o-mini`)
### Authentication
@@ -61,7 +61,7 @@ Set environment variables (or create a `.env` file):
```
FOUNDRY_PROJECT_ENDPOINT=https://your-project.openai.azure.com/
AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o-mini
AZURE_OPENAI_MODEL=gpt-4o-mini
```
Authenticate with Azure CLI:
@@ -29,7 +29,7 @@ When `require_script_approval=True` is set, the agent pauses before executing an
Set the required environment variables in a `.env` file (see `python/.env.example`):
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `AZURE_OPENAI_DEPLOYMENT_NAME`: The name of your model deployment (defaults to `gpt-4o-mini`)
- `AZURE_OPENAI_MODEL`: The name of your model deployment (defaults to `gpt-4o-mini`)
### Authentication
+4 -4
View File
@@ -38,7 +38,7 @@ async def demo_anthropic_chat_client() -> None:
print("\n=== Anthropic ChatClient with TypedDict Options ===\n")
# Create Anthropic client
client = AnthropicClient(model_id="claude-sonnet-4-5-20250929")
client = AnthropicClient(model="claude-sonnet-4-5-20250929")
# Standard options work great:
response = await client.get_response(
@@ -53,14 +53,14 @@ async def demo_anthropic_chat_client() -> None:
)
print(f"Anthropic Response: {response.text}")
print(f"Model used: {response.model_id}")
print(f"Model used: {response.model}")
async def demo_anthropic_agent() -> None:
"""Demonstrate Agent with Anthropic client and typed options."""
print("\n=== Agent with Anthropic and Typed Options ===\n")
client = AnthropicClient(model_id="claude-sonnet-4-5-20250929")
client = AnthropicClient(model="claude-sonnet-4-5-20250929")
# Create a typed agent for Anthropic - IDE knows Anthropic-specific options!
agent = Agent(
@@ -129,7 +129,7 @@ async def demo_openai_chat_client_reasoning_models() -> None:
)
print(f"OpenAI Response: {response.text}")
print(f"Model used: {response.model_id}")
print(f"Model used: {response.model}")
async def demo_openai_agent() -> None:
@@ -48,9 +48,7 @@ async def main() -> None:
project_endpoint = os.getenv("FOUNDRY_PROJECT_ENDPOINT")
model = os.getenv("FOUNDRY_MODEL")
if not project_endpoint or not model:
raise ValueError(
"FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL must be set"
)
raise ValueError("FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL must be set")
print(f"Connecting to A2A agent at: {a2a_agent_host}")
+3 -3
View File
@@ -67,12 +67,12 @@ def main() -> None:
# Validate environment
project_endpoint = os.getenv("FOUNDRY_PROJECT_ENDPOINT")
deployment_name = os.getenv("FOUNDRY_MODEL")
model = os.getenv("FOUNDRY_MODEL")
if not project_endpoint:
print("Error: FOUNDRY_PROJECT_ENDPOINT environment variable is not set.")
sys.exit(1)
if not deployment_name:
if not model:
print("Error: FOUNDRY_MODEL environment variable is not set.")
sys.exit(1)
@@ -80,7 +80,7 @@ def main() -> None:
credential = AzureCliCredential()
client = FoundryChatClient(
project_endpoint=project_endpoint,
model=deployment_name,
model=model,
credential=credential,
)
@@ -7,7 +7,7 @@ Components used in this sample:
- AgentFunctionApp to register multiple agents and expose dedicated HTTP endpoints.
- Custom tool functions to demonstrate tool invocation from different agents.
Prerequisites: set `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_DEPLOYMENT_NAME`, and sign in with Azure CLI before starting the Functions host."""
Prerequisites: set `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_MODEL`, and sign in with Azure CLI before starting the Functions host."""
import logging
from typing import Any
@@ -10,4 +10,4 @@ TASKHUB_NAME=default
# Azure OpenAI Configuration
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
AZURE_OPENAI_DEPLOYMENT_NAME=your-deployment-name
AZURE_OPENAI_MODEL=your-deployment-name
@@ -99,7 +99,7 @@ The sample can run locally without Azure Functions infrastructure using DevUI:
```
2. Configure `.env` with your Azure OpenAI credentials (`AZURE_OPENAI_ENDPOINT` and
`AZURE_OPENAI_DEPLOYMENT_NAME`)
`AZURE_OPENAI_MODEL`)
3. Install dependencies:
```bash
@@ -21,7 +21,7 @@ Key architectural points:
- Mixed agent/executor fan-outs execute concurrently
Prerequisites:
- Configure `AZURE_OPENAI_ENDPOINT` and `AZURE_OPENAI_DEPLOYMENT_NAME`
- Configure `AZURE_OPENAI_ENDPOINT` and `AZURE_OPENAI_MODEL`
- Sign in with Azure CLI (`az login`) for `AzureCliCredential`
- Ensure Azurite and the Durable Task Scheduler emulator are running
"""
@@ -362,7 +362,7 @@ def _create_workflow() -> Workflow:
credential = AzureCliCredential()
chat_client = OpenAIChatCompletionClient(
model=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"],
model=os.environ["AZURE_OPENAI_MODEL"],
api_key=get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default"),
)
@@ -6,6 +6,6 @@
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
"TASKHUB_NAME": "default",
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
"AZURE_OPENAI_MODEL": "<AZURE_OPENAI_MODEL>"
}
}
@@ -8,7 +8,7 @@ each with their own specialized capabilities and tools.
Prerequisites:
- The worker must be running with both agents registered
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_DEPLOYMENT_NAME when running the worker
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_MODEL when running the worker
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running
"""
@@ -5,7 +5,7 @@ This sample demonstrates running both the worker and client in a single process
for multiple agents with different tools. The worker registers two agents
(WeatherAgent and MathAgent), each with their own specialized capabilities.
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_DEPLOYMENT_NAME
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running (e.g., using Docker)
To run this sample:
@@ -7,7 +7,7 @@ with their own specialized tools. This demonstrates how to host multiple agents
with different capabilities in a single worker process.
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_DEPLOYMENT_NAME
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Start a Durable Task Scheduler (e.g., using Docker)
"""

Some files were not shown because too many files have changed in this diff Show More