├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── Sources
└── AISwiftAssist
│ ├── Client
│ ├── Models
│ │ ├── AssistantAndThreadConfig.swift
│ │ ├── AssistantCreationParams.swift
│ │ └── ASAOpenAIModel.swift
│ ├── AISwiftAssistConfig.swift
│ └── AISwiftAssistClient.swift
│ ├── Extensions
│ ├── Int+localStatusCode.swift
│ ├── URLComponents.swift
│ ├── Data+decode.swift
│ └── Utils.swift
│ ├── Base
│ ├── HTTPRequestMethods.swift
│ ├── Constants.swift
│ ├── Endpoint.swift
│ ├── HTTPRequestError.swift
│ └── HTTPClient.swift
│ ├── Models
│ ├── Main
│ │ ├── Message
│ │ │ ├── ASAImageContent.swift
│ │ │ ├── ASATextContent.swift
│ │ │ ├── ASAMessageContent.swift
│ │ │ └── ASAMessage.swift
│ │ ├── ASAModel.swift
│ │ ├── ASAAssistantFile.swift
│ │ ├── ASAMessageFile.swift
│ │ ├── ASAThread.swift
│ │ ├── ASAFile.swift
│ │ ├── ASAAssistant.swift
│ │ ├── ASARun.swift
│ │ └── ASARunStep.swift
│ ├── Request
│ │ ├── ASAModifyRunRequest.swift
│ │ ├── ASAModifyThreadRequest.swift
│ │ ├── ASAModifyMessageRequest.swift
│ │ ├── ASACreateAssistantFileRequest.swift
│ │ ├── ASAToolOutput.swift
│ │ ├── ASAListRunStepsParameters.swift
│ │ ├── ASAListRunsParameters.swift
│ │ ├── ASAListMessagesParameters.swift
│ │ ├── ASACreateThreadRunRequest.swift
│ │ ├── ASAListAssistantsParameters.swift
│ │ ├── ASACreateMessageRequest.swift
│ │ ├── ASACreateThreadRequest.swift
│ │ ├── ASAModifyAssistantRequest.swift
│ │ ├── ASACreateRunRequest.swift
│ │ └── ASACreateAssistantRequest.swift
│ └── Response
│ │ ├── ASAModelsListResponse.swift
│ │ ├── ASADeleteModelResponse.swift
│ │ ├── ASARunStepsListResponse.swift
│ │ ├── ASAMessageFilesListResponse.swift
│ │ ├── ASARunsListResponse.swift
│ │ ├── ASAAssistantFilesListResponse.swift
│ │ ├── ASAMessagesListResponse.swift
│ │ └── ASAAssistantsListResponse.swift
│ ├── Endpoints
│ ├── ModelsEndpoint.swift
│ ├── ThreadsEndpoint.swift
│ ├── MessagesEndpoint.swift
│ ├── AssistantEndpoint.swift
│ └── RunsEndpoint.swift
│ └── APIs
│ ├── ModelsAPI.swift
│ ├── ThreadsAPI.swift
│ ├── MessagesAPI.swift
│ ├── AssistantsAPI.swift
│ └── RunsAPI.swift
├── Package.swift
├── Tests
└── AISwiftAssistTests
│ ├── Mosks
│ ├── ThreadsMocks.swift
│ ├── ModelsMocks.swift
│ ├── AssistantMocks.swift
│ ├── RunsMocks.swift
│ └── MessagesMocks.swift
│ ├── Resourses
│ └── MockURLProtocol.swift
│ └── APIs
│ ├── ModelsAPITests.swift
│ ├── ThreadsAPITests.swift
│ ├── AssistantsAPITests.swift
│ ├── MessagesAPITests.swift
│ └── RunsAPITests.swift
├── .gitignore
├── README.md
└── LICENSE
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Client/Models/AssistantAndThreadConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/17/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct AssistantAndThreadConfig {
11 | public let assistant: ASAAssistant
12 | public let thread: ASAThread
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Extensions/Int+localStatusCode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Int {
11 | var localStatusCode: String {
12 | return HTTPURLResponse.localizedString(forStatusCode: self)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Base/HTTPRequestMethods.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum HTTPRequestMethods: String {
11 | case get = "GET"
12 | case post = "POST"
13 | case put = "PUT"
14 | case delete = "DELETE"
15 | case path = "PATCH"
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Extensions/URLComponents.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension URLComponents {
11 | static var `default`: Self {
12 | var components: Self = .init()
13 | components.scheme = Constants.baseScheme
14 | components.host = Constants.baseHost
15 | return components
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Extensions/Data+decode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Data {
11 | func decode(model: T.Type) throws -> T {
12 | do {
13 | return try JSONDecoder().decode(model, from: self)
14 | } catch {
15 | print(error)
16 | throw error
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Base/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum Constants {
11 | public static var baseScheme: String = "https"
12 | public static var baseHost: String = "api.openai.com"
13 | public static var path: String = "/v1/"
14 | public static var apiKey: String = ""
15 | public static var organizationId: String?
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/Message/ASAImageContent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/13/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents an image file in the content of a message.
11 | public struct ASAImageContent: Codable {
12 | /// The File ID of the image in the message content.
13 | public let file_id: String
14 |
15 | enum CodingKeys: String, CodingKey {
16 | case file_id
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Client/AISwiftAssistConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/16/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct AISwiftAssistConfig {
11 |
12 | public let apiKey: String
13 | public let organizationId: String?
14 |
15 | public init(apiKey: String,
16 | organizationId: String? = nil) {
17 | self.apiKey = apiKey
18 | self.organizationId = organizationId
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASAModifyRunRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/16/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A request structure for modifying a run.
11 | public struct ASAModifyRunRequest: Codable {
12 | /// Optional: Set of 16 key-value pairs that can be attached to the run.
13 | public let metadata: [String: String]?
14 |
15 | enum CodingKeys: String, CodingKey {
16 | case metadata
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASAModifyThreadRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A request structure for modifying a thread.
11 | public struct ASAModifyThreadRequest: Codable {
12 | /// Optional: Set of 16 key-value pairs that can be attached to the thread.
13 | public let metadata: [String: String]?
14 |
15 | enum CodingKeys: String, CodingKey {
16 | case metadata
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Response/ASAModelsListResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/17/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct ASAModelsListResponse: Codable {
11 |
12 | /// The object type, which is always "list".
13 | public let object: String
14 |
15 | /// The deletion status of the model.
16 | public let data: [ASAModel]
17 |
18 | enum CodingKeys: String, CodingKey {
19 | case object, data
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Response/ASADeleteModelResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct ASADeleteModelResponse: Codable {
11 | /// The model identifier, which can be referenced in the API endpoints.
12 | public let id: String
13 |
14 | /// The object type, which is always "model".
15 | public let object: String
16 |
17 | /// The deletion status of the model.
18 | public let deleted: Bool
19 |
20 | enum CodingKeys: String, CodingKey {
21 | case id, object, deleted
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASAModifyMessageRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A request structure for modifying a message in a thread.
11 | public struct ASAModifyMessageRequest: Codable {
12 |
13 | /// Optional: Set of 16 key-value pairs that can be attached to the message.
14 | public let metadata: [String: String]?
15 |
16 | enum CodingKeys: String, CodingKey {
17 | case metadata
18 | }
19 |
20 | public init(metadata: [String : String]? = nil) {
21 | self.metadata = metadata
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASACreateAssistantFileRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a request to create an assistant file.
11 | public struct ASACreateAssistantFileRequest: Codable {
12 | /// A File ID (with purpose="assistants") that the assistant should use.
13 | /// Useful for tools like retrieval and code_interpreter that can access files.
14 | public let fileId: String
15 |
16 | public init(fileId: String) {
17 | self.fileId = fileId
18 | }
19 |
20 | enum CodingKeys: String, CodingKey {
21 | case fileId = "file_id"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASAToolOutput.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a tool output for submission.
11 | public struct ASAToolOutput: Codable {
12 | /// The ID of the tool call.
13 | public let toolCallId: String
14 |
15 | /// The output of the tool call.
16 | public let output: String
17 |
18 | public init(toolCallId: String, output: String) {
19 | self.toolCallId = toolCallId
20 | self.output = output
21 | }
22 |
23 | enum CodingKeys: String, CodingKey {
24 | case toolCallId = "tool_call_id"
25 | case output
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/ASAModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct ASAModel: Codable {
11 | // The model identifier, which can be referenced in the API endpoints.
12 | public let id: String
13 |
14 | // The Unix timestamp (in seconds) when the model was created.
15 | public let created: Int
16 |
17 | // The object type, which is always "model".
18 | public let object: String
19 |
20 | // The organization that owns the model.
21 | public let ownedBy: String
22 |
23 | enum CodingKeys: String, CodingKey {
24 | case id, created, object
25 | case ownedBy = "owned_by"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Response/ASARunStepsListResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a response containing a list of run steps.
11 | public struct ASARunStepsListResponse: Codable {
12 | /// The object type, which is always 'list'.
13 | public let object: String
14 |
15 | /// The list of run steps.
16 | public let data: [ASARunStep]
17 |
18 | /// Additional details about the list.
19 | public let hasMore: Bool
20 | public let firstId: String?
21 | public let lastId: String?
22 |
23 | enum CodingKeys: String, CodingKey {
24 | case data, object, hasMore = "has_more", firstId = "first_id", lastId = "last_id"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/ASAAssistantFile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents an assistant file that can be used by the assistant.
11 | public struct ASAAssistantFile: Codable {
12 | /// The identifier of the assistant file.
13 | public let id: String
14 |
15 | /// The object type, which is always 'assistant.file'.
16 | public let objectType: String
17 |
18 | /// The Unix timestamp (in seconds) for when the assistant file was created.
19 | public let createdAt: Int
20 |
21 | /// The identifier of the assistant to which this file belongs.
22 | public let assistantId: String
23 |
24 | enum CodingKeys: String, CodingKey {
25 | case id, objectType = "object", createdAt = "created_at", assistantId = "assistant_id"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/ASAMessageFile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a file attached to a message.
11 | public struct ASAMessageFile: Codable {
12 | /// The identifier of the file, which can be referenced in API endpoints.
13 | public let id: String
14 |
15 | /// The object type, which is always 'thread.message.file'.
16 | public let object: String
17 |
18 | /// The Unix timestamp (in seconds) for when the message file was created.
19 | public let createdAt: Int
20 |
21 | /// The ID of the message that the file is attached to.
22 | public let messageId: String
23 |
24 | enum CodingKeys: String, CodingKey {
25 | case id, object
26 | case createdAt = "created_at"
27 | case messageId = "message_id"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASAListRunStepsParameters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Parameters for listing run steps in a thread.
11 | public struct ASAListRunStepsParameters: Codable {
12 | /// A limit on the number of objects to be returned.
13 | public let limit: Int?
14 |
15 | /// Sort order by the created_at timestamp of the objects.
16 | public let order: String?
17 |
18 | /// A cursor for use in pagination.
19 | public let after: String?
20 |
21 | /// A cursor for use in pagination.
22 | public let before: String?
23 |
24 | public init(limit: Int? = nil, order: String? = nil, after: String? = nil, before: String? = nil) {
25 | self.limit = limit
26 | self.order = order
27 | self.after = after
28 | self.before = before
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Response/ASAMessageFilesListResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a response containing a list of assistant files.
11 | public struct ASAMessageFilesListResponse: Codable {
12 | /// The object type, which is always 'list'.
13 | public let object: String
14 |
15 | /// The list of assistant files.
16 | public let data: [ASAMessageFile]
17 |
18 | /// The ID of the first file in the list.
19 | public let firstId: String
20 |
21 | /// The ID of the last file in the list.
22 | public let lastId: String
23 |
24 | /// Boolean indicating if there are more files available.
25 | public let hasMore: Bool
26 |
27 | enum CodingKeys: String, CodingKey {
28 | case data, firstId = "first_id", lastId = "last_id", hasMore = "has_more", object
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Response/ASARunsListResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File 2.swift
3 | //
4 | //
5 | // Created by Alexey on 11/17/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a response containing a list of runs.
11 | public struct ASARunsListResponse: Codable {
12 | /// The object type, which is always 'list'.
13 | public let object: String
14 |
15 | /// The list of runs.
16 | public let data: [ASARun]
17 |
18 | /// The ID of the first run in the list.
19 | public let firstId: String
20 |
21 | /// The ID of the last run in the list.
22 | public let lastId: String
23 |
24 | /// Indicates whether there are more runs to fetch.
25 | public let hasMore: Bool
26 |
27 | enum CodingKeys: String, CodingKey {
28 | case object, data
29 | case firstId = "first_id"
30 | case lastId = "last_id"
31 | case hasMore = "has_more"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Response/ASAAssistantFilesListResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a response containing a list of assistant files.
11 | public struct ASAAssistantFilesListResponse: Codable {
12 | /// The object type, which is always 'list'.
13 | public let object: String
14 |
15 | /// The list of assistant files.
16 | public let data: [ASAAssistantFile]
17 |
18 | /// The ID of the first file in the list.
19 | public let firstId: String
20 |
21 | /// The ID of the last file in the list.
22 | public let lastId: String
23 |
24 | /// Boolean indicating if there are more files available.
25 | public let hasMore: Bool
26 |
27 | enum CodingKeys: String, CodingKey {
28 | case data, firstId = "first_id", lastId = "last_id", hasMore = "has_more", object
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASAListRunsParameters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/17/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Parameters for listing runs in a thread.
11 | public struct ASAListRunsParameters: Codable {
12 |
13 | /// Optional: A limit on the number of objects to be returned.
14 | public let limit: Int?
15 |
16 | /// Optional: Sort order by the created_at timestamp of the objects.
17 | public let order: String?
18 |
19 | /// Optional: A cursor for use in pagination.
20 | public let after: String?
21 |
22 | /// Optional: A cursor for use in pagination.
23 | public let before: String?
24 |
25 | public init(limit: Int? = nil, order: String? = nil, after: String? = nil, before: String? = nil) {
26 | self.limit = limit
27 | self.order = order
28 | self.after = after
29 | self.before = before
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Response/ASAMessagesListResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/16/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a response containing a list of messages.
11 | public struct ASAMessagesListResponse: Codable {
12 | /// The object type, which is always 'list'.
13 | public let object: String
14 |
15 | /// The list of messages.
16 | public let data: [ASAMessage]
17 |
18 | /// The ID of the first message in the list.
19 | public let firstId: String
20 |
21 | /// The ID of the last message in the list.
22 | public let lastId: String
23 |
24 | /// Indicates whether there are more messages to fetch.
25 | public let hasMore: Bool
26 |
27 | enum CodingKeys: String, CodingKey {
28 | case object, data
29 | case firstId = "first_id"
30 | case lastId = "last_id"
31 | case hasMore = "has_more"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Response/ASAAssistantsListResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A response structure for listing assistants.
11 | public struct ASAAssistantsListResponse: Codable {
12 | /// The object type, which is always 'list'.
13 | public let object: String
14 |
15 | /// The list of assistant objects.
16 | public let data: [ASAAssistant]
17 |
18 | /// The ID of the first assistant in the list.
19 | public let firstId: String
20 |
21 | /// The ID of the last assistant in the list.
22 | public let lastId: String
23 |
24 | /// Boolean indicating whether there are more objects after the last one in the list.
25 | public let hasMore: Bool
26 |
27 | enum CodingKeys: String, CodingKey {
28 | case object, data
29 | case firstId = "first_id"
30 | case lastId = "last_id"
31 | case hasMore = "has_more"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/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: "AISwiftAssist",
8 | platforms: [
9 | .iOS(.v13),
10 | .watchOS(.v8)
11 | ],
12 | products: [
13 | // Products define the executables and libraries a package produces, making them visible to other packages.
14 | .library(
15 | name: "AISwiftAssist",
16 | targets: ["AISwiftAssist"]),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package, defining a module or a test suite.
20 | // Targets can depend on other targets in this package and products from dependencies.
21 | .target(
22 | name: "AISwiftAssist"),
23 | .testTarget(
24 | name: "AISwiftAssistTests",
25 | dependencies: ["AISwiftAssist"]
26 | ),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Client/Models/AssistantCreationParams.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/17/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct AssistantCreationParams {
11 |
12 | public let model: ASAOpenAIModel
13 | public let name: String
14 | public let description: String
15 | public let instructions: String
16 | public let tools: [ASACreateAssistantRequest.Tool]?
17 | public let fileIds: [String]?
18 | public let metadata: [String: String]?
19 |
20 | public init(model: ASAOpenAIModel, name: String, description: String, instructions: String, tools: [ASACreateAssistantRequest.Tool]? = nil, fileIds: [String]? = nil, metadata: [String : String]? = nil) {
21 | self.model = model
22 | self.name = name
23 | self.description = description
24 | self.instructions = instructions
25 | self.tools = tools
26 | self.fileIds = fileIds
27 | self.metadata = metadata
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/ASAThread.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 | /// Represents a thread that contains messages.
10 | public struct ASAThread: Codable {
11 | /// The identifier of the thread, which can be referenced in API endpoints.
12 | public let id: String
13 |
14 | /// The object type, which is always 'thread'.
15 | public let object: String
16 |
17 | /// The Unix timestamp (in seconds) for when the thread was created.
18 | public let createdAt: Int
19 |
20 | /// Optional: Set of 16 key-value pairs that can be attached to the thread.
21 | /// This can be useful for storing additional information about the thread in a structured format.
22 | /// Keys can be a maximum of 64 characters long, and values can be a maximum of 512 characters long.
23 | public let metadata: [String: String]?
24 |
25 | enum CodingKeys: String, CodingKey {
26 | case id, object
27 | case createdAt = "created_at"
28 | case metadata
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/ASAFile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a document that has been uploaded to OpenAI.
11 | public struct ASAFile: Codable {
12 | /// The file identifier, which can be referenced in the API endpoints.
13 | public let id: String
14 |
15 | /// The size of the file, in bytes.
16 | public let bytes: Int
17 |
18 | /// The Unix timestamp (in seconds) for when the file was created.
19 | public let createdAt: Int
20 |
21 | /// The name of the file.
22 | public let filename: String
23 |
24 | /// The object type, which is always 'file'.
25 | public let object: String
26 |
27 | /// The intended purpose of the file. Supported values are 'fine-tune', 'fine-tune-results', 'assistants', and 'assistants_output'.
28 | public let purpose: String
29 |
30 | enum CodingKeys: String, CodingKey {
31 | case id, bytes
32 | case createdAt = "created_at"
33 | case filename, object, purpose
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASAListMessagesParameters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Parameters for listing messages in a thread.
11 | public struct ASAListMessagesParameters: Codable {
12 |
13 | /// Optional: A limit on the number of objects to be returned. Limit can range between 1 and 100.
14 | public let limit: Int?
15 |
16 | /// Optional: Sort order by the created_at timestamp of the objects. 'asc' for ascending order and 'desc' for descending order.
17 | public let order: String?
18 |
19 | /// Optional: A cursor for use in pagination. 'after' is an object ID that defines your place in the list.
20 | public let after: String?
21 |
22 | /// Optional: A cursor for use in pagination. 'before' is an object ID that defines your place in the list.
23 | public let before: String?
24 |
25 | public init(limit: Int? = nil, order: String? = nil, after: String? = nil, before: String? = nil) {
26 | self.limit = limit
27 | self.order = order
28 | self.after = after
29 | self.before = before
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/Mosks/ThreadsMocks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/20/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension ThreadsAPITests {
11 | static let create: String =
12 | """
13 | {
14 | "id": "thread_abc123",
15 | "object": "thread",
16 | "created_at": 1699012949,
17 | "metadata": {}
18 | }
19 | """
20 |
21 | static let retrieve: String =
22 | """
23 | {
24 | "id": "thread_abc123",
25 | "object": "thread",
26 | "created_at": 1699014083,
27 | "metadata": {}
28 | }
29 | """
30 |
31 | static let modify: String =
32 | """
33 | {
34 | "id": "thread_abc123",
35 | "object": "thread",
36 | "created_at": 1699014083,
37 | "metadata": {
38 | "modified": "true",
39 | "user": "abc123"
40 | }
41 | }
42 | """
43 |
44 | static let delete: String =
45 | """
46 | {
47 | "id": "thread_abc123",
48 | "object": "thread.deleted",
49 | "deleted": true
50 | }
51 | """
52 | }
53 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASACreateThreadRunRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A request structure for creating a thread and run.
11 | public struct ASACreateThreadRunRequest: Codable {
12 | /// The ID of the assistant to use to execute this run.
13 | public let assistantId: String
14 |
15 | /// The thread object containing messages and other parameters.
16 | public let thread: Thread
17 |
18 | /// Represents a thread containing messages and other parameters.
19 | public struct Thread: Codable {
20 | /// The messages to be processed in this thread.
21 | public let messages: [Message]
22 |
23 | /// Represents a single message in a thread.
24 | public struct Message: Codable {
25 | /// The role of the message sender, e.g., 'user' or 'system'.
26 | public let role: String
27 |
28 | /// The content of the message.
29 | public let content: String
30 | }
31 | }
32 |
33 | public init(assistantId: String, thread: Thread) {
34 | self.assistantId = assistantId
35 | self.thread = thread
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASAListAssistantsParameters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Parameters for listing assistants.
11 | public struct ASAListAssistantsParameters: Encodable {
12 |
13 | /// Optional: A limit on the number of objects to be returned. Can range between 1 and 100. Defaults to 20.
14 | public let limit: Int?
15 |
16 | /// Optional: Sort order by the created_at timestamp of the objects. 'asc' for ascending order and 'desc' for descending order. Defaults to 'desc'.
17 | public let order: String?
18 |
19 | /// Optional: A cursor for use in pagination. 'after' is an object ID that defines your place in the list, to fetch the next page of the list.
20 | public let after: String?
21 |
22 | /// Optional: A cursor for use in pagination. 'before' is an object ID that defines your place in the list, to fetch the previous page of the list.
23 | public let before: String?
24 |
25 | public init(limit: Int? = nil, order: String? = nil, after: String? = nil, before: String? = nil) {
26 | self.limit = limit
27 | self.order = order
28 | self.after = after
29 | self.before = before
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/Message/ASATextContent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/13/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents the text content of a message.
11 | public struct ASATextContent: Codable {
12 | /// The value of the text.
13 | public let value: String
14 |
15 | /// Optional: Annotations
16 | public let annotations: [ASAAnnotation]?
17 | }
18 |
19 | public struct ASAAnnotation: Codable {
20 | let type: String
21 | let text: String
22 | let startIndex: Int
23 | let endIndex: Int
24 | let fileCitation: ASAFileCitation?
25 | let filePath: ASAFilePath?
26 |
27 | enum CodingKeys: String, CodingKey {
28 | case type, text, startIndex = "start_index", endIndex = "end_index", fileCitation = "file_citation", filePath = "file_path"
29 | }
30 | }
31 |
32 | public struct ASAFileCitation: Codable {
33 | public let fileId: String
34 | public let quote: String
35 |
36 | enum CodingKeys: String, CodingKey {
37 | case fileId = "file_id", quote
38 | }
39 | }
40 |
41 | public struct ASAFilePath: Codable {
42 | public let fileId: String
43 |
44 | enum CodingKeys: String, CodingKey {
45 | case fileId = "file_id"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASACreateMessageRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A request structure for creating a message in a thread.
11 | public struct ASACreateMessageRequest: Codable {
12 |
13 | /// The role of the entity that is creating the message. Currently only 'user' is supported.
14 | public let role: String
15 |
16 | /// The content of the message.
17 | public let content: String
18 |
19 | /// Optional: A list of File IDs that the message should use. A maximum of 10 files can be attached to a message.
20 | public let fileIds: [String]?
21 |
22 | /// Optional: Set of 16 key-value pairs that can be attached to the message. Useful for storing additional information.
23 | public let metadata: [String: String]?
24 |
25 | enum CodingKeys: String, CodingKey {
26 | case role, content
27 | case fileIds = "file_ids"
28 | case metadata
29 | }
30 |
31 | public init(role: String, content: String, fileIds: [String]? = nil, metadata: [String : String]? = nil) {
32 | self.role = role
33 | self.content = content
34 | self.fileIds = fileIds
35 | self.metadata = metadata
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASACreateThreadRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A request structure for creating a thread.
11 | public struct ASACreateThreadRequest: Codable {
12 | /// Optional: A list of messages to start the thread with.
13 | public let messages: [Message]?
14 |
15 | public struct Message: Codable {
16 | /// Required: The role of the entity that is creating the message. Currently, only 'user' is supported.
17 | public let role: String
18 |
19 | /// Required: The content of the message.
20 | public let content: String
21 |
22 | /// Optional: A list of File IDs that the message should use. A maximum of 10 files can be attached to a message.
23 | public let fileIds: [String]?
24 |
25 | /// Optional: Set of 16 key-value pairs that can be attached to the message. Useful for storing additional information.
26 | public let metadata: [String: String]?
27 |
28 | enum CodingKeys: String, CodingKey {
29 | case role, content
30 | case fileIds = "file_ids"
31 | case metadata
32 | }
33 | }
34 |
35 | public init(messages: [Message]? = nil) {
36 | self.messages = messages
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/Mosks/ModelsMocks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/20/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension ModelsAPITests {
11 | static let list: String =
12 | """
13 | {
14 | "object": "list",
15 | "data": [
16 | {
17 | "id": "model-id-0",
18 | "object": "model",
19 | "created": 1686935002,
20 | "owned_by": "organization-owner"
21 | },
22 | {
23 | "id": "model-id-1",
24 | "object": "model",
25 | "created": 1686935002,
26 | "owned_by": "organization-owner"
27 | },
28 | {
29 | "id": "model-id-2",
30 | "object": "model",
31 | "created": 1686935002,
32 | "owned_by": "openai"
33 | }
34 | ]
35 | }
36 | """
37 |
38 | static let retrieve: String =
39 | """
40 | {
41 | "id": "gpt-3.5-turbo-instruct",
42 | "object": "model",
43 | "created": 1686935002,
44 | "owned_by": "openai"
45 | }
46 | """
47 |
48 | static let delete: String =
49 | """
50 | {
51 | "id": "ft:gpt-3.5-turbo:acemeco:suffix:abc123",
52 | "object": "model",
53 | "deleted": true
54 | }
55 | """
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Base/Endpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol Endpoint {
11 | var url: URL? { get }
12 | var method: HTTPRequestMethods { get }
13 | var header: [String: String]? { get }
14 | var body: BodyInfo? { get }
15 | }
16 |
17 | protocol CustomEndpoint: Endpoint {
18 | var queryItems: [URLQueryItem]? { get }
19 | var path: String { get }
20 | }
21 |
22 | struct BodyInfo {
23 | private let bodyDict: [String: String]?
24 | private let bodyData: Data?
25 |
26 | public var data: Data? {
27 | if let bodyDict = bodyDict {
28 | return try? JSONEncoder().encode(bodyDict)
29 | }
30 | if let bodyData = bodyData {
31 | return bodyData
32 | }
33 | return nil
34 | }
35 |
36 | public init(bodyDict: [String: String]?) {
37 | self.bodyData = nil
38 | self.bodyDict = bodyDict
39 | }
40 |
41 | public init(bodyData: Data?) {
42 | self.bodyDict = nil
43 | self.bodyData = bodyData
44 | }
45 |
46 | public init(object: T) {
47 | self.bodyDict = nil
48 | self.bodyData = try? JSONEncoder().encode(object)
49 | }
50 |
51 | /// If you need to initialize body with empty parameters
52 | public init() {
53 | self.bodyData = nil
54 | self.bodyDict = nil
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Endpoints/ModelsEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ModelsEndpoint {
11 | case getModels
12 | case retrieveModel(String)
13 | case deleteModel(String)
14 | }
15 |
16 | extension ModelsEndpoint: CustomEndpoint {
17 | public var url: URL? {
18 | var urlComponents: URLComponents = .default
19 | urlComponents.queryItems = queryItems
20 | urlComponents.path = Constants.path + path
21 | return urlComponents.url
22 | }
23 |
24 | public var queryItems: [URLQueryItem]? {
25 | var items: [URLQueryItem]?
26 | switch self {
27 | case .getModels, .retrieveModel, .deleteModel: items = nil
28 | }
29 | return items
30 | }
31 |
32 | public var path: String {
33 | switch self {
34 | case .getModels: return "models"
35 | case .retrieveModel(let modelId): return "models/\(modelId)"
36 | case .deleteModel(let modelId): return "models/\(modelId)"
37 | }
38 | }
39 |
40 | public var method: HTTPRequestMethods {
41 | switch self {
42 | case .getModels, .retrieveModel: return .get
43 | case .deleteModel: return .delete
44 | }
45 | }
46 |
47 | public var header: [String : String]? {
48 | switch self {
49 | case .getModels, .retrieveModel, .deleteModel: nil
50 | }
51 | }
52 |
53 | public var body: BodyInfo? {
54 | switch self {
55 | case .getModels, .retrieveModel, .deleteModel: return nil
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/Message/ASAMessageContent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/13/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum ASAMessageContent: Codable {
11 | case image(ASAImageContent)
12 | case text(ASATextContent)
13 |
14 | enum CodingKeys: String, CodingKey {
15 | case type, imageFile = "image_file", text
16 | }
17 |
18 | public init(from decoder: Decoder) throws {
19 | let container = try decoder.container(keyedBy: CodingKeys.self)
20 | let type = try container.decode(String.self, forKey: .type)
21 |
22 | switch type {
23 | case "image_file":
24 | let imageContent = try container.decode(ASAImageContent.self, forKey: .imageFile)
25 | self = .image(imageContent)
26 | case "text":
27 | let textContent = try container.decode(ASATextContent.self, forKey: .text)
28 | self = .text(textContent)
29 | default:
30 | throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Invalid type")
31 | }
32 | }
33 |
34 | public func encode(to encoder: Encoder) throws {
35 | var container = encoder.container(keyedBy: CodingKeys.self)
36 |
37 | switch self {
38 | case .image(let imageContent):
39 | try container.encode("image_file", forKey: .type)
40 | try container.encode(imageContent, forKey: .imageFile)
41 | case .text(let textContent):
42 | try container.encode("text", forKey: .type)
43 | try container.encode(textContent, forKey: .text)
44 | }
45 | }
46 | }
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/Resourses/MockURLProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/19/23.
6 | //
7 |
8 | import Foundation
9 |
10 | class MockURLProtocol: URLProtocol {
11 | // Handler to test the request and return a mock response.
12 | static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data?))?
13 |
14 | override class func canInit(with request: URLRequest) -> Bool {
15 | // To check if this protocol can handle the given request.
16 | return true
17 | }
18 |
19 | override class func canonicalRequest(for request: URLRequest) -> URLRequest {
20 | // Return the original request as the canonical version.
21 | return request
22 | }
23 |
24 | override func startLoading() {
25 | guard let handler = MockURLProtocol.requestHandler else {
26 | fatalError("Handler is unavailable.")
27 | }
28 |
29 | do {
30 | // Call handler with received request and capture the tuple of response and data.
31 | let (response, data) = try handler(request)
32 |
33 | // Send received response to the client.
34 | client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
35 |
36 | if let data = data {
37 | // Send received data to the client.
38 | client?.urlProtocol(self, didLoad: data)
39 | }
40 |
41 | // Notify request has been finished.
42 | client?.urlProtocolDidFinishLoading(self)
43 | } catch {
44 | // Notify received error.
45 | client?.urlProtocol(self, didFailWithError: error)
46 | }
47 | }
48 |
49 | override func stopLoading() {
50 | // This is called if the request gets canceled or completed.
51 | }
52 | }
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASAModifyAssistantRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 | /// A request structure for modifying an existing assistant.
10 | public struct ASAModifyAssistantRequest: Codable {
11 |
12 | /// Optional: ID of the model to use.
13 | public let model: String?
14 |
15 | /// Optional: The name of the assistant. Maximum length is 256 characters.
16 | public let name: String?
17 |
18 | /// Optional: The description of the assistant. Maximum length is 512 characters.
19 | public let description: String?
20 |
21 | /// Optional: The system instructions that the assistant uses. Maximum length is 32768 characters.
22 | public let instructions: String?
23 |
24 | /// Optional: A list of tools enabled on the assistant. Maximum of 128 tools per assistant.
25 | public let tools: [ASAAssistant.Tool]?
26 |
27 | /// Optional: A list of file IDs attached to the assistant. Maximum of 20 files attached to the assistant.
28 | public let fileIds: [String]?
29 |
30 | /// Optional: A map of key-value pairs for storing additional information. Maximum of 16 pairs.
31 | public let metadata: [String: String]?
32 |
33 | enum CodingKeys: String, CodingKey {
34 | case model, name, description, instructions, tools
35 | case fileIds = "file_ids"
36 | case metadata
37 | }
38 |
39 | public init(model: String? = nil, name: String? = nil, description: String? = nil, instructions: String? = nil, tools: [ASAAssistant.Tool]? = nil, fileIds: [String]? = nil, metadata: [String : String]? = nil) {
40 | self.model = model
41 | self.name = name
42 | self.description = description
43 | self.instructions = instructions
44 | self.tools = tools
45 | self.fileIds = fileIds
46 | self.metadata = metadata
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/Message/ASAMessage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a message within a thread.
11 | public struct ASAMessage: Codable {
12 | /// The identifier of the message, which can be referenced in API endpoints.
13 | public let id: String
14 |
15 | /// The object type, which is always 'thread.message'.
16 | public let object: String
17 |
18 | /// The Unix timestamp (in seconds) for when the message was created.
19 | public let createdAt: Int
20 |
21 | /// The thread ID that this message belongs to.
22 | public let threadId: String
23 |
24 | /// The entity that produced the message. One of 'user' or 'assistant'.
25 | public let role: String
26 |
27 | /// The content of the message in array of text and/or images.
28 | public let content: [ASAMessageContent]
29 |
30 | /// If applicable, the ID of the assistant that authored this message.
31 | public let assistantId: String?
32 |
33 | /// If applicable, the ID of the run associated with the authoring of this message.
34 | public let runId: String?
35 |
36 | /// A list of file IDs that the assistant should use. Useful for tools like retrieval and code_interpreter that can access files.
37 | /// A maximum of 10 files can be attached to a message.
38 | public let fileIds: [String]
39 |
40 | /// Optional: Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information
41 | /// about the object in a structured format. Keys can be a maximum of 64 characters long, and values can be a maximum of 512 characters long.
42 | public let metadata: [String: String]?
43 |
44 | enum CodingKeys: String, CodingKey {
45 | case id, object
46 | case createdAt = "created_at"
47 | case threadId = "thread_id"
48 | case role, content
49 | case assistantId = "assistant_id"
50 | case runId = "run_id"
51 | case fileIds = "file_ids"
52 | case metadata
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Extensions/Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | final class Utils {
11 | /// The method creates a [URLQueryItem] object from the passed structure, signed under the Encodable protocol.
12 | /// - Parameter object: Object of the structure signed under the Encodable protocol.
13 | /// - Returns: [URLQueryItem] object.
14 | static func createURLQueryItems(from object: T) -> [URLQueryItem]? {
15 | do {
16 | let json = try JSONEncoder().encode(object)
17 | let dictionary = try JSONSerialization.jsonObject(with: json, options: []) as? [String: Any]
18 | return dictionary?.map { URLQueryItem(name: $0, value: "\($1)") }
19 | } catch {
20 | return nil
21 | }
22 | }
23 |
24 | static func extractDomain(from url: String) -> (String?, String?, Int?) {
25 | let pattern = "(https?)://([^/]+)"
26 | do {
27 | let regex = try NSRegularExpression(pattern: pattern, options: [])
28 | let matches = regex.matches(in: url, options: [], range: NSRange(location: 0, length: url.utf16.count))
29 |
30 | if let match = matches.first {
31 | let schemeRange = match.range(at: 1)
32 | let hostRange = match.range(at: 2)
33 |
34 | let scheme = Range(schemeRange, in: url).map { String(url[$0]) }
35 | let fullHost = Range(hostRange, in: url).map { String(url[$0]) }
36 |
37 | if let fullHost = fullHost {
38 | let hostComponents = fullHost.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
39 | let host = String(hostComponents[0])
40 | let port = hostComponents.count > 1 ? Int(hostComponents[1]) : nil
41 | return (scheme, host, port)
42 | }
43 | }
44 | } catch {
45 | print("Invalid regular expression")
46 | }
47 | return (nil, nil, nil)
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Endpoints/ThreadsEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ThreadsEndpoint {
11 | case createThread(ASACreateThreadRequest?)
12 | case retrieveThread(String)
13 | case modifyThread(String, ASAModifyThreadRequest)
14 | case deleteThread(String)
15 |
16 | }
17 |
18 | extension ThreadsEndpoint: CustomEndpoint {
19 | public var url: URL? {
20 | var urlComponents: URLComponents = .default
21 | urlComponents.queryItems = queryItems
22 | urlComponents.path = Constants.path + path
23 | return urlComponents.url
24 | }
25 |
26 | public var queryItems: [URLQueryItem]? {
27 | var items: [URLQueryItem]?
28 | switch self {
29 | case .createThread, .retrieveThread, .deleteThread, .modifyThread: items = nil
30 | }
31 | return items
32 | }
33 |
34 | public var path: String {
35 | switch self {
36 | case .createThread: return "threads"
37 | case .retrieveThread(let threadId): return "threads/\(threadId)"
38 | case .modifyThread(let threadId, _): return "threads/\(threadId)"
39 | case .deleteThread(let threadId): return "threads/\(threadId)"
40 | }
41 | }
42 |
43 | public var method: HTTPRequestMethods {
44 | switch self {
45 | case .createThread: return .post
46 | case .retrieveThread: return .get
47 | case .modifyThread: return .post
48 | case .deleteThread: return .delete
49 | }
50 | }
51 |
52 | public var header: [String : String]? {
53 | let headers: [String: String] = ["OpenAI-Beta": "assistants=v1",
54 | "Content-Type": "application/json"]
55 | return headers
56 | }
57 |
58 | public var body: BodyInfo? {
59 | switch self {
60 | case .createThread(let request): return .init(object: request)
61 | case .retrieveThread: return nil
62 | case .modifyThread(_, let request): return .init(object: request)
63 | case .deleteThread: return nil
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/ASAAssistant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents an assistant that can call the model and use tools.
11 | public struct ASAAssistant: Codable {
12 | /// The identifier of the assistant, which can be referenced in API endpoints.
13 | public let id: String
14 |
15 | /// The object type, which is always 'assistant'.
16 | public let objectType: String
17 |
18 | /// The Unix timestamp (in seconds) for when the assistant was created.
19 | public let createdAt: Int
20 |
21 | /// Optional: The name of the assistant. The maximum length is 256 characters.
22 | public let name: String?
23 |
24 | /// Optional: The description of the assistant. The maximum length is 512 characters.
25 | public let description: String?
26 |
27 | /// ID of the model to use. You can use the List models API to see all of your available models.
28 | public let model: String
29 |
30 | /// Optional: The system instructions that the assistant uses. The maximum length is 32768 characters.
31 | public let instructions: String?
32 |
33 | /// A list of tools enabled on the assistant. There can be a maximum of 128 tools per assistant.
34 | /// Tools can be of types code_interpreter, retrieval, or function.
35 | public let tools: [Tool]
36 |
37 | /// A list of file IDs attached to this assistant. There can be a maximum of 20 files attached to the assistant.
38 | /// Files are ordered by their creation date in ascending order.
39 | public let fileIds: [String]
40 |
41 | /// Optional: Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information
42 | /// about the object in a structured format. Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
43 | public let metadata: [String: String]?
44 |
45 | public enum CodingKeys: String, CodingKey {
46 | case id
47 | case objectType = "object"
48 | case createdAt = "created_at"
49 | case name, description, model, instructions, tools
50 | case fileIds = "file_ids"
51 | case metadata
52 | }
53 |
54 | /// Represents a tool enabled on the assistant.
55 | public struct Tool: Codable {
56 | /// The type of the tool (e.g., code_interpreter, retrieval, function).
57 | public let type: String
58 | }
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASACreateRunRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/16/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// A request structure for creating a run in a thread.
11 | public struct ASACreateRunRequest: Codable {
12 |
13 | /// The ID of the assistant to use to execute this run.
14 | public let assistantId: String
15 |
16 | /// Optional: The ID of the Model to be used to execute this run.
17 | public let model: String?
18 |
19 | /// Optional: Override the default system message of the assistant.
20 | public let instructions: String?
21 |
22 | /// Optional: Override the tools the assistant can use for this run.
23 | public let tools: [Tool]?
24 |
25 | /// Optional: Set of 16 key-value pairs that can be attached to the run.
26 | public let metadata: [String: String]?
27 |
28 | /// Represents a tool that can be used by the assistant during the run.
29 | public struct Tool: Codable {
30 | /// The type of tool being defined: 'code_interpreter', 'retrieval', 'function'.
31 | public let type: String
32 |
33 | /// Additional details for the 'function' tool type.
34 | public let function: Function?
35 |
36 | /// Represents a function tool's details.
37 | public struct Function: Codable {
38 | /// Optional: A description of what the function does.
39 | public let description: String?
40 |
41 | /// The name of the function to be called.
42 | public let name: String
43 |
44 | /// The parameters the functions accepts, described as a JSON Schema object.
45 | public let parameters: [String: String]
46 |
47 | enum CodingKeys: String, CodingKey {
48 | case description, name, parameters
49 | }
50 | }
51 |
52 | enum CodingKeys: String, CodingKey {
53 | case type, function
54 | }
55 | }
56 |
57 | enum CodingKeys: String, CodingKey {
58 | case assistantId = "assistant_id"
59 | case model, instructions, tools, metadata
60 | }
61 |
62 | public init(assistantId: String, model: String? = nil, instructions: String? = nil, tools: [ASACreateRunRequest.Tool]? = nil, metadata: [String : String]? = nil) {
63 | self.assistantId = assistantId
64 | self.model = model
65 | self.instructions = instructions
66 | self.tools = tools
67 | self.metadata = metadata
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/APIs/ModelsAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Describes an OpenAI model offering that can be used with the API. [Link for Models](https://platform.openai.com/docs/api-reference/models)
11 | public protocol IModelsAPI: AnyObject {
12 |
13 | /// Lists the currently available models, and provides basic information about each one such as the owner and availability.
14 | /// - Returns: A list of model objects.
15 | func get() async throws -> ASAModelsListResponse
16 |
17 | /// Retrieves a model instance, providing basic information about the model such as the owner and permissioning.
18 | /// - Parameter modelId: The ID of the model to use for this request
19 | /// - Returns: The model object matching the specified ID.
20 | func retrieve(by modelId: String) async throws -> ASAModel
21 |
22 | /// Delete a fine-tuned model. You must have the Owner role in your organization to delete a model.
23 | /// - Parameter modelId: The model to delete
24 | /// - Returns: Deletion status.
25 | func delete(by modelId: String) async throws -> ASADeleteModelResponse
26 | }
27 |
28 | public final class ModelsAPI: HTTPClient, IModelsAPI {
29 |
30 | let urlSession: URLSession
31 |
32 | public init(apiKey: String,
33 | baseScheme: String = Constants.baseScheme,
34 | baseHost: String = Constants.baseHost,
35 | path: String = Constants.path,
36 | urlSession: URLSession = .shared) {
37 | Constants.apiKey = apiKey
38 | Constants.baseScheme = baseScheme
39 | Constants.baseHost = baseHost
40 | Constants.path = path
41 | self.urlSession = urlSession
42 | }
43 |
44 | public init(urlSession: URLSession = .shared) {
45 | self.urlSession = urlSession
46 | }
47 |
48 | public func get() async throws -> ASAModelsListResponse {
49 | let endpoint = ModelsEndpoint.getModels
50 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAModelsListResponse.self)
51 | }
52 |
53 | public func retrieve(by modelId: String) async throws -> ASAModel {
54 | let endpoint = ModelsEndpoint.retrieveModel(modelId)
55 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAModel.self)
56 | }
57 |
58 | public func delete(by modelId: String) async throws -> ASADeleteModelResponse {
59 | let endpoint = ModelsEndpoint.deleteModel(modelId)
60 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASADeleteModelResponse.self)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Client/AISwiftAssistClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public final class AISwiftAssistClient {
11 |
12 | public let assistantsApi: IAssistantsAPI
13 | public let messagesApi: IMessagesAPI
14 | public let modelsApi: IModelsAPI
15 | public let runsApi: IRunsAPI
16 | public let threadsApi: IThreadsAPI
17 |
18 | public init(config: AISwiftAssistConfig,
19 | baseScheme: String = Constants.baseScheme,
20 | baseHost: String = Constants.baseHost,
21 | path: String = Constants.path) {
22 | Constants.apiKey = config.apiKey
23 | Constants.organizationId = config.organizationId
24 | Constants.baseScheme = baseScheme
25 | Constants.baseHost = baseHost
26 | Constants.path = path
27 | self.assistantsApi = AssistantsAPI(urlSession: .shared)
28 | self.messagesApi = MessagesAPI(urlSession: .shared)
29 | self.modelsApi = ModelsAPI(urlSession: .shared)
30 | self.runsApi = RunsAPI(urlSession: .shared)
31 | self.threadsApi = ThreadsAPI(urlSession: .shared)
32 | }
33 |
34 | }
35 |
36 | extension AISwiftAssistClient {
37 | /// Creates an assistant and thread based on the provided parameters.
38 | public func createAssistantAndThread(with params: AssistantCreationParams) async throws -> AssistantAndThreadConfig {
39 | let modelsResponse = try await modelsApi.get()
40 | guard let model = modelsResponse.data.first(where: { $0.id == params.model.rawValue }) else {
41 | throw NSError(domain: "AISwiftAssistClient", code: 0, userInfo: [NSLocalizedDescriptionKey: "Model not found"])
42 | }
43 |
44 | let createAssistantRequest = ASACreateAssistantRequest(asaModel: model,
45 | name: params.name,
46 | description: params.description,
47 | instructions: params.instructions,
48 | tools: params.tools,
49 | fileIds: params.fileIds,
50 | metadata: params.metadata)
51 | let assistant = try await assistantsApi.create(by: createAssistantRequest)
52 |
53 | let threadRequest = ASACreateThreadRequest(messages: nil)
54 | let thread = try await threadsApi.create(by: threadRequest)
55 |
56 | return AssistantAndThreadConfig(assistant: assistant, thread: thread)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Base/HTTPRequestError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct ValidatorErrorResponse: Codable {
11 | public let code: Int
12 | public let desc: String
13 | }
14 |
15 | /// Types of HTTP Request Errors
16 | public enum HTTPRequestError: Error {
17 | /// Model decoding error
18 | case decode(String)
19 | /// URL validation error
20 | case invalidURL
21 | /// Error receiving response from server
22 | case noResponse
23 | /// Error getting data
24 | case request(localizedDiscription: String)
25 | /// End of current session error
26 | case unauthorizate
27 | /// Error for unexpected status codes
28 | case unexpectedStatusCode(code: Int, localized: String?)
29 | /// Server request processing error
30 | case defaultServerError(error: String)
31 | /// Loading data missing error
32 | case noBodyData
33 | /// Submitted data validation error
34 | case validator(error: ValidatorErrorResponse)
35 | /// A timeout occurs when an operation exceeds a set time limit
36 | case timeout
37 | /// Connection lost, occurs when an established network connection is broken
38 | case connectionLost
39 | /// No internet connection
40 | case notInternetConnection
41 | /// Server Errors
42 | case server(code: String, message: String?)
43 | /// Unknown error
44 | case unknown
45 | }
46 |
47 | extension HTTPRequestError: LocalizedError {
48 | public var errorDescription: String? {
49 | switch self {
50 | case .decode(let message):
51 | return "Decoding error\n\(message)"
52 | case .invalidURL:
53 | return "Invalid URL"
54 | case .noResponse:
55 | return "No answer"
56 | case .request(let localizedDiscription):
57 | return localizedDiscription
58 | case .unauthorizate:
59 | return "Session ended"
60 | case .unexpectedStatusCode(let code, let local):
61 | return "unexpectedStatusCode: \(code) - " + (local ?? "")
62 | case .defaultServerError(error: let error):
63 | return "server error: " + error
64 | case .noBodyData:
65 | return "No data being transmitted"
66 | case .validator(let error):
67 | return "Validator error: \(error.desc)"
68 | case .unknown:
69 | return "Unknown error"
70 | case .timeout:
71 | return "Long connection wait"
72 | case .connectionLost:
73 | return "Connection lost"
74 | case .server(let code, let message):
75 | return "\(code)\n\(message ?? "Empty message")"
76 | case .notInternetConnection:
77 | return "No internet connection"
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Request/ASACreateAssistantRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct ASACreateAssistantRequest: Codable {
11 | /// ID of the model to use. You can use the List models API to see all of your available models.
12 | public let model: String
13 |
14 | /// Optional: The name of the assistant. The maximum length is 256 characters.
15 | public let name: String?
16 |
17 | /// Optional: The description of the assistant. The maximum length is 512 characters.
18 | public let description: String?
19 |
20 | /// Optional: The system instructions that the assistant uses. The maximum length is 32768 characters.
21 | public let instructions: String?
22 |
23 | /// Optional: A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant.
24 | /// Tools can be of types code_interpreter, retrieval, or function.
25 | public let tools: [Tool]?
26 |
27 | /// Optional: A list of file IDs attached to this assistant. There can be a maximum of 20 files attached to the assistant.
28 | /// Files are ordered by their creation date in ascending order.
29 | public let fileIds: [String]?
30 |
31 | /// Optional: Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information
32 | /// about the object in a structured format. Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
33 | public let metadata: [String: String]?
34 |
35 | public enum CodingKeys: String, CodingKey {
36 | case model, name, description, instructions, tools
37 | case fileIds = "file_ids"
38 | case metadata
39 | }
40 |
41 | public init(model: String, name: String? = nil, description: String? = nil, instructions: String? = nil, tools: [Tool]? = nil, fileIds: [String]? = nil, metadata: [String : String]? = nil) {
42 | self.model = model
43 | self.name = name
44 | self.description = description
45 | self.instructions = instructions
46 | self.tools = tools
47 | self.fileIds = fileIds
48 | self.metadata = metadata
49 | }
50 |
51 | public init(asaModel: ASAModel, name: String? = nil, description: String? = nil, instructions: String? = nil, tools: [Tool]? = nil, fileIds: [String]? = nil, metadata: [String : String]? = nil) {
52 | self.model = asaModel.id
53 | self.name = name
54 | self.description = description
55 | self.instructions = instructions
56 | self.tools = tools
57 | self.fileIds = fileIds
58 | self.metadata = metadata
59 | }
60 |
61 | /// Represents a tool enabled on the assistant.
62 | public struct Tool: Codable {
63 | /// The type of the tool (e.g., code_interpreter, retrieval, function).
64 | let type: String
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Base/HTTPClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol HTTPClient: AnyObject {
11 | func sendRequest(session: URLSession,
12 | endpoint: any Endpoint,
13 | responseModel: T.Type) async throws -> T
14 | }
15 |
16 | extension HTTPClient {
17 |
18 | func sendRequest(
19 | session: URLSession = .shared,
20 | endpoint: any Endpoint,
21 | responseModel: T.Type) async throws -> T {
22 | let request = try createRequest(by: endpoint)
23 | let (data, response): (Data, URLResponse) = try await session.data(for: request)
24 | return try handlingDataTask(data: data,
25 | response: response,
26 | responseModel: responseModel)
27 |
28 | }
29 |
30 | private func createRequest(by endpoint: any Endpoint) throws -> URLRequest {
31 | guard let url = endpoint.url else {
32 | throw HTTPRequestError.invalidURL
33 | }
34 | var request: URLRequest = .init(url: url)
35 | request.httpMethod = endpoint.method.rawValue
36 | request.allHTTPHeaderFields = endpoint.header
37 | if request.allHTTPHeaderFields == nil {
38 | request.allHTTPHeaderFields = ["Authorization": "Bearer \(Constants.apiKey)"]
39 | } else {
40 | request.allHTTPHeaderFields?["Authorization"] = "Bearer \(Constants.apiKey)"
41 | }
42 | request.httpBody = endpoint.body?.data
43 |
44 | return request
45 | }
46 |
47 | /// A helper method that handles the response from a request.
48 | func handlingDataTask(
49 | data: Data,
50 | response: URLResponse,
51 | responseModel: T.Type
52 | ) throws -> T {
53 | guard let responseCode = (response as? HTTPURLResponse)?.statusCode else {
54 | throw HTTPRequestError.noResponse
55 | }
56 | switch responseCode {
57 | case 200...299:
58 | if responseModel is Data.Type {
59 | return responseModel as! T
60 | }
61 | do {
62 | let decodeData = try data.decode(model: responseModel)
63 | return decodeData
64 | } catch {
65 | throw HTTPRequestError.decode(error.localizedDescription)
66 | }
67 | case 400:
68 | do {
69 | let decodeData = try data.decode(model: ValidatorErrorResponse.self)
70 | throw HTTPRequestError.validator(error: decodeData)
71 | } catch {
72 | throw HTTPRequestError.unexpectedStatusCode(code: responseCode,
73 | localized: responseCode.localStatusCode)
74 | }
75 | case 401, 403: throw HTTPRequestError.unauthorizate
76 | default: throw HTTPRequestError.unexpectedStatusCode(code: responseCode,
77 | localized: responseCode.localStatusCode)
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Endpoints/MessagesEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum MessagesEndpoint {
11 | case createMessage(String, ASACreateMessageRequest)
12 | case retrieveMessage(String, String)
13 | case modifyMessage(String, String, ASAModifyMessageRequest)
14 | case listMessages(String, ASAListMessagesParameters?)
15 | case retrieveFile(String, String, String)
16 | case listFiles(String, String, ASAListMessagesParameters?)
17 | }
18 |
19 | extension MessagesEndpoint: CustomEndpoint {
20 | public var url: URL? {
21 | var urlComponents: URLComponents = .default
22 | urlComponents.queryItems = queryItems
23 | urlComponents.path = Constants.path + path
24 | return urlComponents.url
25 | }
26 |
27 | public var queryItems: [URLQueryItem]? {
28 | var items: [URLQueryItem]?
29 | switch self {
30 | case .createMessage, .retrieveMessage, .modifyMessage, .retrieveFile: items = nil
31 | case .listMessages(_, let params): items = Utils.createURLQueryItems(from: params)
32 | case .listFiles(_, _, let params): items = Utils.createURLQueryItems(from: params)
33 | }
34 | return items
35 | }
36 |
37 | public var path: String {
38 | switch self {
39 | case .createMessage(let threadId, _):
40 | return "threads/\(threadId)/messages"
41 | case .retrieveMessage(let threadId, let messageId):
42 | return "threads/\(threadId)/messages/\(messageId)"
43 | case .modifyMessage(let threadId, let messageId, _):
44 | return "threads/\(threadId)/messages/\(messageId)"
45 | case .listMessages(let threadId, _):
46 | return "threads/\(threadId)/messages"
47 | case .retrieveFile(let threadId, let messageId, let fileId):
48 | return "threads/\(threadId)/messages/\(messageId)/files/\(fileId)"
49 | case .listFiles(let threadId, let messageId, _):
50 | return "threads/\(threadId)/messages/\(messageId)/files"
51 |
52 | }
53 | }
54 |
55 | public var method: HTTPRequestMethods {
56 | switch self {
57 | case .createMessage: return .post
58 | case .retrieveMessage: return .get
59 | case .modifyMessage: return .post
60 | case .listMessages: return .get
61 | case .retrieveFile: return .get
62 | case .listFiles: return .get
63 | }
64 | }
65 |
66 | public var header: [String : String]? {
67 | let headers: [String: String] = ["OpenAI-Beta": "assistants=v1",
68 | "Content-Type": "application/json"]
69 | return headers
70 | }
71 |
72 | public var body: BodyInfo? {
73 | switch self {
74 | case .createMessage(_, let createMessageRequest): return .init(object: createMessageRequest)
75 | case .modifyMessage(_, _, let request): return .init(object: request)
76 | case .retrieveMessage: return nil
77 | case .listMessages: return nil
78 | case .retrieveFile: return nil
79 | case .listFiles: return nil
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/APIs/ThreadsAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Create threads that assistants can interact with. [Link for Threads](https://platform.openai.com/docs/api-reference/threads)
11 | public protocol IThreadsAPI: AnyObject {
12 |
13 | /// Create a thread.
14 | /// - Parameter createThreads: Object with parameters for creating a thread.
15 | /// - Returns: A thread object.
16 | func create(by createThreads: ASACreateThreadRequest?) async throws -> ASAThread
17 |
18 | /// Retrieves a thread by its ID.
19 | /// - Parameter threadId: The ID of the thread to retrieve.
20 | /// - Returns: The thread object matching the specified ID.
21 | func retrieve(threadId: String) async throws -> ASAThread
22 |
23 | /// Modifies a thread by its ID.
24 | /// - Parameters:
25 | /// - threadId: The ID of the thread to modify.
26 | /// - modifyThread: Object with parameters for modifying a thread.
27 | /// - Returns: The modified thread object.
28 | func modify(threadId: String, with modifyThread: ASAModifyThreadRequest) async throws -> ASAThread
29 |
30 | /// Deletes a thread by its ID.
31 | /// - Parameter threadId: The ID of the thread to delete.
32 | /// - Returns: A boolean indicating the deletion status.
33 | func delete(threadId: String) async throws -> ASADeleteModelResponse
34 | }
35 |
36 | public final class ThreadsAPI: HTTPClient, IThreadsAPI {
37 |
38 | let urlSession: URLSession
39 |
40 | public init(apiKey: String,
41 | baseScheme: String = Constants.baseScheme,
42 | baseHost: String = Constants.baseHost,
43 | path: String = Constants.path,
44 | urlSession: URLSession = .shared) {
45 | Constants.apiKey = apiKey
46 | Constants.baseScheme = baseScheme
47 | Constants.baseHost = baseHost
48 | Constants.path = path
49 | self.urlSession = urlSession
50 | }
51 |
52 | public init(urlSession: URLSession = .shared) {
53 | self.urlSession = urlSession
54 | }
55 |
56 | public func create(by createThreads: ASACreateThreadRequest?) async throws -> ASAThread {
57 | let endpoint = ThreadsEndpoint.createThread(createThreads)
58 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAThread.self)
59 | }
60 |
61 | public func retrieve(threadId: String) async throws -> ASAThread {
62 | let endpoint = ThreadsEndpoint.retrieveThread(threadId)
63 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAThread.self)
64 | }
65 |
66 | public func modify(threadId: String, with modifyThread: ASAModifyThreadRequest) async throws -> ASAThread {
67 | let endpoint = ThreadsEndpoint.modifyThread(threadId, modifyThread)
68 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAThread.self)
69 | }
70 |
71 | public func delete(threadId: String) async throws -> ASADeleteModelResponse {
72 | let endpoint = ThreadsEndpoint.deleteThread(threadId)
73 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASADeleteModelResponse.self)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Endpoints/AssistantEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum AssistantEndpoint {
11 | case getAssistants(ASAListAssistantsParameters?)
12 | case createAssistant(ASACreateAssistantRequest)
13 | case retrieveAssistant(String)
14 | case modifyAssistant(String, ASAModifyAssistantRequest)
15 | case deleteAssistant(String)
16 | case createFile(String, ASACreateAssistantFileRequest)
17 | case retrieveFile(String, String)
18 | case deleteFile(String, String)
19 | case listFiles(String, ASAListAssistantsParameters?)
20 | }
21 |
22 | extension AssistantEndpoint: CustomEndpoint {
23 | public var url: URL? {
24 | var urlComponents: URLComponents = .default
25 | urlComponents.queryItems = queryItems
26 | urlComponents.path = Constants.path + path
27 | return urlComponents.url
28 | }
29 |
30 | public var queryItems: [URLQueryItem]? {
31 | var items: [URLQueryItem]?
32 | switch self {
33 | case .createAssistant, .deleteAssistant, .retrieveAssistant, .modifyAssistant, .createFile, .retrieveFile, .deleteFile: items = nil
34 | case .getAssistants(let params): items = Utils.createURLQueryItems(from: params)
35 | case .listFiles(_, let params): items = Utils.createURLQueryItems(from: params)
36 | }
37 | return items
38 | }
39 |
40 | public var path: String {
41 | switch self {
42 | case .createAssistant, .getAssistants: return "assistants"
43 | case .retrieveAssistant(let assistantId): return "assistants/\(assistantId)"
44 | case .modifyAssistant(let assistantId, _): return "assistants/\(assistantId)"
45 | case .deleteAssistant(let assistantId): return "assistants/\(assistantId)"
46 | case .createFile(let assistantId, _): return "assistants/\(assistantId)/files"
47 | case .retrieveFile(let assistantId, let fileId): return "assistants/\(assistantId)/files/\(fileId)"
48 | case .deleteFile(let assistantId, let fileId): return "assistants/\(assistantId)/files/\(fileId)"
49 | case .listFiles(let assistantId, _): return "assistants/\(assistantId)/files"
50 | }
51 | }
52 |
53 | public var method: HTTPRequestMethods {
54 | switch self {
55 | case .getAssistants: return .get
56 | case .createAssistant: return .post
57 | case .retrieveAssistant: return .get
58 | case .modifyAssistant: return .post
59 | case .deleteAssistant: return .delete
60 | case .createFile: return .post
61 | case .retrieveFile: return .get
62 | case .deleteFile: return .delete
63 | case .listFiles: return .get
64 | }
65 | }
66 |
67 | public var header: [String : String]? {
68 | let headers: [String: String] = ["OpenAI-Beta": "assistants=v1",
69 | "Content-Type": "application/json"]
70 | return headers
71 | }
72 |
73 | public var body: BodyInfo? {
74 | switch self {
75 | case .createAssistant(let createAssistant): return .init(object: createAssistant)
76 | case .modifyAssistant(_, let request): return .init(object: request)
77 | case .createFile(_, let request): return .init(object: request)
78 | case .deleteAssistant, .retrieveAssistant, .getAssistants, .retrieveFile, .deleteFile, .listFiles: return nil
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/APIs/ModelsAPITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModelsAPITests.swift
3 | //
4 | //
5 | // Created by Alexey on 11/20/23.
6 | //
7 |
8 | import XCTest
9 | @testable import AISwiftAssist
10 |
11 | final class ModelsAPITests: XCTestCase {
12 |
13 | var modelsAPI: IModelsAPI!
14 |
15 | override func setUp() {
16 | super.setUp()
17 | let configuration = URLSessionConfiguration.default
18 | configuration.protocolClasses = [MockURLProtocol.self]
19 | let mockURLSession = URLSession(configuration: configuration)
20 | modelsAPI = ModelsAPI(urlSession: mockURLSession)
21 | }
22 |
23 | override func tearDown() {
24 | modelsAPI = nil
25 | super.tearDown()
26 | }
27 |
28 | func testListModels() async {
29 | do {
30 | // Simulate server response
31 | let mockData = Self.list.data(using: .utf8)!
32 |
33 | MockURLProtocol.requestHandler = { request in
34 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
35 | return (response, mockData)
36 | }
37 |
38 | let listResponse: ASAModelsListResponse = try await modelsAPI.get()
39 |
40 | XCTAssertEqual(listResponse.data[0].id, "model-id-0")
41 | XCTAssertEqual(listResponse.data[0].object, "model")
42 | XCTAssertEqual(listResponse.data[0].created, 1686935002)
43 | XCTAssertEqual(listResponse.data[0].ownedBy, "organization-owner")
44 |
45 | XCTAssertEqual(listResponse.data[1].id, "model-id-1")
46 | XCTAssertEqual(listResponse.data[1].object, "model")
47 | XCTAssertEqual(listResponse.data[1].created, 1686935002)
48 | XCTAssertEqual(listResponse.data[1].ownedBy, "organization-owner")
49 |
50 | XCTAssertEqual(listResponse.data[2].id, "model-id-2")
51 | XCTAssertEqual(listResponse.data[2].object, "model")
52 | XCTAssertEqual(listResponse.data[2].created, 1686935002)
53 | XCTAssertEqual(listResponse.data[2].ownedBy, "openai")
54 | } catch {
55 | XCTFail("Error: \(error)")
56 | }
57 | }
58 |
59 | func testRetrieveModel() async {
60 | do {
61 | // Simulate server response
62 | let mockData = Self.retrieve.data(using: .utf8)!
63 |
64 | MockURLProtocol.requestHandler = { request in
65 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
66 | return (response, mockData)
67 | }
68 |
69 | let modelId = "gpt-3.5-turbo-instruct"
70 | let model: ASAModel = try await modelsAPI.retrieve(by: modelId)
71 |
72 | XCTAssertEqual(model.id, modelId)
73 | XCTAssertEqual(model.object, "model")
74 | XCTAssertEqual(model.created, 1686935002)
75 | XCTAssertEqual(model.ownedBy, "openai")
76 | } catch {
77 | XCTFail("Error: \(error)")
78 | }
79 | }
80 |
81 | func testDeleteModel() async {
82 | do {
83 | // Simulate server response
84 | let mockData = Self.delete.data(using: .utf8)!
85 |
86 | MockURLProtocol.requestHandler = { request in
87 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
88 | return (response, mockData)
89 | }
90 |
91 | let modelId = "ft:gpt-3.5-turbo:acemeco:suffix:abc123"
92 | let deleteResponse: ASADeleteModelResponse = try await modelsAPI.delete(by: modelId)
93 |
94 | XCTAssertEqual(deleteResponse.id, "ft:gpt-3.5-turbo:acemeco:suffix:abc123")
95 | XCTAssertEqual(deleteResponse.object, "model")
96 | XCTAssertTrue(deleteResponse.deleted)
97 | } catch {
98 | XCTFail("Error: \(error)")
99 | }
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AISwiftAssist
2 |
3 | A Swift Package Manager (SPM) library for iOS 13 and above, designed to simplify the integration with OpenAI's Assistants API in iOS applications. This library allows you to build AI-powered assistants efficiently and interactively.
4 |
5 | [](https://www.buymeacoffee.com/zelentsov)
6 |
7 | ## Features
8 |
9 | - Seamless integration with OpenAI's Assistants API.
10 | - Easily create and manage AI assistants with various models and tools.
11 | - Handle conversations through threads and messages.
12 | - Utilize Code Interpreter, Retrieval, and Function calling tools.
13 | - Optimized for iOS 13+ with Swift.
14 |
15 | ## Installation
16 |
17 | ### Using Xcode
18 |
19 | To add `AISwiftAssist` to your Xcode project, follow these steps:
20 |
21 | 1. Open your project in Xcode.
22 | 2. Click on "File" in the menu bar, then select "Swift Packages" and then "Add Package Dependency..."
23 | 3. In the dialog that appears, enter the package repository URL: `https://github.com/DeveloperZelentsov/AiSwiftAssist.git`.
24 | 4. Choose the last version to integrate and click "Next".
25 | 5. Confirm the package products and targets and click "Finish".
26 |
27 | ## Usage
28 |
29 | ### Step 1: Import the Library
30 |
31 | Import `AISwiftAssist` into your Swift file where you wish to use it:
32 |
33 | ```swift
34 | import AISwiftAssist
35 | ```
36 |
37 | ### Step 2: Initialize the Client
38 |
39 | Create an instance of AISwiftAssistClient using your API key and organization ID:
40 |
41 | ```swift
42 | let config = AISwiftAssistConfig(apiKey: "your-api-key", organizationId: "your-org-id")
43 | let aiSwiftAssistClient = AISwiftAssistClient(config: config)
44 | ```
45 |
46 | ### Step 3: Create an Assistant
47 |
48 | To create an AI assistant, define its parameters such as model, name, description, and instructions. You can also specify tools and file IDs if required:
49 |
50 | ```swift
51 | let assistantParams = AssistantCreationParams(
52 | modelName: "gpt-4-1106-preview",
53 | name: "Math Tutor",
54 | description: "Your personal math tutor.",
55 | instructions: "Solve math problems and explain solutions."
56 | )
57 |
58 | let creationAssistantParams = try await aiSwiftAssistClient.createAssistantAndThread(with: assistantParams)
59 | let assistantId = creationParams.assistant.id
60 | let threadId = creationParams.thread.id
61 | ```
62 |
63 | ### Step 4: Manage Conversations
64 |
65 | Sending Messages
66 |
67 | Send a message to your assistant through a created thread:
68 |
69 | ```swift
70 | let messageRequest: ASACreateMessageRequest = .init(role: "user", content: content)
71 | try await aiSwiftAssistClient.messagesApi.create(by: threadId, createMessage: messageRequest)
72 | ```
73 |
74 | Initiating a Run
75 |
76 | To process the message and receive a response, initiate a run:
77 |
78 | ```swift
79 | let runRequest = ASACreateRunRequest(assistantId: assistantId)
80 | try await aiSwiftAssistClient.runsApi.create(by: threadId, createRun: runRequest)
81 | ```
82 |
83 | Fetching Responses
84 |
85 | Retrieve the assistant's response:
86 |
87 | ```swift
88 | let messages = try await aiSwiftAssistClient.messagesApi.list(threadId: threadId)
89 | // Process and display these messages, including the assistant's response.
90 | ```
91 |
92 | ### Step 5: Continuous Interaction
93 |
94 | Continue the conversation by sending additional messages, initiating runs, and fetching responses. This creates a dynamic and interactive communication flow with the assistant.
95 |
96 | ## Beyond the Basics
97 |
98 | This is an example of the most basic usage of the Assistants API. There is potential for much more complex and interesting assistants, and I will continue to evolve this Swift API to enable you to leverage these advanced capabilities.
99 |
100 | ## Feedback and Contributions
101 |
102 | We welcome your feedback and contributions to enhance `AISwiftAssist`. Please feel free to report issues or submit pull requests on our GitHub repository.
103 |
104 | ## License
105 |
106 | This library is distributed under the MIT License.
107 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Endpoints/RunsEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum RunsEndpoint {
11 | case createRun(String, ASACreateRunRequest)
12 | case retrieveRun(String, String)
13 | case modifyRun(String, String, ASAModifyRunRequest)
14 | case listRuns(String, ASAListRunsParameters?)
15 | case submitToolOutputs(String, String, [ASAToolOutput])
16 | case cancelRun(String, String)
17 | case createThreadAndRun(ASACreateThreadRunRequest)
18 | case retrieveRunStep(String, String, String)
19 | case listRunSteps(String, String, ASAListRunStepsParameters?)
20 | }
21 |
22 | extension RunsEndpoint: CustomEndpoint {
23 |
24 | public var url: URL? {
25 | var urlComponents: URLComponents = .default
26 | urlComponents.queryItems = queryItems
27 | urlComponents.path = Constants.path + path
28 | return urlComponents.url
29 | }
30 |
31 | public var queryItems: [URLQueryItem]? {
32 | var items: [URLQueryItem]?
33 | switch self {
34 | case .createRun, .retrieveRun, .modifyRun, .submitToolOutputs, .cancelRun, .createThreadAndRun, .retrieveRunStep: items = nil
35 | case .listRuns(_, let params): items = Utils.createURLQueryItems(from: params)
36 | case .listRunSteps(_, _, let params): items = Utils.createURLQueryItems(from: params)
37 | }
38 | return items
39 | }
40 |
41 | public var path: String {
42 | switch self {
43 | case .createRun(let threadId, _):
44 | return "threads/\(threadId)/runs"
45 | case .retrieveRun(let threadId, let runId):
46 | return "threads/\(threadId)/runs/\(runId)"
47 | case .modifyRun(let threadId, let runId, _):
48 | return "threads/\(threadId)/runs/\(runId)"
49 | case .listRuns(let threadId, _):
50 | return "threads/\(threadId)/runs"
51 | case .submitToolOutputs(let threadId, let runId, _):
52 | return "threads/\(threadId)/runs/\(runId)/submit_tool_outputs"
53 | case .cancelRun(let threadId, let runId):
54 | return "threads/\(threadId)/runs/\(runId)/cancel"
55 | case .createThreadAndRun:
56 | return "threads/runs"
57 | case .retrieveRunStep(let threadId, let runId, let stepId):
58 | return "threads/\(threadId)/runs/\(runId)/steps/\(stepId)"
59 | case .listRunSteps(let threadId, let runId, _):
60 | return "threads/\(threadId)/runs/\(runId)/steps"
61 | }
62 | }
63 |
64 | public var method: HTTPRequestMethods {
65 | switch self {
66 | case .createRun:
67 | return .post
68 | case .retrieveRun:
69 | return .get
70 | case .modifyRun:
71 | return .post
72 | case .listRuns:
73 | return .get
74 | case .submitToolOutputs:
75 | return .post
76 | case .cancelRun:
77 | return .post
78 | case .createThreadAndRun:
79 | return .post
80 | case .retrieveRunStep:
81 | return .get
82 | case .listRunSteps:
83 | return .get
84 | }
85 | }
86 |
87 | public var header: [String : String]? {
88 | let headers: [String: String] = ["OpenAI-Beta": "assistants=v1",
89 | "Content-Type": "application/json"]
90 | return headers
91 | }
92 |
93 | public var body: BodyInfo? {
94 | switch self {
95 | case .createRun(_, let request):
96 | return .init(object: request)
97 | case .modifyRun(_, _, let request):
98 | return .init(object: request)
99 | case .submitToolOutputs(_, _, let toolOutputs):
100 | return .init(object: ["tool_outputs": toolOutputs])
101 | case .createThreadAndRun(let request):
102 | return .init(object: request)
103 | case .retrieveRun, .listRuns, .cancelRun, .retrieveRunStep, .listRunSteps:
104 | return nil
105 | }
106 | }
107 | }
108 |
109 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/APIs/ThreadsAPITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThreadsAPITests.swift
3 | //
4 | //
5 | // Created by Alexey on 11/20/23.
6 | //
7 |
8 | import XCTest
9 | @testable import AISwiftAssist
10 |
11 | final class ThreadsAPITests: XCTestCase {
12 |
13 | var threadsAPI: IThreadsAPI!
14 |
15 | override func setUp() {
16 | super.setUp()
17 | let configuration = URLSessionConfiguration.default
18 | configuration.protocolClasses = [MockURLProtocol.self]
19 | let mockURLSession = URLSession(configuration: configuration)
20 | threadsAPI = ThreadsAPI(urlSession: mockURLSession)
21 | }
22 |
23 | override func tearDown() {
24 | threadsAPI = nil
25 | super.tearDown()
26 | }
27 |
28 | func testCreateThread() async {
29 | do {
30 | // Simulate server response
31 | let mockData = Self.create.data(using: .utf8)!
32 |
33 | MockURLProtocol.requestHandler = { request in
34 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
35 | return (response, mockData)
36 | }
37 |
38 | let message = ASACreateThreadRequest.Message(role: "user",
39 | content: "Hello World",
40 | fileIds: nil,
41 | metadata: nil)
42 | let createRequest = ASACreateThreadRequest(messages: [message])
43 | let thread: ASAThread = try await threadsAPI.create(by: createRequest)
44 |
45 | // Checks
46 | XCTAssertEqual(thread.id, "thread_abc123")
47 | XCTAssertEqual(thread.object, "thread")
48 | XCTAssertEqual(thread.createdAt, 1699012949)
49 | XCTAssertTrue(thread.metadata?.isEmpty ?? true)
50 | } catch {
51 | XCTFail("Error: \(error)")
52 | }
53 | }
54 |
55 | func testRetrieveThread() async {
56 | do {
57 | // Simulate server response
58 | let mockData = Self.retrieve.data(using: .utf8)!
59 |
60 | MockURLProtocol.requestHandler = { request in
61 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
62 | return (response, mockData)
63 | }
64 |
65 | let threadId = "thread_abc123"
66 | let thread: ASAThread = try await threadsAPI.retrieve(threadId: threadId)
67 |
68 | // Checks
69 | XCTAssertEqual(thread.id, threadId)
70 | XCTAssertEqual(thread.object, "thread")
71 | XCTAssertEqual(thread.createdAt, 1699014083)
72 | XCTAssertTrue(thread.metadata?.isEmpty ?? true)
73 | } catch {
74 | XCTFail("Error: \(error)")
75 | }
76 | }
77 |
78 | func testModifyThread() async {
79 | do {
80 | // Simulate server response
81 | let mockData = Self.modify.data(using: .utf8)!
82 |
83 | MockURLProtocol.requestHandler = { request in
84 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
85 | return (response, mockData)
86 | }
87 |
88 | let threadId = "thread_abc123"
89 | let modifyRequest = ASAModifyThreadRequest(metadata: ["modified": "true",
90 | "user": "abc123"])
91 |
92 | let modifiedThread: ASAThread = try await threadsAPI.modify(threadId: threadId,
93 | with: modifyRequest)
94 |
95 | // Checks
96 | XCTAssertEqual(modifiedThread.id, threadId)
97 | XCTAssertEqual(modifiedThread.object, "thread")
98 | XCTAssertEqual(modifiedThread.createdAt, 1699014083)
99 | XCTAssertEqual(modifiedThread.metadata?["modified"], "true")
100 | XCTAssertEqual(modifiedThread.metadata?["user"], "abc123")
101 | } catch {
102 | XCTFail("Error: \(error)")
103 | }
104 | }
105 |
106 | func testDeleteThread() async {
107 | do {
108 | // Simulate server response
109 | let mockData = Self.delete.data(using: .utf8)!
110 |
111 | MockURLProtocol.requestHandler = { request in
112 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
113 | return (response, mockData)
114 | }
115 |
116 | let threadId = "thread_abc123"
117 | let deleteResponse: ASADeleteModelResponse = try await threadsAPI.delete(threadId: threadId)
118 |
119 | // Checks
120 | XCTAssertEqual(deleteResponse.id, threadId)
121 | XCTAssertEqual(deleteResponse.object, "thread.deleted")
122 | XCTAssertTrue(deleteResponse.deleted)
123 | } catch {
124 | XCTFail("Error: \(error)")
125 | }
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/ASARun.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents an execution run on a thread.
11 | public struct ASARun: Codable {
12 | /// The identifier of the run, which can be referenced in API endpoints.
13 | public let id: String
14 |
15 | /// The object type, which is always 'thread.run'.
16 | public let object: String
17 |
18 | /// The Unix timestamp (in seconds) for when the run was created.
19 | public let createdAt: Int
20 |
21 | /// The ID of the thread that was executed on as a part of this run.
22 | public let threadId: String
23 |
24 | /// The ID of the assistant used for execution of this run.
25 | public let assistantId: String
26 |
27 | /// The status of the run, which can be either queued, in_progress, requires_action, cancelling, cancelled, failed, completed, or expired.
28 | public let status: String
29 |
30 | /// Details on the action required to continue the run. Will be null if no action is required.
31 | public let requiredAction: RequiredAction?
32 |
33 | /// The last error associated with this run. Will be null if there are no errors.
34 | public let lastError: LastError?
35 |
36 | /// The Unix timestamp (in seconds) for when the run will expire.
37 | public let expiresAt: Int?
38 |
39 | /// The Unix timestamp (in seconds) for when the run was started. Null if not started.
40 | public let startedAt: Int?
41 |
42 | /// The Unix timestamp (in seconds) for when the run was cancelled. Null if not cancelled.
43 | public let cancelledAt: Int?
44 |
45 | /// The Unix timestamp (in seconds) for when the run failed. Null if not failed.
46 | public let failedAt: Int?
47 |
48 | /// The Unix timestamp (in seconds) for when the run was completed. Null if not completed.
49 | public let completedAt: Int?
50 |
51 | /// The model that the assistant used for this run.
52 | public let model: String
53 |
54 | /// The instructions that the assistant used for this run.
55 | public let instructions: String?
56 |
57 | /// The list of tools that the assistant used for this run.
58 | /// Tools can be of types code_interpreter, retrieval, or function.
59 | public let tools: [Tool]
60 |
61 | /// The list of File IDs the assistant used for this run.
62 | public let fileIds: [String]
63 |
64 | /// Set of 16 key-value pairs that can be attached to the run. Useful for storing additional information.
65 | public let metadata: [String: String]?
66 |
67 | /// Represents the required action details for the run to continue.
68 | public struct RequiredAction: Codable {
69 | /// For now, this is always 'submit_tool_outputs'.
70 | public let type: String
71 |
72 | /// Details on the tool outputs needed for this run to continue.
73 | public let submitToolOutputs: SubmitToolOutputs
74 |
75 | enum CodingKeys: String, CodingKey {
76 | case type
77 | case submitToolOutputs = "submit_tool_outputs"
78 | }
79 |
80 | /// Represents the tool outputs needed for this run to continue.
81 | public struct SubmitToolOutputs: Codable {
82 | /// A list of the relevant tool calls.
83 | public let toolCalls: [ToolCall]
84 |
85 | /// Represents a single tool call.
86 | public struct ToolCall: Codable {
87 | /// The ID of the tool call.
88 | public let id: String
89 |
90 | /// The type of tool call the output is required for. For now, this is always 'function'.
91 | public let type: String
92 |
93 | /// The function definition.
94 | public let function: Function
95 |
96 | /// Represents the function definition.
97 | public struct Function: Codable {
98 | /// The name of the function.
99 | public let name: String
100 |
101 | /// The arguments that the model expects you to pass to the function.
102 | public let arguments: String
103 | }
104 | }
105 | }
106 | }
107 |
108 | public struct LastError: Codable {
109 | /// One of 'server_error' or 'rate_limit_exceeded'.
110 | public let code: String
111 |
112 | /// A human-readable description of the error.
113 | public let message: String
114 | }
115 |
116 | /// Represents a tool enabled on the assistant.
117 | public struct Tool: Codable {
118 | /// The type of the tool (e.g., code_interpreter, retrieval, function).
119 | public let type: String
120 | }
121 |
122 | enum CodingKeys: String, CodingKey {
123 | case id, object
124 | case createdAt = "created_at"
125 | case threadId = "thread_id"
126 | case assistantId = "assistant_id"
127 | case status, requiredAction = "required_action"
128 | case lastError = "last_error"
129 | case expiresAt = "expires_at"
130 | case startedAt = "started_at"
131 | case cancelledAt = "cancelled_at"
132 | case failedAt = "failed_at"
133 | case completedAt = "completed_at"
134 | case model, instructions, tools
135 | case fileIds = "file_ids"
136 | case metadata
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/Mosks/AssistantMocks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/19/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension AssistantsAPITests {
11 | static let list: String =
12 | """
13 | {
14 | "object": "list",
15 | "data": [
16 | {
17 | "id": "asst_abc123",
18 | "object": "assistant",
19 | "created_at": 1698982736,
20 | "name": "Coding Tutor",
21 | "description": null,
22 | "model": "gpt-4",
23 | "instructions": "You are a helpful assistant designed to make me better at coding!",
24 | "tools": [],
25 | "file_ids": [],
26 | "metadata": {}
27 | },
28 | {
29 | "id": "asst_abc456",
30 | "object": "assistant",
31 | "created_at": 1698982718,
32 | "name": "My Assistant",
33 | "description": null,
34 | "model": "gpt-4",
35 | "instructions": "You are a helpful assistant designed to make me better at coding!",
36 | "tools": [],
37 | "file_ids": [],
38 | "metadata": {}
39 | },
40 | {
41 | "id": "asst_abc789",
42 | "object": "assistant",
43 | "created_at": 1698982643,
44 | "name": null,
45 | "description": null,
46 | "model": "gpt-4",
47 | "instructions": null,
48 | "tools": [],
49 | "file_ids": [],
50 | "metadata": {}
51 | }
52 | ],
53 | "first_id": "asst_abc123",
54 | "last_id": "asst_abc789",
55 | "has_more": false
56 | }
57 | """
58 |
59 | static let create: String =
60 | """
61 | {
62 | "id": "asst_abc123",
63 | "object": "assistant",
64 | "created_at": 1698984975,
65 | "name": "Math Tutor",
66 | "description": null,
67 | "model": "gpt-4",
68 | "instructions": "You are a personal math tutor. When asked a question, write and run Python code to answer the question.",
69 | "tools": [
70 | {
71 | "type": "code_interpreter"
72 | }
73 | ],
74 | "file_ids": [],
75 | "metadata": {}
76 | }
77 | """
78 |
79 | static let retrieve: String =
80 | """
81 | {
82 | "id": "asst_abc123",
83 | "object": "assistant",
84 | "created_at": 1699009709,
85 | "name": "HR Helper",
86 | "description": null,
87 | "model": "gpt-4",
88 | "instructions": "You are an HR bot, and you have access to files to answer employee questions about company policies.",
89 | "tools": [
90 | {
91 | "type": "retrieval"
92 | }
93 | ],
94 | "file_ids": [
95 | "file-abc123"
96 | ],
97 | "metadata": {}
98 | }
99 | """
100 |
101 | static let modify: String =
102 | """
103 | {
104 | "id": "asst_abc123",
105 | "object": "assistant",
106 | "created_at": 1699009709,
107 | "name": "HR Helper",
108 | "description": null,
109 | "model": "gpt-4",
110 | "instructions": "You are an HR bot, and you have access to files to answer employee questions about company policies. Always response with info from either of the files.",
111 | "tools": [
112 | {
113 | "type": "retrieval"
114 | }
115 | ],
116 | "file_ids": [
117 | "file-abc123",
118 | "file-abc456"
119 | ],
120 | "metadata": {}
121 | }
122 | """
123 |
124 | static let delete: String =
125 | """
126 | {
127 | "id": "asst_abc123",
128 | "object": "assistant.deleted",
129 | "deleted": true
130 | }
131 | """
132 |
133 | static let createFile: String =
134 | """
135 | {
136 | "id": "file-abc123",
137 | "object": "assistant.file",
138 | "created_at": 1699055364,
139 | "assistant_id": "asst_abc123"
140 | }
141 | """
142 |
143 | static let retrieveFile: String =
144 | """
145 | {
146 | "id": "file-abc123",
147 | "object": "assistant.file",
148 | "created_at": 1699055364,
149 | "assistant_id": "asst_abc123"
150 | }
151 | """
152 |
153 | static let deleteFile: String =
154 | """
155 | {
156 | "id": "file-abc123",
157 | "object": "assistant.file.deleted",
158 | "deleted": true
159 | }
160 | """
161 |
162 | static let listFiles: String =
163 | """
164 | {
165 | "object": "list",
166 | "data": [
167 | {
168 | "id": "file-abc123",
169 | "object": "assistant.file",
170 | "created_at": 1699060412,
171 | "assistant_id": "asst_abc123"
172 | },
173 | {
174 | "id": "file-abc456",
175 | "object": "assistant.file",
176 | "created_at": 1699060412,
177 | "assistant_id": "asst_abc123"
178 | }
179 | ],
180 | "first_id": "file-abc123",
181 | "last_id": "file-abc456",
182 | "has_more": false
183 | }
184 | """
185 | }
186 |
187 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/APIs/MessagesAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Create messages within threads [Link for Messages](https://platform.openai.com/docs/api-reference/messages)
11 | public protocol IMessagesAPI: AnyObject {
12 |
13 | /// Create a message.
14 | /// - Parameters:
15 | /// - threadId: The ID of the thread to create a message for.
16 | /// - createMessage: Object with parameters for creating a message.
17 | /// - Returns: A message object.
18 | func create(by threadId: String, createMessage: ASACreateMessageRequest) async throws -> ASAMessage
19 |
20 | /// Retrieve a message.
21 | /// - Parameters:
22 | /// - threadId: The ID of the thread to which this message belongs.
23 | /// - messageId: The ID of the message to retrieve.
24 | /// - Returns: The message object matching the specified ID.
25 | func retrieve(by threadId: String, messageId: String) async throws -> ASAMessage
26 |
27 | /// Modifies a message.
28 | /// - Parameters:
29 | /// - threadId: The ID of the thread to which this message belongs.
30 | /// - messageId: The ID of the message to modify.
31 | /// - modifyMessage: Object with parameters for modifying a message.
32 | /// - Returns: The modified message object.
33 | func modify(by threadId: String, messageId: String, modifyMessage: ASAModifyMessageRequest) async throws -> ASAMessage
34 |
35 | /// Returns a list of messages for a given thread.
36 | /// - Parameters:
37 | /// - threadId: The ID of the thread the messages belong to.
38 | /// - parameters: Parameters for the list of messages.
39 | /// - Returns: A list of message objects.
40 | func getMessages(by threadId: String, parameters: ASAListMessagesParameters?) async throws -> ASAMessagesListResponse
41 |
42 | /// Retrieves a file associated with a message.
43 | /// - Parameters:
44 | /// - threadId: The ID of the thread to which the message and file belong.
45 | /// - messageId: The ID of the message the file belongs to.
46 | /// - fileId: The ID of the file being retrieved.
47 | /// - Returns: The message file object.
48 | func retrieveFile(by threadId: String, messageId: String, fileId: String) async throws -> ASAMessageFile
49 |
50 | /// Returns a list of files associated with a message.
51 | /// - Parameters:
52 | /// - threadId: The ID of the thread that the message and files belong to.
53 | /// - messageId: The ID of the message that the files belong to.
54 | /// - parameters: Optional parameters for pagination and sorting.
55 | /// - Returns: A list of message file objects.
56 | func listFiles(by threadId: String, messageId: String, parameters: ASAListMessagesParameters?) async throws -> ASAMessageFilesListResponse
57 | }
58 |
59 | public final class MessagesAPI: HTTPClient, IMessagesAPI {
60 |
61 | let urlSession: URLSession
62 |
63 | public init(apiKey: String,
64 | baseScheme: String = Constants.baseScheme,
65 | baseHost: String = Constants.baseHost,
66 | path: String = Constants.path,
67 | urlSession: URLSession = .shared) {
68 | Constants.apiKey = apiKey
69 | Constants.baseScheme = baseScheme
70 | Constants.baseHost = baseHost
71 | Constants.path = path
72 | self.urlSession = urlSession
73 | }
74 |
75 | public init(urlSession: URLSession = .shared) {
76 | self.urlSession = urlSession
77 | }
78 |
79 | public func create(by threadId: String, createMessage: ASACreateMessageRequest) async throws -> ASAMessage {
80 | let endpoint = MessagesEndpoint.createMessage(threadId, createMessage)
81 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessage.self)
82 | }
83 |
84 | public func retrieve(by threadId: String, messageId: String) async throws -> ASAMessage {
85 | let endpoint = MessagesEndpoint.retrieveMessage(threadId, messageId)
86 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessage.self)
87 | }
88 |
89 | public func modify(by threadId: String, messageId: String, modifyMessage: ASAModifyMessageRequest) async throws -> ASAMessage {
90 | let endpoint = MessagesEndpoint.modifyMessage(threadId, messageId, modifyMessage)
91 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessage.self)
92 | }
93 |
94 | public func getMessages(by threadId: String, parameters: ASAListMessagesParameters?) async throws -> ASAMessagesListResponse {
95 | let endpoint = MessagesEndpoint.listMessages(threadId, parameters)
96 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessagesListResponse.self)
97 | }
98 |
99 | public func retrieveFile(by threadId: String, messageId: String, fileId: String) async throws -> ASAMessageFile {
100 | let endpoint = MessagesEndpoint.retrieveFile(threadId, messageId, fileId)
101 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessageFile.self)
102 | }
103 |
104 | public func listFiles(by threadId: String, messageId: String, parameters: ASAListMessagesParameters?) async throws -> ASAMessageFilesListResponse {
105 | let endpoint = MessagesEndpoint.listFiles(threadId, messageId, parameters)
106 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAMessageFilesListResponse.self)
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Models/Main/ASARunStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 12/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents a step in the execution of a run.
11 | public struct ASARunStep: Codable {
12 | /// The identifier of the run step, which can be referenced in API endpoints.
13 | let id: String
14 |
15 | /// The object type, which is always `thread.run.step`.
16 | let object: String
17 |
18 | /// The Unix timestamp (in seconds) for when the run step was created.
19 | let createdAt: Int
20 |
21 | /// The ID of the assistant associated with the run step.
22 | let assistantId: String
23 |
24 | /// The ID of the thread that was run.
25 | let threadId: String
26 |
27 | /// The ID of the run that this run step is a part of.
28 | let runId: String
29 |
30 | /// The type of run step, which can be either `message_creation` or `tool_calls`.
31 | let type: String
32 |
33 | /// The status of the run step, which can be either `in_progress`, `cancelled`, `failed`, `completed`, or `expired`.
34 | let status: String
35 |
36 | /// The details of the run step.
37 | let stepDetails: StepDetails
38 |
39 | /// The last error associated with this run step. Will be `null` if there are no errors.
40 | let lastError: LastError?
41 |
42 | /// The Unix timestamp (in seconds) for when the run step expired. A step is considered expired if the parent run is expired.
43 | let expiredAt: Int?
44 |
45 | /// The Unix timestamp (in seconds) for when the run step was cancelled.
46 | let cancelledAt: Int?
47 |
48 | /// The Unix timestamp (in seconds) for when the run step failed.
49 | let failedAt: Int?
50 |
51 | /// The Unix timestamp (in seconds) for when the run step completed.
52 | let completedAt: Int?
53 |
54 | /// A set of 16 key-value pairs that can be attached to an object.
55 | let metadata: [String: String]?
56 |
57 | /// A structure to represent the step details.
58 | struct StepDetails: Codable {
59 | let type: String // This can be 'message_creation' or 'tool_calls'.
60 | let messageCreation: MessageCreation?
61 | let toolCalls: [ToolCall]?
62 |
63 | /// A structure to represent the message creation details.
64 | struct MessageCreation: Codable {
65 | let messageId: String
66 |
67 | enum CodingKeys: String, CodingKey {
68 | case messageId = "message_id"
69 | }
70 | }
71 |
72 | /// A structure to represent the tool calls.
73 | struct ToolCall: Codable {
74 | let id: String
75 | let type: String // This can be 'code_interpreter', 'retrieval', or 'function'.
76 | let details: ToolCallDetails?
77 |
78 | enum CodingKeys: String, CodingKey {
79 | case id, type, details
80 | }
81 |
82 | /// A structure to represent the details of a tool call.
83 | struct ToolCallDetails: Codable {
84 | let codeInterpreter: CodeInterpreter?
85 | let retrieval: Retrieval?
86 | let function: FunctionCall?
87 |
88 | enum CodingKeys: String, CodingKey {
89 | case codeInterpreter = "code_interpreter"
90 | case retrieval
91 | case function
92 | }
93 |
94 | /// A structure to represent the code interpreter tool call.
95 | struct CodeInterpreter: Codable {
96 | let id: String
97 | let type: String // This will always be 'code_interpreter'.
98 | let input: String
99 | let outputs: [CodeInterpreterOutput]
100 |
101 | enum CodingKeys: String, CodingKey {
102 | case id, type, input, outputs
103 | }
104 |
105 | /// A structure to represent the outputs of a code interpreter tool call.
106 | struct CodeInterpreterOutput: Codable {
107 | let type: String // Can be 'logs' or 'image'.
108 | let logs: String?
109 | let image: ImageOutput?
110 |
111 | enum CodingKeys: String, CodingKey {
112 | case type, logs, image
113 | }
114 |
115 | /// A structure to represent the image output.
116 | struct ImageOutput: Codable {
117 | let fileId: String
118 |
119 | enum CodingKeys: String, CodingKey {
120 | case fileId = "file_id"
121 | }
122 | }
123 | }
124 | }
125 |
126 | /// A structure to represent the retrieval tool call.
127 | struct Retrieval: Codable {
128 | // For now, it's always an empty object.
129 | }
130 |
131 | /// A structure to represent the function tool call.
132 | struct FunctionCall: Codable {
133 | let name: String
134 | let arguments: String
135 | let output: String?
136 |
137 | enum CodingKeys: String, CodingKey {
138 | case name, arguments, output
139 | }
140 | }
141 | }
142 | }
143 |
144 | enum CodingKeys: String, CodingKey {
145 | case type
146 | case messageCreation = "message_creation"
147 | case toolCalls = "tool_calls"
148 | }
149 | }
150 |
151 | /// A structure to represent the last error.
152 | struct LastError: Codable {
153 | /// One of `server_error` or `rate_limit_exceeded`.
154 | let code: String
155 |
156 | /// A human-readable description of the error.
157 | let message: String
158 | }
159 |
160 | /// Coding keys to match the JSON property names.
161 | enum CodingKeys: String, CodingKey {
162 | case id, object, type, status, lastError, metadata
163 | case stepDetails = "step_details"
164 | case createdAt = "created_at"
165 | case assistantId = "assistant_id"
166 | case threadId = "thread_id"
167 | case runId = "run_id"
168 | case expiredAt = "expired_at"
169 | case cancelledAt = "cancelled_at"
170 | case failedAt = "failed_at"
171 | case completedAt = "completed_at"
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/APIs/AssistantsAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Build assistants that can call models and use tools to perform tasks. [Link for Assistants](https://platform.openai.com/docs/api-reference/assistants)
11 | public protocol IAssistantsAPI: AnyObject {
12 |
13 | /// Returns a list of assistants.
14 | /// - Parameter parameters: Parameters for the list of assistants.
15 | /// - Returns: A list of assistant objects.
16 | func get(with parameters: ASAListAssistantsParameters?) async throws -> ASAAssistantsListResponse
17 |
18 | /// Create an assistant with a model and instructions.
19 | /// - Parameter createAssistant: The create assistant model.
20 | /// - Returns: An assistant object.
21 | func create(by createAssistant: ASACreateAssistantRequest) async throws -> ASAAssistant
22 |
23 | /// Retrieves an assistant.
24 | /// - Parameter assistantId: The ID of the assistant to retrieve.
25 | /// - Returns: The assistant object matching the specified ID.
26 | func retrieve(by assistantId: String) async throws -> ASAAssistant
27 |
28 | /// Modifies an assistant.
29 | /// - Parameters:
30 | /// - assistantId: The ID of the assistant to modify.
31 | /// - modifyAssistant: Object containing the properties to update.
32 | /// - Returns: The modified assistant object.
33 | func modify(by assistantId: String, modifyAssistant: ASAModifyAssistantRequest) async throws -> ASAAssistant
34 |
35 | /// Delete an assistant.
36 | /// - Parameter assistantId: The ID of the assistant to delete.
37 | /// - Returns: Deletion status
38 | func delete(by assistantId: String) async throws -> ASADeleteModelResponse
39 |
40 | /// Create an assistant file by attaching a File to an assistant.
41 | /// - Parameters:
42 | /// - assistantId: The ID of the assistant for which to create a File.
43 | /// - request: The request object containing the File ID.
44 | /// - Returns: An assistant file object.
45 | func createFile(for assistantId: String, with request: ASACreateAssistantFileRequest) async throws -> ASAAssistantFile
46 |
47 | /// Retrieves an assistant file.
48 | /// - Parameters:
49 | /// - assistantId: The ID of the assistant who the file belongs to.
50 | /// - fileId: The ID of the file to retrieve.
51 | /// - Returns: The assistant file object matching the specified ID.
52 | func retrieveFile(for assistantId: String, fileId: String) async throws -> ASAAssistantFile
53 |
54 | /// Delete an assistant file.
55 | /// - Parameters:
56 | /// - assistantId: The ID of the assistant that the file belongs to.
57 | /// - fileId: The ID of the file to delete.
58 | /// - Returns: Deletion status.
59 | func deleteFile(for assistantId: String, fileId: String) async throws -> ASADeleteModelResponse
60 |
61 | /// Returns a list of assistant files.
62 | /// - Parameters:
63 | /// - assistantId: The ID of the assistant the file belongs to.
64 | /// - parameters: Parameters for the list of assistant files.
65 | /// - Returns: A list of assistant file objects.
66 | func listFiles(for assistantId: String, with parameters: ASAListAssistantsParameters?) async throws -> ASAAssistantFilesListResponse
67 | }
68 |
69 | public final class AssistantsAPI: HTTPClient, IAssistantsAPI {
70 |
71 | let urlSession: URLSession
72 |
73 | public init(apiKey: String,
74 | baseScheme: String = Constants.baseScheme,
75 | baseHost: String = Constants.baseHost,
76 | path: String = Constants.path,
77 | urlSession: URLSession = .shared) {
78 | Constants.apiKey = apiKey
79 | Constants.baseScheme = baseScheme
80 | Constants.baseHost = baseHost
81 | Constants.path = path
82 | self.urlSession = urlSession
83 | }
84 |
85 | public init(urlSession: URLSession = .shared) {
86 | self.urlSession = urlSession
87 | }
88 |
89 | public func get(with parameters: ASAListAssistantsParameters? = nil) async throws -> ASAAssistantsListResponse {
90 | let endpoint = AssistantEndpoint.getAssistants(parameters)
91 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistantsListResponse.self)
92 | }
93 |
94 | public func create(by createAssistant: ASACreateAssistantRequest) async throws -> ASAAssistant {
95 | let endpoint = AssistantEndpoint.createAssistant(createAssistant)
96 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistant.self)
97 | }
98 |
99 | public func retrieve(by assistantId: String) async throws -> ASAAssistant {
100 | let endpoint = AssistantEndpoint.retrieveAssistant(assistantId)
101 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistant.self)
102 | }
103 |
104 | public func modify(by assistantId: String, modifyAssistant: ASAModifyAssistantRequest) async throws -> ASAAssistant {
105 | let endpoint = AssistantEndpoint.modifyAssistant(assistantId, modifyAssistant)
106 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistant.self)
107 | }
108 |
109 | public func delete(by assistantId: String) async throws -> ASADeleteModelResponse {
110 | let endpoint = AssistantEndpoint.deleteAssistant(assistantId)
111 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASADeleteModelResponse.self)
112 | }
113 |
114 | public func createFile(for assistantId: String, with request: ASACreateAssistantFileRequest) async throws -> ASAAssistantFile {
115 | let endpoint = AssistantEndpoint.createFile(assistantId, request)
116 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistantFile.self)
117 | }
118 |
119 | public func retrieveFile(for assistantId: String, fileId: String) async throws -> ASAAssistantFile {
120 | let endpoint = AssistantEndpoint.retrieveFile(assistantId, fileId)
121 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistantFile.self)
122 | }
123 |
124 | public func deleteFile(for assistantId: String, fileId: String) async throws -> ASADeleteModelResponse {
125 | let endpoint = AssistantEndpoint.deleteFile(assistantId, fileId)
126 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASADeleteModelResponse.self)
127 | }
128 |
129 | public func listFiles(for assistantId: String, with parameters: ASAListAssistantsParameters? = nil) async throws -> ASAAssistantFilesListResponse {
130 | let endpoint = AssistantEndpoint.listFiles(assistantId, parameters)
131 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASAAssistantFilesListResponse.self)
132 | }
133 | }
134 |
135 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/APIs/RunsAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/15/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents an execution run on a thread. [Link for Runs](https://platform.openai.com/docs/api-reference/runs)
11 | public protocol IRunsAPI: AnyObject {
12 |
13 | /// Create a run.
14 | /// - Parameters:
15 | /// - threadId: The ID of the thread to run.
16 | /// - createRun: Object with parameters for creating a run.
17 | /// - Returns: A run object.
18 | func create(by threadId: String, createRun: ASACreateRunRequest) async throws -> ASARun
19 |
20 | /// Returns a list of runs belonging to a thread.
21 | /// - Parameters:
22 | /// - threadId: The ID of the thread the run belongs to.
23 | /// - parameters: Parameters for the list of runs.
24 | /// - Returns: A list of run objects.
25 | func listRuns(by threadId: String, parameters: ASAListRunsParameters?) async throws -> ASARunsListResponse
26 |
27 | /// Modifies a run.
28 | /// - Parameters:
29 | /// - threadId: The ID of the thread that was run.
30 | /// - runId: The ID of the run to modify.
31 | /// - modifyRun: A request structure for modifying a run.
32 | /// - Returns: The modified run object matching the specified ID.
33 | func modify(by threadId: String, runId: String, modifyRun: ASAModifyRunRequest) async throws -> ASARun
34 |
35 | /// Retrieves a run.
36 | /// - Parameters:
37 | /// - threadId: The ID of the thread that was run.
38 | /// - runId: The ID of the run to retrieve.
39 | /// - Returns: The run object matching the specified ID.
40 | func retrieve(by threadId: String, runId: String) async throws -> ASARun
41 |
42 | /// Submits tool outputs for a run that requires action.
43 | /// - Parameters:
44 | /// - threadId: The ID of the thread to which this run belongs.
45 | /// - runId: The ID of the run that requires the tool output submission.
46 | /// - toolOutputs: A list of tools for which the outputs are being submitted.
47 | /// - Returns: The modified run object matching the specified ID.
48 | func submitToolOutputs(by threadId: String, runId: String, toolOutputs: [ASAToolOutput]) async throws -> ASARun
49 |
50 | /// Cancels a run that is in progress.
51 | /// - Parameters:
52 | /// - threadId: The ID of the thread to which this run belongs.
53 | /// - runId: The ID of the run to cancel.
54 | /// - Returns: The modified run object matching the specified ID.
55 | func cancelRun(by threadId: String, runId: String) async throws -> ASARun
56 |
57 | /// Creates a thread and runs it in one request.
58 | /// - Parameters:
59 | /// - createThreadRun: Object with parameters for creating a thread and run.
60 | /// - Returns: A run object.
61 | func createThreadAndRun(createThreadRun: ASACreateThreadRunRequest) async throws -> ASARun
62 |
63 | /// Retrieves a run step.
64 | /// - Parameters:
65 | /// - threadId: The ID of the thread to which the run and run step belongs.
66 | /// - runId: The ID of the run to which the run step belongs.
67 | /// - stepId: The ID of the run step to retrieve.
68 | /// - Returns: The run step object matching the specified ID.
69 | func retrieveRunStep(by threadId: String, runId: String, stepId: String) async throws -> ASARunStep
70 |
71 | /// Returns a list of run steps belonging to a run.
72 | /// - Parameters:
73 | /// - threadId: The ID of the thread the run and run steps belong to.
74 | /// - runId: The ID of the run the run steps belong to.
75 | /// - parameters: Parameters for the list of run steps.
76 | /// - Returns: A list of run step objects.
77 | func listRunSteps(by threadId: String, runId: String, parameters: ASAListRunStepsParameters?) async throws -> ASARunStepsListResponse
78 | }
79 |
80 | public final class RunsAPI: HTTPClient, IRunsAPI {
81 |
82 | let urlSession: URLSession
83 |
84 | public init(apiKey: String,
85 | baseScheme: String = Constants.baseScheme,
86 | baseHost: String = Constants.baseHost,
87 | path: String = Constants.path,
88 | urlSession: URLSession = .shared) {
89 | Constants.apiKey = apiKey
90 | Constants.baseScheme = baseScheme
91 | Constants.baseHost = baseHost
92 | Constants.path = path
93 | self.urlSession = urlSession
94 | }
95 |
96 | public init(urlSession: URLSession = .shared) {
97 | self.urlSession = urlSession
98 | }
99 |
100 | public func create(by threadId: String, createRun: ASACreateRunRequest) async throws -> ASARun {
101 | let endpoint = RunsEndpoint.createRun(threadId, createRun)
102 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASARun.self)
103 | }
104 |
105 | public func listRuns(by threadId: String, parameters: ASAListRunsParameters?) async throws -> ASARunsListResponse {
106 | let endpoint = RunsEndpoint.listRuns(threadId, parameters)
107 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASARunsListResponse.self)
108 | }
109 |
110 | public func modify(by threadId: String, runId: String, modifyRun: ASAModifyRunRequest) async throws -> ASARun {
111 | let endpoint = RunsEndpoint.modifyRun(threadId, runId, modifyRun)
112 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASARun.self)
113 | }
114 |
115 | public func retrieve(by threadId: String, runId: String) async throws -> ASARun {
116 | let endpoint = RunsEndpoint.retrieveRun(threadId, runId)
117 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASARun.self)
118 | }
119 |
120 | public func submitToolOutputs(by threadId: String, runId: String, toolOutputs: [ASAToolOutput]) async throws -> ASARun {
121 | let endpoint = RunsEndpoint.submitToolOutputs(threadId, runId, toolOutputs)
122 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASARun.self)
123 | }
124 |
125 | public func cancelRun(by threadId: String, runId: String) async throws -> ASARun {
126 | let endpoint = RunsEndpoint.cancelRun(threadId, runId)
127 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASARun.self)
128 | }
129 |
130 | public func createThreadAndRun(createThreadRun: ASACreateThreadRunRequest) async throws -> ASARun {
131 | let endpoint = RunsEndpoint.createThreadAndRun(createThreadRun)
132 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASARun.self)
133 | }
134 |
135 | public func retrieveRunStep(by threadId: String, runId: String, stepId: String) async throws -> ASARunStep {
136 | let endpoint = RunsEndpoint.retrieveRunStep(threadId, runId, stepId)
137 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASARunStep.self)
138 | }
139 |
140 | public func listRunSteps(by threadId: String, runId: String, parameters: ASAListRunStepsParameters?) async throws -> ASARunStepsListResponse {
141 | let endpoint = RunsEndpoint.listRunSteps(threadId, runId, parameters)
142 | return try await sendRequest(session: urlSession, endpoint: endpoint, responseModel: ASARunStepsListResponse.self)
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/Mosks/RunsMocks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/20/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension RunsAPITests {
11 | static let create: String =
12 | """
13 | {
14 | "id": "run_abc123",
15 | "object": "thread.run",
16 | "created_at": 1699063290,
17 | "assistant_id": "asst_abc123",
18 | "thread_id": "thread_abc123",
19 | "status": "queued",
20 | "started_at": 1699063290,
21 | "expires_at": null,
22 | "cancelled_at": null,
23 | "failed_at": null,
24 | "completed_at": 1699063291,
25 | "last_error": null,
26 | "model": "gpt-4",
27 | "instructions": null,
28 | "tools": [
29 | {
30 | "type": "code_interpreter"
31 | }
32 | ],
33 | "file_ids": [
34 | "file-abc123",
35 | "file-abc456"
36 | ],
37 | "metadata": {}
38 | }
39 | """
40 |
41 | static let retrieve: String =
42 | """
43 | {
44 | "id": "run_abc123",
45 | "object": "thread.run",
46 | "created_at": 1699075072,
47 | "assistant_id": "asst_abc123",
48 | "thread_id": "thread_abc123",
49 | "status": "completed",
50 | "started_at": 1699075072,
51 | "expires_at": null,
52 | "cancelled_at": null,
53 | "failed_at": null,
54 | "completed_at": 1699075073,
55 | "last_error": null,
56 | "model": "gpt-3.5-turbo",
57 | "instructions": null,
58 | "tools": [
59 | {
60 | "type": "code_interpreter"
61 | }
62 | ],
63 | "file_ids": [
64 | "file-abc123",
65 | "file-abc456"
66 | ],
67 | "metadata": {}
68 | }
69 | """
70 |
71 | static let modify: String =
72 | """
73 | {
74 | "id": "run_abc123",
75 | "object": "thread.run",
76 | "created_at": 1699075072,
77 | "assistant_id": "asst_abc123",
78 | "thread_id": "thread_abc123",
79 | "status": "completed",
80 | "started_at": 1699075072,
81 | "expires_at": null,
82 | "cancelled_at": null,
83 | "failed_at": null,
84 | "completed_at": 1699075073,
85 | "last_error": null,
86 | "model": "gpt-3.5-turbo",
87 | "instructions": null,
88 | "tools": [
89 | {
90 | "type": "code_interpreter"
91 | }
92 | ],
93 | "file_ids": [
94 | "file-abc123",
95 | "file-abc456"
96 | ],
97 | "metadata": {
98 | "user_id": "user_abc123"
99 | }
100 | }
101 | """
102 |
103 | static let list: String =
104 | """
105 | {
106 | "object": "list",
107 | "data": [
108 | {
109 | "id": "run_abc123",
110 | "object": "thread.run",
111 | "created_at": 1699075072,
112 | "assistant_id": "asst_abc123",
113 | "thread_id": "thread_abc123",
114 | "status": "completed",
115 | "started_at": 1699075072,
116 | "expires_at": null,
117 | "cancelled_at": null,
118 | "failed_at": null,
119 | "completed_at": 1699075073,
120 | "last_error": null,
121 | "model": "gpt-3.5-turbo",
122 | "instructions": null,
123 | "tools": [
124 | {
125 | "type": "code_interpreter"
126 | }
127 | ],
128 | "file_ids": [
129 | "file-abc123",
130 | "file-abc456"
131 | ],
132 | "metadata": {}
133 | },
134 | {
135 | "id": "run_abc456",
136 | "object": "thread.run",
137 | "created_at": 1699063290,
138 | "assistant_id": "asst_abc123",
139 | "thread_id": "thread_abc123",
140 | "status": "completed",
141 | "started_at": 1699063290,
142 | "expires_at": null,
143 | "cancelled_at": null,
144 | "failed_at": null,
145 | "completed_at": 1699063291,
146 | "last_error": null,
147 | "model": "gpt-3.5-turbo",
148 | "instructions": null,
149 | "tools": [
150 | {
151 | "type": "code_interpreter"
152 | }
153 | ],
154 | "file_ids": [
155 | "file-abc123",
156 | "file-abc456"
157 | ],
158 | "metadata": {}
159 | }
160 | ],
161 | "first_id": "run_abc123",
162 | "last_id": "run_abc456",
163 | "has_more": false
164 | }
165 | """
166 | static let submitToolOutputs: String =
167 | """
168 | {
169 | "id": "run_abc123",
170 | "object": "thread.run",
171 | "created_at": 1699063291,
172 | "thread_id": "thread_abc123",
173 | "assistant_id": "asst_abc123",
174 | "status": "completed",
175 | "started_at": 1699063292,
176 | "expires_at": 1699066891,
177 | "cancelled_at": null,
178 | "failed_at": null,
179 | "completed_at": 1699063391,
180 | "last_error": null,
181 | "model": "gpt-3.5-turbo",
182 | "instructions": "You are a helpful assistant.",
183 | "tools": [
184 | {
185 | "type": "function",
186 | "function": {
187 | "name": "get_weather",
188 | "description": "Determine weather in my location",
189 | "parameters": {
190 | "location": "San Francisco, CA",
191 | "unit": "c"
192 | }
193 | }
194 | }
195 | ],
196 | "file_ids": ["file-abc123"],
197 | "metadata": {
198 | "additional_info": "test"
199 | }
200 | }
201 | """
202 | static let cancelRun: String =
203 | """
204 | {
205 | "id": "run_abc123",
206 | "object": "thread.run",
207 | "created_at": 1699075072,
208 | "assistant_id": "asst_abc123",
209 | "thread_id": "thread_abc123",
210 | "status": "cancelled",
211 | "started_at": 1699075072,
212 | "expires_at": 1699075672,
213 | "cancelled_at": 1699075092,
214 | "failed_at": null,
215 | "completed_at": null,
216 | "last_error": null,
217 | "model": "gpt-3.5-turbo",
218 | "instructions": "Provide instructions",
219 | "tools": [],
220 | "file_ids": ["file-abc123"],
221 | "metadata": {"key": "value"}
222 | }
223 | """
224 |
225 | static let createThreadAndRun: String =
226 | """
227 | {
228 | "id": "run_xyz123",
229 | "object": "thread.run",
230 | "created_at": 1699080000,
231 | "thread_id": "thread_xyz123",
232 | "assistant_id": "asst_xyz123",
233 | "status": "in_progress",
234 | "started_at": 1699080001,
235 | "expires_at": 1699080600,
236 | "cancelled_at": null,
237 | "failed_at": null,
238 | "completed_at": null,
239 | "last_error": null,
240 | "model": "gpt-3.5-turbo",
241 | "instructions": "Explain deep learning to a 5 year old.",
242 | "tools": [
243 | {
244 | "type": "code_interpreter"
245 | }
246 | ],
247 | "file_ids": ["file-xyz123"],
248 | "metadata": {"session": "1"}
249 | }
250 | """
251 |
252 | static let retrieveRunStep: String =
253 | """
254 | {
255 | "id": "step_abc123",
256 | "object": "thread.run.step",
257 | "created_at": 1699063291,
258 | "run_id": "run_abc123",
259 | "assistant_id": "asst_abc123",
260 | "thread_id": "thread_abc123",
261 | "type": "message_creation",
262 | "status": "completed",
263 | "cancelled_at": null,
264 | "completed_at": 1699063291,
265 | "expired_at": null,
266 | "failed_at": null,
267 | "last_error": null,
268 | "step_details": {
269 | "type": "message_creation",
270 | "message_creation": {
271 | "message_id": "msg_abc123"
272 | }
273 | }
274 | }
275 | """
276 |
277 | static let listRunSteps: String =
278 | """
279 | {
280 | "object": "list",
281 | "data": [
282 | {
283 | "id": "step_xyz123",
284 | "object": "thread.run.step",
285 | "created_at": 1699080100,
286 | "run_id": "run_xyz123",
287 | "assistant_id": "asst_xyz123",
288 | "thread_id": "thread_xyz123",
289 | "type": "message_creation",
290 | "status": "completed",
291 | "cancelled_at": null,
292 | "completed_at": 1699080200,
293 | "expired_at": null,
294 | "failed_at": null,
295 | "last_error": null,
296 | "step_details": {
297 | "type": "message_creation",
298 | "message_creation": {
299 | "message_id": "msg_xyz123"
300 | }
301 | }
302 | },
303 | ],
304 | "first_id": "step_xyz123",
305 | "last_id": "step_xyz456",
306 | "has_more": false
307 | }
308 | """
309 |
310 | }
311 |
--------------------------------------------------------------------------------
/Sources/AISwiftAssist/Client/Models/ASAOpenAIModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/17/23.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Represents various models created and used by OpenAI and its partners.
11 | public enum ASAOpenAIModel: String {
12 |
13 | /// Model "text-search-babbage-doc-001" created by openai-dev, a document search model based on the Babbage architecture.
14 | case textSearchBabbageDoc001 = "text-search-babbage-doc-001"
15 |
16 | /// Model "gpt-3.5-turbo-16k" created by openai-internal, a high-capacity version of GPT-3.5 optimized for performance.
17 | case gpt3_5Turbo16k = "gpt-3.5-turbo-16k"
18 |
19 | /// Model "curie-search-query" created by openai-dev, designed for efficient and effective search query processing.
20 | case curieSearchQuery = "curie-search-query"
21 |
22 | /// Model "text-davinci-003" created by openai-internal, an advanced iteration of the Davinci text processing model.
23 | case textDavinci003 = "text-davinci-003"
24 |
25 | /// Model "text-search-babbage-query-001" created by openai-dev, specialized in processing and understanding search queries.
26 | case textSearchBabbageQuery001 = "text-search-babbage-query-001"
27 |
28 | /// Model "babbage" created by openai, a versatile and powerful AI model.
29 | case babbage = "babbage"
30 |
31 | /// Model "babbage-search-query" created by openai-dev, tailored for searching and analyzing large datasets.
32 | case babbageSearchQuery = "babbage-search-query"
33 |
34 | /// Model "text-babbage-001" created by openai, a variant of the Babbage model with specific enhancements.
35 | case textBabbage001 = "text-babbage-001"
36 |
37 | /// Model "text-similarity-davinci-001" created by openai-dev, designed for high-accuracy text similarity analysis.
38 | case textSimilarityDavinci001 = "text-similarity-davinci-001"
39 |
40 | /// Model "davinci-similarity" created by openai-dev, optimized for comparing and contrasting large text data sets.
41 | case davinciSimilarity = "davinci-similarity"
42 |
43 | /// Model "code-davinci-edit-001" created by openai, focused on refining and editing code-based text inputs.
44 | case codeDavinciEdit001 = "code-davinci-edit-001"
45 |
46 | /// Model "curie-similarity" created by openai-dev, a model for analyzing and comparing text for similarities.
47 | case curieSimilarity = "curie-similarity"
48 |
49 | /// Model "babbage-search-document" created by openai-dev, designed for document retrieval and information extraction.
50 | case babbageSearchDocument = "babbage-search-document"
51 |
52 | /// Model "curie-instruct-beta" created by openai, a version of the Curie model trained for instructional and explanatory tasks.
53 | case curieInstructBeta = "curie-instruct-beta"
54 |
55 | /// Model "text-search-ada-doc-001" created by openai-dev, specialized in document search and analysis.
56 | case textSearchAdaDoc001 = "text-search-ada-doc-001"
57 |
58 | /// Model "davinci-instruct-beta" created by openai, an iteration of Davinci trained for instructional content generation.
59 | case davinciInstructBeta = "davinci-instruct-beta"
60 |
61 | /// Model "gpt-3.5-turbo-instruct" created by system, a turbocharged version of GPT-3.5 with instructional capabilities.
62 | case gpt3_5TurboInstruct = "gpt-3.5-turbo-instruct"
63 |
64 | /// Model "text-similarity-babbage-001" created by openai-dev, for analyzing text similarities using the Babbage architecture.
65 | case textSimilarityBabbage001 = "text-similarity-babbage-001"
66 |
67 | /// Model "text-search-davinci-doc-001" created by openai-dev, a Davinci-based model for document search and analysis.
68 | case textSearchDavinciDoc001 = "text-search-davinci-doc-001"
69 |
70 | /// Model "gpt-3.5-turbo-instruct-0914" created by system, an advanced version of GPT-3.5-turbo with specialized instructive abilities.
71 | case gpt3_5TurboInstruct0914 = "gpt-3.5-turbo-instruct-0914"
72 |
73 | /// Model "babbage-similarity" created by openai-dev, focuses on comparing text snippets for similarities using Babbage architecture.
74 | case babbageSimilarity = "babbage-similarity"
75 |
76 | /// Model "text-embedding-ada-002" created by openai-internal, designed for embedding text using the Ada model.
77 | case textEmbeddingAda002 = "text-embedding-ada-002"
78 |
79 | /// Model "davinci-search-query" created by openai-dev, an advanced Davinci model optimized for search queries.
80 | case davinciSearchQuery = "davinci-search-query"
81 |
82 | /// Model "text-similarity-curie-001" created by openai-dev, a Curie-based model for analyzing text similarities.
83 | case textSimilarityCurie001 = "text-similarity-curie-001"
84 |
85 | /// Model "text-davinci-001" created by openai, an early iteration of the Davinci model with advanced text processing capabilities.
86 | case textDavinci001 = "text-davinci-001"
87 |
88 | /// Model "text-search-davinci-query-001" created by openai-dev, designed for processing search queries using the Davinci model.
89 | case textSearchDavinciQuery001 = "text-search-davinci-query-001"
90 |
91 | /// Model "ada-search-document" created by openai-dev, an Ada-based model for document search and retrieval.
92 | case adaSearchDocument = "ada-search-document"
93 |
94 | /// Model "ada-code-search-code" created by openai-dev, specialized in searching and analyzing code snippets using the Ada model.
95 | case adaCodeSearchCode = "ada-code-search-code"
96 |
97 | /// Model "babbage-002" created by system, a second iteration of the Babbage model with enhanced capabilities.
98 | case babbage002 = "babbage-002"
99 |
100 | /// Model "gpt-4-vision-preview" created by system, a preview version of GPT-4 with vision processing capabilities.
101 | case gpt4VisionPreview = "gpt-4-vision-preview"
102 |
103 | /// Model "davinci-002" created by system, a second iteration of the Davinci model with improvements in text processing.
104 | case davinci002 = "davinci-002"
105 |
106 | /// Model "gpt-4-0314" created by openai, a version of GPT-4 specialized in various AI tasks.
107 | case gpt40314 = "gpt-4-0314"
108 |
109 | /// Model "davinci-search-document" created by openai-dev, focused on document retrieval and analysis using the Davinci model.
110 | case davinciSearchDocument = "davinci-search-document"
111 |
112 | /// Model "curie-search-document" created by openai-dev, a Curie-based model designed for document search and retrieval.
113 | case curieSearchDocument = "curie-search-document"
114 |
115 | /// Model "babbage-code-search-code" created by openai-dev, specialized in code search and analysis using the Babbage model.
116 | case babbageCodeSearchCode = "babbage-code-search-code"
117 |
118 | /// Model "gpt-4-0613" created by openai, an iteration of GPT-4 with specific enhancements and capabilities.
119 | case gpt40613 = "gpt-4-0613"
120 |
121 | /// Model "text-search-ada-query-001" created by openai-dev, an Ada model designed for processing search queries.
122 | case textSearchAdaQuery001 = "text-search-ada-query-001"
123 |
124 | /// Model "code-search-ada-text-001" created by openai-dev, optimized for searching text in code using the Ada model.
125 | case codeSearchAdaText001 = "code-search-ada-text-001"
126 |
127 | /// Model "gpt-3.5-turbo-16k-0613" created by openai, a high-capacity, performance-optimized version of GPT-3.5.
128 | case gpt3_5Turbo16k0613 = "gpt-3.5-turbo-16k-0613"
129 |
130 | /// Model "babbage-code-search-text" created by openai-dev, a Babbage model for text search in code.
131 | case babbageCodeSearchText = "babbage-code-search-text"
132 |
133 | /// Model "code-search-babbage-code-001" created by openai-dev, specialized for code searching using the Babbage model.
134 | case codeSearchBabbageCode001 = "code-search-babbage-code-001"
135 |
136 | /// Model "ada-search-query" created by openai-dev, an Ada-based model optimized for search query processing.
137 | case adaSearchQuery = "ada-search-query"
138 |
139 | /// Model "ada-code-search-text" created by openai-dev, focused on text search in code using the Ada model.
140 | case adaCodeSearchText = "ada-code-search-text"
141 |
142 | /// Model "tts-1-hd" created by system, a high-definition text-to-speech model.
143 | case tts1Hd = "tts-1-hd"
144 |
145 | /// Model "text-search-curie-query-001" created by openai-dev, a Curie-based model for effective search query processing.
146 | case textSearchCurieQuery001 = "text-search-curie-query-001"
147 |
148 | /// Model "text-search-curie-doc-001" created by openai-dev, a Curie-based document search model.
149 | case textSearchCurieDoc001 = "text-search-curie-doc-001"
150 |
151 | /// Model "text-curie-001" created by openai, an early version of the Curie model for versatile text processing.
152 | case textCurie001 = "text-curie-001"
153 |
154 | /// Model "curie" created by openai, a powerful and adaptable AI model for various text-related tasks.
155 | case curie = "curie"
156 |
157 | /// Model "canary-tts" created by system, a test version of a text-to-speech model.
158 | case canaryTTS = "canary-tts"
159 |
160 | /// Model "tts-1" created by openai-internal, a versatile text-to-speech model.
161 | case tts1 = "tts-1"
162 |
163 | /// Model "gpt-3.5-turbo-1106" created by system, an enhanced version of GPT-3.5 Turbo.
164 | case gpt3_5Turbo1106 = "gpt-3.5-turbo-1106"
165 |
166 | /// Model "gpt-3.5-turbo-0613" created by openai, a variant of GPT-3.5 Turbo.
167 | case gpt3_5Turbo0613 = "gpt-3.5-turbo-0613"
168 |
169 | /// Model "gpt-4-1106-preview" created by system, a preview version of GPT-4.
170 | case gpt41106Preview = "gpt-4-1106-preview"
171 |
172 | /// Model "gpt-3.5-turbo-0301" created by openai, an iteration of GPT-3.5 Turbo.
173 | case gpt3_5Turbo0301 = "gpt-3.5-turbo-0301"
174 |
175 | /// Model "gpt-3.5-turbo" created by openai, a powerful and fast version of GPT-3.5.
176 | case gpt3_5Turbo = "gpt-3.5-turbo"
177 |
178 | /// Model "davinci" created by openai, a highly advanced and capable AI model for various applications.
179 | case davinci = "davinci"
180 |
181 | /// Model "dall-e-2" created by system, an AI model specializing in generating and manipulating images.
182 | case dallE2 = "dall-e-2"
183 |
184 | /// Model "tts-1-1106" created by system, a text-to-speech model with specific enhancements.
185 | case tts11106 = "tts-1-1106"
186 |
187 | /// Model "dall-e-3" created by system, an advanced version of the DALL-E image generation model.
188 | case dallE3 = "dall-e-3"
189 | }
190 |
191 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/Mosks/MessagesMocks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Alexey on 11/20/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension MessagesAPITests {
11 |
12 | static let list: String =
13 | """
14 | {
15 | "object": "list",
16 | "data": [
17 | {
18 | "id": "msg_SM7CyvQn3UnrAyOh96TGN5jR",
19 | "object": "thread.message",
20 | "created_at": 1702586152,
21 | "thread_id": "thread_11LlFPiPpEw7WhZr0AqB2WhF",
22 | "role": "assistant",
23 | "content": [
24 | {
25 | "type": "text",
26 | "text": {
27 | "value": "I now have full annotations for every date mentioned in the file",
28 | "annotations": [
29 | {
30 | "type": "file_citation",
31 | "text": "【21†source】",
32 | "start_index": 136,
33 | "end_index": 147,
34 | "file_citation": {
35 | "file_id": "",
36 | "quote": "adfadlfkjamdf"
37 | }
38 | },
39 | {
40 | "type": "file_citation",
41 | "text": "【22†source】",
42 | "start_index": 197,
43 | "end_index": 208,
44 | "file_citation": {
45 | "file_id": "",
46 | "quote": "01.12.202318"
47 | }
48 | },
49 | {
50 | "type": "file_citation",
51 | "text": "【29†source】",
52 | "start_index": 287,
53 | "end_index": 298,
54 | "file_citation": {
55 | "file_id": "",
56 | "quote": "ajfnailfbnloabiufnliajnsfl"
57 | }
58 | },
59 | {
60 | "type": "file_citation",
61 | "text": "【33†source】",
62 | "start_index": 378,
63 | "end_index": 389,
64 | "file_citation": {
65 | "file_id": "",
66 | "quote": "DXB"
67 | }
68 | }
69 | ]
70 | }
71 | }
72 | ],
73 | "file_ids": [],
74 | "assistant_id": "asst_wAI24kgbPjUpBqnEbsyjS8iO",
75 | "run_id": "run_RapQjuYYvH1gpyOQB2DSAurf",
76 | "metadata": {}
77 | },
78 | {
79 | "id": "msg_oacFAKp8WbIKYnV2Wmsyh5aE",
80 | "object": "thread.message",
81 | "created_at": 1702560337,
82 | "thread_id": "thread_11LlFPiPpEw7WhZr0AqB2WhF",
83 | "role": "assistant",
84 | "content": [
85 | {
86 | "type": "text",
87 | "text": {
88 | "value": "Привет! Как я могу помочь вам сегодня?",
89 | "annotations": []
90 | }
91 | }
92 | ],
93 | "file_ids": [],
94 | "assistant_id": "asst_wAI24kgbPjUpBqnEbsyjS8iO",
95 | "run_id": "run_9P57TYv8dxBPQkHK8plrNztk",
96 | "metadata": {}
97 | },
98 | {
99 | "id": "msg_V8hf7PCvWceW4DpQKpQV83Ia",
100 | "object": "thread.message",
101 | "created_at": 1702560334,
102 | "thread_id": "thread_11LlFPiPpEw7WhZr0AqB2WhF",
103 | "role": "user",
104 | "content": [
105 | {
106 | "type": "text",
107 | "text": {
108 | "value": "привет",
109 | "annotations": []
110 | }
111 | }
112 | ],
113 | "file_ids": [],
114 | "assistant_id": null,
115 | "run_id": null,
116 | "metadata": {}
117 | }
118 | ],
119 | "first_id": "msg_SM7CyvQn3UnrAyOh96TGN5jR",
120 | "last_id": "msg_V8hf7PCvWceW4DpQKpQV83Ia",
121 | "has_more": false
122 | }
123 | """
124 |
125 | static let modify: String =
126 | """
127 | {
128 | "id": "12345",
129 | "object": "thread.message",
130 | "created_at": 1639530000,
131 | "thread_id": "thread123",
132 | "role": "assistant",
133 | "content": [
134 | {
135 | "type": "text",
136 | "text": {
137 | "value": "This is a text message with annotations.",
138 | "annotations": [
139 |
140 | {
141 | "type": "file_citation",
142 | "text": "document link",
143 | "file_citation": {
144 | "file_id": "file123",
145 | "quote": "A quote from the file"
146 | },
147 | "start_index": 0,
148 | "end_index": 23
149 | },
150 | {
151 | "type": "file_path",
152 | "text": "path to file",
153 | "file_path": {
154 | "file_id": "file456"
155 | },
156 | "start_index": 24,
157 | "end_index": 37
158 | }
159 | ]
160 | }
161 | },
162 | {
163 | "type": "image_file",
164 | "image_file": {
165 | "file_id": "image789"
166 | }
167 | }
168 | ],
169 | "assistant_id": "assistant123",
170 | "run_id": "run123",
171 | "file_ids": ["file123", "file456", "image789"],
172 | "metadata": {
173 | "key1": "value1",
174 | "key2": "value2"
175 | }
176 | }
177 | """
178 |
179 | static let retrieve: String =
180 | """
181 | {
182 | "id": "12345",
183 | "object": "thread.message",
184 | "created_at": 1639530000,
185 | "thread_id": "thread123",
186 | "role": "assistant",
187 | "content": [
188 | {
189 | "type": "text",
190 | "text": {
191 | "value": "This is a text message with annotations.",
192 | "annotations": [
193 |
194 | {
195 | "type": "file_citation",
196 | "text": "document link",
197 | "file_citation": {
198 | "file_id": "file123",
199 | "quote": "A quote from the file"
200 | },
201 | "start_index": 0,
202 | "end_index": 23
203 | },
204 | {
205 | "type": "file_path",
206 | "text": "path to file",
207 | "file_path": {
208 | "file_id": "file456"
209 | },
210 | "start_index": 24,
211 | "end_index": 37
212 | }
213 | ]
214 | }
215 | },
216 | {
217 | "type": "image_file",
218 | "image_file": {
219 | "file_id": "image789"
220 | }
221 | }
222 | ],
223 | "assistant_id": "assistant123",
224 | "run_id": "run123",
225 | "file_ids": ["file123", "file456", "image789"],
226 | "metadata": {
227 | "key1": "value1",
228 | "key2": "value2"
229 | }
230 | }
231 | """
232 |
233 | static let create: String =
234 | """
235 | {
236 | "id": "12345",
237 | "object": "thread.message",
238 | "created_at": 1639530000,
239 | "thread_id": "thread123",
240 | "role": "assistant",
241 | "content": [
242 | {
243 | "type": "text",
244 | "text": {
245 | "value": "This is a text message with annotations.",
246 | "annotations": [
247 |
248 | {
249 | "type": "file_citation",
250 | "text": "document link",
251 | "file_citation": {
252 | "file_id": "file123",
253 | "quote": "A quote from the file"
254 | },
255 | "start_index": 0,
256 | "end_index": 23
257 | },
258 | {
259 | "type": "file_path",
260 | "text": "path to file",
261 | "file_path": {
262 | "file_id": "file456"
263 | },
264 | "start_index": 24,
265 | "end_index": 37
266 | }
267 | ]
268 | }
269 | },
270 | {
271 | "type": "image_file",
272 | "image_file": {
273 | "file_id": "image789"
274 | }
275 | }
276 | ],
277 | "assistant_id": "assistant123",
278 | "run_id": "run123",
279 | "file_ids": ["file123", "file456", "image789"],
280 | "metadata": {
281 | "key1": "value1",
282 | "key2": "value2"
283 | }
284 | }
285 | """
286 |
287 | static let retrieveFile: String =
288 | """
289 | {
290 | "id": "file-abc123",
291 | "object": "thread.message.file",
292 | "created_at": 1698107661,
293 | "message_id": "message_QLoItBbqwyAJEzlTy4y9kOMM",
294 | "file_id": "file-abc123"
295 | }
296 | """
297 |
298 | static let listFiles: String =
299 | """
300 | {
301 | "object": "list",
302 | "data": [
303 | {
304 | "id": "file-abc123",
305 | "object": "thread.message.file",
306 | "created_at": 1698107661,
307 | "message_id": "message_QLoItBbqwyAJEzlTy4y9kOMM"
308 | },
309 | {
310 | "id": "file-abc456",
311 | "object": "thread.message.file",
312 | "created_at": 1698107662,
313 | "message_id": "message_QLoItBbqwyAJEzlTy4y9kONN"
314 | }
315 | ],
316 | "first_id": "file-abc123",
317 | "last_id": "file-abc456",
318 | "has_more": false
319 | }
320 | """
321 | }
322 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/APIs/AssistantsAPITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AssistantsAPITests.swift
3 | //
4 | //
5 | // Created by Alexey on 11/19/23.
6 | //
7 |
8 | import XCTest
9 | @testable import AISwiftAssist
10 |
11 | final class AssistantsAPITests: XCTestCase {
12 |
13 | var assistantsAPI: IAssistantsAPI!
14 |
15 | override func setUp() {
16 | super.setUp()
17 | let configuration = URLSessionConfiguration.default
18 | configuration.protocolClasses = [MockURLProtocol.self]
19 | let mockURLSession = URLSession(configuration: configuration)
20 | assistantsAPI = AssistantsAPI(urlSession: mockURLSession)
21 | }
22 |
23 | override func tearDown() {
24 | assistantsAPI = nil
25 | super.tearDown()
26 | }
27 |
28 | func testGetAssistants() async {
29 | do {
30 | // Simulate server response
31 | let mockData = AssistantsAPITests.list.data(using: .utf8)!
32 |
33 | MockURLProtocol.requestHandler = { request in
34 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
35 | return (response, mockData)
36 | }
37 |
38 | let response: ASAAssistantsListResponse = try await assistantsAPI.get(with: nil)
39 |
40 | XCTAssertNotNil(response)
41 | XCTAssertEqual(response.object, "list")
42 | XCTAssertEqual(response.data.count, 3)
43 | XCTAssertEqual(response.firstId, "asst_abc123")
44 | XCTAssertEqual(response.lastId, "asst_abc789")
45 | XCTAssertFalse(response.hasMore)
46 |
47 | // Checks for specific assistants
48 | XCTAssertEqual(response.data[0].id, "asst_abc123")
49 | XCTAssertEqual(response.data[0].objectType, "assistant")
50 | XCTAssertEqual(response.data[0].createdAt, 1698982736)
51 | XCTAssertEqual(response.data[0].name, "Coding Tutor")
52 | XCTAssertEqual(response.data[0].model, "gpt-4")
53 | XCTAssertEqual(response.data[0].instructions, "You are a helpful assistant designed to make me better at coding!")
54 |
55 | XCTAssertEqual(response.data[1].id, "asst_abc456")
56 | XCTAssertEqual(response.data[1].objectType, "assistant")
57 | XCTAssertEqual(response.data[1].createdAt, 1698982718)
58 | XCTAssertEqual(response.data[1].name, "My Assistant")
59 | XCTAssertEqual(response.data[1].model, "gpt-4")
60 | XCTAssertEqual(response.data[1].instructions, "You are a helpful assistant designed to make me better at coding!")
61 |
62 | XCTAssertEqual(response.data[2].id, "asst_abc789")
63 | XCTAssertEqual(response.data[2].objectType, "assistant")
64 | XCTAssertEqual(response.data[2].createdAt, 1698982643)
65 | XCTAssertNil(response.data[2].name)
66 | XCTAssertEqual(response.data[2].model, "gpt-4")
67 | XCTAssertNil(response.data[2].instructions)
68 | } catch {
69 | XCTFail("Error: \(error)")
70 | }
71 | }
72 |
73 | func testCreateAssistant() async {
74 | do {
75 | // Simulate server response
76 | let mockData = AssistantsAPITests.create.data(using: .utf8)!
77 |
78 | MockURLProtocol.requestHandler = { request in
79 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
80 | return (response, mockData)
81 | }
82 |
83 | let createRequest = ASACreateAssistantRequest(model: "gpt-4",
84 | name: "Math Tutor",
85 | instructions: "You are a personal math tutor. When asked a question, write and run Python code to answer the question.")
86 |
87 | let assistant: ASAAssistant = try await assistantsAPI.create(by: createRequest)
88 |
89 | // Checks
90 | XCTAssertEqual(assistant.id, "asst_abc123")
91 | XCTAssertEqual(assistant.objectType, "assistant")
92 | XCTAssertEqual(assistant.createdAt, 1698984975)
93 | XCTAssertEqual(assistant.name, "Math Tutor")
94 | XCTAssertEqual(assistant.model, "gpt-4")
95 | XCTAssertEqual(assistant.instructions, "You are a personal math tutor. When asked a question, write and run Python code to answer the question.")
96 | XCTAssertEqual(assistant.tools.count, 1)
97 | XCTAssertEqual(assistant.tools.first?.type, "code_interpreter")
98 | XCTAssertTrue(assistant.fileIds.isEmpty)
99 | XCTAssertTrue(assistant.metadata?.isEmpty ?? true)
100 | } catch {
101 | XCTFail("Error: \(error)")
102 | }
103 | }
104 |
105 | func testRetrieveAssistant() async {
106 | do {
107 | // Simulate server response
108 | let mockData = AssistantsAPITests.retrieve.data(using: .utf8)!
109 |
110 | MockURLProtocol.requestHandler = { request in
111 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
112 | return (response, mockData)
113 | }
114 |
115 | let assistantId = "asst_abc123"
116 | let assistant: ASAAssistant = try await assistantsAPI.retrieve(by: assistantId)
117 |
118 | // Checks
119 | XCTAssertEqual(assistant.id, assistantId)
120 | XCTAssertEqual(assistant.objectType, "assistant")
121 | XCTAssertEqual(assistant.createdAt, 1699009709)
122 | XCTAssertEqual(assistant.name, "HR Helper")
123 | XCTAssertEqual(assistant.model, "gpt-4")
124 | XCTAssertEqual(assistant.instructions, "You are an HR bot, and you have access to files to answer employee questions about company policies.")
125 | XCTAssertEqual(assistant.tools.count, 1)
126 | XCTAssertEqual(assistant.tools.first?.type, "retrieval")
127 | XCTAssertEqual(assistant.fileIds, ["file-abc123"])
128 | XCTAssertTrue(assistant.metadata?.isEmpty ?? true)
129 | } catch {
130 | XCTFail("Error: \(error)")
131 | }
132 | }
133 |
134 | func testModifyAssistant() async {
135 | do {
136 | // Simulate server response
137 | let mockData = AssistantsAPITests.modify.data(using: .utf8)!
138 |
139 | MockURLProtocol.requestHandler = { request in
140 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
141 | return (response, mockData)
142 | }
143 |
144 | let assistantId = "asst_abc123"
145 | let modifyRequest = ASAModifyAssistantRequest(
146 | instructions: "You are an HR bot, and you have access to files to answer employee questions about company policies. Always response with info from either of the files.",
147 | fileIds: ["file-abc123", "file-abc456"]
148 | )
149 |
150 | let modifiedAssistant: ASAAssistant = try await assistantsAPI.modify(by: assistantId,
151 | modifyAssistant: modifyRequest)
152 |
153 | // Checks
154 | XCTAssertEqual(modifiedAssistant.id, assistantId)
155 | XCTAssertEqual(modifiedAssistant.objectType, "assistant")
156 | XCTAssertEqual(modifiedAssistant.createdAt, 1699009709)
157 | XCTAssertEqual(modifiedAssistant.name, "HR Helper")
158 | XCTAssertEqual(modifiedAssistant.model, "gpt-4")
159 | XCTAssertEqual(modifiedAssistant.instructions, modifyRequest.instructions)
160 | XCTAssertEqual(modifiedAssistant.tools.count, 1)
161 | XCTAssertEqual(modifiedAssistant.tools.first?.type, "retrieval")
162 | XCTAssertEqual(modifiedAssistant.fileIds, modifyRequest.fileIds)
163 | XCTAssertTrue(modifiedAssistant.metadata?.isEmpty ?? true)
164 | } catch {
165 | XCTFail("Error: \(error)")
166 | }
167 | }
168 |
169 | func testDeleteAssistant() async {
170 | do {
171 | // Simulate server response
172 | let mockData = AssistantsAPITests.delete.data(using: .utf8)!
173 |
174 | MockURLProtocol.requestHandler = { request in
175 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
176 | return (response, mockData)
177 | }
178 |
179 | let assistantId = "asst_abc123"
180 | let deleteResponse: ASADeleteModelResponse = try await assistantsAPI.delete(by: assistantId)
181 |
182 | // Checks
183 | XCTAssertEqual(deleteResponse.id, assistantId)
184 | XCTAssertEqual(deleteResponse.object, "assistant.deleted")
185 | XCTAssertTrue(deleteResponse.deleted)
186 | } catch {
187 | XCTFail("Error: \(error)")
188 | }
189 | }
190 |
191 | func testCreateFile() async {
192 | do {
193 | // Simulate server response
194 | let mockData = AssistantsAPITests.createFile.data(using: .utf8)!
195 |
196 | MockURLProtocol.requestHandler = { request in
197 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
198 | return (response, mockData)
199 | }
200 |
201 | let createRequest = ASACreateAssistantFileRequest(fileId: "file-abc123")
202 | let file: ASAAssistantFile = try await assistantsAPI.createFile(for: "asst_abc123", with: createRequest)
203 |
204 | // Checks
205 | XCTAssertEqual(file.id, "file-abc123")
206 | XCTAssertEqual(file.objectType, "assistant.file")
207 | XCTAssertEqual(file.createdAt, 1699055364)
208 | XCTAssertEqual(file.assistantId, "asst_abc123")
209 | } catch {
210 | XCTFail("Error: \(error)")
211 | }
212 | }
213 |
214 | func testRetrieveFile() async {
215 | do {
216 | // Simulate server response
217 | let mockData = AssistantsAPITests.retrieveFile.data(using: .utf8)!
218 |
219 | MockURLProtocol.requestHandler = { request in
220 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
221 | return (response, mockData)
222 | }
223 |
224 | let file: ASAAssistantFile = try await assistantsAPI.retrieveFile(for: "asst_abc123", fileId: "file-abc123")
225 |
226 | // Checks
227 | XCTAssertEqual(file.id, "file-abc123")
228 | XCTAssertEqual(file.objectType, "assistant.file")
229 | XCTAssertEqual(file.createdAt, 1699055364)
230 | XCTAssertEqual(file.assistantId, "asst_abc123")
231 | } catch {
232 | XCTFail("Error: \(error)")
233 | }
234 | }
235 |
236 | func testDeleteFile() async {
237 | do {
238 | // Simulate server response
239 | let mockData = AssistantsAPITests.deleteFile.data(using: .utf8)!
240 |
241 | MockURLProtocol.requestHandler = { request in
242 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
243 | return (response, mockData)
244 | }
245 |
246 | let deleteResponse: ASADeleteModelResponse = try await assistantsAPI.deleteFile(for: "asst_abc123", fileId: "file-abc123")
247 |
248 | // Checks
249 | XCTAssertEqual(deleteResponse.id, "file-abc123")
250 | XCTAssertEqual(deleteResponse.object, "assistant.file.deleted")
251 | XCTAssertTrue(deleteResponse.deleted)
252 | } catch {
253 | XCTFail("Error: \(error)")
254 | }
255 | }
256 |
257 | func testListFiles() async {
258 | do {
259 | // Simulate server response
260 | let mockData = AssistantsAPITests.listFiles.data(using: .utf8)!
261 |
262 | MockURLProtocol.requestHandler = { request in
263 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
264 | return (response, mockData)
265 | }
266 |
267 | let listParameters = ASAListAssistantsParameters()
268 | let fileList: ASAAssistantFilesListResponse = try await assistantsAPI.listFiles(for: "asst_abc123", with: listParameters)
269 |
270 | // Checks
271 | XCTAssertEqual(fileList.object, "list")
272 | XCTAssertEqual(fileList.data.count, 2)
273 | XCTAssertEqual(fileList.firstId, "file-abc123")
274 | XCTAssertEqual(fileList.lastId, "file-abc456")
275 | XCTAssertFalse(fileList.hasMore)
276 | XCTAssertEqual(fileList.data[0].id, "file-abc123")
277 | XCTAssertEqual(fileList.data[1].id, "file-abc456")
278 | } catch {
279 | XCTFail("Error: \(error)")
280 | }
281 | }
282 | }
283 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/APIs/MessagesAPITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MessagesAPITests.swift
3 | //
4 | //
5 | // Created by Alexey on 11/20/23.
6 | //
7 |
8 | import XCTest
9 | @testable import AISwiftAssist
10 |
11 | final class MessagesAPITests: XCTestCase {
12 |
13 | var messagesAPI: IMessagesAPI!
14 |
15 | override func setUp() {
16 | super.setUp()
17 | let configuration = URLSessionConfiguration.default
18 | configuration.protocolClasses = [MockURLProtocol.self]
19 | let mockURLSession = URLSession(configuration: configuration)
20 | messagesAPI = MessagesAPI(urlSession: mockURLSession)
21 | }
22 |
23 | override func tearDown() {
24 | messagesAPI = nil
25 | super.tearDown()
26 | }
27 |
28 | func testCreateMessage() async {
29 | do {
30 | let mockData = Self.create.data(using: .utf8)!
31 |
32 | MockURLProtocol.requestHandler = { request in
33 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
34 | return (response, mockData)
35 | }
36 |
37 | let createMessageRequest = ASACreateMessageRequest(role: "user", content: "How does AI work? Explain it in simple terms.")
38 | let message: ASAMessage = try await messagesAPI.create(by: "thread123", createMessage: createMessageRequest)
39 |
40 | XCTAssertEqual(message.id, "12345")
41 | XCTAssertEqual(message.object, "thread.message")
42 | XCTAssertEqual(message.createdAt, 1639530000)
43 | XCTAssertEqual(message.threadId, "thread123")
44 | XCTAssertEqual(message.role, "assistant")
45 | XCTAssertEqual(message.content.count, 2)
46 |
47 | if let firstContent = message.content.first {
48 | switch firstContent {
49 | case .text(let textContent):
50 | XCTAssertEqual(textContent.value, "This is a text message with annotations.")
51 | XCTAssertEqual(textContent.annotations?.count, 2)
52 | default:
53 | XCTFail("First content is not of type text.")
54 | }
55 | } else {
56 | XCTFail("First content is empty")
57 | }
58 |
59 | if message.content.count > 1 {
60 | switch message.content[1] {
61 | case .image(let imageContent):
62 | XCTAssertEqual(imageContent.file_id, "image789")
63 | default:
64 | XCTFail("Second content is not of type image.")
65 | }
66 | } else {
67 | XCTFail("Second content is missing.")
68 | }
69 |
70 | XCTAssertEqual(message.assistantId, "assistant123")
71 | XCTAssertEqual(message.runId, "run123")
72 | XCTAssertEqual(message.fileIds, ["file123", "file456", "image789"])
73 | XCTAssertEqual(message.metadata?["key1"], "value1")
74 | XCTAssertEqual(message.metadata?["key2"], "value2")
75 | } catch {
76 | XCTFail("Error: \(error)")
77 | }
78 | }
79 |
80 |
81 | func testRetrieveMessage() async {
82 | do {
83 | let mockData = Self.retrieve.data(using: .utf8)!
84 |
85 | MockURLProtocol.requestHandler = { request in
86 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
87 | return (response, mockData)
88 | }
89 |
90 | let message: ASAMessage = try await messagesAPI.retrieve(by: "thread123", messageId: "12345")
91 |
92 | XCTAssertEqual(message.id, "12345")
93 | XCTAssertEqual(message.object, "thread.message")
94 | XCTAssertEqual(message.createdAt, 1639530000)
95 | XCTAssertEqual(message.threadId, "thread123")
96 | XCTAssertEqual(message.role, "assistant")
97 | XCTAssertEqual(message.content.count, 2)
98 |
99 | if let firstContent = message.content.first {
100 | switch firstContent {
101 | case .text(let textContent):
102 | XCTAssertEqual(textContent.value, "This is a text message with annotations.")
103 | XCTAssertEqual(textContent.annotations?.count, 2)
104 |
105 | if let firstAnnotation = textContent.annotations?.first {
106 | XCTAssertEqual(firstAnnotation.type, "file_citation")
107 | XCTAssertEqual(firstAnnotation.text, "document link")
108 | XCTAssertEqual(firstAnnotation.startIndex, 0)
109 | XCTAssertEqual(firstAnnotation.endIndex, 23)
110 | XCTAssertEqual(firstAnnotation.fileCitation?.fileId, "file123")
111 | XCTAssertEqual(firstAnnotation.fileCitation?.quote, "A quote from the file")
112 | } else {
113 | XCTFail("First annotation not found.")
114 | }
115 | default:
116 | XCTFail("First content is not of type text.")
117 | }
118 | } else {
119 | XCTFail("Content is empty")
120 | }
121 |
122 | if let secondContent = message.content.last {
123 | switch secondContent {
124 | case .image(let imageContent):
125 | XCTAssertEqual(imageContent.file_id, "image789")
126 | default:
127 | XCTFail("Second content is not of type image.")
128 | }
129 | } else {
130 | XCTFail("Second content is missing.")
131 | }
132 |
133 | XCTAssertEqual(message.assistantId, "assistant123")
134 | XCTAssertEqual(message.runId, "run123")
135 | XCTAssertEqual(message.fileIds, ["file123", "file456", "image789"])
136 | XCTAssertEqual(message.metadata?["key1"], "value1")
137 | XCTAssertEqual(message.metadata?["key2"], "value2")
138 | } catch {
139 | XCTFail("Error: \(error)")
140 | }
141 | }
142 |
143 |
144 | func testModifyMessage() async {
145 | do {
146 | let mockData = Self.modify.data(using: .utf8)!
147 |
148 | MockURLProtocol.requestHandler = { request in
149 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
150 | return (response, mockData)
151 | }
152 |
153 | let modifyMessageRequest = ASAModifyMessageRequest(metadata: ["modified": "true",
154 | "user": "abc123"])
155 | let message: ASAMessage = try await messagesAPI.modify(by: "thread123",
156 | messageId: "12345",
157 | modifyMessage: modifyMessageRequest)
158 |
159 | XCTAssertEqual(message.id, "12345")
160 | XCTAssertEqual(message.object, "thread.message")
161 | XCTAssertEqual(message.createdAt, 1639530000)
162 | XCTAssertEqual(message.threadId, "thread123")
163 | XCTAssertEqual(message.role, "assistant")
164 |
165 | if let firstContent = message.content.first {
166 | switch firstContent {
167 | case .text(let textContent):
168 | XCTAssertEqual(textContent.value, "This is a text message with annotations.")
169 | XCTAssertEqual(textContent.annotations?.count, 2)
170 | default:
171 | XCTFail("First content is not of type text.")
172 | }
173 | } else {
174 | XCTFail("Content is empty")
175 | }
176 |
177 | if message.content.count > 1 {
178 | switch message.content[1] {
179 | case .image(let imageContent):
180 | XCTAssertEqual(imageContent.file_id, "image789")
181 | default:
182 | XCTFail("Second content is not of type image.")
183 | }
184 | } else {
185 | XCTFail("Second content is missing.")
186 | }
187 |
188 | XCTAssertEqual(message.assistantId, "assistant123")
189 | XCTAssertEqual(message.runId, "run123")
190 | XCTAssertEqual(message.fileIds, ["file123", "file456", "image789"])
191 | XCTAssertEqual(message.metadata?["key1"], "value1")
192 | XCTAssertEqual(message.metadata?["key2"], "value2")
193 | } catch {
194 | XCTFail("Error: \(error)")
195 | }
196 | }
197 |
198 |
199 | func testListMessages() async {
200 | do {
201 | let mockData = Self.list.data(using: .utf8)!
202 |
203 | MockURLProtocol.requestHandler = { request in
204 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
205 | return (response, mockData)
206 | }
207 |
208 | let listResponse: ASAMessagesListResponse = try await messagesAPI.getMessages(by: "thread_abc123", parameters: nil)
209 |
210 | XCTAssertEqual(listResponse.object, "list")
211 | XCTAssertEqual(listResponse.data.count, 3)
212 | XCTAssertEqual(listResponse.data[0].id, "msg_SM7CyvQn3UnrAyOh96TGN5jR")
213 | XCTAssertEqual(listResponse.data[0].threadId, "thread_11LlFPiPpEw7WhZr0AqB2WhF")
214 | XCTAssertEqual(listResponse.data[0].role, "assistant")
215 |
216 | // Тестирование содержимого первого сообщения
217 | if let firstContent = listResponse.data[0].content.first {
218 | switch firstContent {
219 | case .text(let textContent):
220 | XCTAssertEqual(textContent.value, "I now have full annotations for every date mentioned in the file")
221 | XCTAssertEqual(textContent.annotations?.count, 4)
222 |
223 | if let firstAnnotation = textContent.annotations?.first {
224 | XCTAssertEqual(firstAnnotation.type, "file_citation")
225 | XCTAssertEqual(firstAnnotation.text, "【21†source】")
226 | XCTAssertEqual(firstAnnotation.startIndex, 136)
227 | XCTAssertEqual(firstAnnotation.endIndex, 147)
228 | XCTAssertEqual(firstAnnotation.fileCitation?.fileId, "")
229 | XCTAssertEqual(firstAnnotation.fileCitation?.quote, "adfadlfkjamdf")
230 | } else {
231 | XCTFail("First annotation not found.")
232 | }
233 |
234 | if let firstAnnotation = textContent.annotations?.last {
235 | XCTAssertEqual(firstAnnotation.type, "file_citation")
236 | XCTAssertEqual(firstAnnotation.text, "【33†source】")
237 | XCTAssertEqual(firstAnnotation.startIndex, 378)
238 | XCTAssertEqual(firstAnnotation.endIndex, 389)
239 | XCTAssertEqual(firstAnnotation.fileCitation?.fileId, "")
240 | XCTAssertEqual(firstAnnotation.fileCitation?.quote, "DXB")
241 | } else {
242 | XCTFail("First annotation not found.")
243 | }
244 | default:
245 | XCTFail("First content is not of type text.")
246 | }
247 | } else {
248 | XCTFail("Content is empty")
249 | }
250 |
251 | XCTAssertEqual(listResponse.data[1].id, "msg_oacFAKp8WbIKYnV2Wmsyh5aE")
252 | XCTAssertEqual(listResponse.data[1].threadId, "thread_11LlFPiPpEw7WhZr0AqB2WhF")
253 | XCTAssertEqual(listResponse.data[1].role, "assistant")
254 |
255 | XCTAssertEqual(listResponse.data[2].id, "msg_V8hf7PCvWceW4DpQKpQV83Ia")
256 | XCTAssertEqual(listResponse.data[2].threadId, "thread_11LlFPiPpEw7WhZr0AqB2WhF")
257 | XCTAssertEqual(listResponse.data[2].role, "user")
258 | } catch {
259 | XCTFail("Error: \(error)")
260 | }
261 | }
262 |
263 |
264 | func testRetrieveFile() async {
265 | do {
266 | let mockData = Self.retrieveFile.data(using: .utf8)!
267 |
268 | MockURLProtocol.requestHandler = { request in
269 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
270 | return (response, mockData)
271 | }
272 |
273 | let file: ASAMessageFile = try await messagesAPI.retrieveFile(by: "thread_abc123", messageId: "msg_abc123", fileId: "file-abc123")
274 |
275 | XCTAssertEqual(file.id, "file-abc123")
276 | XCTAssertEqual(file.object, "thread.message.file")
277 | XCTAssertEqual(file.createdAt, 1698107661)
278 | XCTAssertEqual(file.messageId, "message_QLoItBbqwyAJEzlTy4y9kOMM")
279 | } catch {
280 | XCTFail("Error: \(error)")
281 | }
282 | }
283 |
284 | func testListFiles() async {
285 | do {
286 | let mockData = Self.listFiles.data(using: .utf8)!
287 |
288 | MockURLProtocol.requestHandler = { request in
289 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
290 | return (response, mockData)
291 | }
292 |
293 | let listResponse: ASAMessageFilesListResponse = try await messagesAPI.listFiles(by: "thread_abc123", messageId: "msg_abc123", parameters: nil)
294 |
295 | XCTAssertEqual(listResponse.object, "list")
296 | XCTAssertEqual(listResponse.data.count, 2)
297 | XCTAssertEqual(listResponse.data[0].id, "file-abc123")
298 | XCTAssertEqual(listResponse.data[0].createdAt, 1698107661)
299 | XCTAssertEqual(listResponse.data[0].messageId, "message_QLoItBbqwyAJEzlTy4y9kOMM")
300 | } catch {
301 | XCTFail("Error: \(error)")
302 | }
303 | }
304 |
305 | }
306 |
--------------------------------------------------------------------------------
/Tests/AISwiftAssistTests/APIs/RunsAPITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RunsAPITests.swift
3 | //
4 | //
5 | // Created by Alexey on 11/20/23.
6 | //
7 |
8 | import XCTest
9 | @testable import AISwiftAssist
10 |
11 | final class RunsAPITests: XCTestCase {
12 |
13 | var runsAPI: IRunsAPI!
14 |
15 | override func setUp() {
16 | super.setUp()
17 | let configuration = URLSessionConfiguration.default
18 | configuration.protocolClasses = [MockURLProtocol.self]
19 | let mockURLSession = URLSession(configuration: configuration)
20 | runsAPI = RunsAPI(urlSession: mockURLSession)
21 | }
22 |
23 | override func tearDown() {
24 | runsAPI = nil
25 | super.tearDown()
26 | }
27 |
28 | func testCreateRun() async {
29 | do {
30 | let mockData = Self.create.data(using: .utf8)!
31 |
32 | MockURLProtocol.requestHandler = { request in
33 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
34 | return (response, mockData)
35 | }
36 |
37 | let createRunRequest = ASACreateRunRequest(assistantId: "asst_abc123")
38 | let run: ASARun = try await runsAPI.create(by: "thread_abc123",
39 | createRun: createRunRequest)
40 |
41 | XCTAssertEqual(run.id, "run_abc123")
42 | XCTAssertEqual(run.object, "thread.run")
43 | XCTAssertEqual(run.createdAt, 1699063290)
44 | XCTAssertEqual(run.assistantId, "asst_abc123")
45 | XCTAssertEqual(run.threadId, "thread_abc123")
46 | XCTAssertEqual(run.status, "queued")
47 | XCTAssertEqual(run.startedAt, 1699063290)
48 | XCTAssertNil(run.expiresAt)
49 | XCTAssertNil(run.cancelledAt)
50 | XCTAssertNil(run.failedAt)
51 | XCTAssertEqual(run.completedAt, 1699063291)
52 | XCTAssertNil(run.lastError)
53 | XCTAssertEqual(run.model, "gpt-4")
54 | XCTAssertNil(run.instructions)
55 | XCTAssertEqual(run.tools.count, 1)
56 | XCTAssertEqual(run.tools.first?.type, "code_interpreter")
57 | XCTAssertEqual(run.fileIds, ["file-abc123", "file-abc456"])
58 | XCTAssertTrue(run.metadata?.isEmpty ?? true)
59 | } catch {
60 | XCTFail("Error: \(error.localizedDescription)")
61 | }
62 | }
63 |
64 | func testRetrieveRun() async {
65 | do {
66 | let mockData = Self.retrieve.data(using: .utf8)!
67 |
68 | MockURLProtocol.requestHandler = { request in
69 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
70 | return (response, mockData)
71 | }
72 |
73 | let run: ASARun = try await runsAPI.retrieve(by: "thread_abc123",
74 | runId: "run_abc123")
75 |
76 | XCTAssertEqual(run.id, "run_abc123")
77 | XCTAssertEqual(run.object, "thread.run")
78 | XCTAssertEqual(run.createdAt, 1699075072)
79 | XCTAssertEqual(run.assistantId, "asst_abc123")
80 | XCTAssertEqual(run.threadId, "thread_abc123")
81 | XCTAssertEqual(run.status, "completed")
82 | XCTAssertEqual(run.startedAt, 1699075072)
83 | XCTAssertNil(run.expiresAt)
84 | XCTAssertNil(run.cancelledAt)
85 | XCTAssertNil(run.failedAt)
86 | XCTAssertEqual(run.completedAt, 1699075073)
87 | XCTAssertNil(run.lastError)
88 | XCTAssertEqual(run.model, "gpt-3.5-turbo")
89 | XCTAssertNil(run.instructions)
90 | XCTAssertEqual(run.tools.count, 1)
91 | XCTAssertEqual(run.tools.first?.type, "code_interpreter")
92 | XCTAssertEqual(run.fileIds, ["file-abc123", "file-abc456"])
93 | XCTAssertTrue(run.metadata?.isEmpty ?? true)
94 | } catch {
95 | XCTFail("Error: \(error)")
96 | }
97 | }
98 |
99 | func testModifyRun() async {
100 | do {
101 | let mockData = Self.modify.data(using: .utf8)!
102 |
103 | MockURLProtocol.requestHandler = { request in
104 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
105 | return (response, mockData)
106 | }
107 |
108 | let modifyRunRequest = ASAModifyRunRequest(metadata: ["user_id": "user_abc123"])
109 | let run: ASARun = try await runsAPI.modify(by: "thread_abc123",
110 | runId: "run_abc123",
111 | modifyRun: modifyRunRequest)
112 |
113 | XCTAssertEqual(run.id, "run_abc123")
114 | XCTAssertEqual(run.object, "thread.run")
115 | XCTAssertEqual(run.createdAt, 1699075072)
116 | XCTAssertEqual(run.assistantId, "asst_abc123")
117 | XCTAssertEqual(run.threadId, "thread_abc123")
118 | XCTAssertEqual(run.status, "completed")
119 | XCTAssertEqual(run.startedAt, 1699075072)
120 | XCTAssertNil(run.expiresAt)
121 | XCTAssertNil(run.cancelledAt)
122 | XCTAssertNil(run.failedAt)
123 | XCTAssertEqual(run.completedAt, 1699075073)
124 | XCTAssertNil(run.lastError)
125 | XCTAssertEqual(run.model, "gpt-3.5-turbo")
126 | XCTAssertNil(run.instructions)
127 | XCTAssertEqual(run.tools.count, 1)
128 | XCTAssertEqual(run.tools.first?.type, "code_interpreter")
129 | XCTAssertEqual(run.fileIds, ["file-abc123", "file-abc456"])
130 | XCTAssertEqual(run.metadata?["user_id"], "user_abc123")
131 | } catch {
132 | XCTFail("Error: \(error)")
133 | }
134 | }
135 |
136 | func testListRuns() async {
137 | do {
138 | let mockData = Self.list.data(using: .utf8)!
139 |
140 | MockURLProtocol.requestHandler = { request in
141 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
142 | return (response, mockData)
143 | }
144 |
145 | let listResponse: ASARunsListResponse = try await runsAPI.listRuns(by: "thread_abc123",
146 | parameters: nil)
147 |
148 | XCTAssertEqual(listResponse.object, "list")
149 | XCTAssertEqual(listResponse.data.count, 2)
150 |
151 | let firstRun = listResponse.data[0]
152 | XCTAssertEqual(firstRun.id, "run_abc123")
153 | XCTAssertEqual(firstRun.object, "thread.run")
154 | XCTAssertEqual(firstRun.createdAt, 1699075072)
155 | XCTAssertEqual(firstRun.assistantId, "asst_abc123")
156 | XCTAssertEqual(firstRun.threadId, "thread_abc123")
157 | XCTAssertEqual(firstRun.status, "completed")
158 | XCTAssertEqual(firstRun.startedAt, 1699075072)
159 | XCTAssertNil(firstRun.expiresAt)
160 | XCTAssertNil(firstRun.cancelledAt)
161 | XCTAssertNil(firstRun.failedAt)
162 | XCTAssertEqual(firstRun.completedAt, 1699075073)
163 | XCTAssertNil(firstRun.lastError)
164 | XCTAssertEqual(firstRun.model, "gpt-3.5-turbo")
165 | XCTAssertNil(firstRun.instructions)
166 | XCTAssertEqual(firstRun.tools.count, 1)
167 | XCTAssertEqual(firstRun.tools[0].type, "code_interpreter")
168 | XCTAssertEqual(firstRun.fileIds, ["file-abc123", "file-abc456"])
169 | XCTAssertTrue(firstRun.metadata?.isEmpty ?? true)
170 |
171 | XCTAssertEqual(listResponse.firstId, "run_abc123")
172 | XCTAssertEqual(listResponse.lastId, "run_abc456")
173 | XCTAssertFalse(listResponse.hasMore)
174 | } catch {
175 | XCTFail("Error: \(error)")
176 | }
177 | }
178 |
179 | func testSubmitToolOutputs() async {
180 | do {
181 | let mockData = RunsAPITests.submitToolOutputs.data(using: .utf8)!
182 |
183 | MockURLProtocol.requestHandler = { request in
184 | XCTAssertEqual(request.url?.path, "/v1/threads/thread_abc123/runs/run_abc123/submit_tool_outputs")
185 | XCTAssertEqual(request.httpMethod, "POST")
186 | // Проверка тела запроса может быть добавлена здесь
187 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
188 | return (response, mockData)
189 | }
190 |
191 | let toolOutputs = [ASAToolOutput(toolCallId: "call_abc123", output: "28C")]
192 | let run: ASARun = try await runsAPI.submitToolOutputs(by: "thread_abc123", runId: "run_abc123", toolOutputs: toolOutputs)
193 |
194 | XCTAssertEqual(run.id, "run_abc123")
195 | XCTAssertEqual(run.object, "thread.run")
196 | XCTAssertEqual(run.createdAt, 1699063291)
197 | XCTAssertEqual(run.threadId, "thread_abc123")
198 | XCTAssertEqual(run.assistantId, "asst_abc123")
199 | XCTAssertEqual(run.status, "completed")
200 | XCTAssertEqual(run.startedAt, 1699063292)
201 | XCTAssertEqual(run.expiresAt, 1699066891)
202 | XCTAssertNil(run.cancelledAt)
203 | XCTAssertNil(run.failedAt)
204 | XCTAssertEqual(run.completedAt, 1699063391)
205 | XCTAssertNil(run.lastError)
206 | XCTAssertEqual(run.model, "gpt-3.5-turbo")
207 | XCTAssertEqual(run.instructions, "You are a helpful assistant.")
208 | XCTAssertEqual(run.tools.count, 1)
209 | XCTAssertEqual(run.tools.first?.type, "function")
210 | XCTAssertEqual(run.fileIds, ["file-abc123"])
211 | XCTAssertEqual(run.metadata?["additional_info"], "test")
212 | } catch {
213 | XCTFail("Error: \(error.localizedDescription)")
214 | }
215 | }
216 |
217 | func testCancelRun() async {
218 | do {
219 |
220 | let mockData = RunsAPITests.cancelRun.data(using: .utf8)!
221 |
222 | MockURLProtocol.requestHandler = { request in
223 | XCTAssertEqual(request.url?.path, "/v1/threads/thread_abc123/runs/run_abc123/cancel")
224 | XCTAssertEqual(request.httpMethod, "POST")
225 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
226 | return (response, mockData)
227 | }
228 |
229 | let run: ASARun = try await runsAPI.cancelRun(by: "thread_abc123", runId: "run_abc123")
230 |
231 | XCTAssertEqual(run.id, "run_abc123")
232 | XCTAssertEqual(run.object, "thread.run")
233 | XCTAssertEqual(run.status, "cancelled")
234 | XCTAssertEqual(run.createdAt, 1699075072)
235 | XCTAssertEqual(run.assistantId, "asst_abc123")
236 | XCTAssertEqual(run.threadId, "thread_abc123")
237 | XCTAssertEqual(run.status, "cancelled")
238 | XCTAssertEqual(run.startedAt, 1699075072)
239 | XCTAssertEqual(run.expiresAt, 1699075672)
240 | XCTAssertEqual(run.cancelledAt, 1699075092)
241 | XCTAssertNil(run.failedAt)
242 | XCTAssertNil(run.completedAt)
243 | XCTAssertNil(run.lastError)
244 | XCTAssertEqual(run.model, "gpt-3.5-turbo")
245 | XCTAssertEqual(run.instructions, "Provide instructions")
246 | XCTAssertTrue(run.tools.isEmpty)
247 | XCTAssertEqual(run.fileIds, ["file-abc123"])
248 | XCTAssertEqual(run.metadata?["key"], "value")
249 | } catch {
250 | XCTFail("Error: \(error.localizedDescription)")
251 | }
252 | }
253 |
254 | func testCreateThreadAndRun() async {
255 | do {
256 | let mockData = RunsAPITests.createThreadAndRun.data(using: .utf8)!
257 |
258 | MockURLProtocol.requestHandler = { request in
259 | XCTAssertEqual(request.url?.path, "/v1/threads/runs")
260 | XCTAssertEqual(request.httpMethod, "POST")
261 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
262 | return (response, mockData)
263 | }
264 |
265 | let createThreadRunRequest = ASACreateThreadRunRequest(assistantId: "", thread: ASACreateThreadRunRequest.Thread(messages: []))
266 | let run: ASARun = try await runsAPI.createThreadAndRun(createThreadRun: createThreadRunRequest)
267 |
268 | XCTAssertEqual(run.id, "run_xyz123")
269 | XCTAssertEqual(run.object, "thread.run")
270 | XCTAssertEqual(run.createdAt, 1699080000)
271 | XCTAssertEqual(run.threadId, "thread_xyz123")
272 | XCTAssertEqual(run.assistantId, "asst_xyz123")
273 | XCTAssertEqual(run.status, "in_progress")
274 | XCTAssertEqual(run.startedAt, 1699080001)
275 | XCTAssertEqual(run.expiresAt, 1699080600)
276 | XCTAssertNil(run.cancelledAt)
277 | XCTAssertNil(run.failedAt)
278 | XCTAssertNil(run.completedAt)
279 | XCTAssertNil(run.lastError)
280 | XCTAssertEqual(run.model, "gpt-3.5-turbo")
281 | XCTAssertEqual(run.instructions, "Explain deep learning to a 5 year old.")
282 | XCTAssertEqual(run.tools.count, 1)
283 | XCTAssertEqual(run.fileIds, ["file-xyz123"])
284 | XCTAssertEqual(run.metadata?["session"], "1")
285 | } catch {
286 | XCTFail("Error: \(error.localizedDescription)")
287 | }
288 | }
289 |
290 | func testRetrieveRunStep() async {
291 | do {
292 | let mockData = RunsAPITests.retrieveRunStep.data(using: .utf8)!
293 |
294 | MockURLProtocol.requestHandler = { request in
295 | XCTAssertEqual(request.url?.path, "/v1/threads/thread_abc123/runs/run_abc123/steps/step_xyz123")
296 | XCTAssertEqual(request.httpMethod, "GET")
297 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
298 | return (response, mockData)
299 | }
300 |
301 | let runStep: ASARunStep = try await runsAPI.retrieveRunStep(by: "thread_abc123", runId: "run_abc123", stepId: "step_xyz123")
302 |
303 | XCTAssertEqual(runStep.id, "step_abc123")
304 | XCTAssertEqual(runStep.object, "thread.run.step")
305 | XCTAssertEqual(runStep.createdAt, 1699063291)
306 | XCTAssertEqual(runStep.runId, "run_abc123")
307 | XCTAssertEqual(runStep.assistantId, "asst_abc123")
308 | XCTAssertEqual(runStep.threadId, "thread_abc123")
309 | XCTAssertEqual(runStep.type, "message_creation")
310 | XCTAssertEqual(runStep.status, "completed")
311 | XCTAssertNil(runStep.cancelledAt)
312 | XCTAssertEqual(runStep.completedAt, 1699063291)
313 | XCTAssertNil(runStep.expiredAt)
314 | XCTAssertNil(runStep.failedAt)
315 | XCTAssertNil(runStep.lastError)
316 | } catch {
317 | XCTFail("Error: \(error.localizedDescription)")
318 | }
319 | }
320 |
321 | func testListRunSteps() async {
322 | do {
323 | let mockData = RunsAPITests.listRunSteps.data(using: .utf8)!
324 |
325 | MockURLProtocol.requestHandler = { request in
326 | XCTAssertEqual(request.url?.path, "/v1/threads/thread_abc123/runs/run_abc123/steps")
327 | XCTAssertEqual(request.httpMethod, "GET")
328 | let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
329 | return (response, mockData)
330 | }
331 |
332 | let listResponse: ASARunStepsListResponse = try await runsAPI.listRunSteps(by: "thread_abc123", runId: "run_abc123", parameters: nil)
333 |
334 | XCTAssertEqual(listResponse.object, "list")
335 | XCTAssertEqual(listResponse.data.count, 1)
336 |
337 | let firstStep = listResponse.data[0]
338 | XCTAssertEqual(firstStep.id, "step_xyz123")
339 | XCTAssertEqual(firstStep.object, "thread.run.step")
340 | XCTAssertEqual(firstStep.createdAt, 1699080100)
341 | XCTAssertEqual(firstStep.runId, "run_xyz123")
342 | XCTAssertEqual(firstStep.assistantId, "asst_xyz123")
343 | XCTAssertEqual(firstStep.threadId, "thread_xyz123")
344 | XCTAssertEqual(firstStep.type, "message_creation")
345 | XCTAssertEqual(firstStep.status, "completed")
346 | XCTAssertNil(firstStep.cancelledAt)
347 | XCTAssertEqual(firstStep.completedAt, 1699080200)
348 | XCTAssertNil(firstStep.expiredAt)
349 | XCTAssertNil(firstStep.failedAt)
350 | XCTAssertNil(firstStep.lastError)
351 |
352 | } catch {
353 | XCTFail("Error: \(error.localizedDescription)")
354 | }
355 | }
356 |
357 |
358 | }
359 |
--------------------------------------------------------------------------------