diff --git a/Sources/AnyLanguageModel/Models/AnthropicLanguageModel.swift b/Sources/AnyLanguageModel/Models/AnthropicLanguageModel.swift index d074433b..97795f1a 100644 --- a/Sources/AnyLanguageModel/Models/AnthropicLanguageModel.swift +++ b/Sources/AnyLanguageModel/Models/AnthropicLanguageModel.swift @@ -41,8 +41,8 @@ public struct AnthropicLanguageModel: LanguageModel { /// The base URL for the API endpoint. public let baseURL: URL - /// The API key for authentication. - public let apiKey: String + /// The closure providing the API key for authentication. + private let tokenProvider: @Sendable () -> String /// The API version to use for requests. public let apiVersion: String @@ -54,20 +54,19 @@ public struct AnthropicLanguageModel: LanguageModel { public let model: String private let urlSession: URLSession - private let headers: [String: String] /// Creates an Anthropic language model. /// /// - Parameters: /// - baseURL: The base URL for the API endpoint. Defaults to Anthropic's official API. - /// - apiKey: Your Anthropic API key. + /// - apiKey: Your Anthropic API key or a closure that returns it. /// - apiVersion: The API version to use for requests. Defaults to `2023-06-01`. /// - betas: Optional beta version(s) of the API to use. /// - model: The model identifier (for example, "claude-3-5-sonnet-20241022"). /// - session: The URL session to use for network requests. public init( baseURL: URL = defaultBaseURL, - apiKey: String, + apiKey tokenProvider: @escaping @autoclosure @Sendable () -> String, apiVersion: String = defaultAPIVersion, betas: [String]? = nil, model: String, @@ -79,22 +78,11 @@ public struct AnthropicLanguageModel: LanguageModel { } self.baseURL = baseURL - self.apiKey = apiKey + self.tokenProvider = tokenProvider self.apiVersion = apiVersion self.betas = betas self.model = model self.urlSession = session - - var headers: [String: String] = [ - "x-api-key": apiKey, - "anthropic-version": apiVersion, - ] - - if let betas = betas, !betas.isEmpty { - headers["anthropic-beta"] = betas.joined(separator: ",") - } - - self.headers = headers } public func respond( @@ -109,6 +97,9 @@ public struct AnthropicLanguageModel: LanguageModel { fatalError("AnthropicLanguageModel only supports generating String content") } + let url = baseURL.appendingPathComponent("v1/messages") + let headers = buildHeaders() + let messages = [ AnthropicMessage(role: .user, content: [.text(.init(text: prompt.description))]) ] @@ -126,8 +117,8 @@ public struct AnthropicLanguageModel: LanguageModel { options: options ) - let url = baseURL.appendingPathComponent("v1/messages") let body = try JSONEncoder().encode(params) + let message: AnthropicMessageResponse = try await urlSession.fetch( .post, url: url, @@ -189,6 +180,8 @@ public struct AnthropicLanguageModel: LanguageModel { continuation in let task = Task { @Sendable in do { + let headers = buildHeaders() + // Convert available tools to Anthropic format let anthropicTools: [AnthropicTool] = try session.tools.map { tool in try convertToolToAnthropicFormat(tool) @@ -247,6 +240,19 @@ public struct AnthropicLanguageModel: LanguageModel { return LanguageModelSession.ResponseStream(stream: stream) } + + private func buildHeaders() -> [String: String] { + var headers: [String: String] = [ + "x-api-key": tokenProvider(), + "anthropic-version": apiVersion, + ] + + if let betas = betas, !betas.isEmpty { + headers["anthropic-beta"] = betas.joined(separator: ",") + } + + return headers + } } // MARK: - Conversions diff --git a/Sources/AnyLanguageModel/Models/OpenAILanguageModel.swift b/Sources/AnyLanguageModel/Models/OpenAILanguageModel.swift index 1c2dae1c..c6f2f486 100644 --- a/Sources/AnyLanguageModel/Models/OpenAILanguageModel.swift +++ b/Sources/AnyLanguageModel/Models/OpenAILanguageModel.swift @@ -37,8 +37,8 @@ public struct OpenAILanguageModel: LanguageModel { /// The base URL for the API endpoint. public let baseURL: URL - /// The API key for authentication. - public let apiKey: String + /// The closure providing the API key for authentication. + private let tokenProvider: @Sendable () -> String /// The model identifier to use for generation. public let model: String @@ -52,13 +52,13 @@ public struct OpenAILanguageModel: LanguageModel { /// /// - Parameters: /// - baseURL: The base URL for the API endpoint. Defaults to OpenAI's official API. - /// - apiKey: Your OpenAI API key. + /// - apiKey: Your OpenAI API key or a closure that returns it. /// - model: The model identifier (for example, "gpt-4" or "gpt-3.5-turbo"). /// - apiVariant: The API variant to use. Defaults to `.chatCompletions`. /// - session: The URL session to use for network requests. public init( baseURL: URL = defaultBaseURL, - apiKey: String, + apiKey tokenProvider: @escaping @autoclosure @Sendable () -> String, model: String, apiVariant: APIVariant = .chatCompletions, session: URLSession = URLSession(configuration: .default) @@ -69,7 +69,7 @@ public struct OpenAILanguageModel: LanguageModel { } self.baseURL = baseURL - self.apiKey = apiKey + self.tokenProvider = tokenProvider self.model = model self.apiVariant = apiVariant self.urlSession = session @@ -140,7 +140,7 @@ public struct OpenAILanguageModel: LanguageModel { .post, url: url, headers: [ - "Authorization": "Bearer \(apiKey)" + "Authorization": "Bearer \(tokenProvider())" ], body: body ) @@ -193,7 +193,7 @@ public struct OpenAILanguageModel: LanguageModel { .post, url: url, headers: [ - "Authorization": "Bearer \(apiKey)" + "Authorization": "Bearer \(tokenProvider())" ], body: body ) @@ -269,7 +269,7 @@ public struct OpenAILanguageModel: LanguageModel { .post, url: url, headers: [ - "Authorization": "Bearer \(apiKey)" + "Authorization": "Bearer \(tokenProvider())" ], body: body ) @@ -332,7 +332,7 @@ public struct OpenAILanguageModel: LanguageModel { .post, url: url, headers: [ - "Authorization": "Bearer \(apiKey)" + "Authorization": "Bearer \(tokenProvider())" ], body: body ) diff --git a/Tests/AnyLanguageModelTests/OpenAILanguageModelTests.swift b/Tests/AnyLanguageModelTests/OpenAILanguageModelTests.swift index e2f23330..d76c47af 100644 --- a/Tests/AnyLanguageModelTests/OpenAILanguageModelTests.swift +++ b/Tests/AnyLanguageModelTests/OpenAILanguageModelTests.swift @@ -18,7 +18,6 @@ struct OpenAILanguageModelTests { for apiVariant in [OpenAILanguageModel.APIVariant.chatCompletions, .responses] { let model = OpenAILanguageModel(apiKey: "test-key", model: "test-model", apiVariant: apiVariant) #expect(model.apiVariant == apiVariant) - #expect(model.apiKey == "test-key") #expect(model.model == "test-model") } }