Python: Foundry Agent Completeness (#954)

* foundry completeness

* tests + openapi sample

* bing grounding sample

* options integration tests

* merge conflict fix

* fix failing test

* add mcp approval handling
This commit is contained in:
Giles Odigwe
2025-10-01 06:38:51 -07:00
committed by GitHub
Unverified
parent 74e2e2e21d
commit b0971fdec6
10 changed files with 1520 additions and 25 deletions
@@ -7,12 +7,17 @@ This folder contains examples demonstrating different ways to create and use age
| File | Description |
|------|-------------|
| [`azure_ai_basic.py`](azure_ai_basic.py) | The simplest way to create an agent using `ChatAgent` with `AzureAIAgentClient`. It automatically handles all configuration using environment variables. |
| [`azure_ai_with_explicit_settings.py`](azure_ai_with_explicit_settings.py) | Shows how to create an agent with explicitly configured `AzureAIAgentClient` settings, including project endpoint, model deployment, credentials, and agent name. |
| [`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_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 a pre-existing agent by providing the agent ID to the Azure AI chat client. This example also demonstrates proper cleanup of manually created agents. |
| [`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 to the Azure AI chat client. This example also 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 `AzureAIAgentClient` settings, including project endpoint, model deployment, credentials, and agent name. |
| [`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. Includes both streaming and non-streaming examples. |
| [`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_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_local_mcp.py`](azure_ai_with_local_mcp.py) | Shows how to integrate Azure AI agents with Model Context Protocol (MCP) servers for enhanced functionality and tool integration. Demonstrates both agent-level and run-level tool configuration. |
| [`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 using weather and countries APIs. |
| [`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
@@ -0,0 +1,61 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import ChatAgent, HostedWebSearchTool
from agent_framework_azure_ai import AzureAIAgentClient
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 the BING_CONNECTION_ID environment variable to your Bing connection ID
Example: BING_CONNECTION_ID="/subscriptions/{subscription-id}/resourceGroups/{resource-group}/
providers/Microsoft.CognitiveServices/accounts/{ai-service-name}/projects/{project-name}/
connections/{connection-name}"
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 it as 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 BING_CONNECTION_ID environment variable
bing_search_tool = HostedWebSearchTool(
name="Bing Grounding Search",
description="Search the web for current information using Bing",
)
# 2. Use AzureAIAgentClient as async context manager for automatic cleanup
async with (
AzureAIAgentClient(async_credential=AzureCliCredential()) as client,
ChatAgent(
chat_client=client,
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,
) as agent,
):
# 4. 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())
@@ -0,0 +1,81 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from pathlib import Path
from agent_framework import ChatAgent, HostedFileSearchTool, HostedVectorStoreContent
from agent_framework_azure_ai import AzureAIAgentClient
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."""
client = AzureAIAgentClient(async_credential=AzureCliCredential())
file: FileInfo | None = None
vector_store: VectorStore | None = None
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 client.project_client.agents.files.upload_and_poll(
file_path=str(pdf_file_path), purpose="assistants"
)
print(f"Uploaded file, file ID: {file.id}")
vector_store = await client.project_client.agents.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
# The tool_resources are automatically extracted from HostedFileSearchTool
async with ChatAgent(
chat_client=client,
name="EmployeeSearchAgent",
instructions=(
"You are a helpful assistant that can search through uploaded employee files "
"to answer questions about employees."
),
tools=file_search_tool,
) as agent:
# 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
try:
if vector_store is not None:
await client.project_client.agents.vector_stores.delete(vector_store.id)
if file is not None:
await client.project_client.agents.files.delete(file.id)
except Exception:
# Ignore cleanup errors to avoid masking issues
pass
finally:
await client.close()
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,91 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import json
from pathlib import Path
from typing import Any
from agent_framework import ChatAgent
from agent_framework_azure_ai import AzureAIAgentClient
from azure.ai.agents.models import OpenApiAnonymousAuthDetails, OpenApiTool
from azure.identity.aio import AzureCliCredential
"""
The following sample demonstrates how to create a simple, Azure AI agent that
uses OpenAPI tools to answer user questions.
"""
# Simulate a conversation with the agent
USER_INPUTS = [
"What is the name and population of the country that uses currency with abbreviation THB?",
"What is the current weather in the capital city of that country?",
]
def load_openapi_specs() -> tuple[dict[str, Any], dict[str, Any]]:
"""Load OpenAPI specification files."""
resources_path = Path(__file__).parent.parent / "resources"
with open(resources_path / "weather.json") as weather_file:
weather_spec = json.load(weather_file)
with open(resources_path / "countries.json") as countries_file:
countries_spec = json.load(countries_file)
return weather_spec, countries_spec
async def main() -> None:
"""Main function demonstrating Azure AI agent with OpenAPI tools."""
# 1. Load OpenAPI specifications (synchronous operation)
weather_openapi_spec, countries_openapi_spec = load_openapi_specs()
# 2. Use AzureAIAgentClient as async context manager for automatic cleanup
async with AzureAIAgentClient(async_credential=AzureCliCredential()) as client:
# 3. Create OpenAPI tools using Azure AI's OpenApiTool
auth = OpenApiAnonymousAuthDetails()
openapi_weather = OpenApiTool(
name="get_weather",
spec=weather_openapi_spec,
description="Retrieve weather information for a location using wttr.in service",
auth=auth,
)
openapi_countries = OpenApiTool(
name="get_country_info",
spec=countries_openapi_spec,
description="Retrieve country information including population and capital city",
auth=auth,
)
# 4. Create an agent with OpenAPI tools
# Note: We need to pass the Azure AI native OpenApiTool definitions directly
# since the agent framework doesn't have a HostedOpenApiTool wrapper yet
async with ChatAgent(
chat_client=client,
name="OpenAPIAgent",
instructions=(
"You are a helpful assistant that can search for country information "
"and weather data using APIs. When asked about countries, use the country "
"API to find information. When asked about weather, use the weather API. "
"Provide clear, informative answers based on the API results."
),
# Pass the raw tool definitions from Azure AI's OpenApiTool
tools=[*openapi_countries.definitions, *openapi_weather.definitions],
) as agent:
# 5. Simulate conversation with the agent maintaining thread context
print("=== Azure AI Agent with OpenAPI Tools ===\n")
# Create a thread to maintain conversation context across multiple runs
thread = agent.get_new_thread()
for user_input in USER_INPUTS:
print(f"User: {user_input}")
# Pass the thread to maintain context across multiple agent.run() calls
response = await agent.run(user_input, thread=thread)
print(f"Agent: {response.text}\n")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,141 @@
{
"openapi": "3.0.1",
"info": {
"title": "REST Countries API",
"description": "Get information about countries of the world",
"version": "3.1"
},
"servers": [
{
"url": "https://restcountries.com/v3.1"
}
],
"paths": {
"/currency/{currency}": {
"get": {
"operationId": "getCountriesByCurrency",
"summary": "Get countries by currency",
"description": "Search for countries by currency code",
"parameters": [
{
"name": "currency",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Currency code (e.g., THB, USD, EUR)"
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "object",
"properties": {
"common": {"type": "string"},
"official": {"type": "string"}
}
},
"population": {"type": "integer"},
"region": {"type": "string"},
"subregion": {"type": "string"},
"capital": {
"type": "array",
"items": {"type": "string"}
},
"currencies": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"name": {"type": "string"},
"symbol": {"type": "string"}
}
}
},
"languages": {
"type": "object",
"additionalProperties": {"type": "string"}
},
"latlng": {
"type": "array",
"items": {"type": "number"}
}
}
}
}
}
}
}
}
}
},
"/name/{name}": {
"get": {
"operationId": "getCountryByName",
"summary": "Get country by name",
"description": "Search for countries by name",
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Country name"
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "object",
"properties": {
"common": {"type": "string"},
"official": {"type": "string"}
}
},
"population": {"type": "integer"},
"region": {"type": "string"},
"subregion": {"type": "string"},
"capital": {
"type": "array",
"items": {"type": "string"}
},
"currencies": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"name": {"type": "string"},
"symbol": {"type": "string"}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,76 @@
%PDF-1.7
%
1 0 obj
<</Type/Catalog/Pages 2 0 R/Lang(en) /StructTreeRoot 22 0 R/MarkInfo<</Marked true>>/Metadata 132 0 R/ViewerPreferences 133 0 R>>
endobj
2 0 obj
<</Type/Pages/Count 1/Kids[ 4 0 R] >>
endobj
3 0 obj
<</Author(Test Author) /Creator(Test Creator) /Title(Employee Directory) >>
endobj
4 0 obj
<</Type/Page/Parent 2 0 R/MediaBox[0 0 612 792]/Resources<</Font<</F1 5 0 R>>>>/Contents 6 0 R>>
endobj
5 0 obj
<</Type/Font/Subtype/Type1/BaseFont/Times-Roman>>
endobj
6 0 obj
<</Length 200>>
stream
BT
/F1 12 Tf
50 750 Td
(Employee Directory) Tj
0 -30 Td
(Name: John Smith) Tj
0 -15 Td
(Department: Engineering) Tj
0 -15 Td
(Age: 28) Tj
0 -30 Td
(Name: Alice Johnson) Tj
0 -15 Td
(Department: Sales) Tj
0 -15 Td
(Age: 24) Tj
0 -30 Td
(Name: Bob Wilson) Tj
0 -15 Td
(Department: Marketing) Tj
0 -15 Td
(Age: 35) Tj
ET
endstream
endobj
22 0 obj
<</Type/StructTreeRoot>>
endobj
132 0 obj
<</Type/Metadata/Subtype/XML>>
endobj
133 0 obj
<</DisplayDocTitle true>>
endobj
xref
0 10
0000000000 65535 f
0000000015 00000 n
0000000152 00000 n
0000000209 00000 n
0000000300 00000 n
0000000420 00000 n
0000000490 00000 n
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
22 1
0000000740 00000 n
132 2
0000000780 00000 n
0000000820 00000 n
trailer
<</Size 134/Root 1 0 R/Info 3 0 R>>
startxref
860
%%EOF
@@ -0,0 +1,62 @@
{
"openapi": "3.1.0",
"info": {
"title": "wttr.in Weather API",
"description": "Retrieves current weather data for a location using wttr.in service",
"version": "v1.0.0"
},
"servers": [
{
"url": "https://wttr.in"
}
],
"paths": {
"/{location}": {
"get": {
"operationId": "GetCurrentWeather",
"summary": "Get weather information for a specific location",
"description": "Get weather information for a specific location",
"parameters": [
{
"name": "location",
"in": "path",
"description": "City or location to retrieve the weather for",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "format",
"in": "query",
"description": "Format in which to return data. Always use 3.",
"required": true,
"schema": {
"type": "integer",
"default": 3
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"404": {
"description": "Location not found"
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {}
}
}