├── .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 | [![Buy me a coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](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 | --------------------------------------------------------------------------------