├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── SwiftSwarm │ ├── Agent.swift │ ├── Interfaces │ ├── AgentRepresentable.swift │ └── ToolResponseHandler.swift │ ├── Response.swift │ ├── StreamChunk.swift │ ├── Swarm.swift │ └── Utils │ ├── ContentType+String.swift │ └── String+Dictionary.swift ├── SwiftSwarmExample ├── SwiftSwarmExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── SwiftSwarmExample │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── SwiftSwarmExample.entitlements │ ├── SwiftSwarmExampleApp.swift │ ├── TeamDemo │ │ ├── Team.swift │ │ ├── TeamDemoResponseHandler.swift │ │ └── TeamDemoViewModel.swift │ └── UI │ │ ├── ApiKeyIntroView.swift │ │ ├── CellViewModel.swift │ │ ├── ChatScreen.swift │ │ └── OptionsListView.swift ├── SwiftSwarmExampleTests │ └── SwiftSwarmExampleTests.swift └── SwiftSwarmExampleUITests │ ├── SwiftSwarmExampleUITests.swift │ └── SwiftSwarmExampleUITestsLaunchTests.swift └── Tests └── SwiftSwarmTests └── SwiftSwarmTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 James Rochabrun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swiftopenai", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/jamesrochabrun/SwiftOpenAI", 7 | "state" : { 8 | "branch" : "main", 9 | "revision" : "e29332ea3e50d9fde6e358d0334226dc77674692" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftSwarm", 8 | platforms: [ 9 | .iOS(.v15), 10 | .macOS(.v13), 11 | .watchOS(.v9) 12 | ], 13 | products: [ 14 | // Products define the executables and libraries a package produces, making them visible to other packages. 15 | .library( 16 | name: "SwiftSwarm", 17 | targets: ["SwiftSwarm"]), 18 | ], 19 | dependencies: [ 20 | .package(url: "https://github.com/jamesrochabrun/SwiftOpenAI", branch: "main") 21 | ], 22 | targets: [ 23 | // Targets are the basic building blocks of a package, defining a module or a test suite. 24 | // Targets can depend on other targets in this package and products from dependencies. 25 | .target( 26 | name: "SwiftSwarm", 27 | dependencies: ["SwiftOpenAI"]), 28 | .testTarget( 29 | name: "SwiftSwarmTests", 30 | dependencies: ["SwiftSwarm"]), 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftSwarm 2 | 3 | Screenshot 2024-10-23 at 11 49 51 AM 4 | 5 | 6 | [![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/) 7 | [![swift-package-manager](https://img.shields.io/badge/package%20manager-compatible-brightgreen.svg?logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNjJweCIgaGVpZ2h0PSI0OXB4IiB2aWV3Qm94PSIwIDAgNjIgNDkiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDYzLjEgKDkyNDUyKSAtIGh0dHBzOi8vc2tldGNoLmNvbSAtLT4KICAgIDx0aXRsZT5Hcm91cDwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSJHcm91cCIgZmlsbC1ydWxlPSJub256ZXJvIj4KICAgICAgICAgICAgPHBvbHlnb24gaWQ9IlBhdGgiIGZpbGw9IiNEQkI1NTEiIHBvaW50cz0iNTEuMzEwMzQ0OCAwIDEwLjY4OTY1NTIgMCAwIDEzLjUxNzI0MTQgMCA0OSA2MiA0OSA2MiAxMy41MTcyNDE0Ij48L3BvbHlnb24+CiAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJQYXRoIiBmaWxsPSIjRjdFM0FGIiBwb2ludHM9IjI3IDI1IDMxIDI1IDM1IDI1IDM3IDI1IDM3IDE0IDI1IDE0IDI1IDI1Ij48L3BvbHlnb24+CiAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJQYXRoIiBmaWxsPSIjRUZDNzVFIiBwb2ludHM9IjEwLjY4OTY1NTIgMCAwIDE0IDYyIDE0IDUxLjMxMDM0NDggMCI+PC9wb2x5Z29uPgogICAgICAgICAgICA8cG9seWdvbiBpZD0iUmVjdGFuZ2xlIiBmaWxsPSIjRjdFM0FGIiBwb2ludHM9IjI3IDAgMzUgMCAzNyAxNCAyNSAxNCI+PC9wb2x5Z29uPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+)](https://github.com/apple/swift-package-manager) 8 | 9 | Swift framework exploring ergonomic, lightweight multi-agent orchestration. Highly inspired by OpenAI library [swarm](https://github.com/openai/swarm). 10 | 11 | The primary goal is just exploratory and a starting point for Swift engineers to explore Agents in their apps. 12 | SwiftSwarm is open source and accepts contributions PR's are more than welcome! 13 | 14 | ## Table of Contents 15 | 16 | - [Overview](#overview) 17 | - [Installation](#installation) 18 | - [Usage](#usage) 19 | - [Documentation](#documentation) 20 | - [Demo](#demo) 21 | - [Dependencies](#dependencies) 22 | 23 | ## Overview 24 | 25 | SwiftSwarm focuses on making agent coordination and execution lightweight. Its main use case is to enable seamless transitions between agents in a conversation. 26 | 27 | It accomplishes this through two abstractions: AgentRepresentable and ToolResponseHandler. An AgentRepresentable contains an Agent that encompasses instructions and tools. At any point, it can hand off a conversation to another Agent through the ToolResponseHandler. 28 | 29 | ⚠️ **Important** 30 | 31 | > Same as Swarm OpenAI library, SwiftSwarm Agents are not related to Assistants in the Assistants API. They are named similarly for convenience, 32 | > but are otherwise completely unrelated. SwiftSwarm is entirely powered by the Chat Completions API and is hence 33 | > stateless between calls. 34 | 35 | To see demonstrations of state management in iOS applications, visit the [Examples folder](https://github.com/jamesrochabrun/SwiftSwarm/tree/main/SwiftSwarmExample) in this project. These examples are demos only and are not intended for production use. 36 | 37 | Note: Currently, only streaming is supported. While the project uses the OpenAI API exclusively, Claude support could be added to the roadmap if there is sufficient interest. 38 | 39 | ## Installation 40 | 41 | ### Swift Package Manager 42 | 43 | 1. Open your Swift project in Xcode. 44 | 2. Go to `File` -> `Add Package Dependency`. 45 | 3. In the search bar, enter [this URL](https://github.com/jamesrochabrun/SwiftSwarm). 46 | 4. Choose the main branch. (see the note below). 47 | 5. Click `Add Package`. 48 | 49 | Note: Xcode has a quirk where it defaults an SPM package's upper limit to 2.0.0. This package is beyond that 50 | limit, so you should not accept the defaults that Xcode proposes. Instead, enter the lower bound of the 51 | [release version](https://github.com/jamesrochabrun/SwiftOpenAI/releases) that you'd like to support, and then 52 | tab out of the input box for Xcode to adjust the upper bound. Alternatively, you may select `branch` -> `main` 53 | to stay on the bleeding edge. 54 | 55 | ## Documentation 56 | 57 | ### The Agent model 58 | 59 | ```swift 60 | /// A structure representing an agent in the system. 61 | /// 62 | /// The `Agent` structure contains the essential properties required to define 63 | /// an agent, including the model it uses, its instructions, and its available tools. 64 | 65 | public struct Agent { 66 | public var name: String 67 | public var model: Model 68 | public var instructions: String 69 | public var tools: [ChatCompletionParameters.Tool] 70 | } 71 | ``` 72 | 73 | SwiftSwarm enables autonomous agents through two main interfaces: `AgentRepresentable` and `ToolResponseHandler` 74 | 75 | ### The `AgentRepresentable` interface 76 | 77 | ```swift 78 | public protocol AgentRepresentable: CaseIterable, RawRepresentable where RawValue == String { 79 | 80 | /// The `Agent` that contains all tools for agent orchestration. 81 | var agent: Agent { get } 82 | 83 | /// The base definition for this agent type. 84 | /// 85 | /// This property allows each conforming type to provide its base configuration 86 | /// such as model, instructions, and custom tools. 87 | /// This should only be used internally - consumers should always use the `agent` 88 | /// property when making run requests. 89 | var agentDefinition: AgentDefinition { get } 90 | } 91 | ``` 92 | 93 | ### The `ToolResponseHandler` interface 94 | 95 | ```swift 96 | public protocol ToolResponseHandler { 97 | 98 | /// The type of agent that this handler works with. 99 | /// 100 | /// `AgentType` must conform to the `AgentRepresentable` protocol, ensuring that 101 | /// it can be converted to or from an agent. 102 | associatedtype AgentType: AgentRepresentable 103 | 104 | /// Attempts to transfer the tool parameters to a matching agent. 105 | /// 106 | /// This method checks the provided parameters to find a suitable agent 107 | /// that matches the given tool keys and values, returning the corresponding agent if found. 108 | /// 109 | /// - Parameter parameters: A dictionary of parameters that may contain information 110 | /// for selecting an agent. 111 | /// - Returns: An optional `Agent` that matches the parameters or `nil` if no match is found. 112 | func transferToAgent(_ parameters: [String: Any]) -> Agent? 113 | 114 | /// Handles the tool response content asynchronously. 115 | /// 116 | /// Given a set of parameters, this method processes the content generated by the tools 117 | /// and returns the resulting string asynchronously. 118 | /// 119 | /// - Parameter parameters: A dictionary of parameters containing tool inputs. 120 | /// - Returns: A string representing the tool's response content. 121 | /// - Throws: Any errors that may occur during content handling. 122 | func handleToolResponseContent(parameters: [String: Any]) async throws -> String? 123 | } 124 | ``` 125 | 126 | ### Usage 127 | 128 | In this section, we will walk through step by step what to do to use SwiftSwarm 129 | 130 | ### 1 - Defining your agents: 131 | 132 | First, define your agents using an enum with a `String` raw value that conforms to `AgentRepresentable`. Here's an example: 133 | 134 | ```swift 135 | enum Team: String { 136 | 137 | case engineer = "Engineer" 138 | case designer = "Designer" 139 | case product = "Product" 140 | } 141 | ``` 142 | 143 | In this example, we have a Team enum with 3 agents. These agents can transfer the conversation to a new agent when the user's question requires specialized expertise. 144 | 145 | Now let's use the AgentRepresentable protocol to define the agents. The final implementation will look like this: 146 | 147 | ```swift 148 | enum Team: String, AgentRepresentable { 149 | 150 | case engineer = "Engineer" 151 | case designer = "Designer" 152 | case product = "Product" 153 | 154 | var agentDefinition: AgentDefinition { 155 | switch self { 156 | case .engineer: 157 | .init(agent: Agent( 158 | name: self.rawValue, 159 | model: .gpt4o, 160 | instructions: "You are a technical engineer, if user asks about you, you answer with your name \(self.rawValue)", 161 | tools: [])) // <---- you can define specific tools for each agent. 162 | case .designer: 163 | .init(agent: Agent( 164 | name: self.rawValue, 165 | model: .gpt4o, 166 | instructions: "You are a UX/UI designer, if user asks about you, you answer with your name \(self.rawValue)", 167 | tools: [])) // <---- you can define specific tools for each agent. 168 | case .product: 169 | .init(agent: Agent( 170 | name: self.rawValue, 171 | model: .gpt4o, 172 | instructions: "You are a product manager, if user asks about you, you answer with your name \(self.rawValue)", 173 | tools: [])) // <---- you can define specific tools for each agent. 174 | } 175 | } 176 | } 177 | ``` 178 | 179 | Note: You can define specific tools for each agent. 180 | 181 | ### 2 - Defining your tools handler. 182 | 183 | Now that we have defined our agents, we need to create an object that conforms to ToolResponseHandler. Here's an example: 184 | 185 | ```swift 186 | struct TeamDemoResponseHandler: ToolResponseHandler { 187 | 188 | /// 1. 189 | typealias AgentType = Team 190 | 191 | /// 2. 192 | func handleToolResponseContent( 193 | parameters: [String: Any]) 194 | async throws -> String? 195 | { 196 | /// 3. 197 | } 198 | } 199 | ``` 200 | 201 | 1. The `ToolResponseHandler` associated type must be an enum that conforms to AgentRepresentable. 202 | 203 | 2. The `handleToolResponseContent` function is triggered when a function call associated with your agent tools occurs. This is where you implement custom functionality based on the provided parameters. 204 | 205 | Note: The `ToolResponseHandler` automatically manages agent switching using the `transferToAgent` function in a protocol extension. 206 | 207 | 3. When a function call is triggered, you can retrieve values using the keys defined in your tool definition's parameter properties. For instructions in how to define a tool got to `SwiftOpenAI` [function call documentation](https://github.com/jamesrochabrun/SwiftOpenAI?tab=readme-ov-file#function-calling). 208 | 209 | ### 3 - Instantiating a Swarm object. 210 | 211 | SwiftSwarm relies on SwiftOpenAI to manage communication with OpenAI APIs. A Swarm object requires two dependencies: 212 | 213 | - An instance of `OpenAIService` 214 | - An instance of `ToolResponseHandler` 215 | 216 | The code will look like this: 217 | 218 | ```swift 219 | import SwiftOpenAI 220 | import SwiftSwarm 221 | 222 | let apiKey = "MY_API_KEY" 223 | let openAIService = OpenAIServiceFactory.service(apiKey: apiKey) 224 | let toolResponseHandler = TeamDemoResponseHandler() 225 | let swarm = Swarm(client: openAIService, toolResponseHandler: toolResponseHandler) 226 | 227 | let userMessage = "I need design input" 228 | let message = ChatCompletionParameters.Message(role: .user, content: .text(userMessage)) 229 | var currentAgent = Team.engineer.agent 230 | 231 | let streamChunks = await swarm.runStream(agent: currentAgent, messages: [message]) 232 | 233 | for try await streamChunk in streamChunks { 234 | content += streamChunk.content ?? "" 235 | if let response = streamChunk.response { 236 | currentAgent = response.agent <--- Transfer to designer here. 237 | } 238 | } 239 | 240 | print("Switched to: \(currentAgent.name)") 241 | print("Content: \(content)") 242 | 243 | ``` 244 | 245 | This will print: 246 | 247 | ```console 248 | Switched to: Designer 249 | 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! 250 | ``` 251 | 252 | ## Demo 253 | 254 | Note: SwiftSwarm is stateless. For examples of how to manage agent conversations with state, please visit the [examples folder](https://github.com/jamesrochabrun/SwiftSwarm/tree/main/SwiftSwarmExample) in this repository. 255 | 256 | ![Screenshot 2024-10-23 at 4 17 34 PM](https://github.com/user-attachments/assets/7a6b70bd-4ab8-4e20-a078-b032fc1ceac7) 257 | 258 | ## Dependencies 259 | 260 | This project uses [SwiftOpenAI](https://github.com/jamesrochabrun/SwiftOpenAI) as a dependency, therefore an OpenAI API Key is needed. Go to SwiftOpenAI documentation for more information. 261 | -------------------------------------------------------------------------------- /Sources/SwiftSwarm/Agent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Agent.swift 3 | // 4 | // 5 | // Created by James Rochabrun on 10/18/24. 6 | // 7 | 8 | import SwiftOpenAI 9 | 10 | /// A structure representing an agent in the system. 11 | /// 12 | /// The `Agent` structure contains the essential properties required to define 13 | /// an agent, including the model it uses, its instructions, and its available tools. 14 | public struct Agent { 15 | 16 | /// The name of the agent. 17 | public var name: String 18 | 19 | /// The model associated with the agent. 20 | /// 21 | /// This defines the language model the agent is using for generating responses, 22 | /// such as `gpt-4` or another variant. 23 | public var model: Model 24 | 25 | /// The instructions provided to the agent. 26 | /// 27 | /// These are typically guidelines or system messages that define the behavior 28 | /// or scope of the agent when it generates responses. 29 | public var instructions: String 30 | 31 | /// The list of tools available to the agent. 32 | /// 33 | /// Each tool is a callable function that the agent can use to assist in generating 34 | /// responses or executing actions as part of its workflow. 35 | public var tools: [ChatCompletionParameters.Tool] 36 | 37 | public init( 38 | name: String, 39 | model: Model, 40 | instructions: String, 41 | tools: [ChatCompletionParameters.Tool]) 42 | { 43 | self.name = name 44 | self.model = model 45 | self.instructions = instructions 46 | self.tools = tools 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/SwiftSwarm/Interfaces/AgentRepresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AgentRepresentable.swift 3 | // 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftOpenAI 10 | 11 | /// A protocol that defines the requirements for an agent to be representable. 12 | /// 13 | /// `AgentRepresentable` ensures that conforming types can be iterated over (via `CaseIterable`), 14 | /// represented by a raw value (via `RawRepresentable`), and associated with an `Agent` instance. 15 | /// 16 | /// This is useful for creating enums or other structures that represent different agents in the system. 17 | public protocol AgentRepresentable: CaseIterable, RawRepresentable where RawValue == String { 18 | 19 | /// The `Agent` that contains all tools for agent orchestration. 20 | var agent: Agent { get } 21 | 22 | /// The base definition for this agent type. 23 | /// 24 | /// This property allows each conforming type to provide its base configuration 25 | /// such as model, instructions, and custom tools. 26 | /// This should only be used internally - consumers should always use the `agent` 27 | /// property when making run requests. 28 | var agentDefinition: AgentDefinition { get } 29 | } 30 | 31 | /// A wrapper structure that holds the base configuration for an agent. 32 | /// 33 | /// This structure serves as an intermediate layer between the raw agent configuration 34 | /// and its final form with orchestration tools. It helps separate the basic agent setup 35 | /// from its runtime capabilities. 36 | public struct AgentDefinition { 37 | 38 | /// The base agent configuration without orchestration tools. 39 | var agent: Agent 40 | 41 | /// Creates a new agent definition with the specified base configuration. 42 | /// 43 | /// - Parameter agent: The base agent configuration to use. 44 | public init(agent: Agent) { 45 | self.agent = agent 46 | } 47 | } 48 | 49 | public extension AgentRepresentable { 50 | 51 | var agent: Agent { 52 | let base = agentDefinition.agent 53 | return Agent( 54 | name: base.name, 55 | model: base.model, 56 | instructions: base.instructions, 57 | tools: base.tools + orchestrationTools) 58 | } 59 | 60 | /// A collection of tools that enable agent-to-agent communication and task delegation. 61 | /// 62 | /// This property automatically generates tools for each agent type in the system, allowing: 63 | /// - Seamless transitions between different agent roles 64 | /// - Dynamic task handoffs between agents 65 | /// 66 | /// Each generated tool: 67 | /// - Is named after its corresponding agent type 68 | /// - Can transfer control to the specified agent 69 | private var orchestrationTools: [ChatCompletionParameters.Tool] { 70 | var tools: [ChatCompletionParameters.Tool] = [] 71 | for item in Self.allCases { 72 | tools.append(.init(function: .init( 73 | name: "\(item.rawValue)", 74 | strict: nil, 75 | description: "Transfer to \(item.rawValue) agent, for agent \(item.rawValue) perspective", 76 | parameters: .init( 77 | type: .object, 78 | properties: [ 79 | "agent": .init(type: .string, description: "Returns \(item.rawValue)") 80 | ], 81 | required: ["agent"])))) 82 | } 83 | return tools 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/SwiftSwarm/Interfaces/ToolResponseHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolResponseHandler.swift 3 | // 4 | // 5 | // Created by James Rochabrun on 10/21/24. 6 | // 7 | 8 | import Foundation 9 | 10 | /// A protocol that defines the required behavior for handling tool responses. 11 | /// 12 | /// `ToolResponseHandler` is designed to manage how tools and agents interact within the system, 13 | /// allowing for the selection of agents based on parameters and handling the tool response content. 14 | public protocol ToolResponseHandler { 15 | 16 | /// The type of agent that this handler works with. 17 | /// 18 | /// `AgentType` must conform to the `AgentRepresentable` protocol, ensuring that 19 | /// it can be converted to or from an agent. 20 | associatedtype AgentType: AgentRepresentable 21 | 22 | /// Attempts to transfer the tool parameters to a matching agent. 23 | /// 24 | /// This method checks the provided parameters to find a suitable agent 25 | /// that matches the given tool keys and values, returning the corresponding agent if found. 26 | /// 27 | /// - Parameter parameters: A dictionary of parameters that may contain information 28 | /// for selecting an agent. 29 | /// - Returns: An optional `Agent` that matches the parameters or `nil` if no match is found. 30 | func transferToAgent(_ parameters: [String: Any]) -> Agent? 31 | 32 | /// Handles the tool response content asynchronously. 33 | /// 34 | /// Given a set of parameters, this method processes the content generated by the tools 35 | /// and returns the resulting string asynchronously. 36 | /// 37 | /// - Parameter parameters: A dictionary of parameters containing tool inputs. 38 | /// - Returns: A string representing the tool's response content. 39 | /// - Throws: Any errors that may occur during content handling. 40 | func handleToolResponseContent(parameters: [String: Any]) async throws -> String? 41 | } 42 | 43 | public extension ToolResponseHandler { 44 | 45 | /// Tries to transfer the tool parameters to a suitable agent based on tool keys. 46 | /// 47 | /// This method iterates over the available agents, checking their tool keys against the 48 | /// provided parameters. If a matching agent is found, it is returned. 49 | /// 50 | /// - Parameter parameters: A dictionary of parameters to match with an agent's tools. 51 | /// - Returns: An optional `Agent` that matches the provided parameters, or `nil` if no match is found. 52 | func transferToAgent(_ parameters: [String: Any]) -> Agent? { 53 | for agent in agents { 54 | let toolKeys = Set(agent.agent.tools.compactMap { tool -> [String]? in 55 | tool.function.parameters?.properties?.keys.map { $0 } 56 | }.flatMap { $0 }) 57 | 58 | // Check if any of this agent's tool keys match with parameters 59 | for key in toolKeys { 60 | if let value = parameters[key] as? String, 61 | agent.rawValue == value { 62 | return agent.agent 63 | } 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | /// A list of agents conforming to `AgentType`, ensuring that the handler has access to all cases. 70 | /// 71 | /// This computed property retrieves all agents that conform to `AgentType` and makes them available for use. 72 | private var agents: [AgentType] { 73 | (AgentType.allCases as? [AgentType]) ?? [] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/SwiftSwarm/Response.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Response.swift 3 | // 4 | // 5 | // Created by James Rochabrun on 10/18/24. 6 | // 7 | 8 | import SwiftOpenAI 9 | 10 | /// A structure that represents the response generated by an agent. 11 | /// 12 | /// The `Response` structure encapsulates the messages, agent, and context variables 13 | /// resulting from an agent's execution. It is used to capture the outcome of a session 14 | /// or a series of interactions with an agent. 15 | public struct Response { 16 | 17 | /// The list of messages generated during the interaction. 18 | /// 19 | /// These messages include all the content exchanged between the agent and the user 20 | /// or other systems during the response generation. 21 | public var messages: [ChatCompletionParameters.Message] 22 | 23 | /// The agent responsible for generating the response. 24 | /// 25 | /// This property holds the agent that was active during the generation of the response. 26 | public var agent: Agent 27 | 28 | /// A dictionary of context variables that were used or updated during the interaction. 29 | /// 30 | /// These variables can store additional context or state information relevant to the agent's responses. 31 | public var contextVariables: [String: String] 32 | } 33 | -------------------------------------------------------------------------------- /Sources/SwiftSwarm/StreamChunk.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StreamChunk.swift 3 | // 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftOpenAI 10 | 11 | /// A structure representing a chunk of streamed data during an agent's response generation. 12 | /// 13 | /// The `StreamChunk` structure is used to capture portions of the response as they are streamed 14 | /// from the agent, including any content, tool calls, delimiters, or a final response. 15 | public struct StreamChunk { 16 | 17 | /// The content of the current chunk, if available. 18 | /// 19 | /// This represents a portion of the agent's response as a string, which may be incomplete or streamed progressively. 20 | public var content: String? 21 | 22 | /// A list of tool calls associated with the current chunk, if any. 23 | /// 24 | /// These tool calls represent actions that the agent may request or perform as part of generating the response. 25 | public var toolCalls: [ToolCall]? 26 | 27 | /// A delimiter that may indicate the start or end of a streamed section. 28 | /// 29 | /// This optional string can be used to signify when a chunk starts or ends, for example, with `"start"` or `"end"`. 30 | public var delim: String? 31 | 32 | /// The final response, if this chunk represents the conclusion of the stream. 33 | /// 34 | /// This property is set when the streaming process completes and the full `Response` is ready. 35 | public var response: Response? 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Sources/SwiftSwarm/Swarm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swarm.swift 3 | // 4 | // 5 | // Created by James Rochabrun on 10/18/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftOpenAI 10 | 11 | /// An actor that manages the streaming of agent interactions and tool responses. 12 | /// 13 | /// The `Swarm` actor coordinates the communication between an agent and tools, handling 14 | /// the streaming of responses, executing tool calls, and updating context variables during the conversation. 15 | public actor Swarm { 16 | 17 | private let client: OpenAIService 18 | private let toolResponseHandler: Handler 19 | 20 | /// Initializes a new instance of the `Swarm` actor. 21 | /// 22 | /// - Parameters: 23 | /// - client: An instance of `OpenAIService` used for making requests. 24 | /// - toolResponseHandler: A handler conforming to `ToolResponseHandler` responsible for processing tool responses. 25 | public init(client: OpenAIService, toolResponseHandler: Handler) { 26 | self.client = client 27 | self.toolResponseHandler = toolResponseHandler 28 | } 29 | 30 | /// Runs a stream of interactions between the agent and the provided messages. 31 | /// 32 | /// This function handles the streaming of chat completion, managing tool calls, and updating the agent and context variables. 33 | /// 34 | /// - Parameters: 35 | /// - agent: The agent responsible for processing the messages. 36 | /// - messages: A list of chat messages to be included in the interaction. 37 | /// - contextVariables: Optional context variables to use during the conversation. 38 | /// - modelOverride: An optional model to override the agent's default model. 39 | /// - maxTurns: The maximum number of turns the agent is allowed to take. 40 | /// - executeTools: A Boolean value to determine whether the agent should execute tools during the process. 41 | /// - Returns: An `AsyncThrowingStream` of `StreamChunk` objects, representing the streamed interaction data. 42 | public func runStream( 43 | agent: Agent, 44 | messages: [ChatCompletionParameters.Message], 45 | contextVariables: [String: String] = [:], 46 | modelOverride: Model? = nil, 47 | executeTools: Bool = true) 48 | -> AsyncThrowingStream 49 | { 50 | AsyncThrowingStream { continuation in 51 | Task { 52 | do { 53 | var activeAgent = agent 54 | var currentContextVariables = contextVariables 55 | var history = messages 56 | let initialMessageCount = messages.count 57 | 58 | continuation.yield(StreamChunk(delim: "start")) 59 | 60 | let completionStream = try await getChatCompletionStream( 61 | agent: activeAgent, 62 | history: history, 63 | contextVariables: currentContextVariables, 64 | modelOverride: modelOverride) 65 | 66 | let (content, toolCalls) = try await accumulateStreamContent(completionStream, continuation: continuation) 67 | 68 | let assistantMessage = ChatCompletionParameters.Message( 69 | role: .assistant, 70 | content: .text(content), 71 | toolCalls: toolCalls 72 | ) 73 | history.append(assistantMessage) 74 | 75 | /// Check if there are available tools. 76 | if let availableToolCalls = toolCalls, !availableToolCalls.isEmpty && executeTools { 77 | let partialResponse = try await handleToolCalls( 78 | availableToolCalls, 79 | agent: activeAgent, 80 | contextVariables: currentContextVariables) 81 | 82 | history.append(contentsOf: partialResponse.messages) 83 | currentContextVariables.merge(partialResponse.contextVariables) { _, new in new } 84 | 85 | activeAgent = partialResponse.agent 86 | 87 | for message in partialResponse.messages { 88 | if case .text(_) = message.content { 89 | // We only need to stream the `availableToolCalls` at this point. 90 | continuation.yield(StreamChunk(content: "", toolCalls: availableToolCalls)) 91 | } 92 | } 93 | 94 | // Get final response after tool execution 95 | let finalStream = try await getChatCompletionStream( 96 | agent: activeAgent, 97 | history: history, 98 | contextVariables: currentContextVariables, 99 | modelOverride: modelOverride) 100 | 101 | let (finalContent, tools) = try await accumulateStreamContent(finalStream, continuation: continuation) 102 | 103 | if !finalContent.isEmpty { 104 | let finalAssistantMessage = ChatCompletionParameters.Message( 105 | role: .assistant, 106 | content: .text(finalContent) 107 | ) 108 | history.append(finalAssistantMessage) 109 | } 110 | } 111 | 112 | continuation.yield(StreamChunk(delim: "end")) 113 | 114 | let finalResponse = Response( 115 | messages: Array(history.dropFirst(initialMessageCount)), 116 | agent: activeAgent, 117 | contextVariables: currentContextVariables 118 | ) 119 | continuation.yield(StreamChunk(response: finalResponse)) 120 | continuation.finish() 121 | 122 | } catch { 123 | continuation.finish(throwing: error) 124 | } 125 | } 126 | } 127 | } 128 | 129 | /// Accumulates content from a streaming response. 130 | /// 131 | /// This function gathers content and tool calls from the streamed chunks and sends updates via the provided continuation. 132 | /// 133 | /// - Parameters: 134 | /// - stream: The `AsyncThrowingStream` of `ChatCompletionChunkObject` to process. 135 | /// - continuation: A continuation to yield the accumulated content and tool calls as `StreamChunk` objects. 136 | /// - Returns: A tuple containing the accumulated content and tool calls. 137 | private func accumulateStreamContent( 138 | _ stream: AsyncThrowingStream, 139 | continuation: AsyncThrowingStream.Continuation) 140 | async throws -> (String, [ToolCall]?) 141 | { 142 | var content = "" 143 | var accumulatedTools: [String: (ToolCall, String)] = [:] 144 | 145 | for try await chunk in stream { 146 | if let chunkContent = chunk.choices.first?.delta.content, !chunkContent.isEmpty { 147 | content += chunkContent 148 | continuation.yield(StreamChunk(content: chunkContent)) 149 | } 150 | 151 | if let toolCalls = chunk.choices.first?.delta.toolCalls, !toolCalls.isEmpty { 152 | for toolCall in toolCalls { 153 | if let id = toolCall.id { 154 | accumulatedTools[id] = (toolCall, toolCall.function.arguments) 155 | } else if let index = toolCall.index, let existingTool = accumulatedTools.values.first(where: { $0.0.index == index }) { 156 | let updatedArguments = existingTool.1 + (toolCall.function.arguments) 157 | accumulatedTools[existingTool.0.id ?? ""] = (existingTool.0, updatedArguments) 158 | } 159 | } 160 | continuation.yield(StreamChunk(toolCalls: toolCalls)) 161 | } 162 | 163 | if chunk.choices.first?.finishReason != nil { 164 | break 165 | } 166 | } 167 | let finalToolCalls = accumulatedTools.isEmpty ? nil : accumulatedTools.map { (_, value) in 168 | let (toolCall, arguments) = value 169 | return ToolCall( 170 | id: toolCall.id, 171 | type: toolCall.type ?? "function", 172 | function: FunctionCall(arguments: arguments, name: toolCall.function.name ?? "") 173 | ) 174 | } 175 | 176 | return (content, finalToolCalls) 177 | } 178 | 179 | /// Retrieves the streamed chat completion from the agent. 180 | /// 181 | /// This function sends the agent's history and context variables to retrieve a streamed response. 182 | /// 183 | /// - Parameters: 184 | /// - agent: The agent to use for generating the response. 185 | /// - history: The chat history for the agent to base the response on. 186 | /// - contextVariables: The context variables to pass to the agent. 187 | /// - modelOverride: An optional model to override the agent's default model. 188 | /// - Returns: An `AsyncThrowingStream` of `ChatCompletionChunkObject` representing the streamed response. 189 | private func getChatCompletionStream( 190 | agent: Agent, 191 | history: [ChatCompletionParameters.Message], 192 | contextVariables: [String: String], 193 | modelOverride: Model?) 194 | async throws -> AsyncThrowingStream 195 | { 196 | 197 | // Add a system message for agent's instructions 198 | var updatedHistory = history 199 | 200 | // Check if a system message with instructions is already present 201 | if let lastSystemMessageIndex = updatedHistory.lastIndex(where: { $0.role == "system" }) { 202 | // Update the existing system message with the current agent's instructions 203 | updatedHistory[lastSystemMessageIndex] = ChatCompletionParameters.Message( 204 | role: .system, 205 | content: .text(agent.instructions) 206 | ) 207 | } else { 208 | // Add a new system message if it doesn't exist 209 | let systemMessage = ChatCompletionParameters.Message( 210 | role: .system, 211 | content: .text(agent.instructions) 212 | ) 213 | updatedHistory.insert(systemMessage, at: 0) 214 | } 215 | 216 | let parameters = ChatCompletionParameters( 217 | messages: updatedHistory, 218 | model: modelOverride ?? agent.model, 219 | tools: agent.tools, 220 | parallelToolCalls: false) 221 | 222 | return try await client.startStreamedChat(parameters: parameters) 223 | } 224 | 225 | /// Handles tool calls within a response, transferring the context and updating the agent. 226 | /// 227 | /// This function processes tool calls, executes the necessary tools, and updates the agent and context variables. 228 | /// 229 | /// - Parameters: 230 | /// - toolCalls: A list of tool calls made by the agent during the interaction. 231 | /// - agent: The agent currently managing the conversation. 232 | /// - contextVariables: The context variables associated with the conversation. 233 | /// - Returns: A `Response` object that includes the updated messages, agent, and context variables. 234 | private func handleToolCalls( 235 | _ toolCalls: [ToolCall], 236 | agent: Agent, 237 | contextVariables: [String: String]) 238 | async throws -> Response 239 | { 240 | var partialResponse = Response(messages: [], agent: agent, contextVariables: contextVariables) 241 | 242 | debugPrint("Handling Tool Call for agent \(agent.name)") 243 | 244 | for toolCall in toolCalls { 245 | debugPrint("Handling Tool Call \(toolCall.function.name ?? "No name")") 246 | guard let tool = agent.tools.first(where: { $0.function.name == toolCall.function.name }) else { 247 | debugPrint("Tool not found:", toolCall.function.name ?? "no name") 248 | continue 249 | } 250 | 251 | let parameters = toolCall.function.arguments.toDictionary() ?? [:] 252 | let newAgent = toolResponseHandler.transferToAgent(parameters) 253 | let content = try await toolResponseHandler.handleToolResponseContent(parameters: parameters) 254 | 255 | if let newAgent = newAgent { 256 | partialResponse.agent = newAgent 257 | debugPrint("Handling Tool Call transferring to \(newAgent.name)") 258 | } 259 | let toolMessage = ChatCompletionParameters.Message( 260 | role: .tool, 261 | content: .text(content ?? ""), 262 | name: tool.function.name, 263 | toolCallID: toolCall.id 264 | ) 265 | partialResponse.messages.append(toolMessage) 266 | } 267 | 268 | return partialResponse 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /Sources/SwiftSwarm/Utils/ContentType+String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentType+String.swift 3 | // 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftOpenAI 10 | 11 | public extension ChatCompletionParameters.Message.ContentType { 12 | 13 | var text: String? { 14 | switch self { 15 | case .text(let string): 16 | return string 17 | default: 18 | return nil 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/SwiftSwarm/Utils/String+Dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Dictionary.swift 3 | // 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension String { 11 | 12 | /// Converts a JSON-formatted string into a dictionary. This is useful for `ToolCallDefinition` conformers 13 | /// to transform the LLM's argument response, which is a JSON string, into a dictionary for easier value retrieval. 14 | func toDictionary() -> [String: Any]? { 15 | guard let data = data(using: .utf8) else { return nil } 16 | return try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 60; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7B5A9E512CC86672002FF152 /* SwiftSwarmExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E502CC86672002FF152 /* SwiftSwarmExampleApp.swift */; }; 11 | 7B5A9E552CC86674002FF152 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7B5A9E542CC86674002FF152 /* Assets.xcassets */; }; 12 | 7B5A9E592CC86674002FF152 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7B5A9E582CC86674002FF152 /* Preview Assets.xcassets */; }; 13 | 7B5A9E632CC86674002FF152 /* SwiftSwarmExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E622CC86674002FF152 /* SwiftSwarmExampleTests.swift */; }; 14 | 7B5A9E6D2CC86674002FF152 /* SwiftSwarmExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E6C2CC86674002FF152 /* SwiftSwarmExampleUITests.swift */; }; 15 | 7B5A9E6F2CC86674002FF152 /* SwiftSwarmExampleUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E6E2CC86674002FF152 /* SwiftSwarmExampleUITestsLaunchTests.swift */; }; 16 | 7B5A9E7D2CC866CA002FF152 /* SwiftSwarm in Frameworks */ = {isa = PBXBuildFile; productRef = 7B5A9E7C2CC866CA002FF152 /* SwiftSwarm */; }; 17 | 7B5A9E802CC867B9002FF152 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E7F2CC867B9002FF152 /* Team.swift */; }; 18 | 7B5A9E832CC867CA002FF152 /* CellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E822CC867CA002FF152 /* CellViewModel.swift */; }; 19 | 7B5A9E852CC868BD002FF152 /* TeamDemoResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E842CC868BD002FF152 /* TeamDemoResponseHandler.swift */; }; 20 | 7B5A9E872CC86956002FF152 /* TeamDemoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E862CC86956002FF152 /* TeamDemoViewModel.swift */; }; 21 | 7B5A9E892CC869E6002FF152 /* ChatScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E882CC869E6002FF152 /* ChatScreen.swift */; }; 22 | 7B5A9E8B2CC8B5F7002FF152 /* ApiKeyIntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E8A2CC8B5F7002FF152 /* ApiKeyIntroView.swift */; }; 23 | 7B5A9E8D2CC8B615002FF152 /* OptionsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5A9E8C2CC8B615002FF152 /* OptionsListView.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | 7B5A9E5F2CC86674002FF152 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 7B5A9E452CC86672002FF152 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 7B5A9E4C2CC86672002FF152; 32 | remoteInfo = SwiftSwarmExample; 33 | }; 34 | 7B5A9E692CC86674002FF152 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 7B5A9E452CC86672002FF152 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 7B5A9E4C2CC86672002FF152; 39 | remoteInfo = SwiftSwarmExample; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 7B5A9E4D2CC86672002FF152 /* SwiftSwarmExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftSwarmExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 7B5A9E502CC86672002FF152 /* SwiftSwarmExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftSwarmExampleApp.swift; sourceTree = ""; }; 46 | 7B5A9E542CC86674002FF152 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 7B5A9E562CC86674002FF152 /* SwiftSwarmExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftSwarmExample.entitlements; sourceTree = ""; }; 48 | 7B5A9E582CC86674002FF152 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 49 | 7B5A9E5E2CC86674002FF152 /* SwiftSwarmExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftSwarmExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 7B5A9E622CC86674002FF152 /* SwiftSwarmExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftSwarmExampleTests.swift; sourceTree = ""; }; 51 | 7B5A9E682CC86674002FF152 /* SwiftSwarmExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftSwarmExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 7B5A9E6C2CC86674002FF152 /* SwiftSwarmExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftSwarmExampleUITests.swift; sourceTree = ""; }; 53 | 7B5A9E6E2CC86674002FF152 /* SwiftSwarmExampleUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftSwarmExampleUITestsLaunchTests.swift; sourceTree = ""; }; 54 | 7B5A9E7F2CC867B9002FF152 /* Team.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; }; 55 | 7B5A9E822CC867CA002FF152 /* CellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellViewModel.swift; sourceTree = ""; }; 56 | 7B5A9E842CC868BD002FF152 /* TeamDemoResponseHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamDemoResponseHandler.swift; sourceTree = ""; }; 57 | 7B5A9E862CC86956002FF152 /* TeamDemoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamDemoViewModel.swift; sourceTree = ""; }; 58 | 7B5A9E882CC869E6002FF152 /* ChatScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScreen.swift; sourceTree = ""; }; 59 | 7B5A9E8A2CC8B5F7002FF152 /* ApiKeyIntroView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiKeyIntroView.swift; sourceTree = ""; }; 60 | 7B5A9E8C2CC8B615002FF152 /* OptionsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsListView.swift; sourceTree = ""; }; 61 | /* End PBXFileReference section */ 62 | 63 | /* Begin PBXFrameworksBuildPhase section */ 64 | 7B5A9E4A2CC86672002FF152 /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | 7B5A9E7D2CC866CA002FF152 /* SwiftSwarm in Frameworks */, 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | 7B5A9E5B2CC86674002FF152 /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 7B5A9E652CC86674002FF152 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 7B5A9E442CC86672002FF152 = { 90 | isa = PBXGroup; 91 | children = ( 92 | 7B5A9E4F2CC86672002FF152 /* SwiftSwarmExample */, 93 | 7B5A9E612CC86674002FF152 /* SwiftSwarmExampleTests */, 94 | 7B5A9E6B2CC86674002FF152 /* SwiftSwarmExampleUITests */, 95 | 7B5A9E4E2CC86672002FF152 /* Products */, 96 | ); 97 | sourceTree = ""; 98 | }; 99 | 7B5A9E4E2CC86672002FF152 /* Products */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 7B5A9E4D2CC86672002FF152 /* SwiftSwarmExample.app */, 103 | 7B5A9E5E2CC86674002FF152 /* SwiftSwarmExampleTests.xctest */, 104 | 7B5A9E682CC86674002FF152 /* SwiftSwarmExampleUITests.xctest */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 7B5A9E4F2CC86672002FF152 /* SwiftSwarmExample */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 7B5A9E812CC867BF002FF152 /* UI */, 113 | 7B5A9E7E2CC8674E002FF152 /* TeamDemo */, 114 | 7B5A9E502CC86672002FF152 /* SwiftSwarmExampleApp.swift */, 115 | 7B5A9E542CC86674002FF152 /* Assets.xcassets */, 116 | 7B5A9E562CC86674002FF152 /* SwiftSwarmExample.entitlements */, 117 | 7B5A9E572CC86674002FF152 /* Preview Content */, 118 | ); 119 | path = SwiftSwarmExample; 120 | sourceTree = ""; 121 | }; 122 | 7B5A9E572CC86674002FF152 /* Preview Content */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 7B5A9E582CC86674002FF152 /* Preview Assets.xcassets */, 126 | ); 127 | path = "Preview Content"; 128 | sourceTree = ""; 129 | }; 130 | 7B5A9E612CC86674002FF152 /* SwiftSwarmExampleTests */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 7B5A9E622CC86674002FF152 /* SwiftSwarmExampleTests.swift */, 134 | ); 135 | path = SwiftSwarmExampleTests; 136 | sourceTree = ""; 137 | }; 138 | 7B5A9E6B2CC86674002FF152 /* SwiftSwarmExampleUITests */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 7B5A9E6C2CC86674002FF152 /* SwiftSwarmExampleUITests.swift */, 142 | 7B5A9E6E2CC86674002FF152 /* SwiftSwarmExampleUITestsLaunchTests.swift */, 143 | ); 144 | path = SwiftSwarmExampleUITests; 145 | sourceTree = ""; 146 | }; 147 | 7B5A9E7E2CC8674E002FF152 /* TeamDemo */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 7B5A9E7F2CC867B9002FF152 /* Team.swift */, 151 | 7B5A9E842CC868BD002FF152 /* TeamDemoResponseHandler.swift */, 152 | 7B5A9E862CC86956002FF152 /* TeamDemoViewModel.swift */, 153 | ); 154 | path = TeamDemo; 155 | sourceTree = ""; 156 | }; 157 | 7B5A9E812CC867BF002FF152 /* UI */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 7B5A9E822CC867CA002FF152 /* CellViewModel.swift */, 161 | 7B5A9E882CC869E6002FF152 /* ChatScreen.swift */, 162 | 7B5A9E8A2CC8B5F7002FF152 /* ApiKeyIntroView.swift */, 163 | 7B5A9E8C2CC8B615002FF152 /* OptionsListView.swift */, 164 | ); 165 | path = UI; 166 | sourceTree = ""; 167 | }; 168 | /* End PBXGroup section */ 169 | 170 | /* Begin PBXNativeTarget section */ 171 | 7B5A9E4C2CC86672002FF152 /* SwiftSwarmExample */ = { 172 | isa = PBXNativeTarget; 173 | buildConfigurationList = 7B5A9E722CC86674002FF152 /* Build configuration list for PBXNativeTarget "SwiftSwarmExample" */; 174 | buildPhases = ( 175 | 7B5A9E492CC86672002FF152 /* Sources */, 176 | 7B5A9E4A2CC86672002FF152 /* Frameworks */, 177 | 7B5A9E4B2CC86672002FF152 /* Resources */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | ); 183 | name = SwiftSwarmExample; 184 | packageProductDependencies = ( 185 | 7B5A9E7C2CC866CA002FF152 /* SwiftSwarm */, 186 | ); 187 | productName = SwiftSwarmExample; 188 | productReference = 7B5A9E4D2CC86672002FF152 /* SwiftSwarmExample.app */; 189 | productType = "com.apple.product-type.application"; 190 | }; 191 | 7B5A9E5D2CC86674002FF152 /* SwiftSwarmExampleTests */ = { 192 | isa = PBXNativeTarget; 193 | buildConfigurationList = 7B5A9E752CC86674002FF152 /* Build configuration list for PBXNativeTarget "SwiftSwarmExampleTests" */; 194 | buildPhases = ( 195 | 7B5A9E5A2CC86674002FF152 /* Sources */, 196 | 7B5A9E5B2CC86674002FF152 /* Frameworks */, 197 | 7B5A9E5C2CC86674002FF152 /* Resources */, 198 | ); 199 | buildRules = ( 200 | ); 201 | dependencies = ( 202 | 7B5A9E602CC86674002FF152 /* PBXTargetDependency */, 203 | ); 204 | name = SwiftSwarmExampleTests; 205 | productName = SwiftSwarmExampleTests; 206 | productReference = 7B5A9E5E2CC86674002FF152 /* SwiftSwarmExampleTests.xctest */; 207 | productType = "com.apple.product-type.bundle.unit-test"; 208 | }; 209 | 7B5A9E672CC86674002FF152 /* SwiftSwarmExampleUITests */ = { 210 | isa = PBXNativeTarget; 211 | buildConfigurationList = 7B5A9E782CC86674002FF152 /* Build configuration list for PBXNativeTarget "SwiftSwarmExampleUITests" */; 212 | buildPhases = ( 213 | 7B5A9E642CC86674002FF152 /* Sources */, 214 | 7B5A9E652CC86674002FF152 /* Frameworks */, 215 | 7B5A9E662CC86674002FF152 /* Resources */, 216 | ); 217 | buildRules = ( 218 | ); 219 | dependencies = ( 220 | 7B5A9E6A2CC86674002FF152 /* PBXTargetDependency */, 221 | ); 222 | name = SwiftSwarmExampleUITests; 223 | productName = SwiftSwarmExampleUITests; 224 | productReference = 7B5A9E682CC86674002FF152 /* SwiftSwarmExampleUITests.xctest */; 225 | productType = "com.apple.product-type.bundle.ui-testing"; 226 | }; 227 | /* End PBXNativeTarget section */ 228 | 229 | /* Begin PBXProject section */ 230 | 7B5A9E452CC86672002FF152 /* Project object */ = { 231 | isa = PBXProject; 232 | attributes = { 233 | BuildIndependentTargetsInParallel = 1; 234 | LastSwiftUpdateCheck = 1500; 235 | LastUpgradeCheck = 1500; 236 | TargetAttributes = { 237 | 7B5A9E4C2CC86672002FF152 = { 238 | CreatedOnToolsVersion = 15.0; 239 | }; 240 | 7B5A9E5D2CC86674002FF152 = { 241 | CreatedOnToolsVersion = 15.0; 242 | TestTargetID = 7B5A9E4C2CC86672002FF152; 243 | }; 244 | 7B5A9E672CC86674002FF152 = { 245 | CreatedOnToolsVersion = 15.0; 246 | TestTargetID = 7B5A9E4C2CC86672002FF152; 247 | }; 248 | }; 249 | }; 250 | buildConfigurationList = 7B5A9E482CC86672002FF152 /* Build configuration list for PBXProject "SwiftSwarmExample" */; 251 | compatibilityVersion = "Xcode 14.0"; 252 | developmentRegion = en; 253 | hasScannedForEncodings = 0; 254 | knownRegions = ( 255 | en, 256 | Base, 257 | ); 258 | mainGroup = 7B5A9E442CC86672002FF152; 259 | packageReferences = ( 260 | 7B5A9E7B2CC866CA002FF152 /* XCLocalSwiftPackageReference ".." */, 261 | ); 262 | productRefGroup = 7B5A9E4E2CC86672002FF152 /* Products */; 263 | projectDirPath = ""; 264 | projectRoot = ""; 265 | targets = ( 266 | 7B5A9E4C2CC86672002FF152 /* SwiftSwarmExample */, 267 | 7B5A9E5D2CC86674002FF152 /* SwiftSwarmExampleTests */, 268 | 7B5A9E672CC86674002FF152 /* SwiftSwarmExampleUITests */, 269 | ); 270 | }; 271 | /* End PBXProject section */ 272 | 273 | /* Begin PBXResourcesBuildPhase section */ 274 | 7B5A9E4B2CC86672002FF152 /* Resources */ = { 275 | isa = PBXResourcesBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | 7B5A9E592CC86674002FF152 /* Preview Assets.xcassets in Resources */, 279 | 7B5A9E552CC86674002FF152 /* Assets.xcassets in Resources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | 7B5A9E5C2CC86674002FF152 /* Resources */ = { 284 | isa = PBXResourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | 7B5A9E662CC86674002FF152 /* Resources */ = { 291 | isa = PBXResourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | /* End PBXResourcesBuildPhase section */ 298 | 299 | /* Begin PBXSourcesBuildPhase section */ 300 | 7B5A9E492CC86672002FF152 /* Sources */ = { 301 | isa = PBXSourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | 7B5A9E872CC86956002FF152 /* TeamDemoViewModel.swift in Sources */, 305 | 7B5A9E852CC868BD002FF152 /* TeamDemoResponseHandler.swift in Sources */, 306 | 7B5A9E8B2CC8B5F7002FF152 /* ApiKeyIntroView.swift in Sources */, 307 | 7B5A9E802CC867B9002FF152 /* Team.swift in Sources */, 308 | 7B5A9E832CC867CA002FF152 /* CellViewModel.swift in Sources */, 309 | 7B5A9E512CC86672002FF152 /* SwiftSwarmExampleApp.swift in Sources */, 310 | 7B5A9E892CC869E6002FF152 /* ChatScreen.swift in Sources */, 311 | 7B5A9E8D2CC8B615002FF152 /* OptionsListView.swift in Sources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | 7B5A9E5A2CC86674002FF152 /* Sources */ = { 316 | isa = PBXSourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | 7B5A9E632CC86674002FF152 /* SwiftSwarmExampleTests.swift in Sources */, 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | 7B5A9E642CC86674002FF152 /* Sources */ = { 324 | isa = PBXSourcesBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | 7B5A9E6F2CC86674002FF152 /* SwiftSwarmExampleUITestsLaunchTests.swift in Sources */, 328 | 7B5A9E6D2CC86674002FF152 /* SwiftSwarmExampleUITests.swift in Sources */, 329 | ); 330 | runOnlyForDeploymentPostprocessing = 0; 331 | }; 332 | /* End PBXSourcesBuildPhase section */ 333 | 334 | /* Begin PBXTargetDependency section */ 335 | 7B5A9E602CC86674002FF152 /* PBXTargetDependency */ = { 336 | isa = PBXTargetDependency; 337 | target = 7B5A9E4C2CC86672002FF152 /* SwiftSwarmExample */; 338 | targetProxy = 7B5A9E5F2CC86674002FF152 /* PBXContainerItemProxy */; 339 | }; 340 | 7B5A9E6A2CC86674002FF152 /* PBXTargetDependency */ = { 341 | isa = PBXTargetDependency; 342 | target = 7B5A9E4C2CC86672002FF152 /* SwiftSwarmExample */; 343 | targetProxy = 7B5A9E692CC86674002FF152 /* PBXContainerItemProxy */; 344 | }; 345 | /* End PBXTargetDependency section */ 346 | 347 | /* Begin XCBuildConfiguration section */ 348 | 7B5A9E702CC86674002FF152 /* Debug */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ALWAYS_SEARCH_USER_PATHS = NO; 352 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 353 | CLANG_ANALYZER_NONNULL = YES; 354 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 355 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 356 | CLANG_ENABLE_MODULES = YES; 357 | CLANG_ENABLE_OBJC_ARC = YES; 358 | CLANG_ENABLE_OBJC_WEAK = YES; 359 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 360 | CLANG_WARN_BOOL_CONVERSION = YES; 361 | CLANG_WARN_COMMA = YES; 362 | CLANG_WARN_CONSTANT_CONVERSION = YES; 363 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 364 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 365 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 366 | CLANG_WARN_EMPTY_BODY = YES; 367 | CLANG_WARN_ENUM_CONVERSION = YES; 368 | CLANG_WARN_INFINITE_RECURSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 371 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 372 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 373 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 374 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 375 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 376 | CLANG_WARN_STRICT_PROTOTYPES = YES; 377 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 378 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 379 | CLANG_WARN_UNREACHABLE_CODE = YES; 380 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 381 | COPY_PHASE_STRIP = NO; 382 | DEBUG_INFORMATION_FORMAT = dwarf; 383 | ENABLE_STRICT_OBJC_MSGSEND = YES; 384 | ENABLE_TESTABILITY = YES; 385 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 386 | GCC_C_LANGUAGE_STANDARD = gnu17; 387 | GCC_DYNAMIC_NO_PIC = NO; 388 | GCC_NO_COMMON_BLOCKS = YES; 389 | GCC_OPTIMIZATION_LEVEL = 0; 390 | GCC_PREPROCESSOR_DEFINITIONS = ( 391 | "DEBUG=1", 392 | "$(inherited)", 393 | ); 394 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 395 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 396 | GCC_WARN_UNDECLARED_SELECTOR = YES; 397 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 398 | GCC_WARN_UNUSED_FUNCTION = YES; 399 | GCC_WARN_UNUSED_VARIABLE = YES; 400 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 401 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 402 | MTL_FAST_MATH = YES; 403 | ONLY_ACTIVE_ARCH = YES; 404 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 405 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 406 | }; 407 | name = Debug; 408 | }; 409 | 7B5A9E712CC86674002FF152 /* Release */ = { 410 | isa = XCBuildConfiguration; 411 | buildSettings = { 412 | ALWAYS_SEARCH_USER_PATHS = NO; 413 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 414 | CLANG_ANALYZER_NONNULL = YES; 415 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 416 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 417 | CLANG_ENABLE_MODULES = YES; 418 | CLANG_ENABLE_OBJC_ARC = YES; 419 | CLANG_ENABLE_OBJC_WEAK = YES; 420 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 421 | CLANG_WARN_BOOL_CONVERSION = YES; 422 | CLANG_WARN_COMMA = YES; 423 | CLANG_WARN_CONSTANT_CONVERSION = YES; 424 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 425 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 426 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 427 | CLANG_WARN_EMPTY_BODY = YES; 428 | CLANG_WARN_ENUM_CONVERSION = YES; 429 | CLANG_WARN_INFINITE_RECURSION = YES; 430 | CLANG_WARN_INT_CONVERSION = YES; 431 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 432 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 433 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 434 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 435 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 436 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 437 | CLANG_WARN_STRICT_PROTOTYPES = YES; 438 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 439 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 440 | CLANG_WARN_UNREACHABLE_CODE = YES; 441 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 442 | COPY_PHASE_STRIP = NO; 443 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 444 | ENABLE_NS_ASSERTIONS = NO; 445 | ENABLE_STRICT_OBJC_MSGSEND = YES; 446 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 447 | GCC_C_LANGUAGE_STANDARD = gnu17; 448 | GCC_NO_COMMON_BLOCKS = YES; 449 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 450 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 451 | GCC_WARN_UNDECLARED_SELECTOR = YES; 452 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 453 | GCC_WARN_UNUSED_FUNCTION = YES; 454 | GCC_WARN_UNUSED_VARIABLE = YES; 455 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 456 | MTL_ENABLE_DEBUG_INFO = NO; 457 | MTL_FAST_MATH = YES; 458 | SWIFT_COMPILATION_MODE = wholemodule; 459 | }; 460 | name = Release; 461 | }; 462 | 7B5A9E732CC86674002FF152 /* Debug */ = { 463 | isa = XCBuildConfiguration; 464 | buildSettings = { 465 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 466 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 467 | CODE_SIGN_ENTITLEMENTS = SwiftSwarmExample/SwiftSwarmExample.entitlements; 468 | CODE_SIGN_STYLE = Automatic; 469 | CURRENT_PROJECT_VERSION = 1; 470 | DEVELOPMENT_ASSET_PATHS = "\"SwiftSwarmExample/Preview Content\""; 471 | DEVELOPMENT_TEAM = CQ45U4X9K3; 472 | ENABLE_HARDENED_RUNTIME = YES; 473 | ENABLE_PREVIEWS = YES; 474 | GENERATE_INFOPLIST_FILE = YES; 475 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 476 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 477 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 478 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 479 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 480 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 481 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 482 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 483 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 484 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 485 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 486 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 487 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 488 | MACOSX_DEPLOYMENT_TARGET = 14.0; 489 | MARKETING_VERSION = 1.0; 490 | PRODUCT_BUNDLE_IDENTIFIER = jamesRochabrun.SwiftSwarmExample; 491 | PRODUCT_NAME = "$(TARGET_NAME)"; 492 | SDKROOT = auto; 493 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 494 | SWIFT_EMIT_LOC_STRINGS = YES; 495 | SWIFT_VERSION = 5.0; 496 | TARGETED_DEVICE_FAMILY = "1,2"; 497 | }; 498 | name = Debug; 499 | }; 500 | 7B5A9E742CC86674002FF152 /* Release */ = { 501 | isa = XCBuildConfiguration; 502 | buildSettings = { 503 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 504 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 505 | CODE_SIGN_ENTITLEMENTS = SwiftSwarmExample/SwiftSwarmExample.entitlements; 506 | CODE_SIGN_STYLE = Automatic; 507 | CURRENT_PROJECT_VERSION = 1; 508 | DEVELOPMENT_ASSET_PATHS = "\"SwiftSwarmExample/Preview Content\""; 509 | DEVELOPMENT_TEAM = CQ45U4X9K3; 510 | ENABLE_HARDENED_RUNTIME = YES; 511 | ENABLE_PREVIEWS = YES; 512 | GENERATE_INFOPLIST_FILE = YES; 513 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 514 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 515 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 516 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 517 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 518 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 519 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 520 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 521 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 522 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 523 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 524 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 525 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 526 | MACOSX_DEPLOYMENT_TARGET = 14.0; 527 | MARKETING_VERSION = 1.0; 528 | PRODUCT_BUNDLE_IDENTIFIER = jamesRochabrun.SwiftSwarmExample; 529 | PRODUCT_NAME = "$(TARGET_NAME)"; 530 | SDKROOT = auto; 531 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 532 | SWIFT_EMIT_LOC_STRINGS = YES; 533 | SWIFT_VERSION = 5.0; 534 | TARGETED_DEVICE_FAMILY = "1,2"; 535 | }; 536 | name = Release; 537 | }; 538 | 7B5A9E762CC86674002FF152 /* Debug */ = { 539 | isa = XCBuildConfiguration; 540 | buildSettings = { 541 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 542 | BUNDLE_LOADER = "$(TEST_HOST)"; 543 | CODE_SIGN_STYLE = Automatic; 544 | CURRENT_PROJECT_VERSION = 1; 545 | DEVELOPMENT_TEAM = CQ45U4X9K3; 546 | GENERATE_INFOPLIST_FILE = YES; 547 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 548 | MACOSX_DEPLOYMENT_TARGET = 14.0; 549 | MARKETING_VERSION = 1.0; 550 | PRODUCT_BUNDLE_IDENTIFIER = jamesRochabrun.SwiftSwarmExampleTests; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SDKROOT = auto; 553 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 554 | SWIFT_EMIT_LOC_STRINGS = NO; 555 | SWIFT_VERSION = 5.0; 556 | TARGETED_DEVICE_FAMILY = "1,2"; 557 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftSwarmExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SwiftSwarmExample"; 558 | }; 559 | name = Debug; 560 | }; 561 | 7B5A9E772CC86674002FF152 /* Release */ = { 562 | isa = XCBuildConfiguration; 563 | buildSettings = { 564 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 565 | BUNDLE_LOADER = "$(TEST_HOST)"; 566 | CODE_SIGN_STYLE = Automatic; 567 | CURRENT_PROJECT_VERSION = 1; 568 | DEVELOPMENT_TEAM = CQ45U4X9K3; 569 | GENERATE_INFOPLIST_FILE = YES; 570 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 571 | MACOSX_DEPLOYMENT_TARGET = 14.0; 572 | MARKETING_VERSION = 1.0; 573 | PRODUCT_BUNDLE_IDENTIFIER = jamesRochabrun.SwiftSwarmExampleTests; 574 | PRODUCT_NAME = "$(TARGET_NAME)"; 575 | SDKROOT = auto; 576 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 577 | SWIFT_EMIT_LOC_STRINGS = NO; 578 | SWIFT_VERSION = 5.0; 579 | TARGETED_DEVICE_FAMILY = "1,2"; 580 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftSwarmExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SwiftSwarmExample"; 581 | }; 582 | name = Release; 583 | }; 584 | 7B5A9E792CC86674002FF152 /* Debug */ = { 585 | isa = XCBuildConfiguration; 586 | buildSettings = { 587 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 588 | CODE_SIGN_STYLE = Automatic; 589 | CURRENT_PROJECT_VERSION = 1; 590 | DEVELOPMENT_TEAM = CQ45U4X9K3; 591 | GENERATE_INFOPLIST_FILE = YES; 592 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 593 | MACOSX_DEPLOYMENT_TARGET = 14.0; 594 | MARKETING_VERSION = 1.0; 595 | PRODUCT_BUNDLE_IDENTIFIER = jamesRochabrun.SwiftSwarmExampleUITests; 596 | PRODUCT_NAME = "$(TARGET_NAME)"; 597 | SDKROOT = auto; 598 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 599 | SWIFT_EMIT_LOC_STRINGS = NO; 600 | SWIFT_VERSION = 5.0; 601 | TARGETED_DEVICE_FAMILY = "1,2"; 602 | TEST_TARGET_NAME = SwiftSwarmExample; 603 | }; 604 | name = Debug; 605 | }; 606 | 7B5A9E7A2CC86674002FF152 /* Release */ = { 607 | isa = XCBuildConfiguration; 608 | buildSettings = { 609 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 610 | CODE_SIGN_STYLE = Automatic; 611 | CURRENT_PROJECT_VERSION = 1; 612 | DEVELOPMENT_TEAM = CQ45U4X9K3; 613 | GENERATE_INFOPLIST_FILE = YES; 614 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 615 | MACOSX_DEPLOYMENT_TARGET = 14.0; 616 | MARKETING_VERSION = 1.0; 617 | PRODUCT_BUNDLE_IDENTIFIER = jamesRochabrun.SwiftSwarmExampleUITests; 618 | PRODUCT_NAME = "$(TARGET_NAME)"; 619 | SDKROOT = auto; 620 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 621 | SWIFT_EMIT_LOC_STRINGS = NO; 622 | SWIFT_VERSION = 5.0; 623 | TARGETED_DEVICE_FAMILY = "1,2"; 624 | TEST_TARGET_NAME = SwiftSwarmExample; 625 | }; 626 | name = Release; 627 | }; 628 | /* End XCBuildConfiguration section */ 629 | 630 | /* Begin XCConfigurationList section */ 631 | 7B5A9E482CC86672002FF152 /* Build configuration list for PBXProject "SwiftSwarmExample" */ = { 632 | isa = XCConfigurationList; 633 | buildConfigurations = ( 634 | 7B5A9E702CC86674002FF152 /* Debug */, 635 | 7B5A9E712CC86674002FF152 /* Release */, 636 | ); 637 | defaultConfigurationIsVisible = 0; 638 | defaultConfigurationName = Release; 639 | }; 640 | 7B5A9E722CC86674002FF152 /* Build configuration list for PBXNativeTarget "SwiftSwarmExample" */ = { 641 | isa = XCConfigurationList; 642 | buildConfigurations = ( 643 | 7B5A9E732CC86674002FF152 /* Debug */, 644 | 7B5A9E742CC86674002FF152 /* Release */, 645 | ); 646 | defaultConfigurationIsVisible = 0; 647 | defaultConfigurationName = Release; 648 | }; 649 | 7B5A9E752CC86674002FF152 /* Build configuration list for PBXNativeTarget "SwiftSwarmExampleTests" */ = { 650 | isa = XCConfigurationList; 651 | buildConfigurations = ( 652 | 7B5A9E762CC86674002FF152 /* Debug */, 653 | 7B5A9E772CC86674002FF152 /* Release */, 654 | ); 655 | defaultConfigurationIsVisible = 0; 656 | defaultConfigurationName = Release; 657 | }; 658 | 7B5A9E782CC86674002FF152 /* Build configuration list for PBXNativeTarget "SwiftSwarmExampleUITests" */ = { 659 | isa = XCConfigurationList; 660 | buildConfigurations = ( 661 | 7B5A9E792CC86674002FF152 /* Debug */, 662 | 7B5A9E7A2CC86674002FF152 /* Release */, 663 | ); 664 | defaultConfigurationIsVisible = 0; 665 | defaultConfigurationName = Release; 666 | }; 667 | /* End XCConfigurationList section */ 668 | 669 | /* Begin XCLocalSwiftPackageReference section */ 670 | 7B5A9E7B2CC866CA002FF152 /* XCLocalSwiftPackageReference ".." */ = { 671 | isa = XCLocalSwiftPackageReference; 672 | relativePath = ..; 673 | }; 674 | /* End XCLocalSwiftPackageReference section */ 675 | 676 | /* Begin XCSwiftPackageProductDependency section */ 677 | 7B5A9E7C2CC866CA002FF152 /* SwiftSwarm */ = { 678 | isa = XCSwiftPackageProductDependency; 679 | productName = SwiftSwarm; 680 | }; 681 | /* End XCSwiftPackageProductDependency section */ 682 | }; 683 | rootObject = 7B5A9E452CC86672002FF152 /* Project object */; 684 | } 685 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swiftopenai", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/jamesrochabrun/SwiftOpenAI", 7 | "state" : { 8 | "branch" : "main", 9 | "revision" : "e29332ea3e50d9fde6e358d0334226dc77674692" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "1x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "2x", 16 | "size" : "16x16" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "1x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "2x", 26 | "size" : "32x32" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "2x", 36 | "size" : "128x128" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "2x", 46 | "size" : "256x256" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "1x", 51 | "size" : "512x512" 52 | }, 53 | { 54 | "idiom" : "mac", 55 | "scale" : "2x", 56 | "size" : "512x512" 57 | } 58 | ], 59 | "info" : { 60 | "author" : "xcode", 61 | "version" : 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/SwiftSwarmExample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/SwiftSwarmExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftSwarmExampleApp.swift 3 | // SwiftSwarmExample 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct SwiftSwarmExampleApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ApiKeyIntroView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/TeamDemo/Team.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Team.swift 3 | // SwiftSwarmExample 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftOpenAI 10 | import SwiftSwarm 11 | 12 | enum Team: String, AgentRepresentable { 13 | 14 | case engineer = "Engineer" 15 | case designer = "Designer" 16 | case product = "Product" 17 | 18 | var agentDefinition: AgentDefinition { 19 | switch self { 20 | case .engineer: 21 | .init(agent: Agent( 22 | name: self.rawValue, 23 | model: .gpt4o, 24 | instructions: "You are a technical engineer, if user asks about you, you answer with your name \(self.rawValue)", 25 | tools: [])) 26 | case .designer: 27 | .init(agent: Agent( 28 | name: self.rawValue, 29 | model: .gpt4o, 30 | instructions: "You are a UX/UI designer, if user asks about you, you answer with your name \(self.rawValue)", 31 | tools: [])) 32 | case .product: 33 | .init(agent: Agent( 34 | name: self.rawValue, 35 | model: .gpt4o, 36 | instructions: "You are a product manager, if user asks about you, you answer with your name \(self.rawValue)", 37 | tools: [])) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/TeamDemo/TeamDemoResponseHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TeamDemoResponseHandler.swift 3 | // SwiftSwarmExample 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftSwarm 10 | 11 | struct TeamDemoResponseHandler: ToolResponseHandler { 12 | 13 | typealias AgentType = Team 14 | 15 | func handleToolResponseContent( 16 | parameters: [String: Any]) 17 | async throws -> String? 18 | { 19 | return nil 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/TeamDemo/TeamDemoViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TeamDemoViewModel.swift 3 | // SwiftSwarmExample 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftSwarm 10 | import SwiftOpenAI 11 | 12 | @Observable 13 | final class TeamDemoViewModel { 14 | 15 | init(swarm: Swarm) { 16 | self.swarm = swarm 17 | } 18 | 19 | // Swarm is stateless 20 | let swarm: Swarm 21 | 22 | // Array to store the history of all messages 23 | var allMessages: [ChatCompletionParameters.Message] = [] 24 | 25 | // Array for updating SwiftUI list of cells 26 | var cells: [CellViewModel] = [] 27 | 28 | // Track the ID of the current cell being updated 29 | private var currentCellID: UUID? = nil 30 | 31 | // Track the current assistant (agent) 32 | var activeAgent: Agent? = nil 33 | 34 | func startOver() { 35 | cells = [] 36 | allMessages = [] 37 | activeAgent = nil 38 | } 39 | 40 | // Unified function to handle both initial and continued conversations 41 | @MainActor 42 | func handleConversation( 43 | newMessages: [ChatCompletionParameters.Message], 44 | initialAgent: Agent? = nil) 45 | async throws 46 | { 47 | // If there's no active agent, use the provided agent (for the first query), otherwise continue with the current agent 48 | let currentAgent = activeAgent ?? initialAgent 49 | 50 | guard let agent = currentAgent else { 51 | print("Error: No agent provided or available.") 52 | return 53 | } 54 | 55 | // Append new messages to the message history 56 | allMessages.append(contentsOf: newMessages) 57 | 58 | // First, add a new user cell for each new message 59 | for message in newMessages { 60 | let userCell = CellViewModel(content: message.content.text ?? "", role: .user, agentName: agent.name) 61 | cells.append(userCell) 62 | } 63 | 64 | // Run the stream with the updated message history and the current or provided agent 65 | let streamChunks = await swarm.runStream(agent: agent, messages: allMessages) 66 | 67 | for try await streamChunk in streamChunks { 68 | 69 | if let chunkContent = streamChunk.content { 70 | 71 | if let toolCall = streamChunk.toolCalls?.first, 72 | let name = toolCall.function.name, 73 | let agent = Team(rawValue: name) { 74 | cells.append(CellViewModel( 75 | content: "👤 Switching to \(agent.rawValue)", 76 | role: .agent, 77 | agentName: agent.rawValue 78 | )) 79 | } 80 | 81 | // Update or create a new cell with chunk content 82 | if let currentID = currentCellID, let index = cells.firstIndex(where: { $0.id == currentID }) { 83 | cells[index].content += chunkContent 84 | } else { 85 | let newCell = CellViewModel(content: chunkContent, role: .agent, agentName: currentAgent?.name ?? "") 86 | cells.append(newCell) 87 | currentCellID = newCell.id 88 | } 89 | } 90 | 91 | // Check if there's a response from the agent 92 | if let response = streamChunk.response { 93 | // Store the returned messages in the message history 94 | allMessages.append(contentsOf: response.messages) 95 | let newAgent = response.agent 96 | if newAgent.name != currentAgent?.name { 97 | activeAgent = newAgent 98 | } 99 | 100 | // Lastly we need to update the last visible cell. 101 | if let currentID = currentCellID, let index = cells.firstIndex(where: { $0.id == currentID }) { 102 | var cellViewModel = cells[index] 103 | cellViewModel.agentName = activeAgent?.name ?? agent.name 104 | cells[index] = cellViewModel 105 | } 106 | 107 | currentCellID = nil 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/UI/ApiKeyIntroView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApiKeyIntroView.swift 3 | // SwiftSwarmExample 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftOpenAI 10 | import SwiftUI 11 | 12 | struct ApiKeyIntroView: View { 13 | 14 | @State private var apiKey = "" 15 | @State private var organizationIdentifier = "" 16 | @State private var localOrganizationID: String? = nil 17 | 18 | var body: some View { 19 | NavigationStack { 20 | VStack { 21 | Spacer() 22 | VStack(spacing: 24) { 23 | TextField("Enter API Key", text: $apiKey) 24 | TextField("Enter Organization ID (Optional)", text: $organizationIdentifier) 25 | .onChange(of: organizationIdentifier) { _, newValue in 26 | if !newValue.isEmpty { 27 | localOrganizationID = newValue 28 | } 29 | } 30 | } 31 | .padding() 32 | .textFieldStyle(.roundedBorder) 33 | NavigationLink(destination: OptionsListView(openAIService: OpenAIServiceFactory.service(apiKey: apiKey, organizationID: localOrganizationID, debugEnabled: true), options: OptionsListView.DemoOption.allCases)) { 34 | Text("Continue") 35 | .padding() 36 | .padding(.horizontal, 48) 37 | .foregroundColor(.white) 38 | .background( 39 | Capsule() 40 | .foregroundColor(apiKey.isEmpty ? .gray.opacity(0.2) : Color(red: 64/255, green: 195/255, blue: 125/255))) 41 | } 42 | .disabled(apiKey.isEmpty) 43 | Spacer() 44 | Group { 45 | Text("If you don't have a valid API KEY yet, you can visit ") + Text("[this link](https://platform.openai.com/account/api-keys)") + Text(" to get started.") 46 | } 47 | .font(.caption) 48 | } 49 | .padding() 50 | .navigationTitle("Enter OpenAI API KEY") 51 | } 52 | } 53 | } 54 | 55 | #Preview { 56 | ApiKeyIntroView() 57 | } 58 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/UI/CellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellViewModel.swift 3 | // SwiftSwarmExample 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct CellViewModel: Identifiable { 11 | let id = UUID() 12 | var content: String 13 | var role: Role 14 | var agentName: String 15 | 16 | enum Role { 17 | case user 18 | case agent 19 | case tool 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/UI/ChatScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatScreen.swift 3 | // SwiftSwarmExample 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftOpenAI 10 | import SwiftUI 11 | import SwiftSwarm 12 | 13 | struct ChatScreen: View { 14 | 15 | let viewModel: TeamDemoViewModel 16 | 17 | @State private var isProcessing = false 18 | @State private var prompt: String = "" 19 | @Environment(\.dismiss) private var dismiss 20 | 21 | var body: some View { 22 | VStack { 23 | clearButton 24 | List(viewModel.cells) { cell in 25 | VStack(alignment: .leading, spacing: 4) { 26 | if cell.role == .agent { 27 | Text(cell.agentName) 28 | .font(.caption) 29 | .foregroundColor(.blue) 30 | } 31 | Text(cell.content) 32 | } 33 | .padding() 34 | } 35 | .safeAreaInset(edge: .bottom) { 36 | textArea 37 | } 38 | .listStyle(.plain) 39 | } 40 | } 41 | 42 | var clearButton: some View { 43 | HStack { 44 | Spacer() 45 | Button { 46 | Task { 47 | viewModel.startOver() 48 | prompt = "" 49 | } 50 | } label: { 51 | Image(systemName: "trash") 52 | } 53 | Button { 54 | Task { 55 | dismiss() 56 | } 57 | } label: { 58 | Image(systemName: "xmark") 59 | } 60 | } 61 | .padding(.horizontal) 62 | } 63 | 64 | var textArea: some View { 65 | HStack { 66 | TextField(text: $prompt) { 67 | Text("placeholder") 68 | } 69 | Button { 70 | Task { 71 | let message = ChatCompletionParameters.Message(role: .user, content: .text(prompt)) 72 | let agent: Agent = Team.engineer.agent 73 | try await viewModel.handleConversation( 74 | newMessages: [message], 75 | initialAgent: agent) 76 | prompt = "" 77 | } 78 | 79 | } label: { 80 | Text("Send") 81 | } 82 | .disabled(isProcessing) 83 | } 84 | .padding() 85 | .background(.ultraThickMaterial) 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExample/UI/OptionsListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionsListView.swift 3 | // SwiftSwarmExample 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftSwarm 10 | import SwiftOpenAI 11 | import SwiftUI 12 | 13 | struct OptionsListView: View { 14 | 15 | let openAIService: OpenAIService 16 | var options: [DemoOption] 17 | 18 | @State private var selection: DemoOption? = nil 19 | 20 | enum DemoOption: String, CaseIterable, Identifiable { 21 | case team = "Team" 22 | 23 | var id: String { rawValue } 24 | } 25 | 26 | var body: some View { 27 | List(options, id: \.self, selection: $selection) { option in 28 | HStack { 29 | Text(option.rawValue) 30 | Spacer() 31 | Image(systemName: "chevron.right") 32 | } 33 | .sheet(item: $selection) { selection in 34 | VStack { 35 | Text(selection.rawValue) 36 | .font(.largeTitle) 37 | .padding() 38 | switch selection { 39 | case .team: 40 | let swarm = Swarm(client: openAIService, toolResponseHandler: TeamDemoResponseHandler()) 41 | let viewModel = TeamDemoViewModel(swarm: swarm) 42 | ChatScreen(viewModel: viewModel) 43 | .frame(minWidth: 500, minHeight: 500) 44 | } 45 | } 46 | } 47 | } 48 | .listStyle(.inset) 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExampleTests/SwiftSwarmExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftSwarmExampleTests.swift 3 | // SwiftSwarmExampleTests 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import XCTest 9 | 10 | final class SwiftSwarmExampleTests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | } 15 | 16 | override func tearDownWithError() throws { 17 | // Put teardown code here. This method is called after the invocation of each test method in the class. 18 | } 19 | 20 | func testExample() throws { 21 | // This is an example of a functional test case. 22 | // Use XCTAssert and related functions to verify your tests produce the correct results. 23 | // Any test you write for XCTest can be annotated as throws and async. 24 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 25 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 26 | } 27 | 28 | func testPerformanceExample() throws { 29 | // This is an example of a performance test case. 30 | measure { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExampleUITests/SwiftSwarmExampleUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftSwarmExampleUITests.swift 3 | // SwiftSwarmExampleUITests 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import XCTest 9 | 10 | final class SwiftSwarmExampleUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | func testLaunchPerformance() throws { 34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 35 | // This measures how long it takes to launch your application. 36 | measure(metrics: [XCTApplicationLaunchMetric()]) { 37 | XCUIApplication().launch() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SwiftSwarmExample/SwiftSwarmExampleUITests/SwiftSwarmExampleUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftSwarmExampleUITestsLaunchTests.swift 3 | // SwiftSwarmExampleUITests 4 | // 5 | // Created by James Rochabrun on 10/22/24. 6 | // 7 | 8 | import XCTest 9 | 10 | final class SwiftSwarmExampleUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/SwiftSwarmTests/SwiftSwarmTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SwiftSwarm 3 | 4 | final class SwiftSwarmTests: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | --------------------------------------------------------------------------------