Agent Transfers in Swift with SwiftSwarm

James Rochabrun
4 min readOct 29, 2024

--

https://github.com/jamesrochabrun/SwiftSwarm

You may have heard about agents or agentic flows. A high-level explanation of agents is that they are AI entities designed to handle specific roles. Each agent has its own instructions (system messages) that define its behavior, along with tools (function calls) it can use to perform tasks. For example, an engineer agent might have instructions to handle technical discussions and tools to analyze code, while a designer agent might have instructions to focus on user experience and tools to evaluate design patterns.

OpenAI recently released Swarm, an educational framework for exploring lightweight multi-agent orchestration. You can use it to easily transfer conversations between agents. Their API looks like this…

In this post, we will use SwiftSwarm, a Swift library inspired by the Swarm framework to handle lightweight agent orchestration. I will show you how to transfer control between agents — specifically, we’ll see how an agent playing the role of an engineer can switch to a designer agent when a user asks a UX-related question.

Disclaimer:

Like OpenAI’s Swarm library, SwiftSwarm’s Agents are distinct from the Assistants in the Assistants API. While they share similar naming for convenience, they are completely separate implementations. SwiftSwarm operates entirely through the Chat Completions API and is therefore stateless between calls.

The following code samples won’t demonstrate conversation state management or chat UI implementation. Instead, we’ll focus on the core library functionality. Don’t worry though — you can find a complete sample project with state management examples in this examples folder!

Step 1: define your agents, you need to create an enum like this…

enum Team: String  {

case engineer
case designer
case product
}

Step 2: add AgentRepresentable conformance…

enum Team: String, AgentRepresentable  {

case engineer
case designer
case product

var agentDefinition: AgentDefinition {
switch self {
case .engineer:
.init(agent: Agent(
name: self.rawValue,
model: .gpt4o,
instructions: "You are a technical engineer, if user asks about you, you answer with your name \(self.rawValue)",
tools: [])) // <---- you can define specific tools for each agent if you want.
case .designer:
.init(agent: Agent(
name: self.rawValue,
model: .gpt4o,
instructions: "You are a UX/UI designer, if user asks about you, you answer with your name \(self.rawValue)",
tools: [])) // <---- you can define specific tools for each agent if you want.
case .product:
.init(agent: Agent(
name: self.rawValue,
model: .gpt4o,
instructions: "You are a product manager, if user asks about you, you answer with your name \(self.rawValue)",
tools: [])) // <---- you can define specific tools for each agent if you want.
}
}
}

Step 3: Now that we have defined our agents, we need to create an object that conforms to ToolResponseHandler. Here’s an example…

struct TeamDemoResponseHandler: ToolResponseHandler {

/// 1.
typealias AgentType = Team

/// 2.
func handleToolResponseContent(
parameters: [String: Any])
async throws -> String?
{
/// 3.
nil
}
}
  1. ToolResponseHandler is a protocol with an associated AgentRepresentable type, we will use the Team enum we defined previously, to satisfy this requirement.

2. The handleToolResponseContent function is called whenever a tool associated with your agent executes a function call. This is where you implement custom logic to process the tool’s response based on the parameters it receives.

Note: The ToolResponseHandler automatically manages agent switching using the transferToAgent function in a protocol extension.

3 . When a function call occurs, you can access parameter values using the keys defined in your tool’s parameter properties. For instructions on how to define a tool, please refer to the SwiftOpenAI function call documentation. In this example, we aren't returning any values since we haven't defined any tools for the three agents we created earlier.

Step 4: Now that you have defined your agents and tool handler, you’re ready to instantiate a Swarm object. The Swarm object manages your OpenAI API requests and invokes your defined tool response handler whenever a function call occurs.

SwiftSwarm relies on SwiftOpenAI to manage communication with OpenAI APIs. A Swarm object requires these dependencies

  • An instance of OpenAIService
  • An instance of ToolResponseHandler

The code will look like this:

import SwiftOpenAI
import SwiftSwarm

let apiKey = "MY_OPENAI_API_KEY"
let openAIService = OpenAIServiceFactory.service(apiKey: apiKey)

let toolResponseHandler = TeamDemoResponseHandler()
let swarm = Swarm(client: openAIService, toolResponseHandler: toolResponseHandler)

let message = ChatCompletionParameters.Message(role: .user, content: .text("I need design input"))
var currentAgent = Team.engineer.agent
let streamChunks = await swarm.runStream(agent: currentAgent, messages: [message])
for try await streamChunk in streamChunks {
content += streamChunk.content ?? ""
if let response = streamChunk.response {
currentAgent = response.agent <--- Transferred to designer here.
}
}
print("Switched to: \(currentAgent.name)")
print("Content: \(content)")

This will print:

Switched to: Designer
Content: As a UX/UI designer, I'm here to help you with your design needs. What specific aspect of design do you need input on? Whether it's user interface design, user experience strategy, or even color schemes and typography, feel free to let me know!

That’s all you need to create a chat system that seamlessly switches between agents in Swift. I hope you find this useful in your personal projects!

--

--

No responses yet