diff --git a/Sources/OpenAI/AIProxy/AIProxyService.swift b/Sources/OpenAI/AIProxy/AIProxyService.swift index cfd78bf..da27dab 100644 --- a/Sources/OpenAI/AIProxy/AIProxyService.swift +++ b/Sources/OpenAI/AIProxy/AIProxyService.swift @@ -653,10 +653,11 @@ struct AIProxyService: OpenAIService { } func modifyVectorStore( + parameters: VectorStoreParameter, id: String) async throws -> VectorStoreObject { - let request = try await OpenAIAPI.vectorStore(.modify(vectorStoreID: id)).request(aiproxyPartialKey: partialKey, organizationID: organizationID, method: .post, betaHeaderField: Self.assistantsBetaV2, deviceCheckBypass: deviceCheckBypass) + let request = try await OpenAIAPI.vectorStore(.modify(vectorStoreID: id)).request(aiproxyPartialKey: partialKey, organizationID: organizationID, method: .post, params: parameters, betaHeaderField: Self.assistantsBetaV2, deviceCheckBypass: deviceCheckBypass) return try await fetch(type: VectorStoreObject.self, with: request) } diff --git a/Sources/OpenAI/Azure/AzureOpenAIAPI.swift b/Sources/OpenAI/Azure/AzureOpenAIAPI.swift index 8097a65..39766ad 100644 --- a/Sources/OpenAI/Azure/AzureOpenAIAPI.swift +++ b/Sources/OpenAI/Azure/AzureOpenAIAPI.swift @@ -13,7 +13,76 @@ enum AzureOpenAIAPI { static var azureOpenAIResource: String = "" - case chat(deploymentID: String) // https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions + /// https://learn.microsoft.com/en-us/azure/ai-services/openai/assistants-reference?tabs=python + /// https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/assistant + case assistant(AssistantCategory) + /// https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions + case chat(deploymentID: String) + /// https://learn.microsoft.com/en-us/azure/ai-services/openai/assistants-reference-messages?tabs=python + case message(MessageCategory) + /// https://learn.microsoft.com/en-us/azure/ai-services/openai/assistants-reference-runs?tabs=python + case run(RunCategory) + /// https://learn.microsoft.com/en-us/azure/ai-services/openai/assistants-reference-runs?tabs=python#list-run-steps + case runStep(RunStepCategory) + /// https://learn.microsoft.com/en-us/azure/ai-services/openai/assistants-reference-threads?tabs=python#create-a-thread + case thread(ThreadCategory) + /// https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/file-search?tabs=python#vector-stores + case vectorStore(VectorStoreCategory) + /// https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/file-search?tabs=python#vector-stores + case vectorStoreFile(VectorStoreFileCategory) + + enum AssistantCategory { + case create + case list + case retrieve(assistantID: String) + case modify(assistantID: String) + case delete(assistantID: String) + } + + enum MessageCategory { + case create(threadID: String) + case retrieve(threadID: String, messageID: String) + case modify(threadID: String, messageID: String) + case list(threadID: String) + } + + enum RunCategory { + case create(threadID: String) + case retrieve(threadID: String, runID: String) + case modify(threadID: String, runID: String) + case list(threadID: String) + case cancel(threadID: String, runID: String) + case submitToolOutput(threadID: String, runID: String) + case createThreadAndRun + } + + enum RunStepCategory { + case retrieve(threadID: String, runID: String, stepID: String) + case list(threadID: String, runID: String) + } + + enum ThreadCategory { + case create + case retrieve(threadID: String) + case modify(threadID: String) + case delete(threadID: String) + } + + enum VectorStoreCategory { + case create + case list + case retrieve(vectorStoreID: String) + case modify(vectorStoreID: String) + case delete(vectorStoreID: String) + } + + /// https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/file-search?tabs=python#file-search-support + enum VectorStoreFileCategory { + case create(vectorStoreID: String) + case list(vectorStoreID: String) + case retrieve(vectorStoreID: String, fileID: String) + case delete(vectorStoreID: String, fileID: String) + } } // MARK: Endpoint @@ -21,12 +90,50 @@ enum AzureOpenAIAPI { extension AzureOpenAIAPI: Endpoint { var base: String { - "https://\(Self.azureOpenAIResource).openai.azure.com" + "https://\(Self.azureOpenAIResource)/openai.azure.com" } var path: String { switch self { - case .chat(let deploymentID): "/openai/deployments/\(deploymentID)/chat/completions" + case .chat(let deploymentID): return "/openai/deployments/\(deploymentID)/chat/completions" + case .assistant(let category): + switch category { + case .create, .list: return "/openai/assistants" + case .retrieve(let assistantID), .modify(let assistantID), .delete(let assistantID): return "/openai/assistants/\(assistantID)" + } + case .message(let category): + switch category { + case .create(let threadID), .list(let threadID): return "/openai/threads/\(threadID)/messages" + case .retrieve(let threadID, let messageID), .modify(let threadID, let messageID): return "/openai/threads/\(threadID)/messages/\(messageID)" + } + case .run(let category): + switch category { + case .create(let threadID), .list(let threadID): return "/openai/threads/\(threadID)/runs" + case .retrieve(let threadID, let runID), .modify(let threadID, let runID): return "/openai/threads/\(threadID)/runs/\(runID)" + case .cancel(let threadID, let runID): return "/openai/threads/\(threadID)/runs/\(runID)/cancel" + case .submitToolOutput(let threadID, let runID): return "/openai/threads/\(threadID)/runs/\(runID)//submit_tool_outputs" + case .createThreadAndRun: return "/openai/threads/runs" + } + case .runStep(let category): + switch category { + case .retrieve(let threadID, let runID, let stepID): return "/openai/threads/\(threadID)/runs/\(runID)/steps/\(stepID)" + case .list(let threadID, let runID): return "/openai/threads/\(threadID)/runs/\(runID)/steps" + } + case .thread(let category): + switch category { + case .create: return "/openai/threads" + case .retrieve(let threadID), .modify(let threadID), .delete(let threadID): return "/openai/threads/\(threadID)" + } + case .vectorStore(let category): + switch category { + case .create, .list: return "/openai/vector_stores" + case .retrieve(let vectorStoreID), .modify(let vectorStoreID), .delete(let vectorStoreID): return "/openai/vector_stores/\(vectorStoreID)" + } + case .vectorStoreFile(let category): + switch category { + case .create(let vectorStoreID), .list(let vectorStoreID): return "/openai/vector_stores/\(vectorStoreID)/files" + case .retrieve(let vectorStoreID, let fileID), .delete(let vectorStoreID, let fileID): return "/openai/vector_stores/\(vectorStoreID)/files/\(fileID)" + } } } } diff --git a/Sources/OpenAI/Azure/AzureOpenAIConfiguration.swift b/Sources/OpenAI/Azure/AzureOpenAIConfiguration.swift index a0f4960..c8cf690 100644 --- a/Sources/OpenAI/Azure/AzureOpenAIConfiguration.swift +++ b/Sources/OpenAI/Azure/AzureOpenAIConfiguration.swift @@ -19,13 +19,18 @@ public struct AzureOpenAIConfiguration { /// The API version to use for this operation. This follows the YYYY-MM-DD format. let apiVersion: String + /// Azure configuration extra headers for a request. + let extraHeaders: [String: String]? + public init( resourceName: String, openAIAPIKey: Authorization, - apiVersion: String) + apiVersion: String, + extraHeaders: [String: String]? = nil) { self.resourceName = resourceName self.openAIAPIKey = openAIAPIKey self.apiVersion = apiVersion + self.extraHeaders = extraHeaders } } diff --git a/Sources/OpenAI/Azure/DefaultOpenAIAzureService.swift b/Sources/OpenAI/Azure/DefaultOpenAIAzureService.swift index 3760f8d..31e79ad 100644 --- a/Sources/OpenAI/Azure/DefaultOpenAIAzureService.swift +++ b/Sources/OpenAI/Azure/DefaultOpenAIAzureService.swift @@ -18,13 +18,18 @@ final public class DefaultOpenAIAzureService: OpenAIService { self.decoder = decoder AzureOpenAIAPI.azureOpenAIResource = azureConfiguration.resourceName apiKey = azureConfiguration.openAIAPIKey - apiVersion = azureConfiguration.apiVersion + extraHeaders = azureConfiguration.extraHeaders + initialQueryItems = [.init(name: "api-version", value: azureConfiguration.apiVersion)] } public let session: URLSession public let decoder: JSONDecoder private let apiKey: Authorization - private let apiVersion: String + private let initialQueryItems: [URLQueryItem] + + // Assistants API + private let extraHeaders: [String: String]? + private static let assistantsBetaV2 = "assistants=v2" public func createTranscription(parameters: AudioTranscriptionParameters) async throws -> AudioObject { fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") @@ -41,26 +46,24 @@ final public class DefaultOpenAIAzureService: OpenAIService { public func startChat(parameters: ChatCompletionParameters) async throws -> ChatCompletionObject { var chatParameters = parameters chatParameters.stream = false - let queryItems: [URLQueryItem] = [.init(name: "api-version", value: apiVersion)] let request = try AzureOpenAIAPI.chat(deploymentID: parameters.model).request( apiKey: apiKey, organizationID: nil, method: .post, params: chatParameters, - queryItems: queryItems) + queryItems: initialQueryItems) return try await fetch(type: ChatCompletionObject.self, with: request) } public func startStreamedChat(parameters: ChatCompletionParameters) async throws -> AsyncThrowingStream { var chatParameters = parameters chatParameters.stream = true - let queryItems: [URLQueryItem] = [.init(name: "api-version", value: apiVersion)] let request = try AzureOpenAIAPI.chat(deploymentID: parameters.model).request( apiKey: apiKey, organizationID: nil, method: .post, params: chatParameters, - queryItems: queryItems + queryItems: initialQueryItems ) return try await fetchStream(type: ChatCompletionChunkObject.self, with: request) } @@ -142,97 +145,332 @@ final public class DefaultOpenAIAzureService: OpenAIService { } public func createAssistant(parameters: AssistantParameters) async throws -> AssistantObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.assistant(.create).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders + ) + return try await fetch(type: AssistantObject.self, with: request) } public func retrieveAssistant(id: String) async throws -> AssistantObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.assistant(.retrieve(assistantID: id)).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders + ) + return try await fetch(type: AssistantObject.self, with: request) } public func modifyAssistant(id: String, parameters: AssistantParameters) async throws -> AssistantObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.assistant(.modify(assistantID: id)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders + ) + return try await fetch(type: AssistantObject.self, with: request) } public func deleteAssistant(id: String) async throws -> DeletionStatus { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.assistant(.delete(assistantID: id)).request( + apiKey: apiKey, + organizationID: nil, + method: .delete, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders + ) + return try await fetch(type: DeletionStatus.self, with: request) } public func listAssistants(limit: Int?, order: String?, after: String?, before: String?) async throws -> OpenAIResponse { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + var queryItems: [URLQueryItem] = initialQueryItems + if let limit { + queryItems.append(.init(name: "limit", value: "\(limit)")) + } + if let order { + queryItems.append(.init(name: "order", value: order)) + } + if let after { + queryItems.append(.init(name: "after", value: after)) + } + if let before { + queryItems.append(.init(name: "before", value: before)) + } + let request = try AzureOpenAIAPI.assistant(.list).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: queryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders + ) + return try await fetch(type: OpenAIResponse.self, with: request) } public func createThread(parameters: CreateThreadParameters) async throws -> ThreadObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.thread(.create).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: ThreadObject.self, with: request) } public func retrieveThread(id: String) async throws -> ThreadObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.thread(.retrieve(threadID: id)).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: ThreadObject.self, with: request) } public func modifyThread(id: String, parameters: ModifyThreadParameters) async throws -> ThreadObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.thread(.modify(threadID: id)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: ThreadObject.self, with: request) } public func deleteThread(id: String) async throws -> DeletionStatus { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.thread(.delete(threadID: id)).request( + apiKey: apiKey, + organizationID: nil, + method: .delete, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: DeletionStatus.self, with: request) } public func createMessage(threadID: String, parameters: MessageParameter) async throws -> MessageObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.message(.create(threadID: threadID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: MessageObject.self, with: request) } public func retrieveMessage(threadID: String, messageID: String) async throws -> MessageObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.message(.retrieve(threadID: threadID, messageID: messageID)).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: MessageObject.self, with: request) } public func modifyMessage(threadID: String, messageID: String, parameters: ModifyMessageParameters) async throws -> MessageObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.message(.modify(threadID: threadID, messageID: messageID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: MessageObject.self, with: request) } public func listMessages(threadID: String, limit: Int?, order: String?, after: String?, before: String?, runID: String?) async throws -> OpenAIResponse { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + var queryItems: [URLQueryItem] = initialQueryItems + if let limit { + queryItems.append(.init(name: "limit", value: "\(limit)")) + } + if let order { + queryItems.append(.init(name: "order", value: order)) + } + if let after { + queryItems.append(.init(name: "after", value: after)) + } + if let before { + queryItems.append(.init(name: "before", value: before)) + } + if let runID { + queryItems.append(.init(name: "run_id", value: runID)) + } + let request = try AzureOpenAIAPI.message(.list(threadID: threadID)).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: queryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: OpenAIResponse.self, with: request) } public func createRun(threadID: String, parameters: RunParameter) async throws -> RunObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.run(.create(threadID: threadID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: RunObject.self, with: request) } public func retrieveRun(threadID: String, runID: String) async throws -> RunObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.run(.retrieve(threadID: threadID, runID: runID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: RunObject.self, with: request) } public func modifyRun(threadID: String, runID: String, parameters: ModifyRunParameters) async throws -> RunObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.run(.modify(threadID: threadID, runID: runID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: RunObject.self, with: request) } public func listRuns(threadID: String, limit: Int?, order: String?, after: String?, before: String?) async throws -> OpenAIResponse { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + var queryItems: [URLQueryItem] = initialQueryItems + if let limit { + queryItems.append(.init(name: "limit", value: "\(limit)")) + } + if let order { + queryItems.append(.init(name: "order", value: order)) + } + if let after { + queryItems.append(.init(name: "after", value: after)) + } + if let before { + queryItems.append(.init(name: "before", value: before)) + } + let request = try AzureOpenAIAPI.run(.list(threadID: threadID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + queryItems: queryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: OpenAIResponse.self, with: request) } public func cancelRun(threadID: String, runID: String) async throws -> RunObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.run(.cancel(threadID: threadID, runID: runID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: RunObject.self, with: request) } public func submitToolOutputsToRun(threadID: String, runID: String, parameters: RunToolsOutputParameter) async throws -> RunObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.run(.submitToolOutput(threadID: threadID, runID: runID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: RunObject.self, with: request) } public func createThreadAndRun(parameters: CreateThreadAndRunParameter) async throws -> RunObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.run(.createThreadAndRun).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: RunObject.self, with: request) } public func retrieveRunstep(threadID: String, runID: String, stepID: String) async throws -> RunStepObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try OpenAIAPI.runStep(.retrieve(threadID: threadID, runID: runID, stepID: stepID)).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: RunStepObject.self, with: request) } public func listRunSteps(threadID: String, runID: String, limit: Int?, order: String?, after: String?, before: String?) async throws -> OpenAIResponse { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + var queryItems: [URLQueryItem] = initialQueryItems + if let limit { + queryItems.append(.init(name: "limit", value: "\(limit)")) + } + if let order { + queryItems.append(.init(name: "order", value: order)) + } + if let after { + queryItems.append(.init(name: "after", value: after)) + } + if let before { + queryItems.append(.init(name: "before", value: before)) + } + let request = try AzureOpenAIAPI.runStep(.list(threadID: threadID, runID: runID)).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: queryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: OpenAIResponse.self, with: request) } public func createThreadAndRunStream( parameters: CreateThreadAndRunParameter) - async throws -> AsyncThrowingStream { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + async throws -> AsyncThrowingStream + { + var runParameters = parameters + runParameters.stream = true + let request = try AzureOpenAIAPI.run(.createThreadAndRun).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: runParameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetchAssistantStreamEvents(with: request) } public func createRunStream( @@ -240,15 +478,36 @@ final public class DefaultOpenAIAzureService: OpenAIService { parameters: RunParameter) async throws -> AsyncThrowingStream { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + var runParameters = parameters + runParameters.stream = true + let request = try AzureOpenAIAPI.run(.create(threadID: threadID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: runParameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetchAssistantStreamEvents(with: request) } public func submitToolOutputsToRunStream( threadID: String, runID: String, parameters: RunToolsOutputParameter) - async throws -> AsyncThrowingStream { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + async throws -> AsyncThrowingStream + { + var runToolsOutputParameter = parameters + runToolsOutputParameter.stream = true + let request = try AzureOpenAIAPI.run(.submitToolOutput(threadID: threadID, runID: runID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetchAssistantStreamEvents(with: request) } // MARK: Batch @@ -287,7 +546,15 @@ final public class DefaultOpenAIAzureService: OpenAIService { parameters: VectorStoreParameter) async throws -> VectorStoreObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.vectorStore(.create).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: VectorStoreObject.self, with: request) } public func listVectorStores( @@ -297,46 +564,134 @@ final public class DefaultOpenAIAzureService: OpenAIService { before: String?) async throws -> OpenAIResponse { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + var queryItems: [URLQueryItem] = initialQueryItems + if let limit { + queryItems.append(.init(name: "limit", value: "\(limit)")) + } + if let order { + queryItems.append(.init(name: "order", value: order)) + } + if let after { + queryItems.append(.init(name: "after", value: after)) + } + if let before { + queryItems.append(.init(name: "before", value: before)) + } + let request = try AzureOpenAIAPI.vectorStore(.list).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: queryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: OpenAIResponse.self, with: request) } public func retrieveVectorStore( id: String) async throws -> VectorStoreObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.vectorStore(.retrieve(vectorStoreID: id)).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: VectorStoreObject.self, with: request) } public func modifyVectorStore( + parameters: VectorStoreParameter, id: String) async throws -> VectorStoreObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.vectorStore(.modify(vectorStoreID: id)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: VectorStoreObject.self, with: request) } public func deleteVectorStore( id: String) async throws -> DeletionStatus { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.vectorStore(.delete(vectorStoreID: id)).request( + apiKey: apiKey, + organizationID: nil, + method: .delete, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: DeletionStatus.self, with: request) } // MARK: Vector Store Files public func createVectorStoreFile(vectorStoreID: String, parameters: VectorStoreFileParameter) async throws -> VectorStoreFileObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.vectorStoreFile(.create(vectorStoreID: vectorStoreID)).request( + apiKey: apiKey, + organizationID: nil, + method: .post, + params: parameters, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: VectorStoreFileObject.self, with: request) } public func listVectorStoreFiles(vectorStoreID: String, limit: Int?, order: String?, after: String?, before: String?, filter: String?) async throws -> OpenAIResponse { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + var queryItems: [URLQueryItem] = initialQueryItems + if let limit { + queryItems.append(.init(name: "limit", value: "\(limit)")) + } + if let order { + queryItems.append(.init(name: "order", value: order)) + } + if let after { + queryItems.append(.init(name: "after", value: after)) + } + if let before { + queryItems.append(.init(name: "before", value: before)) + } + if let filter { + queryItems.append(.init(name: "filter", value: filter)) + } + let request = try AzureOpenAIAPI.vectorStoreFile(.list(vectorStoreID: vectorStoreID)).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: queryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: OpenAIResponse.self, with: request) } public func retrieveVectorStoreFile(vectorStoreID: String, fileID: String) async throws -> VectorStoreFileObject { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.vectorStoreFile(.retrieve(vectorStoreID: vectorStoreID, fileID: fileID)).request( + apiKey: apiKey, + organizationID: nil, + method: .get, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: VectorStoreFileObject.self, with: request) } public func deleteVectorStoreFile(vectorStoreID: String, fileID: String) async throws -> DeletionStatus { - fatalError("Currently, this API is not supported. We welcome and encourage contributions to our open-source project. Please consider opening an issue or submitting a pull request to add support for this feature.") + let request = try AzureOpenAIAPI.vectorStoreFile(.delete(vectorStoreID: vectorStoreID, fileID: fileID)).request( + apiKey: apiKey, + organizationID: nil, + method: .delete, + queryItems: initialQueryItems, + betaHeaderField: Self.assistantsBetaV2, + extraHeaders: extraHeaders) + return try await fetch(type: DeletionStatus.self, with: request) } public func createVectorStoreFileBatch(vectorStoreID: String, parameters: VectorStoreFileBatchParameter) async throws -> VectorStoreFileBatchObject { diff --git a/Sources/OpenAI/Private/Networking/Endpoint.swift b/Sources/OpenAI/Private/Networking/Endpoint.swift index a4697f0..d47ccd4 100644 --- a/Sources/OpenAI/Private/Networking/Endpoint.swift +++ b/Sources/OpenAI/Private/Networking/Endpoint.swift @@ -45,7 +45,8 @@ extension Endpoint { method: HTTPMethod, params: Encodable? = nil, queryItems: [URLQueryItem] = [], - betaHeaderField: String? = nil) + betaHeaderField: String? = nil, + extraHeaders: [String: String]? = nil) throws -> URLRequest { var request = URLRequest(url: urlComponents(queryItems: queryItems).url!) @@ -57,6 +58,11 @@ extension Endpoint { if let betaHeaderField { request.addValue(betaHeaderField, forHTTPHeaderField: "OpenAI-Beta") } + if let extraHeaders { + for header in extraHeaders { + request.addValue(header.value, forHTTPHeaderField: header.key) + } + } request.httpMethod = method.rawValue if let params { request.httpBody = try JSONEncoder().encode(params) diff --git a/Sources/OpenAI/Private/Networking/OpenAIAPI.swift b/Sources/OpenAI/Private/Networking/OpenAIAPI.swift index 6925c4b..ee85e84 100644 --- a/Sources/OpenAI/Private/Networking/OpenAIAPI.swift +++ b/Sources/OpenAI/Private/Networking/OpenAIAPI.swift @@ -1,6 +1,6 @@ // -// File.swift -// +// OpenAIAPI.swift +// // // Created by James Rochabrun on 10/10/23. // diff --git a/Sources/OpenAI/Public/Parameters/Assistant/AssistantParameters.swift b/Sources/OpenAI/Public/Parameters/Assistant/AssistantParameters.swift index 520e4f8..fe89dec 100644 --- a/Sources/OpenAI/Public/Parameters/Assistant/AssistantParameters.swift +++ b/Sources/OpenAI/Public/Parameters/Assistant/AssistantParameters.swift @@ -51,6 +51,41 @@ public struct AssistantParameters: Encodable { case toolResources = "tool_resources" } + // Encoding only no nil or non empty parameters, this will avoid sending nil values when using this parameter in the "modifyAssistant" request. + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + if let model = model { + try container.encode(model, forKey: .model) + } + if let name = name { + try container.encode(name, forKey: .name) + } + if let description = description { + try container.encode(description, forKey: .description) + } + if let instructions = instructions { + try container.encode(instructions, forKey: .instructions) + } + if !tools.isEmpty { + try container.encode(tools, forKey: .tools) + } + if let toolResources = toolResources { + try container.encode(toolResources, forKey: .toolResources) + } + if let metadata = metadata { + try container.encode(metadata, forKey: .metadata) + } + if let temperature = temperature { + try container.encode(temperature, forKey: .temperature) + } + if let topP = topP { + try container.encode(topP, forKey: .topP) + } + if let responseFormat = responseFormat { + try container.encode(responseFormat, forKey: .responseFormat) + } + } + public enum Action { case create(model: String) // model is required on creation of assistant. case modify(model: String?) // model is optional on modification of assistant. @@ -75,33 +110,15 @@ public struct AssistantParameters: Encodable { topP: Double? = nil, responseFormat: ResponseFormat? = nil) { - if let action { - self.model = action.model ?? self.model - } - if let name { - self.name = name - } - if let description { - self.description = description - } - if let instructions { - self.instructions = instructions - } + self.model = action?.model + self.name = name + self.description = description + self.instructions = instructions self.tools = tools - if let toolResources { - self.toolResources = toolResources - } - if let metadata { - self.metadata = metadata - } - if let temperature { - self.temperature = temperature - } - if let topP { - self.topP = topP - } - if let responseFormat { - self.responseFormat = responseFormat - } + self.toolResources = toolResources + self.metadata = metadata + self.temperature = temperature + self.topP = topP + self.responseFormat = responseFormat } } diff --git a/Sources/OpenAI/Public/Parameters/VectorStore/VectorStoreParameter.swift b/Sources/OpenAI/Public/Parameters/VectorStore/VectorStoreParameter.swift index b777085..d73b243 100644 --- a/Sources/OpenAI/Public/Parameters/VectorStore/VectorStoreParameter.swift +++ b/Sources/OpenAI/Public/Parameters/VectorStore/VectorStoreParameter.swift @@ -30,6 +30,23 @@ public struct VectorStoreParameter: Encodable { case metadata } + // Encoding only no nil parameters, this will avoid sending nil values when using this parameter in the "modifyVectorStore" request. + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + if let fileIDS = fileIDS { + try container.encode(fileIDS, forKey: .fileIDS) + } + if let name = name { + try container.encode(name, forKey: .name) + } + if let expiresAfter = expiresAfter { + try container.encode(expiresAfter, forKey: .expiresAfter) + } + if let metadata = metadata { + try container.encode(metadata, forKey: .metadata) + } + } + public init( fileIDS: [String]? = nil, name: String? = nil, diff --git a/Sources/OpenAI/Public/Service/DefaultOpenAIService.swift b/Sources/OpenAI/Public/Service/DefaultOpenAIService.swift index 482e9ef..be97308 100644 --- a/Sources/OpenAI/Public/Service/DefaultOpenAIService.swift +++ b/Sources/OpenAI/Public/Service/DefaultOpenAIService.swift @@ -652,10 +652,11 @@ struct DefaultOpenAIService: OpenAIService { } func modifyVectorStore( + parameters: VectorStoreParameter, id: String) async throws -> VectorStoreObject { - let request = try OpenAIAPI.vectorStore(.modify(vectorStoreID: id)).request(apiKey: apiKey, organizationID: organizationID, method: .post, betaHeaderField: Self.assistantsBetaV2) + let request = try OpenAIAPI.vectorStore(.modify(vectorStoreID: id)).request(apiKey: apiKey, organizationID: organizationID, method: .post, params: parameters, betaHeaderField: Self.assistantsBetaV2) return try await fetch(type: VectorStoreObject.self, with: request) } diff --git a/Sources/OpenAI/Public/Service/OpenAIService.swift b/Sources/OpenAI/Public/Service/OpenAIService.swift index b9b5c38..136956b 100644 --- a/Sources/OpenAI/Public/Service/OpenAIService.swift +++ b/Sources/OpenAI/Public/Service/OpenAIService.swift @@ -731,7 +731,7 @@ public protocol OpenAIService { /// Create a vector store. /// - /// - Parameter parameters: The parameters needed to create a batc,. + /// - Parameter parameters: The parameters needed to create a vector store. /// - Returns: A [Vector store](https://platform.openai.com/docs/api-reference/vector-stores) object. /// - Throws: An error if the request fails /// @@ -771,11 +771,13 @@ public protocol OpenAIService { /// Modifies a vector store. /// /// - Parameter id: The ID of the vector store to modify. + /// - Parameter parameters: The parameters needed to modify a vector store. /// - Returns: A [Vector Store](https://platform.openai.com/docs/api-reference/vector-stores) matching the specified ID.. /// - Throws: An error if the request fails. /// /// For more information, refer to [OpenAI's Batch documentation](https://platform.openai.com/docs/api-reference/vector-stores/modify). func modifyVectorStore( + parameters: VectorStoreParameter, id: String) async throws -> VectorStoreObject