Skip to content

Commit

Permalink
Merge pull request #1 from fumito-ito/feature/aws-bedrock
Browse files Browse the repository at this point in the history
AWS Bedrock support
  • Loading branch information
fumito-ito authored Mar 25, 2024
2 parents 7f53075 + 90f4614 commit 217a220
Show file tree
Hide file tree
Showing 29 changed files with 671 additions and 70 deletions.
36 changes: 36 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
{
"pins" : [
{
"identity" : "aws-crt-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/awslabs/aws-crt-swift",
"state" : {
"revision" : "0d0a0cf2e2cb780ceeceac190b4ede94f4f96902",
"version" : "0.26.0"
}
},
{
"identity" : "aws-sdk-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/awslabs/aws-sdk-swift",
"state" : {
"revision" : "7b0d55676eb896e472662922e80ebe30ae3ca8ed",
"version" : "0.39.0"
}
},
{
"identity" : "collectionconcurrencykit",
"kind" : "remoteSourceControl",
Expand All @@ -18,6 +36,15 @@
"version" : "1.8.1"
}
},
{
"identity" : "smithy-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/smithy-lang/smithy-swift",
"state" : {
"revision" : "0267f0c649558cbc1323bbc5a0c8b2403239655b",
"version" : "0.44.0"
}
},
{
"identity" : "sourcekitten",
"kind" : "remoteSourceControl",
Expand All @@ -36,6 +63,15 @@
"version" : "1.2.3"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
"version" : "1.5.4"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
Expand Down
10 changes: 10 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/realm/SwiftLint", .upToNextMajor(from: "0.54.0")),
.package(url: "https://github.com/awslabs/aws-sdk-swift", from: "0.32.0"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
Expand All @@ -28,5 +29,14 @@ let package = Package(
.testTarget(
name: "AnthropicSwiftSDKTests",
dependencies: ["AnthropicSwiftSDK"]),
.target(
name: "AnthropicSwiftSDK-Bedrock",
dependencies: [
"AnthropicSwiftSDK",
.product(name: "AWSBedrockRuntime", package: "aws-sdk-swift")]),
.testTarget(
name: "AnthropicSwiftSDK-BedrockTests",
dependencies: [
"AnthropicSwiftSDK-Bedrock"])
]
)
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ Just add to your Package.swift under dependencies
let package = Package(
name: "MyPackage",
products: [...],
targets: [
.target(
"YouAppModule",
dependencies: [
.product(name: "AnthropicSwiftSDK", package: "AnthropicSwiftSDK")
]
)
],
dependencies: [
.package(url: "https://github.com/fumito-ito/AnthropicSwiftSDK.git", .upToNextMajor(from: "0.0.1"))
]
Expand Down Expand Up @@ -71,6 +79,62 @@ for try await chunk in stream {
}
```

## Amazon Web Services Bedrock

This library provides support for the [Anthropic Bedrock API](https://aws.amazon.com/bedrock/claude/) through a separate package.

```swift
let package = Package(
name: "MyPackage",
products: [...],
targets: [
.target(
"YouAppModule",
dependencies: [
.product(name: "AnthropicSwiftSDK_Bedrock", package: "AnthropicSwiftSDK")
]
)
],
dependencies: [
.package(url: "https://github.com/fumito-ito/AnthropicSwiftSDK.git", .upToNextMajor(from: "0.0.1"))
]
)
```

To create an `AnthropicBedrockClient` from a `BedrockRuntimeClient` with a `Model` to access Claude on Bedrock.
The API usage is the same as the normal AnthropicClient.

```swift
let client = try BedrockRuntimeClient(region: "USEast1")
let anthropic = BedrockRuntimeClient.useAnthropic(client, model: .claude_3_Haiku)

let response = try await anthropic.messages.createMessage(Message(role: .user, content: [.text("This is test text")]), maxTokens: 1024)
for content in response.content {
switch content {
case .text(let text):
print(text)
case .image(let imageContent):
// handle base64 encoded image content
}
}
```

Of course, `Streaming Message API` works in the same way.

```swift
let client = try BedrockRuntimeClient(region: "USEast1")
let anthropic = BedrockRuntimeClient.useAnthropic(client, model: .claude_3_Haiku)

let stream = try await anthropic.messages.streamMessage([Message(role: .user, content: [.text("This is test text")])], maxTokens: 1024)
for try await chunk in stream {
switch chunk.type {
case .messageStart:
// handle message start object with casting chunk into `StreamingMessageStartResponse`
}
}
```


## Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Expand Down
20 changes: 20 additions & 0 deletions Sources/AnthropicSwiftSDK-Bedrock/AnthropicBedrockClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// AnthropicBedrockClient.swift
//
//
// Created by 伊藤史 on 2024/03/22.
//

import Foundation
import AWSBedrockRuntime
import AnthropicSwiftSDK

/// Claude API Client through Bedrock API
public final class AnthropicBedrockClient {
/// Endpoint for Messages API
public let messages: AnthropicSwiftSDK_Bedrock.Messages

init(client: BedrockRuntimeClient, model: Model) {
self.messages = .init(client: client, model: model)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// AnthropicBedrockClientError.swift
//
//
// Created by 伊藤史 on 2024/03/25.
//

import Foundation
import AWSBedrockRuntime

public enum AnthropicBedrockClientError: Error {
case cannotGetAnyDataFromBedrockMessageResponse(InvokeModelOutput)
case cannotGetAnyDataFromBedrockStreamResponse(InvokeModelWithResponseStreamOutput)
case bedrockRuntimeClientGetsUnknownPayload(BedrockRuntimeClientTypes.ResponseStream)
case cannotGetAnyDataFromBedrockRuntimeClientPayload(BedrockRuntimeClientTypes.PayloadPart)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// AnthropicSwiftSDK_Model+Extension.swift
//
//
// Created by 伊藤史 on 2024/03/23.
//

import Foundation
import AnthropicSwiftSDK

extension AnthropicSwiftSDK.Model {
/// Model name for Amazon Bedrock
///
/// for more detail, see https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html
var bedrockModelName: String? {
switch self {
case .claude_3_Opus:
return nil
case .claude_3_Sonnet:
return "anthropic.claude-3-sonnet-20240229-v1:0"
case .claude_3_Haiku:
return "anthropic.claude-3-haiku-20240307-v1:0"
case let .custom(modelName):
return modelName
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// BedrockClient+Extension.swift
//
//
// Created by 伊藤史 on 2024/03/22.
//

import Foundation
import AWSBedrockRuntime
import AnthropicSwiftSDK

public extension BedrockRuntimeClient {
/// Create a client using Claude through Bedrock
/// - Parameters:
/// - client: Bedrock runtime client
/// - model: Claude model, Bedrock supports Haiku or Sonnet instance type.
/// - Returns: Client using Claude through Bedrock
static func useAnthropic(_ client: BedrockRuntimeClient, model: Model) -> AnthropicBedrockClient {
AnthropicBedrockClient(client: client, model: model)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// InvokeModelInput+Extension.swift
//
//
// Created by 伊藤史 on 2024/03/23.
//

import Foundation
import AnthropicSwiftSDK
import AWSBedrockRuntime

extension InvokeModelInput {
/// Constructor for Bedrock invoke model input with Claude request object
/// - Parameters:
/// - accept: acceptable response content type
/// - request: Claude API request. It will be converted to `Data` and contained in bedrock request.
/// - contentType: acceptable request content type
init(accept: String, request: MessagesRequest, contentType: String) throws {
let data = try anthropicJSONEncoder.encode(request)

self.init(
accept: accept,
body: data,
contentType: contentType,
modelId: request.model.bedrockModelName
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// InvokeModelWithResponseStreamInput+Extension.swift
//
//
// Created by 伊藤史 on 2024/03/23.
//

import Foundation
import AnthropicSwiftSDK
import AWSBedrockRuntime

extension InvokeModelWithResponseStreamInput {
/// Constructor for Bedrock invoke model stream input with Claude request object
/// - Parameters:
/// - accept: acceptable response content type
/// - request: Claude API request. It will be converted to `Data` and contained in bedrock request.
/// - contentType: acceptable request content type
init(accept: String, request: MessagesRequest, contentType: String) throws {
let data = try anthropicJSONEncoder.encode(request)

self.init(
accept: accept,
body: data,
contentType: contentType,
modelId: request.model.bedrockModelName
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// MessagesResponse+Extension.swift
//
//
// Created by 伊藤史 on 2024/03/23.
//

import Foundation
import AnthropicSwiftSDK
import AWSBedrockRuntime

extension MessagesResponse {
/// Create `MessagesResponse` object from InvokeModelOutput.
///
/// This constructor converts `InvokeModelOutput.body` into `MessageResponse`
/// - Parameter invokeModelOutput: model output to convert `MessageResponse`
init (from invokeModelOutput: InvokeModelOutput) throws {
guard let data = invokeModelOutput.body else {
throw AnthropicBedrockClientError.cannotGetAnyDataFromBedrockMessageResponse(invokeModelOutput)
}

self = try anthropicJSONDecoder.decode(MessagesResponse.self, from: data)
}
}
Loading

0 comments on commit 217a220

Please sign in to comment.