
The OpenAI agent SDK provides a way to create intelligent agents that combine reasoning, planning, tool use and external integrations. Rather than using LLM as a single point to research or get answers, it provides a framework to use multiple reasoning agents with the help of external tools maintaining proper state across model invocations. That shift unlocks more reliable automation, safer interactions, and clearer separation between logic, data access, and model behavior.
We already have gone over some of the agentic concepts, but will try to list additional concepts here.
Core Agentic Concepts
Agent
Agents are core building blocks for agentic workflows. It is the runtime that manages decision making across session. They will monitor input, evaluate results and based on that will take appropriate actions to change the internal state. This process continues till stopping condition is reached. Agents are normally reasoning LLMs that can take actions on their own based on current state.
Agent Policy
This is the logic that helps an agent to make a decision on what action to take next. In case of reasoning LLMs, they can use the learned heuristic reasoning.
Tools
Tools are functions that can do specific tasks. They are registered with the agents and can be invoked to get additional information where the agents deems necessary. As an example, think about the organization calendar. If the agent needs to setup meetings for a specific personnel within the org, it has to check the calendar to get available time slots.
Memory/ State
This is the short or long term information retained by the agent to remember and correlate context across different calls. It can be ephemeral (only available in session) or persisted in DB for future invocations.
Guardrails
Guardrails ensure that inputs and outputs are bound within the rules mandated by organization governance bodies. Input guardrails ensure that the input to the agents are appropriate. They can also trigger if an input is off topic and out of context for the underlying agent. On the other hand, output guardrails will be triggered when the response generated by an agent is improper.
The way we want to go ahead with this blog is to first go over the installation project and then build a few projects. We will continue using our Ollama models as we are still in the process of learning, so SLMs are good enough to experiment. As shown in one of the previous blogs, it is very easy to switch between commercial models like Calude Opus or GPT-5.5 and open source models.
Setting up Environment
As we did for our previous projects, we will continue to use uv to manage our environment. Following commands should setup what we will need for this blog.
[~/openai] % uv init [~/openai] % uv add python-dotenv [~/openai] % uv add openai-agents
We will install python-dotenv to read .env files. There is no need to provide a valid API_KEY when we are using Ollama, but we will still keep a Key in .env file following best practices.
We can use Ollama libraries to create an agent, but since OpenAI endpoints are already exposed by this service, we will pass in the link for Ollama OpenAI endpoint.
Let’s create a .env file and add the following text in it.
OLLAMA_API_KEY=DUMMY_KEY_FOR_OLLAMA
With that in place, we are ready to start coding.
Examples
Basic Agent
We will start with an agent that is as simplistic as it can get. The only purpose this agent exists is so that we can generate Haikus.
Haiku poetry is a traditional Japanese form of short verse that typically conveys profound meaning. A haiku consists of three lines totaling seventeen syllables, with the line pattern following 5–7–5 syllables respectively. For example:
The sky is so blue. (5 syllables)
The sun is so warm up high. (7 syllables)
I love the sum¤mer. (5 syllables)
Let’s start with imports. These will be imported in almost all OpenAI agent programs we write.
import os from openai import AsyncOpenAI from dotenv import load_dotenv from agents import Agent, Runner, OpenAIChatCompletionsModel, set_tracing_disabled import asyncio
Next is defining the agent and creating a path to invoke it. We will add both in next block.
class BasicAgent:
"""
A basic agent for creating haikus.
"""
def __init__(self, model, ep, key):
set_tracing_disabled(disabled=True)
ai_client = AsyncOpenAI(base_url=ep, api_key=key)
ai_model = OpenAIChatCompletionsModel(model=model, openai_client=ai_client)
self.agent = Agent(name="Haiku Agent", instructions="You are a Great Master of Haiku who can create Haikus on any subject.", model=ai_model)
async def create_haiku(self, subject: str) -> str:
prompt = f"Create a Haiku about the following subject:\n\n{subject}. Also, explain the meaning of this Haiku."
response = await Runner.run(self.agent, prompt)
print(f"Haiku:\n{response.final_output}")
return response
We start by disabling logging on line 6. By default, OpenAI will log everything that can be accessed online on OpenAI website. In a later blog, we will show multiple ways of logging and also create our own logger.
To create the agent, we start by creating an OpenAI client on line 9. This is followed by creating a model object with Ollama and our local SLM/ LLM model. Finally on line 10 we create the Agent that will be used further to generate our Haiku. We create the Agent with specific developer instruction that initializes the model as a Haiku Master.
Rest of the code is just to make a call to this model and create Haiku. Also, our Haiku master is asked to explain what the Haiku means so we don’t have to spend our time understanding what Haiku Master was thinking ????. Our Main method looks as follows.
if __name__ == "__main__":
load_dotenv()
OLLAMA_MODEL = "command-r7b:7b"
OLLAMA_EP = "http://localhost:11434/v1"
OLLAMA_KEY = os.getenv('OLLAMA_API_KEY')
agent = BasicAgent(OLLAMA_MODEL, OLLAMA_EP, OLLAMA_KEY)
subject = "a candle in the dark"
asyncio.run(agent.create_haiku(subject))
We create our agent and create a random subject. Finally we call our agent to generate Haiku.
Haiku:
**Haiku:**
A flame's glow,
Solitude's silent guide,
Darkness finds light.
**Explanation:**
This Haiku captures a solitary moment, where the light from a candle in total darkness provides guidance and comfort. The "flame’s glow" symbolizes resilience and hope amidst the unknown or difficult moments we might face. It reminds us that even small sources of light can make a significant difference in navigating through our personal 'darkness,' offering direction and solace.
Guardrails
Now let’s build a guardrail application. Guardrails are used to ensure that input or output to/ from agents are proper and conform to certain rules. For example, an agent may be created to only respond to help topics related to astronomy. You can add an input guardrail to block any questions that does not pertain to this rule.
We will use a Python validation framework viz. pydantic for some of the agent models. You can read a short introduction for pydantic that I am planning to add to this site.
Let’s start our implementation for a guardrail. First let’s see the additional libraries that we are including here,
from agents import Agent, Runner, OpenAIChatCompletionsModel, InputGuardrail, GuardrailFunctionOutput, InputGuardrailTripwireTriggered from pydantic import BaseModel
This guardrail will make sure that all inputs are kid friendly. We will object to anything that is adult or erotic materials. If the guardrail does not trigger, we can continue with workflow. In this case we have a dummy implementation.
First, let’s define an output format that can be used by the LLM.
class GuardrailResponse(BaseModel):
is_obscene: bool
reason: str
This will be populated by the guardrail. is_obscene will be populated as True if the text is not appropriate.
def __init__(self, model, ep, key):
llm_client = AsyncOpenAI(base_url=ep, api_key=key)
llm_model = OpenAIChatCompletionsModel(openai_client=llm_client, model=model)
self.guardrail_agent = Agent(
name="Input Guardrail Agent",
instructions=("You are an input guardrail agent. From this point forward, I want you to ensure that all interactions are appropriate for children and do not include any "
"adult material, sexual content, or erotica. Please focus on providing answers that are educational, fun, and suitable for all ages. If a query contains "
"words or themes related to adult content, respond with a reminder that the discussion needs to remain kid-friendly. Aim to redirect the conversation to "
"appropriate topics like hobbies, games, science, or any other family-friendly subjects. "
"Respond with is_obscene as True if it is not family friendly, False otherwise. Provide a reason for your decision."),
model=llm_model,
output_type=GuardrailResponse
)
self.summ_agent = Agent(
name="Summarizer Agent",
instructions="You are a summarizer agent. You will respond in a concise manner summarizing the input text.",
model=llm_model,
input_guardrails=[
InputGuardrail(guardrail_function=self.fn_input_guardrail)
]
)
async def fn_input_guardrail(self, ctx, agent, text) -> GuardrailFunctionOutput:
# Run the input guardrail agent with the constructed prompt
response = await Runner.run(self.guardrail_agent, text, context=ctx.context)
return GuardrailFunctionOutput(
output_info=response,
tripwire_triggered=response.final_output.is_obscene
)
async def summarize(self, text: str) -> str:
prompt = f"Summarize the following text:\n\n{text}. Also extract keywords from the text. Provide the summary first followed by the keywords."
# Run the orchestration agent with the constructed prompt
try:
response = await Runner.run(self.summ_agent, prompt)
print(f"{response.final_output}")
return response
except InputGuardrailTripwireTriggered as e:
print(f"Guardrail Triggered: {e}")
return 'Guardrail triggered. Unable to summarize the input text.'
Here we create two Agents, one of them is the guardrail and the other is summarizer agent. We setup a InputGuardrail on line 21 for summarizer agent. This will be called every time the summarizer agent is called. If we call this with an inappropriate content, following will be returned and summary agent will be cancelled.
Guardrail Triggered: Guardrail InputGuardrail triggered tripwire Guardrail triggered. Unable to summarize the input text.
Tools Use
Next we will see how to use tools from OpenAI. OpenAI agents SDK makes it very simple to create a function. A function can invoke other agents or perform any other pythonic operations. Examples can be making service or DB calls. For this example, we will create a simple function that returns personal traits based on Chinese zodiac years. Of course, these calculations are approximate and used only for demonstration.
@staticmethod
@function_tool
def get_chinese_personality_traits(birth_year: int):
print("Fetching Chinese Zodiac personality traits for year:", birth_year)
elements = ['Wood', 'Fire', 'Earth', 'Metal', 'Water']
element = elements[(birth_year - 4) % 10 // 2]
traits = [
"猴: Monkey - Sharp, smart, curiosity",
"鸡: Rooster - Observant, hardworking, courageous",
"狗: Dog - Lovely, honest, prudent",
"猪: Pig - Compassionate, generous, diligent",
"鼠: Rat - Quick-witted, resourceful, versatile, kind",
"牛: Ox - Diligent, dependable, strong, determined",
"虎: Tiger - Brave, confident, competitive",
"兔: Rabbit - Quiet, elegant, kind, responsible",
"龙: Dragon - Confident, intelligent, enthusiastic",
"蛇: Snake - Enigmatic, intelligent, wise",
"马: Horse - Animated, active, energetic",
"羊: Goat - Calm, gentle, sympathetic"
]
trait = traits[birth_year % 12]
response = {
"year": birth_year,
"traits": trait,
"element": element,
"description": f"People born in the year {birth_year} are associated with the {element} element and have the following personality traits: {trait}."
}
print("Generated response:", response)
return response
@functiuon_tool on line 2 is the only annotation needed to declare a python method as an external function. In this function, we are just returning personal traits based on year sent.
The way to use this tool is to add this to the model as given below.
def __init__(self, model, ep, key):
set_tracing_disabled(True)
agent = AsyncOpenAI(base_url=ep, api_key=key)
agent_model = OpenAIChatCompletionsModel(model=model, openai_client=agent)
self.zodiac_agent = Agent(
name="Zodiac Agent",
instructions="You are a helpful assistant who uses get_chinese_personality_traits tool to provide "\
"personality traits. Be concise and informative in your responses.",
model=agent_model,
tools=[ToolUsage.get_chinese_personality_traits]
)
Tool outputs the following for the year 1985.
{
"year": 1985,
"traits": "牛: Ox - Diligent, dependable, strong, determined",
"element": "Wood",
"description": "People born in the year 1985 are associated with the Wood element and have the following personality traits: 牛: Ox - Diligent, dependable, strong, determined."
}
Agent responded with the following,
Based on the Chinese zodiac, someone born in the year 1985 is a Wood Ox. Their personality traits include being diligent, dependable, strong, and determined. This combination of elements suggests that they are hardworking and reliable individuals who value stability and security. They are also known to be very responsible and have strong convictions.
Passing Context
Imagine that you want to display information based on the logged in user. Since, the user is already logged in, you have information about the user that can be utilized by the agent. In this case we send the user information in what is called as context for the agent. Let’s try to build a trip recommendation system for users. Assume that we already know about the favorite food and sports for the user.
We start by creating couple of data classes for storing user information. Of course for this program we will hardcode one of the data classes. We start with the additional imports.
from agents import Agent, RunContextWrapper, OpenAIChatCompletionsModel, Runner, function_tool, set_tracing_disabled
from dataclasses import dataclass
## Other imports ##
@dataclass
class UserPreference:
"""
A dataclass to represent user preferences.
"""
user_id: int
name: str
favorite_sports: str
favorite_food: str
@dataclass
class UserContext:
"""
A dataclass to represent the user's context.
"""
user_id: int
We will create a dummy class to store user preferences.
class DummyUserPreferenceManager:
"""
A dummy manager to simulate fetching user preferences.
"""
def __init__(self):
self.user_profs = [
UserPreference(user_id=1, name="Alice", favorite_sports="Tennis", favorite_food="Pizza"),
UserPreference(user_id=2, name="Bob", favorite_sports="Football", favorite_food="Sushi"),
UserPreference(user_id=3, name="Charlie", favorite_sports="Basketball", favorite_food="Burgers"),
UserPreference(user_id=4, name="Diana", favorite_sports="Swimming", favorite_food="Salad"),
UserPreference(user_id=5, name="Eve", favorite_sports="Rugby", favorite_food="Pasta")
]
def get_user_preference(self, user_id: int) -> str:
up = next((prof for prof in self.user_profs if prof.user_id == user_id), None)
if up:
print(f"Fetched user preferences for user_id {user_id}: {up}")
return f"User {up.name} prefers {up.favorite_sports} in sports, and in food prefers {up.favorite_food}."
else:
return "User not found."
This is just a dummy class containing some random imaginary users. Each users are also given a favorite food and sport. We also created a method to return user preferences.
Let’s create a tool (as before, we use @function_tool). This will return a user context object.
# Class Name: ContextManager @function_tool @staticmethod def get_user_preferences(wrapper:RunContextWrapper[UserContext]) -> str: dummy_manager = DummyUserPreferenceManager() return dummy_manager.get_user_preference(wrapper.context.user_id)
We create the agent and let it know to use the UserContext and provide a way of getting this value. See code below.
def __init__(self, model, ep, key):
set_tracing_disabled(True)
agent = AsyncOpenAI(base_url=ep, api_key=key)
agent_model = OpenAIChatCompletionsModel(model=model, openai_client=agent)
self.user_preference_agent = Agent[UserContext](
name="Travel Manager Agent",
instructions="You are a helpful assistant who provides tailored travel preferences for the user.",
model=agent_model,
tools=[ContextManager.get_user_preferences]
)
Finally, invoking involves passing the User Context. We will send the user ID and agent will fetch user preferences using the tool we have defined earlier. The code below shows how to send a user context to the agent.
async def run(self, user_id: int, location: str):
user_info = UserContext(user_id=user_id)
result = await Runner.run(
starting_agent=self.user_preference_agent,
input=f"Based on the user's food and sports preferences, can you provide a travel recommendation in {location}?",
context=user_info
)
print(result.final_output)
That’s it. Now if we send user_id = 3 (as an example), and send the city to vosot as Portugal, AI will recommend based on Charlies favorite sport (basketball) and food (Burgers). A sample run may look like this.
Based on your preferences, I would recommend visiting the city of Lisbon, Portugal! Lisbon has a lively atmosphere, rich history, and exciting activities that cater to both basketball enthusiasts and burger lovers. For Basketball fans: - Watch a game at Pavilhão do Restelo, the home arena for BC Galitos, one of Portugal's top basketball teams. - Explore the nearby Restelo neighborhood, where you can catch a glimpse of the historic buildings and scenic views. For Burger enthusiasts: - Head to "Os Burgueses" in Lisbon's trendy LX Factory area. This casual eatery serves mouth-watering burgers made with local ingredients. - Visit "Burger Shop", another popular burger spot with a fun atmosphere and unique toppings for adventurous eaters. Additional recommendations: * Stroll through the picturesque Chiado neighborhood, known for its shopping, cafes, and historic architecture. * Explore Lisbon's famous tram system (Elevador Santa Justa) for breathtaking views of the city. * Visit the nearby Parque das Nações, a waterfront park with shops, restaurants, and a casino. Lisbon offers an unforgettable experience in Portugal. Its pleasant climate, stunning scenery, and exciting activities create the perfect blend of relaxation and adventure! What do you think, Charlie?
Maintaining Sessions
Everything that we have done till now is a single question and answer. The agent does not keep any history of conversations, so, we cannot continue on a conversation. But agent can remember. Let’s see how we can maintain history using sessions.
We will start by creating an agent and see what happens when sessions are not present.
class SessionAgent:
def __init__(self, model, ep, key):
set_tracing_disabled(disabled=True)
ai_client = AsyncOpenAI(base_url=ep, api_key=key)
ai_model = OpenAIChatCompletionsModel(model=model, openai_client=ai_client)
self.chat_agent = Agent(
name="Session Agent",
instructions="You are a helpful assistant that can answer general knowledge questions. Be concise.",
model=ai_model
)
#self.session_manager = SessionManager()
async def ask_question(self, question: str, session_name: str = ''):
"""
Asks a question to the agent within a specific session. The session maintains the chat history,
allowing the agent to provide context-aware responses. The response is returned.
"""
if (session_name == ''):
response = await Runner.run(self.chat_agent, question)
else:
session = self.session_manager.get_session(session_name)
response = await Runner.run(self.chat_agent, question, session=session)
return response
async def converse_no_session(self):
"""
Simulates a conversation with the agent, simulating single user asking question without sessions.
"""
question = ''
while True:
question = input("\n\nUSER>>> ")
if question.lower() != 'bye':
response = await self.ask_question(question)
print(f"Response: {response.final_output}")
else:
breakLet’s see a sample run for this.
Starting conversation without session... USER>>> What's my name? Response: I don't have any information about your identity or name. I'm a conversational AI, and our interaction just started. Would you like to share your name with me? USER>>> My name is Suvendra Response: Hello, Suvendra! It's nice to meet you. Is there something I can help you with or would you like some conversation? USER>>> What's my name? Response: I don't have any information about your personal identification, including your name. I'm a large language model, I don't store or keep track of individual user data. Can we start fresh and talk about something else? USER>>> bye
Since we are not maintaining any history, agent did not remember my name. Let’s see how we can fix this.
We will create a class to manage sessions.
class SessionManager:
def __init__(self):
self.sessions = {}
def get_session(self, session_name: str) -> SQLiteSession:
"""
Creates a new session for maintaining chat history. The session is stored in a SQLite database with
the given session name.
"""
if session_name not in self.sessions:
self.sessions[session_name] = SQLiteSession(session_id=session_name)
return self.sessions[session_name]
def clear_sessions(self):
"""
Clears all existing sessions from the session manager.
"""
self.sessions.clear()
def delete_session(self, session_name: str):
"""
Deletes a specific session from the session manager based on the provided session name.
"""
if session_name in self.sessions:
del self.sessions[session_name]OpenAI can create in memory SQLite sessions to manage sessions. We can also persist these on disk. In the code above, we are creating a session at Line #11. We can now initialize and use this class in our main agent test class. If you go to SessionAgent class above, one line was commented out. Let’s uncomment that line and add a method that can use this session.
async def converse_session(self):
question = ''
while True:
question = input("\n\nUSER>>> ")
if question.lower() != 'bye':
response = await self.ask_question(question, 'default')
print(f"Response: {response.final_output}")
else:
break
self.session_manager.clear_sessions()Here we are sending a session (called ‘default’) that will manage session for the user. Let’s see if the agent can remember my name now.
Starting conversation with session... USER>>> what's my name? Response: I don't have information about your personal identity, as I'm just an AI assistant and our conversation has just started. Could you please tell me what your name is? USER>>> my name is Suvendra Response: It was nice chatting with you, Suvendra! Is there something specific you'd like to talk about or ask for help on? I'm all ears (or in this case, all text). USER>>> what's my name? Response: We've had this conversation before, Suvendra. Your name is still... (and only you can confirm) ...not mentioned by me again because I don't recall our prior conversations about your identity. Let's start fresh! What would you like to talk about or ask for help on today? USER>>> bye
Nice, it is great to be remembered. But then is this only limited to one session? No, we can manage multiple users in multiple sessions. Let’s try it now.
async def converse_multiple_sessions(self, users: list[str]):
question = ''
user = ''
user_csv = ','.join(users)
while True:
user = input(f"\n\nUSER ({user_csv})>>> ")
if user.lower() == 'bye':
break
elif user.lower() not in users:
print(f"Invalid user. Please choose from: {user_csv}")
continue
question = input(f"\nQUESTION ({user})>>> ")
if question.lower() != 'bye':
response = await self.ask_question(question, session_name=user.lower())
print(f"Response: {response.final_output}")
else:
self.session_manager.delete_session(user)
print(f"Session for {user} deleted.")
if all(user not in self.session_manager.sessions for user in users):
break
self.session_manager.clear_sessions()Let’s see if this agent can remember all the identities.
QUESTION (alice)>>> what's my name? Response: I don't have any information about your identity, so I won't be able to tell you what your name is. Can I help with something else? USER (alice,bob,charlie)>>> alice QUESTION (alice)>>> My name is Alice. Remember that. Response: I'll remember that Alice! However, please know that our conversation just started, and our conversation will only last as long as this text-based session. If we were to continue the conversation at a later time, I might not retain that information forever, but for now, I've got it noted: "Hello, my name is Alice" USER (alice,bob,charlie)>>> bob QUESTION (bob)>>> what's my name? Response: I don't have information about your personal details, including your name. I'm here to help with general knowledge and provide assistance, but I need more context or data to assist you further. Can I ask how we met or what brought you here today? USER (alice,bob,charlie)>>> charlie QUESTION (charlie)>>> My name is Charlie Response: Nice to meet you, Charlie! Is there something I can help you with or would you like to chat? USER (alice,bob,charlie)>>> bon Invalid user. Please choose from: alice,bob,charlie USER (alice,bob,charlie)>>> bob QUESTION (bob)>>> My name is Bob. But you can call me Bo Response: Nice to meet you, Bo! However, I don't have any prior knowledge about your identity as "Bob" (or "Bob", which sounds like short for Robert), since our conversation just started from scratch. We're fresh and clean slate here! What's new with you today, Bob/Bo? USER (alice,bob,charlie)>>> charlie QUESTION (charlie)>>> what's my name? Response: Charlie is your name! USER (alice,bob,charlie)>>> bob QUESTION (bob)>>> what's my name? Response: Déjà vu, Bo! Since I don't have any prior knowledge or details about your personal identity, including your name, the answer remains... I still don't know what your name is. And that's okay! You told me it's Bob/Bo once we got to chatting. USER (alice,bob,charlie)>>> bye
This showed how agent sessions can be used to maintain multiple users in session
Conclusion
This blog is already pretty long. However, there is a lot remaining still for OpenAI Agents. I decided to stop here and maybe write additional blogs to continue on this topic. Expect to see blogs on OpenAI agents soon. Hope this helped. Ciao for now!