Working with the OpenAI SDK and the Agents SDK

So far in this course we have been using the langchain and langgraph packages exclusively to build our applications. These packages are not the only packages available to build AI agents. In fact, many different libraries have been released over the last few years for this purpose. These notes will introduce you to a couple of alternatives.

The packages we are going to work with are the OpenAI SDK and the OpenAI Agents SDK. We will be using these packages to work with the same LLMs we have been using all term long.

Getting started

To be able to run my examples, you should create a new python environment and install the two packages you will need in that environment:

python3 -m venv openai
source openai/bin/activate
pip install openai openai-agents

First example

The first short example program demonstrates how to send a simple query to an LLM using the OpenAI SDK:

from openai import OpenAI

client = OpenAI(base_url="http://143.44.10.188:1234/v1",api_key="not-needed")

response = client.chat.completions.create(
    model="gpt-oss:20b",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain what MXFP4 quantization is."}
    ]
)
 
print(response.choices[0].message.content)

If you look at the official documentation for the OpenAI SDK you will see them using a newer API, the responses API. Since the gpt-oss models we have been using do not support this newer API, we have to revert back to use the older completions API.

Tool calling example

The next example demonstrates how tool calling works in the OpenAI SDK.

from openai import OpenAI
import json

client = OpenAI(base_url="http://143.44.10.188:1234/v1",api_key="not-needed")

# Define a tool (function) the model can call
def get_current_weather(location: str):
    """
    Get the current weather in a given location.
    """
    # In a real application, you would call an external API here
    weather_data = {
        "location": location,
        "temperature": "25C",
        "conditions": "Sunny"
    }
    return json.dumps(weather_data)

# Define the tool's schema for the model
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "Get the current weather in a given location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {"type": "string", "description": "The city and state, e.g., San Francisco, CA"},
                },
                "required": ["location"],
            },
        },
    }
]

# First interaction: Ask the model a question that might require a tool
messages = [{"role": "user", "content": "What's the weather like in London?"}]
response = client.chat.completions.create(
    model="gpt-oss",
    messages=messages,
    tools=tools,
    tool_choice="auto",  # Allow the model to decide whether to call a tool
)

# Check if the model decided to call a tool
if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0]
    function_name = tool_call.function.name
    function_args = json.loads(tool_call.function.arguments)

    if function_name == "get_current_weather":
        # Execute the tool and get the result
        tool_output = get_current_weather(location=function_args.get("location"))

        # Add the tool output to the conversation history
        messages.append(response.choices[0].message)  # Add the model's tool call message
        messages.append(
            {
                "tool_call_id": tool_call.id,
                "role": "tool",
                "name": function_name,
                "content": tool_output,
            }
        )

        # Second interaction: Send the tool output back to the model for a final response
        second_response = client.chat.completions.create(
            model="gpt-oss:20b",
            messages=messages,
        )
        print(second_response.choices[0].message.content)
else:
    print(response.choices[0].message.content)

What makes this example so verbose is the fact that we have to handle the tool interaction manually. When we get a response back to the initial query the response will contain a tool call. We have to manually unpack the information about the tool the LLM is calling, call the tool ourselves, and then attach a message to the end of the list of messages with the results of the tool call. We then send the modified list of messages back to the LLM, and then wait for the final answer to come back.

Agents example

The Agents SDK is a newer SDK from OpenAI aimed at making it easier to construct agents that can use tools.

Here is an example that demonstrates how to set up a tool and an agent that can use that tool.

from openai import AsyncOpenAI
from agents import Agent, OpenAIChatCompletionsModel, Runner, function_tool

@function_tool
def get_current_time(timezone: str):
    """
    Returns the current time in a specified timezone.
    Args:
        timezone (str): The timezone to get the time for (e.g., "America/New_York").
    """
    # Return a fake time
    return f"The current time in {timezone} is approximately 1:30 PM."

def main():
    local_model = OpenAIChatCompletionsModel(
        model="gpt-oss",
        openai_client=AsyncOpenAI(
            base_url="http://143.44.10.188:1234/v1",
            api_key="not-needed"
        )
    )

    agent = Agent(
        name="TimeAgent",
        instructions="You are an assistant that can tell the current time in different timezones. Use the get_current_time tool when asked about the time.",
        model=local_model,
        tools=[get_current_time],
    )

    print("Running agent with 'What time is it in London?'")
    result = Runner.run_sync(agent, "What time is it in London?")
    print(f"Agent response: {result.final_output}")

if __name__ == "__main__":
    main()

One adjustment I had to make here was in how the model got set up. Once again, the problem we have to work around is that the Agents SDK prefers to use the new responses API with models, while our gpt-oss model only supports the older completions API. The solution is to use OpenAIChatCompletionsModel to force the Agents SDK to revert back to the older completions API when communicating with the LLM.

Setting up and using the Agent in this example looks very similar to the approach that we used with langgraph.

Handoffs

The Agents SDK also features classes that can help you construct routers for multi-agent applications. In the Agents SDK these are called handoffs.

Here is an example demonstrating the use of multiple agents with handoffs.

## Example adapted from https://github.com/openai/openai-agents-python/blob/main/examples/handoffs/message_filter.py
import random
import json

from openai import AsyncOpenAI
from agents import Agent, OpenAIChatCompletionsModel, Runner, function_tool, handoff, trace

@function_tool
def random_number_tool(max: int) -> int:
    """Return a random integer between 0 and the given maximum."""
    return random.randint(0, max)

local_model = OpenAIChatCompletionsModel(
        model="gpt-oss",
        openai_client=AsyncOpenAI(
            base_url="http://143.44.10.188:1234/v1",
            api_key="not-needed"
        )
    )

first_agent = Agent(
    name="Assistant",
    instructions="Be extremely concise.",
    model=local_model,
    tools=[random_number_tool],
)

spanish_agent = Agent(
    name="Spanish Assistant",
    instructions="You only speak Spanish and are extremely concise.",
    handoff_description="A Spanish-speaking assistant.",
    model=local_model
)

second_agent = Agent(
    name="Assistant",
    instructions=(
        "Be a helpful assistant. If the user speaks Spanish, handoff to the Spanish assistant."
    ),
    model=local_model,
    handoffs=[handoff(spanish_agent)],
)


async def main():
    # Trace the entire run as a single workflow
    with trace(workflow_name="Message filtering"):
        # 1. Send a regular message to the first agent
        result = await Runner.run(first_agent, input="Hi, my name is Sora.")

        print("Step 1 done")

        # 2. Ask it to generate a number
        result = await Runner.run(
            first_agent,
            input=result.to_input_list()
            + [{"content": "Can you generate a random number between 0 and 100?", "role": "user"}],
        )

        print("Step 2 done")

        # 3. Call the second agent
        result = await Runner.run(
            second_agent,
            input=result.to_input_list()
            + [
                {
                    "content": "I live in New York City. Whats the population of the city?",
                    "role": "user",
                }
            ],
        )

        print("Step 3 done")

        # 4. Cause a handoff to occur
        result = await Runner.run(
            second_agent,
            input=result.to_input_list()
            + [
                {
                    "content": "Por favor habla en español. ¿Cuál es mi nombre y dónde vivo?",
                    "role": "user",
                }
            ],
        )

        print("Step 4 done")

    print("\n===Final messages===\n")

    # 5. Let's print the messages to see what happened
    for message in result.to_input_list():
        print(json.dumps(message, indent=2))

if __name__ == "__main__":
    import asyncio

    asyncio.run(main())

Further reading

Here a link to the documentation for the OpenAI SDK. (Note: the newest version of the library uses the responses API. The example above uses the older completions API instead, since gpt-oss does not support the newer API.)

Here is a link to the documentation for the OpenAI Agents SDK, which is a more high level API for creating agents that can use tools.