From d694cb0962c736eac2598ee2cc9bc6a6b3bc7179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Wed, 26 Nov 2025 10:58:23 +0100 Subject: [PATCH 1/2] add invoke model example --- Examples/invoke-model/.gitignore | 8 ++++ Examples/invoke-model/Package.swift | 30 +++++++++++++ .../invoke-model/Sources/InvokeModel.swift | 45 +++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 Examples/invoke-model/.gitignore create mode 100644 Examples/invoke-model/Package.swift create mode 100644 Examples/invoke-model/Sources/InvokeModel.swift diff --git a/Examples/invoke-model/.gitignore b/Examples/invoke-model/.gitignore new file mode 100644 index 00000000..0023a534 --- /dev/null +++ b/Examples/invoke-model/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/invoke-model/Package.swift b/Examples/invoke-model/Package.swift new file mode 100644 index 00000000..98cda7fa --- /dev/null +++ b/Examples/invoke-model/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "InvokeModel", + platforms: [.macOS(.v15), .iOS(.v18), .tvOS(.v18)], + products: [ + .executable(name: "InvokeModel", targets: ["InvokeModel"]) + ], + dependencies: [ + // for production use, uncomment the following line + // .package(url: "https://github.com/build-on-aws/swift-bedrock-library.git", branch: "main"), + + // for local development, use the following line + .package(name: "swift-bedrock-library", path: "../.."), + + .package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"), + ], + targets: [ + .executableTarget( + name: "InvokeModel", + dependencies: [ + .product(name: "BedrockService", package: "swift-bedrock-library"), + .product(name: "Logging", package: "swift-log"), + ] + ) + ] +) diff --git a/Examples/invoke-model/Sources/InvokeModel.swift b/Examples/invoke-model/Sources/InvokeModel.swift new file mode 100644 index 00000000..fb10add7 --- /dev/null +++ b/Examples/invoke-model/Sources/InvokeModel.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Bedrock Library open source project +// +// Copyright (c) 2025 Amazon.com, Inc. or its affiliates +// and the Swift Bedrock Library project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift Bedrock Library project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import BedrockService +import Logging + +@main +struct InvokeModel { + static func main() async throws { + do { + try await InvokeModel.run() + } catch { + print("Error:\n\(error)") + } + } + static func run() async throws { + var logger = Logger(label: "InvokeModel") + logger.logLevel = .debug + + let bedrock = try await BedrockService( + region: .useast1, + logger: logger + // uncomment if you use SSO with AWS Identity Center + // authentication: .sso + ) + + let model: BedrockModel = .claude_opus_v4_5 + + let response = try await bedrock.completeText("who are you?", with: model) + + print(response.completion) + } +} From 83f58f5aeb35fad0ec45aacb96392163bafa6038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Wed, 26 Nov 2025 10:58:53 +0100 Subject: [PATCH 2/2] add opus 4.5 and support froo global Cross region inference --- .../Modalities/CrossRegionInference.swift | 7 ++ .../Models/Anthropic/Anthropic.swift | 4 +- .../Anthropic/AnthropicBedrockModels.swift | 17 ++- .../Anthropic/AnthropicGlobalModels.swift | 114 ++++++++++++++++++ .../BedrockService/Models/BedrockModel.swift | 22 +++- 5 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 Sources/BedrockService/Models/Anthropic/AnthropicGlobalModels.swift diff --git a/Sources/BedrockService/BedrockRuntimeClient/Modalities/CrossRegionInference.swift b/Sources/BedrockService/BedrockRuntimeClient/Modalities/CrossRegionInference.swift index 399a98e4..e9834787 100644 --- a/Sources/BedrockService/BedrockRuntimeClient/Modalities/CrossRegionInference.swift +++ b/Sources/BedrockService/BedrockRuntimeClient/Modalities/CrossRegionInference.swift @@ -13,6 +13,8 @@ // //===----------------------------------------------------------------------===// +// https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html + public protocol CrossRegionInferenceModality: Sendable {} extension CrossRegionInferenceModality { public func crossRegionPrefix(forRegion region: Region) -> String { @@ -22,3 +24,8 @@ extension CrossRegionInferenceModality { return "" } } + +public protocol GlobalCrossRegionInferenceModality: Sendable {} +extension GlobalCrossRegionInferenceModality { + public func crossRegionPrefix() -> String { "global." } +} diff --git a/Sources/BedrockService/Models/Anthropic/Anthropic.swift b/Sources/BedrockService/Models/Anthropic/Anthropic.swift index 493de7c1..23bc166f 100644 --- a/Sources/BedrockService/Models/Anthropic/Anthropic.swift +++ b/Sources/BedrockService/Models/Anthropic/Anthropic.swift @@ -67,8 +67,8 @@ struct AnthropicText: TextModality, ConverseModality, ConverseStreamingModality, return AnthropicRequestBody( prompt: prompt, maxTokens: maxTokens, - temperature: temperature ?? parameters.temperature.defaultValue, - topP: topP ?? parameters.topP.defaultValue, + temperature: temperature, + topP: topP, topK: topK ?? parameters.topK.defaultValue, stopSequences: stopSequences ?? parameters.stopSequences.defaultValue ) diff --git a/Sources/BedrockService/Models/Anthropic/AnthropicBedrockModels.swift b/Sources/BedrockService/Models/Anthropic/AnthropicBedrockModels.swift index 5a889791..8f1d90e9 100644 --- a/Sources/BedrockService/Models/Anthropic/AnthropicBedrockModels.swift +++ b/Sources/BedrockService/Models/Anthropic/AnthropicBedrockModels.swift @@ -30,7 +30,6 @@ typealias ClaudeV3_5Sonnet = AnthropicText typealias ClaudeV3_7Sonnet = AnthropicText typealias Claude_Sonnet_v4 = AnthropicText typealias Claude_Opus_v4 = AnthropicText -typealias Claude_Sonnet_v4_5 = AnthropicText // text // https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html @@ -236,4 +235,20 @@ extension BedrockModel { maxReasoningTokens: Parameter(.maxReasoningTokens, minValue: 1_024, maxValue: 8_191, defaultValue: 4_096) ) ) + public static let claude_opus_v4_5: BedrockModel = BedrockModel( + id: "anthropic.claude-opus-4-5-20251101-v1:0", + name: "Claude Opus v4.5", + modality: Claude_Opus_v4_5( + parameters: TextGenerationParameters( + temperature: Parameter(.temperature, minValue: 0, maxValue: 1, defaultValue: 1), + maxTokens: Parameter(.maxTokens, minValue: 1, maxValue: 32_000, defaultValue: 8_192), + topP: Parameter(.topP, minValue: 0, maxValue: 1, defaultValue: 0.999), + topK: Parameter(.topK, minValue: 0, maxValue: 500, defaultValue: 0), + stopSequences: StopSequenceParams(maxSequences: 8191, defaultValue: []), + maxPromptSize: 200_000 + ), + features: [.textGeneration, .systemPrompts, .document, .vision, .toolUse, .reasoning], + maxReasoningTokens: Parameter(.maxReasoningTokens, minValue: 1_024, maxValue: 8_191, defaultValue: 4_096) + ) + ) } diff --git a/Sources/BedrockService/Models/Anthropic/AnthropicGlobalModels.swift b/Sources/BedrockService/Models/Anthropic/AnthropicGlobalModels.swift new file mode 100644 index 00000000..a4381280 --- /dev/null +++ b/Sources/BedrockService/Models/Anthropic/AnthropicGlobalModels.swift @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Bedrock Library open source project +// +// Copyright (c) 2025 Amazon.com, Inc. or its affiliates +// and the Swift Bedrock Library project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift Bedrock Library project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +// Wrapper structs for v4.5 models to support GlobalCrossRegionInferenceModality +struct Claude_Sonnet_v4_5: TextModality, ConverseModality, ConverseStreamingModality, GlobalCrossRegionInferenceModality +{ + private let anthropicText: AnthropicText + + let converseParameters: ConverseParameters + let converseFeatures: [ConverseFeature] + + init(parameters: TextGenerationParameters, features: [ConverseFeature], maxReasoningTokens: Parameter) { + self.anthropicText = AnthropicText( + parameters: parameters, + features: features, + maxReasoningTokens: maxReasoningTokens + ) + self.converseParameters = anthropicText.converseParameters + self.converseFeatures = anthropicText.converseFeatures + } + + func getName() -> String { anthropicText.getName() } + func getParameters() -> TextGenerationParameters { anthropicText.getParameters() } + func getConverseParameters() -> ConverseParameters { anthropicText.getConverseParameters() } + func getConverseFeatures() -> [ConverseFeature] { anthropicText.getConverseFeatures() } + + func getTextRequestBody( + prompt: String, + maxTokens: Int?, + temperature: Double?, + topP: Double?, + topK: Int?, + stopSequences: [String]?, + serviceTier: ServiceTier + ) throws -> BedrockBodyCodable { + try anthropicText.getTextRequestBody( + prompt: prompt, + maxTokens: maxTokens, + temperature: temperature, + topP: topP, + topK: topK, + stopSequences: stopSequences, + serviceTier: serviceTier + ) + } + + func getTextResponseBody(from data: Data) throws -> ContainsTextCompletion { + try anthropicText.getTextResponseBody(from: data) + } +} + +struct Claude_Opus_v4_5: TextModality, ConverseModality, ConverseStreamingModality, GlobalCrossRegionInferenceModality { + private let anthropicText: AnthropicText + + let converseParameters: ConverseParameters + let converseFeatures: [ConverseFeature] + + init(parameters: TextGenerationParameters, features: [ConverseFeature], maxReasoningTokens: Parameter) { + self.anthropicText = AnthropicText( + parameters: parameters, + features: features, + maxReasoningTokens: maxReasoningTokens + ) + self.converseParameters = anthropicText.converseParameters + self.converseFeatures = anthropicText.converseFeatures + } + + func getName() -> String { anthropicText.getName() } + func getParameters() -> TextGenerationParameters { anthropicText.getParameters() } + func getConverseParameters() -> ConverseParameters { anthropicText.getConverseParameters() } + func getConverseFeatures() -> [ConverseFeature] { anthropicText.getConverseFeatures() } + + func getTextRequestBody( + prompt: String, + maxTokens: Int?, + temperature: Double?, + topP: Double?, + topK: Int?, + stopSequences: [String]?, + serviceTier: ServiceTier + ) throws -> BedrockBodyCodable { + try anthropicText.getTextRequestBody( + prompt: prompt, + maxTokens: maxTokens, + temperature: temperature, + topP: topP, + topK: topK, + stopSequences: stopSequences, + serviceTier: serviceTier + ) + } + + func getTextResponseBody(from data: Data) throws -> ContainsTextCompletion { + try anthropicText.getTextResponseBody(from: data) + } +} diff --git a/Sources/BedrockService/Models/BedrockModel.swift b/Sources/BedrockService/Models/BedrockModel.swift index 15bea094..21ddc9b1 100644 --- a/Sources/BedrockService/Models/BedrockModel.swift +++ b/Sources/BedrockService/Models/BedrockModel.swift @@ -81,6 +81,8 @@ public struct BedrockModel: Hashable, Sendable, Equatable, RawRepresentable { self = BedrockModel.claude_opus_v4 case BedrockModel.claude_sonnet_v4_5.id: self = BedrockModel.claude_sonnet_v4_5 + case BedrockModel.claude_opus_v4_5.id: + self = BedrockModel.claude_opus_v4_5 // titan case BedrockModel.titan_text_g1_premier.id: self = BedrockModel.titan_text_g1_premier @@ -132,13 +134,21 @@ public struct BedrockModel: Hashable, Sendable, Equatable, RawRepresentable { // MARK: Cross region inference public func getModelIdWithCrossRegionInferencePrefix(region: Region) -> String { - // If the model does not support cross region inference, return the model ID as is - guard let crossRegionInferenceModality = modality as? CrossRegionInferenceModality else { - return id + + // If the model support global cross region inference, return the global ID first + if let globalCrossRegionInferenceModality = modality as? GlobalCrossRegionInferenceModality { + let prefix = globalCrossRegionInferenceModality.crossRegionPrefix() + return "\(prefix)\(id)" + } + + // If the model support cross region inference, return the regional ID + if let crossRegionInferenceModality = modality as? CrossRegionInferenceModality { + let prefix = crossRegionInferenceModality.crossRegionPrefix(forRegion: region) + return "\(prefix)\(id)" } - // If the model supports cross region inference, return the model ID with the appropriate prefix - let prefix = crossRegionInferenceModality.crossRegionPrefix(forRegion: region) - return "\(prefix)\(id)" + + // otherwise, returns the Id + return id } // MARK: Modality checks