How to use the OpenAI Swarm Agentic Framework

(At the time of writing this article, the OpenAI Swarm Agentic Framework is in the experimental stage. The API I show in the examples may change in the future, or the project may even be discontinued.)

Table of Contents

  1. The building blocks of the Swarm Framework
  2. Building a database retrieval workflow with Swarm Framework
    1. Data preparation
    2. Data retrieval function
    3. Data description
  3. Defining the agents
    1. Database querying agent
    2. The apology agent
    3. The plan agent
  4. Running the workflow
  5. Debugging the workflow
  6. Other ways to run a similar workflow

Regardless of the tool you choose, implementing agentic workflows is not straightforward. Agents tend to make mistakes, task handover is error-prone, debugging is challenging, and anything beyond the simplest use cases requires extensive work.

While Swarm Framework cannot solve all these problems, Swarm simplifies implementation and debugging.

The building blocks of the Swarm Framework

The two main elements of the framework are Agents and functions.

Agents are implemented as system messages for an LLM model, and the instructions we write for the agent will be added at the beginning of the conversation with AI. Additionally, we can define a set of functions the agent can call.

Functions are regular Python functions that the agents call. The functions can perform any task and return a text response, but a function can also return another agent. In such a case, the returned agent replaces the calling agent by swapping the system message.

Building a database retrieval workflow with Swarm Framework

Let’s build a database retrieval workflow where the user can ask any question about the data, and AI will generate a SQL query to retrieve the data. We want the workflow to either answer the question or explain to the user why we cannot help them.

The workflow will consist of three agents:

  1. The Plan agent decides whether the question can be answered with the database we have.
  2. The Query agent generates the SQL query, accesses the database, and returns the answer.
  3. The Apologize agent generates an apology if the question cannot be answered.

Data preparation

First, let’s prepare some data. We will use the Titanic dataset and split its content into three database tables:

import sqlite3
from sklearn.datasets import fetch_openml
import pandas as pd


DB_NAME = "local_database.db"

titanic_dataset = fetch_openml("Titanic", version=1)

df = titanic_dataset.frame
df = df.reset_index(drop=False)
df = df.rename(columns={"index": "passengerId"})

passenger_df = df[["passengerId", "name", "sex", "sibsp", "parch"]]
ticket_df = df[["passengerId", "ticket", "pclass", "fare", "cabin", "embarked"]]
survived_df = df[["passengerId", "survived"]]

conn = sqlite3.connect(DB_NAME)

passenger_df.to_sql("passengers", conn, if_exists="replace", index=False)
ticket_df.to_sql("tickets", conn, if_exists="replace", index=False)
survived_df.to_sql("survived", conn, if_exists="replace", index=False)

conn.commit()
conn.close()

Data retrieval function

We need to query the database, so let’s write a function that executes an SQL query and returns the result as text. In our case, we will get the values formatted as a Markdown table:

def query_db(query):
 conn = sqlite3.connect(DB_NAME)
    try:
        return pd.read_sql_query(query, conn).to_markdown()
    finally:
 conn.close()

We can add a description to the function as a docstring if we want. The description will be used as a part of the prompt passed to the LLM model. But even if we don’t describe the function, the Swarm Framework will build a prompt using the function’s name, the argument names and types, and their default values. So, it’s often sufficient to write a descriptive function signature, and we don’t need to add a docstring.

Data description

Nothing will work if we don’t describe the tables and columns we want to use. Because we use the Titanic dataset, which is well known, we don’t need a detailed description. When you describe your own data, you should be more specific.

data_description = """
The database contains the Titanic dataset split into three tables:

1. Table: passengers:
 This table contains details about passengers, including their ID, name, sex, number of siblings/spouses aboard, and number of parents/children aboard.
 Columns: passengerId, name, sex, sibsp, parch.

2. Table: tickets:
 This table provides ticket details, including ticket number, passenger ID, class of travel, fare amount, cabin number, and port of embarkation.
 Columns: passengerId, ticket, pclass, fare, cabin, embarked.

3. Table: survived:
 This table indicates whether the passenger survived (1) or not (0), along with the passenger ID.
 Columns: passengerId, survived.
"""

Defining the agents

Before we start, we have to instantiate the Swarm framework and configure the OpenAI API key.

import os
from swarm import Swarm, Agent


os.environ["OPENAI_API_KEY"] = "sk-proj-..."

client = Swarm()

We can finally define the agents. An agent consists of a name and the system message (passed as the “instructions” argument). Additionally, we can pass a set of functions the agent can call.

Database querying agent

Our database querying agent is responsible for creating the SQL query and generating the response after receiving the data from the database. The instructions refer to the chat history and mention a plan. We do it because the agent deciding if we can answer the question prepares the plan, so the agent defined below will already have access to the plan.

We also need a pass_to_query function to return the query agent. The function will be one of the tools available for the agent who decides whether we can answer the question.

query = Agent(
    name="Query",
    instructions=f"""You are a database reading bot that can answer users' questions using information from a database. \n

    {data_description} \n\n

 In the previous step, a plan has been prepared. Use the plan to create a SQL query and call the database.""",
    functions=[query_db]
)

def pass_to_query():
    "Hands the task over to the Query agent"
    return query

The apology agent

We want the user to receive an apology and explain why we cannot answer the question. I decided to use a separate agent for this task because it allows me to keep the instructions short and focused on a single task.

apologize = Agent(
    name="Apologize",
    instructions="Apologize and explain to the user why you cannot complete the task and retrieve the data."
)

def pass_to_apologize():
    "Hands the task over to the Apologize agent"
    return apologize

The plan agent

In the Plan agent, we must decide whether we can answer the question and delegate the task to the right agent. I want the agent to have the space to think and reason about the question, so I expect to get a response in a specific format. The format forces the agent to reason through the question and then decide what to do.

I explain that reasoning format in the agent prompt by providing a few examples.

The plan agent has access to two functions because the agent may delegate the task to either the query agent or the apology agent.

plan = Agent(
   instructions=f"""You are a database reading bot that can answer users' questions using information from a database. \n

    {data_description} \n\n

 Given the user's question, decide whether the question can be answered using the information in the database. \n\n

 Return a JSON with two keys, 'reasoning' and 'can_answer', and no preamble or explanation.
 Return one of the following JSON:

    
    {{"reasoning": "I can find the average revenue of customers with tenure over 24 months by averaging the Total Revenue column in the Billing table filtered by Tenure in Months > 24", "can_answer":true}}
    {{"reasoning": "I can find customers who signed up during the last 12 month using the Tenure in Months column in the Billing table", "can_answer":true}}
    {{"reasoning": "I can't answer how many customers churned last year because the Churn table doesn't contain a year", "can_answer":false}}
    

 If the question can be answered, hand it over to the Query agent.
 If it's not possible, pass the task to the Apologize agent.

 You must always pass the task to another agent.""",
   functions=[pass_to_query, pass_to_apologize]
)

Running the workflow

Finally, we can run the swarm of agents. The run function requires the message from the user and the first agent to be called. In the response, we get the entire chat history. Therefore, we read the last message to get the final answer.

def ask_ai(question):
 answers = client.run(
        agent=plan,
        messages=[{"role":"user", "content": question}],
        debug=True
 )
    return answers.messages[-1]["content"]

ask_ai("How many passengers survived?")

# A total of 500 passengers survived.

Is the answer correct? Well, for this version of the Titanic dataset, it is. I don’t know, though, if the dataset contains valid values.

Debugging the workflow

If the debug argument is set to True, we will see log messages in the console. The messages tell us what the agent’s input was, what response it produced, and what function it was called. If the tool accepts parameters (as query_db does), we will see the parameters passed to the function (but not the function’s output).

If we don’t want to run the debug mode, the collection of messages returned by the run function may be sufficient to track the workflow execution. The returned value contains the messages passed to the last agent in the chain.

Other ways to run a similar workflow

In the past, I wrote two articles about the data retrieval workflow with AI decisions. The first one uses Langchain and it’s LangGraph module. The other one was implemented with the Marvin library.

Marvin allows us to write the simplest code but lacks debugging capabilities. LangGraph is verbose and complicated, but let’s use the Langsmith monitoring tool so we know exactly what happens in the workflow. Swarm Framework is somewhere in the middle. It’s simple to use, yet we still have a way to see the internal API calls.

Also, Marvin and LangGraph aren’t agentic frameworks. When you use them, you don’t give AI full control over the workflow.


Do you need help building AI-powered applications for your business?
You can hire me!

Older post

How to setup student Jupyter Notebook servers for a workshop using CoCalc

Learn how to set up Jupyter Notebook servers with GPU access for students using CoCalc. A guide for educators and workshop organizers looking to provide hardware for their students without (too much) hassle.

Newer post

Prompt Management and Request Tracking for LLM Applications Using Langfuse

Learn how to use Langfuse to manage prompts and track LLM requests in your AI applications. Discover how to version prompts, monitor usage, and improve your LLM applications with detailed analytics.

Are you looking for an experienced AI consultant? Do you need assistance with your RAG or Agentic Workflow?
Schedule a call, send me a message on LinkedIn. Schedule a call or send me a message on LinkedIn

>