├── .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 |
4 |
5 |
6 | [](https://lbesson.mit-license.org/)
7 | [](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 | 
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 |
--------------------------------------------------------------------------------