AI Agents with OpenAPI Tools - Part 2: Azure AI Foundry

Β· 1494 words Β· 8 minutes to read

In the previous part of this series, we explored how to attach OpenAPI-based tools to a Semantic Kernel AI agent. In this part, we will look at another SDK for building AI Agents, Azure AI Foundry SDK, to create an agent that can also interact with OpenAPI-based tools.

Series overview πŸ”—

Pre-requisites πŸ”—

Just like in the previous post, we will be using Python to be build the AI agent, but similar design principles apply to other languages that Azure AI Foundry supports, such as C# or JavaScript. Specifically, we will be using the Azure AI Projects package (further info here).

Let’s install all of our dependencies first. Aside from the Azure AI Projects package, we will also need the Azure Identity package to authenticate with Azure, and FastAPI to create a web server where the OpenAPI-based tools will be hosted (this is the same approach we used in the previous part).

Here is the requirements.txt file for this project:

azure-ai-projects
azure-identity
python-dotenv
fastapi
uvicorn
requests

You can install these dependencies into your preferred Python environment using:

pip install -r requirements.txt

As mentioned, the Web API tool will be the same as in the previous part, so we will not repeat it here. You can find the code for the tool in the GitHub repository and in the previous part of this series. The API will be hosted on localhost:5270 and its code is located in the fastapi_weather.py file.

Creating the AI Agent πŸ”—

We will start by creating a project client:

from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv

load_dotenv()

project_client = AIProjectClient.from_connection_string(
    credential=DefaultAzureCredential(), conn_str=os.environ["AZURE_AI_PROJECT_CONNECTION_STRING"]
)

You need to be logged into Azure CLI for the federated identity to work, or you can set up a service principal and use its credentials. The connection string can be obtained from the Azure Foundry for your AI project. In our case, we pass it as an environment variable AZURE_AI_PROJECT_CONNECTION_STRING.

Next, we will need to adddress one major difference between our approach from the previous part and the Azure AI Foundry approach. In the previous part, we used a Semantic Kernel agent, created as the so-called ChatCompletionsAgent. Such agent abstraction is ephemeral, and runs in the same location as the tools, which means that it can directly call the tools (e.g. the configured OpenAPI tools) without any additional configurations. That is also why our agent was able to call the OpenAPI tool which was running on localhost.

With Azure AI Foundry, the agent is a separate entity that runs in the cloud - so it cannot directly call the tools that are running on our development localhost. To facilitate this, we are going to set up an Azure Dev Tunnel that will expose our local web server to the internet, so that the agent can call it.

Important! The dev tunnel we will use is a basic anonymous tunnel. Allowing anonymous access to a dev tunnel means anyone on the internet is able to connect to your local server, if they can guess the dev tunnel ID. Close the tunnel as soon as possible when you are done testing.

Assuming we have the local API server running (it starts by default on port 5270):

python fastapi_weather.py

We can set up the dev tunnel with the Azure by calling:

devtunnel host -p 5270 --allow-anonymous

This should print the dev tunnel metadata:

Hosting port: 5270
Connect via browser: https://{TUNNEL ID}.{REGION}.devtunnels.ms
Inspect network activity: https://{TUNNEL URL}.{REGION}.devtunnels.ms

Ready to accept connections for tunnel: {NAME}

Now we are ready to grab the dev tunnel URL and use it in our AI agent. Let’s set the environment variable DEV_TUNNEL_URL to the dev tunnel URL and then use it in our agent orchestrator code. But before we get there, we need to update the API code to advertise the tunnel URL in the OpenAPI specification. This is important so that the agent knows where to send requests to the OpenAPI tool.

Below is the extra change needed in the fastapi_weather.py file to set the server URL dynamically based on the dev tunnel URL:

load_dotenv()

tunnel_url = os.environ.get("DEV_TUNNEL_URL")
if not tunnel_url:
    print("⚠️  Error: DEV_TUNNEL_URL environment variable is not set")
    exit(1)
else:
    print(f"πŸ”— Using tunnel URL: {tunnel_url}")
    
app = FastAPI(
    title="Weather Forecast API",
    description="A simple weather forecast API that provides 5-day weather predictions with temperature and weather conditions",
    version="1.0.0",
    openapi_url="/openapi.json",
    servers=[
        {
            "url": tunnel_url,
            "description": "API server"
        }
    ]
)

Now back to the agent orchestrator - we will prefetch the OpenAPI specification from the running server, so that we can use it to configure the OpenAPI tool in our agent.

if not tunnel_url:
    print("⚠️  Warning: DEV_TUNNEL_URL environment variable is not set")
    print("   Using {TUNNEL_URL} placeholder directly, which may cause issues")
    tunnel_url = "{TUNNEL_URL}"
else:
    print(f"πŸ”— Using tunnel URL: {tunnel_url}")

# pre-fetch OpenAPI specification dynamically from the running server
try:
    openapi_url = f"{tunnel_url}/openapi.json"
    response = requests.get(openapi_url)
    if response.status_code == 200:
        openapi_spec = response.json()
        print(f"βœ… Successfully fetched OpenAPI spec from {openapi_url}")
    else:
        raise Exception(f"Failed to fetch OpenAPI spec: HTTP {response.status_code}")
except Exception as e:
    print(f"❌ Error fetching OpenAPI spec from {openapi_url}: {e}")
    print(f"Make sure the FastAPI server is running and accessible at {tunnel_url}")
    exit(1)

auth = OpenApiAnonymousAuthDetails()

openapi = OpenApiTool(name="WeatherForecastApi", spec=openapi_spec, description="Provides access to the weather forecast", auth=auth)

Now we can create the agent and add the OpenAPI tool to it:

with project_client:
    print("\nπŸ€– Initializing Weather Forecast Agent...")
    agent = project_client.agents.create_agent(
        model="gpt-4o-mini",
        name="weather-assistant",
        instructions="You are a helpful weather expert. Use the OpenAPI tool to provide weather forecasts.",
        tools=openapi.definitions
    )
    print(f"βœ… Created agent, ID: {agent.id}")

At this point we are ready to start querying our agent. Let’s simulate a user asking for the weather forecast:

    user_query = "Get me the weather forecast for Zurich"
    thread = project_client.agents.create_thread()
    print(f"πŸ“ Created thread, ID: {thread.id}")

    message = project_client.agents.create_message(
        thread_id=thread.id,
        role="user",
        content=user_query,
    )
    print(f"πŸ’¬ User request: {user_query}")
    print(f"πŸ“€ Created message, ID: {message.id}")

    print("\n⏳ Processing request...")
    run = project_client.agents.create_run(thread_id=thread.id, agent_id=agent.id)
    print(f"πŸš€ Started agent run, ID: {run.id}")
    
    previous_status = None
    while run.status in ["queued", "in_progress", "requires_action"]:
        time.sleep(1)
        run = project_client.agents.get_run(thread_id=thread.id, run_id=run.id)
        
        if run.status != previous_status:
            print(f"⏳ Run status: {run.status}")
            previous_status = run.status
            
            if run.status == "requires_action":
                print("πŸ› οΈ  Agent is making API requests...")

    print(f"\nβœ… Run completed with final status: {run.status}")
    
    if run.status == "failed":
        print(f"❌ Run failed: {run.last_error}")

    project_client.agents.delete_agent(agent.id)
    print("🧹 Cleaned up: Agent deleted")
    print("\nπŸ’¬ Conversation summary:")
    messages = project_client.agents.list_messages(thread_id=thread.id)
    
    for msg in reversed(messages.data):
        role_emoji = "πŸ§‘" if msg.role == "user" else "πŸ€–"
        
        print(f"\n{role_emoji} {msg.role.upper()}:")
        
        if msg.content and len(msg.content) > 0 and hasattr(msg.content[0], 'text'):
            content = msg.content[0].text.value
            print(f"{content}")

This code above creates a (persistent!) agent thread, sends a user message to the agent, and waits for the agent to process the request. The agent will use the OpenAPI tool to fetch the weather forecast from our local FastAPI server running behind the dev tunnel.

The output of this should resemble the following:

> python sample.py
πŸ”— Using tunnel URL: https://REDACTED.euw.devtunnels.ms
βœ… Successfully fetched OpenAPI spec from https://REDACTED.euw.devtunnels.ms/openapi.json

πŸ€– Initializing Weather Forecast Agent...
βœ… Created agent, ID: asst_REDACTED
πŸ“ Created thread, ID: thread_REDACTED
πŸ’¬ User request: Get me the weather forecast for Zurich
πŸ“€ Created message, ID: msg_REDACTED

⏳ Processing request...
πŸš€ Started agent run, ID: run_REDACTED
⏳ Run status: RunStatus.IN_PROGRESS
⏳ Run status: RunStatus.COMPLETED

βœ… Run completed with final status: RunStatus.COMPLETED
🧹 Cleaned up: Agent deleted

πŸ’¬ Conversation summary:

πŸ§‘ USER:
Get me the weather forecast for Zurich

πŸ€– ASSISTANT:
Here is the weather forecast for Zurich:

- **June 27, 2025**: Temperature: 10Β°C (50Β°F), Summary: Balmy
- **June 28, 2025**: Temperature: 54Β°C (129Β°F), Summary: Warm
- **June 29, 2025**: Temperature: -1Β°C (30Β°F), Summary: Chilly
- **June 30, 2025**: Temperature: 47Β°C (116Β°F), Summary: Balmy
- **July 1, 2025**: Temperature: 55Β°C (131Β°F), Summary: Bracing

Please note, the temperatures provided seem unusually high 
and may require verification, 
as they may be specific to a particular context or event. 
Let me know if you need more information or another specific date!

This looks very similar to the output we had in the previous part, but this time we are not using Semantic Kernel, but rather the Azure AI Foundry SDK to create the agent. The agent is able to call the OpenAPI tool that is running on our local machine, thanks to the dev tunnel that exposes it to the internet.

Notice that the agent was even clever enough to notice that the temperatures provided by our dummy OpenAPI tool were unusually high, and it prompted us to verify them. This is a great example of how AI agents can use tools to provide more accurate and context-aware responses.

Conclusion πŸ”—

In this part of the series, we explored how to create an AI agent using Azure AI Foundry that can interact with OpenAPI-based tools. We set up a dev tunnel to expose our local FastAPI server to the internet, allowing the persistent agent sitting in Azure to call the OpenAPI tool running on our machine.

If you would like to see the complete code for this example, you can find it in the GitHub repository.

About


Hi! I'm Filip W., a software architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP