Build a Multi-Agent System with OpenAI's Swarm Framework OpenAI's experimental Swarm framework enables developers to build a multi-agent customer support system that routes requests between specialist agents. The system uses a topic guardrail to filter irrelevant messages before a triage agent hands off billing or technical issues to dedicated agents. Swarm, which is not production-ready, demonstrates agent handoffs through function calls and runs locally with Python 3.10+. Build a Multi-Agent System with OpenAI's Swarm Framework Route customer support requests between specialist agents using OpenAI's experimental Swarm framework, with a manual topic guardrail in front of it. Mariana Souza https://sourcefeed.dev/u/mariana souza What you'll build A small customer support system: a triage agent reads an incoming message, decides whether it's a billing or technical issue, and hands off to the right specialist agent. A lightweight classifier agent acts as a topic guardrail, blocking anything unrelated to support before the triage agent even sees it. You'll run all of this locally with OpenAI's Swarm framework and confirm the handoff actually happened by checking which agent produced the final reply. Prerequisites - Python 3.10 or newer Swarm relies on typing features that don't work on older versions - An OpenAI API key with active billing platform.openai.com/api-keys - Basic comfort with Python functions and the OpenAI chat completions message format {"role": ..., "content": ...} pip , git , and a terminal A note on the framework: Swarm is OpenAI's own experimental, educational multi-agent framework openai/swarm on GitHub . It's explicitly not meant for production, there's no built-in tracing dashboard, no persistent sessions, no guardrail primitives. It's a thin, readable layer over the standard openai client that demonstrates one core idea well: an agent can "hand off" a conversation to another agent by returning that agent from a function call. That's exactly what we need here, and it's small enough that you can read the entire source in twenty minutes if you want to know what's happening under the hood. Important: Swarm is not published on PyPI. There's an unrelated package called swarm on PyPI a distributed job queue tool , and pip install swarm will install that instead and quietly break everything downstream. Install straight from GitHub. 1. Set up the project mkdir agent-handoffs && cd agent-handoffs python3 -m venv .venv source .venv/bin/activate Windows: .venv\Scripts\activate pip install git+https://github.com/openai/swarm.git Set your API key as an environment variable rather than hardcoding it: export OPENAI API KEY="sk-..." macOS/Linux setx OPENAI API KEY "sk-..." Windows new shell required after Swarm doesn't take the key directly, it builds an openai.OpenAI client internally, which reads OPENAI API KEY from the environment automatically. Create support system.py . Everything below goes in that one file, in order. 2. Build the specialist agents Start with two agents that each know how to do one job and nothing else. In Swarm, Agent is a simple object with a name, a model, an instructions string, and a list of callable functions the model can invoke as tools. python from swarm import Swarm, Agent client = Swarm billing agent = Agent name="Billing Agent", instructions= "You help customers with billing issues: invoices, charges, and " "subscription changes. Be concise and specific." , technical agent = Agent name="Technical Support Agent", instructions= "You help customers troubleshoot technical problems with the product. " "Ask clarifying questions if you need more detail." , 3. Build the topic guardrail Swarm has no built-in guardrail concept, so we build one the plain way: a cheap classifier agent that runs before triage and decides whether the message is worth routing at all. It replies with JSON, which we parse ourselves. python import json topic guardrail agent = Agent name="Topic Guardrail", model="gpt-4o-mini", instructions= "Decide whether the user's message is a customer support question " "about billing or technical issues. Jokes, homework, and general " "chit-chat are not support related. Reply with ONLY raw JSON, no " 'markdown fences, in this exact shape: {"is support related": true, ' '"reasoning": "short reason"}' , def check support topic user message: str - bool: response = client.run agent=topic guardrail agent, messages= {"role": "user", "content": user message} , raw = response.messages -1 "content" .strip raw = raw.removeprefix " json" .removeprefix " " .removesuffix " " .strip try: parsed = json.loads raw return bool parsed.get "is support related", False except json.JSONDecodeError: return False gpt-4o-mini is a reasonable default here: it's cheap and fast, and a yes/no classification doesn't need a frontier model. The .removeprefix / .removesuffix cleanup exists because small models occasionally wrap JSON in a markdown code fence even when told not to, don't skip it, it's the most common way this guardrail silently breaks. 4. Build the triage agent with handoffs In Swarm, a handoff is nothing special, it's just a Python function that returns an Agent instance instead of a string. When the model calls that function, Swarm swaps the active agent and keeps running the conversation against it. Define the specialists first already done above , then the transfer functions, then the triage agent that uses them. python def transfer to billing : """Call this when the customer's issue is about billing, invoices, or charges.""" return billing agent def transfer to technical : """Call this when the customer has a technical problem with the product.""" return technical agent triage agent = Agent name="Triage Agent", instructions= "Determine whether the user needs billing help or technical support, " "then call the matching transfer function. Don't try to solve the " "problem yourself." , functions= transfer to billing, transfer to technical , The docstring on each transfer function isn't decoration, Swarm sends it to the model as the function's description, same as any other tool. Vague docstrings are the number one reason triage picks the wrong specialist or doesn't hand off at all. 5. Run it python def handle message user message: str : if not check support topic user message : print "Blocked: this doesn't look like a support question." return response = client.run agent=triage agent, messages= {"role": "user", "content": user message} , print f"Handled by: {response.agent.name}" print response.messages -1 "content" if name == " main ": handle message "My last invoice charged me twice, can you fix it?" Run it: python support system.py Verify it works You should see something like: Handled by: Billing Agent I'm sorry about the duplicate charge. I can start a refund for the extra charge... response.agent.name confirms the handoff actually happened, triage routed to billing, not itself. Swap the call at the bottom to handle message "my app crashes when I upload a file" and confirm it routes to the Technical Support Agent instead. Then try handle message "write me a poem about the ocean" , it should print the guardrail block message and never touch the triage agent at all. If you want to see it handle a few cases in one run, loop over a list of test messages instead of calling handle message once, it's cheap since the guardrail model is small. Troubleshooting or import errors that don't match anything in this tutorial: you likely ran ModuleNotFoundError: No module named 'swarm' pip install swarm , which grabs an unrelated PyPI package. Uninstall it pip uninstall swarm and install from GitHub as shown above.: AuthenticationError: Incorrect API key provided OPENAI API KEY isn't set in the shell that's running the script. Confirm with echo $OPENAI API KEY macOS/Linux before running, env vars set in one terminal tab don't carry to another.: the guardrail model wrapped its answer in a code fence or added a stray sentence before the JSON. Tighten the instructions further "reply with nothing but the JSON object" or, if it keeps happening, print json.JSONDecodeError from check support topic raw before parsing so you can see exactly what came back. Triage agent answers the question directly instead of transferring : this usually means the transfer function docstrings are too vague or the triage instructions don't clearly forbid answering directly. Make both more explicit, and double check functions= ... on the triage Agent actually lists both transfer functions. Next steps Swarm is deliberately minimal, there's no persistent session across separate script runs, no built-in tracing, no output guardrails. If you outgrow it, the next stop is OpenAI's Assistants API via the standard openai package , which adds persistent threads, built-in file retrieval, and hosted tool execution at the cost of more moving parts to configure. For now, try adding a third specialist agent say, a "refunds" agent and a third transfer function, or add a second guardrail that checks the specialist's reply before it's printed, useful if you're worried about a specialist agent giving out information it shouldn't. The openai/swarm repo's examples/ directory has a few more handoff patterns worth reading, including one with shared context variables passed between agents. Mariana Souza https://sourcefeed.dev/u/mariana souza ยท Senior Editor Mariana covers the fast-moving world of machine learning and generative AI, with a particular focus on how these technologies are reshaping development workflows. When she isn't stress-testing the latest foundation models, she's usually at a local hackathon. Discussion 0 No comments yet Be the first to weigh in.