AI Agents with OpenAPI Tools - Part 1: Semantic Kernel

Β· 1652 words Β· 8 minutes to read

Today we will kick off a short series on building AI agents which have access to OpenAPI tools. In this first part, we will focus on the Semantic Kernel, and in the second part, we will look at Azure AI Foundry.

Series overview πŸ”—

  • Part 1 (this part): Semantic Kernel
  • Part 2 (coming soon): Azure AI Foundry

Overview πŸ”—

Tool calling is an integral part of building AI agents, allowing them to interact with external APIs and services. I covered the basics of tool calling with Azure OpenAI last year, but in the ever changing landscape of AI, there are new tools and frameworks that make this process even more streamlined.

With the rise of AI agents, we can differentiate three primary main approaches to tool calling:

  1. Dedicated tools: hand-written adapters or functions that are tailored to a specific API or service. These custom integrations sit alongside the AI model, offering precise control, customized error-handling, and a predictable interaction pattern. There is a tight coupling between the code orchestrating the agent and the APIs it uses.
  2. Schema-Driven tools: generic JSON/HTTP interfaces defined by a formal schema - most commonly OpenAPI specifications or Model Context Protocol (MCP) manifests. By importing a spec or manifest, the AI can call any compliant API through a uniform β€œfunction-calling” interface, without needing dedicated code for each endpoint.
  3. Sandboxed code execution: a live REPL (e.g. Python or JavaScript) environment exposed to the model, where it can generate, execute, and iterate on code on the fly. This on-demand compute layer excels at ad-hoc data manipulation, complex transformations, and mathematical reasoning, without requiring pre-defined API bindings.

In this series, we will focus on the schema-driven approach - more specifically, one based on OpenAPI contracts. And in part 1, we will use the Semantic Kernel to build an AI agent that can call OpenAPI tools.

Setting up the prerequisites πŸ”—

Note: For this example, we will use Python, but the same principles apply to C# and Java (other languages supported by Semantic Kernel).

Let’s start by installing the necessary packages:

pip install semantic_kernel[azure] fastapi uvicorn requests

We will also need an OpenAPI tool to call. For the purposes of this post, we will use a dummy weather forecast FastAPI application that exposes an OpenAPI spec. You can create a file named fastapi_weather.py with the following content:

import datetime
import random
from typing import List, Optional
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
import uvicorn

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": "http://localhost:5270",
            "description": "API server"
        }
    ]
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class WeatherForecast(BaseModel):
    """
    Weather forecast data model containing temperature and weather conditions for a specific date.
    """
    date: str = Field(..., description="The date of the forecast in ISO format (YYYY-MM-DD)", example="2025-06-23")
    temperatureC: int = Field(..., description="Temperature in Celsius degrees", example=25, ge=-50, le=60)
    summary: str = Field(..., description="Brief description of weather conditions", example="Warm")
    
    @property
    def temperatureF(self) -> int:
        """Convert Celsius temperature to Fahrenheit"""
        return 32 + int(self.temperatureC / 0.5556)

summaries = [
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", 
    "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
]

@app.get(
    "/weatherforecast", 
    response_model=List[WeatherForecast], 
    operation_id="GetWeatherForecast",
    summary="Get 5-day weather forecast",
    description="Retrieves a 5-day weather forecast starting from today. Each forecast includes the date, temperature in Celsius (and Fahrenheit), and a summary of weather conditions.",
    response_description="A list of weather forecasts for the next 5 days",
    tags=["Weather"]
)
async def get_weather_forecast():
    """
    Get a 5-day weather forecast starting from today.
    
    This endpoint generates random weather data for demonstration purposes.
    Each forecast includes:
    - Date in ISO format (YYYY-MM-DD)
    - Temperature in Celsius (automatically converted to Fahrenheit)
    - Weather summary description
    
    Returns:
        List[WeatherForecast]: A list of 5 weather forecasts starting from today
    """
    forecasts = []
    for index in range(0, 5):
        current_date = datetime.datetime.now() + datetime.timedelta(days=index)
        forecast = WeatherForecast(
            date=current_date.strftime("%Y-%m-%d"),
            temperatureC=random.randint(-20, 55),
            summary=random.choice(summaries)
        )
        forecasts.append(forecast)
    
    return forecasts

if __name__ == "__main__":
    uvicorn.run("fastapi_weather:app", host="127.0.0.1", port=5270, reload=True)

The API returns a 5-day weather forecast with random temperatures and summaries. You can run this FastAPI application by executing:

python fastapi_weather.py

The server is then started on localhost:5270. The OpenAPI spec is generated automatically - hence the rich and descriptive comments in the code above. You can access the OpenAPI spec at http://localhost:5270/openapi.json.

The returned document should be as follows:

{
    "openapi": "3.1.0",
    "info": {
        "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"
    },
    "servers": [
        {
            "url": "http://localhost:5270",
            "description": "API server"
        }
    ],
    "paths": {
        "/weatherforecast": {
            "get": {
                "tags": [
                    "Weather"
                ],
                "summary": "Get 5-day weather forecast",
                "description": "Retrieves a 5-day weather forecast starting from today. Each forecast includes the date, temperature in Celsius (and Fahrenheit), and a summary of weather conditions.",
                "operationId": "GetWeatherForecast",
                "responses": {
                    "200": {
                        "description": "A list of weather forecasts for the next 5 days",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "items": {
                                        "$ref": "#/components/schemas/WeatherForecast"
                                    },
                                    "type": "array",
                                    "title": "Response Getweatherforecast"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "WeatherForecast": {
                "properties": {
                    "date": {
                        "type": "string",
                        "title": "Date",
                        "description": "The date of the forecast in ISO format (YYYY-MM-DD)",
                        "example": "2025-06-23"
                    },
                    "temperatureC": {
                        "type": "integer",
                        "maximum": 60.0,
                        "minimum": -50.0,
                        "title": "Temperaturec",
                        "description": "Temperature in Celsius degrees",
                        "example": 25
                    },
                    "summary": {
                        "type": "string",
                        "title": "Summary",
                        "description": "Brief description of weather conditions",
                        "example": "Warm"
                    }
                },
                "type": "object",
                "required": [
                    "date",
                    "temperatureC",
                    "summary"
                ],
                "title": "WeatherForecast",
                "description": "Weather forecast data model containing temperature and weather conditions for a specific date."
            }
        }
    }
}

Consuming OpenAPI tools with Semantic Kernel πŸ”—

With this in place, we can now setup a Semantic Kernel AI agent that can call our OpenAPI API as its own tool. There are two primary ways how we can inject an OpenAPI plugin into Semantic Kernel. First option is to have a predefined spec content and add it directly to the kernel. The second option is to provide a URL to the OpenAPI spec, and let the kernel fetch it dynamically. In both cases we will actually use the same add_plugin_from_openapi method, but the first option requires us to fetch the OpenAPI spec ourselves, while the second option allows us to pass a URL directly.

Let’s create a file named sample.py with the following content:

import asyncio
import os
import sys
import requests

from dotenv import load_dotenv
from semantic_kernel.agents import ChatCompletionAgent
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel import Kernel

load_dotenv()

async def main():
    kernel = Kernel()
    
    api_host = os.environ.get("API_HOST", "http://localhost:5270")
    openapi_url = f"{api_host}/openapi.json"
    
    # option 1: pre-fetch OpenAPI specification dynamically from the running server
    try:
        response = requests.get(openapi_url)
        if response.status_code == 200:
            swagger_dict = 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 on {api_host}")
        return

    # add the plugin to the kernel
    weather_plugin = kernel.add_plugin_from_openapi(
        plugin_name="WeatherPlugin",
        openapi_parsed_spec=swagger_dict
    )
    print("βœ… Created OpenAPI plugin")

    # more code to follow...

This code initializes a Semantic Kernel instance and fetches the OpenAPI spec from the FastAPI server we started earlier. It then adds the OpenAPI plugin to the kernel as a pre-fetched spec. The alternative variant is probably simpler, although you are no longer in control of the OpenAPI spec retrieval.

    # option 2: create a plugin from a URL directly
    weather_plugin = kernel.add_plugin_from_openapi(
        plugin_name="WeatherPlugin",
        openapi_document_path=openapi_url,
    )
    print("βœ… Created OpenAPI plugin from URL")

The rest of the code is then simply to create an AI agent that can call the OpenAPI tool. We will use Semantic Kernel’s ChatCompletionAgent to create an agent that can interact with the OpenAPI plugin we just created. The necessary environment variables for Azure OpenAI are expected to be set in a .env file or in your environment.

    agent = ChatCompletionAgent(
        service=AzureChatCompletion(
            deployment_name=os.environ.get("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4o-mini"),
            endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
            api_key=os.environ.get("AZURE_OPENAI_API_KEY"),
        ),
        name="WeatherAgent",
        instructions="You are a helpful assistant that provides weather forecasts using the OpenAPI plugin.",
        plugins=[weather_plugin]
    )
    print("βœ… Created weather forecast agent with plugin")
    
    thread = None
    
    user_input = "What's the weather forecast for today?"
    print(f"\nπŸ’¬ User: {user_input}")
            
    response = await agent.get_response(
        messages=user_input,
        thread=thread,
    )
    print(f"\nπŸ€– Agent: {response}")
    thread = response.thread
    
    user_input = "Will it be cold tomorrow?"
    print(f"\nπŸ’¬ User: {user_input}")
    
    response = await agent.get_response(
        messages=user_input,
        thread=thread,
    )
    print(f"\nπŸ€– Agent: {response}")
            
    print("\n🧹 Cleaning up resources...")
    if thread:
        try:
            await thread.delete()
            print("βœ… Thread deleted")
        except Exception as e:
            print(f"⚠️ Error deleting thread: {e}")

if __name__ == "__main__":
    asyncio.run(main())

And that’s it! You can run this code by executing:

python sample.py

Of course make sure the API server is simultaneously running in the background! This should let a sample conversation play itself out with the AI agent, which will call the OpenAPI tool to get the weather forecast. The agent will respond to user queries about the weather, using the OpenAPI plugin we created.

An example of the output would look like this:

> python sample.py
βœ… Created OpenAPI plugin from URL
βœ… Created weather forecast agent with plugin

πŸ’¬ User: What's the weather forecast for today?

πŸ€– Agent: The weather forecast for today, June 23, 2025, is as follows:

- **Temperature:** 18Β°C
- **Summary:** Mild

Please let me know if you need the forecast for any other day or location!

πŸ’¬ User: Will it be cold tomorrow?

πŸ€– Agent: The weather forecast for tomorrow, June 24, 2025, indicates:

- **Temperature:** 35Β°C
- **Summary:** Hot

So, it will not be cold tomorrow; rather, it will be quite hot. Let me know if you need more information!

🧹 Cleaning up resources...
βœ… Thread deleted

And that’s it! It was a simple example of how to use Semantic Kernel to create an AI agent that can call OpenAPI tools. In the next part, we will look at how to do the same with Azure AI Foundry. If you are interested in the demo code from this post, you can find it on GitHub.

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