test
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
Python - Merge - Tests / paths-filter (push) Has been cancelled
Python - Merge - Tests / Python Tests - Core (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / Python Tests - Azure AI (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / python-integration-tests-check (push) Has been cancelled
Python - Lab Tests / paths-filter (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.14) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.14) (push) Has been cancelled
Check .md links / markdown-link-check (push) Has been cancelled

This commit is contained in:
2026-01-24 03:05:12 +11:00
parent f78f2388b3
commit 539852f81c
2584 changed files with 287471 additions and 0 deletions

301
python/samples/README.md Normal file
View File

@@ -0,0 +1,301 @@
# Python Samples
This directory contains samples demonstrating the capabilities of Microsoft Agent Framework for Python.
## Agents
### A2A (Agent-to-Agent)
| File | Description |
|------|-------------|
| [`getting_started/agents/a2a/agent_with_a2a.py`](./getting_started/agents/a2a/agent_with_a2a.py) | Agent2Agent (A2A) Protocol Integration Sample |
### Anthropic
| File | Description |
|------|-------------|
| [`getting_started/agents/anthropic/anthropic_basic.py`](./getting_started/agents/anthropic/anthropic_basic.py) | Agent with Anthropic Client |
| [`getting_started/agents/anthropic/anthropic_advanced.py`](./getting_started/agents/anthropic/anthropic_advanced.py) | Advanced sample with `thinking` and hosted tools. |
### Azure AI (based on `azure-ai-agents` V1 package)
| File | Description |
|------|-------------|
| [`getting_started/agents/azure_ai_agent/azure_ai_basic.py`](./getting_started/agents/azure_ai_agent/azure_ai_basic.py) | Azure AI Agent Basic Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_azure_ai_search.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_azure_ai_search.py) | Azure AI Agent with Azure AI Search Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_bing_grounding.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_bing_grounding.py) | Azure AI agent with Bing Grounding search for real-time web information |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_code_interpreter.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_code_interpreter.py) | Azure AI Agent with Code Interpreter Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_code_interpreter_file_generation.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_code_interpreter_file_generation.py) | Azure AI Agent with Code Interpreter File Generation Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_existing_agent.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_existing_agent.py) | Azure AI Agent with Existing Agent Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_existing_thread.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_existing_thread.py) | Azure AI Agent with Existing Thread Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_explicit_settings.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_explicit_settings.py) | Azure AI Agent with Explicit Settings Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_file_search.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_file_search.py) | Azure AI agent with File Search capabilities |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_function_tools.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_function_tools.py) | Azure AI Agent with Function Tools Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_hosted_mcp.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_hosted_mcp.py) | Azure AI Agent with Hosted MCP Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_local_mcp.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_local_mcp.py) | Azure AI Agent with Local MCP Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_multiple_tools.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_multiple_tools.py) | Azure AI Agent with Multiple Tools Example |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_openapi_tools.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_openapi_tools.py) | Azure AI agent with OpenAPI tools |
| [`getting_started/agents/azure_ai_agent/azure_ai_with_thread.py`](./getting_started/agents/azure_ai_agent/azure_ai_with_thread.py) | Azure AI Agent with Thread Management Example |
### Azure AI (based on `azure-ai-projects` V2 package)
| File | Description |
|------|-------------|
| [`getting_started/agents/azure_ai/azure_ai_basic.py`](./getting_started/agents/azure_ai/azure_ai_basic.py) | Azure AI Agent Basic Example |
| [`getting_started/agents/azure_ai/azure_ai_use_latest_version.py`](./getting_started/agents/azure_ai/azure_ai_use_latest_version.py) | Azure AI Agent latest version reuse example |
| [`getting_started/agents/azure_ai/azure_ai_with_azure_ai_search.py`](./getting_started/agents/azure_ai/azure_ai_with_azure_ai_search.py) | Azure AI Agent with Azure AI Search Example |
| [`getting_started/agents/azure_ai/azure_ai_with_bing_grounding.py`](./getting_started/agents/azure_ai/azure_ai_with_bing_grounding.py) | Azure AI Agent with Bing Grounding Example |
| [`getting_started/agents/azure_ai/azure_ai_with_bing_custom_search.py`](./getting_started/agents/azure_ai/azure_ai_with_bing_custom_search.py) | Azure AI Agent with Bing Custom Search Example |
| [`getting_started/agents/azure_ai/azure_ai_with_browser_automation.py`](./getting_started/agents/azure_ai/azure_ai_with_browser_automation.py) | Azure AI Agent with Browser Automation Example |
| [`getting_started/agents/azure_ai/azure_ai_with_code_interpreter.py`](./getting_started/agents/azure_ai/azure_ai_with_code_interpreter.py) | Azure AI Agent with Code Interpreter Example |
| [`getting_started/agents/azure_ai/azure_ai_with_code_interpreter_file_generation.py`](./getting_started/agents/azure_ai/azure_ai_with_code_interpreter_file_generation.py) | Azure AI Agent with Code Interpreter File Generation Example |
| [`getting_started/agents/azure_ai/azure_ai_with_existing_agent.py`](./getting_started/agents/azure_ai/azure_ai_with_existing_agent.py) | Azure AI Agent with Existing Agent Example |
| [`getting_started/agents/azure_ai/azure_ai_with_existing_conversation.py`](./getting_started/agents/azure_ai/azure_ai_with_existing_conversation.py) | Azure AI Agent with Existing Conversation Example |
| [`getting_started/agents/azure_ai/azure_ai_with_explicit_settings.py`](./getting_started/agents/azure_ai/azure_ai_with_explicit_settings.py) | Azure AI Agent with Explicit Settings Example |
| [`getting_started/agents/azure_ai/azure_ai_with_file_search.py`](./getting_started/agents/azure_ai/azure_ai_with_file_search.py) | Azure AI Agent with File Search Example |
| [`getting_started/agents/azure_ai/azure_ai_with_hosted_mcp.py`](./getting_started/agents/azure_ai/azure_ai_with_hosted_mcp.py) | Azure AI Agent with Hosted MCP Example |
| [`getting_started/agents/azure_ai/azure_ai_with_response_format.py`](./getting_started/agents/azure_ai/azure_ai_with_response_format.py) | Azure AI Agent with Structured Output Example |
| [`getting_started/agents/azure_ai/azure_ai_with_thread.py`](./getting_started/agents/azure_ai/azure_ai_with_thread.py) | Azure AI Agent with Thread Management Example |
| [`getting_started/agents/azure_ai/azure_ai_with_image_generation.py`](./getting_started/agents/azure_ai/azure_ai_with_image_generation.py) | Azure AI Agent with Image Generation Example |
| [`getting_started/agents/azure_ai/azure_ai_with_microsoft_fabric.py`](./getting_started/agents/azure_ai/azure_ai_with_microsoft_fabric.py) | Azure AI Agent with Microsoft Fabric Example |
| [`getting_started/agents/azure_ai/azure_ai_with_web_search.py`](./getting_started/agents/azure_ai/azure_ai_with_web_search.py) | Azure AI Agent with Web Search Example |
### Azure OpenAI
| File | Description |
|------|-------------|
| [`getting_started/agents/azure_openai/azure_assistants_basic.py`](./getting_started/agents/azure_openai/azure_assistants_basic.py) | Azure OpenAI Assistants Basic Example |
| [`getting_started/agents/azure_openai/azure_assistants_with_code_interpreter.py`](./getting_started/agents/azure_openai/azure_assistants_with_code_interpreter.py) | Azure OpenAI Assistants with Code Interpreter Example |
| [`getting_started/agents/azure_openai/azure_assistants_with_existing_assistant.py`](./getting_started/agents/azure_openai/azure_assistants_with_existing_assistant.py) | Azure OpenAI Assistants with Existing Assistant Example |
| [`getting_started/agents/azure_openai/azure_assistants_with_explicit_settings.py`](./getting_started/agents/azure_openai/azure_assistants_with_explicit_settings.py) | Azure OpenAI Assistants with Explicit Settings Example |
| [`getting_started/agents/azure_openai/azure_assistants_with_function_tools.py`](./getting_started/agents/azure_openai/azure_assistants_with_function_tools.py) | Azure OpenAI Assistants with Function Tools Example |
| [`getting_started/agents/azure_openai/azure_assistants_with_thread.py`](./getting_started/agents/azure_openai/azure_assistants_with_thread.py) | Azure OpenAI Assistants with Thread Management Example |
| [`getting_started/agents/azure_openai/azure_chat_client_basic.py`](./getting_started/agents/azure_openai/azure_chat_client_basic.py) | Azure OpenAI Chat Client Basic Example |
| [`getting_started/agents/azure_openai/azure_chat_client_with_explicit_settings.py`](./getting_started/agents/azure_openai/azure_chat_client_with_explicit_settings.py) | Azure OpenAI Chat Client with Explicit Settings Example |
| [`getting_started/agents/azure_openai/azure_chat_client_with_function_tools.py`](./getting_started/agents/azure_openai/azure_chat_client_with_function_tools.py) | Azure OpenAI Chat Client with Function Tools Example |
| [`getting_started/agents/azure_openai/azure_chat_client_with_thread.py`](./getting_started/agents/azure_openai/azure_chat_client_with_thread.py) | Azure OpenAI Chat Client with Thread Management Example |
| [`getting_started/agents/azure_openai/azure_responses_client_basic.py`](./getting_started/agents/azure_openai/azure_responses_client_basic.py) | Azure OpenAI Responses Client Basic Example |
| [`getting_started/agents/azure_openai/azure_responses_client_image_analysis.py`](./getting_started/agents/azure_openai/azure_responses_client_image_analysis.py) | Azure OpenAI Responses Client with Image Analysis Example |
| [`getting_started/agents/azure_openai/azure_responses_client_with_code_interpreter.py`](./getting_started/agents/azure_openai/azure_responses_client_with_code_interpreter.py) | Azure OpenAI Responses Client with Code Interpreter Example |
| [`getting_started/agents/azure_openai/azure_responses_client_with_explicit_settings.py`](./getting_started/agents/azure_openai/azure_responses_client_with_explicit_settings.py) | Azure OpenAI Responses Client with Explicit Settings Example |
| [`getting_started/agents/azure_openai/azure_responses_client_with_function_tools.py`](./getting_started/agents/azure_openai/azure_responses_client_with_function_tools.py) | Azure OpenAI Responses Client with Function Tools Example |
| [`getting_started/agents/azure_openai/azure_responses_client_with_hosted_mcp.py`](./getting_started/agents/azure_openai/azure_responses_client_with_hosted_mcp.py) | Azure OpenAI Responses Client with Hosted Model Context Protocol (MCP) Example |
| [`getting_started/agents/azure_openai/azure_responses_client_with_local_mcp.py`](./getting_started/agents/azure_openai/azure_responses_client_with_local_mcp.py) | Azure OpenAI Responses Client with local Model Context Protocol (MCP) Example |
| [`getting_started/agents/azure_openai/azure_responses_client_with_thread.py`](./getting_started/agents/azure_openai/azure_responses_client_with_thread.py) | Azure OpenAI Responses Client with Thread Management Example |
### Copilot Studio
| File | Description |
|------|-------------|
| [`getting_started/agents/copilotstudio/copilotstudio_basic.py`](./getting_started/agents/copilotstudio/copilotstudio_basic.py) | Copilot Studio Agent Basic Example |
| [`getting_started/agents/copilotstudio/copilotstudio_with_explicit_settings.py`](./getting_started/agents/copilotstudio/copilotstudio_with_explicit_settings.py) | Copilot Studio Agent with Explicit Settings Example |
### Custom
| File | Description |
|------|-------------|
| [`getting_started/agents/custom/custom_agent.py`](./getting_started/agents/custom/custom_agent.py) | Custom Agent Implementation Example |
| [`getting_started/agents/custom/custom_chat_client.py`](./getting_started/agents/custom/custom_chat_client.py) | Custom Chat Client Implementation Example |
### Ollama
The recommended way to use Ollama is via the native `OllamaChatClient` from the `agent-framework-ollama` package.
| File | Description |
|------|-------------|
| [`getting_started/agents/ollama/ollama_agent_basic.py`](./getting_started/agents/ollama/ollama_agent_basic.py) | Basic Ollama Agent with native Ollama Chat Client |
| [`getting_started/agents/ollama/ollama_agent_reasoning.py`](./getting_started/agents/ollama/ollama_agent_reasoning.py) | Ollama Agent with reasoning capabilities |
| [`getting_started/agents/ollama/ollama_chat_client.py`](./getting_started/agents/ollama/ollama_chat_client.py) | Direct usage of Ollama Chat Client |
| [`getting_started/agents/ollama/ollama_chat_multimodal.py`](./getting_started/agents/ollama/ollama_chat_multimodal.py) | Ollama Chat Client with multimodal (image) input |
| [`getting_started/agents/ollama/ollama_with_openai_chat_client.py`](./getting_started/agents/ollama/ollama_with_openai_chat_client.py) | Alternative: Ollama via OpenAI Chat Client |
### OpenAI
| File | Description |
|------|-------------|
| [`getting_started/agents/openai/openai_assistants_basic.py`](./getting_started/agents/openai/openai_assistants_basic.py) | OpenAI Assistants Basic Example |
| [`getting_started/agents/openai/openai_assistants_with_code_interpreter.py`](./getting_started/agents/openai/openai_assistants_with_code_interpreter.py) | OpenAI Assistants with Code Interpreter Example |
| [`getting_started/agents/openai/openai_assistants_with_existing_assistant.py`](./getting_started/agents/openai/openai_assistants_with_existing_assistant.py) | OpenAI Assistants with Existing Assistant Example |
| [`getting_started/agents/openai/openai_assistants_with_explicit_settings.py`](./getting_started/agents/openai/openai_assistants_with_explicit_settings.py) | OpenAI Assistants with Explicit Settings Example |
| [`getting_started/agents/openai/openai_assistants_with_file_search.py`](./getting_started/agents/openai/openai_assistants_with_file_search.py) | OpenAI Assistants with File Search Example |
| [`getting_started/agents/openai/openai_assistants_with_function_tools.py`](./getting_started/agents/openai/openai_assistants_with_function_tools.py) | OpenAI Assistants with Function Tools Example |
| [`getting_started/agents/openai/openai_assistants_with_thread.py`](./getting_started/agents/openai/openai_assistants_with_thread.py) | OpenAI Assistants with Thread Management Example |
| [`getting_started/agents/openai/openai_chat_client_basic.py`](./getting_started/agents/openai/openai_chat_client_basic.py) | OpenAI Chat Client Basic Example |
| [`getting_started/agents/openai/openai_chat_client_with_explicit_settings.py`](./getting_started/agents/openai/openai_chat_client_with_explicit_settings.py) | OpenAI Chat Client with Explicit Settings Example |
| [`getting_started/agents/openai/openai_chat_client_with_function_tools.py`](./getting_started/agents/openai/openai_chat_client_with_function_tools.py) | OpenAI Chat Client with Function Tools Example |
| [`getting_started/agents/openai/openai_chat_client_with_local_mcp.py`](./getting_started/agents/openai/openai_chat_client_with_local_mcp.py) | OpenAI Chat Client with Local MCP Example |
| [`getting_started/agents/openai/openai_chat_client_with_thread.py`](./getting_started/agents/openai/openai_chat_client_with_thread.py) | OpenAI Chat Client with Thread Management Example |
| [`getting_started/agents/openai/openai_chat_client_with_web_search.py`](./getting_started/agents/openai/openai_chat_client_with_web_search.py) | OpenAI Chat Client with Web Search Example |
| [`getting_started/agents/openai/openai_chat_client_with_runtime_json_schema.py`](./getting_started/agents/openai/openai_chat_client_with_runtime_json_schema.py) | OpenAI Chat Client with runtime JSON Schema for structured output without a Pydantic model |
| [`getting_started/agents/openai/openai_responses_client_basic.py`](./getting_started/agents/openai/openai_responses_client_basic.py) | OpenAI Responses Client Basic Example |
| [`getting_started/agents/openai/openai_responses_client_image_analysis.py`](./getting_started/agents/openai/openai_responses_client_image_analysis.py) | OpenAI Responses Client Image Analysis Example |
| [`getting_started/agents/openai/openai_responses_client_image_generation.py`](./getting_started/agents/openai/openai_responses_client_image_generation.py) | OpenAI Responses Client Image Generation Example |
| [`getting_started/agents/openai/openai_responses_client_reasoning.py`](./getting_started/agents/openai/openai_responses_client_reasoning.py) | OpenAI Responses Client Reasoning Example |
| [`getting_started/agents/openai/openai_responses_client_with_code_interpreter.py`](./getting_started/agents/openai/openai_responses_client_with_code_interpreter.py) | OpenAI Responses Client with Code Interpreter Example |
| [`getting_started/agents/openai/openai_responses_client_with_explicit_settings.py`](./getting_started/agents/openai/openai_responses_client_with_explicit_settings.py) | OpenAI Responses Client with Explicit Settings Example |
| [`getting_started/agents/openai/openai_responses_client_with_file_search.py`](./getting_started/agents/openai/openai_responses_client_with_file_search.py) | OpenAI Responses Client with File Search Example |
| [`getting_started/agents/openai/openai_responses_client_with_function_tools.py`](./getting_started/agents/openai/openai_responses_client_with_function_tools.py) | OpenAI Responses Client with Function Tools Example |
| [`getting_started/agents/openai/openai_responses_client_with_hosted_mcp.py`](./getting_started/agents/openai/openai_responses_client_with_hosted_mcp.py) | OpenAI Responses Client with Hosted MCP Example |
| [`getting_started/agents/openai/openai_responses_client_with_local_mcp.py`](./getting_started/agents/openai/openai_responses_client_with_local_mcp.py) | OpenAI Responses Client with Local MCP Example |
| [`getting_started/agents/openai/openai_responses_client_with_structured_output.py`](./getting_started/agents/openai/openai_responses_client_with_structured_output.py) | OpenAI Responses Client with Structured Output Example |
| [`getting_started/agents/openai/openai_responses_client_with_thread.py`](./getting_started/agents/openai/openai_responses_client_with_thread.py) | OpenAI Responses Client with Thread Management Example |
| [`getting_started/agents/openai/openai_responses_client_with_web_search.py`](./getting_started/agents/openai/openai_responses_client_with_web_search.py) | OpenAI Responses Client with Web Search Example |
## Chat Client
| File | Description |
|------|-------------|
| [`getting_started/chat_client/azure_ai_chat_client.py`](./getting_started/chat_client/azure_ai_chat_client.py) | Azure AI Chat Client Direct Usage Example |
| [`getting_started/chat_client/azure_assistants_client.py`](./getting_started/chat_client/azure_assistants_client.py) | Azure OpenAI Assistants Client Direct Usage Example |
| [`getting_started/chat_client/azure_chat_client.py`](./getting_started/chat_client/azure_chat_client.py) | Azure Chat Client Direct Usage Example |
| [`getting_started/chat_client/azure_responses_client.py`](./getting_started/chat_client/azure_responses_client.py) | Azure OpenAI Responses Client Direct Usage Example |
| [`getting_started/chat_client/chat_response_cancellation.py`](./getting_started/chat_client/chat_response_cancellation.py) | Chat Response Cancellation Example |
| [`getting_started/chat_client/openai_assistants_client.py`](./getting_started/chat_client/openai_assistants_client.py) | OpenAI Assistants Client Direct Usage Example |
| [`getting_started/chat_client/openai_chat_client.py`](./getting_started/chat_client/openai_chat_client.py) | OpenAI Chat Client Direct Usage Example |
| [`getting_started/chat_client/openai_responses_client.py`](./getting_started/chat_client/openai_responses_client.py) | OpenAI Responses Client Direct Usage Example |
## Context Providers
### Mem0
| File | Description |
|------|-------------|
| [`getting_started/context_providers/mem0/mem0_basic.py`](./getting_started/context_providers/mem0/mem0_basic.py) | Basic Mem0 integration example |
| [`getting_started/context_providers/mem0/mem0_oss.py`](./getting_started/context_providers/mem0/mem0_oss.py) | Mem0 OSS (Open Source) integration example |
| [`getting_started/context_providers/mem0/mem0_threads.py`](./getting_started/context_providers/mem0/mem0_threads.py) | Mem0 with thread management example |
### Redis
| File | Description |
|------|-------------|
| [`getting_started/context_providers/redis/redis_basics.py`](./getting_started/context_providers/redis/redis_basics.py) | Basic Redis provider example |
| [`getting_started/context_providers/redis/redis_conversation.py`](./getting_started/context_providers/redis/redis_conversation.py) | Redis conversation context management example |
| [`getting_started/context_providers/redis/redis_threads.py`](./getting_started/context_providers/redis/redis_threads.py) | Redis with thread management example |
### Other
| File | Description |
|------|-------------|
| [`getting_started/context_providers/simple_context_provider.py`](./getting_started/context_providers/simple_context_provider.py) | Simple context provider implementation example |
| [`getting_started/context_providers/aggregate_context_provider.py`](./getting_started/context_providers/aggregate_context_provider.py) | Shows how to combine multiple context providers using an AggregateContextProvider |
## Declarative
| File | Description |
|------|-------------|
| [`getting_started/declarative/azure_openai_responses_agent.py`](./getting_started/declarative/azure_openai_responses_agent.py) | Basic agent using Azure OpenAI with structured responses |
| [`getting_started/declarative/get_weather_agent.py`](./getting_started/declarative/get_weather_agent.py) | Agent with custom function tools using declarative bindings |
| [`getting_started/declarative/inline_yaml.py`](./getting_started/declarative/inline_yaml.py) | Agent created from inline YAML string |
| [`getting_started/declarative/mcp_tool_yaml.py`](./getting_started/declarative/mcp_tool_yaml.py) | MCP tool configuration with API key and Azure Foundry connection auth |
| [`getting_started/declarative/microsoft_learn_agent.py`](./getting_started/declarative/microsoft_learn_agent.py) | Agent with MCP server integration for Microsoft Learn documentation |
| [`getting_started/declarative/openai_responses_agent.py`](./getting_started/declarative/openai_responses_agent.py) | Basic agent using OpenAI directly |
## DevUI
| File | Description |
|------|-------------|
| [`getting_started/devui/fanout_workflow/workflow.py`](./getting_started/devui/fanout_workflow/workflow.py) | Complex fan-out/fan-in workflow example |
| [`getting_started/devui/foundry_agent/agent.py`](./getting_started/devui/foundry_agent/agent.py) | Azure AI Foundry agent example |
| [`getting_started/devui/in_memory_mode.py`](./getting_started/devui/in_memory_mode.py) | In-memory mode example for DevUI |
| [`getting_started/devui/spam_workflow/workflow.py`](./getting_started/devui/spam_workflow/workflow.py) | Spam detection workflow example |
| [`getting_started/devui/weather_agent_azure/agent.py`](./getting_started/devui/weather_agent_azure/agent.py) | Weather agent using Azure OpenAI example |
| [`getting_started/devui/workflow_agents/workflow.py`](./getting_started/devui/workflow_agents/workflow.py) | Workflow with multiple agents example |
## Evaluation
| File | Description |
|------|-------------|
| [`getting_started/evaluation/red_teaming/red_team_agent_sample.py`](./getting_started/evaluation/red_teaming/red_team_agent_sample.py) | Red team agent evaluation sample for Azure AI Foundry |
| [`getting_started/evaluation/self_reflection/self_reflection.py`](./getting_started/evaluation/self_reflection/self_reflection.py) | LLM self-reflection with AI Foundry graders example |
| [`demos/workflow_evaluation/run_evaluation.py`](./demos/workflow_evaluation/run_evaluation.py) | Multi-agent workflow evaluation demo with travel planning agents evaluated using Azure AI Foundry evaluators |
## MCP (Model Context Protocol)
| File | Description |
|------|-------------|
| [`getting_started/mcp/agent_as_mcp_server.py`](./getting_started/mcp/agent_as_mcp_server.py) | Agent as MCP Server Example |
| [`getting_started/mcp/mcp_api_key_auth.py`](./getting_started/mcp/mcp_api_key_auth.py) | MCP Authentication Example |
## Middleware
| File | Description |
|------|-------------|
| [`getting_started/middleware/agent_and_run_level_middleware.py`](./getting_started/middleware/agent_and_run_level_middleware.py) | Agent and run-level middleware example |
| [`getting_started/middleware/chat_middleware.py`](./getting_started/middleware/chat_middleware.py) | Chat middleware example |
| [`getting_started/middleware/class_based_middleware.py`](./getting_started/middleware/class_based_middleware.py) | Class-based middleware implementation example |
| [`getting_started/middleware/decorator_middleware.py`](./getting_started/middleware/decorator_middleware.py) | Decorator-based middleware example |
| [`getting_started/middleware/exception_handling_with_middleware.py`](./getting_started/middleware/exception_handling_with_middleware.py) | Exception handling with middleware example |
| [`getting_started/middleware/function_based_middleware.py`](./getting_started/middleware/function_based_middleware.py) | Function-based middleware example |
| [`getting_started/middleware/middleware_termination.py`](./getting_started/middleware/middleware_termination.py) | Middleware termination example |
| [`getting_started/middleware/override_result_with_middleware.py`](./getting_started/middleware/override_result_with_middleware.py) | Override result with middleware example |
| [`getting_started/middleware/runtime_context_delegation.py`](./getting_started/middleware/runtime_context_delegation.py) | Runtime context delegation example demonstrating how to pass API tokens, session data, and other context through hierarchical agent delegation |
| [`getting_started/middleware/shared_state_middleware.py`](./getting_started/middleware/shared_state_middleware.py) | Shared state middleware example |
| [`getting_started/middleware/thread_behavior_middleware.py`](./getting_started/middleware/thread_behavior_middleware.py) | Thread behavior middleware example demonstrating how to track conversation state across multiple agent runs |
## Multimodal Input
| File | Description |
|------|-------------|
| [`getting_started/multimodal_input/azure_chat_multimodal.py`](./getting_started/multimodal_input/azure_chat_multimodal.py) | Azure OpenAI Chat with multimodal (image) input example |
| [`getting_started/multimodal_input/azure_responses_multimodal.py`](./getting_started/multimodal_input/azure_responses_multimodal.py) | Azure OpenAI Responses with multimodal (image) input example |
| [`getting_started/multimodal_input/openai_chat_multimodal.py`](./getting_started/multimodal_input/openai_chat_multimodal.py) | OpenAI Chat with multimodal (image) input example |
## Azure Functions
| Sample | Description |
|--------|-------------|
| [`getting_started/azure_functions/01_single_agent/`](./getting_started/azure_functions/01_single_agent/) | Host a single agent in Azure Functions with Durable Extension HTTP endpoints and per-session state. |
| [`getting_started/azure_functions/02_multi_agent/`](./getting_started/azure_functions/02_multi_agent/) | Register multiple agents in one function app with dedicated run routes and a health check endpoint. |
| [`getting_started/azure_functions/03_reliable_streaming/`](./getting_started/azure_functions/03_reliable_streaming/) | Implement reliable streaming for durable agents using Redis Streams with cursor-based resumption. |
| [`getting_started/azure_functions/04_single_agent_orchestration_chaining/`](./getting_started/azure_functions/04_single_agent_orchestration_chaining/) | Chain sequential agent executions inside a durable orchestration while preserving the shared thread context. |
| [`getting_started/azure_functions/05_multi_agent_orchestration_concurrency/`](./getting_started/azure_functions/05_multi_agent_orchestration_concurrency/) | Run two agents concurrently within a durable orchestration and combine their domain-specific outputs. |
| [`getting_started/azure_functions/06_multi_agent_orchestration_conditionals/`](./getting_started/azure_functions/06_multi_agent_orchestration_conditionals/) | Route orchestration logic based on structured agent responses for spam detection and reply drafting. |
| [`getting_started/azure_functions/07_single_agent_orchestration_hitl/`](./getting_started/azure_functions/07_single_agent_orchestration_hitl/) | Implement a human-in-the-loop approval loop that iterates on agent output inside a durable orchestration. |
## Observability
| File | Description |
|------|-------------|
| [`getting_started/observability/advanced_manual_setup_console_output.py`](./getting_started/observability/advanced_manual_setup_console_output.py) | Advanced manual observability setup with console output |
| [`getting_started/observability/advanced_zero_code.py`](./getting_started/observability/advanced_zero_code.py) | Zero-code observability setup example |
| [`getting_started/observability/agent_observability.py`](./getting_started/observability/agent_observability.py) | Agent observability example |
| [`getting_started/observability/agent_with_foundry_tracing.py`](./getting_started/observability/agent_with_foundry_tracing.py) | Any chat client setup with Azure Foundry Observability |
| [`getting_started/observability/azure_ai_agent_observability.py`](./getting_started/observability/azure_ai_agent_observability.py) | Azure AI agent observability example |
| [`getting_started/observability/configure_otel_providers_with_env_var.py`](./getting_started/observability/configure_otel_providers_with_env_var.py) | Setup observability using environment variables |
| [`getting_started/observability/configure_otel_providers_with_parameters.py`](./getting_started/observability/configure_otel_providers_with_parameters.py) | Setup observability using parameters |
| [`getting_started/observability/workflow_observability.py`](./getting_started/observability/workflow_observability.py) | Workflow observability example |
## Threads
| File | Description |
|------|-------------|
| [`getting_started/threads/custom_chat_message_store_thread.py`](./getting_started/threads/custom_chat_message_store_thread.py) | Implementation of custom chat message store state |
| [`getting_started/threads/redis_chat_message_store_thread.py`](./getting_started/threads/redis_chat_message_store_thread.py) | Basic example of using Redis chat message store |
| [`getting_started/threads/suspend_resume_thread.py`](./getting_started/threads/suspend_resume_thread.py) | Demonstrates how to suspend and resume a service-managed thread |
## Tools
| File | Description |
|------|-------------|
| [`getting_started/tools/ai_function_declaration_only.py`](./getting_started/tools/ai_function_declaration_only.py) | Function declarations without implementations for testing agent reasoning |
| [`getting_started/tools/ai_function_from_dict_with_dependency_injection.py`](./getting_started/tools/ai_function_from_dict_with_dependency_injection.py) | Creating AI functions from dictionary definitions using dependency injection |
| [`getting_started/tools/ai_function_recover_from_failures.py`](./getting_started/tools/ai_function_recover_from_failures.py) | Graceful error handling when tools raise exceptions |
| [`getting_started/tools/ai_function_with_approval.py`](./getting_started/tools/ai_function_with_approval.py) | User approval workflows for function calls without threads |
| [`getting_started/tools/ai_function_with_approval_and_threads.py`](./getting_started/tools/ai_function_with_approval_and_threads.py) | Tool approval workflows using threads for conversation history management |
| [`getting_started/tools/ai_function_with_max_exceptions.py`](./getting_started/tools/ai_function_with_max_exceptions.py) | Limiting tool failure exceptions using max_invocation_exceptions |
| [`getting_started/tools/ai_function_with_max_invocations.py`](./getting_started/tools/ai_function_with_max_invocations.py) | Limiting total tool invocations using max_invocations |
| [`getting_started/tools/ai_functions_in_class.py`](./getting_started/tools/ai_functions_in_class.py) | Using ai_function decorator with class methods for stateful tools |
## Workflows
View the list of Workflows samples [here](./getting_started/workflows/README.md).
## Sample Guidelines
For information on creating new samples, see [SAMPLE_GUIDELINES.md](./SAMPLE_GUIDELINES.md).
## More Information
- [Python Package Documentation](../README.md)

View File

@@ -0,0 +1,76 @@
# Sample Guidelines
Samples are extremely important for developers to get started with Agent Framework. We strive to provide a wide range of samples that demonstrate the capabilities of Agent Framework with consistency and quality. This document outlines the guidelines for creating samples.
## General Guidelines
- **Clear and Concise**: Samples should be clear and concise. They should demonstrate a specific set of features or capabilities of Agent Framework. The less concepts a sample demonstrates, the better.
- **Consistent Structure**: All samples should have a consistent structure. This includes the folder structure, file naming, and the content of the sample.
- **Incremental Complexity**: Samples should start simple and gradually increase in complexity. This helps developers understand the concepts and features of Agent Framework.
- **Documentation**: Samples should be over-documented.
### **Clear and Concise**
Try not to include too many concepts in a single sample. The goal is to demonstrate a specific feature or capability of Agent Framework. If you find yourself including too many concepts, consider breaking the sample into multiple samples. A good example of this is to break non-streaming and streaming modes into separate samples.
### **Consistent Structure**
! TODO: Update folder structure to our new needs.
! TODO: Decide on single samples folder or also samples in extensions
#### Getting Started Samples
The getting started samples are the simplest samples that require minimal setup. These samples should be named in the following format: `step<number>_<name>.py`. One exception to this rule is when the sample is a notebook, in which case the sample should be named in the following format: `<number>_<name>.ipynb`.
### **Incremental Complexity**
Try to do a best effort to make sure that the samples are incremental in complexity. For example, in the getting started samples, each step should build on the previous step, and the concept samples should build on the getting started samples, same with the demos.
### **Documentation**
Try to over-document the samples. This includes comments in the code, README.md files, and any other documentation that is necessary to understand the sample. We use the guidance from [PEP8](https://peps.python.org/pep-0008/#comments) for comments in the code, with a deviation for the initial summary comment in samples and the output of the samples.
For the getting started samples and the concept samples, we should have the following:
1. A README.md file is included in each set of samples that explains the purpose of the samples and the setup required to run them.
2. A summary should be included underneath the imports that explains the purpose of the sample and required components/concepts to understand the sample. For example:
```python
'''
This sample shows how to create a chatbot. This sample uses the following two main components:
- a ChatCompletionService: This component is responsible for generating responses to user messages.
- a ChatHistory: This component is responsible for keeping track of the chat history.
The chatbot in this sample is called Mosscap, who responds to user messages with long flowery prose.
'''
```
3. Mark the code with comments to explain the purpose of each section of the code. For example:
```python
# 1. Create the instance of the Kernel to register the plugin and service.
...
# 2. Create the agent with the kernel instance.
...
```
> This will also allow the sample creator to track if the sample is getting too complex.
4. At the end of the sample, include a section that explains the expected output of the sample. For example:
```python
'''
Sample output:
User:> Why is the sky blue in one sentence?
Mosscap:> The sky is blue due to the scattering of sunlight by the molecules in the Earth's atmosphere,
a phenomenon known as Rayleigh scattering, which causes shorter blue wavelengths to become more
prominent in our visual perception.
'''
```
For the demos, a README.md file must be included that explains the purpose of the demo and how to run it. The README.md file should include the following:
- A description of the demo.
- A list of dependencies required to run the demo.
- Instructions on how to run the demo.
- Expected output of the demo.

View File

View File

@@ -0,0 +1,304 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Script to run all Python samples in the samples directory concurrently.
This script will run all samples and report results at the end.
Note: This script is AI generated. This is for internal validation purposes only.
Samples that require human interaction are known to fail.
Usage:
python run_all_samples.py # Run all samples using uv run (concurrent)
python run_all_samples.py --direct # Run all samples directly (concurrent,
# assumes environment is set up)
python run_all_samples.py --subdir <directory> # Run samples only in specific subdirectory
python run_all_samples.py --subdir getting_started/workflows # Example: run only workflow samples
"""
import argparse
import os
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
def find_python_samples(samples_dir: Path, subdir: str | None = None) -> list[Path]:
"""Find all Python sample files in the samples directory or a subdirectory."""
python_files: list[Path] = []
# Determine the search directory
if subdir:
search_dir = samples_dir / subdir
if not search_dir.exists():
print(f"Warning: Subdirectory '{subdir}' does not exist in {samples_dir}")
return []
print(f"Searching in subdirectory: {search_dir}")
else:
search_dir = samples_dir
print(f"Searching in all samples: {search_dir}")
# Walk through all subdirectories and find .py files
for root, dirs, files in os.walk(search_dir):
# Skip __pycache__ directories
dirs[:] = [d for d in dirs if d != "__pycache__"]
for file in files:
if file.endswith(".py") and not file.startswith("_") and file != "_run_all_samples.py":
python_files.append(Path(root) / file)
# Sort files for consistent execution order
return sorted(python_files)
def run_sample(
sample_path: Path,
use_uv: bool = True,
python_root: Path | None = None,
) -> tuple[bool, str, str, str]:
"""
Run a single sample file using subprocess and return (success, output, error_info, error_type).
Args:
sample_path: Path to the sample file
use_uv: Whether to use uv run
python_root: Root directory for uv run
Returns:
Tuple of (success, output, error_info, error_type)
error_type can be: "timeout", "input_hang", "execution_error", "exception"
"""
if use_uv and python_root:
cmd = ["uv", "run", "python", str(sample_path)]
cwd = python_root
else:
cmd = [sys.executable, sample_path.name]
cwd = sample_path.parent
# Set environment variables to handle Unicode properly
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8" # Force Python to use UTF-8 for I/O
env["PYTHONUTF8"] = "1" # Enable UTF-8 mode in Python 3.7+
try:
# Use Popen for better timeout handling with stdin for samples that may wait for input
# Popen gives us more control over process lifecycle compared to subprocess.run()
process = subprocess.Popen(
cmd, # Command to execute as a list [program, arg1, arg2, ...]
cwd=cwd, # Working directory for the subprocess
stdout=subprocess.PIPE, # Capture stdout so we can read the output
stderr=subprocess.PIPE, # Capture stderr so we can read error messages
stdin=subprocess.PIPE, # Create a pipe for stdin so we can send input
text=True, # Handle input/output as text strings (not bytes)
encoding="utf-8", # Use UTF-8 encoding to handle Unicode characters like emojis
errors="replace", # Replace problematic characters instead of failing
env=env, # Pass environment variables for proper Unicode handling
)
try:
# communicate() sends input to stdin and waits for process to complete
# input="" sends an empty string to stdin, which causes input() calls to
# immediately receive EOFError (End Of File) since there's no data to read.
# This prevents the process from hanging indefinitely waiting for user input.
stdout, stderr = process.communicate(input="", timeout=60)
except subprocess.TimeoutExpired:
# If the process doesn't complete within the timeout period, we need to
# forcibly terminate it. This is especially important for processes that
# ignore EOFError and continue to hang on input() calls.
# First attempt: Send SIGKILL (immediate termination) on Unix or TerminateProcess on Windows
process.kill()
try:
# Give the process a few seconds to clean up after being killed
stdout, stderr = process.communicate(timeout=5)
except subprocess.TimeoutExpired:
# If the process is still alive after kill(), use terminate() as a last resort
# terminate() sends SIGTERM (graceful termination request) which may work
# when kill() doesn't on some systems
process.terminate()
stdout, stderr = "", "Process forcibly terminated"
return False, "", f"TIMEOUT: {sample_path.name} (exceeded 60 seconds)", "timeout"
if process.returncode == 0:
output = stdout.strip() if stdout.strip() else "No output"
return True, output, "", "success"
error_info = f"Exit code: {process.returncode}"
if stderr.strip():
error_info += f"\nSTDERR: {stderr}"
# Check if this looks like an input/interaction related error
error_type = "execution_error"
stderr_safe = stderr.encode("utf-8", errors="replace").decode("utf-8") if stderr else ""
if "EOFError" in stderr_safe or "input" in stderr_safe.lower() or "stdin" in stderr_safe.lower():
error_type = "input_hang"
elif "UnicodeEncodeError" in stderr_safe and ("charmap" in stderr_safe or "codec can't encode" in stderr_safe):
error_type = "input_hang" # Unicode errors often indicate interactive samples with emojis
return False, stdout.strip() if stdout.strip() else "", error_info, error_type
except Exception as e:
return False, "", f"ERROR: {sample_path.name} - Exception: {str(e)}", "exception"
def parse_arguments() -> argparse.Namespace:
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="Run Python samples concurrently",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python run_all_samples.py # Run all samples
python run_all_samples.py --direct # Run all samples directly
python run_all_samples.py --subdir getting_started # Run only getting_started samples
python run_all_samples.py --subdir getting_started/workflows # Run only workflow samples
python run_all_samples.py --subdir semantic-kernel-migration # Run only SK migration samples
""",
)
parser.add_argument(
"--direct", action="store_true", help="Run samples directly with python instead of using uv run"
)
parser.add_argument(
"--subdir", type=str, help="Run samples only in the specified subdirectory (relative to samples/)"
)
parser.add_argument(
"--max-workers", type=int, default=16, help="Maximum number of concurrent workers (default: 16)"
)
return parser.parse_args()
def main() -> None:
"""Main function to run all samples concurrently."""
args = parse_arguments()
# Get the samples directory (assuming this script is in the samples directory)
samples_dir = Path(__file__).parent
python_root = samples_dir.parent # Go up to the python/ directory
print("Python samples runner")
print(f"Samples directory: {samples_dir}")
if args.direct:
print("Running samples directly (assuming environment is set up)")
else:
print(f"Using uv run from: {python_root}")
if args.subdir:
print(f"Filtering to subdirectory: {args.subdir}")
print("🚀 Running samples concurrently...")
# Find all Python sample files
sample_files = find_python_samples(samples_dir, args.subdir)
if not sample_files:
print("No Python sample files found!")
return
print(f"Found {len(sample_files)} Python sample files")
# Run samples concurrently
results: list[tuple[Path, bool, str, str, str]] = []
with ThreadPoolExecutor(max_workers=args.max_workers) as executor:
# Submit all tasks
future_to_sample = {
executor.submit(run_sample, sample_path, not args.direct, python_root): sample_path
for sample_path in sample_files
}
# Collect results as they complete
for future in as_completed(future_to_sample):
sample_path = future_to_sample[future]
try:
success, output, error_info, error_type = future.result()
results.append((sample_path, success, output, error_info, error_type))
# Print progress - show relative path from samples directory
relative_path = sample_path.relative_to(samples_dir)
if success:
print(f"{relative_path}")
else:
# Show error type in progress display
error_display = f"{error_type.upper()}" if error_type != "execution_error" else "ERROR"
print(f"{relative_path} - {error_display}")
except Exception as e:
error_info = f"Future exception: {str(e)}"
results.append((sample_path, False, "", error_info, "exception"))
relative_path = sample_path.relative_to(samples_dir)
print(f"{relative_path} - EXCEPTION")
# Sort results by original file order for consistent reporting
sample_to_index = {path: i for i, path in enumerate(sample_files)}
results.sort(key=lambda x: sample_to_index[x[0]])
successful_runs = sum(1 for _, success, _, _, _ in results if success)
failed_runs = len(results) - successful_runs
# Categorize failures by type
timeout_failures = [r for r in results if not r[1] and r[4] == "timeout"]
input_hang_failures = [r for r in results if not r[1] and r[4] == "input_hang"]
execution_errors = [r for r in results if not r[1] and r[4] == "execution_error"]
exceptions = [r for r in results if not r[1] and r[4] == "exception"]
# Print detailed results
print(f"\n{'=' * 80}")
print("DETAILED RESULTS:")
print(f"{'=' * 80}")
for sample_path, success, output, error_info, error_type in results:
relative_path = sample_path.relative_to(samples_dir)
if success:
print(f"{relative_path}")
if output and output != "No output":
print(f" Output preview: {output[:100]}{'...' if len(output) > 100 else ''}")
else:
# Display error with type indicator
if error_type == "timeout":
print(f"⏱️ {relative_path} - TIMEOUT (likely waiting for input)")
elif error_type == "input_hang":
print(f"⌨️ {relative_path} - INPUT ERROR (interactive sample)")
elif error_type == "exception":
print(f"💥 {relative_path} - EXCEPTION")
else:
print(f"{relative_path} - EXECUTION ERROR")
print(f" Error: {error_info}")
# Print categorized summary
print(f"\n{'=' * 80}")
if failed_runs == 0:
print("🎉 ALL SAMPLES COMPLETED SUCCESSFULLY!")
else:
print(f"{failed_runs} SAMPLE(S) FAILED!")
print(f"Successful runs: {successful_runs}")
print(f"Failed runs: {failed_runs}")
if failed_runs > 0:
print("\nFailure breakdown:")
if len(timeout_failures) > 0:
print(f" ⏱️ Timeouts (likely interactive): {len(timeout_failures)}")
if len(input_hang_failures) > 0:
print(f" ⌨️ Input errors (interactive): {len(input_hang_failures)}")
if len(execution_errors) > 0:
print(f" ❌ Execution errors: {len(execution_errors)}")
if len(exceptions) > 0:
print(f" 💥 Exceptions: {len(exceptions)}")
if args.subdir:
print(f"Subdirectory filter: {args.subdir}")
print(f"{'=' * 80}")
# Exit with error code if any samples failed
if failed_runs > 0:
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1 @@
"""This sample has moved to python/packages/bedrock/samples/bedrock_sample.py."""

View File

@@ -0,0 +1,2 @@
# Ignore autogen source files
autogen

View File

@@ -0,0 +1,61 @@
# AutoGen → Microsoft Agent Framework Migration Samples
This gallery helps AutoGen developers move to the Microsoft Agent Framework (AF) with minimal guesswork. Each script pairs AutoGen code with its AF equivalent so you can compare primitives, tooling, and orchestration patterns side by side while you migrate production workloads.
## What's Included
### Single-Agent Parity
- [01_basic_assistant_agent.py](single_agent/01_basic_assistant_agent.py) — Minimal AutoGen `AssistantAgent` and AF `ChatAgent` comparison.
- [02_assistant_agent_with_tool.py](single_agent/02_assistant_agent_with_tool.py) — Function tool integration in both SDKs.
- [03_assistant_agent_thread_and_stream.py](single_agent/03_assistant_agent_thread_and_stream.py) — Thread management and streaming responses.
- [04_agent_as_tool.py](single_agent/04_agent_as_tool.py) — Using agents as tools (hierarchical agent pattern) and streaming with tools.
### Multi-Agent Orchestration
- [01_round_robin_group_chat.py](orchestrations/01_round_robin_group_chat.py) — AutoGen `RoundRobinGroupChat` → AF `GroupChatBuilder`/`SequentialBuilder`.
- [02_selector_group_chat.py](orchestrations/02_selector_group_chat.py) — AutoGen `SelectorGroupChat` → AF `GroupChatBuilder`.
- [03_swarm.py](orchestrations/03_swarm.py) — AutoGen Swarm pattern → AF `HandoffBuilder`.
- [04_magentic_one.py](orchestrations/04_magentic_one.py) — AutoGen `MagenticOneGroupChat` → AF `MagenticBuilder`.
Each script is fully async and the `main()` routine runs both implementations back to back so you can observe their outputs in a single execution.
## Prerequisites
- Python 3.10 or later.
- Access to the necessary model endpoints (Azure OpenAI, OpenAI, etc.).
- Installed SDKs: Install AutoGen and the Microsoft Agent Framework with:
```bash
pip install "autogen-agentchat autogen-ext[openai] agent-framework"
```
- Service credentials exposed through environment variables (e.g., `OPENAI_API_KEY`).
## Running Single-Agent Samples
From the repository root:
```bash
python samples/autogen-migration/single_agent/01_basic_assistant_agent.py
```
Every script accepts no CLI arguments and will first call the AutoGen implementation, followed by the AF version. Adjust the prompt or credentials inside the file as necessary before running.
## Running Orchestration Samples
Advanced comparisons are in `autogen-migration/orchestrations` (RoundRobin, Selector, Swarm, Magentic). You can run them directly:
```bash
python samples/autogen-migration/orchestrations/01_round_robin_group_chat.py
python samples/autogen-migration/orchestrations/04_magentic_one.py
```
## Tips for Migration
- **Default behavior differences**: AutoGen's `AssistantAgent` is single-turn by default (`max_tool_iterations=1`), while AF's `ChatAgent` is multi-turn and continues tool execution automatically.
- **Thread management**: AF agents are stateless by default. Use `agent.get_new_thread()` and pass it to `run()`/`run_stream()` to maintain conversation state, similar to AutoGen's conversation context.
- **Tools**: AutoGen uses `FunctionTool` wrappers; AF uses `@ai_function` decorators with automatic schema inference.
- **Orchestration patterns**:
- `RoundRobinGroupChat` → `SequentialBuilder` or `WorkflowBuilder`
- `SelectorGroupChat` → `GroupChatBuilder` with LLM-based speaker selection
- `Swarm` → `HandoffBuilder` for agent handoff coordination
- `MagenticOneGroupChat` → `MagenticBuilder` for orchestrated multi-agent workflows

View File

@@ -0,0 +1,185 @@
# Copyright (c) Microsoft. All rights reserved.
"""AutoGen RoundRobinGroupChat vs Agent Framework GroupChatBuilder/SequentialBuilder.
Demonstrates sequential agent orchestration where agents take turns processing
the task in a round-robin fashion.
"""
import asyncio
async def run_autogen() -> None:
"""AutoGen's RoundRobinGroupChat for sequential agent orchestration."""
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
client = OpenAIChatCompletionClient(model="gpt-4.1-mini")
# Create specialized agents
researcher = AssistantAgent(
name="researcher",
model_client=client,
system_message="You are a researcher. Provide facts and data about the topic.",
model_client_stream=True,
)
writer = AssistantAgent(
name="writer",
model_client=client,
system_message="You are a writer. Turn research into engaging content.",
model_client_stream=True,
)
editor = AssistantAgent(
name="editor",
model_client=client,
system_message="You are an editor. Review and finalize the content. End with APPROVED if satisfied.",
model_client_stream=True,
)
# Create round-robin team
team = RoundRobinGroupChat(
participants=[researcher, writer, editor],
termination_condition=TextMentionTermination("APPROVED"),
)
# Run the team and display the conversation.
print("[AutoGen] Round-robin conversation:")
await Console(team.run_stream(task="Create a brief summary about electric vehicles"))
async def run_agent_framework() -> None:
"""Agent Framework's SequentialBuilder for sequential agent orchestration."""
from agent_framework import AgentRunUpdateEvent, SequentialBuilder
from agent_framework.openai import OpenAIChatClient
client = OpenAIChatClient(model_id="gpt-4.1-mini")
# Create specialized agents
researcher = client.as_agent(
name="researcher",
instructions="You are a researcher. Provide facts and data about the topic.",
)
writer = client.as_agent(
name="writer",
instructions="You are a writer. Turn research into engaging content.",
)
editor = client.as_agent(
name="editor",
instructions="You are an editor. Review and finalize the content.",
)
# Create sequential workflow
workflow = SequentialBuilder().participants([researcher, writer, editor]).build()
# Run the workflow
print("[Agent Framework] Sequential conversation:")
current_executor = None
async for event in workflow.run_stream("Create a brief summary about electric vehicles"):
if isinstance(event, AgentRunUpdateEvent):
# Print executor name header when switching to a new agent
if current_executor != event.executor_id:
if current_executor is not None:
print() # Newline after previous agent's message
print(f"---------- {event.executor_id} ----------")
current_executor = event.executor_id
if event.data:
print(event.data.text, end="", flush=True)
print() # Final newline after conversation
async def run_agent_framework_with_cycle() -> None:
"""Agent Framework's WorkflowBuilder with cyclic edges and conditional exit."""
from agent_framework import (
AgentExecutorRequest,
AgentExecutorResponse,
AgentRunUpdateEvent,
WorkflowBuilder,
WorkflowContext,
WorkflowOutputEvent,
executor,
)
from agent_framework.openai import OpenAIChatClient
client = OpenAIChatClient(model_id="gpt-4.1-mini")
# Create specialized agents
researcher = client.as_agent(
name="researcher",
instructions="You are a researcher. Provide facts and data about the topic.",
)
writer = client.as_agent(
name="writer",
instructions="You are a writer. Turn research into engaging content.",
)
editor = client.as_agent(
name="editor",
instructions="You are an editor. Review and finalize the content. End with APPROVED if satisfied.",
)
# Create custom executor for checking approval
@executor
async def check_approval(
response: AgentExecutorResponse, context: WorkflowContext[AgentExecutorRequest, str]
) -> None:
assert response.full_conversation is not None
last_message = response.full_conversation[-1]
if last_message and "APPROVED" in last_message.text:
await context.yield_output("Content approved.")
else:
await context.send_message(AgentExecutorRequest(messages=response.full_conversation, should_respond=True))
workflow = (
WorkflowBuilder()
.add_edge(researcher, writer)
.add_edge(writer, editor)
.add_edge(
editor,
check_approval,
)
.add_edge(check_approval, researcher)
.set_start_executor(researcher)
.build()
)
# Run the workflow
print("[Agent Framework with Cycle] Cyclic conversation:")
current_executor = None
async for event in workflow.run_stream("Create a brief summary about electric vehicles"):
if isinstance(event, WorkflowOutputEvent):
print("\n---------- Workflow Output ----------")
print(event.data)
elif isinstance(event, AgentRunUpdateEvent):
# Print executor name header when switching to a new agent
if current_executor != event.executor_id:
if current_executor is not None:
print() # Newline after previous agent's message
print(f"---------- {event.executor_id} ----------")
current_executor = event.executor_id
if event.data:
print(event.data.text, end="", flush=True)
print() # Final newline after conversation
async def main() -> None:
print("=" * 60)
print("Round-Robin / Sequential Orchestration Comparison")
print("=" * 60)
print("AutoGen: RoundRobinGroupChat")
print("Agent Framework: SequentialBuilder + WorkflowBuilder with cycles\n")
await run_autogen()
print()
await run_agent_framework()
print()
await run_agent_framework_with_cycle()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,128 @@
# Copyright (c) Microsoft. All rights reserved.
"""AutoGen SelectorGroupChat vs Agent Framework GroupChatBuilder.
Demonstrates LLM-based speaker selection where an orchestrator decides
which agent should speak next based on the conversation context.
"""
import asyncio
async def run_autogen() -> None:
"""AutoGen's SelectorGroupChat with LLM-based speaker selection."""
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import MaxMessageTermination
from autogen_agentchat.teams import SelectorGroupChat
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
client = OpenAIChatCompletionClient(model="gpt-4.1-mini")
# Create specialized agents
python_expert = AssistantAgent(
name="python_expert",
model_client=client,
system_message="You are a Python programming expert. Answer Python-related questions.",
description="Expert in Python programming",
model_client_stream=True,
)
javascript_expert = AssistantAgent(
name="javascript_expert",
model_client=client,
system_message="You are a JavaScript programming expert. Answer JavaScript-related questions.",
description="Expert in JavaScript programming",
model_client_stream=True,
)
database_expert = AssistantAgent(
name="database_expert",
model_client=client,
system_message="You are a database expert. Answer SQL and database-related questions.",
description="Expert in databases and SQL",
model_client_stream=True,
)
# Create selector group chat - LLM selects appropriate expert
team = SelectorGroupChat(
participants=[python_expert, javascript_expert, database_expert],
model_client=client,
termination_condition=MaxMessageTermination(2),
selector_prompt="Based on the conversation so far:\n{history}\n, "
"select the most appropriate expert from {roles} to respond next.",
)
# Run with a question that requires expert selection
print("[AutoGen] Selector group chat conversation:")
await Console(team.run_stream(task="How do I connect to a PostgreSQL database using Python?"))
async def run_agent_framework() -> None:
"""Agent Framework's GroupChatBuilder with LLM-based speaker selection."""
from agent_framework import AgentRunUpdateEvent, GroupChatBuilder
from agent_framework.openai import OpenAIChatClient
client = OpenAIChatClient(model_id="gpt-4.1-mini")
# Create specialized agents
python_expert = client.as_agent(
name="python_expert",
instructions="You are a Python programming expert. Answer Python-related questions.",
description="Expert in Python programming",
)
javascript_expert = client.as_agent(
name="javascript_expert",
instructions="You are a JavaScript programming expert. Answer JavaScript-related questions.",
description="Expert in JavaScript programming",
)
database_expert = client.as_agent(
name="database_expert",
instructions="You are a database expert. Answer SQL and database-related questions.",
description="Expert in databases and SQL",
)
workflow = (
GroupChatBuilder()
.participants([python_expert, javascript_expert, database_expert])
.set_manager(
manager=client.as_agent(
name="selector_manager",
instructions="Based on the conversation, select the most appropriate expert to respond next.",
),
display_name="SelectorManager",
)
.with_max_rounds(1)
.build()
)
# Run with a question that requires expert selection
print("[Agent Framework] Group chat conversation:")
current_executor = None
async for event in workflow.run_stream("How do I connect to a PostgreSQL database using Python?"):
if isinstance(event, AgentRunUpdateEvent):
# Print executor name header when switching to a new agent
if current_executor != event.executor_id:
if current_executor is not None:
print() # Newline after previous agent's message
print(f"---------- {event.executor_id} ----------")
current_executor = event.executor_id
if event.data:
print(event.data.text, end="", flush=True)
print() # Final newline after conversation
async def main() -> None:
print("=" * 60)
print("Selector Group Chat Comparison")
print("=" * 60)
print("AutoGen: SelectorGroupChat")
print("Agent Framework: GroupChatBuilder with standard_manager\n")
await run_autogen()
print()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,238 @@
# Copyright (c) Microsoft. All rights reserved.
"""AutoGen Swarm pattern vs Agent Framework HandoffBuilder.
Demonstrates agent handoff coordination where agents can transfer control
to other specialized agents based on the task requirements.
"""
import asyncio
async def run_autogen() -> None:
"""AutoGen's Swarm pattern with human-in-the-loop handoffs."""
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import HandoffTermination, TextMentionTermination
from autogen_agentchat.messages import HandoffMessage
from autogen_agentchat.teams import Swarm
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
client = OpenAIChatCompletionClient(model="gpt-4.1-mini")
# Create triage agent that routes to specialists
triage_agent = AssistantAgent(
name="triage",
model_client=client,
system_message=(
"You are a triage agent. Analyze the user's request and hand off to the appropriate specialist.\n"
"If you need information from the user, first send your message, then handoff to user.\n"
"Use TERMINATE when the issue is fully resolved."
),
handoffs=["billing_agent", "technical_support", "user"],
model_client_stream=True,
)
# Create billing specialist
billing_agent = AssistantAgent(
name="billing_agent",
model_client=client,
system_message=(
"You are a billing specialist. Help with payment and billing questions.\n"
"If you need information from the user, first send your message, then handoff to user.\n"
"When the issue is resolved, handoff to triage to finalize."
),
handoffs=["triage", "user"],
model_client_stream=True,
)
# Create technical support specialist
tech_support = AssistantAgent(
name="technical_support",
model_client=client,
system_message=(
"You are technical support. Help with technical issues.\n"
"If you need information from the user, first send your message, then handoff to user.\n"
"When the issue is resolved, handoff to triage to finalize."
),
handoffs=["triage", "user"],
model_client_stream=True,
)
# Create swarm team with human-in-the-loop termination
termination = HandoffTermination(target="user") | TextMentionTermination("TERMINATE")
team = Swarm(
participants=[triage_agent, billing_agent, tech_support],
termination_condition=termination,
)
# Scripted user responses for demonstration
scripted_responses = [
"I was charged twice for my subscription",
"Yes, the charge of $49.99 appears twice on my credit card statement.",
"Thank you for your help!",
]
response_index = 0
# Run with human-in-the-loop pattern
print("[AutoGen] Swarm handoff conversation:")
task_result = await Console(team.run_stream(task=scripted_responses[response_index]))
last_message = task_result.messages[-1]
response_index += 1
# Continue conversation when agents handoff to user
while (
isinstance(last_message, HandoffMessage)
and last_message.target == "user"
and response_index < len(scripted_responses)
):
user_message = scripted_responses[response_index]
task_result = await Console(
team.run_stream(task=HandoffMessage(source="user", target=last_message.source, content=user_message))
)
last_message = task_result.messages[-1]
response_index += 1
async def run_agent_framework() -> None:
"""Agent Framework's HandoffBuilder for agent coordination."""
from agent_framework import (
AgentRunUpdateEvent,
HandoffBuilder,
HandoffUserInputRequest,
RequestInfoEvent,
WorkflowRunState,
WorkflowStatusEvent,
)
from agent_framework.openai import OpenAIChatClient
client = OpenAIChatClient(model_id="gpt-4.1-mini")
# Create triage agent
triage_agent = client.as_agent(
name="triage",
instructions=(
"You are a triage agent. Analyze the user's request and route to the appropriate specialist:\n"
"- For billing issues: call handoff_to_billing_agent\n"
"- For technical issues: call handoff_to_technical_support"
),
description="Routes requests to appropriate specialists",
)
# Create billing specialist
billing_agent = client.as_agent(
name="billing_agent",
instructions="You are a billing specialist. Help with payment and billing questions. Provide clear assistance.",
description="Handles billing and payment questions",
)
# Create technical support specialist
tech_support = client.as_agent(
name="technical_support",
instructions="You are technical support. Help with technical issues. Provide clear assistance.",
description="Handles technical support questions",
)
# Create handoff workflow - simpler configuration
# After specialists respond, control returns to user (via triage as coordinator)
workflow = (
HandoffBuilder(
name="support_handoff",
participants=[triage_agent, billing_agent, tech_support],
)
.set_coordinator(triage_agent)
.add_handoff(triage_agent, [billing_agent, tech_support])
.with_termination_condition(lambda conv: sum(1 for msg in conv if msg.role.value == "user") > 3)
.build()
)
# Scripted user responses
scripted_responses = [
"I was charged twice for my subscription",
"Yes, the charge of $49.99 appears twice on my credit card statement.",
"Thank you for your help!",
]
# Run with initial message
print("[Agent Framework] Handoff conversation:")
print("---------- user ----------")
print(scripted_responses[0])
current_executor = None
stream_line_open = False
pending_requests: list[RequestInfoEvent] = []
async for event in workflow.run_stream(scripted_responses[0]):
if isinstance(event, AgentRunUpdateEvent):
# Print executor name header when switching to a new agent
if current_executor != event.executor_id:
if stream_line_open:
print()
stream_line_open = False
print(f"---------- {event.executor_id} ----------")
current_executor = event.executor_id
stream_line_open = True
if event.data:
print(event.data.text, end="", flush=True)
elif isinstance(event, RequestInfoEvent):
if isinstance(event.data, HandoffUserInputRequest):
pending_requests.append(event)
elif isinstance(event, WorkflowStatusEvent):
if event.state in {WorkflowRunState.IDLE_WITH_PENDING_REQUESTS} and stream_line_open:
print()
stream_line_open = False
# Process scripted responses
response_index = 1
while pending_requests and response_index < len(scripted_responses):
user_response = scripted_responses[response_index]
print("---------- user ----------")
print(user_response)
responses = {req.request_id: user_response for req in pending_requests}
pending_requests = []
current_executor = None
stream_line_open = False
async for event in workflow.send_responses_streaming(responses):
if isinstance(event, AgentRunUpdateEvent):
# Print executor name header when switching to a new agent
if current_executor != event.executor_id:
if stream_line_open:
print()
stream_line_open = False
print(f"---------- {event.executor_id} ----------")
current_executor = event.executor_id
stream_line_open = True
if event.data:
print(event.data.text, end="", flush=True)
elif isinstance(event, RequestInfoEvent):
if isinstance(event.data, HandoffUserInputRequest):
pending_requests.append(event)
elif isinstance(event, WorkflowStatusEvent):
if (
event.state in {WorkflowRunState.IDLE_WITH_PENDING_REQUESTS, WorkflowRunState.IDLE}
and stream_line_open
):
print()
stream_line_open = False
response_index += 1
if stream_line_open:
print()
print() # Final newline after conversation
async def main() -> None:
print("=" * 60)
print("Swarm / Handoff Pattern Comparison")
print("=" * 60)
print("AutoGen: Swarm with handoffs")
print("Agent Framework: HandoffBuilder\n")
await run_autogen()
print()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,153 @@
# Copyright (c) Microsoft. All rights reserved.
"""AutoGen MagenticOneGroupChat vs Agent Framework MagenticBuilder.
Demonstrates orchestrated multi-agent workflows with a central coordinator
managing specialized agents for complex tasks.
"""
import asyncio
async def run_autogen() -> None:
"""AutoGen's MagenticOneGroupChat for orchestrated collaboration."""
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import MagenticOneGroupChat
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
client = OpenAIChatCompletionClient(model="gpt-4.1-mini")
# Create specialized agents
researcher = AssistantAgent(
name="researcher",
model_client=client,
system_message="You are a research analyst. Gather and analyze information.",
description="Research analyst for data gathering",
model_client_stream=True,
)
coder = AssistantAgent(
name="coder",
model_client=client,
system_message="You are a programmer. Write code based on requirements.",
description="Software developer for implementation",
model_client_stream=True,
)
reviewer = AssistantAgent(
name="reviewer",
model_client=client,
system_message="You are a code reviewer. Review code for quality and correctness.",
description="Code reviewer for quality assurance",
model_client_stream=True,
)
# Create MagenticOne team with coordinator
team = MagenticOneGroupChat(
participants=[researcher, coder, reviewer],
model_client=client, # Coordinator uses this client
max_turns=20,
max_stalls=3,
)
# Run complex task and display the conversation
print("[AutoGen] Magentic One conversation:")
await Console(team.run_stream(task="Research Python async patterns and write a simple example"))
async def run_agent_framework() -> None:
"""Agent Framework's MagenticBuilder for orchestrated collaboration."""
from agent_framework import (
MagenticAgentDeltaEvent,
MagenticAgentMessageEvent,
MagenticBuilder,
MagenticFinalResultEvent,
MagenticOrchestratorMessageEvent,
)
from agent_framework.openai import OpenAIChatClient
client = OpenAIChatClient(model_id="gpt-4.1-mini")
# Create specialized agents
researcher = client.as_agent(
name="researcher",
instructions="You are a research analyst. Gather and analyze information.",
description="Research analyst for data gathering",
)
coder = client.as_agent(
name="coder",
instructions="You are a programmer. Write code based on requirements.",
description="Software developer for implementation",
)
reviewer = client.as_agent(
name="reviewer",
instructions="You are a code reviewer. Review code for quality and correctness.",
description="Code reviewer for quality assurance",
)
# Create Magentic workflow
workflow = (
MagenticBuilder()
.participants(researcher=researcher, coder=coder, reviewer=reviewer)
.with_standard_manager(
chat_client=client,
max_round_count=20,
max_stall_count=3,
max_reset_count=1,
)
.build()
)
# Run complex task
print("[Agent Framework] Magentic conversation:")
last_stream_agent_id: str | None = None
stream_line_open: bool = False
async for event in workflow.run_stream("Research Python async patterns and write a simple example"):
if isinstance(event, MagenticOrchestratorMessageEvent):
if stream_line_open:
print()
stream_line_open = False
print(f"---------- Orchestrator:{event.kind} ----------")
print(getattr(event.message, "text", ""))
elif isinstance(event, MagenticAgentDeltaEvent):
if last_stream_agent_id != event.agent_id or not stream_line_open:
if stream_line_open:
print()
print(f"---------- {event.agent_id} ----------")
last_stream_agent_id = event.agent_id
stream_line_open = True
if event.text:
print(event.text, end="", flush=True)
elif isinstance(event, MagenticAgentMessageEvent):
if stream_line_open:
print()
stream_line_open = False
elif isinstance(event, MagenticFinalResultEvent):
if stream_line_open:
print()
stream_line_open = False
print("---------- Final Result ----------")
if event.message is not None:
print(event.message.text)
if stream_line_open:
print()
print() # Final newline after conversation
async def main() -> None:
print("=" * 60)
print("Magentic One Orchestration Comparison")
print("=" * 60)
print("AutoGen: MagenticOneGroupChat")
print("Agent Framework: MagenticBuilder\n")
await run_autogen()
print()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,5 @@
{
"exclude": [
"autogen"
]
}

View File

@@ -0,0 +1,56 @@
# Copyright (c) Microsoft. All rights reserved.
"""Basic AutoGen AssistantAgent vs Agent Framework ChatAgent.
Both samples expect OpenAI-compatible environment variables (OPENAI_API_KEY or
Azure OpenAI configuration). Update the prompts or client wiring to match your
model of choice before running.
"""
import asyncio
async def run_autogen() -> None:
"""Call AutoGen's AssistantAgent for a simple question."""
from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.openai import OpenAIChatCompletionClient
# AutoGen agent with OpenAI model client
client = OpenAIChatCompletionClient(model="gpt-4.1-mini")
agent = AssistantAgent(
name="assistant",
model_client=client,
system_message="You are a helpful assistant. Answer in one sentence.",
)
# Run the agent (AutoGen maintains conversation state internally)
result = await agent.run(task="What is the capital of France?")
print("[AutoGen]", result.messages[-1].to_text())
async def run_agent_framework() -> None:
"""Call Agent Framework's ChatAgent created from OpenAIChatClient."""
from agent_framework.openai import OpenAIChatClient
# AF constructs a lightweight ChatAgent backed by OpenAIChatClient
client = OpenAIChatClient(model_id="gpt-4.1-mini")
agent = client.as_agent(
name="assistant",
instructions="You are a helpful assistant. Answer in one sentence.",
)
# Run the agent (AF agents are stateless by default)
result = await agent.run("What is the capital of France?")
print("[Agent Framework]", result.text)
async def main() -> None:
print("=" * 60)
print("Basic Assistant Agent Comparison")
print("=" * 60)
await run_autogen()
print()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,89 @@
# Copyright (c) Microsoft. All rights reserved.
"""AutoGen AssistantAgent vs Agent Framework ChatAgent with function tools.
Demonstrates how to create and attach tools to agents in both frameworks.
"""
import asyncio
async def run_autogen() -> None:
"""AutoGen agent with a FunctionTool."""
from autogen_agentchat.agents import AssistantAgent
from autogen_core.tools import FunctionTool
from autogen_ext.models.openai import OpenAIChatCompletionClient
# Define a simple tool function
def get_weather(location: str) -> str:
"""Get the weather for a location.
Args:
location: The city name or location.
Returns:
A weather description.
"""
return f"The weather in {location} is sunny and 72°F."
# Wrap function in FunctionTool
weather_tool = FunctionTool(
func=get_weather,
description="Get weather information for a location",
)
# Create agent with tool
client = OpenAIChatCompletionClient(model="gpt-4.1-mini")
agent = AssistantAgent(
name="assistant",
model_client=client,
tools=[weather_tool],
system_message="You are a helpful assistant. Use available tools to answer questions.",
)
# Run with tool usage
result = await agent.run(task="What's the weather in Seattle?")
print("[AutoGen]", result.messages[-1].to_text())
async def run_agent_framework() -> None:
"""Agent Framework agent with @ai_function decorator."""
from agent_framework import ai_function
from agent_framework.openai import OpenAIChatClient
# Define tool with @ai_function decorator (automatic schema inference)
@ai_function
def get_weather(location: str) -> str:
"""Get the weather for a location.
Args:
location: The city name or location.
Returns:
A weather description.
"""
return f"The weather in {location} is sunny and 72°F."
# Create agent with tool
client = OpenAIChatClient(model_id="gpt-4.1-mini")
agent = client.as_agent(
name="assistant",
instructions="You are a helpful assistant. Use available tools to answer questions.",
tools=[get_weather],
)
# Run with tool usage
result = await agent.run("What's the weather in Seattle?")
print("[Agent Framework]", result.text)
async def main() -> None:
print("=" * 60)
print("Assistant Agent with Tools Comparison")
print("=" * 60)
await run_autogen()
print()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,79 @@
# Copyright (c) Microsoft. All rights reserved.
"""AutoGen vs Agent Framework: Thread management and streaming responses.
Demonstrates conversation state management and streaming in both frameworks.
"""
import asyncio
async def run_autogen() -> None:
"""AutoGen agent with conversation history and streaming."""
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
client = OpenAIChatCompletionClient(model="gpt-4.1-mini")
agent = AssistantAgent(
name="assistant",
model_client=client,
system_message="You are a helpful math tutor.",
model_client_stream=True,
)
print("[AutoGen] Conversation with history:")
# First turn - AutoGen maintains state internally with Console for streaming
result = await agent.run(task="What is 15 + 27?")
print(f" Q1: {result.messages[-1].to_text()}")
# Second turn - agent remembers context
result = await agent.run(task="What about that number times 2?")
print(f" Q2: {result.messages[-1].to_text()}")
print("\n[AutoGen] Streaming response:")
# Stream response with Console for token streaming
await Console(agent.run_stream(task="Count from 1 to 5"))
async def run_agent_framework() -> None:
"""Agent Framework agent with explicit thread and streaming."""
from agent_framework.openai import OpenAIChatClient
client = OpenAIChatClient(model_id="gpt-4.1-mini")
agent = client.as_agent(
name="assistant",
instructions="You are a helpful math tutor.",
)
print("[Agent Framework] Conversation with thread:")
# Create a thread to maintain state
thread = agent.get_new_thread()
# First turn - pass thread to maintain history
result1 = await agent.run("What is 15 + 27?", thread=thread)
print(f" Q1: {result1.text}")
# Second turn - agent remembers context via thread
result2 = await agent.run("What about that number times 2?", thread=thread)
print(f" Q2: {result2.text}")
print("\n[Agent Framework] Streaming response:")
# Stream response
print(" ", end="")
async for chunk in agent.run_stream("Count from 1 to 5"):
if chunk.text:
print(chunk.text, end="", flush=True)
print()
async def main() -> None:
print("=" * 60)
print("Thread Management and Streaming Comparison")
print("=" * 60)
await run_autogen()
print()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,130 @@
# Copyright (c) Microsoft. All rights reserved.
"""AutoGen vs Agent Framework: Agent-as-a-Tool pattern.
Demonstrates hierarchical agent architectures where one agent delegates
work to specialized sub-agents wrapped as tools.
"""
import asyncio
async def run_autogen() -> None:
"""AutoGen's AgentTool for hierarchical agents with streaming."""
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.tools import AgentTool
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
# Create a specialized writer agent
writer_client = OpenAIChatCompletionClient(model="gpt-4.1-mini")
writer = AssistantAgent(
name="writer",
model_client=writer_client,
system_message="You are a creative writer. Write short, engaging content.",
model_client_stream=True,
)
# Wrap writer agent as a tool (description is taken from agent.description)
writer_tool = AgentTool(agent=writer)
# Create coordinator agent with writer as a tool
# IMPORTANT: Disable parallel_tool_calls when using AgentTool
coordinator_client = OpenAIChatCompletionClient(
model="gpt-4.1-mini",
parallel_tool_calls=False,
)
coordinator = AssistantAgent(
name="coordinator",
model_client=coordinator_client,
tools=[writer_tool],
system_message="You coordinate with specialized agents. Delegate writing tasks to the writer agent.",
model_client_stream=True,
)
# Run coordinator with streaming - it will delegate to writer
print("[AutoGen]")
await Console(coordinator.run_stream(task="Create a tagline for a coffee shop"))
async def run_agent_framework() -> None:
"""Agent Framework's as_tool() for hierarchical agents with streaming."""
from agent_framework import FunctionCallContent, FunctionResultContent
from agent_framework.openai import OpenAIChatClient
client = OpenAIChatClient(model_id="gpt-4.1-mini")
# Create specialized writer agent
writer = client.as_agent(
name="writer",
instructions="You are a creative writer. Write short, engaging content.",
)
# Convert writer to a tool using as_tool()
writer_tool = writer.as_tool(
name="creative_writer",
description="Generate creative content",
arg_name="request",
arg_description="What to write",
)
# Create coordinator agent with writer tool
coordinator = client.as_agent(
name="coordinator",
instructions="You coordinate with specialized agents. Delegate writing tasks to the writer agent.",
tools=[writer_tool],
)
# Run coordinator with streaming - it will delegate to writer
print("[Agent Framework]")
# Track accumulated function calls (they stream in incrementally)
accumulated_calls: dict[str, FunctionCallContent] = {}
async for chunk in coordinator.run_stream("Create a tagline for a coffee shop"):
# Stream text tokens
if chunk.text:
print(chunk.text, end="", flush=True)
# Process streaming function calls and results
if chunk.contents:
for content in chunk.contents:
if isinstance(content, FunctionCallContent):
# Accumulate function call content as it streams in
call_id = content.call_id
if call_id in accumulated_calls:
# Add to existing call (arguments stream in gradually)
accumulated_calls[call_id] = accumulated_calls[call_id] + content
else:
# First chunk of this function call
accumulated_calls[call_id] = content
print("\n[Function Call - streaming]", flush=True)
print(f" Call ID: {call_id}", flush=True)
print(f" Name: {content.name}", flush=True)
# Show accumulated arguments so far
current_args = accumulated_calls[call_id].arguments
print(f" Arguments: {current_args}", flush=True)
elif isinstance(content, FunctionResultContent):
# Tool result - shows writer's response
result_text = content.result if isinstance(content.result, str) else str(content.result)
if result_text.strip():
print("\n[Function Result]", flush=True)
print(f" Call ID: {content.call_id}", flush=True)
print(f" Result: {result_text[:150]}{'...' if len(result_text) > 150 else ''}", flush=True)
print()
async def main() -> None:
print("=" * 60)
print("Agent-as-Tool Pattern Comparison")
print("=" * 60)
print("Note: AutoGen requires parallel_tool_calls=False for AgentTool")
print(" Agent Framework handles this automatically\n")
await run_autogen()
print()
await run_agent_framework()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,4 @@
*.db
*.db-shm
*.db-wal
uploads/

View File

@@ -0,0 +1,318 @@
# ChatKit Integration Sample with Weather Agent and Image Analysis
This sample demonstrates how to integrate Microsoft Agent Framework with OpenAI ChatKit. It provides a complete implementation of a weather assistant with interactive widget visualization, image analysis, and file upload support.
**Features:**
- Weather information with interactive widgets
- Image analysis using vision models
- Current time queries
- File upload with attachment storage
- Chat interface with streaming responses
- City selector widget with one-click weather
## Architecture
```mermaid
graph TB
subgraph Frontend["React Frontend (ChatKit UI)"]
UI[ChatKit Components]
Upload[File Upload]
end
subgraph Backend["FastAPI Server"]
FastAPI[FastAPI Endpoints]
subgraph ChatKit["WeatherChatKitServer"]
Respond[respond method]
Action[action method]
end
subgraph Stores["Data & Storage Layer"]
SQLite[SQLiteStore<br/>Store Protocol]
AttStore[FileBasedAttachmentStore<br/>AttachmentStore Protocol]
DB[(SQLite DB<br/>chatkit_demo.db)]
Files[/uploads directory/]
end
subgraph Integration["Agent Framework Integration"]
Converter[ThreadItemConverter]
Streamer[stream_agent_response]
Agent[ChatAgent]
end
Widgets[Widget Rendering<br/>render_weather_widget<br/>render_city_selector_widget]
end
subgraph Azure["Azure AI"]
Foundry[GPT-5<br/>with Vision]
end
UI -->|HTTP POST /chatkit| FastAPI
Upload -->|HTTP POST /upload/id| FastAPI
FastAPI --> ChatKit
ChatKit -->|save/load threads| SQLite
ChatKit -->|save/load attachments| AttStore
ChatKit -->|convert messages| Converter
SQLite -.->|persist| DB
AttStore -.->|save files| Files
AttStore -.->|save metadata| SQLite
Converter -->|ChatMessage array| Agent
Agent -->|AgentResponseUpdate| Streamer
Streamer -->|ThreadStreamEvent| ChatKit
ChatKit --> Widgets
Widgets -->|WidgetItem| ChatKit
Agent <-->|Chat Completions API| Foundry
ChatKit -->|ThreadStreamEvent| FastAPI
FastAPI -->|SSE Stream| UI
style ChatKit fill:#e1f5ff
style Stores fill:#fff4e1
style Integration fill:#f0e1ff
style Azure fill:#e1ffe1
```
### Server Implementation
The sample implements a ChatKit server using the `ChatKitServer` base class from the `chatkit` package:
**Core Components:**
- **`WeatherChatKitServer`**: Custom ChatKit server implementation that:
- Extends `ChatKitServer[dict[str, Any]]`
- Uses Agent Framework's `ChatAgent` with Azure OpenAI
- Converts ChatKit messages to Agent Framework format using `ThreadItemConverter`
- Streams responses back to ChatKit using `stream_agent_response`
- Creates and streams interactive widgets after agent responses
- **`SQLiteStore`**: Data persistence layer that:
- Implements the `Store[dict[str, Any]]` protocol from ChatKit
- Persists threads, messages, and attachment metadata in SQLite
- Provides thread management and item history
- Stores attachment metadata for the upload lifecycle
- **`FileBasedAttachmentStore`**: File storage implementation that:
- Implements the `AttachmentStore[dict[str, Any]]` protocol from ChatKit
- Stores uploaded files on the local filesystem (in `./uploads` directory)
- Generates upload URLs for two-phase file upload
- Saves attachment metadata to the data store for upload tracking
- Provides preview URLs for images
**Key Integration Points:**
```python
# Converting ChatKit messages to Agent Framework
converter = ThreadItemConverter(
attachment_data_fetcher=self._fetch_attachment_data
)
agent_messages = await converter.to_agent_input(user_message_item)
# Running agent and streaming back to ChatKit
async for event in stream_agent_response(
self.weather_agent.run_stream(agent_messages),
thread_id=thread.id,
):
yield event
# Streaming widgets
widget = render_weather_widget(weather_data)
async for event in stream_widget(thread_id=thread.id, widget=widget):
yield event
```
## Installation and Setup
### Prerequisites
- Python 3.10+
- Node.js 18.18+ and npm 9+
- Azure OpenAI service configured
- Azure CLI for authentication (`az login`)
### Network Requirements
> **Important:** This sample uses the OpenAI ChatKit frontend, which requires internet connectivity to OpenAI services.
The frontend makes outbound requests to:
- `cdn.platform.openai.com` - ChatKit UI library (required)
- `chatgpt.com` - Configuration endpoint
- `api-js.mixpanel.com` - Telemetry
**This sample is not suitable for air-gapped or network-restricted environments.** The ChatKit frontend library cannot be self-hosted. See [Limitations](#limitations) for details.
### Domain Key Configuration
For **local development**, the sample uses a default domain key (`domain_pk_localhost_dev`).
For **production deployment**:
1. Register your domain at [platform.openai.com](https://platform.openai.com/settings/organization/security/domain-allowlist)
2. Create a `.env` file in the `frontend` directory:
```
VITE_CHATKIT_API_DOMAIN_KEY=your_domain_key_here
```
### Backend Setup
1. **Install Python packages:**
```bash
cd python/samples/demos/chatkit-integration
pip install agent-framework-chatkit fastapi uvicorn azure-identity
```
2. **Configure Azure OpenAI:**
```bash
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
export AZURE_OPENAI_API_VERSION="2024-06-01"
export AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="gpt-4o"
```
3. **Authenticate with Azure:**
```bash
az login
```
### Frontend Setup
Install the Node.js dependencies:
```bash
cd frontend
npm install
```
## How to Run
### Start the Backend Server
From the `chatkit-integration` directory:
```bash
python app.py
```
Or with auto-reload for development:
```bash
uvicorn app:app --host 127.0.0.1 --port 8001 --reload
```
The backend will start on `http://localhost:8001`
### Start the Frontend Development Server
In a new terminal, from the `frontend` directory:
```bash
npm run dev
```
The frontend will start on `http://localhost:5171`
### Access the Application
Open your browser and navigate to:
```
http://localhost:5171
```
You can now:
- Ask about weather in any location (weather widgets display automatically)
- Upload images for analysis using the attachment button
- Get the current time
- Ask to see available cities and click city buttons for instant weather
### Project Structure
```
chatkit-integration/
├── app.py # FastAPI backend with ChatKitServer implementation
├── store.py # SQLiteStore implementation
├── attachment_store.py # FileBasedAttachmentStore implementation
├── weather_widget.py # Widget rendering functions
├── chatkit_demo.db # SQLite database (auto-created)
├── uploads/ # Uploaded files directory (auto-created)
└── frontend/
├── package.json
├── vite.config.ts
├── index.html
└── src/
├── main.tsx
└── App.tsx # ChatKit UI integration
```
### Configuration
You can customize the application by editing constants at the top of `app.py`:
```python
# Server configuration
SERVER_HOST = "127.0.0.1" # Bind to localhost only for security (local dev)
SERVER_PORT = 8001
SERVER_BASE_URL = f"http://localhost:{SERVER_PORT}"
# Database configuration
DATABASE_PATH = "chatkit_demo.db"
# File storage configuration
UPLOADS_DIRECTORY = "./uploads"
# User context
DEFAULT_USER_ID = "demo_user"
```
### Sample Conversations
Try these example queries:
- "What's the weather like in Tokyo?"
- "Show me available cities" (displays interactive city selector)
- "What's the current time?"
- Upload an image and ask "What do you see in this image?"
## Limitations
### Air-Gapped / Regulated Environments
The ChatKit frontend (`chatkit.js`) is loaded from OpenAI's CDN and cannot be self-hosted. This means:
- **Not suitable for air-gapped environments** where `*.openai.com` is blocked
- **Not suitable for regulated environments** that prohibit external telemetry
- **Requires domain registration** with OpenAI for production use
**What you CAN self-host:**
- The Python backend (FastAPI server, `ChatKitServer`, stores)
- The `agent-framework-chatkit` integration layer
- Your LLM infrastructure (Azure OpenAI, local models, etc.)
**What you CANNOT self-host:**
- The ChatKit frontend UI library
For more details, see:
- [openai/chatkit-js#57](https://github.com/openai/chatkit-js/issues/57) - Self-hosting feature request
- [openai/chatkit-js#76](https://github.com/openai/chatkit-js/issues/76) - Domain key requirements
## Learn More
- [Agent Framework Documentation](https://aka.ms/agent-framework)
- [ChatKit Documentation](https://platform.openai.com/docs/guides/chatkit)
- [Azure OpenAI Documentation](https://learn.microsoft.com/en-us/azure/ai-foundry/)

View File

@@ -0,0 +1 @@
# Copyright (c) Microsoft. All rights reserved.

View File

@@ -0,0 +1,631 @@
# Copyright (c) Microsoft. All rights reserved.
"""
ChatKit Integration Sample with Weather Agent and Image Analysis
This sample demonstrates how to integrate Microsoft Agent Framework with OpenAI ChatKit
using a weather tool with widget visualization, image analysis, and Azure OpenAI. It shows
a complete ChatKit server implementation using Agent Framework agents with proper FastAPI
setup, interactive weather widgets, and vision capabilities for analyzing uploaded images.
"""
import logging
from collections.abc import AsyncIterator, Callable
from datetime import datetime, timezone
from random import randint
from typing import Annotated, Any
import uvicorn
# Agent Framework imports
from agent_framework import AgentResponseUpdate, ChatAgent, ChatMessage, FunctionResultContent, Role
from agent_framework.azure import AzureOpenAIChatClient
# Agent Framework ChatKit integration
from agent_framework_chatkit import ThreadItemConverter, stream_agent_response
# Local imports
from attachment_store import FileBasedAttachmentStore
from azure.identity import AzureCliCredential
# ChatKit imports
from chatkit.actions import Action
from chatkit.server import ChatKitServer
from chatkit.store import StoreItemType, default_generate_id
from chatkit.types import (
ThreadItem,
ThreadItemDoneEvent,
ThreadMetadata,
ThreadStreamEvent,
UserMessageItem,
WidgetItem,
)
from chatkit.widgets import WidgetRoot
from fastapi import FastAPI, File, Request, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse, Response, StreamingResponse
from pydantic import Field
from store import SQLiteStore
from weather_widget import (
WeatherData,
city_selector_copy_text,
render_city_selector_widget,
render_weather_widget,
weather_widget_copy_text,
)
# ============================================================================
# Configuration Constants
# ============================================================================
# Server configuration
SERVER_HOST = "127.0.0.1" # Bind to localhost only for security (local dev)
SERVER_PORT = 8001
SERVER_BASE_URL = f"http://localhost:{SERVER_PORT}"
# Database configuration
DATABASE_PATH = "chatkit_demo.db"
# File storage configuration
UPLOADS_DIRECTORY = "./uploads"
# User context
DEFAULT_USER_ID = "demo_user"
# Logging configuration
LOG_LEVEL = logging.INFO
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
# ============================================================================
# Logging Setup
# ============================================================================
logging.basicConfig(
level=LOG_LEVEL,
format=LOG_FORMAT,
datefmt=LOG_DATE_FORMAT,
)
logger = logging.getLogger(__name__)
class WeatherResponse(str):
"""A string response that also carries WeatherData for widget creation."""
def __new__(cls, text: str, weather_data: WeatherData):
instance = super().__new__(cls, text)
instance.weather_data = weather_data # type: ignore
return instance
async def stream_widget(
thread_id: str,
widget: WidgetRoot,
copy_text: str | None = None,
generate_id: Callable[[StoreItemType], str] = default_generate_id,
) -> AsyncIterator[ThreadStreamEvent]:
"""Stream a ChatKit widget as a ThreadStreamEvent.
This helper function creates a ChatKit widget item and yields it as a
ThreadItemDoneEvent that can be consumed by the ChatKit UI.
Args:
thread_id: The ChatKit thread ID for the conversation.
widget: The ChatKit widget to display.
copy_text: Optional text representation of the widget for copy/paste.
generate_id: Optional function to generate IDs for ChatKit items.
Yields:
ThreadStreamEvent: ChatKit event containing the widget.
"""
item_id = generate_id("message")
widget_item = WidgetItem(
id=item_id,
thread_id=thread_id,
created_at=datetime.now(),
widget=widget,
copy_text=copy_text,
)
yield ThreadItemDoneEvent(type="thread.item.done", item=widget_item)
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location.
Returns a string description with embedded WeatherData for widget creation.
"""
logger.info(f"Fetching weather for location: {location}")
conditions = ["sunny", "cloudy", "rainy", "stormy", "snowy", "foggy"]
temperature = randint(-5, 35)
condition = conditions[randint(0, len(conditions) - 1)]
# Add some realistic details
humidity = randint(30, 90)
wind_speed = randint(5, 25)
weather_data = WeatherData(
location=location,
condition=condition,
temperature=temperature,
humidity=humidity,
wind_speed=wind_speed,
)
logger.debug(f"Weather data generated: {condition}, {temperature}°C, {humidity}% humidity, {wind_speed} km/h wind")
# Return a WeatherResponse that is both a string (for the LLM) and carries structured data
text = (
f"Weather in {location}:\n"
f"• Condition: {condition.title()}\n"
f"• Temperature: {temperature}°C\n"
f"• Humidity: {humidity}%\n"
f"• Wind: {wind_speed} km/h"
)
return WeatherResponse(text, weather_data)
def get_time() -> str:
"""Get the current UTC time."""
current_time = datetime.now(timezone.utc)
logger.info("Getting current UTC time")
return f"Current UTC time: {current_time.strftime('%Y-%m-%d %H:%M:%S')} UTC"
def show_city_selector() -> str:
"""Show an interactive city selector widget to the user.
This function triggers the display of a widget that allows users
to select from popular cities to get weather information.
Returns a special marker string that will be detected to show the widget.
"""
logger.info("Activating city selector widget")
return "__SHOW_CITY_SELECTOR__"
class WeatherChatKitServer(ChatKitServer[dict[str, Any]]):
"""ChatKit server implementation using Agent Framework.
This server integrates Agent Framework agents with ChatKit's server protocol,
providing weather information with interactive widgets and time queries through Azure OpenAI.
"""
def __init__(self, data_store: SQLiteStore, attachment_store: FileBasedAttachmentStore):
super().__init__(data_store, attachment_store)
logger.info("Initializing WeatherChatKitServer")
# Create Agent Framework agent with Azure OpenAI
# For authentication, run `az login` command in terminal
try:
self.weather_agent = ChatAgent(
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
instructions=(
"You are a helpful weather assistant with image analysis capabilities. "
"You can provide weather information for any location, tell the current time, "
"and analyze images that users upload. Be friendly and informative in your responses.\n\n"
"If a user asks to see a list of cities or wants to choose from available cities, "
"use the show_city_selector tool to display an interactive city selector.\n\n"
"When users upload images, you will automatically receive them and can analyze their content. "
"Describe what you see in detail and be helpful in answering questions about the images."
),
tools=[get_weather, get_time, show_city_selector],
)
logger.info("Weather agent initialized successfully with Azure OpenAI")
except Exception as e:
logger.error(f"Failed to initialize weather agent: {e}")
raise
# Create ThreadItemConverter with attachment data fetcher
self.converter = ThreadItemConverter(
attachment_data_fetcher=self._fetch_attachment_data,
)
logger.info("WeatherChatKitServer initialized")
async def _fetch_attachment_data(self, attachment_id: str) -> bytes:
"""Fetch attachment binary data for the converter.
Args:
attachment_id: The ID of the attachment to fetch.
Returns:
The binary data of the attachment.
"""
return await attachment_store.read_attachment_bytes(attachment_id)
async def _update_thread_title(
self, thread: ThreadMetadata, thread_items: list[ThreadItem], context: dict[str, Any]
) -> None:
"""Update thread title using LLM to generate a concise summary.
Args:
thread: The thread metadata to update.
thread_items: All items in the thread.
context: The context dictionary.
"""
logger.info(f"Attempting to update thread title for thread: {thread.id}")
if not thread_items:
logger.debug("No thread items available for title generation")
return
# Collect user messages to understand the conversation topic
user_messages: list[str] = []
for item in thread_items:
if isinstance(item, UserMessageItem) and item.content:
for content_part in item.content:
if hasattr(content_part, "text") and isinstance(content_part.text, str):
user_messages.append(content_part.text)
break
if not user_messages:
logger.debug("No user messages found for title generation")
return
logger.debug(f"Found {len(user_messages)} user message(s) for title generation")
try:
# Use the agent's chat client to generate a concise title
# Combine first few messages to capture the conversation topic
conversation_context = "\n".join(user_messages[:3])
title_prompt = [
ChatMessage(
role=Role.USER,
text=(
f"Generate a very short, concise title (max 40 characters) for a conversation "
f"that starts with:\n\n{conversation_context}\n\n"
"Respond with ONLY the title, nothing else."
),
)
]
# Use the chat client directly for a quick, lightweight call
response = await self.weather_agent.chat_client.get_response(
messages=title_prompt,
options={
"temperature": 0.3,
"max_tokens": 20,
},
)
if response.messages and response.messages[-1].text:
title = response.messages[-1].text.strip().strip('"').strip("'")
# Ensure it's not too long
if len(title) > 50:
title = title[:47] + "..."
thread.title = title
await self.store.save_thread(thread, context)
logger.info(f"Updated thread {thread.id} title to: {title}")
except Exception as e:
logger.warning(f"Failed to generate thread title, using fallback: {e}")
# Fallback to simple truncation
first_message: str = user_messages[0]
title: str = first_message[:50].strip()
if len(first_message) > 50:
title += "..."
thread.title = title
await self.store.save_thread(thread, context)
logger.info(f"Updated thread {thread.id} title to (fallback): {title}")
async def respond(
self,
thread: ThreadMetadata,
input_user_message: UserMessageItem | None,
context: dict[str, Any],
) -> AsyncIterator[ThreadStreamEvent]:
"""Handle incoming user messages and generate responses.
This method converts ChatKit messages to Agent Framework format using ThreadItemConverter,
runs the agent, converts the response back to ChatKit events using stream_agent_response,
and creates interactive weather widgets when weather data is queried.
"""
from agent_framework import FunctionResultContent
if input_user_message is None:
logger.debug("Received None user message, skipping")
return
logger.info(f"Processing message for thread: {thread.id}")
try:
# Track weather data and city selector flag for this request
weather_data: WeatherData | None = None
show_city_selector = False
# Load full thread history from the store
thread_items_page = await self.store.load_thread_items(
thread_id=thread.id,
after=None,
limit=1000,
order="asc",
context=context,
)
thread_items = thread_items_page.data
# Convert ALL thread items to Agent Framework ChatMessages using ThreadItemConverter
# This ensures the agent has the full conversation context
agent_messages = await self.converter.to_agent_input(thread_items)
if not agent_messages:
logger.warning("No messages after conversion")
return
logger.info(f"Running agent with {len(agent_messages)} message(s)")
# Run the Agent Framework agent with streaming
agent_stream = self.weather_agent.run_stream(agent_messages)
# Create an intercepting stream that extracts function results while passing through updates
async def intercept_stream() -> AsyncIterator[AgentResponseUpdate]:
nonlocal weather_data, show_city_selector
async for update in agent_stream:
# Check for function results in the update
if update.contents:
for content in update.contents:
if isinstance(content, FunctionResultContent):
result = content.result
# Check if it's a WeatherResponse (string subclass with weather_data attribute)
if isinstance(result, str) and hasattr(result, "weather_data"):
extracted_data = getattr(result, "weather_data", None)
if isinstance(extracted_data, WeatherData):
weather_data = extracted_data
logger.info(f"Weather data extracted: {weather_data.location}")
# Check if it's the city selector marker
elif isinstance(result, str) and result == "__SHOW_CITY_SELECTOR__":
show_city_selector = True
logger.info("City selector flag detected")
yield update
# Stream updates as ChatKit events with interception
async for event in stream_agent_response(
intercept_stream(),
thread_id=thread.id,
):
yield event
# If weather data was collected during the tool call, create a widget
if weather_data is not None and isinstance(weather_data, WeatherData):
logger.info(f"Creating weather widget for location: {weather_data.location}")
# Create weather widget
widget = render_weather_widget(weather_data)
copy_text = weather_widget_copy_text(weather_data)
# Stream the widget
async for widget_event in stream_widget(thread_id=thread.id, widget=widget, copy_text=copy_text):
yield widget_event
logger.debug("Weather widget streamed successfully")
# If city selector should be shown, create and stream that widget
if show_city_selector:
logger.info("Creating city selector widget")
# Create city selector widget
selector_widget = render_city_selector_widget()
selector_copy_text = city_selector_copy_text()
# Stream the widget
async for widget_event in stream_widget(
thread_id=thread.id, widget=selector_widget, copy_text=selector_copy_text
):
yield widget_event
logger.debug("City selector widget streamed successfully")
# Update thread title based on first user message if not already set
if not thread.title or thread.title == "New thread":
await self._update_thread_title(thread, thread_items, context)
logger.info(f"Completed processing message for thread: {thread.id}")
except Exception as e:
logger.error(f"Error processing message for thread {thread.id}: {e}", exc_info=True)
async def action(
self,
thread: ThreadMetadata,
action: Action[str, Any],
sender: WidgetItem | None,
context: dict[str, Any],
) -> AsyncIterator[ThreadStreamEvent]:
"""Handle widget actions from the frontend.
This method processes actions triggered by interactive widgets,
such as city selection from the city selector widget.
"""
logger.info(f"Received action: {action.type} for thread: {thread.id}")
if action.type == "city_selected":
# Extract city information from the action payload
city_label = action.payload.get("city_label", "Unknown")
logger.info(f"City selected: {city_label}")
logger.debug(f"Action payload: {action.payload}")
# Track weather data for this request
weather_data: WeatherData | None = None
# Create an agent message asking about the weather
agent_messages = [ChatMessage(role=Role.USER, text=f"What's the weather in {city_label}?")]
logger.debug(f"Processing weather query: {agent_messages[0].text}")
# Run the Agent Framework agent with streaming
agent_stream = self.weather_agent.run_stream(agent_messages)
# Create an intercepting stream that extracts function results while passing through updates
async def intercept_stream() -> AsyncIterator[AgentResponseUpdate]:
nonlocal weather_data
async for update in agent_stream:
# Check for function results in the update
if update.contents:
for content in update.contents:
if isinstance(content, FunctionResultContent):
result = content.result
# Check if it's a WeatherResponse (string subclass with weather_data attribute)
if isinstance(result, str) and hasattr(result, "weather_data"):
extracted_data = getattr(result, "weather_data", None)
if isinstance(extracted_data, WeatherData):
weather_data = extracted_data
logger.info(f"Weather data extracted: {weather_data.location}")
yield update
# Stream updates as ChatKit events with interception
async for event in stream_agent_response(
intercept_stream(),
thread_id=thread.id,
):
yield event
# If weather data was collected during the tool call, create a widget
if weather_data is not None and isinstance(weather_data, WeatherData):
logger.info(f"Creating weather widget for: {weather_data.location}")
# Create weather widget
widget = render_weather_widget(weather_data)
copy_text = weather_widget_copy_text(weather_data)
# Stream the widget
async for widget_event in stream_widget(thread_id=thread.id, widget=widget, copy_text=copy_text):
yield widget_event
logger.debug("Weather widget created successfully from action")
else:
logger.warning("No weather data available to create widget after action")
# FastAPI application setup
app = FastAPI(
title="ChatKit Weather & Vision Agent",
description="Weather and image analysis assistant powered by Agent Framework and Azure OpenAI",
version="1.0.0",
)
# Add CORS middleware to allow frontend connections
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, specify exact origins
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Initialize data store and ChatKit server
logger.info("Initializing application components")
data_store = SQLiteStore(db_path=DATABASE_PATH)
attachment_store = FileBasedAttachmentStore(
uploads_dir=UPLOADS_DIRECTORY,
base_url=SERVER_BASE_URL,
data_store=data_store,
)
chatkit_server = WeatherChatKitServer(data_store, attachment_store)
logger.info("Application initialization complete")
@app.post("/chatkit")
async def chatkit_endpoint(request: Request):
"""Main ChatKit endpoint that handles all ChatKit requests.
This endpoint follows the ChatKit server protocol and handles both
streaming and non-streaming responses.
"""
logger.debug(f"Received ChatKit request from {request.client}")
request_body = await request.body()
# Create context following the working examples pattern
context = {"request": request}
try:
# Process the request using ChatKit server
result = await chatkit_server.process(request_body, context)
# Return appropriate response type
if hasattr(result, "__aiter__"): # StreamingResult
logger.debug("Returning streaming response")
return StreamingResponse(result, media_type="text/event-stream") # type: ignore[arg-type]
# NonStreamingResult
logger.debug("Returning non-streaming response")
return Response(content=result.json, media_type="application/json") # type: ignore[union-attr]
except Exception as e:
logger.error(f"Error processing ChatKit request: {e}", exc_info=True)
raise
@app.post("/upload/{attachment_id}")
async def upload_file(attachment_id: str, file: UploadFile = File(...)):
"""Handle file upload for two-phase upload.
The client POSTs the file bytes here after creating the attachment
via the ChatKit attachments.create endpoint.
"""
logger.info(f"Receiving file upload for attachment: {attachment_id}")
try:
# Read file contents
contents = await file.read()
# Save to disk
file_path = attachment_store.get_file_path(attachment_id)
file_path.write_bytes(contents)
logger.info(f"Saved {len(contents)} bytes to {file_path}")
# Load the attachment metadata from the data store
attachment = await data_store.load_attachment(attachment_id, {"user_id": DEFAULT_USER_ID})
# Clear the upload_url since upload is complete
attachment.upload_url = None
# Save the updated attachment back to the store
await data_store.save_attachment(attachment, {"user_id": DEFAULT_USER_ID})
# Return the attachment metadata as JSON
return JSONResponse(content=attachment.model_dump(mode="json"))
except Exception as e:
logger.error(f"Error uploading file for attachment {attachment_id}: {e}", exc_info=True)
return JSONResponse(status_code=500, content={"error": "Failed to upload file."})
@app.get("/preview/{attachment_id}")
async def preview_image(attachment_id: str):
"""Serve image preview/thumbnail.
For simplicity, this serves the full image. In production, you should
generate and cache thumbnails.
"""
logger.debug(f"Serving preview for attachment: {attachment_id}")
try:
file_path = attachment_store.get_file_path(attachment_id)
if not file_path.exists():
return JSONResponse(status_code=404, content={"error": "File not found"})
# Determine media type from file extension or attachment metadata
# For simplicity, we'll try to load from the store
try:
attachment = await data_store.load_attachment(attachment_id, {"user_id": DEFAULT_USER_ID})
media_type = attachment.mime_type
except Exception:
# Default to binary if we can't determine
media_type = "application/octet-stream"
return FileResponse(file_path, media_type=media_type)
except Exception as e:
logger.error(f"Error serving preview for attachment {attachment_id}: {e}", exc_info=True)
return JSONResponse(status_code=500, content={"error": "Error serving preview for attachment."})
if __name__ == "__main__":
# Run the server
logger.info(f"Starting ChatKit Weather Agent server on {SERVER_HOST}:{SERVER_PORT}")
uvicorn.run(app, host=SERVER_HOST, port=SERVER_PORT, log_level="info")

View File

@@ -0,0 +1,119 @@
# Copyright (c) Microsoft. All rights reserved.
"""File-based AttachmentStore implementation for ChatKit.
This module provides a simple AttachmentStore implementation that stores
uploaded files on the local filesystem. In production, you should use
cloud storage like S3, Azure Blob Storage, or Google Cloud Storage.
"""
from pathlib import Path
from typing import TYPE_CHECKING, Any
from chatkit.store import AttachmentStore
from chatkit.types import Attachment, AttachmentCreateParams, FileAttachment, ImageAttachment
from pydantic import AnyUrl
if TYPE_CHECKING:
from store import SQLiteStore
class FileBasedAttachmentStore(AttachmentStore[dict[str, Any]]):
"""File-based AttachmentStore that stores files on local disk.
This implementation stores uploaded files in a local directory and provides
upload URLs that point to the FastAPI upload endpoint. It supports both
image and file attachments.
Features:
- Stores files in a local uploads directory
- Generates upload URLs for two-phase upload
- Generates preview URLs for images
- Proper cleanup on deletion
Note: This is for demonstration purposes. In production, use cloud storage
with signed URLs for better security and scalability.
"""
def __init__(
self,
uploads_dir: str = "./uploads",
base_url: str = "http://localhost:8001",
data_store: "SQLiteStore | None" = None,
):
"""Initialize the file-based attachment store.
Args:
uploads_dir: Directory where uploaded files will be stored
base_url: Base URL for generating upload and preview URLs
data_store: Optional data store to persist attachment metadata
"""
self.uploads_dir = Path(uploads_dir)
self.base_url = base_url.rstrip("/")
self.data_store = data_store
# Create uploads directory if it doesn't exist
self.uploads_dir.mkdir(parents=True, exist_ok=True)
def get_file_path(self, attachment_id: str) -> Path:
"""Get the filesystem path for an attachment."""
return self.uploads_dir / attachment_id
async def delete_attachment(self, attachment_id: str, context: dict[str, Any]) -> None:
"""Delete an attachment and its file from disk."""
file_path = self.get_file_path(attachment_id)
if file_path.exists():
file_path.unlink()
async def create_attachment(self, input: AttachmentCreateParams, context: dict[str, Any]) -> Attachment:
"""Create an attachment with upload URL for two-phase upload.
This creates the attachment metadata and returns upload URLs that
the client will use to POST the actual file bytes.
"""
# Generate unique ID for this attachment
attachment_id = self.generate_attachment_id(input.mime_type, context)
# Generate upload URL that points to our FastAPI upload endpoint
upload_url = f"{self.base_url}/upload/{attachment_id}"
# Create appropriate attachment type based on MIME type
if input.mime_type.startswith("image/"):
# For images, also provide a preview URL
preview_url = f"{self.base_url}/preview/{attachment_id}"
attachment = ImageAttachment(
id=attachment_id,
type="image",
mime_type=input.mime_type,
name=input.name,
upload_url=AnyUrl(upload_url),
preview_url=AnyUrl(preview_url),
)
else:
# For files, just provide upload URL
attachment = FileAttachment(
id=attachment_id,
type="file",
mime_type=input.mime_type,
name=input.name,
upload_url=AnyUrl(upload_url),
)
# Save attachment metadata to data store so it's available during upload
if self.data_store is not None:
await self.data_store.save_attachment(attachment, context)
return attachment
async def read_attachment_bytes(self, attachment_id: str) -> bytes:
"""Read the raw bytes of an uploaded attachment.
This is used by the ThreadItemConverter to create base64-encoded
content for sending to the Agent Framework.
"""
file_path = self.get_file_path(attachment_id)
if not file_path.exists():
raise FileNotFoundError(f"Attachment {attachment_id} not found on disk")
return file_path.read_bytes()

View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ChatKit + Agent Framework Demo</title>
<!--
IMPORTANT: The ChatKit UI library is loaded from OpenAI's CDN and cannot be self-hosted.
This requires internet connectivity and is not suitable for air-gapped environments.
See: https://github.com/openai/chatkit-js/issues/57
-->
<script src="https://cdn.platform.openai.com/deployments/chatkit/chatkit.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
height: 100vh;
display: flex;
flex-direction: column;
}
header {
padding: 1rem;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
}
h1 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
p {
color: #666;
font-size: 0.9rem;
}
#root {
flex: 1;
overflow: hidden;
}
</style>
</head>
<body>
<header>
<h1>ChatKit + Agent Framework Demo</h1>
<p>Simple weather assistant powered by Agent Framework and ChatKit</p>
</header>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
{
"name": "chatkit-agent-framework-demo",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"engines": {
"node": ">=18.18",
"npm": ">=9"
},
"dependencies": {
"@openai/chatkit-react": "^0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"typescript": "^5.4.0",
"vite": "^7.1.12"
}
}

View File

@@ -0,0 +1,39 @@
import { ChatKit, useChatKit } from "@openai/chatkit-react";
const CHATKIT_API_URL = "/chatkit";
// Domain key for ChatKit integration
// - Local development: Uses default "domain_pk_localhost_dev"
// - Production: Register your domain at https://platform.openai.com/settings/organization/security/domain-allowlist
// and set VITE_CHATKIT_API_DOMAIN_KEY in your .env file
// See: https://github.com/openai/chatkit-js/issues/76
const CHATKIT_API_DOMAIN_KEY =
import.meta.env.VITE_CHATKIT_API_DOMAIN_KEY ?? "domain_pk_localhost_dev";
export default function App() {
const chatkit = useChatKit({
api: {
url: CHATKIT_API_URL,
domainKey: CHATKIT_API_DOMAIN_KEY,
uploadStrategy: { type: "two_phase" },
},
startScreen: {
greeting: "Hello! I'm your weather and image analysis assistant. Ask me about the weather in any location or upload images for me to analyze.",
prompts: [
{ label: "Weather in New York", prompt: "What's the weather in New York?" },
{ label: "Select City to Get Weather", prompt: "Show me the city selector for weather" },
{ label: "Current Time", prompt: "What time is it?" },
{ label: "Analyze an Image", prompt: "I'll upload an image for you to analyze" },
],
},
composer: {
placeholder: "Ask about weather or upload an image...",
attachments: {
enabled: true,
accept: { "image/*": [".png", ".jpg", ".jpeg", ".gif", ".webp"] },
},
},
});
return <ChatKit control={chatkit.control} style={{ height: "100%" }} />;
}

View File

@@ -0,0 +1,15 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const container = document.getElementById("root");
if (!container) {
throw new Error("Root element with id 'root' not found");
}
createRoot(container).render(
<StrictMode>
<App />
</StrictMode>,
);

View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,24 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
const backendTarget = process.env.BACKEND_URL ?? "http://127.0.0.1:8001";
export default defineConfig({
plugins: [react()],
server: {
host: "0.0.0.0",
port: 5171,
proxy: {
"/chatkit": {
target: backendTarget,
changeOrigin: true,
},
},
// For production deployments, you need to add your public domains to this list
allowedHosts: [
// You can remove these examples added just to demonstrate how to configure the allowlist
".ngrok.io",
".trycloudflare.com",
],
},
});

View File

@@ -0,0 +1,348 @@
# Copyright (c) Microsoft. All rights reserved.
"""SQLite-based store implementation for ChatKit data persistence.
This module provides a complete Store implementation using SQLite for data persistence.
It includes proper thread safety, user isolation, and follows the ChatKit Store protocol.
"""
import sqlite3
import uuid
from typing import Any
from chatkit.store import NotFoundError, Store
from chatkit.types import (
Attachment,
Page,
ThreadItem,
ThreadMetadata,
)
from pydantic import BaseModel
class ThreadData(BaseModel):
"""Model for serializing thread data to SQLite."""
thread: ThreadMetadata
class ItemData(BaseModel):
"""Model for serializing thread item data to SQLite."""
item: ThreadItem
class AttachmentData(BaseModel):
"""Model for serializing attachment data to SQLite."""
attachment: Attachment
class SQLiteStore(Store[dict[str, Any]]):
"""SQLite-based store implementation for ChatKit data.
This implementation follows the pattern from the ChatKit Python tests
and provides persistent storage for threads, messages, and attachments.
Features:
- Thread-safe SQLite connections with WAL mode
- User isolation for multi-tenant support
- Proper error handling and transaction management
- Complete Store protocol implementation
Note: This is for demonstration purposes. In production, you should
implement proper error handling, connection pooling, and migration strategies.
"""
def __init__(self, db_path: str | None = None):
self.db_path = db_path or "chatkit_demo.db" # Use file-based DB for demo
self._create_tables()
def _create_connection(self):
# Enable thread safety and WAL mode for better concurrent access
conn = sqlite3.connect(self.db_path, check_same_thread=False)
conn.execute("PRAGMA journal_mode=WAL")
return conn
def _create_tables(self):
with self._create_connection() as conn:
# Create threads table
conn.execute(
"""CREATE TABLE IF NOT EXISTS threads (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
created_at TEXT NOT NULL,
data TEXT NOT NULL
)"""
)
# Create items table
conn.execute(
"""CREATE TABLE IF NOT EXISTS items (
id TEXT PRIMARY KEY,
thread_id TEXT NOT NULL,
user_id TEXT NOT NULL,
created_at TEXT NOT NULL,
data TEXT NOT NULL
)"""
)
# Create attachments table
conn.execute(
"""CREATE TABLE IF NOT EXISTS attachments (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
data TEXT NOT NULL
)"""
)
conn.commit()
def generate_thread_id(self, context: dict[str, Any]) -> str:
return f"thr_{uuid.uuid4().hex[:8]}"
def generate_item_id(
self,
item_type: str,
thread: ThreadMetadata,
context: dict[str, Any],
) -> str:
prefix_map = {
"message": "msg",
"tool_call": "tc",
"task": "tsk",
"workflow": "wf",
"attachment": "atc",
}
prefix = prefix_map.get(item_type, "itm")
return f"{prefix}_{uuid.uuid4().hex[:8]}"
async def load_thread(self, thread_id: str, context: dict[str, Any]) -> ThreadMetadata:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
cursor = conn.execute(
"SELECT data FROM threads WHERE id = ? AND user_id = ?",
(thread_id, user_id),
).fetchone()
if cursor is None:
raise NotFoundError(f"Thread {thread_id} not found")
thread_data = ThreadData.model_validate_json(cursor[0])
return thread_data.thread
async def save_thread(self, thread: ThreadMetadata, context: dict[str, Any]) -> None:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
thread_data = ThreadData(thread=thread)
# Replace existing thread data
conn.execute(
"DELETE FROM threads WHERE id = ? AND user_id = ?",
(thread.id, user_id),
)
conn.execute(
"INSERT INTO threads (id, user_id, created_at, data) VALUES (?, ?, ?, ?)",
(
thread.id,
user_id,
thread.created_at.isoformat(),
thread_data.model_dump_json(),
),
)
conn.commit()
async def load_thread_items(
self,
thread_id: str,
after: str | None,
limit: int,
order: str,
context: dict[str, Any],
) -> Page[ThreadItem]:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
created_after: str | None = None
if after:
after_cursor = conn.execute(
"SELECT created_at FROM items WHERE id = ? AND user_id = ?",
(after, user_id),
).fetchone()
if after_cursor is None:
raise NotFoundError(f"Item {after} not found")
created_after = after_cursor[0]
query = """
SELECT data FROM items
WHERE thread_id = ? AND user_id = ?
"""
params: list[Any] = [thread_id, user_id]
if created_after:
query += " AND created_at > ?" if order == "asc" else " AND created_at < ?"
params.append(created_after)
query += f" ORDER BY created_at {order} LIMIT ?"
params.append(limit + 1)
items_cursor = conn.execute(query, params).fetchall()
items = [ItemData.model_validate_json(row[0]).item for row in items_cursor]
has_more = len(items) > limit
if has_more:
items = items[:limit]
return Page[ThreadItem](data=items, has_more=has_more, after=items[-1].id if items else None)
async def save_attachment(self, attachment: Attachment, context: dict[str, Any]) -> None:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
attachment_data = AttachmentData(attachment=attachment)
conn.execute(
"INSERT OR REPLACE INTO attachments (id, user_id, data) VALUES (?, ?, ?)",
(
attachment.id,
user_id,
attachment_data.model_dump_json(),
),
)
conn.commit()
async def load_attachment(self, attachment_id: str, context: dict[str, Any]) -> Attachment:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
cursor = conn.execute(
"SELECT data FROM attachments WHERE id = ? AND user_id = ?",
(attachment_id, user_id),
).fetchone()
if cursor is None:
raise NotFoundError(f"Attachment {attachment_id} not found")
attachment_data = AttachmentData.model_validate_json(cursor[0])
return attachment_data.attachment
async def delete_attachment(self, attachment_id: str, context: dict[str, Any]) -> None:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
conn.execute(
"DELETE FROM attachments WHERE id = ? AND user_id = ?",
(attachment_id, user_id),
)
conn.commit()
async def load_threads(
self,
limit: int,
after: str | None,
order: str,
context: dict[str, Any],
) -> Page[ThreadMetadata]:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
created_after: str | None = None
if after:
after_cursor = conn.execute(
"SELECT created_at FROM threads WHERE id = ? AND user_id = ?",
(after, user_id),
).fetchone()
if after_cursor is None:
raise NotFoundError(f"Thread {after} not found")
created_after = after_cursor[0]
query = "SELECT data FROM threads WHERE user_id = ?"
params: list[Any] = [user_id]
if created_after:
query += " AND created_at > ?" if order == "asc" else " AND created_at < ?"
params.append(created_after)
query += f" ORDER BY created_at {order} LIMIT ?"
params.append(limit + 1)
threads_cursor = conn.execute(query, params).fetchall()
threads = [ThreadData.model_validate_json(row[0]).thread for row in threads_cursor]
has_more = len(threads) > limit
if has_more:
threads = threads[:limit]
return Page[ThreadMetadata](data=threads, has_more=has_more, after=threads[-1].id if threads else None)
async def add_thread_item(self, thread_id: str, item: ThreadItem, context: dict[str, Any]) -> None:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
item_data = ItemData(item=item)
conn.execute(
"INSERT INTO items (id, thread_id, user_id, created_at, data) VALUES (?, ?, ?, ?, ?)",
(
item.id,
thread_id,
user_id,
item.created_at.isoformat(),
item_data.model_dump_json(),
),
)
conn.commit()
async def save_item(self, thread_id: str, item: ThreadItem, context: dict[str, Any]) -> None:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
item_data = ItemData(item=item)
conn.execute(
"UPDATE items SET data = ? WHERE id = ? AND thread_id = ? AND user_id = ?",
(
item_data.model_dump_json(),
item.id,
thread_id,
user_id,
),
)
conn.commit()
async def load_item(self, thread_id: str, item_id: str, context: dict[str, Any]) -> ThreadItem:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
cursor = conn.execute(
"SELECT data FROM items WHERE id = ? AND thread_id = ? AND user_id = ?",
(item_id, thread_id, user_id),
).fetchone()
if cursor is None:
raise NotFoundError(f"Item {item_id} not found in thread {thread_id}")
item_data = ItemData.model_validate_json(cursor[0])
return item_data.item
async def delete_thread(self, thread_id: str, context: dict[str, Any]) -> None:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
conn.execute(
"DELETE FROM threads WHERE id = ? AND user_id = ?",
(thread_id, user_id),
)
conn.execute(
"DELETE FROM items WHERE thread_id = ? AND user_id = ?",
(thread_id, user_id),
)
conn.commit()
async def delete_thread_item(self, thread_id: str, item_id: str, context: dict[str, Any]) -> None:
user_id = context.get("user_id", "demo_user")
with self._create_connection() as conn:
conn.execute(
"DELETE FROM items WHERE id = ? AND thread_id = ? AND user_id = ?",
(item_id, thread_id, user_id),
)
conn.commit()

View File

@@ -0,0 +1,436 @@
# Copyright (c) Microsoft. All rights reserved.
"""Weather widget rendering for ChatKit integration sample."""
import base64
from dataclasses import dataclass
from chatkit.actions import ActionConfig
from chatkit.widgets import Box, Button, Card, Col, Image, Row, Text, Title, WidgetRoot
WEATHER_ICON_COLOR = "#1D4ED8"
WEATHER_ICON_ACCENT = "#DBEAFE"
# Popular cities for the selector
POPULAR_CITIES = [
{"value": "seattle", "label": "Seattle, WA", "description": "Pacific Northwest"},
{"value": "new_york", "label": "New York, NY", "description": "East Coast"},
{"value": "san_francisco", "label": "San Francisco, CA", "description": "Bay Area"},
{"value": "chicago", "label": "Chicago, IL", "description": "Midwest"},
{"value": "miami", "label": "Miami, FL", "description": "Southeast"},
{"value": "austin", "label": "Austin, TX", "description": "Southwest"},
{"value": "boston", "label": "Boston, MA", "description": "New England"},
{"value": "denver", "label": "Denver, CO", "description": "Mountain West"},
{"value": "portland", "label": "Portland, OR", "description": "Pacific Northwest"},
{"value": "atlanta", "label": "Atlanta, GA", "description": "Southeast"},
]
# Mapping from city values to display names for weather queries
CITY_VALUE_TO_NAME = {city["value"]: city["label"] for city in POPULAR_CITIES}
def _sun_svg() -> str:
"""Generate SVG for sunny weather icon."""
color = WEATHER_ICON_COLOR
accent = WEATHER_ICON_ACCENT
return (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">'
f'<circle cx="32" cy="32" r="13" fill="{accent}" stroke="{color}" stroke-width="3"/>'
f'<g stroke="{color}" stroke-width="3" stroke-linecap="round">'
'<line x1="32" y1="8" x2="32" y2="16"/>'
'<line x1="32" y1="48" x2="32" y2="56"/>'
'<line x1="8" y1="32" x2="16" y2="32"/>'
'<line x1="48" y1="32" x2="56" y2="32"/>'
'<line x1="14.93" y1="14.93" x2="20.55" y2="20.55"/>'
'<line x1="43.45" y1="43.45" x2="49.07" y2="49.07"/>'
'<line x1="14.93" y1="49.07" x2="20.55" y2="43.45"/>'
'<line x1="43.45" y1="20.55" x2="49.07" y2="14.93"/>'
"</g>"
"</svg>"
)
def _cloud_svg() -> str:
"""Generate SVG for cloudy weather icon."""
color = WEATHER_ICON_COLOR
accent = WEATHER_ICON_ACCENT
return (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">'
f'<path d="M22 46H44C50.075 46 55 41.075 55 35S50.075 24 44 24H42.7C41.2 16.2 34.7 10 26.5 10 18 10 11.6 16.1 11 24.3 6.5 25.6 3 29.8 3 35s4.925 11 11 11h8Z" '
f'fill="{accent}" stroke="{color}" stroke-width="3" stroke-linejoin="round"/>'
"</svg>"
)
def _rain_svg() -> str:
"""Generate SVG for rainy weather icon."""
color = WEATHER_ICON_COLOR
accent = WEATHER_ICON_ACCENT
return (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">'
f'<path d="M22 40H44C50.075 40 55 35.075 55 29S50.075 18 44 18H42.7C41.2 10.2 34.7 4 26.5 4 18 4 11.6 10.1 11 18.3 6.5 19.6 3 23.8 3 29s4.925 11 11 11h8Z" '
f'fill="{accent}" stroke="{color}" stroke-width="3" stroke-linejoin="round"/>'
f'<g stroke="{color}" stroke-width="3" stroke-linecap="round">'
'<line x1="20" y1="48" x2="24" y2="56"/>'
'<line x1="30" y1="50" x2="34" y2="58"/>'
'<line x1="40" y1="48" x2="44" y2="56"/>'
"</g>"
"</svg>"
)
def _storm_svg() -> str:
"""Generate SVG for stormy weather icon."""
color = WEATHER_ICON_COLOR
accent = WEATHER_ICON_ACCENT
return (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">'
f'<path d="M22 40H44C50.075 40 55 35.075 55 29S50.075 18 44 18H42.7C41.2 10.2 34.7 4 26.5 4 18 4 11.6 10.1 11 18.3 6.5 19.6 3 23.8 3 29s4.925 11 11 11h8Z" '
f'fill="{accent}" stroke="{color}" stroke-width="3" stroke-linejoin="round"/>'
f'<path d="M34 46L28 56H34L30 64L42 50H36L40 46Z" '
f'fill="{color}" stroke="{color}" stroke-width="2" stroke-linejoin="round"/>'
"</svg>"
)
def _snow_svg() -> str:
"""Generate SVG for snowy weather icon."""
color = WEATHER_ICON_COLOR
accent = WEATHER_ICON_ACCENT
return (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">'
f'<path d="M22 40H44C50.075 40 55 35.075 55 29S50.075 18 44 18H42.7C41.2 10.2 34.7 4 26.5 4 18 4 11.6 10.1 11 18.3 6.5 19.6 3 23.8 3 29s4.925 11 11 11h8Z" '
f'fill="{accent}" stroke="{color}" stroke-width="3" stroke-linejoin="round"/>'
f'<g stroke="{color}" stroke-width="2" stroke-linecap="round">'
'<line x1="20" y1="48" x2="20" y2="56"/>'
'<line x1="17" y1="51" x2="23" y2="53"/>'
'<line x1="17" y1="53" x2="23" y2="51"/>'
'<line x1="36" y1="48" x2="36" y2="56"/>'
'<line x1="33" y1="51" x2="39" y2="53"/>'
'<line x1="33" y1="53" x2="39" y2="51"/>'
"</g>"
"</svg>"
)
def _fog_svg() -> str:
"""Generate SVG for foggy weather icon."""
color = WEATHER_ICON_COLOR
accent = WEATHER_ICON_ACCENT
return (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">'
f'<path d="M22 40H44C50.075 40 55 35.075 55 29S50.075 18 44 18H42.7C41.2 10.2 34.7 4 26.5 4 18 4 11.6 10.1 11 18.3 6.5 19.6 3 23.8 3 29s4.925 11 11 11h8Z" '
f'fill="{accent}" stroke="{color}" stroke-width="3" stroke-linejoin="round"/>'
f'<g stroke="{color}" stroke-width="3" stroke-linecap="round">'
'<line x1="18" y1="50" x2="42" y2="50"/>'
'<line x1="24" y1="56" x2="48" y2="56"/>'
"</g>"
"</svg>"
)
def _encode_svg(svg: str) -> str:
"""Encode SVG as base64 data URI."""
encoded = base64.b64encode(svg.encode("utf-8")).decode("ascii")
return f"data:image/svg+xml;base64,{encoded}"
# Weather condition to icon mapping
WEATHER_ICONS = {
"sunny": _encode_svg(_sun_svg()),
"cloudy": _encode_svg(_cloud_svg()),
"rainy": _encode_svg(_rain_svg()),
"stormy": _encode_svg(_storm_svg()),
"snowy": _encode_svg(_snow_svg()),
"foggy": _encode_svg(_fog_svg()),
}
DEFAULT_WEATHER_ICON = _encode_svg(_cloud_svg())
@dataclass
class WeatherData:
"""Weather data container."""
location: str
condition: str
temperature: int
humidity: int
wind_speed: int
def render_weather_widget(data: WeatherData) -> WidgetRoot:
"""Render a weather widget from weather data.
Args:
data: WeatherData containing weather information
Returns:
A ChatKit WidgetRoot (Card) displaying the weather information
"""
# Get weather icon
weather_icon_src = WEATHER_ICONS.get(data.condition.lower(), DEFAULT_WEATHER_ICON)
# Build the widget
header = Box(
padding=5,
background="surface-tertiary",
children=[
Row(
justify="between",
align="center",
children=[
Col(
align="start",
gap=1,
children=[
Text(
value=data.location,
size="lg",
weight="semibold",
),
Text(
value="Current conditions",
color="tertiary",
size="xs",
),
],
),
Box(
padding=3,
radius="full",
background="blue-100",
children=[
Image(
src=weather_icon_src,
alt=data.condition,
size=28,
fit="contain",
)
],
),
],
),
Row(
align="start",
gap=4,
children=[
Title(
value=f"{data.temperature}°C",
size="lg",
weight="semibold",
),
Col(
align="start",
gap=1,
children=[
Text(
value=data.condition.title(),
color="secondary",
size="sm",
weight="medium",
),
],
),
],
),
],
)
# Details section
details = Box(
padding=5,
gap=4,
children=[
Text(value="Weather details", weight="semibold", size="sm"),
Row(
gap=3,
wrap="wrap",
children=[
_detail_chip("Humidity", f"{data.humidity}%"),
_detail_chip("Wind", f"{data.wind_speed} km/h"),
],
),
],
)
return Card(
key="weather",
padding=0,
children=[header, details],
)
def _detail_chip(label: str, value: str) -> Box:
"""Create a detail chip widget component."""
return Box(
padding=3,
radius="xl",
background="surface-tertiary",
width=150,
minWidth=150,
maxWidth=150,
minHeight=80,
maxHeight=80,
flex="0 0 auto",
children=[
Col(
align="stretch",
gap=2,
children=[
Text(value=label, size="xs", weight="medium", color="tertiary"),
Row(
justify="center",
margin={"top": 2},
children=[Text(value=value, weight="semibold", size="lg")],
),
],
)
],
)
def weather_widget_copy_text(data: WeatherData) -> str:
"""Generate plain text representation of weather data.
Args:
data: WeatherData containing weather information
Returns:
Plain text description for copy/paste functionality
"""
return (
f"Weather in {data.location}:\n"
f"• Condition: {data.condition.title()}\n"
f"• Temperature: {data.temperature}°C\n"
f"• Humidity: {data.humidity}%\n"
f"• Wind: {data.wind_speed} km/h"
)
def render_city_selector_widget() -> WidgetRoot:
"""Render an interactive city selector widget.
This widget displays popular cities as a visual selection interface.
Users can click or ask about any city to get weather information.
Returns:
A ChatKit WidgetRoot (Card) with city selection display
"""
# Create location icon SVG
location_icon = _encode_svg(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">'
f'<path d="M32 8c-8.837 0-16 7.163-16 16 0 12 16 32 16 32s16-20 16-32c0-8.837-7.163-16-16-16z" '
f'fill="{WEATHER_ICON_ACCENT}" stroke="{WEATHER_ICON_COLOR}" stroke-width="3" stroke-linejoin="round"/>'
f'<circle cx="32" cy="24" r="6" fill="{WEATHER_ICON_COLOR}"/>'
"</svg>"
)
# Header section
header = Box(
padding=5,
background="surface-tertiary",
children=[
Row(
gap=3,
align="center",
children=[
Box(
padding=3,
radius="full",
background="blue-100",
children=[
Image(
src=location_icon,
alt="Location",
size=28,
fit="contain",
)
],
),
Col(
align="start",
gap=1,
children=[
Title(
value="Popular Cities",
size="md",
weight="semibold",
),
Text(
value="Select a city or ask about any location",
color="tertiary",
size="xs",
),
],
),
],
),
],
)
# Create city chips in a grid layout
city_chips: list[Button] = []
for city in POPULAR_CITIES:
# Create a button that sends an action to query weather for the selected city
chip = Button(
label=city["label"],
variant="outline",
size="md",
onClickAction=ActionConfig(
type="city_selected",
payload={"city_value": city["value"], "city_label": city["label"]},
handler="server", # Handle on server-side
),
)
city_chips.append(chip)
# Arrange in rows of 3
city_rows: list[Row] = []
for i in range(0, len(city_chips), 3):
row_chips: list[Button] = city_chips[i : i + 3]
city_rows.append(
Row(
gap=3,
wrap="wrap",
justify="start",
children=list(row_chips), # Convert to generic list
)
)
# Cities display section
cities_section = Box(
padding=5,
gap=3,
children=[
*city_rows,
Box(
padding=3,
radius="md",
background="blue-50",
children=[
Text(
value="💡 Click any city to get its weather, or ask about any other location!",
size="xs",
color="secondary",
),
],
),
],
)
return Card(
key="city_selector",
padding=0,
children=[header, cities_section],
)
def city_selector_copy_text() -> str:
"""Generate plain text representation of city selector.
Returns:
Plain text description for copy/paste functionality
"""
cities_list = "\n".join([f"{city['label']}" for city in POPULAR_CITIES])
return f"Popular cities (click to get weather):\n{cities_list}\n\nYou can also ask about weather in any other location!"

View File

@@ -0,0 +1,16 @@
FROM python:3.12-slim
WORKDIR /app
COPY . user_agent/
WORKDIR /app/user_agent
RUN if [ -f requirements.txt ]; then \
pip install -r requirements.txt; \
else \
echo "No requirements.txt found"; \
fi
EXPOSE 8088
CMD ["python", "main.py"]

View File

@@ -0,0 +1,30 @@
# Unique identifier/name for this agent
name: agent-with-hosted-mcp
# Brief description of what this agent does
description: >
An AI agent that uses Azure OpenAI with a Hosted Model Context Protocol (MCP) server.
The agent answers questions by searching Microsoft Learn documentation using MCP tools.
metadata:
# Categorization tags for organizing and discovering agents
authors:
- Microsoft Agent Framework Team
tags:
- Azure AI AgentServer
- Microsoft Agent Framework
- Model Context Protocol
- MCP
template:
name: agent-with-hosted-mcp
# The type of agent - "hosted" for HOBO, "container" for COBO
kind: hosted
protocols:
- protocol: responses
environment_variables:
- name: AZURE_OPENAI_ENDPOINT
value: ${AZURE_OPENAI_ENDPOINT}
- name: AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
value: "{{chat}}"
resources:
- kind: model
id: gpt-4o-mini
name: chat

View File

@@ -0,0 +1,25 @@
# Copyright (c) Microsoft. All rights reserved.
from agent_framework import HostedMCPTool
from agent_framework.azure import AzureOpenAIChatClient
from azure.ai.agentserver.agentframework import from_agent_framework # pyright: ignore[reportUnknownVariableType]
from azure.identity import DefaultAzureCredential
def main():
# Create an Agent using the Azure OpenAI Chat Client with a MCP Tool that connects to Microsoft Learn MCP
agent = AzureOpenAIChatClient(credential=DefaultAzureCredential()).as_agent(
name="DocsAgent",
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
tools=HostedMCPTool(
name="Microsoft Learn MCP",
url="https://learn.microsoft.com/api/mcp",
),
)
# Run the agent as a hosted agent
from_agent_framework(agent).run()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,2 @@
azure-ai-agentserver-agentframework==1.0.0b3
agent-framework

View File

@@ -0,0 +1,16 @@
FROM python:3.12-slim
WORKDIR /app
COPY . user_agent/
WORKDIR /app/user_agent
RUN if [ -f requirements.txt ]; then \
pip install -r requirements.txt; \
else \
echo "No requirements.txt found"; \
fi
EXPOSE 8088
CMD ["python", "main.py"]

View File

@@ -0,0 +1,33 @@
# Unique identifier/name for this agent
name: agent-with-text-search-rag
# Brief description of what this agent does
description: >
An AI agent that uses a ContextProvider for retrieval augmented generation (RAG) capabilities.
The agent runs searches against an external knowledge base before each model invocation and
injects the results into the model context. It can answer questions about Contoso Outdoors
policies and products, including return policies, refunds, shipping options, and product care
instructions such as tent maintenance.
metadata:
# Categorization tags for organizing and discovering agents
authors:
- Microsoft Agent Framework Team
tags:
- Azure AI AgentServer
- Microsoft Agent Framework
- Retrieval-Augmented Generation
- RAG
template:
name: agent-with-text-search-rag
# The type of agent - "hosted" for HOBO, "container" for COBO
kind: hosted
protocols:
- protocol: responses
environment_variables:
- name: AZURE_OPENAI_ENDPOINT
value: ${AZURE_OPENAI_ENDPOINT}
- name: AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
value: "{{chat}}"
resources:
- kind: model
id: gpt-4o-mini
name: chat

View File

@@ -0,0 +1,110 @@
# Copyright (c) Microsoft. All rights reserved.
import json
import sys
from collections.abc import MutableSequence
from dataclasses import dataclass
from typing import Any
from agent_framework import ChatMessage, Context, ContextProvider, Role
from agent_framework.azure import AzureOpenAIChatClient
from azure.ai.agentserver.agentframework import from_agent_framework # pyright: ignore[reportUnknownVariableType]
from azure.identity import DefaultAzureCredential
if sys.version_info >= (3, 12):
from typing import override
else:
from typing_extensions import override
@dataclass
class TextSearchResult:
source_name: str
source_link: str
text: str
class TextSearchContextProvider(ContextProvider):
"""A simple context provider that simulates text search results based on keywords in the user's message."""
def _get_most_recent_message(self, messages: ChatMessage | MutableSequence[ChatMessage]) -> ChatMessage:
"""Helper method to extract the most recent message from the input."""
if isinstance(messages, ChatMessage):
return messages
if messages:
return messages[-1]
raise ValueError("No messages provided")
@override
async def invoking(self, messages: ChatMessage | MutableSequence[ChatMessage], **kwargs: Any) -> Context:
message = self._get_most_recent_message(messages)
query = message.text.lower()
results: list[TextSearchResult] = []
if "return" in query and "refund" in query:
results.append(
TextSearchResult(
source_name="Contoso Outdoors Return Policy",
source_link="https://contoso.com/policies/returns",
text=(
"Customers may return any item within 30 days of delivery. "
"Items should be unused and include original packaging. "
"Refunds are issued to the original payment method within 5 business days of inspection."
),
)
)
if "shipping" in query:
results.append(
TextSearchResult(
source_name="Contoso Outdoors Shipping Guide",
source_link="https://contoso.com/help/shipping",
text=(
"Standard shipping is free on orders over $50 and typically arrives in 3-5 business days "
"within the continental United States. Expedited options are available at checkout."
),
)
)
if "tent" in query or "fabric" in query:
results.append(
TextSearchResult(
source_name="TrailRunner Tent Care Instructions",
source_link="https://contoso.com/manuals/trailrunner-tent",
text=(
"Clean the tent fabric with lukewarm water and a non-detergent soap. "
"Allow it to air dry completely before storage and avoid prolonged UV "
"exposure to extend the lifespan of the waterproof coating."
),
)
)
if not results:
return Context()
return Context(
messages=[
ChatMessage(
role=Role.USER, text="\n\n".join(json.dumps(result.__dict__, indent=2) for result in results)
)
]
)
def main():
# Create an Agent using the Azure OpenAI Chat Client
agent = AzureOpenAIChatClient(credential=DefaultAzureCredential()).as_agent(
name="SupportSpecialist",
instructions=(
"You are a helpful support specialist for Contoso Outdoors. "
"Answer questions using the provided context and cite the source document when available."
),
context_provider=TextSearchContextProvider(),
)
# Run the agent as a hosted agent
from_agent_framework(agent).run()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,2 @@
azure-ai-agentserver-agentframework==1.0.0b3
agent-framework

View File

@@ -0,0 +1,16 @@
FROM python:3.12-slim
WORKDIR /app
COPY . user_agent/
WORKDIR /app/user_agent
RUN if [ -f requirements.txt ]; then \
pip install -r requirements.txt; \
else \
echo "No requirements.txt found"; \
fi
EXPOSE 8088
CMD ["python", "main.py"]

View File

@@ -0,0 +1,28 @@
# Unique identifier/name for this agent
name: agents-in-workflow
# Brief description of what this agent does
description: >
A workflow agent that responds to product launch strategy inquiries by concurrently leveraging insights from three specialized agents.
metadata:
# Categorization tags for organizing and discovering agents
authors:
- Microsoft Agent Framework Team
tags:
- Azure AI AgentServer
- Microsoft Agent Framework
- Workflows
template:
name: agents-in-workflow
# The type of agent - "hosted" for HOBO, "container" for COBO
kind: hosted
protocols:
- protocol: responses
environment_variables:
- name: AZURE_OPENAI_ENDPOINT
value: ${AZURE_OPENAI_ENDPOINT}
- name: AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
value: "{{chat}}"
resources:
- kind: model
id: gpt-4o-mini
name: chat

View File

@@ -0,0 +1,44 @@
# Copyright (c) Microsoft. All rights reserved.
from agent_framework import ConcurrentBuilder
from agent_framework.azure import AzureOpenAIChatClient
from azure.ai.agentserver.agentframework import from_agent_framework
from azure.identity import DefaultAzureCredential # pyright: ignore[reportUnknownVariableType]
def main():
# Create agents
researcher = AzureOpenAIChatClient(credential=DefaultAzureCredential()).as_agent(
instructions=(
"You're an expert market and product researcher. "
"Given a prompt, provide concise, factual insights, opportunities, and risks."
),
name="researcher",
)
marketer = AzureOpenAIChatClient(credential=DefaultAzureCredential()).as_agent(
instructions=(
"You're a creative marketing strategist. "
"Craft compelling value propositions and target messaging aligned to the prompt."
),
name="marketer",
)
legal = AzureOpenAIChatClient(credential=DefaultAzureCredential()).as_agent(
instructions=(
"You're a cautious legal/compliance reviewer. "
"Highlight constraints, disclaimers, and policy concerns based on the prompt."
),
name="legal",
)
# Build a concurrent workflow
workflow = ConcurrentBuilder().participants([researcher, marketer, legal]).build()
# Convert the workflow to an agent
workflow_agent = workflow.as_agent()
# Run the agent as a hosted agent
from_agent_framework(workflow_agent).run()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,2 @@
azure-ai-agentserver-agentframework==1.0.0b3
agent-framework

View File

@@ -0,0 +1,17 @@
# OpenAI Configuration
OPENAI_API_KEY=
OPENAI_CHAT_MODEL_ID=
# Agent 365 Agentic Authentication Configuration
USE_ANONYMOUS_MODE=
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__SCOPES=
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__TYPE=AgenticUserAuthorization
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__SCOPES=https://graph.microsoft.com/.default
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__ALTERNATEBLUEPRINTCONNECTIONNAME=https://graph.microsoft.com/.default
CONNECTIONSMAP_0_SERVICEURL=*
CONNECTIONSMAP_0_CONNECTION=SERVICE_CONNECTION

View File

@@ -0,0 +1,100 @@
# Microsoft Agent Framework Python Weather Agent sample (M365 Agents SDK)
This sample demonstrates a simple Weather Forecast Agent built with the Python Microsoft Agent Framework, exposed through the Microsoft 365 Agents SDK compatible endpoints. The agent accepts natural language requests for a weather forecast and responds with a textual answer. It supports multi-turn conversations to gather required information.
## Prerequisites
- Python 3.11+
- [uv](https://github.com/astral-sh/uv) for fast dependency management
- [devtunnel](https://learn.microsoft.com/azure/developer/dev-tunnels/get-started?tabs=windows)
- [Microsoft 365 Agents Toolkit](https://github.com/OfficeDev/microsoft-365-agents-toolkit) for playground/testing
- Access to OpenAI or Azure OpenAI with a model like `gpt-4o-mini`
## Configuration
Set the following environment variables:
```bash
# Common
export PORT=3978
export USE_ANONYMOUS_MODE=True # set to false if using auth
# OpenAI
export OPENAI_API_KEY="..."
export OPENAI_CHAT_MODEL_ID="..."
```
## Installing Dependencies
From the repository root or the sample folder:
```bash
uv sync
```
## Running the Agent Locally
```bash
# Activate environment first if not already
source .venv/bin/activate # (Windows PowerShell: .venv\Scripts\Activate.ps1)
# Run the weather agent demo
python m365_agent_demo/app.py
```
The agent starts on `http://localhost:3978`. Health check: `GET /api/health`.
## QuickStart using Agents Playground
1. Install (if not already):
```bash
winget install agentsplayground
```
2. Start the Python agent locally: `python m365_agent_demo/app.py`
3. Start the playground: `agentsplayground`
4. Chat with the Weather Agent.
## QuickStart using WebChat (Azure Bot)
To test via WebChat you can provision an Azure Bot and point its messaging endpoint to your agent.
1. Create an Azure Bot (choose Client Secret auth for local tunneling).
2. Create a `.env` file in this sample folder with the following (replace placeholders):
```bash
# Authentication / Agentic configuration
USE_ANONYMOUS_MODE=False
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID="<client-id>"
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET="<client-secret>"
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID="<tenant-id>"
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__SCOPES=https://graph.microsoft.com/.default
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__TYPE=AgenticUserAuthorization
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__SCOPES=https://graph.microsoft.com/.default
AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__ALTERNATEBLUEPRINTCONNECTIONNAME=https://graph.microsoft.com/.default
```
3. Host dev tunnel:
```bash
devtunnel host -p 3978 --allow-anonymous
```
4. Set the bot Messaging endpoint to: `https://<tunnel-host>/api/messages`
5. Run your local agent: `python m365_agent_demo/app.py`
6. Use "Test in WebChat" in Azure Portal.
> Federated Credentials or Managed Identity auth types typically require deployment to Azure App Service instead of tunneling.
## Troubleshooting
- 404 on `/api/messages`: Ensure you are POSTing and using the correct tunnel URL.
- Empty responses: Check model key / quota and ensure environment variables are set.
- Auth errors when anonymous disabled: Validate MSAL config matches your Azure Bot registration.
## Further Reading
- [Microsoft 365 Agents SDK](https://learn.microsoft.com/microsoft-365/agents-sdk/)
- [Devtunnel docs](https://learn.microsoft.com/azure/developer/dev-tunnels/)

View File

@@ -0,0 +1,238 @@
# Copyright (c) Microsoft. All rights reserved.
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "microsoft-agents-hosting-aiohttp",
# "microsoft-agents-hosting-core",
# "microsoft-agents-authentication-msal",
# "microsoft-agents-activity",
# "agent-framework-core",
# "aiohttp"
# ]
# ///
import os
from dataclasses import dataclass
from random import randint
from typing import Annotated
from agent_framework import ChatAgent
from agent_framework.openai import OpenAIChatClient
from aiohttp import web
from aiohttp.web_middlewares import middleware
from microsoft_agents.activity import load_configuration_from_env
from microsoft_agents.authentication.msal import MsalConnectionManager
from microsoft_agents.hosting.aiohttp import CloudAdapter, start_agent_process
from microsoft_agents.hosting.core import (
AgentApplication,
AuthenticationConstants,
Authorization,
ClaimsIdentity,
MemoryStorage,
TurnContext,
TurnState,
)
from pydantic import Field
"""
Demo application using Microsoft Agent 365 SDK.
This sample demonstrates how to build an AI agent using the Agent Framework,
integrating with Microsoft 365 authentication and hosting components.
The agent provides a simple weather tool and can be run in either anonymous mode
(no authentication required) or authenticated mode using MSAL and Azure AD.
Key features:
- Loads configuration from environment variables.
- Demonstrates agent creation and tool registration.
- Supports both anonymous and authenticated scenarios.
- Uses aiohttp for web hosting.
To run, set the appropriate environment variables (check .env.example file) for authentication or use
anonymous mode for local testing.
"""
@dataclass
class AppConfig:
use_anonymous_mode: bool
port: int
agents_sdk_config: dict
def load_app_config() -> AppConfig:
"""Load application configuration from environment variables.
Returns:
AppConfig: Consolidated configuration including anonymous mode flag, port, and SDK config.
"""
agents_sdk_config = load_configuration_from_env(os.environ)
use_anonymous_mode = os.environ.get("USE_ANONYMOUS_MODE", "true").lower() == "true"
port_str = os.getenv("PORT", "3978")
try:
port = int(port_str)
except ValueError:
port = 3978
return AppConfig(use_anonymous_mode=use_anonymous_mode, port=port, agents_sdk_config=agents_sdk_config)
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Generate a mock weather report for the provided location.
Args:
location: The geographic location name.
Returns:
str: Human-readable weather summary.
"""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
def build_agent() -> ChatAgent:
"""Create and return the chat agent instance with weather tool registered."""
return OpenAIChatClient().as_agent(
name="WeatherAgent", instructions="You are a helpful weather agent.", tools=get_weather
)
def build_connection_manager(config: AppConfig) -> MsalConnectionManager | None:
"""Build the connection manager unless running in anonymous mode.
Args:
config: Application configuration.
Returns:
MsalConnectionManager | None: Connection manager when authenticated mode is enabled.
"""
if config.use_anonymous_mode:
return None
return MsalConnectionManager(**config.agents_sdk_config)
def build_adapter(connection_manager: MsalConnectionManager | None) -> CloudAdapter:
"""Instantiate the CloudAdapter with the optional connection manager."""
return CloudAdapter(connection_manager=connection_manager)
def build_authorization(
storage: MemoryStorage, connection_manager: MsalConnectionManager | None, config: AppConfig
) -> Authorization | None:
"""Create Authorization component if not in anonymous mode.
Args:
storage: State storage backend.
connection_manager: Optional connection manager.
config: Application configuration.
Returns:
Authorization | None: Authorization component when enabled.
"""
if config.use_anonymous_mode:
return None
return Authorization(storage, connection_manager, **config.agents_sdk_config)
def build_agent_application(
storage: MemoryStorage,
adapter: CloudAdapter,
authorization: Authorization | None,
config: AppConfig,
) -> AgentApplication[TurnState]:
"""Compose and return the AgentApplication instance.
Args:
storage: Storage implementation.
adapter: CloudAdapter handling requests.
authorization: Optional authorization component.
config: App configuration.
Returns:
AgentApplication[TurnState]: Configured agent application.
"""
return AgentApplication[TurnState](
storage=storage, adapter=adapter, authorization=authorization, **config.agents_sdk_config
)
def build_anonymous_claims_middleware(use_anonymous_mode: bool):
"""Return a middleware that injects anonymous claims when enabled.
Args:
use_anonymous_mode: Whether to apply anonymous identity for each request.
Returns:
Callable: Aiohttp middleware function.
"""
@middleware
async def anonymous_claims_middleware(request, handler):
"""Inject claims for anonymous users if anonymous mode is active."""
if use_anonymous_mode:
request["claims_identity"] = ClaimsIdentity(
{
AuthenticationConstants.AUDIENCE_CLAIM: "anonymous",
AuthenticationConstants.APP_ID_CLAIM: "anonymous-app",
},
False,
"Anonymous",
)
return await handler(request)
return anonymous_claims_middleware
def create_app(config: AppConfig) -> web.Application:
"""Create and configure the aiohttp web application.
Args:
config: Loaded application configuration.
Returns:
web.Application: Fully initialized web application.
"""
middleware_fn = build_anonymous_claims_middleware(config.use_anonymous_mode)
app = web.Application(middleware=[middleware_fn])
storage = MemoryStorage()
agent = build_agent()
connection_manager = build_connection_manager(config)
adapter = build_adapter(connection_manager)
authorization = build_authorization(storage, connection_manager, config)
agent_app = build_agent_application(storage, adapter, authorization, config)
@agent_app.activity("message")
async def on_message(context: TurnContext, _: TurnState):
user_message = context.activity.text or ""
if not user_message.strip():
return
response = await agent.run(user_message)
response_text = response.text
await context.send_activity(response_text)
async def health(request: web.Request) -> web.Response:
return web.json_response({"status": "ok"})
async def entry_point(req: web.Request) -> web.Response:
return await start_agent_process(req, req.app["agent_app"], req.app["adapter"])
app.add_routes([
web.get("/api/health", health),
web.get("/api/messages", lambda _: web.Response(status=200)),
web.post("/api/messages", entry_point),
])
app["agent_app"] = agent_app
app["adapter"] = adapter
return app
def main() -> None:
"""Entry point: load configuration, build app, and start server."""
config = load_app_config()
app = create_app(config)
web.run_app(app, host="localhost", port=config.port)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,2 @@
AZURE_AI_PROJECT_ENDPOINT="<your-project-endpoint>"
AZURE_AI_MODEL_DEPLOYMENT_NAME="<your-model-deployment>"

View File

@@ -0,0 +1,30 @@
# Multi-Agent Travel Planning Workflow Evaluation
This sample demonstrates evaluating a multi-agent workflow using Azure AI's built-in evaluators. The workflow processes travel planning requests through seven specialized agents in a fan-out/fan-in pattern: travel request handler, hotel/flight/activity search agents, booking aggregator, booking confirmation, and payment processing.
## Evaluation Metrics
The evaluation uses four Azure AI built-in evaluators:
- **Relevance** - How well responses address the user query
- **Groundedness** - Whether responses are grounded in available context
- **Tool Call Accuracy** - Correct tool selection and parameter usage
- **Tool Output Utilization** - Effective use of tool outputs in responses
## Setup
Create a `.env` file with configuration as in the `.env.example` file in this folder.
## Running the Evaluation
Execute the complete workflow and evaluation:
```bash
python run_evaluation.py
```
The script will:
1. Execute the multi-agent travel planning workflow
2. Display response summary for each agent
3. Create and run evaluation on hotel, flight, and activity search agents
4. Monitor progress and display the evaluation report URL

View File

@@ -0,0 +1,754 @@
# Copyright (c) Microsoft. All rights reserved.
import json
from datetime import datetime
from typing import Annotated
from agent_framework import ai_function
from pydantic import Field
# --- Travel Planning Tools ---
# Note: These are mock tools for demonstration purposes. They return simulated data
# and do not make real API calls or bookings.
# Mock hotel search tool
@ai_function(name="search_hotels", description="Search for available hotels based on location and dates.")
def search_hotels(
location: Annotated[str, Field(description="City or region to search for hotels.")],
check_in: Annotated[str, Field(description="Check-in date (e.g., 'December 15, 2025').")],
check_out: Annotated[str, Field(description="Check-out date (e.g., 'December 18, 2025').")],
guests: Annotated[int, Field(description="Number of guests.")] = 2,
) -> str:
"""Search for available hotels based on location and dates.
Returns:
JSON string containing search results with hotel details including name, rating,
price, distance to landmarks, amenities, and availability.
"""
# Specific mock data for Paris December 15-18, 2025
if "paris" in location.lower():
mock_hotels = [
{
"name": "Hotel Eiffel Trocadéro",
"rating": 4.6,
"price_per_night": "$185",
"total_price": "$555 for 3 nights",
"distance_to_eiffel_tower": "0.3 miles",
"amenities": ["WiFi", "Breakfast", "Eiffel Tower View", "Concierge"],
"availability": "Available",
"address": "35 Rue Benjamin Franklin, 16th arr., Paris"
},
{
"name": "Mercure Paris Centre Tour Eiffel",
"rating": 4.4,
"price_per_night": "$220",
"total_price": "$660 for 3 nights",
"distance_to_eiffel_tower": "0.5 miles",
"amenities": ["WiFi", "Restaurant", "Bar", "Gym", "Air Conditioning"],
"availability": "Available",
"address": "20 Rue Jean Rey, 15th arr., Paris"
},
{
"name": "Pullman Paris Tour Eiffel",
"rating": 4.7,
"price_per_night": "$280",
"total_price": "$840 for 3 nights",
"distance_to_eiffel_tower": "0.2 miles",
"amenities": ["WiFi", "Spa", "Gym", "Restaurant", "Rooftop Bar", "Concierge"],
"availability": "Limited",
"address": "18 Avenue de Suffren, 15th arr., Paris"
}
]
else:
mock_hotels = [
{
"name": "Grand Plaza Hotel",
"rating": 4.5,
"price_per_night": "$150",
"amenities": ["WiFi", "Pool", "Gym", "Restaurant"],
"availability": "Available"
}
]
return json.dumps({
"location": location,
"check_in": check_in,
"check_out": check_out,
"guests": guests,
"hotels_found": len(mock_hotels),
"hotels": mock_hotels,
"note": "Hotel search results matching your query"
})
# Mock hotel details tool
@ai_function(name="get_hotel_details", description="Get detailed information about a specific hotel.")
def get_hotel_details(
hotel_name: Annotated[str, Field(description="Name of the hotel to get details for.")],
) -> str:
"""Get detailed information about a specific hotel.
Returns:
JSON string containing detailed hotel information including description,
check-in/out times, cancellation policy, reviews, and nearby attractions.
"""
hotel_details = {
"Hotel Eiffel Trocadéro": {
"description": "Charming boutique hotel with stunning Eiffel Tower views from select rooms. Perfect for couples and families.",
"check_in_time": "3:00 PM",
"check_out_time": "11:00 AM",
"cancellation_policy": "Free cancellation up to 24 hours before check-in",
"reviews": {
"total": 1247,
"recent_comments": [
"Amazing location! Walked to Eiffel Tower in 5 minutes.",
"Staff was incredibly helpful with restaurant recommendations.",
"Rooms are cozy and clean with great views."
]
},
"nearby_attractions": ["Eiffel Tower (0.3 mi)", "Trocadéro Gardens (0.2 mi)", "Seine River (0.4 mi)"]
},
"Mercure Paris Centre Tour Eiffel": {
"description": "Modern hotel with contemporary rooms and excellent dining options. Close to metro stations.",
"check_in_time": "2:00 PM",
"check_out_time": "12:00 PM",
"cancellation_policy": "Free cancellation up to 48 hours before check-in",
"reviews": {
"total": 2156,
"recent_comments": [
"Great value for money, clean and comfortable.",
"Restaurant had excellent French cuisine.",
"Easy access to public transportation."
]
},
"nearby_attractions": ["Eiffel Tower (0.5 mi)", "Champ de Mars (0.4 mi)", "Les Invalides (0.8 mi)"]
},
"Pullman Paris Tour Eiffel": {
"description": "Luxury hotel offering panoramic views, upscale amenities, and exceptional service. Ideal for a premium experience.",
"check_in_time": "3:00 PM",
"check_out_time": "12:00 PM",
"cancellation_policy": "Free cancellation up to 72 hours before check-in",
"reviews": {
"total": 3421,
"recent_comments": [
"Rooftop bar has the best Eiffel Tower views in Paris!",
"Luxurious rooms with every amenity you could want.",
"Worth the price for the location and service."
]
},
"nearby_attractions": ["Eiffel Tower (0.2 mi)", "Seine River Cruise Dock (0.3 mi)", "Trocadéro (0.5 mi)"]
}
}
details = hotel_details.get(hotel_name, {
"name": hotel_name,
"description": "Comfortable hotel with modern amenities",
"check_in_time": "3:00 PM",
"check_out_time": "11:00 AM",
"cancellation_policy": "Standard cancellation policy applies",
"reviews": {"total": 0, "recent_comments": []},
"nearby_attractions": []
})
return json.dumps({
"hotel_name": hotel_name,
"details": details
})
# Mock flight search tool
@ai_function(name="search_flights", description="Search for available flights between two locations.")
def search_flights(
origin: Annotated[str, Field(description="Departure airport or city (e.g., 'JFK' or 'New York').")],
destination: Annotated[str, Field(description="Arrival airport or city (e.g., 'CDG' or 'Paris').")],
departure_date: Annotated[str, Field(description="Departure date (e.g., 'December 15, 2025').")],
return_date: Annotated[str | None, Field(description="Return date (e.g., 'December 18, 2025').")] = None,
passengers: Annotated[int, Field(description="Number of passengers.")] = 1,
) -> str:
"""Search for available flights between two locations.
Returns:
JSON string containing flight search results with details including flight numbers,
airlines, departure/arrival times, prices, durations, and baggage allowances.
"""
# Specific mock data for JFK to Paris December 15-18, 2025
if "jfk" in origin.lower() or "new york" in origin.lower():
if "paris" in destination.lower() or "cdg" in destination.lower():
mock_flights = [
{
"outbound": {
"flight_number": "AF007",
"airline": "Air France",
"departure": "December 15, 2025 at 6:30 PM",
"arrival": "December 16, 2025 at 8:15 AM",
"duration": "7h 45m",
"aircraft": "Boeing 777-300ER",
"class": "Economy",
"price": "$520"
},
"return": {
"flight_number": "AF008",
"airline": "Air France",
"departure": "December 18, 2025 at 11:00 AM",
"arrival": "December 18, 2025 at 2:15 PM",
"duration": "8h 15m",
"aircraft": "Airbus A350-900",
"class": "Economy",
"price": "Included"
},
"total_price": "$520",
"stops": "Nonstop",
"baggage": "1 checked bag included"
},
{
"outbound": {
"flight_number": "DL264",
"airline": "Delta",
"departure": "December 15, 2025 at 10:15 PM",
"arrival": "December 16, 2025 at 12:05 PM",
"duration": "7h 50m",
"aircraft": "Airbus A330-900neo",
"class": "Economy",
"price": "$485"
},
"return": {
"flight_number": "DL265",
"airline": "Delta",
"departure": "December 18, 2025 at 1:45 PM",
"arrival": "December 18, 2025 at 5:00 PM",
"duration": "8h 15m",
"aircraft": "Airbus A330-900neo",
"class": "Economy",
"price": "Included"
},
"total_price": "$485",
"stops": "Nonstop",
"baggage": "1 checked bag included"
},
{
"outbound": {
"flight_number": "UA57",
"airline": "United Airlines",
"departure": "December 15, 2025 at 5:00 PM",
"arrival": "December 16, 2025 at 6:50 AM",
"duration": "7h 50m",
"aircraft": "Boeing 767-400ER",
"class": "Economy",
"price": "$560"
},
"return": {
"flight_number": "UA58",
"airline": "United Airlines",
"departure": "December 18, 2025 at 9:30 AM",
"arrival": "December 18, 2025 at 12:45 PM",
"duration": "8h 15m",
"aircraft": "Boeing 787-10",
"class": "Economy",
"price": "Included"
},
"total_price": "$560",
"stops": "Nonstop",
"baggage": "1 checked bag included"
}
]
else:
mock_flights = [{"flight_number": "XX123", "airline": "Generic Air", "price": "$400", "note": "Generic route"}]
else:
mock_flights = [
{
"outbound": {
"flight_number": "AA123",
"airline": "Generic Airlines",
"departure": f"{departure_date} at 9:00 AM",
"arrival": f"{departure_date} at 2:30 PM",
"duration": "5h 30m",
"class": "Economy",
"price": "$350"
},
"total_price": "$350",
"stops": "Nonstop"
}
]
return json.dumps({
"origin": origin,
"destination": destination,
"departure_date": departure_date,
"return_date": return_date,
"passengers": passengers,
"flights_found": len(mock_flights),
"flights": mock_flights,
"note": "Flight search results for JFK to Paris CDG"
})
# Mock flight details tool
@ai_function(name="get_flight_details", description="Get detailed information about a specific flight.")
def get_flight_details(
flight_number: Annotated[str, Field(description="Flight number (e.g., 'AF007' or 'DL264').")],
) -> str:
"""Get detailed information about a specific flight.
Returns:
JSON string containing detailed flight information including airline, aircraft type,
departure/arrival airports and times, gates, terminals, duration, and amenities.
"""
mock_details = {
"flight_number": flight_number,
"airline": "Sky Airways",
"aircraft": "Boeing 737-800",
"departure": {
"airport": "JFK International Airport",
"terminal": "Terminal 4",
"gate": "B23",
"time": "08:00 AM"
},
"arrival": {
"airport": "Charles de Gaulle Airport",
"terminal": "Terminal 2E",
"gate": "K15",
"time": "11:30 AM local time"
},
"duration": "3h 30m",
"baggage_allowance": {
"carry_on": "1 bag (10kg)",
"checked": "1 bag (23kg)"
},
"amenities": ["WiFi", "In-flight entertainment", "Meals included"]
}
return json.dumps({
"flight_details": mock_details
})
# Mock activity search tool
@ai_function(name="search_activities", description="Search for available activities and attractions at a destination.")
def search_activities(
location: Annotated[str, Field(description="City or region to search for activities.")],
date: Annotated[str | None, Field(description="Date for the activity (e.g., 'December 16, 2025').")] = None,
category: Annotated[str | None, Field(description="Activity category (e.g., 'Sightseeing', 'Culture', 'Culinary').")] = None,
) -> str:
"""Search for available activities and attractions at a destination.
Returns:
JSON string containing activity search results with details including name, category,
duration, price, rating, description, availability, and booking requirements.
"""
# Specific mock data for Paris activities
if "paris" in location.lower():
all_activities = [
{
"name": "Eiffel Tower Summit Access",
"category": "Sightseeing",
"duration": "2-3 hours",
"price": "$35",
"rating": 4.8,
"description": "Skip-the-line access to all three levels including the summit. Best views of Paris!",
"availability": "Daily 9:30 AM - 11:00 PM",
"best_time": "Early morning or sunset",
"booking_required": True
},
{
"name": "Louvre Museum Guided Tour",
"category": "Sightseeing",
"duration": "3 hours",
"price": "$55",
"rating": 4.7,
"description": "Expert-guided tour covering masterpieces including Mona Lisa and Venus de Milo.",
"availability": "Daily except Tuesdays, 9:00 AM entry",
"best_time": "Morning entry recommended",
"booking_required": True
},
{
"name": "Seine River Cruise",
"category": "Sightseeing",
"duration": "1 hour",
"price": "$18",
"rating": 4.6,
"description": "Scenic cruise past Notre-Dame, Eiffel Tower, and historic bridges.",
"availability": "Every 30 minutes, 10:00 AM - 10:00 PM",
"best_time": "Evening for illuminated monuments",
"booking_required": False
},
{
"name": "Musée d'Orsay Visit",
"category": "Culture",
"duration": "2-3 hours",
"price": "$16",
"rating": 4.7,
"description": "Impressionist masterpieces in a stunning Beaux-Arts railway station.",
"availability": "Tuesday-Sunday 9:30 AM - 6:00 PM",
"best_time": "Weekday mornings",
"booking_required": True
},
{
"name": "Versailles Palace Day Trip",
"category": "Culture",
"duration": "5-6 hours",
"price": "$75",
"rating": 4.9,
"description": "Explore the opulent palace and stunning gardens of Louis XIV (includes transport).",
"availability": "Daily except Mondays, 8:00 AM departure",
"best_time": "Full day trip",
"booking_required": True
},
{
"name": "Montmartre Walking Tour",
"category": "Culture",
"duration": "2.5 hours",
"price": "$25",
"rating": 4.6,
"description": "Discover the artistic heart of Paris, including Sacré-Cœur and artists' square.",
"availability": "Daily at 10:00 AM and 2:00 PM",
"best_time": "Morning or late afternoon",
"booking_required": False
},
{
"name": "French Cooking Class",
"category": "Culinary",
"duration": "3 hours",
"price": "$120",
"rating": 4.9,
"description": "Learn to make classic French dishes like coq au vin and crème brûlée, then enjoy your creations.",
"availability": "Tuesday-Saturday, 10:00 AM and 6:00 PM sessions",
"best_time": "Morning or evening sessions",
"booking_required": True
},
{
"name": "Wine & Cheese Tasting",
"category": "Culinary",
"duration": "1.5 hours",
"price": "$65",
"rating": 4.7,
"description": "Sample French wines and artisanal cheeses with expert sommelier guidance.",
"availability": "Daily at 5:00 PM and 7:30 PM",
"best_time": "Evening sessions",
"booking_required": True
},
{
"name": "Food Market Tour",
"category": "Culinary",
"duration": "2 hours",
"price": "$45",
"rating": 4.6,
"description": "Explore authentic Parisian markets and taste local specialties like cheeses, pastries, and charcuterie.",
"availability": "Tuesday, Thursday, Saturday mornings",
"best_time": "Morning (markets are freshest)",
"booking_required": False
}
]
if category:
activities = [act for act in all_activities if act["category"] == category]
else:
activities = all_activities
else:
activities = [
{
"name": "City Walking Tour",
"category": "Sightseeing",
"duration": "3 hours",
"price": "$45",
"rating": 4.7,
"description": "Explore the historic downtown area with an expert guide",
"availability": "Daily at 10:00 AM and 2:00 PM"
}
]
return json.dumps({
"location": location,
"date": date,
"category": category,
"activities_found": len(activities),
"activities": activities,
"note": "Activity search results for Paris with sightseeing, culture, and culinary options"
})
# Mock activity details tool
@ai_function(name="get_activity_details", description="Get detailed information about a specific activity.")
def get_activity_details(
activity_name: Annotated[str, Field(description="Name of the activity to get details for.")],
) -> str:
"""Get detailed information about a specific activity.
Returns:
JSON string containing detailed activity information including description, duration,
price, included items, meeting point, what to bring, cancellation policy, and reviews.
"""
# Paris-specific activity details
activity_details_map = {
"Eiffel Tower Summit Access": {
"name": "Eiffel Tower Summit Access",
"description": "Skip-the-line access to all three levels of the Eiffel Tower, including the summit. Enjoy panoramic views of Paris from 276 meters high.",
"duration": "2-3 hours (self-guided)",
"price": "$35 per person",
"included": ["Skip-the-line ticket", "Access to all 3 levels", "Summit access", "Audio guide app"],
"meeting_point": "Eiffel Tower South Pillar entrance, look for priority access line",
"what_to_bring": ["Photo ID", "Comfortable shoes", "Camera", "Light jacket (summit can be windy)"],
"cancellation_policy": "Free cancellation up to 24 hours in advance",
"languages": ["English", "French", "Spanish", "German", "Italian"],
"max_group_size": "No limit",
"rating": 4.8,
"reviews_count": 15234
},
"Louvre Museum Guided Tour": {
"name": "Louvre Museum Guided Tour",
"description": "Expert-guided tour of the world's largest art museum, focusing on must-see masterpieces including Mona Lisa, Venus de Milo, and Winged Victory.",
"duration": "3 hours",
"price": "$55 per person",
"included": ["Skip-the-line entry", "Expert art historian guide", "Headsets for groups over 6", "Museum highlights map"],
"meeting_point": "Glass Pyramid main entrance, look for guide with 'Louvre Tours' sign",
"what_to_bring": ["Photo ID", "Comfortable shoes", "Camera (no flash)", "Water bottle"],
"cancellation_policy": "Free cancellation up to 48 hours in advance",
"languages": ["English", "French", "Spanish"],
"max_group_size": 20,
"rating": 4.7,
"reviews_count": 8921
},
"French Cooking Class": {
"name": "French Cooking Class",
"description": "Hands-on cooking experience where you'll learn to prepare classic French dishes like coq au vin, ratatouille, and crème brûlée under expert chef guidance.",
"duration": "3 hours",
"price": "$120 per person",
"included": ["All ingredients", "Chef instruction", "Apron and recipe booklet", "Wine pairing", "Lunch/dinner of your creations"],
"meeting_point": "Le Chef Cooking Studio, 15 Rue du Bac, 7th arrondissement",
"what_to_bring": ["Appetite", "Camera for food photos"],
"cancellation_policy": "Free cancellation up to 72 hours in advance",
"languages": ["English", "French"],
"max_group_size": 12,
"rating": 4.9,
"reviews_count": 2341
}
}
details = activity_details_map.get(activity_name, {
"name": activity_name,
"description": "An immersive experience that showcases the best of local culture and attractions.",
"duration": "3 hours",
"price": "$45 per person",
"included": ["Professional guide", "Entry fees"],
"meeting_point": "Central meeting location",
"what_to_bring": ["Comfortable shoes", "Camera"],
"cancellation_policy": "Free cancellation up to 24 hours in advance",
"languages": ["English"],
"max_group_size": 15,
"rating": 4.5,
"reviews_count": 100
})
return json.dumps({
"activity_details": details
})
# Mock booking confirmation tool
@ai_function(name="confirm_booking", description="Confirm a booking reservation.")
def confirm_booking(
booking_type: Annotated[str, Field(description="Type of booking (e.g., 'hotel', 'flight', 'activity').")],
booking_id: Annotated[str, Field(description="Unique booking identifier.")],
customer_info: Annotated[dict, Field(description="Customer information including name and email.")],
) -> str:
"""Confirm a booking reservation.
Returns:
JSON string containing confirmation details including confirmation number,
booking status, customer information, and next steps.
"""
confirmation_number = f"CONF-{booking_type.upper()}-{booking_id}"
confirmation_data = {
"confirmation_number": confirmation_number,
"booking_type": booking_type,
"status": "Confirmed",
"customer_name": customer_info.get("name", "Guest"),
"email": customer_info.get("email", "guest@example.com"),
"confirmation_sent": True,
"next_steps": [
"Check your email for booking details",
"Arrive 30 minutes before scheduled time",
"Bring confirmation number and valid ID"
]
}
return json.dumps({
"confirmation": confirmation_data
})
# Mock hotel availability check tool
@ai_function(name="check_hotel_availability", description="Check availability for hotel rooms.")
def check_hotel_availability(
hotel_name: Annotated[str, Field(description="Name of the hotel to check availability for.")],
check_in: Annotated[str, Field(description="Check-in date (e.g., 'December 15, 2025').")],
check_out: Annotated[str, Field(description="Check-out date (e.g., 'December 18, 2025').")],
rooms: Annotated[int, Field(description="Number of rooms needed.")] = 1,
) -> str:
"""Check availability for hotel rooms.
Sample Date format: "December 15, 2025"
Returns:
JSON string containing availability status, available rooms count, price per night,
and last checked timestamp.
"""
availability_status = "Available"
availability_data = {
"service_type": "hotel",
"hotel_name": hotel_name,
"check_in": check_in,
"check_out": check_out,
"rooms_requested": rooms,
"status": availability_status,
"available_rooms": 8,
"price_per_night": "$185",
"last_checked": datetime.now().isoformat()
}
return json.dumps({
"availability": availability_data
})
# Mock flight availability check tool
@ai_function(name="check_flight_availability", description="Check availability for flight seats.")
def check_flight_availability(
flight_number: Annotated[str, Field(description="Flight number to check availability for.")],
date: Annotated[str, Field(description="Flight date (e.g., 'December 15, 2025').")],
passengers: Annotated[int, Field(description="Number of passengers.")] = 1,
) -> str:
"""Check availability for flight seats.
Sample Date format: "December 15, 2025"
Returns:
JSON string containing availability status, available seats count, price per passenger,
and last checked timestamp.
"""
availability_status = "Available"
availability_data = {
"service_type": "flight",
"flight_number": flight_number,
"date": date,
"passengers_requested": passengers,
"status": availability_status,
"available_seats": 45,
"price_per_passenger": "$520",
"last_checked": datetime.now().isoformat()
}
return json.dumps({
"availability": availability_data
})
# Mock activity availability check tool
@ai_function(name="check_activity_availability", description="Check availability for activity bookings.")
def check_activity_availability(
activity_name: Annotated[str, Field(description="Name of the activity to check availability for.")],
date: Annotated[str, Field(description="Activity date (e.g., 'December 16, 2025').")],
participants: Annotated[int, Field(description="Number of participants.")] = 1,
) -> str:
"""Check availability for activity bookings.
Sample Date format: "December 16, 2025"
Returns:
JSON string containing availability status, available spots count, price per person,
and last checked timestamp.
"""
availability_status = "Available"
availability_data = {
"service_type": "activity",
"activity_name": activity_name,
"date": date,
"participants_requested": participants,
"status": availability_status,
"available_spots": 15,
"price_per_person": "$45",
"last_checked": datetime.now().isoformat()
}
return json.dumps({
"availability": availability_data
})
# Mock payment processing tool
@ai_function(name="process_payment", description="Process payment for a booking.")
def process_payment(
amount: Annotated[float, Field(description="Payment amount.")],
currency: Annotated[str, Field(description="Currency code (e.g., 'USD', 'EUR').")],
payment_method: Annotated[dict, Field(description="Payment method details (type, card info).")],
booking_reference: Annotated[str, Field(description="Booking reference number for the payment.")],
) -> str:
"""Process payment for a booking.
Returns:
JSON string containing payment result with transaction ID, status, amount, currency,
payment method details, and receipt URL.
"""
transaction_id = f"TXN-{datetime.now().strftime('%Y%m%d%H%M%S')}"
payment_result = {
"transaction_id": transaction_id,
"amount": amount,
"currency": currency,
"status": "Success",
"payment_method": payment_method.get("type", "Credit Card"),
"last_4_digits": payment_method.get("last_4", "****"),
"booking_reference": booking_reference,
"timestamp": datetime.now().isoformat(),
"receipt_url": f"https://payments.travelagency.com/receipt/{transaction_id}"
}
return json.dumps({
"payment_result": payment_result
})
# Mock payment validation tool
@ai_function(name="validate_payment_method", description="Validate a payment method before processing.")
def validate_payment_method(
payment_method: Annotated[dict, Field(description="Payment method to validate (type, number, expiry, cvv).")],
) -> str:
"""Validate payment method details.
Returns:
JSON string containing validation result with is_valid flag, payment method type,
validation messages, supported currencies, and processing fee information.
"""
method_type = payment_method.get("type", "credit_card")
# Validation logic
is_valid = True
validation_messages = []
if method_type == "credit_card":
if not payment_method.get("number"):
is_valid = False
validation_messages.append("Card number is required")
if not payment_method.get("expiry"):
is_valid = False
validation_messages.append("Expiry date is required")
if not payment_method.get("cvv"):
is_valid = False
validation_messages.append("CVV is required")
validation_result = {
"is_valid": is_valid,
"payment_method_type": method_type,
"validation_messages": validation_messages if not is_valid else ["Payment method is valid"],
"supported_currencies": ["USD", "EUR", "GBP", "JPY"],
"processing_fee": "2.5%"
}
return json.dumps({
"validation_result": validation_result
})

View File

@@ -0,0 +1,440 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Multi-Agent Travel Planning Workflow Evaluation with Multiple Response Tracking
This sample demonstrates a multi-agent travel planning workflow using the Azure AI Client that:
1. Processes travel queries through 7 specialized agents
2. Tracks MULTIPLE response and conversation IDs per agent for evaluation
3. Uses the new Prompt Agents API (V2)
4. Captures complete interaction sequences including multiple invocations
5. Aggregates findings through a travel planning coordinator
WORKFLOW STRUCTURE (7 agents):
- Travel Agent Executor → Hotel Search, Flight Search, Activity Search (fan-out)
- Hotel Search Executor → Booking Information Aggregation Executor
- Flight Search Executor → Booking Information Aggregation Executor
- Booking Information Aggregation Executor → Booking Confirmation Executor
- Booking Confirmation Executor → Booking Payment Executor
- Booking Information Aggregation, Booking Payment, Activity Search → Travel Planning Coordinator (ResearchLead) for final aggregation (fan-in)
Agents:
1. Travel Agent - Main coordinator (no tools to avoid thread conflicts)
2. Hotel Search - Searches hotels with tools
3. Flight Search - Searches flights with tools
4. Activity Search - Searches activities with tools
5. Booking Information Aggregation - Aggregates hotel & flight booking info
6. Booking Confirmation - Confirms bookings with tools
7. Booking Payment - Processes payments with tools
"""
import asyncio
import os
from collections import defaultdict
from _tools import (
check_flight_availability,
check_hotel_availability,
confirm_booking,
get_flight_details,
get_hotel_details,
process_payment,
search_activities,
search_flights,
# Travel planning tools
search_hotels,
validate_payment_method,
)
from agent_framework import (
AgentExecutorResponse,
AgentResponseUpdate,
AgentRunUpdateEvent,
ChatMessage,
Executor,
Role,
WorkflowBuilder,
WorkflowContext,
WorkflowOutputEvent,
executor,
handler,
)
from agent_framework.azure import AzureAIClient
from azure.ai.projects.aio import AIProjectClient
from azure.identity.aio import DefaultAzureCredential
from dotenv import load_dotenv
from typing_extensions import Never
load_dotenv()
@executor(id="start_executor")
async def start_executor(input: str, ctx: WorkflowContext[list[ChatMessage]]) -> None:
"""Initiates the workflow by sending the user query to all specialized agents."""
await ctx.send_message([ChatMessage(role="user", text=input)])
class ResearchLead(Executor):
"""Aggregates and summarizes travel planning findings from all specialized agents."""
def __init__(self, chat_client: AzureAIClient, id: str = "travel-planning-coordinator"):
# store=True to preserve conversation history for evaluation
self.agent = chat_client.as_agent(
id="travel-planning-coordinator",
instructions=(
"You are the final coordinator. You will receive responses from multiple agents: "
"booking-info-aggregation-agent (hotel/flight options), booking-payment-agent (payment confirmation), "
"and activity-search-agent (activities). "
"Review each agent's response, then create a comprehensive travel itinerary organized by: "
"1. Flights 2. Hotels 3. Activities 4. Booking confirmations 5. Payment details. "
"Clearly indicate which information came from which agent. Do not use tools."
),
name="travel-planning-coordinator",
store=True,
)
super().__init__(id=id)
@handler
async def fan_in_handle(self, responses: list[AgentExecutorResponse], ctx: WorkflowContext[Never, str]) -> None:
user_query = responses[0].full_conversation[0].text
# Extract findings from all agent responses
agent_findings = self._extract_agent_findings(responses)
summary_text = (
"\n".join(agent_findings) if agent_findings else "No specific findings were provided by the agents."
)
# Generate comprehensive travel plan summary
messages = [
ChatMessage(
role=Role.SYSTEM,
text="You are a travel planning coordinator. Summarize findings from multiple specialized travel agents and provide a clear, comprehensive travel plan based on the user's query.",
),
ChatMessage(
role=Role.USER,
text=f"Original query: {user_query}\n\nFindings from specialized travel agents:\n{summary_text}\n\nPlease provide a comprehensive travel plan based on these findings.",
),
]
try:
final_response = await self.agent.run(messages)
output_text = (
final_response.messages[-1].text
if final_response.messages and final_response.messages[-1].text
else f"Based on the available findings, here's your travel plan for '{user_query}': {summary_text}"
)
except Exception:
output_text = f"Based on the available findings, here's your travel plan for '{user_query}': {summary_text}"
await ctx.yield_output(output_text)
def _extract_agent_findings(self, responses: list[AgentExecutorResponse]) -> list[str]:
"""Extract findings from agent responses."""
agent_findings = []
for response in responses:
findings = []
if response.agent_response and response.agent_response.messages:
for msg in response.agent_response.messages:
if msg.role == Role.ASSISTANT and msg.text and msg.text.strip():
findings.append(msg.text.strip())
if findings:
combined_findings = " ".join(findings)
agent_findings.append(f"[{response.executor_id}]: {combined_findings}")
return agent_findings
async def run_workflow_with_response_tracking(query: str, chat_client: AzureAIClient | None = None) -> dict:
"""Run multi-agent workflow and track conversation IDs, response IDs, and interaction sequence.
Args:
query: The user query to process through the multi-agent workflow
chat_client: Optional AzureAIClient instance
Returns:
Dictionary containing interaction sequence, conversation/response IDs, and conversation analysis
"""
if chat_client is None:
try:
# Create AIProjectClient with the correct API version for V2 prompt agents
project_client = AIProjectClient(
endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
credential=credential,
api_version="2025-11-15-preview",
)
async with (
DefaultAzureCredential() as credential,
project_client,
AzureAIClient(project_client=project_client, credential=credential) as client,
):
return await _run_workflow_with_client(query, client)
except Exception as e:
print(f"Error during workflow execution: {e}")
raise
else:
return await _run_workflow_with_client(query, chat_client)
async def _run_workflow_with_client(query: str, chat_client: AzureAIClient) -> dict:
"""Execute workflow with given client and track all interactions."""
# Initialize tracking variables - use lists to track multiple responses per agent
conversation_ids = defaultdict(list)
response_ids = defaultdict(list)
workflow_output = None
# Create workflow components and keep agent references
# Pass project_client and credential to create separate client instances per agent
workflow, agent_map = await _create_workflow(chat_client.project_client, chat_client.credential)
# Process workflow events
events = workflow.run_stream(query)
workflow_output = await _process_workflow_events(events, conversation_ids, response_ids)
return {
"conversation_ids": dict(conversation_ids),
"response_ids": dict(response_ids),
"output": workflow_output,
"query": query,
}
async def _create_workflow(project_client, credential):
"""Create the multi-agent travel planning workflow with specialized agents.
IMPORTANT: Each agent needs its own client instance because the V2 client stores
agent_name and agent_version as instance variables, causing all agents to share
the same agent identity if they share a client.
"""
# Create separate client for Final Coordinator
final_coordinator_client = AzureAIClient(
project_client=project_client, credential=credential, agent_name="final-coordinator"
)
final_coordinator = ResearchLead(chat_client=final_coordinator_client, id="final-coordinator")
# Agent 1: Travel Request Handler (initial coordinator)
# Create separate client with unique agent_name
travel_request_handler_client = AzureAIClient(
project_client=project_client, credential=credential, agent_name="travel-request-handler"
)
travel_request_handler = travel_request_handler_client.as_agent(
id="travel-request-handler",
instructions=(
"You receive user travel queries and relay them to specialized agents. Extract key information: destination, dates, budget, and preferences. Pass this information forward clearly to the next agents."
),
name="travel-request-handler",
store=True,
)
# Agent 2: Hotel Search Executor
hotel_search_client = AzureAIClient(
project_client=project_client, credential=credential, agent_name="hotel-search-agent"
)
hotel_search_agent = hotel_search_client.as_agent(
id="hotel-search-agent",
instructions=(
"You are a hotel search specialist. Your task is ONLY to search for and provide hotel information. Use search_hotels to find options, get_hotel_details for specifics, and check_availability to verify rooms. Output format: List hotel names, prices per night, total cost for the stay, locations, ratings, amenities, and addresses. IMPORTANT: Only provide hotel information without additional commentary."
),
name="hotel-search-agent",
tools=[search_hotels, get_hotel_details, check_hotel_availability],
store=True,
)
# Agent 3: Flight Search Executor
flight_search_client = AzureAIClient(
project_client=project_client, credential=credential, agent_name="flight-search-agent"
)
flight_search_agent = flight_search_client.as_agent(
id="flight-search-agent",
instructions=(
"You are a flight search specialist. Your task is ONLY to search for and provide flight information. Use search_flights to find options, get_flight_details for specifics, and check_availability for seats. Output format: List flight numbers, airlines, departure/arrival times, prices, durations, and cabin class. IMPORTANT: Only provide flight information without additional commentary."
),
name="flight-search-agent",
tools=[search_flights, get_flight_details, check_flight_availability],
store=True,
)
# Agent 4: Activity Search Executor
activity_search_client = AzureAIClient(
project_client=project_client, credential=credential, agent_name="activity-search-agent"
)
activity_search_agent = activity_search_client.as_agent(
id="activity-search-agent",
instructions=(
"You are an activities specialist. Your task is ONLY to search for and provide activity information. Use search_activities to find options for activities. Output format: List activity names, descriptions, prices, durations, ratings, and categories. IMPORTANT: Only provide activity information without additional commentary."
),
name="activity-search-agent",
tools=[search_activities],
store=True,
)
# Agent 5: Booking Confirmation Executor
booking_confirmation_client = AzureAIClient(
project_client=project_client, credential=credential, agent_name="booking-confirmation-agent"
)
booking_confirmation_agent = booking_confirmation_client.as_agent(
id="booking-confirmation-agent",
instructions=(
"You confirm bookings. Use check_hotel_availability and check_flight_availability to verify slots, then confirm_booking to finalize. Provide ONLY: confirmation numbers, booking references, and confirmation status."
),
name="booking-confirmation-agent",
tools=[confirm_booking, check_hotel_availability, check_flight_availability],
store=True,
)
# Agent 6: Booking Payment Executor
booking_payment_client = AzureAIClient(
project_client=project_client, credential=credential, agent_name="booking-payment-agent"
)
booking_payment_agent = booking_payment_client.as_agent(
id="booking-payment-agent",
instructions=(
"You process payments. Use validate_payment_method to verify payment, then process_payment to complete transactions. Provide ONLY: payment confirmation status, transaction IDs, and payment amounts."
),
name="booking-payment-agent",
tools=[process_payment, validate_payment_method],
store=True,
)
# Agent 7: Booking Information Aggregation Executor
booking_info_client = AzureAIClient(
project_client=project_client, credential=credential, agent_name="booking-info-aggregation-agent"
)
booking_info_aggregation_agent = booking_info_client.as_agent(
id="booking-info-aggregation-agent",
instructions=(
"You aggregate hotel and flight search results. Receive options from search agents and organize them. Provide: top 2-3 hotel options with prices and top 2-3 flight options with prices in a structured format."
),
name="booking-info-aggregation-agent",
store=True,
)
# Build workflow with logical booking flow:
# 1. start_executor → travel_request_handler
# 2. travel_request_handler → hotel_search, flight_search, activity_search (fan-out)
# 3. hotel_search → booking_info_aggregation
# 4. flight_search → booking_info_aggregation
# 5. booking_info_aggregation → booking_confirmation
# 6. booking_confirmation → booking_payment
# 7. booking_info_aggregation, booking_payment, activity_search → final_coordinator (final aggregation, fan-in)
workflow = (
WorkflowBuilder(name="Travel Planning Workflow")
.set_start_executor(start_executor)
.add_edge(start_executor, travel_request_handler)
.add_fan_out_edges(travel_request_handler, [hotel_search_agent, flight_search_agent, activity_search_agent])
.add_edge(hotel_search_agent, booking_info_aggregation_agent)
.add_edge(flight_search_agent, booking_info_aggregation_agent)
.add_edge(booking_info_aggregation_agent, booking_confirmation_agent)
.add_edge(booking_confirmation_agent, booking_payment_agent)
.add_fan_in_edges(
[booking_info_aggregation_agent, booking_payment_agent, activity_search_agent], final_coordinator
)
.build()
)
# Return workflow and agent map for thread ID extraction
agent_map = {
"travel_request_handler": travel_request_handler,
"hotel-search-agent": hotel_search_agent,
"flight-search-agent": flight_search_agent,
"activity-search-agent": activity_search_agent,
"booking-confirmation-agent": booking_confirmation_agent,
"booking-payment-agent": booking_payment_agent,
"booking-info-aggregation-agent": booking_info_aggregation_agent,
"final-coordinator": final_coordinator.agent,
}
return workflow, agent_map
async def _process_workflow_events(events, conversation_ids, response_ids):
"""Process workflow events and track interactions."""
workflow_output = None
async for event in events:
if isinstance(event, WorkflowOutputEvent):
workflow_output = event.data
# Handle Unicode characters that may not be displayable in Windows console
try:
print(f"\nWorkflow Output: {event.data}\n")
except UnicodeEncodeError:
output_str = str(event.data).encode("ascii", "replace").decode("ascii")
print(f"\nWorkflow Output: {output_str}\n")
elif isinstance(event, AgentRunUpdateEvent):
_track_agent_ids(event, event.executor_id, response_ids, conversation_ids)
return workflow_output
def _track_agent_ids(event, agent, response_ids, conversation_ids):
"""Track agent response and conversation IDs - supporting multiple responses per agent."""
if isinstance(event.data, AgentResponseUpdate):
# Check for conversation_id and response_id from raw_representation
# V2 API stores conversation_id directly on raw_representation (ChatResponseUpdate)
if hasattr(event.data, "raw_representation") and event.data.raw_representation:
raw = event.data.raw_representation
# Try conversation_id directly on raw representation
if hasattr(raw, "conversation_id") and raw.conversation_id:
# Only add if not already in the list
if raw.conversation_id not in conversation_ids[agent]:
conversation_ids[agent].append(raw.conversation_id)
# Extract response_id from the OpenAI event (available from first event)
if hasattr(raw, "raw_representation") and raw.raw_representation:
openai_event = raw.raw_representation
# Check if event has response object with id
if hasattr(openai_event, "response") and hasattr(openai_event.response, "id"):
# Only add if not already in the list
if openai_event.response.id not in response_ids[agent]:
response_ids[agent].append(openai_event.response.id)
async def create_and_run_workflow():
"""Run the workflow evaluation and display results.
Returns:
Dictionary containing agents data with conversation IDs, response IDs, and query information
"""
example_queries = [
"Plan a 3-day trip to Paris from December 15-18, 2025. Budget is $2000. Need hotel near Eiffel Tower, round-trip flights from New York JFK, and recommend 2-3 activities per day.",
"Find a budget hotel in Tokyo for January 5-10, 2026 under $150/night near Shibuya station, book activities including a sushi making class",
"Search for round-trip flights from Los Angeles to London departing March 20, 2026, returning March 27, 2026. Economy class, 2 passengers. Recommend tourist attractions and museums.",
]
query = example_queries[0]
print(f"Query: {query}\n")
result = await run_workflow_with_response_tracking(query)
# Create output data structure
output_data = {"agents": {}, "query": result["query"], "output": result.get("output", "")}
# Create agent-specific mappings - now with lists of IDs
all_agents = set(result["conversation_ids"].keys()) | set(result["response_ids"].keys())
for agent_name in all_agents:
output_data["agents"][agent_name] = {
"conversation_ids": result["conversation_ids"].get(agent_name, []),
"response_ids": result["response_ids"].get(agent_name, []),
"response_count": len(result["response_ids"].get(agent_name, [])),
}
print(f"\nTotal agents tracked: {len(output_data['agents'])}")
# Print summary of multiple responses
print("\n=== Multi-Response Summary ===")
for agent_name, agent_data in output_data["agents"].items():
response_count = agent_data["response_count"]
print(f"{agent_name}: {response_count} response(s)")
return output_data
if __name__ == "__main__":
asyncio.run(create_and_run_workflow())

View File

@@ -0,0 +1,220 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Script to run multi-agent travel planning workflow and evaluate agent responses.
This script:
1. Executes the multi-agent workflow
2. Displays response data summary
3. Creates and runs evaluation with multiple evaluators
4. Monitors evaluation progress and displays results
"""
import asyncio
import os
import time
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv
from create_workflow import create_and_run_workflow
def print_section(title: str):
"""Print a formatted section header."""
print(f"\n{'='*80}")
print(f"{title}")
print(f"{'='*80}")
async def run_workflow():
"""Execute the multi-agent travel planning workflow.
Returns:
Dictionary containing workflow data with agent response IDs
"""
print_section("Step 1: Running Workflow")
print("Executing multi-agent travel planning workflow...")
print("This may take a few minutes...")
workflow_data = await create_and_run_workflow()
print("Workflow execution completed")
return workflow_data
def display_response_summary(workflow_data: dict):
"""Display summary of response data."""
print_section("Step 2: Response Data Summary")
print(f"Query: {workflow_data['query']}")
print(f"\nAgents tracked: {len(workflow_data['agents'])}")
for agent_name, agent_data in workflow_data['agents'].items():
response_count = agent_data['response_count']
print(f" {agent_name}: {response_count} response(s)")
def fetch_agent_responses(openai_client, workflow_data: dict, agent_names: list):
"""Fetch and display final responses from specified agents."""
print_section("Step 3: Fetching Agent Responses")
for agent_name in agent_names:
if agent_name not in workflow_data['agents']:
continue
agent_data = workflow_data['agents'][agent_name]
if not agent_data['response_ids']:
continue
final_response_id = agent_data['response_ids'][-1]
print(f"\n{agent_name}")
print(f" Response ID: {final_response_id}")
try:
response = openai_client.responses.retrieve(response_id=final_response_id)
content = response.output[-1].content[-1].text
truncated = content[:300] + "..." if len(content) > 300 else content
print(f" Content preview: {truncated}")
except Exception as e:
print(f" Error: {e}")
def create_evaluation(openai_client, model_deployment: str):
"""Create evaluation with multiple evaluators."""
print_section("Step 4: Creating Evaluation")
data_source_config = {"type": "azure_ai_source", "scenario": "responses"}
testing_criteria = [
{
"type": "azure_ai_evaluator",
"name": "relevance",
"evaluator_name": "builtin.relevance",
"initialization_parameters": {"deployment_name": model_deployment}
},
{
"type": "azure_ai_evaluator",
"name": "groundedness",
"evaluator_name": "builtin.groundedness",
"initialization_parameters": {"deployment_name": model_deployment}
},
{
"type": "azure_ai_evaluator",
"name": "tool_call_accuracy",
"evaluator_name": "builtin.tool_call_accuracy",
"initialization_parameters": {"deployment_name": model_deployment}
},
{
"type": "azure_ai_evaluator",
"name": "tool_output_utilization",
"evaluator_name": "builtin.tool_output_utilization",
"initialization_parameters": {"deployment_name": model_deployment}
},
]
eval_object = openai_client.evals.create(
name="Travel Workflow Multi-Evaluator Assessment",
data_source_config=data_source_config,
testing_criteria=testing_criteria,
)
evaluator_names = [criterion["name"] for criterion in testing_criteria]
print(f"Evaluation created: {eval_object.id}")
print(f"Evaluators ({len(evaluator_names)}): {', '.join(evaluator_names)}")
return eval_object
def run_evaluation(openai_client, eval_object, workflow_data: dict, agent_names: list):
"""Run evaluation on selected agent responses."""
print_section("Step 5: Running Evaluation")
selected_response_ids = []
for agent_name in agent_names:
if agent_name in workflow_data['agents']:
agent_data = workflow_data['agents'][agent_name]
if agent_data['response_ids']:
selected_response_ids.append(agent_data['response_ids'][-1])
print(f"Selected {len(selected_response_ids)} responses for evaluation")
data_source = {
"type": "azure_ai_responses",
"item_generation_params": {
"type": "response_retrieval",
"data_mapping": {"response_id": "{{item.resp_id}}"},
"source": {
"type": "file_content",
"content": [{"item": {"resp_id": resp_id}} for resp_id in selected_response_ids]
},
},
}
eval_run = openai_client.evals.runs.create(
eval_id=eval_object.id,
name="Multi-Agent Response Evaluation",
data_source=data_source
)
print(f"Evaluation run created: {eval_run.id}")
return eval_run
def monitor_evaluation(openai_client, eval_object, eval_run):
"""Monitor evaluation progress and display results."""
print_section("Step 6: Monitoring Evaluation")
print("Waiting for evaluation to complete...")
while eval_run.status not in ["completed", "failed"]:
eval_run = openai_client.evals.runs.retrieve(
run_id=eval_run.id,
eval_id=eval_object.id
)
print(f"Status: {eval_run.status}")
time.sleep(5)
if eval_run.status == "completed":
print("\nEvaluation completed successfully")
print(f"Result counts: {eval_run.result_counts}")
print(f"\nReport URL: {eval_run.report_url}")
else:
print("\nEvaluation failed")
async def main():
"""Main execution flow."""
load_dotenv()
print("Travel Planning Workflow Evaluation")
workflow_data = await run_workflow()
display_response_summary(workflow_data)
project_client = AIProjectClient(
endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
credential=DefaultAzureCredential(),
api_version="2025-11-15-preview"
)
openai_client = project_client.get_openai_client()
agents_to_evaluate = ["hotel-search-agent", "flight-search-agent", "activity-search-agent"]
fetch_agent_responses(openai_client, workflow_data, agents_to_evaluate)
model_deployment = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "gpt-4o-mini")
eval_object = create_evaluation(openai_client, model_deployment)
eval_run = run_evaluation(openai_client, eval_object, workflow_data, agents_to_evaluate)
monitor_evaluation(openai_client, eval_object, eval_run)
print_section("Complete")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,42 @@
# Agent Examples
This folder contains examples demonstrating how to create and use agents with different chat clients from the Agent Framework. Each sub-folder focuses on a specific provider and client type, showing various capabilities like function tools, code interpreter, thread management, structured outputs, image processing, web search, Model Context Protocol (MCP) integration, and more.
## Examples by Provider
### Azure AI Foundry Examples
| Folder | Description |
|--------|-------------|
| **[`azure_ai_agent/`](azure_ai_agent/)** | Create agents using Azure AI Agent Service (based on `azure-ai-agents` V1 package) including function tools, code interpreter, MCP integration, thread management, and more. |
| **[`azure_ai/`](azure_ai/)** | Create agents using Azure AI Agent Service (based on `azure-ai-projects` [V2](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/CHANGELOG.md#200b1-2025-11-11) package) including function tools, code interpreter, MCP integration, thread management, and more. |
### Microsoft Copilot Studio Examples
| Folder | Description |
|--------|-------------|
| **[`copilotstudio/`](copilotstudio/)** | Create agents using Microsoft Copilot Studio with streaming and non-streaming responses, authentication handling, and explicit configuration options |
### Azure OpenAI Examples
| Folder | Description |
|--------|-------------|
| **[`azure_openai/`](azure_openai/)** | Create agents using Azure OpenAI APIs with multiple client types (Assistants, Chat, and Responses clients) supporting function tools, code interpreter, thread management, and more |
### OpenAI Examples
| Folder | Description |
|--------|-------------|
| **[`openai/`](openai/)** | Create agents using OpenAI APIs with comprehensive examples including Assistants, Chat, and Responses clients featuring function tools, code interpreter, file search, web search, MCP integration, image analysis/generation, structured outputs, reasoning, and thread management |
### Anthropic Examples
| Folder | Description |
|--------|-------------|
| **[`anthropic/`](anthropic/)** | Create agents using Anthropic models through OpenAI Chat Client configuration, demonstrating tool calling capabilities |
### Custom Implementation Examples
| Folder | Description |
|--------|-------------|
| **[`custom/`](custom/)** | Create custom agents and chat clients by extending the base framework classes, showing complete control over agent behavior and backend integration |

View File

@@ -0,0 +1,31 @@
# A2A Agent Examples
This folder contains examples demonstrating how to create and use agents with the A2A (Agent2Agent) protocol from the `agent_framework` package to communicate with remote A2A agents.
For more information about the A2A protocol specification, visit: https://a2a-protocol.org/latest/
## Examples
| File | Description |
|------|-------------|
| [`agent_with_a2a.py`](agent_with_a2a.py) | The simplest way to connect to and use a single A2A agent. Demonstrates agent discovery via agent cards and basic message exchange using the A2A protocol. |
## Environment Variables
Make sure to set the following environment variables before running the example:
### Required
- `A2A_AGENT_HOST`: URL of a single A2A agent (for simple sample, e.g., `http://localhost:5001/`)
## Quick Testing with .NET A2A Servers
For quick testing and demonstration, you can use the pre-built .NET A2A servers from this repository:
**Quick Testing Reference**: Use the .NET A2A Client Server sample at:
`..\agent-framework\dotnet\samples\A2AClientServer`
### Run Python A2A Sample
```powershell
# Simple A2A sample (single agent)
uv run python agent_with_a2a.py
```

View File

@@ -0,0 +1,78 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
import httpx
from a2a.client import A2ACardResolver
from agent_framework.a2a import A2AAgent
"""
Agent2Agent (A2A) Protocol Integration Sample
This sample demonstrates how to connect to and communicate with external agents using
the A2A protocol. A2A is a standardized communication protocol that enables interoperability
between different agent systems, allowing agents built with different frameworks and
technologies to communicate seamlessly.
For more information about the A2A protocol specification, visit: https://a2a-protocol.org/latest/
Key concepts demonstrated:
- Discovering A2A-compliant agents using AgentCard resolution
- Creating A2AAgent instances to wrap external A2A endpoints
- Converting Agent Framework messages to A2A protocol format
- Handling A2A responses (Messages and Tasks) back to framework types
To run this sample:
1. Set the A2A_AGENT_HOST environment variable to point to an A2A-compliant agent endpoint
Example: export A2A_AGENT_HOST="https://your-a2a-agent.example.com"
2. Ensure the target agent exposes its AgentCard at /.well-known/agent.json
3. Run: uv run python agent_with_a2a.py
The sample will:
- Connect to the specified A2A agent endpoint
- Retrieve and parse the agent's capabilities via its AgentCard
- Send a message using the A2A protocol
- Display the agent's response
Visit the README.md for more details on setting up and running A2A agents.
"""
async def main():
"""Demonstrates connecting to and communicating with an A2A-compliant agent."""
# Get A2A agent host from environment
a2a_agent_host = os.getenv("A2A_AGENT_HOST")
if not a2a_agent_host:
raise ValueError("A2A_AGENT_HOST environment variable is not set")
print(f"Connecting to A2A agent at: {a2a_agent_host}")
# Initialize A2ACardResolver
async with httpx.AsyncClient(timeout=60.0) as http_client:
resolver = A2ACardResolver(httpx_client=http_client, base_url=a2a_agent_host)
# Get agent card
agent_card = await resolver.get_agent_card()
print(f"Found agent: {agent_card.name} - {agent_card.description}")
# Create A2A agent instance
agent = A2AAgent(
name=agent_card.name,
description=agent_card.description,
agent_card=agent_card,
url=a2a_agent_host,
)
# Invoke the agent and output the result
print("\nSending message to A2A agent...")
response = await agent.run("What are your capabilities?")
# Print the response
print("\nAgent Response:")
for message in response.messages:
print(message.text)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,24 @@
# Anthropic Examples
This folder contains examples demonstrating how to use Anthropic's Claude models with the Agent Framework.
## Examples
| File | Description |
|------|-------------|
| [`anthropic_basic.py`](anthropic_basic.py) | Demonstrates how to setup a simple agent using the AnthropicClient, with both streaming and non-streaming responses. |
| [`anthropic_advanced.py`](anthropic_advanced.py) | Shows advanced usage of the AnthropicClient, including hosted tools and `thinking`. |
| [`anthropic_skills.py`](anthropic_skills.py) | Illustrates how to use Anthropic-managed Skills with an agent, including the Code Interpreter tool and file generation and saving. |
| [`anthropic_foundry.py`](anthropic_foundry.py) | Example of using Foundry's Anthropic integration with the Agent Framework. |
## Environment Variables
Set the following environment variables before running the examples:
- `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`)
Or, for Foundry:
- `ANTHROPIC_FOUNDRY_API_KEY`: Your Foundry Anthropic API key
- `ANTHROPIC_FOUNDRY_ENDPOINT`: The endpoint URL for your Foundry Anthropic resource
- `ANTHROPIC_CHAT_MODEL_ID`: The Claude model to use in Foundry (e.g., `claude-haiku-4-5`)

View File

@@ -0,0 +1,54 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import HostedMCPTool, HostedWebSearchTool, TextReasoningContent, UsageContent
from agent_framework.anthropic import AnthropicChatOptions, AnthropicClient
"""
Anthropic Chat Agent Example
This sample demonstrates using Anthropic with:
- Setting up an Anthropic-based agent with hosted tools.
- Using the `thinking` feature.
- Displaying both thinking and usage information during streaming responses.
"""
async def main() -> None:
"""Example of streaming response (get results as they are generated)."""
agent = AnthropicClient[AnthropicChatOptions]().as_agent(
name="DocsAgent",
instructions="You are a helpful agent for both Microsoft docs questions and general questions.",
tools=[
HostedMCPTool(
name="Microsoft Learn MCP",
url="https://learn.microsoft.com/api/mcp",
),
HostedWebSearchTool(),
],
default_options={
# anthropic needs a value for the max_tokens parameter
# we set it to 1024, but you can override like this:
"max_tokens": 20000,
"thinking": {"type": "enabled", "budget_tokens": 10000},
},
)
query = "Can you compare Python decorators with C# attributes?"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
async for chunk in agent.run_stream(query):
for content in chunk.contents:
if isinstance(content, TextReasoningContent):
print(f"\033[32m{content.text}\033[0m", end="", flush=True)
if isinstance(content, UsageContent):
print(f"\n\033[34m[Usage so far: {content.usage_details}]\033[0m\n", end="", flush=True)
if chunk.text:
print(chunk.text, end="", flush=True)
print("\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,69 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from random import randint
from typing import Annotated
from agent_framework.anthropic import AnthropicClient
"""
Anthropic Chat Agent Example
This sample demonstrates using Anthropic with an agent and a single custom tool.
"""
def get_weather(
location: Annotated[str, "The location to get the weather for."],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def non_streaming_example() -> None:
"""Example of non-streaming response (get the complete result at once)."""
print("=== Non-streaming Response Example ===")
agent = AnthropicClient(
).as_agent(
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
query = "What's the weather like in Seattle?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Result: {result}\n")
async def streaming_example() -> None:
"""Example of streaming response (get results as they are generated)."""
print("=== Streaming Response Example ===")
agent = AnthropicClient(
).as_agent(
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
query = "What's the weather like in Portland and in Paris?"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
async for chunk in agent.run_stream(query):
if chunk.text:
print(chunk.text, end="", flush=True)
print("\n")
async def main() -> None:
print("=== Anthropic Example ===")
await streaming_example()
await non_streaming_example()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,65 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import HostedMCPTool, HostedWebSearchTool, TextReasoningContent, UsageContent
from agent_framework.anthropic import AnthropicClient
from anthropic import AsyncAnthropicFoundry
"""
Anthropic Foundry Chat Agent Example
This sample demonstrates using Anthropic with:
- Setting up an Anthropic-based agent with hosted tools.
- Using the `thinking` feature.
- Displaying both thinking and usage information during streaming responses.
This example requires `anthropic>=0.74.0` and an endpoint in Foundry for Anthropic.
To use the Foundry integration ensure you have the following environment variables set:
- ANTHROPIC_FOUNDRY_API_KEY
Alternatively you can pass in a azure_ad_token_provider function to the AsyncAnthropicFoundry constructor.
- ANTHROPIC_FOUNDRY_ENDPOINT
Should be something like https://<your-resource-name>.services.ai.azure.com/anthropic/
- ANTHROPIC_CHAT_MODEL_ID
Should be something like claude-haiku-4-5
"""
async def main() -> None:
"""Example of streaming response (get results as they are generated)."""
agent = AnthropicClient(anthropic_client=AsyncAnthropicFoundry()).as_agent(
name="DocsAgent",
instructions="You are a helpful agent for both Microsoft docs questions and general questions.",
tools=[
HostedMCPTool(
name="Microsoft Learn MCP",
url="https://learn.microsoft.com/api/mcp",
),
HostedWebSearchTool(),
],
default_options={
# anthropic needs a value for the max_tokens parameter
# we set it to 1024, but you can override like this:
"max_tokens": 20000,
"thinking": {"type": "enabled", "budget_tokens": 10000},
},
)
query = "Can you compare Python decorators with C# attributes?"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
async for chunk in agent.run_stream(query):
for content in chunk.contents:
if isinstance(content, TextReasoningContent):
print(f"\033[32m{content.text}\033[0m", end="", flush=True)
if isinstance(content, UsageContent):
print(f"\n\033[34m[Usage so far: {content.usage_details}]\033[0m\n", end="", flush=True)
if chunk.text:
print(chunk.text, end="", flush=True)
print("\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,88 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import logging
from pathlib import Path
from agent_framework import HostedCodeInterpreterTool, HostedFileContent
from agent_framework.anthropic import AnthropicChatOptions, AnthropicClient
logger = logging.getLogger(__name__)
"""
Anthropic Skills Agent Example
This sample demonstrates using Anthropic with:
- Listing and using Anthropic-managed Skills.
- One approach to add additional beta flags.
You can also set additonal_chat_options with "additional_beta_flags" per request.
- Creating an agent with the Code Interpreter tool and a Skill.
- Catching and downloading generated files from the agent.
"""
async def main() -> None:
"""Example of streaming response (get results as they are generated)."""
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"])
for skill in skills.data:
print(f"{skill.source}: {skill.id} (version: {skill.latest_version})")
# Create a agent with the pptx skill enabled
# Skills also need the code interpreter tool to function
agent = client.as_agent(
name="DocsAgent",
instructions="You are a helpful agent for creating powerpoint presentations.",
tools=HostedCodeInterpreterTool(),
default_options={
"max_tokens": 20000,
"thinking": {"type": "enabled", "budget_tokens": 10000},
"container": {"skills": [{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]},
},
)
print(
"The agent output will use the following colors:\n"
"\033[0mUser: (default)\033[0m\n"
"\033[0mAgent: (default)\033[0m\n"
"\033[32mAgent Reasoning: (green)\033[0m\n"
"\033[34mUsage: (blue)\033[0m\n"
)
query = "Create a presentation about renewable energy with 5 slides"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
files: list[HostedFileContent] = []
async for chunk in agent.run_stream(query):
for content in chunk.contents:
match content.type:
case "text":
print(content.text, end="", flush=True)
case "text_reasoning":
print(f"\033[32m{content.text}\033[0m", end="", flush=True)
case "usage":
print(f"\n\033[34m[Usage so far: {content.usage_details}]\033[0m\n", end="", flush=True)
case "hosted_file":
# Catch generated files
files.append(content)
case _:
logger.debug("Unhandled content type: %s", content.type)
pass
print("\n")
if files:
# Save to a new file (will be in the folder where you are running this script)
# When running this sample multiple times, the files will be overritten
# 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_id=file.file_id, betas=["files-api-2025-04-14"]
)
with open(Path(__file__).parent / f"renewable_energy-{idx}.pptx", "wb") as f:
await file_content.write_to_file(f.name)
print(f"File {idx}: renewable_energy-{idx}.pptx saved to disk.")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,95 @@
# Azure AI Agent Examples
This folder contains examples demonstrating different ways to create and use agents with the Azure AI client from the `agent_framework.azure` package. These examples use the `AzureAIClient` with the `azure-ai-projects` 2.x (V2) API surface (see [changelog](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/CHANGELOG.md#200b1-2025-11-11)). For V1 (`azure-ai-agents` 1.x) samples using `AzureAIAgentClient`, see the [Azure AI V1 examples folder](../azure_ai_agent/).
## Examples
| File | Description |
|------|-------------|
| [`azure_ai_basic.py`](azure_ai_basic.py) | The simplest way to create an agent using `AzureAIProjectAgentProvider`. Demonstrates both streaming and non-streaming responses with function tools. Shows automatic agent creation and basic weather functionality. |
| [`azure_ai_provider_methods.py`](azure_ai_provider_methods.py) | Comprehensive guide to `AzureAIProjectAgentProvider` methods: `create_agent()` for creating new agents, `get_agent()` for retrieving existing agents (by name, reference, or details), and `as_agent()` for wrapping SDK objects without HTTP calls. |
| [`azure_ai_use_latest_version.py`](azure_ai_use_latest_version.py) | Demonstrates how to reuse the latest version of an existing agent instead of creating a new agent version on each instantiation by using `provider.get_agent()` to retrieve the latest version. |
| [`azure_ai_with_agent_as_tool.py`](azure_ai_with_agent_as_tool.py) | Shows how to use the agent-as-tool pattern with Azure AI agents, where one agent delegates work to specialized sub-agents wrapped as tools using `as_tool()`. Demonstrates hierarchical agent architectures. |
| [`azure_ai_with_agent_to_agent.py`](azure_ai_with_agent_to_agent.py) | Shows how to use Agent-to-Agent (A2A) capabilities with Azure AI agents to enable communication with other agents using the A2A protocol. Requires an A2A connection configured in your Azure AI project. |
| [`azure_ai_with_azure_ai_search.py`](azure_ai_with_azure_ai_search.py) | Shows how to use Azure AI Search with Azure AI agents to search through indexed data and answer user questions with proper citations. Requires an Azure AI Search connection and index configured in your Azure AI project. |
| [`azure_ai_with_bing_grounding.py`](azure_ai_with_bing_grounding.py) | Shows how to use Bing Grounding search with Azure AI agents to search the web for current information and provide grounded responses with citations. Requires a Bing connection configured in your Azure AI project. |
| [`azure_ai_with_bing_custom_search.py`](azure_ai_with_bing_custom_search.py) | Shows how to use Bing Custom Search with Azure AI agents to search custom search instances and provide responses with relevant results. Requires a Bing Custom Search connection and instance configured in your Azure AI project. |
| [`azure_ai_with_browser_automation.py`](azure_ai_with_browser_automation.py) | Shows how to use Browser Automation with Azure AI agents to perform automated web browsing tasks and provide responses based on web interactions. Requires a Browser Automation connection configured in your Azure AI project. |
| [`azure_ai_with_code_interpreter.py`](azure_ai_with_code_interpreter.py) | Shows how to use the `HostedCodeInterpreterTool` with Azure AI agents to write and execute Python code for mathematical problem solving and data analysis. |
| [`azure_ai_with_code_interpreter_file_generation.py`](azure_ai_with_code_interpreter_file_generation.py) | Shows how to retrieve file IDs from code interpreter generated files using both streaming and non-streaming approaches. |
| [`azure_ai_with_code_interpreter_file_download.py`](azure_ai_with_code_interpreter_file_download.py) | Shows how to download files generated by code interpreter using the OpenAI containers API. |
| [`azure_ai_with_content_filtering.py`](azure_ai_with_content_filtering.py) | Shows how to enable content filtering (RAI policy) on Azure AI agents using `RaiConfig`. Requires creating an RAI policy in Azure AI Foundry portal first. |
| [`azure_ai_with_existing_agent.py`](azure_ai_with_existing_agent.py) | Shows how to work with a pre-existing agent by providing the agent name and version to the Azure AI client. Demonstrates agent reuse patterns for production scenarios. |
| [`azure_ai_with_existing_conversation.py`](azure_ai_with_existing_conversation.py) | Demonstrates how to use an existing conversation created on the service side with Azure AI agents. Shows two approaches: specifying conversation ID at the client level and using AgentThread with an existing conversation ID. |
| [`azure_ai_with_application_endpoint.py`](azure_ai_with_application_endpoint.py) | Demonstrates calling the Azure AI application-scoped endpoint. |
| [`azure_ai_with_explicit_settings.py`](azure_ai_with_explicit_settings.py) | Shows how to create an agent with explicitly configured `AzureAIClient` settings, including project endpoint, model deployment, and credentials rather than relying on environment variable defaults. |
| [`azure_ai_with_file_search.py`](azure_ai_with_file_search.py) | Shows how to use the `HostedFileSearchTool` with Azure AI agents to upload files, create vector stores, and enable agents to search through uploaded documents to answer user questions. |
| [`azure_ai_with_hosted_mcp.py`](azure_ai_with_hosted_mcp.py) | Shows how to integrate hosted Model Context Protocol (MCP) tools with Azure AI Agent. |
| [`azure_ai_with_local_mcp.py`](azure_ai_with_local_mcp.py) | Shows how to integrate local Model Context Protocol (MCP) tools with Azure AI agents. |
| [`azure_ai_with_response_format.py`](azure_ai_with_response_format.py) | Shows how to use structured outputs (response format) with Azure AI agents using Pydantic models to enforce specific response schemas. |
| [`azure_ai_with_runtime_json_schema.py`](azure_ai_with_runtime_json_schema.py) | Shows how to use structured outputs (response format) with Azure AI agents using a JSON schema to enforce specific response schemas. |
| [`azure_ai_with_search_context_agentic.py`](../../context_providers/azure_ai_search/azure_ai_with_search_context_agentic.py) | Shows how to use AzureAISearchContextProvider with agentic mode. Uses Knowledge Bases for multi-hop reasoning across documents with query planning. Recommended for most scenarios - slightly slower with more token consumption for query planning, but more accurate results. |
| [`azure_ai_with_search_context_semantic.py`](../../context_providers/azure_ai_search/azure_ai_with_search_context_semantic.py) | Shows how to use AzureAISearchContextProvider with semantic mode. Fast hybrid search with vector + keyword search and semantic ranking for RAG. Best for simple queries where speed is critical. |
| [`azure_ai_with_sharepoint.py`](azure_ai_with_sharepoint.py) | Shows how to use SharePoint grounding with Azure AI agents to search through SharePoint content and answer user questions with proper citations. Requires a SharePoint connection configured in your Azure AI project. |
| [`azure_ai_with_thread.py`](azure_ai_with_thread.py) | Demonstrates thread management with Azure AI agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |
| [`azure_ai_with_image_generation.py`](azure_ai_with_image_generation.py) | Shows how to use the `ImageGenTool` with Azure AI agents to generate images based on text prompts. |
| [`azure_ai_with_memory_search.py`](azure_ai_with_memory_search.py) | Shows how to use memory search functionality with Azure AI agents for conversation persistence. Demonstrates creating memory stores and enabling agents to search through conversation history. |
| [`azure_ai_with_microsoft_fabric.py`](azure_ai_with_microsoft_fabric.py) | Shows how to use Microsoft Fabric with Azure AI agents to query Fabric data sources and provide responses based on data analysis. Requires a Microsoft Fabric connection configured in your Azure AI project. |
| [`azure_ai_with_openapi.py`](azure_ai_with_openapi.py) | Shows how to integrate OpenAPI specifications with Azure AI agents using dictionary-based tool configuration. Demonstrates using external REST APIs for dynamic data lookup. |
| [`azure_ai_with_reasoning.py`](azure_ai_with_reasoning.py) | Shows how to enable reasoning for a model that supports it. |
| [`azure_ai_with_web_search.py`](azure_ai_with_web_search.py) | Shows how to use the `HostedWebSearchTool` with Azure AI agents to perform web searches and retrieve up-to-date information from the internet. |
## Environment Variables
Before running the examples, you need to set up your environment variables. You can do this in one of two ways:
### Option 1: Using a .env file (Recommended)
1. Copy the `.env.example` file from the `python` directory to create a `.env` file:
```bash
cp ../../../../.env.example ../../../../.env
```
2. Edit the `.env` file and add your values:
```env
AZURE_AI_PROJECT_ENDPOINT="your-project-endpoint"
AZURE_AI_MODEL_DEPLOYMENT_NAME="your-model-deployment-name"
```
### Option 2: Using environment variables directly
Set the environment variables in your shell:
```bash
export AZURE_AI_PROJECT_ENDPOINT="your-project-endpoint"
export AZURE_AI_MODEL_DEPLOYMENT_NAME="your-model-deployment-name"
```
### Required Variables
- `AZURE_AI_PROJECT_ENDPOINT`: Your Azure AI project endpoint (required for all examples)
- `AZURE_AI_MODEL_DEPLOYMENT_NAME`: The name of your model deployment (required for all examples)
## Authentication
All examples use `AzureCliCredential` for authentication by default. Before running the examples:
1. Install the Azure CLI
2. Run `az login` to authenticate with your Azure account
3. Ensure you have appropriate permissions to the Azure AI project
Alternatively, you can replace `AzureCliCredential` with other authentication options like `DefaultAzureCredential` or environment-based credentials.
## Running the Examples
Each example can be run independently. Navigate to this directory and run any example:
```bash
python azure_ai_basic.py
python azure_ai_with_code_interpreter.py
# ... etc
```
The examples demonstrate various patterns for working with Azure AI agents, from basic usage to advanced scenarios like thread management and structured outputs.

View File

@@ -0,0 +1,82 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from random import randint
from typing import Annotated
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Agent Basic Example
This sample demonstrates basic usage of AzureAIProjectAgentProvider.
Shows both streaming and non-streaming responses with function tools.
"""
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def non_streaming_example() -> None:
"""Example of non-streaming response (get the complete result at once)."""
print("=== Non-streaming Response Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="BasicWeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
query = "What's the weather like in Seattle?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
async def streaming_example() -> None:
"""Example of streaming response (get results as they are generated)."""
print("=== Streaming Response Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="BasicWeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
query = "What's the weather like in Tokyo?"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
async for chunk in agent.run_stream(query):
if chunk.text:
print(chunk.text, end="", flush=True)
print("\n")
async def main() -> None:
print("=== Basic Azure AI Chat Client Agent Example ===")
await non_streaming_example()
await streaming_example()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,249 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from random import randint
from typing import Annotated
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import AgentReference, PromptAgentDefinition
from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Project Agent Provider Methods Example
This sample demonstrates the three main methods of AzureAIProjectAgentProvider:
1. create_agent() - Create a new agent on the Azure AI service
2. get_agent() - Retrieve an existing agent from the service
3. as_agent() - Wrap an SDK agent version object without making HTTP calls
It also shows how to use a single provider instance to spawn multiple agents
with different configurations, which is efficient for multi-agent scenarios.
Each method returns a ChatAgent that can be used for conversations.
"""
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}C."
async def create_agent_example() -> None:
"""Example of using provider.create_agent() to create a new agent.
This method creates a new agent version on the Azure AI service and returns
a ChatAgent. Use this when you want to create a fresh agent with
specific configuration.
"""
print("=== provider.create_agent() Example ===")
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
# Create a new agent with custom configuration
agent = await provider.create_agent(
name="WeatherAssistant",
instructions="You are a helpful weather assistant. Always be concise.",
description="An agent that provides weather information.",
tools=get_weather,
)
print(f"Created agent: {agent.name}")
print(f"Agent ID: {agent.id}")
query = "What's the weather in Paris?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
async def get_agent_by_name_example() -> None:
"""Example of using provider.get_agent(name=...) to retrieve an agent by name.
This method fetches the latest version of an existing agent from the service.
Use this when you know the agent name and want to use the most recent version.
"""
print("=== provider.get_agent(name=...) Example ===")
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
):
# First, create an agent using the SDK directly
created_agent = await project_client.agents.create_version(
agent_name="TestAgentByName",
description="Test agent for get_agent by name example.",
definition=PromptAgentDefinition(
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
instructions="You are a helpful assistant. End each response with '- Your Assistant'.",
),
)
try:
# Get the agent using the provider by name (fetches latest version)
provider = AzureAIProjectAgentProvider(project_client=project_client)
agent = await provider.get_agent(name=created_agent.name)
print(f"Retrieved agent: {agent.name}")
query = "Hello!"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
finally:
# Clean up the agent
await project_client.agents.delete_version(
agent_name=created_agent.name, agent_version=created_agent.version
)
async def get_agent_by_reference_example() -> None:
"""Example of using provider.get_agent(reference=...) to retrieve a specific agent version.
This method fetches a specific version of an agent using an AgentReference.
Use this when you need to use a particular version of an agent.
"""
print("=== provider.get_agent(reference=...) Example ===")
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
):
# First, create an agent using the SDK directly
created_agent = await project_client.agents.create_version(
agent_name="TestAgentByReference",
description="Test agent for get_agent by reference example.",
definition=PromptAgentDefinition(
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
instructions="You are a helpful assistant. Always respond in uppercase.",
),
)
try:
# Get the agent using an AgentReference with specific version
provider = AzureAIProjectAgentProvider(project_client=project_client)
reference = AgentReference(name=created_agent.name, version=created_agent.version)
agent = await provider.get_agent(reference=reference)
print(f"Retrieved agent: {agent.name} (version via reference)")
query = "Say hello"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
finally:
# Clean up the agent
await project_client.agents.delete_version(
agent_name=created_agent.name, agent_version=created_agent.version
)
async def multiple_agents_example() -> None:
"""Example of using a single provider to spawn multiple agents.
A single provider instance can create multiple agents with different
configurations.
"""
print("=== Multiple Agents from Single Provider Example ===")
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
# Create multiple specialized agents from the same provider
weather_agent = await provider.create_agent(
name="WeatherExpert",
instructions="You are a weather expert. Provide brief weather information.",
tools=get_weather,
)
translator_agent = await provider.create_agent(
name="Translator",
instructions="You are a translator. Translate any text to French. Only output the translation.",
)
poet_agent = await provider.create_agent(
name="Poet",
instructions="You are a poet. Respond to everything with a short haiku.",
)
print(f"Created agents: {weather_agent.name}, {translator_agent.name}, {poet_agent.name}\n")
# Use each agent for its specialty
weather_query = "What's the weather in London?"
print(f"User to WeatherExpert: {weather_query}")
weather_result = await weather_agent.run(weather_query)
print(f"WeatherExpert: {weather_result}\n")
translate_query = "Hello, how are you today?"
print(f"User to Translator: {translate_query}")
translate_result = await translator_agent.run(translate_query)
print(f"Translator: {translate_result}\n")
poet_query = "Tell me about the morning sun"
print(f"User to Poet: {poet_query}")
poet_result = await poet_agent.run(poet_query)
print(f"Poet: {poet_result}\n")
async def as_agent_example() -> None:
"""Example of using provider.as_agent() to wrap an SDK object without HTTP calls.
This method wraps an existing AgentVersionDetails into a ChatAgent without
making additional HTTP calls. Use this when you already have the full
AgentVersionDetails from a previous SDK operation.
"""
print("=== provider.as_agent() Example ===")
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
):
# Create an agent using the SDK directly - this returns AgentVersionDetails
agent_version_details = await project_client.agents.create_version(
agent_name="TestAgentAsAgent",
description="Test agent for as_agent example.",
definition=PromptAgentDefinition(
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
instructions="You are a helpful assistant. Keep responses under 20 words.",
),
)
try:
# Wrap the SDK object directly without any HTTP calls
provider = AzureAIProjectAgentProvider(project_client=project_client)
agent = provider.as_agent(agent_version_details)
print(f"Wrapped agent: {agent.name} (no HTTP call needed)")
print(f"Agent version: {agent_version_details.version}")
query = "What can you do?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
finally:
# Clean up the agent
await project_client.agents.delete_version(
agent_name=agent_version_details.name, agent_version=agent_version_details.version
)
async def main() -> None:
print("=== Azure AI Project Agent Provider Methods Example ===\n")
await create_agent_example()
await get_agent_by_name_example()
await get_agent_by_reference_example()
await as_agent_example()
await multiple_agents_example()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,64 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from random import randint
from typing import Annotated
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Agent Latest Version Example
This sample demonstrates how to reuse the latest version of an existing agent
instead of creating a new agent version on each instantiation. The first call creates a new agent,
while subsequent calls with `get_agent()` reuse the latest agent version.
"""
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def main() -> None:
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
# First call creates a new agent
agent = await provider.create_agent(
name="MyWeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
query = "What's the weather like in Seattle?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
# Second call retrieves the existing agent (latest version) instead of creating a new one
# This is useful when you want to reuse an agent that was created earlier
agent2 = await provider.get_agent(
name="MyWeatherAgent",
tools=get_weather, # Tools must be provided for function tools
)
query = "What's the weather like in Tokyo?"
print(f"User: {query}")
result = await agent2.run(query)
print(f"Agent: {result}\n")
print(f"First agent ID with version: {agent.id}")
print(f"Second agent ID with version: {agent2.id}")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,70 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from collections.abc import Awaitable, Callable
from agent_framework import FunctionInvocationContext
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent-as-Tool Example
Demonstrates hierarchical agent architectures where one agent delegates
work to specialized sub-agents wrapped as tools using as_tool().
This pattern is useful when you want a coordinator agent to orchestrate
multiple specialized agents, each focusing on specific tasks.
"""
async def logging_middleware(
context: FunctionInvocationContext,
next: Callable[[FunctionInvocationContext], Awaitable[None]],
) -> None:
"""Middleware that logs tool invocations to show the delegation flow."""
print(f"[Calling tool: {context.function.name}]")
print(f"[Request: {context.arguments}]")
await next(context)
print(f"[Response: {context.result}]")
async def main() -> None:
print("=== Azure AI Agent-as-Tool Pattern ===")
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
# Create a specialized writer agent
writer = await provider.create_agent(
name="WriterAgent",
instructions="You are a creative writer. Write short, engaging content.",
)
# Convert writer agent to a tool using as_tool()
writer_tool = writer.as_tool(
name="creative_writer",
description="Generate creative content like taglines, slogans, or short copy",
arg_name="request",
arg_description="What to write",
)
# Create coordinator agent with writer as a tool
coordinator = await provider.create_agent(
name="CoordinatorAgent",
instructions="You coordinate with specialized agents. Delegate writing tasks to the creative_writer tool.",
tools=[writer_tool],
middleware=[logging_middleware],
)
query = "Create a tagline for a coffee shop"
print(f"User: {query}")
result = await coordinator.run(query)
print(f"Coordinator: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,53 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Agent-to-Agent (A2A) Example
This sample demonstrates usage of AzureAIProjectAgentProvider with Agent-to-Agent (A2A) capabilities
to enable communication with other agents using the A2A protocol.
Prerequisites:
1. Set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME environment variables.
2. Ensure you have an A2A connection configured in your Azure AI project
and set A2A_PROJECT_CONNECTION_ID environment variable.
3. (Optional) A2A_ENDPOINT - If the connection is missing target (e.g., "Custom keys" type),
set the A2A endpoint URL directly.
"""
async def main() -> None:
# Configure A2A tool with connection ID
a2a_tool = {
"type": "a2a_preview",
"project_connection_id": os.environ["A2A_PROJECT_CONNECTION_ID"],
}
# If the connection is missing a target, we need to set the A2A endpoint URL
if os.environ.get("A2A_ENDPOINT"):
a2a_tool["base_url"] = os.environ["A2A_ENDPOINT"]
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MyA2AAgent",
instructions="""You are a helpful assistant that can communicate with other agents.
Use the A2A tool when you need to interact with other agents to complete tasks
or gather information from specialized agents.""",
tools=a2a_tool,
)
query = "What can the secondary agent do?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Result: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,39 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework import ChatAgent
from agent_framework.azure import AzureAIClient
from azure.ai.projects.aio import AIProjectClient
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Application Endpoint Example
This sample demonstrates working with pre-existing Azure AI Agents by providing
application endpoint instead of project endpoint.
"""
async def main() -> None:
# Create the client
async with (
AzureCliCredential() as credential,
# Endpoint here should be application endpoint with format:
# /api/projects/<project-name>/applications/<application-name>/protocols
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
ChatAgent(
chat_client=AzureAIClient(
project_client=project_client,
),
) as agent,
):
query = "How are you?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,52 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Azure AI Search Example
This sample demonstrates usage of AzureAIProjectAgentProvider with Azure AI Search
to search through indexed data and answer user questions about it.
Prerequisites:
1. Set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME environment variables.
2. Ensure you have an Azure AI Search connection configured in your Azure AI project
and set AI_SEARCH_PROJECT_CONNECTION_ID and AI_SEARCH_INDEX_NAME environment variable.
"""
async def main() -> None:
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MySearchAgent",
instructions="""You are a helpful assistant. You must always provide citations for
answers using the tool and render them as: `[message_idx:search_idx†source]`.""",
tools={
"type": "azure_ai_search",
"azure_ai_search": {
"indexes": [
{
"project_connection_id": os.environ["AI_SEARCH_PROJECT_CONNECTION_ID"],
"index_name": os.environ["AI_SEARCH_INDEX_NAME"],
# For query_type=vector, ensure your index has a field with vectorized data.
"query_type": "simple",
}
]
},
},
)
query = "Tell me about insurance options"
print(f"User: {query}")
result = await agent.run(query)
print(f"Result: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,50 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Bing Custom Search Example
This sample demonstrates usage of AzureAIProjectAgentProvider with Bing Custom Search
to search custom search instances and provide responses with relevant results.
Prerequisites:
1. Set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME environment variables.
2. Ensure you have a Bing Custom Search connection configured in your Azure AI project
and set BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID and BING_CUSTOM_SEARCH_INSTANCE_NAME environment variables.
"""
async def main() -> None:
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MyCustomSearchAgent",
instructions="""You are a helpful agent that can use Bing Custom Search tools to assist users.
Use the available Bing Custom Search tools to answer questions and perform tasks.""",
tools={
"type": "bing_custom_search_preview",
"bing_custom_search_preview": {
"search_configurations": [
{
"project_connection_id": os.environ["BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID"],
"instance_name": os.environ["BING_CUSTOM_SEARCH_INSTANCE_NAME"],
}
]
},
},
)
query = "Tell me more about foundry agent service"
print(f"User: {query}")
result = await agent.run(query)
print(f"Result: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,56 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Bing Grounding Example
This sample demonstrates usage of AzureAIProjectAgentProvider with Bing Grounding
to search the web for current information and provide grounded responses.
Prerequisites:
1. Set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME environment variables.
2. Ensure you have a Bing connection configured in your Azure AI project
and set BING_PROJECT_CONNECTION_ID environment variable.
To get your Bing connection ID:
- Go to Azure AI Foundry portal (https://ai.azure.com)
- Navigate to your project's "Connected resources" section
- Add a new connection for "Grounding with Bing Search"
- Copy the connection ID and set it as the BING_PROJECT_CONNECTION_ID environment variable
"""
async def main() -> None:
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MyBingGroundingAgent",
instructions="""You are a helpful assistant that can search the web for current information.
Use the Bing search tool to find up-to-date information and provide accurate, well-sourced answers.
Always cite your sources when possible.""",
tools={
"type": "bing_grounding",
"bing_grounding": {
"search_configurations": [
{
"project_connection_id": os.environ["BING_PROJECT_CONNECTION_ID"],
}
]
},
},
)
query = "What is today's date and weather in Seattle?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Result: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,54 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Browser Automation Example
This sample demonstrates usage of AzureAIProjectAgentProvider with Browser Automation
to perform automated web browsing tasks and provide responses based on web interactions.
Prerequisites:
1. Set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME environment variables.
2. Ensure you have a Browser Automation connection configured in your Azure AI project
and set BROWSER_AUTOMATION_PROJECT_CONNECTION_ID environment variable.
"""
async def main() -> None:
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MyBrowserAutomationAgent",
instructions="""You are an Agent helping with browser automation tasks.
You can answer questions, provide information, and assist with various tasks
related to web browsing using the Browser Automation tool available to you.""",
tools={
"type": "browser_automation_preview",
"browser_automation_preview": {
"connection": {
"project_connection_id": os.environ["BROWSER_AUTOMATION_PROJECT_CONNECTION_ID"],
}
},
},
)
query = """Your goal is to report the percent of Microsoft year-to-date stock price change.
To do that, go to the website finance.yahoo.com.
At the top of the page, you will find a search bar.
Enter the value 'MSFT', to get information about the Microsoft stock price.
At the top of the resulting page you will see a default chart of Microsoft stock price.
Click on 'YTD' at the top of that chart, and report the percent value that shows up just below it."""
print(f"User: {query}")
result = await agent.run(query)
print(f"Result: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,58 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import ChatResponse, HostedCodeInterpreterTool
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
from openai.types.responses.response import Response as OpenAIResponse
from openai.types.responses.response_code_interpreter_tool_call import ResponseCodeInterpreterToolCall
"""
Azure AI Agent Code Interpreter Example
This sample demonstrates using HostedCodeInterpreterTool with AzureAIProjectAgentProvider
for Python code execution and mathematical problem solving.
"""
async def main() -> None:
"""Example showing how to use the HostedCodeInterpreterTool with AzureAIProjectAgentProvider."""
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MyCodeInterpreterAgent",
instructions="You are a helpful assistant that can write and execute Python code to solve problems.",
tools=HostedCodeInterpreterTool(),
)
query = "Use code to get the factorial of 100?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Result: {result}\n")
if (
isinstance(result.raw_representation, ChatResponse)
and isinstance(result.raw_representation.raw_representation, OpenAIResponse)
and len(result.raw_representation.raw_representation.output) > 0
):
# Find the first ResponseCodeInterpreterToolCall item
code_interpreter_item = next(
(
item
for item in result.raw_representation.raw_representation.output
if isinstance(item, ResponseCodeInterpreterToolCall)
),
None,
)
if code_interpreter_item is not None:
generated_code = code_interpreter_item.code
print(f"Generated code:\n{generated_code}")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,219 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import tempfile
from pathlib import Path
from agent_framework import (
AgentResponseUpdate,
ChatAgent,
CitationAnnotation,
HostedCodeInterpreterTool,
HostedFileContent,
TextContent,
)
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI V2 Code Interpreter File Download Sample
This sample demonstrates how the AzureAIProjectAgentProvider handles file annotations
when code interpreter generates text files. It shows:
1. How to extract file IDs and container IDs from annotations
2. How to download container files using the OpenAI containers API
3. How to save downloaded files locally
Note: Code interpreter generates files in containers, which require both
file_id and container_id to download via client.containers.files.content.retrieve().
"""
QUERY = (
"Write a simple Python script that creates a text file called 'sample.txt' containing "
"'Hello from the code interpreter!' and save it to disk."
)
async def download_container_files(
file_contents: list[CitationAnnotation | HostedFileContent], agent: ChatAgent
) -> list[Path]:
"""Download container files using the OpenAI containers API.
Code interpreter generates files in containers, which require both file_id
and container_id to download. The container_id is stored in additional_properties.
This function works for both streaming (HostedFileContent) and non-streaming
(CitationAnnotation) responses.
Args:
file_contents: List of CitationAnnotation or HostedFileContent objects
containing file_id and container_id.
agent: The ChatAgent instance with access to the AzureAIClient.
Returns:
List of Path objects for successfully downloaded files.
"""
if not file_contents:
return []
# Create output directory in system temp folder
temp_dir = Path(tempfile.gettempdir())
output_dir = temp_dir / "agent_framework_downloads"
output_dir.mkdir(exist_ok=True)
print(f"\nDownloading {len(file_contents)} container file(s) to {output_dir.absolute()}...")
# Access the OpenAI client from AzureAIClient
openai_client = agent.chat_client.client
downloaded_files: list[Path] = []
for content in file_contents:
file_id = content.file_id
# Extract container_id from additional_properties
if not content.additional_properties or "container_id" not in content.additional_properties:
print(f" File {file_id}: ✗ Missing container_id")
continue
container_id = content.additional_properties["container_id"]
# Extract filename based on content type
if isinstance(content, CitationAnnotation):
filename = content.url or f"{file_id}.txt"
# Extract filename from sandbox URL if present (e.g., sandbox:/mnt/data/sample.txt)
if filename.startswith("sandbox:"):
filename = filename.split("/")[-1]
else: # HostedFileContent
filename = content.additional_properties.get("filename") or f"{file_id}.txt"
output_path = output_dir / filename
try:
# Download using containers API
print(f" Downloading {filename}...", end="", flush=True)
file_content = await openai_client.containers.files.content.retrieve(
file_id=file_id,
container_id=container_id,
)
# file_content is HttpxBinaryResponseContent, read it
content_bytes = file_content.read()
# Save to disk
output_path.write_bytes(content_bytes)
file_size = output_path.stat().st_size
print(f"({file_size} bytes)")
downloaded_files.append(output_path)
except Exception as e:
print(f"Failed: {e}")
return downloaded_files
async def non_streaming_example() -> None:
"""Example of downloading files from non-streaming response using CitationAnnotation."""
print("=== Non-Streaming Response Example ===")
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="V2CodeInterpreterFileAgent",
instructions="You are a helpful assistant that can write and execute Python code to create files.",
tools=HostedCodeInterpreterTool(),
)
print(f"User: {QUERY}\n")
result = await agent.run(QUERY)
print(f"Agent: {result.text}\n")
# Check for annotations in the response
annotations_found: list[CitationAnnotation] = []
# AgentResponse has messages property, which contains ChatMessage objects
for message in result.messages:
for content in message.contents:
if isinstance(content, TextContent) and content.annotations:
for annotation in content.annotations:
if isinstance(annotation, CitationAnnotation) and annotation.file_id:
annotations_found.append(annotation)
print(f"Found file annotation: file_id={annotation.file_id}")
if annotation.additional_properties and "container_id" in annotation.additional_properties:
print(f" container_id={annotation.additional_properties['container_id']}")
if annotations_found:
print(f"SUCCESS: Found {len(annotations_found)} file annotation(s)")
# Download the container files
downloaded_paths = await download_container_files(annotations_found, agent)
if downloaded_paths:
print("\nDownloaded files available at:")
for path in downloaded_paths:
print(f" - {path.absolute()}")
else:
print("WARNING: No file annotations found in non-streaming response")
async def streaming_example() -> None:
"""Example of downloading files from streaming response using HostedFileContent."""
print("\n=== Streaming Response Example ===")
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="V2CodeInterpreterFileAgentStreaming",
instructions="You are a helpful assistant that can write and execute Python code to create files.",
tools=HostedCodeInterpreterTool(),
)
print(f"User: {QUERY}\n")
file_contents_found: list[HostedFileContent] = []
text_chunks: list[str] = []
async for update in agent.run_stream(QUERY):
if isinstance(update, AgentResponseUpdate):
for content in update.contents:
if isinstance(content, TextContent):
if content.text:
text_chunks.append(content.text)
if content.annotations:
for annotation in content.annotations:
if isinstance(annotation, CitationAnnotation) and annotation.file_id:
print(f"Found streaming CitationAnnotation: file_id={annotation.file_id}")
elif isinstance(content, HostedFileContent):
file_contents_found.append(content)
print(f"Found streaming HostedFileContent: file_id={content.file_id}")
if content.additional_properties and "container_id" in content.additional_properties:
print(f" container_id={content.additional_properties['container_id']}")
print(f"\nAgent response: {''.join(text_chunks)[:200]}...")
if file_contents_found:
print(f"SUCCESS: Found {len(file_contents_found)} file reference(s) in streaming")
# Download the container files
downloaded_paths = await download_container_files(file_contents_found, agent)
if downloaded_paths:
print("\n✓ Downloaded files available at:")
for path in downloaded_paths:
print(f" - {path.absolute()}")
else:
print("WARNING: No file annotations found in streaming response")
async def main() -> None:
print("AzureAIClient Code Interpreter File Download Sample\n")
await non_streaming_example()
await streaming_example()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,112 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import (
AgentResponseUpdate,
HostedCodeInterpreterTool,
)
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI V2 Code Interpreter File Generation Sample
This sample demonstrates how the AzureAIProjectAgentProvider handles file annotations
when code interpreter generates text files. It shows both non-streaming
and streaming approaches to verify file ID extraction.
"""
QUERY = (
"Write a simple Python script that creates a text file called 'sample.txt' containing "
"'Hello from the code interpreter!' and save it to disk."
)
async def non_streaming_example() -> None:
"""Example of extracting file annotations from non-streaming response."""
print("=== Non-Streaming Response Example ===")
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="V2CodeInterpreterFileAgent",
instructions="You are a helpful assistant that can write and execute Python code to create files.",
tools=HostedCodeInterpreterTool(),
)
print(f"User: {QUERY}\n")
result = await agent.run(QUERY)
print(f"Agent: {result.text}\n")
# Check for annotations in the response
annotations_found: list[str] = []
# AgentResponse has messages property, which contains ChatMessage objects
for message in result.messages:
for content in message.contents:
if content.type == "text" and content.annotations:
for annotation in content.annotations:
if annotation.file_id:
annotations_found.append(annotation.file_id)
print(f"Found file annotation: file_id={annotation.file_id}")
if annotations_found:
print(f"SUCCESS: Found {len(annotations_found)} file annotation(s)")
else:
print("WARNING: No file annotations found in non-streaming response")
async def streaming_example() -> None:
"""Example of extracting file annotations from streaming response."""
print("\n=== Streaming Response Example ===")
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="V2CodeInterpreterFileAgentStreaming",
instructions="You are a helpful assistant that can write and execute Python code to create files.",
tools=HostedCodeInterpreterTool(),
)
print(f"User: {QUERY}\n")
annotations_found: list[str] = []
text_chunks: list[str] = []
file_ids_found: list[str] = []
async for update in agent.run_stream(QUERY):
if isinstance(update, AgentResponseUpdate):
for content in update.contents:
if content.type == "text":
if content.text:
text_chunks.append(content.text)
if content.annotations:
for annotation in content.annotations:
if annotation.file_id:
annotations_found.append(annotation.file_id)
print(f"Found streaming annotation: file_id={annotation.file_id}")
elif content.type == "hosted_file":
file_ids_found.append(content.file_id)
print(f"Found streaming HostedFileContent: file_id={content.file_id}")
print(f"\nAgent response: {''.join(text_chunks)[:200]}...")
if annotations_found or file_ids_found:
total = len(annotations_found) + len(file_ids_found)
print(f"SUCCESS: Found {total} file reference(s) in streaming")
else:
print("WARNING: No file annotations found in streaming response")
async def main() -> None:
print("AzureAIClient Code Interpreter File Generation Sample\n")
await non_streaming_example()
await streaming_example()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,66 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.ai.projects.models import RaiConfig
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Content Filtering (RAI Policy) Example
This sample demonstrates how to enable content filtering on Azure AI agents using RaiConfig.
Prerequisites:
1. Create an RAI Policy in Azure AI Foundry portal:
- Go to Azure AI Foundry > Your Project > Guardrails + Controls > Content Filters
- Create a new content filter or use an existing one
- Note the policy name
2. Set environment variables:
- AZURE_AI_PROJECT_ENDPOINT: Your Azure AI Foundry project endpoint
- AZURE_AI_MODEL_DEPLOYMENT_NAME: Your model deployment name
3. Run `az login` to authenticate
"""
async def main() -> None:
print("=== Azure AI Agent with Content Filtering ===\n")
# Replace with your RAI policy from Azure AI Foundry portal
rai_policy_name = (
"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/"
"Microsoft.CognitiveServices/accounts/{accountName}/raiPolicies/{policyName}"
)
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
# Create agent with content filtering enabled via default_options
agent = await provider.create_agent(
name="ContentFilteredAgent",
instructions="You are a helpful assistant.",
default_options={"rai_config": RaiConfig(rai_policy_name=rai_policy_name)},
)
# Test with a normal query
query = "What is the capital of France?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
# Test with a query that might trigger content filtering
# (depending on your RAI policy configuration)
query2 = "Tell me something inappropriate."
print(f"User: {query2}")
try:
result2 = await agent.run(query2)
print(f"Agent: {result2}\n")
except Exception as e:
print(f"Content filter triggered: {e}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,66 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import PromptAgentDefinition
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Existing Agent Example
This sample demonstrates working with pre-existing Azure AI Agents by using provider.get_agent() method,
showing agent reuse patterns for production scenarios.
"""
async def using_provider_get_agent() -> None:
print("=== Get existing Azure AI agent with provider.get_agent() ===")
# Create the client
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
):
# Create remote agent using SDK directly
azure_ai_agent = await project_client.agents.create_version(
agent_name="MyNewTestAgent",
description="Agent for testing purposes.",
definition=PromptAgentDefinition(
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
# Setting specific requirements to verify that this agent is used.
instructions="End each response with [END].",
),
)
try:
# Get newly created agent as ChatAgent by using provider.get_agent()
provider = AzureAIProjectAgentProvider(project_client=project_client)
agent = await provider.get_agent(name=azure_ai_agent.name)
# Verify agent properties
print(f"Agent ID: {agent.id}")
print(f"Agent name: {agent.name}")
print(f"Agent description: {agent.description}")
query = "How are you?"
print(f"User: {query}")
result = await agent.run(query)
# Response that indicates that previously created agent was used:
# "I'm here and ready to help you! How can I assist you today? [END]"
print(f"Agent: {result}\n")
finally:
# Clean up the agent manually
await project_client.agents.delete_version(
agent_name=azure_ai_agent.name, agent_version=azure_ai_agent.version
)
async def main() -> None:
await using_provider_get_agent()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,99 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from random import randint
from typing import Annotated
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.ai.projects.aio import AIProjectClient
from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Agent Existing Conversation Example
This sample demonstrates usage of AzureAIProjectAgentProvider with existing conversation created on service side.
"""
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def example_with_conversation_id() -> None:
"""Example shows how to use existing conversation ID with the provider."""
print("=== Azure AI Agent With Existing Conversation ===")
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
):
# Create a conversation using OpenAI client
openai_client = project_client.get_openai_client()
conversation = await openai_client.conversations.create()
conversation_id = conversation.id
print(f"Conversation ID: {conversation_id}")
provider = AzureAIProjectAgentProvider(project_client=project_client)
agent = await provider.create_agent(
name="BasicAgent",
instructions="You are a helpful agent.",
tools=get_weather,
)
# Pass conversation_id at run level
query = "What's the weather like in Seattle?"
print(f"User: {query}")
result = await agent.run(query, conversation_id=conversation_id)
print(f"Agent: {result.text}\n")
query = "What was my last question?"
print(f"User: {query}")
result = await agent.run(query, conversation_id=conversation_id)
print(f"Agent: {result.text}\n")
async def example_with_thread() -> None:
"""This example shows how to specify existing conversation ID with AgentThread."""
print("=== Azure AI Agent With Existing Conversation and Thread ===")
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
):
provider = AzureAIProjectAgentProvider(project_client=project_client)
agent = await provider.create_agent(
name="BasicAgent",
instructions="You are a helpful agent.",
tools=get_weather,
)
# Create a conversation using OpenAI client
openai_client = project_client.get_openai_client()
conversation = await openai_client.conversations.create()
conversation_id = conversation.id
print(f"Conversation ID: {conversation_id}")
# Create a thread with the existing ID
thread = agent.get_new_thread(service_thread_id=conversation_id)
query = "What's the weather like in Seattle?"
print(f"User: {query}")
result = await agent.run(query, thread=thread)
print(f"Agent: {result.text}\n")
query = "What was my last question?"
print(f"User: {query}")
result = await agent.run(query, thread=thread)
print(f"Agent: {result.text}\n")
async def main() -> None:
await example_with_conversation_id()
await example_with_thread()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,52 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from random import randint
from typing import Annotated
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Agent with Explicit Settings Example
This sample demonstrates creating Azure AI Agents with explicit configuration
settings rather than relying on environment variable defaults.
"""
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def main() -> None:
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=credential,
) as provider,
):
agent = await provider.create_agent(
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
query = "What's the weather like in New York?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,75 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from pathlib import Path
from agent_framework import HostedFileSearchTool, HostedVectorStoreContent
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.ai.agents.aio import AgentsClient
from azure.ai.agents.models import FileInfo, VectorStore
from azure.identity.aio import AzureCliCredential
"""
The following sample demonstrates how to create a simple, Azure AI agent that
uses a file search tool to answer user questions.
"""
# Simulate a conversation with the agent
USER_INPUTS = [
"Who is the youngest employee?",
"Who works in sales?",
"I have a customer request, who can help me?",
]
async def main() -> None:
"""Main function demonstrating Azure AI agent with file search capabilities."""
file: FileInfo | None = None
vector_store: VectorStore | None = None
async with (
AzureCliCredential() as credential,
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
try:
# 1. Upload file and create vector store
pdf_file_path = Path(__file__).parent.parent / "resources" / "employees.pdf"
print(f"Uploading file from: {pdf_file_path}")
file = await agents_client.files.upload_and_poll(file_path=str(pdf_file_path), purpose="assistants")
print(f"Uploaded file, file ID: {file.id}")
vector_store = await agents_client.vector_stores.create_and_poll(file_ids=[file.id], name="my_vectorstore")
print(f"Created vector store, vector store ID: {vector_store.id}")
# 2. Create file search tool with uploaded resources
file_search_tool = HostedFileSearchTool(inputs=[HostedVectorStoreContent(vector_store_id=vector_store.id)])
# 3. Create an agent with file search capabilities using the provider
agent = await provider.create_agent(
name="EmployeeSearchAgent",
instructions=(
"You are a helpful assistant that can search through uploaded employee files "
"to answer questions about employees."
),
tools=file_search_tool,
)
# 4. Simulate conversation with the agent
for user_input in USER_INPUTS:
print(f"# User: '{user_input}'")
response = await agent.run(user_input)
print(f"# Agent: {response.text}")
finally:
# 5. Cleanup: Delete the vector store and file in case of earlier failure to prevent orphaned resources.
if vector_store:
await agents_client.vector_stores.delete(vector_store.id)
if file:
await agents_client.files.delete(file.id)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,119 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from typing import Any
from agent_framework import AgentProtocol, AgentResponse, AgentThread, ChatMessage, HostedMCPTool
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Hosted MCP Example
This sample demonstrates integrating hosted Model Context Protocol (MCP) tools with Azure AI Agent.
"""
async def handle_approvals_without_thread(query: str, agent: "AgentProtocol") -> AgentResponse:
"""When we don't have a thread, we need to ensure we return with the input, approval request and approval."""
result = await agent.run(query, store=False)
while len(result.user_input_requests) > 0:
new_inputs: list[Any] = [query]
for user_input_needed in result.user_input_requests:
print(
f"User Input Request for function from {agent.name}: {user_input_needed.function_call.name}"
f" with arguments: {user_input_needed.function_call.arguments}"
)
new_inputs.append(ChatMessage(role="assistant", contents=[user_input_needed]))
user_approval = input("Approve function call? (y/n): ")
new_inputs.append(
ChatMessage(role="user", contents=[user_input_needed.create_response(user_approval.lower() == "y")])
)
result = await agent.run(new_inputs, store=False)
return result
async def handle_approvals_with_thread(query: str, agent: "AgentProtocol", thread: "AgentThread") -> AgentResponse:
"""Here we let the thread deal with the previous responses, and we just rerun with the approval."""
result = await agent.run(query, thread=thread)
while len(result.user_input_requests) > 0:
new_input: list[Any] = []
for user_input_needed in result.user_input_requests:
print(
f"User Input Request for function from {agent.name}: {user_input_needed.function_call.name}"
f" with arguments: {user_input_needed.function_call.arguments}"
)
user_approval = input("Approve function call? (y/n): ")
new_input.append(
ChatMessage(
role="user",
contents=[user_input_needed.create_response(user_approval.lower() == "y")],
)
)
result = await agent.run(new_input, thread=thread)
return result
async def run_hosted_mcp_without_approval() -> None:
"""Example showing MCP Tools without approval."""
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MyLearnDocsAgent",
instructions="You are a helpful assistant that can help with Microsoft documentation questions.",
tools=HostedMCPTool(
name="Microsoft Learn MCP",
url="https://learn.microsoft.com/api/mcp",
approval_mode="never_require",
),
)
query = "How to create an Azure storage account using az cli?"
print(f"User: {query}")
result = await handle_approvals_without_thread(query, agent)
print(f"{agent.name}: {result}\n")
async def run_hosted_mcp_with_approval_and_thread() -> None:
"""Example showing MCP Tools with approvals using a thread."""
print("=== MCP with approvals and with thread ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MyApiSpecsAgent",
instructions="You are a helpful agent that can use MCP tools to assist users.",
tools=HostedMCPTool(
name="api-specs",
url="https://gitmcp.io/Azure/azure-rest-api-specs",
approval_mode="always_require",
),
)
thread = agent.get_new_thread()
query = "Please summarize the Azure REST API specifications Readme"
print(f"User: {query}")
result = await handle_approvals_with_thread(query, agent, thread)
print(f"{agent.name}: {result}\n")
async def main() -> None:
print("=== Azure AI Agent with Hosted MCP Tools Example ===\n")
await run_hosted_mcp_without_approval()
await run_hosted_mcp_with_approval_and_thread()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,110 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import base64
import tempfile
from pathlib import Path
from urllib import request as urllib_request
import aiofiles
from agent_framework import HostedImageGenerationTool
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Image Generation Example
This sample demonstrates basic usage of AzureAIProjectAgentProvider to create an agent
that can generate images based on user requirements.
Pre-requisites:
- Make sure to set up the AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME
environment variables before running this sample.
"""
async def main() -> None:
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="ImageGenAgent",
instructions="Generate images based on user requirements.",
tools=[
HostedImageGenerationTool(
options={
"model_id": "gpt-image-1",
"image_size": "1024x1024",
"media_type": "png",
},
additional_properties={
"quality": "low",
"background": "opaque",
},
)
],
)
query = "Generate an image of Microsoft logo."
print(f"User: {query}")
result = await agent.run(
query,
# These additional options are required for image generation
options={
"extra_headers": {"x-ms-oai-image-generation-deployment": "gpt-image-1-mini"},
},
)
print(f"Agent: {result}\n")
# Save the image to a file
print("Downloading generated image...")
image_data = [
content.outputs
for content in result.messages[0].contents
if content.type == "image_generation_tool_result" and content.outputs is not None
]
if image_data and image_data[0]:
# Save to the OS temporary directory
filename = "microsoft.png"
file_path = Path(tempfile.gettempdir()) / filename
# outputs can be a list of Content items (data/uri) or a single item
out = image_data[0][0] if isinstance(image_data[0], list) else image_data[0]
data_bytes: bytes | None = None
uri = getattr(out, "uri", None)
if isinstance(uri, str):
if ";base64," in uri:
try:
b64 = uri.split(";base64,", 1)[1]
data_bytes = base64.b64decode(b64)
except Exception:
data_bytes = None
else:
try:
data_bytes = await asyncio.to_thread(lambda: urllib_request.urlopen(uri).read())
except Exception:
data_bytes = None
if data_bytes is None:
raise RuntimeError("Image output present but could not retrieve bytes.")
async with aiofiles.open(file_path, "wb") as f:
await f.write(data_bytes)
print(f"Image downloaded and saved to: {file_path}")
else:
print("No image data found in the agent response.")
"""
Sample output:
User: Generate an image of Microsoft logo.
Agent: Here is the Microsoft logo image featuring its iconic four quadrants.
Downloading generated image...
Image downloaded and saved to: .../microsoft.png
"""
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,56 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import MCPStreamableHTTPTool
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Local MCP Example
This sample demonstrates integration of Azure AI Agents with local Model Context Protocol (MCP)
servers.
Pre-requisites:
- Make sure to set up the AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME
environment variables before running this sample.
"""
async def main() -> None:
"""Example showing use of Local MCP Tool with AzureAIProjectAgentProvider."""
print("=== Azure AI Agent with Local MCP Tools Example ===\n")
mcp_tool = MCPStreamableHTTPTool(
name="Microsoft Learn MCP",
url="https://learn.microsoft.com/api/mcp",
)
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="DocsAgent",
instructions="You are a helpful assistant that can help with Microsoft documentation questions.",
tools=mcp_tool,
)
# Use agent as context manager to ensure proper cleanup
async with agent:
# First query
first_query = "How to create an Azure storage account using az cli?"
print(f"User: {first_query}")
first_result = await agent.run(first_query)
print(f"Agent: {first_result}")
print("\n=======================================\n")
# Second query
second_query = "What is Microsoft Agent Framework?"
print(f"User: {second_query}")
second_result = await agent.run(second_query)
print(f"Agent: {second_result}")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,88 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
import uuid
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import MemoryStoreDefaultDefinition, MemoryStoreDefaultOptions
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Memory Search Example
This sample demonstrates usage of AzureAIProjectAgentProvider with memory search capabilities
to retrieve relevant past user messages and maintain conversation context across sessions.
It shows explicit memory store creation using Azure AI Projects client and agent creation
using the Agent Framework.
Prerequisites:
1. Set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME environment variables.
2. Set AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME for the memory chat model.
3. Set AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME for the memory embedding model.
4. Deploy both a chat model (e.g. gpt-4.1) and an embedding model (e.g. text-embedding-3-small).
"""
async def main() -> None:
endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"]
# Generate a unique memory store name to avoid conflicts
memory_store_name = f"agent_framework_memory_store_{uuid.uuid4().hex[:8]}"
async with AzureCliCredential() as credential:
# Create the memory store using Azure AI Projects client
async with AIProjectClient(endpoint=endpoint, credential=credential) as project_client:
# Create a memory store using proper model classes
memory_store_definition = MemoryStoreDefaultDefinition(
chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"],
embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"],
options=MemoryStoreDefaultOptions(user_profile_enabled=True, chat_summary_enabled=True),
)
memory_store = await project_client.memory_stores.create(
name=memory_store_name,
description="Memory store for Agent Framework conversations",
definition=memory_store_definition,
)
print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}")
# Then, create the agent using Agent Framework provider
async with AzureAIProjectAgentProvider(credential=credential) as provider:
agent = await provider.create_agent(
name="MyMemoryAgent",
instructions="""You are a helpful assistant that remembers past conversations.
Use the memory search tool to recall relevant information from previous interactions.""",
tools={
"type": "memory_search",
"memory_store_name": memory_store.name,
"scope": "user_123",
"update_delay": 1, # Wait 1 second before updating memories (use higher value in production)
},
)
# First interaction - establish some preferences
print("=== First conversation ===")
query1 = "I prefer dark roast coffee"
print(f"User: {query1}")
result1 = await agent.run(query1)
print(f"Agent: {result1}\n")
# Wait for memories to be processed
print("Waiting for memories to be stored...")
await asyncio.sleep(5) # Reduced wait time for demo purposes
# Second interaction - test memory recall
print("=== Second conversation ===")
query2 = "Please order my usual coffee"
print(f"User: {query2}")
result2 = await agent.run(query2)
print(f"Agent: {result2}\n")
# Clean up - delete the memory store
async with AIProjectClient(endpoint=endpoint, credential=credential) as project_client:
await project_client.memory_stores.delete(memory_store_name)
print("Memory store deleted")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,48 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Microsoft Fabric Example
This sample demonstrates usage of AzureAIProjectAgentProvider with Microsoft Fabric
to query Fabric data sources and provide responses based on data analysis.
Prerequisites:
1. Set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME environment variables.
2. Ensure you have a Microsoft Fabric connection configured in your Azure AI project
and set FABRIC_PROJECT_CONNECTION_ID environment variable.
"""
async def main() -> None:
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MyFabricAgent",
instructions="You are a helpful assistant.",
tools={
"type": "fabric_dataagent_preview",
"fabric_dataagent_preview": {
"project_connections": [
{
"project_connection_id": os.environ["FABRIC_PROJECT_CONNECTION_ID"],
}
]
},
},
)
query = "Tell me about sales records"
print(f"User: {query}")
result = await agent.run(query)
print(f"Result: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,56 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import json
from pathlib import Path
import aiofiles
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with OpenAPI Tool Example
This sample demonstrates usage of AzureAIProjectAgentProvider with OpenAPI tools
to call external APIs defined by OpenAPI specifications.
Prerequisites:
1. Set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME environment variables.
2. The countries.json OpenAPI specification is included in the resources folder.
"""
async def main() -> None:
# Load the OpenAPI specification
resources_path = Path(__file__).parent.parent / "resources" / "countries.json"
async with aiofiles.open(resources_path, "r") as f:
content = await f.read()
openapi_countries = json.loads(content)
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MyOpenAPIAgent",
instructions="""You are a helpful assistant that can use country APIs to provide information.
Use the available OpenAPI tools to answer questions about countries, currencies, and demographics.""",
tools={
"type": "openapi",
"openapi": {
"name": "get_countries",
"spec": openapi_countries,
"description": "Retrieve information about countries by currency code",
"auth": {"type": "anonymous"},
},
},
)
query = "What is the name and population of the country that uses currency with abbreviation THB?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,94 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.ai.projects.models import Reasoning
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Reasoning Example
Demonstrates how to enable reasoning capabilities using the Reasoning option.
Shows both non-streaming and streaming approaches, including how to access
reasoning content (type="text_reasoning") separately from answer content.
Requires a reasoning-capable model (e.g., gpt-5.2) deployed in your Azure AI Project configured
as `AZURE_AI_MODEL_DEPLOYMENT_NAME` in your environment.
"""
async def non_streaming_example() -> None:
"""Example of non-streaming response (get the complete result at once)."""
print("=== Non-streaming Response Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="ReasoningWeatherAgent",
instructions="You are a helpful weather agent who likes to understand the underlying physics.",
default_options={"reasoning": Reasoning(effort="medium", summary="concise")},
)
query = "How does the Bernoulli effect work?"
print(f"User: {query}")
result = await agent.run(query)
for msg in result.messages:
for content in msg.contents:
if content.type == "text_reasoning":
print(f"[Reasoning]: {content.text}")
elif content.type == "text":
print(f"[Answer]: {content.text}")
print()
async def streaming_example() -> None:
"""Example of streaming response (get results as they are generated)."""
print("=== Streaming Response Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="ReasoningWeatherAgent",
instructions="You are a helpful weather agent who likes to understand the underlying physics.",
default_options={"reasoning": Reasoning(effort="medium", summary="concise")},
)
query = "Help explain how air updrafts work?"
print(f"User: {query}")
shown_reasoning_label = False
shown_text_label = False
async for chunk in agent.run_stream(query):
for content in chunk.contents:
if content.type == "text_reasoning":
if not shown_reasoning_label:
print("[Reasoning]: ", end="", flush=True)
shown_reasoning_label = True
print(content.text, end="", flush=True)
elif content.type == "text":
if not shown_text_label:
print("\n\n[Answer]: ", end="", flush=True)
shown_text_label = True
print(content.text, end="", flush=True)
print("\n")
async def main() -> None:
print("=== Azure AI Agent with Reasoning Example ===")
# await non_streaming_example()
await streaming_example()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,54 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
from pydantic import BaseModel, ConfigDict
"""
Azure AI Agent Response Format Example
This sample demonstrates basic usage of AzureAIProjectAgentProvider with response format,
also known as structured outputs.
"""
class ReleaseBrief(BaseModel):
feature: str
benefit: str
launch_date: str
model_config = ConfigDict(extra="forbid")
async def main() -> None:
"""Example of using response_format property."""
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="ProductMarketerAgent",
instructions="Return launch briefs as structured JSON.",
# Specify Pydantic model for structured output via default_options
default_options={"response_format": ReleaseBrief},
)
query = "Draft a launch brief for the Contoso Note app."
print(f"User: {query}")
result = await agent.run(query)
if release_brief := result.try_parse_value(ReleaseBrief):
print("Agent:")
print(f"Feature: {release_brief.feature}")
print(f"Benefit: {release_brief.benefit}")
print(f"Launch date: {release_brief.launch_date}")
else:
print(f"Failed to parse response: {result.text}")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,64 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent Response Format Example with Runtime JSON Schema
This sample demonstrates basic usage of AzureAIProjectAgentProvider with response format,
also known as structured outputs.
"""
runtime_schema = {
"title": "WeatherDigest",
"type": "object",
"properties": {
"location": {"type": "string"},
"conditions": {"type": "string"},
"temperature_c": {"type": "number"},
"advisory": {"type": "string"},
},
# OpenAI strict mode requires every property to appear in required.
"required": ["location", "conditions", "temperature_c", "advisory"],
"additionalProperties": False,
}
async def main() -> None:
"""Example of using response_format property with a runtime JSON schema."""
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
# Pass response_format via default_options using dict schema format
agent = await provider.create_agent(
name="WeatherDigestAgent",
instructions="Return sample weather digest as structured JSON.",
default_options={
"response_format": {
"type": "json_schema",
"json_schema": {
"name": runtime_schema["title"],
"strict": True,
"schema": runtime_schema,
},
}
},
)
query = "Draft a sample weather digest."
print(f"User: {query}")
result = await agent.run(query)
print(result.text)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,49 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with SharePoint Example
This sample demonstrates usage of AzureAIProjectAgentProvider with SharePoint
to search through SharePoint content and answer user questions about it.
Prerequisites:
1. Set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME environment variables.
2. Ensure you have a SharePoint connection configured in your Azure AI project
and set SHAREPOINT_PROJECT_CONNECTION_ID environment variable.
"""
async def main() -> None:
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MySharePointAgent",
instructions="""You are a helpful agent that can use SharePoint tools to assist users.
Use the available SharePoint tools to answer questions and perform tasks.""",
tools={
"type": "sharepoint_grounding_preview",
"sharepoint_grounding_preview": {
"project_connections": [
{
"project_connection_id": os.environ["SHAREPOINT_PROJECT_CONNECTION_ID"],
}
]
},
},
)
query = "What is Contoso whistleblower policy?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Result: {result}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,156 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from random import randint
from typing import Annotated
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Agent with Thread Management Example
This sample demonstrates thread management with Azure AI Agent, showing
persistent conversation capabilities using service-managed threads as well as storing messages in-memory.
"""
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def example_with_automatic_thread_creation() -> None:
"""Example showing automatic thread creation."""
print("=== Automatic Thread Creation Example ===")
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="BasicWeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
# First conversation - no thread provided, will be created automatically
query1 = "What's the weather like in Seattle?"
print(f"User: {query1}")
result1 = await agent.run(query1)
print(f"Agent: {result1.text}")
# Second conversation - still no thread provided, will create another new thread
query2 = "What was the last city I asked about?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2)
print(f"Agent: {result2.text}")
print("Note: Each call creates a separate thread, so the agent doesn't remember previous context.\n")
async def example_with_thread_persistence_in_memory() -> None:
"""
Example showing thread persistence across multiple conversations.
In this example, messages are stored in-memory.
"""
print("=== Thread Persistence Example (In-Memory) ===")
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="BasicWeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
# Create a new thread that will be reused
thread = agent.get_new_thread()
# First conversation
query1 = "What's the weather like in Tokyo?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread, store=False)
print(f"Agent: {result1.text}")
# Second conversation using the same thread - maintains context
query2 = "How about London?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2, thread=thread, store=False)
print(f"Agent: {result2.text}")
# Third conversation - agent should remember both previous cities
query3 = "Which of the cities I asked about has better weather?"
print(f"\nUser: {query3}")
result3 = await agent.run(query3, thread=thread, store=False)
print(f"Agent: {result3.text}")
print("Note: The agent remembers context from previous messages in the same thread.\n")
async def example_with_existing_thread_id() -> None:
"""
Example showing how to work with an existing thread ID from the service.
In this example, messages are stored on the server.
"""
print("=== Existing Thread ID Example ===")
# First, create a conversation and capture the thread ID
existing_thread_id = None
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="BasicWeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
# Start a conversation and get the thread ID
thread = agent.get_new_thread()
query1 = "What's the weather in Paris?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread)
print(f"Agent: {result1.text}")
# The thread ID is set after the first response
existing_thread_id = thread.service_thread_id
print(f"Thread ID: {existing_thread_id}")
if existing_thread_id:
print("\n--- Continuing with the same thread ID in a new agent instance ---")
# Create a new agent instance from the same provider
agent2 = await provider.create_agent(
name="BasicWeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
# Create a thread with the existing ID
thread = agent2.get_new_thread(service_thread_id=existing_thread_id)
query2 = "What was the last city I asked about?"
print(f"User: {query2}")
result2 = await agent2.run(query2, thread=thread)
print(f"Agent: {result2.text}")
print("Note: The agent continues the conversation from the previous thread by using thread ID.\n")
async def main() -> None:
print("=== Azure AI Agent Thread Management Examples ===\n")
await example_with_automatic_thread_creation()
await example_with_thread_persistence_in_memory()
await example_with_existing_thread_id()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,49 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import HostedWebSearchTool
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent With Web Search
This sample demonstrates basic usage of AzureAIProjectAgentProvider to create an agent
that can perform web searches using the HostedWebSearchTool.
Pre-requisites:
- Make sure to set up the AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME
environment variables before running this sample.
"""
async def main() -> None:
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIProjectAgentProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="WebsearchAgent",
instructions="You are a helpful assistant that can search the web",
tools=[HostedWebSearchTool()],
)
query = "What's the weather today in Seattle?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
"""
Sample output:
User: What's the weather today in Seattle?
Agent: Here is the updated weather forecast for Seattle: The current temperature is approximately 57°F,
mostly cloudy conditions, with light winds and a chance of rain later tonight. Check out more details
at the [National Weather Service](https://forecast.weather.gov/zipcity.php?inputstring=Seattle%2CWA).
"""
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,114 @@
# Azure AI Agent Examples
This folder contains examples demonstrating different ways to create and use agents with Azure AI using the `AzureAIAgentsProvider` from the `agent_framework.azure` package. These examples use the `azure-ai-agents` 1.x (V1) API surface. For updated V2 (`azure-ai-projects` 2.x) samples, see the [Azure AI V2 examples folder](../azure_ai/).
## Provider Pattern
All examples in this folder use the `AzureAIAgentsProvider` class which provides a high-level interface for agent operations:
- **`create_agent()`** - Create a new agent on the Azure AI service
- **`get_agent()`** - Retrieve an existing agent by ID or from a pre-fetched Agent object
- **`as_agent()`** - Wrap an SDK Agent object as a ChatAgent without HTTP calls
```python
from agent_framework.azure import AzureAIAgentsProvider
from azure.identity.aio import AzureCliCredential
async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="MyAgent",
instructions="You are a helpful assistant.",
tools=my_function,
)
result = await agent.run("Hello!")
```
## Examples
| File | Description |
|------|-------------|
| [`azure_ai_provider_methods.py`](azure_ai_provider_methods.py) | Comprehensive example demonstrating all `AzureAIAgentsProvider` methods: `create_agent()`, `get_agent()`, `as_agent()`, and managing multiple agents from a single provider. |
| [`azure_ai_basic.py`](azure_ai_basic.py) | The simplest way to create an agent using `AzureAIAgentsProvider`. It automatically handles all configuration using environment variables. Shows both streaming and non-streaming responses. |
| [`azure_ai_with_bing_custom_search.py`](azure_ai_with_bing_custom_search.py) | Shows how to use Bing Custom Search with Azure AI agents to find real-time information from the web using custom search configurations. Demonstrates how to set up and use HostedWebSearchTool with custom search instances. |
| [`azure_ai_with_bing_grounding.py`](azure_ai_with_bing_grounding.py) | Shows how to use Bing Grounding search with Azure AI agents to find real-time information from the web. Demonstrates web search capabilities with proper source citations and comprehensive error handling. |
| [`azure_ai_with_bing_grounding_citations.py`](azure_ai_with_bing_grounding_citations.py) | Demonstrates how to extract and display citations from Bing Grounding search responses. Shows how to collect citation annotations (title, URL, snippet) during streaming responses, enabling users to verify sources and access referenced content. |
| [`azure_ai_with_code_interpreter_file_generation.py`](azure_ai_with_code_interpreter_file_generation.py) | Shows how to retrieve file IDs from code interpreter generated files using both streaming and non-streaming approaches. |
| [`azure_ai_with_code_interpreter.py`](azure_ai_with_code_interpreter.py) | Shows how to use the HostedCodeInterpreterTool with Azure AI agents to write and execute Python code. Includes helper methods for accessing code interpreter data from response chunks. |
| [`azure_ai_with_existing_agent.py`](azure_ai_with_existing_agent.py) | Shows how to work with an existing SDK Agent object using `provider.as_agent()`. This wraps the agent without making HTTP calls. |
| [`azure_ai_with_existing_thread.py`](azure_ai_with_existing_thread.py) | Shows how to work with a pre-existing thread by providing the thread ID. Demonstrates proper cleanup of manually created threads. |
| [`azure_ai_with_explicit_settings.py`](azure_ai_with_explicit_settings.py) | Shows how to create an agent with explicitly configured provider settings, including project endpoint and model deployment name. |
| [`azure_ai_with_azure_ai_search.py`](azure_ai_with_azure_ai_search.py) | Demonstrates how to use Azure AI Search with Azure AI agents. Shows how to create an agent with search tools using the SDK directly and wrap it with `provider.get_agent()`. |
| [`azure_ai_with_file_search.py`](azure_ai_with_file_search.py) | Demonstrates how to use the HostedFileSearchTool with Azure AI agents to search through uploaded documents. Shows file upload, vector store creation, and querying document content. |
| [`azure_ai_with_function_tools.py`](azure_ai_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
| [`azure_ai_with_hosted_mcp.py`](azure_ai_with_hosted_mcp.py) | Shows how to integrate Azure AI agents with hosted Model Context Protocol (MCP) servers for enhanced functionality and tool integration. Demonstrates remote MCP server connections and tool discovery. |
| [`azure_ai_with_local_mcp.py`](azure_ai_with_local_mcp.py) | Shows how to integrate Azure AI agents with local Model Context Protocol (MCP) servers for enhanced functionality and tool integration. Demonstrates both agent-level and run-level tool configuration. |
| [`azure_ai_with_multiple_tools.py`](azure_ai_with_multiple_tools.py) | Demonstrates how to use multiple tools together with Azure AI agents, including web search, MCP servers, and function tools. Shows coordinated multi-tool interactions and approval workflows. |
| [`azure_ai_with_openapi_tools.py`](azure_ai_with_openapi_tools.py) | Demonstrates how to use OpenAPI tools with Azure AI agents to integrate external REST APIs. Shows OpenAPI specification loading, anonymous authentication, thread context management, and coordinated multi-API conversations. |
| [`azure_ai_with_response_format.py`](azure_ai_with_response_format.py) | Demonstrates how to use structured outputs with Azure AI agents using Pydantic models. |
| [`azure_ai_with_thread.py`](azure_ai_with_thread.py) | Demonstrates thread management with Azure AI agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |
## Environment Variables
Before running the examples, you need to set up your environment variables. You can do this in one of two ways:
### Option 1: Using a .env file (Recommended)
1. Copy the `.env.example` file from the `python` directory to create a `.env` file:
```bash
cp ../../.env.example ../../.env
```
2. Edit the `.env` file and add your values:
```
AZURE_AI_PROJECT_ENDPOINT="your-project-endpoint"
AZURE_AI_MODEL_DEPLOYMENT_NAME="your-model-deployment-name"
```
3. For samples using Bing Grounding search (like `azure_ai_with_bing_grounding.py` and `azure_ai_with_multiple_tools.py`), you'll also need:
```
BING_CONNECTION_ID="your-bing-connection-id"
```
To get your Bing connection details:
- Go to [Azure AI Foundry portal](https://ai.azure.com)
- Navigate to your project's "Connected resources" section
- Add a new connection for "Grounding with Bing Search"
- Copy the ID
4. For samples using Bing Custom Search (like `azure_ai_with_bing_custom_search.py`), you'll also need:
```
BING_CUSTOM_CONNECTION_ID="your-bing-custom-connection-id"
BING_CUSTOM_INSTANCE_NAME="your-bing-custom-instance-name"
```
To get your Bing Custom Search connection details:
- Go to [Azure AI Foundry portal](https://ai.azure.com)
- Navigate to your project's "Connected resources" section
- Add a new connection for "Grounding with Bing Custom Search"
- Copy the connection ID and instance name
### Option 2: Using environment variables directly
Set the environment variables in your shell:
```bash
export AZURE_AI_PROJECT_ENDPOINT="your-project-endpoint"
export AZURE_AI_MODEL_DEPLOYMENT_NAME="your-model-deployment-name"
export BING_CONNECTION_ID="your-bing-connection-id"
export BING_CUSTOM_CONNECTION_ID="your-bing-custom-connection-id"
export BING_CUSTOM_INSTANCE_NAME="your-bing-custom-instance-name"
```
### Required Variables
- `AZURE_AI_PROJECT_ENDPOINT`: Your Azure AI project endpoint (required for all examples)
- `AZURE_AI_MODEL_DEPLOYMENT_NAME`: The name of your model deployment (required for all examples)
### Optional Variables
- `BING_CONNECTION_ID`: Your Bing connection ID (required for `azure_ai_with_bing_grounding.py` and `azure_ai_with_multiple_tools.py`)
- `BING_CUSTOM_CONNECTION_ID`: Your Bing Custom Search connection ID (required for `azure_ai_with_bing_custom_search.py`)
- `BING_CUSTOM_INSTANCE_NAME`: Your Bing Custom Search instance name (required for `azure_ai_with_bing_custom_search.py`)

View File

@@ -0,0 +1,80 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from random import randint
from typing import Annotated
from agent_framework.azure import AzureAIAgentsProvider
from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Agent Basic Example
This sample demonstrates basic usage of AzureAIAgentsProvider to create agents with automatic
lifecycle management. Shows both streaming and non-streaming responses with function tools.
"""
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def non_streaming_example() -> None:
"""Example of non-streaming response (get the complete result at once)."""
print("=== Non-streaming Response Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
query = "What's the weather like in Seattle?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
async def streaming_example() -> None:
"""Example of streaming response (get results as they are generated)."""
print("=== Streaming Response Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
query = "What's the weather like in Portland?"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
async for chunk in agent.run_stream(query):
if chunk.text:
print(chunk.text, end="", flush=True)
print("\n")
async def main() -> None:
print("=== Basic Azure AI Chat Client Agent Example ===")
await non_streaming_example()
await streaming_example()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,142 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from random import randint
from typing import Annotated
from agent_framework.azure import AzureAIAgentsProvider
from azure.ai.agents.aio import AgentsClient
from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Agent Provider Methods Example
This sample demonstrates the methods available on the AzureAIAgentsProvider class:
- create_agent(): Create a new agent on the service
- get_agent(): Retrieve an existing agent by ID
- as_agent(): Wrap an SDK Agent object without making HTTP calls
"""
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def create_agent_example() -> None:
"""Create a new agent using provider.create_agent()."""
print("\n--- create_agent() ---")
async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="WeatherAgent",
instructions="You are a helpful weather assistant.",
tools=get_weather,
)
print(f"Created: {agent.name} (ID: {agent.id})")
result = await agent.run("What's the weather in Seattle?")
print(f"Response: {result}")
async def get_agent_example() -> None:
"""Retrieve an existing agent by ID using provider.get_agent()."""
print("\n--- get_agent() ---")
async with (
AzureCliCredential() as credential,
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
AzureAIAgentsProvider(agents_client=agents_client) as provider,
):
# Create an agent directly with SDK (simulating pre-existing agent)
sdk_agent = await agents_client.create_agent(
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
name="ExistingAgent",
instructions="You always respond with 'Hello!'",
)
try:
# Retrieve using provider
agent = await provider.get_agent(sdk_agent.id)
print(f"Retrieved: {agent.name} (ID: {agent.id})")
result = await agent.run("Hi there!")
print(f"Response: {result}")
finally:
await agents_client.delete_agent(sdk_agent.id)
async def as_agent_example() -> None:
"""Wrap an SDK Agent object using provider.as_agent()."""
print("\n--- as_agent() ---")
async with (
AzureCliCredential() as credential,
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
AzureAIAgentsProvider(agents_client=agents_client) as provider,
):
# Create agent using SDK
sdk_agent = await agents_client.create_agent(
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
name="WrappedAgent",
instructions="You respond with poetry.",
)
try:
# Wrap synchronously (no HTTP call)
agent = provider.as_agent(sdk_agent)
print(f"Wrapped: {agent.name} (ID: {agent.id})")
result = await agent.run("Tell me about the sunset.")
print(f"Response: {result}")
finally:
await agents_client.delete_agent(sdk_agent.id)
async def multiple_agents_example() -> None:
"""Create and manage multiple agents with a single provider."""
print("\n--- Multiple Agents ---")
async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
):
weather_agent = await provider.create_agent(
name="WeatherSpecialist",
instructions="You are a weather specialist.",
tools=get_weather,
)
greeter_agent = await provider.create_agent(
name="GreeterAgent",
instructions="You are a friendly greeter.",
)
print(f"Created: {weather_agent.name}, {greeter_agent.name}")
greeting = await greeter_agent.run("Hello!")
print(f"Greeter: {greeting}")
weather = await weather_agent.run("What's the weather in Tokyo?")
print(f"Weather: {weather}")
async def main() -> None:
print("Azure AI Agent Provider Methods")
await create_agent_example()
await get_agent_example()
await as_agent_example()
await multiple_agents_example()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,116 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework import Annotation
from agent_framework.azure import AzureAIAgentsProvider
from azure.ai.agents.aio import AgentsClient
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import ConnectionType
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Azure AI Search Example
This sample demonstrates how to create an Azure AI agent that uses Azure AI Search
to search through indexed hotel data and answer user questions about hotels.
Prerequisites:
1. Set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME environment variables
2. Ensure you have an Azure AI Search connection configured in your Azure AI project
3. The search index "hotels-sample-index" should exist in your Azure AI Search service
(you can create this using the Azure portal with sample hotel data)
NOTE: To ensure consistent search tool usage:
- Include explicit instructions for the agent to use the search tool
- Mention the search requirement in your queries
- Use `tool_choice="required"` to force tool usage
More info on `query type` can be found here:
https://learn.microsoft.com/en-us/python/api/azure-ai-agents/azure.ai.agents.models.aisearchindexresource?view=azure-python-preview
"""
async def main() -> None:
"""Main function demonstrating Azure AI agent with raw Azure AI Search tool."""
print("=== Azure AI Agent with Raw Azure AI Search Tool ===")
# Create the client and manually create an agent with Azure AI Search tool
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
AzureAIAgentsProvider(agents_client=agents_client) as provider,
):
ai_search_conn_id = ""
async for connection in project_client.connections.list():
if connection.type == ConnectionType.AZURE_AI_SEARCH:
ai_search_conn_id = connection.id
break
# 1. Create Azure AI agent with the search tool using SDK directly
# (Azure AI Search tool requires special tool_resources configuration)
azure_ai_agent = await agents_client.create_agent(
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
name="HotelSearchAgent",
instructions=(
"You are a helpful agent that searches hotel information using Azure AI Search. "
"Always use the search tool and index to find hotel data and provide accurate information."
),
tools=[{"type": "azure_ai_search"}],
tool_resources={
"azure_ai_search": {
"indexes": [
{
"index_connection_id": ai_search_conn_id,
"index_name": "hotels-sample-index",
"query_type": "vector",
}
]
}
},
)
try:
# 2. Use provider.as_agent() to wrap the existing agent
agent = provider.as_agent(agent=azure_ai_agent)
print("This agent uses raw Azure AI Search tool to search hotel data.\n")
# 3. Simulate conversation with the agent
user_input = (
"Use Azure AI search knowledge tool to find detailed information about a winter hotel."
" Use the search tool and index." # You can modify prompt to force tool usage
)
print(f"User: {user_input}")
print("Agent: ", end="", flush=True)
# Stream the response and collect citations
citations: list[Annotation] = []
async for chunk in agent.run_stream(user_input):
if chunk.text:
print(chunk.text, end="", flush=True)
# Collect citations from Azure AI Search responses
for content in getattr(chunk, "contents", []):
annotations = getattr(content, "annotations", [])
if annotations:
citations.extend(annotations)
print()
# Display collected citation
if citations:
print("\n\nCitation:")
for i, citation in enumerate(citations, 1):
print(f"[{i}] {citation.get('url')}")
print("\n" + "=" * 50 + "\n")
print("Hotel search conversation completed!")
finally:
# Clean up the agent manually
await agents_client.delete_agent(azure_ai_agent.id)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,64 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import HostedWebSearchTool
from agent_framework.azure import AzureAIAgentsProvider
from azure.identity.aio import AzureCliCredential
"""
The following sample demonstrates how to create an Azure AI agent that
uses Bing Custom Search to find real-time information from the web.
More information on Bing Custom Search and difference from Bing Grounding can be found here:
https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/tools/bing-custom-search
Prerequisites:
1. A connected Grounding with Bing Custom Search resource in your Azure AI project
2. Set BING_CUSTOM_CONNECTION_ID environment variable
Example: BING_CUSTOM_CONNECTION_ID="your-bing-custom-connection-id"
3. Set BING_CUSTOM_INSTANCE_NAME environment variable
Example: BING_CUSTOM_INSTANCE_NAME="your-bing-custom-instance-name"
To set up Bing Custom Search:
1. Go to Azure AI Foundry portal (https://ai.azure.com)
2. Navigate to your project's "Connected resources" section
3. Add a new connection for "Grounding with Bing Custom Search"
4. Copy the connection ID and instance name and set the appropriate environment variables
"""
async def main() -> None:
"""Main function demonstrating Azure AI agent with Bing Custom Search."""
# 1. Create Bing Custom Search tool using HostedWebSearchTool
# The connection ID and instance name will be automatically picked up from environment variables
bing_search_tool = HostedWebSearchTool(
name="Bing Custom Search",
description="Search the web for current information using Bing Custom Search",
)
# 2. Use AzureAIAgentsProvider for agent creation and management
async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="BingSearchAgent",
instructions=(
"You are a helpful agent that can use Bing Custom Search tools to assist users. "
"Use the available Bing Custom Search tools to answer questions and perform tasks."
),
tools=bing_search_tool,
)
# 3. Demonstrate agent capabilities with bing custom search
print("=== Azure AI Agent with Bing Custom Search ===\n")
user_input = "Tell me more about foundry agent service"
print(f"User: {user_input}")
response = await agent.run(user_input)
print(f"Agent: {response.text}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,60 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import HostedWebSearchTool
from agent_framework.azure import AzureAIAgentsProvider
from azure.identity.aio import AzureCliCredential
"""
The following sample demonstrates how to create an Azure AI agent that
uses Bing Grounding search to find real-time information from the web.
Prerequisites:
1. A connected Grounding with Bing Search resource in your Azure AI project
2. Set BING_CONNECTION_ID environment variable
Example: BING_CONNECTION_ID="your-bing-connection-id"
To set up Bing Grounding:
1. Go to Azure AI Foundry portal (https://ai.azure.com)
2. Navigate to your project's "Connected resources" section
3. Add a new connection for "Grounding with Bing Search"
4. Copy either the connection name or ID and set the appropriate environment variable
"""
async def main() -> None:
"""Main function demonstrating Azure AI agent with Bing Grounding search."""
# 1. Create Bing Grounding search tool using HostedWebSearchTool
# The connection ID will be automatically picked up from environment variable
bing_search_tool = HostedWebSearchTool(
name="Bing Grounding Search",
description="Search the web for current information using Bing",
)
# 2. Use AzureAIAgentsProvider for agent creation and management
async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="BingSearchAgent",
instructions=(
"You are a helpful assistant that can search the web for current information. "
"Use the Bing search tool to find up-to-date information and provide accurate, "
"well-sourced answers. Always cite your sources when possible."
),
tools=bing_search_tool,
)
# 3. Demonstrate agent capabilities with web search
print("=== Azure AI Agent with Bing Grounding Search ===\n")
user_input = "What is the most popular programming language?"
print(f"User: {user_input}")
response = await agent.run(user_input)
print(f"Agent: {response.text}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,87 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import Annotation, HostedWebSearchTool
from agent_framework.azure import AzureAIAgentsProvider
from azure.identity.aio import AzureCliCredential
"""
This sample demonstrates how to create an Azure AI agent that uses Bing Grounding
search to find real-time information from the web with comprehensive citation support.
It shows how to extract and display citations (title, URL, and snippet) from Bing
Grounding responses, enabling users to verify sources and explore referenced content.
Prerequisites:
1. A connected Grounding with Bing Search resource in your Azure AI project
2. Set BING_CONNECTION_ID environment variable
Example: BING_CONNECTION_ID="your-bing-connection-id"
To set up Bing Grounding:
1. Go to Azure AI Foundry portal (https://ai.azure.com)
2. Navigate to your project's "Connected resources" section
3. Add a new connection for "Grounding with Bing Search"
4. Copy the connection ID and set the BING_CONNECTION_ID environment variable
"""
async def main() -> None:
"""Main function demonstrating Azure AI agent with Bing Grounding search."""
# 1. Create Bing Grounding search tool using HostedWebSearchTool
# The connection ID will be automatically picked up from environment variable
bing_search_tool = HostedWebSearchTool(
name="Bing Grounding Search",
description="Search the web for current information using Bing",
)
# 2. Use AzureAIAgentsProvider for agent creation and management
async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="BingSearchAgent",
instructions=(
"You are a helpful assistant that can search the web for current information. "
"Use the Bing search tool to find up-to-date information and provide accurate, "
"well-sourced answers. Always cite your sources when possible."
),
tools=bing_search_tool,
)
# 3. Demonstrate agent capabilities with web search
print("=== Azure AI Agent with Bing Grounding Search ===\n")
user_input = "What is the most popular programming language?"
print(f"User: {user_input}")
print("Agent: ", end="", flush=True)
# Stream the response and collect citations
citations: list[Annotation] = []
async for chunk in agent.run_stream(user_input):
if chunk.text:
print(chunk.text, end="", flush=True)
# Collect citations from Bing Grounding responses
for content in getattr(chunk, "contents", []):
annotations = getattr(content, "annotations", [])
if annotations:
citations.extend(annotations)
print()
# Display collected citations
if citations:
print("\n\nCitations:")
for i, citation in enumerate(citations, 1):
print(f"[{i}] {citation['title']}: {citation.get('url')}")
if "snippet" in citation:
print(f" Snippet: {citation.get('snippet')}")
else:
print("\nNo citations found in the response.")
print()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,59 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import AgentResponse, ChatResponseUpdate, HostedCodeInterpreterTool
from agent_framework.azure import AzureAIAgentsProvider
from azure.ai.agents.models import (
RunStepDeltaCodeInterpreterDetailItemObject,
)
from azure.identity.aio import AzureCliCredential
"""
Azure AI Agent with Code Interpreter Example
This sample demonstrates using HostedCodeInterpreterTool with Azure AI Agents
for Python code execution and mathematical problem solving.
"""
def print_code_interpreter_inputs(response: AgentResponse) -> None:
"""Helper method to access code interpreter data."""
print("\nCode Interpreter Inputs during the run:")
if response.raw_representation is None:
return
for chunk in response.raw_representation:
if isinstance(chunk, ChatResponseUpdate) and isinstance(
chunk.raw_representation, RunStepDeltaCodeInterpreterDetailItemObject
):
print(chunk.raw_representation.input, end="")
print("\n")
async def main() -> None:
"""Example showing how to use the HostedCodeInterpreterTool with Azure AI."""
print("=== Azure AI Agent with Code Interpreter Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
):
agent = await provider.create_agent(
name="CodingAgent",
instructions=("You are a helpful assistant that can write and execute Python code to solve problems."),
tools=HostedCodeInterpreterTool(),
)
query = "Generate the factorial of 100 using python code, show the code and execute it."
print(f"User: {query}")
response = await agent.run(query)
print(f"Agent: {response}")
# To review the code interpreter outputs, you can access
# them from the response raw_representations, just uncomment the next line:
# print_code_interpreter_inputs(response)
if __name__ == "__main__":
asyncio.run(main())

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