of abstraction constructed on prime of essentially easy concepts, some agent framework devs appear to consider complexity is a advantage.
I are likely to associate with Einstein’s maxim, “Every thing must be made so simple as doable, however not easier”. So, let me present you a framework that’s straightforward to make use of and simple to grasp.
OpenAI takes a refreshingly completely different method to different framework builders : they don’t attempt to be intelligent, they attempt to be clear.
On this article, I’ll present how one can construct multi-agent apps utilizing OpenAI’s open-source SDK.
We’ll see assemble a easy single-agent app after which go on to discover multi-agent configurations. We’ll cowl tool-calling, linear and hierarchical configurations, handoffs from one agent to a different and utilizing brokers as instruments.
Particularly, we are going to see the next examples:
- A easy name to an agent
- A tool-using agent
- Handoffs from one agent to a different
- Handoffs to a number of brokers
- Utilizing brokers as instruments
- Hierarchical agent orchestration utilizing brokers as instruments
The agent SDK
The agent SDK relies on a handful of ideas important to agentic and multi-agent programs and builds a framework round them — it replaces Swarm, an academic framework developed by OpenAI, the place these ideas had been recognized and applied. The Agent SDK builds upon and expands Swarm whereas sustaining its founding ideas of being light-weight and easy.
Easy it might be, however you may assemble subtle agent-based programs with this framework the place brokers use instruments (which might be different brokers), hand off to different brokers, and might be orchestrated in any variety of intelligent methods.
Set up is by way of pip, or your most popular package deal administration device, and the package deal known as openai-agents
. I favour UV, so to begin a brand new venture, I’d do one thing like the next.
uv init agentTest
cd agentTest
uv add openai-agents
A easy name to an agent
A easy agent name is proven within the diagram beneath.
It is a knowledge movement diagram that exhibits the working agent as a course of with knowledge flowing out and in. The movement that begins the method is the person immediate; the agent makes a number of calls to the LLM and receives responses. When it has accomplished its activity, it outputs the agent response.
Beneath we see the code for a primary program that makes use of the SDK to implement this movement. It instantiates an agent, provides it a reputation and a few directions; it then runs it and prints the end result. It’s much like the primary instance from OpenAI’s documentation, however right here we are going to create a Streamlit app.
First, we import the libraries.
import streamlit as st
import asyncio from brokers
import Agent, Runner
We want the Streamlit package deal, after all, and asyncio
as a result of we are going to use its performance to attend for the agent to finish earlier than continuing. Subsequent, we import the minimal from the brokers package deal, Agent
(to create an agent) and Runner
(to run the agent).
Beneath, we outline the code to create and run the agent.
agent = Agent(title="Assistant", directions="You're a useful assistant")
async def run_agent(input_string):
end result = await Runner.run(agent, input_string)
return end result.final_output
This code makes use of the default mannequin from OpenAI (and assumes you might have a sound API key set as an surroundings variable – and you’ll, after all, be charged , however for our functions right here, it gained’t be a lot. I’ve solely spent a number of tens of cents on this).
First, we instantiate an agent referred to as “Assistant” with some easy directions, then we outline an asynchronous operate that may run it with a string (the question) offered by the person.
The run
operate is asynchronous; we have to anticipate the LLM to finish earlier than we proceed, and so we are going to run the operate utilizing asyncio
.
We outline the person interface with Streamlit capabilities.
st.title("Easy Agent SDK Question")
user_input = st.text_input("Enter a question and press 'Ship':")
st.write("Response:")
response_container = st.container(peak=300, border=True)
if st.button("Ship"):
response = asyncio.run(run_agent(user_input))
with response_container:
st.markdown(response)
That is principally self-explanatory. The person is prompted to enter a question and press the ‘Ship’ button. When the button is pressed run_agent
is run by way of a name to asyncio.run
. The result’s displayed in a scrollable container. Beneath is a screenshot of a pattern run.

Your end result might differ (LLMs are famend for not giving the identical reply twice).
To outline an agent, give it a reputation and a few directions. Working can also be simple; cross within the agent and a question. Working the agent begins a loop that completes when a closing reply is reached. This instance is straightforward and doesn’t must run by means of the loop greater than as soon as, however an agent that calls instruments would possibly must undergo a number of iterations earlier than a solution is finalised.
The result’s simply displayed. As we are able to see, it’s the final_output
attribute of the worth that’s returned from the Runner
.
This program makes use of default values for a number of parameters that could possibly be set manually, such because the mannequin title and the temperature setting for the LLM. The Agent SDK additionally makes use of the Responses API by default. That’s an OpenAI-only API (thus far, at the very least), so if you should use the SDK with one other LLM, you need to swap to the extra broadly supported Chat Completions API.
from brokers import set_default_openai_api
set_default_openai_api("chat_completions")
Initially, and for simplicity, we’ll use the default Response API.
A tool-using agent
Brokers can use instruments, and the agent, together with the LLM, decides which instruments, if any, it wants to make use of.
Here’s a knowledge movement diagram that exhibits a tool-using agent.

It’s much like the easy agent, however we are able to see an extra course of, the device, that the agent utilises. When the agent makes a name to the LLM, the response will point out whether or not or not a device must be used. If it does, then the agent will make that decision and submit the end result again to the LLM. Once more, the response from the LLM will point out whether or not one other device name is important. The agent will proceed this loop till the LLM now not requires the enter from a device. At this level, the agent can reply to the person.
Beneath is the code for a single agent utilizing a single device.
This system consists of 4 components:
- The imports from the Brokers library and
wikipedia
(which shall be used as a device). - The definition of a device — that is merely a operate with the
@function_tool
decorator. - The definition of the agent that makes use of the device.
- Working the agent and printing the lead to a Streamlit app, as earlier than.
import streamlit as st
import asyncio
from brokers import Agent, Runner, function_tool
import wikipedia
@function_tool
def wikipedia_lookup(q: str) -> str:
"""Search for a question in Wikipedia and return the end result"""
return wikipedia.web page(q).abstract
research_agent = Agent(
title="Analysis agent",
directions="""You analysis matters utilizing Wikipedia and report on
the outcomes. """,
mannequin="o4-mini",
instruments=[wikipedia_lookup],
)
async def run_agent(input_string):
end result = await Runner.run(research_agent, input_string)
return end result.final_output
# Streamlit UI
st.title("Easy Device-using Agent")
st.write("This agent makes use of Wikipedia to search for data.")
user_input = st.text_input("Enter a question and press 'Ship':")
st.write("Response:")
response_container = st.container(peak=300, border=True)
if st.button("Ship"):
response = asyncio.run(run_agent(user_input))
with response_container:
st.markdown(response)
The device seems up a Wikipedia web page and returns a abstract by way of a typical name to a library operate. Be aware that we’ve used kind hints and a docstring to explain the operate so the agent can work out use it.
Subsequent is the definition of the agent, and right here we see that there are extra parameters than earlier: we specify the mannequin that we wish to use and a listing of instruments (there’s just one on this record).
Working and printing the result’s as earlier than, and it dutifully returns a solution (the peak of the Eiffel Tower).

That may be a easy check of the tool-using agent, which solely requires a single lookup. A extra complicated question might use a device greater than as soon as to gather the data.
For instance, I requested, “Discover the title of the well-known tower in Paris, discover its peak after which discover the date of beginning of its creator“. This required two device calls, one to get details about the Eiffel Tower and the second to search out when Gustav Eiffel was born.
This course of will not be mirrored within the closing output, however we are able to see the levels that the agent went by means of by viewing the uncooked messages within the agent’s end result. I printed end result.raw_messages
for the question above, and the result’s proven beneath.
[
0:"ModelResponse(output=[ResponseReasoningItem(id='rs_6849968a438081a2b2fda44aa5bc775e073e3026529570c1', summary=[], kind='reasoning', standing=None),
ResponseFunctionToolCall(arguments='{"q":"Eiffel Tower"}', call_id='call_w1iL6fHcVqbPFE1kAuCGPFok', title='wikipedia_lookup', kind='function_call', id='fc_6849968c0c4481a29a1b6c0ad80fba54073e3026529570c1', standing='accomplished')], utilization=Utilization(requests=1, input_tokens=111, output_tokens=214, total_tokens=325),
response_id='resp_68499689c60881a2af6411d137c13d82073e3026529570c1')"
1:"ModelResponse(output=[ResponseReasoningItem(id='rs_6849968e00ec81a280bf53dcd30842b1073e3026529570c1', summary=[], kind='reasoning', standing=None),
ResponseFunctionToolCall(arguments='{"q":"Gustave Eiffel"}', call_id='call_DfYTuEjjBMulsRNeCZaqvV8w', title='wikipedia_lookup', kind='function_call', id='fc_6849968e74ac81a298dc17d8be4012a7073e3026529570c1', standing='accomplished')], utilization=Utilization(requests=1, input_tokens=940, output_tokens=23, total_tokens=963),
response_id='resp_6849968d7c3081a2acd7b837cfee5672073e3026529570c1')"
2:"ModelResponse(output=[ResponseReasoningItem(id='rs_68499690e33c81a2b0bda68a99380840073e3026529570c1', summary=[], kind='reasoning', standing=None),
ResponseOutputMessage(id='msg_6849969221a081a28ede4c52ea34aa54073e3026529570c1', content material=[ResponseOutputText(annotations=[], textual content='The well-known tower in Paris is the Eiffel Tower. n• Top: 330 metres (1,083 ft) tall n• Creator: Alexandre Gustave Eiffel, born 15 December 1832', kind='output_text')], function='assistant', standing='accomplished', kind='message')], utilization=Utilization(requests=1, input_tokens=1190, output_tokens=178, total_tokens=1368),
response_id='resp_6849968ff15481a292939a6eed683216073e3026529570c1')"
]
You’ll be able to see that there are three responses: the primary two are the results of the 2 device calls, and the final is the ultimate output, which is generated from the data derived from the device calls.
We’ll see instruments once more shortly after we use brokers as instruments, however now we’re going to take into account how we are able to use a number of brokers that cooperate.
A number of brokers
Many agent purposes solely require a single agent, and these are already an extended step past easy chat completions that you just discover within the LLM chat interfaces, equivalent to ChatGPT. Agents run in loops and may use instruments, making even a single agent fairly highly effective. Nonetheless, a number of brokers working collectively can obtain much more complicated behaviours.
Consistent with its easy philosophy, OpenAI doesn’t try to include agent orchestration abstractions like another frameworks. However regardless of its easy design, it helps the development of each easy and complicated configurations.
First, we’ll have a look at handoffs the place one agent passes management to a different. After that, we’ll see how brokers might be mixed hierarchically.
Handoffs
When an agent decides that it has accomplished its activity and passes data to a different agent for additional work, that’s termed a handoff.
There are two elementary methods of attaining a handoff: with an agentic handoff, your complete message historical past is handed from one agent to a different. It’s a bit like while you name the financial institution however the individual you first converse to doesn’t know your specific circumstances, and so passes you on to somebody who does. The distinction is that, within the case of the AI agent, the brand new agent has a document of all that was stated to the earlier one.
The second methodology is a programmatic handoff. That is the place solely the required data offered by one agent is handed to a different (by way of typical programming strategies).
Let’s have a look at programmatic handoffs first.
Programmatic handoffs
Typically the brand new agent doesn’t must know your complete historical past of a transaction; maybe solely the ultimate result’s required. On this case, as an alternative of a full handoff, you may prepare a programmatic handoff the place solely the related knowledge is handed to the second agent.

The diagram exhibits a generic programmatic handoff between two brokers.
Beneath is an instance of this performance, the place one agent finds details about a subject and one other takes that data and writes an article that’s appropriate for youths.
To maintain issues easy, we gained’t use our Wikipedia device on this instance; as an alternative, we depend on the LLM’s data.
import streamlit as st
import asyncio
from brokers import Agent, Runner
writer_agent = Agent(
title="Author agent",
directions=f"""Re-write the article in order that it's appropriate for youths
aged round 8. Be enthusiastic concerning the subject -
every little thing is an journey!""",
mannequin="o4-mini",
)
researcher_agent = Agent(
title="Analysis agent",
directions=f"""You analysis matters and report on the outcomes.""",
mannequin="o4-mini",
)
async def run_agent(input_string):
end result = await Runner.run(researcher_agent, input_string)
result2 = await Runner.run(writer_agent, end result.final_output)
return result2
# Streamlit UI
st.title("Author Agent")
st.write("Write stuff for youths.")
user_input = st.text_input("Enter a question and press 'Ship':")
st.write("Response:")
response_container = st.container(peak=300, border=True)
if st.button("Ship"):
response = asyncio.run(run_agent(user_input))
with response_container:
st.markdown(response.final_output)
st.write(response)
st.json(response.raw_responses)
Within the code above, we outline two brokers: one researches a subject and the opposite produces textual content appropriate for youths.
This system doesn’t depend on any particular SDK capabilities; it merely runs one agent, will get the output in end result
and makes use of it because the enter for the following agent (output in result2
). It’s identical to utilizing the output of 1 operate because the enter for the following in typical programming. Certainly, that’s exactly what it’s.
Agentic handoffs
Nonetheless, typically an agent must know the historical past of what occurred beforehand. That’s the place the OpenAI Brokers Handoffs are available.
Beneath is the information movement diagram that represents the Agentic Handoff. You will notice that it is extremely much like the Programmatic Handoff; the distinction is the information being transferred to the second agent, and in addition, there’s a doable output from the primary agent when the handoff will not be required.

The code can also be much like the earlier instance. I’ve tweaked the directions barely, however the primary distinction is the handoffs
record in researcher_agent
. This isn’t dissimilar to the best way we declare instruments.
The Analysis Agent has been allowed at hand off to the Child’s Author Agent when it has accomplished its work. The impact of that is that the Child’s Author Agent not solely takes over management of the processing but in addition has data of what the Analysis Agent did, in addition to the unique immediate.
Nonetheless, there’s one other main distinction. It’s as much as the agent to find out whether or not the handoff takes place or not. Within the instance run beneath, I’ve instructed the agent to jot down one thing appropriate for youths, and so it fingers off to the Children’ Author Agent. If I had not instructed it to try this, it could have merely returned the unique textual content.
import streamlit as st
import asyncio
from brokers import Agent, Runner
kids_writer_agent = Agent(
title="Children Author Agent",
directions=f"""Re-write the article in order that it's appropriate for youths aged round 8.
Be enthusiastic concerning the subject - every little thing is an journey!""",
mannequin="o4-mini",
)
researcher_agent = Agent(
title="Analysis agent",
directions=f"""Reply the question and report the outcomes.""",
mannequin="o4-mini",
handoffs = [kids_writer_agent]
)
async def run_agent(input_string):
end result = await Runner.run(researcher_agent, input_string)
return end result
# Streamlit UI
st.title("Author Agent2")
st.write("Write stuff for youths.")
user_input = st.text_input("Enter a question and press 'Ship':")
st.write("Response:")
response_container = st.container(peak=300, border=True)
if st.button("Ship"):
response = asyncio.run(run_agent(user_input))
with response_container:
st.markdown(response.final_output)
st.write(response)
st.json(response.raw_responses)
It isn’t within the screenshot, however I’ve added code to output the response
and the raw_responses
with the intention to see the handoff in operation when you run the code your self.
Beneath is a screenshot of this agent.

An agent can have a listing of handoffs at its disposal, and it’ll intelligently select the proper agent (or none) at hand off to. You’ll be able to see how this may be helpful in a customer support state of affairs the place a troublesome buyer question may be escalated by means of a collection of extra professional brokers, every of whom wants to concentrate on the question historical past.
We’ll now have a look at how we are able to use handoffs that contain a number of brokers.
Handoffs to a number of brokers
We are going to now see a brand new model of the earlier program the place the Analysis Agent chooses at hand off to completely different brokers relying on the reader’s age.
The agent’s job is to provide textual content for 3 audiences: adults, youngsters and children. The Analysis Agent will collect data after which hand it off to one in all three different brokers. Right here is the information movement (notice that I’ve excluded the hyperlinks to an LLM for readability – every agent communicates with an LLM, however we are able to take into account that as an inner operate of the agent).

And right here is the code.
import streamlit as st
import asyncio
from brokers import Agent, Runner, handoff
adult_writer_agent = Agent(
title="Grownup Author Agent",
directions=f"""Write the article primarily based on the data provided that it's appropriate for adults taken with tradition.
""",
mannequin="o4-mini",
)
teen_writer_agent = Agent(
title="Teen Author Agent",
directions=f"""Write the article primarily based on the data provided that it's appropriate for youngsters who wish to have a cool time.
""",
mannequin="o4-mini",
)
kid_writer_agent = Agent(
title="Child Author Agent",
directions=f"""Write the article primarily based on the data provided that it's appropriate for youths of round 8 years previous.
Be enthusiastic!
""",
mannequin="o4-mini",
)
researcher_agent = Agent(
title="Analysis agent",
directions=f"""Discover data on the subject(s) given.""",
mannequin="o4-mini",
handoffs = [kid_writer_agent, teen_writer_agent, adult_writer_agent]
)
async def run_agent(input_string):
end result = await Runner.run(researcher_agent, input_string)
return end result
# Streamlit UI
st.title("Author Agent3")
st.write("Write stuff for adults, youngsters or youngsters.")
user_input = st.text_input("Enter a question and press 'Ship':")
st.write("Response:")
response_container = st.container(peak=300, border=True)
if st.button("Ship"):
response = asyncio.run(run_agent(user_input))
with response_container:
st.markdown(response.final_output)
st.write(response)
st.json(response.raw_responses)
This system’s construction is analogous, however now we now have a set of brokers at hand off to and a listing of them within the Analysis Agent. The directions within the varied brokers are self-explanatory, and this system will accurately reply to a immediate equivalent to “Write an essay about Paris, France for youths” or “…for youngsters” or “…for adults”. The Analysis Agent will accurately select the suitable Author Agent for the duty.
The screenshot beneath exhibits an instance of writing for youngsters.

The prompts offered on this instance are easy. Extra subtle prompts would doubtless yield a greater and extra constant end result, however the goal right here is to indicate the methods reasonably than to construct a intelligent app.
That’s one kind of collaboration; one other is to make use of different brokers as instruments. This isn’t too dissimilar to the programmatic handoff we noticed earlier.
Brokers as instruments
Working an agent is looking a operate in the identical means as calling a device. So why not use brokers as clever instruments?
As an alternative of giving management over to a brand new agent, we use it as a operate that we cross data to and get data again from.
Beneath is an information movement diagram that illustrates the thought. Not like a handoff, the primary agent doesn’t cross total management to a different agent; as an alternative, it intelligently chooses to name an agent as if it had been a device. The referred to as agent does its job after which passes management again to the calling agent. Once more, the information flows to an LLM have been omitted for readability.

Beneath is a screenshot of a modified model of the earlier program. We modified the character of the app slightly. The primary agent is now a journey agent; it expects the person to offer it a vacation spot and the age group for which it ought to write. The UI is modified in order that the age group is chosen by way of a radio button. The textual content enter subject must be a vacation spot.

Quite a few modifications have been made to the logic of the app. The UI modifications the best way the data is enter, and that is mirrored in the best way that the immediate is constructed – we use an f-string to include the 2 items of knowledge into the immediate.
Moreover, we now have an additional agent that codecs the textual content. The opposite brokers are comparable (however notice that the prompts have been refined), and we additionally use a structured output to make sure that the textual content that we output is exactly what we anticipate.
Basically, although, we see that the author brokers and the formatting agent are specified as instruments within the researcher agent.
import streamlit as st
import asyncio
from brokers import Agent, Runner, function_tool
from pydantic import BaseModel
class PRArticle(BaseModel):
article_text: str
commentary: str
adult_writer_agent = Agent(
title="Grownup Author Agent",
directions="""Write the article primarily based on the data provided that it's appropriate for adults taken with tradition.
Be mature.""",
mannequin="gpt-4o",
)
teen_writer_agent = Agent(
title="Teen Author Agent",
directions="""Write the article primarily based on the data provided that it's appropriate for youngsters who wish to have time.
Be cool!""",
mannequin="gpt-4o",
)
kid_writer_agent = Agent(
title="Child Author Agent",
directions="""Write the article primarily based on the data provided that it's appropriate for youths of round 8 years previous.
Be enthusiastic!""",
mannequin="gpt-4o",
)
format_agent = Agent(
title="Format Agent",
directions=f"""Edit the article so as to add a title and subtitles and make sure the textual content is formatted as Markdown. Return solely the textual content of article.""",
mannequin="gpt-4o",
)
researcher_agent = Agent(
title="Analysis agent",
directions="""You're a Journey Agent who will discover helpful data to your clients of all ages.
Discover data on the vacation spot(s) given.
When you might have a end result ship it to the suitable author agent to provide a brief PR textual content.
When you might have the end result ship it to the Format agent for closing processing.
""",
mannequin="gpt-4o",
instruments = [kid_writer_agent.as_tool(
tool_name="kids_article_writer",
tool_description="Write an essay for kids",),
teen_writer_agent.as_tool(
tool_name="teen_article_writer",
tool_description="Write an essay for teens",),
adult_writer_agent.as_tool(
tool_name="adult_article_writer",
tool_description="Write an essay for adults",),
format_agent.as_tool(
tool_name="format_article",
tool_description="Add titles and subtitles and format as Markdown",
),],
output_type = PRArticle
)
async def run_agent(input_string):
end result = await Runner.run(researcher_agent, input_string)
return end result
# Streamlit UI
st.title("Journey Agent")
st.write("The journey agent will write about locations for various audiences.")
vacation spot = st.text_input("Enter a vacation spot, choose the age group and press 'Ship':")
age_group = st.radio(
"What age group is the reader?",
["Adult", "Teenager", "Child"],
horizontal=True,
)
st.write("Response:")
response_container = st.container(peak=500, border=True)
if st.button("Ship"):
response = asyncio.run(run_agent(f"The vacation spot is {vacation spot} and reader the age group is {age_group}"))
with response_container:
st.markdown(response.final_output.article_text)
st.write(response)
st.json(response.raw_responses)
The instruments record is slightly completely different to the one we noticed earlier:
- The device title is the agent title plus
.agent_as_tool()
, a technique that makes the agent appropriate with different instruments . - The device wants a few parameters — a reputation and an outline.
One different addition, which could be very helpful, is using structured outputs, as talked about above. This separates the textual content that we wish from some other commentary that the LLM would possibly wish to insert. For those who run the code, you may see within the raw_responses
the extra data that the LLM generates.
Utilizing structured outputs helps to provide constant outcomes and solves an issue that may be a specific bugbear of mine.
I’ve requested the output to be run by means of a formatter agent that may construction the end result as Markdown. It is dependent upon the LLM, it is dependent upon the immediate, and who is aware of, possibly it is dependent upon the time of day or the climate, however every time I believe I’ve acquired it proper, an LLM will immediately insert Markdown fencing. So as an alternative of a clear:
## It is a header
That is some textual content
I as an alternative get:
Right here is your textual content formatted as Markdown:
''' Markdown
# It is a header
That is some textual content
'''
Infuriating!
Anyway, the reply appears to be to make use of structured outputs. For those who requested it to format the response because the textual content of what you need, plus a second subject referred to as ‘commentary’ or some such factor, it seems to do the fitting factor. Any extraneous stuff the LLM decides to spout goes within the second subject, and the unadulterated Markdown goes within the textual content subject.
OK, Shakespeare, it isn’t: adjusting the directions in order that they’re extra detailed would possibly give higher outcomes (the present prompts are quite simple). However it works properly sufficient as an instance the strategy.
Conclusion
That’s scratched the floor of OpenAI’s Brokers SDK. Thanks for studying, and I hope you discovered it helpful. We’ve got seen create brokers and mix them in several methods, and we took a really fast have a look at structured outputs.
The examples are, after all, easy, however I hope they illustrate the straightforward means that brokers might be orchestrated merely with out resorting to complicated abstractions and unwieldy frameworks.
The code right here makes use of the Response API as a result of that’s the default. Nonetheless, it ought to run the identical means with the Completions API, as properly. Which implies that you’re not restricted to ChatGPT and, with a little bit of jiggery-pokery, this SDK can be utilized with any LLM that helps the OpenAI Completions API.
There’s lots extra to search out out in OpenAI’s documentation.
- Pictures are by the creator except in any other case acknowledged.