diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a29c0224..9e1978d6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -40,9 +40,17 @@ jobs: # libssl-dev which is not installed on swift:6.2-noble # Using swift:6.2-amazonlinux2 is not a solution # because the @checkout action doesn't works on ALI2 (requires Node.js 20) + # will re-enable when Amazon Linux 2023 will be available api_breakage_check_enabled: false api_breakage_check_container_image: "swift:6.2-noble" - docs_check_container_image: "swift:6.2-noble" + + # disabled because images needs libssl-dev (which excludes swift:6.2-noble) + # and Node 20+ (for the checkout action), whcih excludes Amazon Linux 2 + # will re-enable when Amazon Linux 2023 will be available + docs_check_enabled: false + docs_check_container_image: "swift:6.2-amazonlinux2" + + format_check_enabled: true format_check_container_image: "swift:6.2-noble" yamllint_check_enabled: true @@ -83,7 +91,7 @@ jobs: # We pass the list of examples here, but we can't pass an array as argument # Instead, we pass a String with a valid JSON array. # The workaround is mentioned here https://github.com/orgs/community/discussions/11692 - examples: "[ 'api-key', 'converse', 'converse-stream', 'openai', 'text_chat' ]" + examples: "[ 'api-key', 'converse', 'converse-stream', 'embeddings', 'openai', 'text_chat' ]" swift-6-language-mode: name: Swift 6 Language Mode diff --git a/.gitignore b/.gitignore index ac3515cc..c9ec5d7e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ Package.resolved Makefile **/temp node_modules +docc-output # **/backend **/backend/.DS_Store diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 00000000..b4a7c6e0 --- /dev/null +++ b/.spi.yml @@ -0,0 +1,4 @@ +version: 1 +builder: + configs: + - documentation_targets: [BedrockService] diff --git a/Package.swift b/Package.swift index 35059e4c..cbb0967b 100644 --- a/Package.swift +++ b/Package.swift @@ -15,6 +15,7 @@ let package = Package( .package(url: "https://github.com/smithy-lang/smithy-swift", from: "0.158.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.6.4"), .package(url: "https://github.com/awslabs/aws-crt-swift", from: "0.53.0"), + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), ], targets: [ .target( diff --git a/README.md b/README.md index 77579713..cb888bd9 100644 --- a/README.md +++ b/README.md @@ -1,1224 +1,70 @@ # Swift Bedrock Library -A tiny layer on top of the [AWS SDK for Swift](https://github.com/awslabs/aws-sdk-swift) for interacting with [Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) foundation models. This library provides a convenient way to access Amazon Bedrock's capabilities from Swift applications. +A Swift library for interacting with [Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) foundation models. -## Acknowledgment - -This library and playground have been written by [Mona Dierickx](https://www.linkedin.com/in/mona-dierickx/), during her last year of studies at [HoGent](https://www.hogent.be/), Belgium. - -Thank you for your enthousiasm and positive attitude during the three months we worked together. (February 2025 - May 2025). - -Thank for Professor Steven Van Impe for allowing us to work with these young talents. - -## Getting started with BedrockService +📖 **[Complete Documentation](https://swiftpackageindex.com/build-on-aws/swift-bedrock-library/documentation)** - Comprehensive guides and API reference -1. Set-up your `Package.swift` - -First add dependencies: -```bash -swift package add-dependency https://github.com/build-on-aws/swift-bedrock-library.git --branch main -swift package add-target-dependency BedrockService TargetName --package swift-bedrock-library -``` - -Next up add `platforms` configuration after `name` - -```swift -platforms: [.macOS(.v15), .iOS(.v18), .tvOS(.v18)], -``` +## TL;DR - Quick Start -Your `Package.swift` should now look something like this: ```swift -import PackageDescription - -let package = Package( - name: "ProjectName", - platforms: [.macOS(.v15), .iOS(.v18), .tvOS(.v18)], - dependencies: [ - .package(url: "https://github.com/build-on-aws/swift-bedrock-library.git", branch: "main"), - ], - targets: [ - .executableTarget( - name: "TargetName", - dependencies: [ - .product(name: "BedrockService", package: "swift-bedrock-library"), - ] - ) - ] -) -``` - -2. Import the BedrockService - -```swift import BedrockService -``` - -3. Initialize the BedrockService - -Choose what Region to use, whether to use AWS SSO authentication instead of standard credentials and pass a logger. If no region is passed it will default to `.useast1`, if no logger is provided a default logger with the name `bedrock.service` is created. The log level will be set to the environment variable `BEDROCK_SERVICE_LOG_LEVEL` or default to `.trace`. Choose the form of authentication you wish to use. - -```swift -let bedrock = try await BedrockService( - region: .uswest1, - logger: logger, - authentication: .sso -) -``` - -4. List the available models - -Use the `listModels()` function to test your set-up. This function will return an array of `ModelSummary` objects, each one representing a model supported by Amazon Bedrock. The ModelSummaries that contain a `BedrockModel` object are the models supported by BedrockService. - -```swift -let models = try await bedrock.listModels() -``` - -## Authentication - -The Swift Bedrock Library supports multiple authentication methods to work with Amazon Bedrock. By default, it uses the standard AWS credential provider chain, but you can specify different authentication types when initializing the `BedrockService`. - -### Default Authentication - -Uses the standard AWS credential provider chain, which checks for credentials in the following order: -1. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`) -2. AWS credentials file (`~/.aws/credentials`) -3. AWS config file (`~/.aws/config`) -4. IAM roles for Amazon EC2 instances -5. IAM roles for tasks (Amazon ECS) -6. IAM roles for Lambda functions - -```swift -let bedrock = try await BedrockService( - region: .uswest2 - // authentication defaults to .default -) -``` - -### Profile-based Authentication - -Use a specific profile from your AWS credentials file. This is useful when you have multiple AWS accounts or roles configured locally. - -```swift -let bedrock = try await BedrockService( - region: .uswest2, - authentication: .profile(profileName: "my-profile") -) -``` - -### SSO Authentication - -Use AWS Single Sign-On (SSO) authentication. You must run `aws sso login --profile ` before using this authentication method. - -```swift -let bedrock = try await BedrockService( - region: .uswest2, - authentication: .sso(profileName: "my-sso-profile") -) -``` - -### Web Identity Token Authentication - -Use a JWT token from an external identity provider (like Sign In with Apple or Google) to assume an IAM role. This is particularly useful for iOS, tvOS, and macOS applications where traditional AWS CLI-based authentication isn't available. -```swift -let bedrock = try await BedrockService( - region: .uswest2, - authentication: .webIdentity( - token: jwtToken, - roleARN: "arn:aws:iam::123456789012:role/MyAppRole", - region: .uswest2, - notification: { - // Optional: Called on main thread when credentials are retrieved - print("AWS credentials updated") - } - ) -) -``` - -### API Key Authentication - -Use an API key for authentication. API keys are generated in the AWS console and provide a simpler authentication method for specific use cases. - -```swift -let bedrock = try await BedrockService( - region: .uswest2, - authentication: .apiKey(key: "your-api-key-here") -) -``` - -As usual, do not store or hardcode API Keys in your front end application. +// Initialize the service +let bedrock = try await BedrockService(region: .uswest2) -### Static Credentials Authentication - -Use static AWS credentials directly. **This method is strongly discouraged for production use** and should only be used for testing and debugging purposes. - -```swift -let bedrock = try await BedrockService( - region: .uswest2, - authentication: .static( - accessKey: "AKIAIOSFODNN7EXAMPLE", - secretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - sessionToken: "optional-session-token" - ) -) -``` - -**Security Note**: Never hardcode credentials in your source code or commit them to version control. Use environment variables, secure credential storage, or other secure methods to manage credentials in production applications. - -## Chatting using the Converse or ConverseStream API - -### Text prompt - -To send a text prompt to a model, first choose a model that supports converse, you can verify this by using the `hasConverseModality` function on the `BedrockModel`. Then use the model to create a `ConverseRequestBuilder`, add your prompt to it with the `.withPrompt` function. Use the builder to send your request to the Converse API with the `converse` function. You can then easily print the reply and use it to create a new builder with the same model and inference parameters but with an updated history. - -```swift +// Send a simple text prompt let model: BedrockModel = .nova_lite - -guard model.hasConverseModality() else { - throw MyError.incorrectModality("\(model.name) does not support converse") -} - -var builder = try ConverseRequestBuilder(with: model) - .withPrompt("Tell me about rainbows") - -var reply = try await bedrock.converse(with: builder) - -print("Assistant: \(reply)") - -builder = try ConverseRequestBuilder(from: builder, with: reply) - .withPrompt("Do you think birds can see them too?") - -reply = try await bedrock.converse(with: builder) - -print("Assistant: \(reply)") -``` - -Optionally add inference parameters. Note that the builder can be used to create the next builder with the same parameters and the updated history. - -```swift let builder = try ConverseRequestBuilder(with: model) .withPrompt("Tell me about rainbows") - .withMaxTokens(512) - .withTemperature(0.2) - .withStopSequences(["END", "STOP", ""]) - .withSystemPrompts(["Do not pretend to be human", "Never talk about goats", "You like puppies"]) - -var reply = try await bedrock.converse(with: builder) - -builder = try ConverseRequestBuilder(from: builder, with: reply) - .withPrompt("Do you think birds can see them too?") - -reply = try await bedrock.converse(with: builder) -``` - -To get a streaming response, use the same `ConverseRequestBuilder`, but the `converseStream` function instead of the `converse` function. Ensure the model you are using supports streaming. -The stream will contain a `.stream` of `ConverseStreamElement` objects that indicate the progress into the response. - -To create the next builder, with the same model and inference parameters, use the full message from the `.messageComplete`. The `ConverseStreamElement` enum provides several cases to track the streaming response: - -- `.messageStart(Role)`: Indicates the beginning of a message with the specified role (assistant, user, etc.) -- `.text(Int, String)`: Contains partial text content with an index and the text fragment -- `.reasoning(Int, String)`: Contains partial reasoning content with an index and the reasoning fragment -- `.toolUse(Int, ToolUseBlock)`: Contains a complete tool use response with an index and the tool use details -- `.messageComplete(Message)`: Provides the complete message with all content blocks and reason for stopping -- `.metaData(ResponseMetadata)`: Contains metadata about the response including token usage and latency metrics - -The index in each case helps track the order of content blocks in the final message. When processing a stream, you should handle each element type appropriately. For convenience, the `messageComplete` event contains the full response, ready to use. - -The stream provided by this library is a balance between convenience of use and latency. If you need more flexibility or very low latency (for example, no buffering on the tool use response), the `ConverseStreamreply` also exposes the low-level stream returned by the AWS SDK. - -```swift - let model: BedrockModel = .nova_lite - - guard model.hasConverseModality() else { - throw MyError.incorrectModality("\(model.name) does not support converse") - } - - // create a request - let builder = try ConverseRequestBuilder(with: model) - .withPrompt("Tell me about rainbows") - - // send the request - let reply = try await bedrock.converseStream(with: builder) - - // consume the stream of elements - for try await element in reply.stream { - - switch element { - case .messageStart(let role): - logger.info("Message started with role: \(role)") - - case .text(_, let text): - print(text, terminator: "") - - case .reasoning(let index, let reasoning): - logger.info("Reasoning delta: \(reasoning)", metadata: ["index": "\(index)"]) - - case .toolUse(let index, let toolUse): - logger.info( - "Tool use: \(toolUse.name) with id: \(toolUse.id) and input: \(toolUse.input)", - metadata: ["index": "\(index)"] - ) - - case .messageComplete(_): - print("\n") - - case .metaData(let metaData): - logger.info("Metadata: \(metaData)") - } - } -``` - -### Vision - -To send an image to a model, first ensure the model supports vision. Next simply add the image to the `ConverseRequestBuilder` with the `withImage` function. The function can either take an `ImageBlock` object or the format and bytes to construct the object. - - -```swift -let model: BedrockModel = .nova_lite - -guard model.hasConverseModality(.vision) else { - throw MyError.incorrectModality("\(model.name) does not support converse vision") -} - -let builder = try ConverseRequestBuilder(with: model) - .withPrompt("Can you tell me about this plant?") - .withImage(format: .jpeg, source: base64EncodedImage) - -let reply = try await bedrock.converse(with: builder) - -print("Assistant: \(reply)") -``` - -Optionally add inference parameters. - -```swift -let builder = try ConverseRequestBuilder(with: model) - .withPrompt("Can you tell me about this plant?") - .withImage(format: .jpeg, source: base64EncodedImage) - .withTemperature(0.8) - -let reply = try await bedrock.converse(with: builder) -``` - -Note that the builder can be used to create the next builder with the same parameters and the updated history. - -```swift -var builder = try ConverseRequestBuilder(with: model) - .withPrompt("Can you tell me about this plant?") - .withImage(format: .jpeg, source: base64EncodedImage) - .withTemperature(0.8) - -var reply = try await bedrock.converse(with: builder) - -builder = try ConverseRequestBuilder(from: builder, with: reply) - .withPrompt("Where can I find those plants?") - -reply = try await bedrock.converse(with: builder) -``` - -To use streaming use the exact same `ConverseRequestBuilder`, but use the `converseStream` function instead of the `converse` function. An example is given in the [text prompt section](#text-prompt). - -### Document - -To send a document to a model, first ensure the model supports document. Next simply add the document to the `ConverseRequestBuilder` with the `withDocument` function. The function can either take a `DocumentBlock` object or the name, format and bytes to construct the object. - -```swift -let model: BedrockModel = .nova_lite - -guard model.hasConverseModality(.document) else { - throw MyError.incorrectModality("\(model.name) does not support converse document") -} - -let builder = try ConverseRequestBuilder(with: model) - .withPrompt("Can you give me a summary of this chapter?") - .withDocument(name: "Chapter 1", format: .pdf, source: base64EncodedDocument) let reply = try await bedrock.converse(with: builder) - print("Assistant: \(reply)") ``` -Optionally add inference parameters. - -```swift -let builder = try ConverseRequestBuilder(with: model) - .withPrompt("Can you give me a summary of this chapter?") - .withDocument(name: "Chapter 1", format: .pdf, source: base64EncodedDocument) - .withMaxTokens(512) - .withTemperature(0.4) - -var reply = try await bedrock.converse(with: builder) -``` +## Installation -Note that the builder can be used to create the next builder with the same parameters and the updated history. +Add to your `Package.swift`: ```swift -var builder = try ConverseRequestBuilder(with: model) - .withPrompt("Can you give me a summary of this chapter?") - .withDocument(name: "Chapter 1", format: .pdf, source: base64EncodedDocument) - .withMaxTokens(512) - .withTemperature(0.4) - -var reply = try await bedrock.converse(with: builder) - -builder = try ConverseRequestBuilder(from: builder, with: reply) - .withPrompt("Thanks, can you make a Dutch version as well?") - -reply = try await bedrock.converse(with: builder) -``` - -To use streaming use the exact same `ConverseRequestBuilder`, but use the `converseStream` function instead of the `converse` function. An example is given in the [text prompt section](#text-prompt). - -### Tools - -For tool usage, first ensure the model supports the use of tools. Next define at least one `Tool` and add it to the `ConverseRequestBuilder` with the `withTool` function (or the `withTools` function to add several tools at once). After sending a request the model could now send back a `ToolUse` asking for specific information from a specific tool. Use this to send the information back in a `ToolResult`, by using the `withToolResult` function. You will now receive a reply informed by the result from the tool. - - -```swift -let model: BedrockModel = .nova_lite - -// verify that the model supports tool usage -guard model.hasConverseModality(.toolUse) else { - throw MyError.incorrectModality("\(model.name) does not support converse tools") -} - -// define the inputschema for your tool -let inputSchema = JSON([ - "type": "object", - "properties": [ - "sign": [ - "type": "string", - "description": "The call sign for the radio station for which you want the most popular song. Example calls signs are WZPZ and WKRP." +dependencies: [ + .package(url: "https://github.com/build-on-aws/swift-bedrock-library.git", branch: "main") +], +targets: [ + .target( + name: "YourTarget", + dependencies: [ + .product(name: "BedrockService", package: "swift-bedrock-library") ] - ], - "required": [ - "sign" - ] -]) - -// create a Tool object -let tool = try Tool(name: "top_song", inputSchema: inputSchema, description: "Get the most popular song played on a radio station.") - -// create a ConverseRequestBuilder with a prompt and the Tool object -var builder = try ConverseRequestBuilder(with: model) - .withPrompt("What is the most popular song on WZPZ?") - .withTool(tool) - -// pass the ConverseRequestBuilder object to the converse function -var reply = try await bedrock.converse(with: builder) - -if let toolUse = try? reply.getToolUse() { - let id = toolUse.id - let name = toolUse.name - let input = toolUse.input - - // ... Logic to use the tool here ... - - // Send the toolResult back to the model - builder = try ConverseRequestBuilder(from: builder, with: reply) - .withToolResult("The Best Song Ever") // pass any Codable or Data - - reply = try await bedrock.converse(with: builder) -} - -print("Assistant: \(reply)") -// The final reply will be similar to: "The most popular song currently played on WZPZ is \"The Best Song Ever\". If you need more information or have another request, feel free to ask!" -``` - -To use streaming use the exact same `ConverseRequestBuilder`, but use the `converseStream` function instead of the `converse` function. - -```swift -let bedrock = try await BedrockService(authentication: .sso()) -let model: BedrockModel = .claudev3_7_sonnet - -// define the inputschema for your tool -let schema = JSON(with: [ - "type": "object", - "properties": [ - "sign": [ - "type": "string", - "description": - "The call sign for the radio station for which you want the most popular song. Example calls signs are WZPZ, StuBru and Klara.", - ] - ], - "required": [ - "sign" - ], -]) - -// pass a prompt and the tool to converse -var builder = try ConverseRequestBuilder(with: model) - .withPrompt("Introduce yourself and mention the tools you have access to?") - .withTool( - name: "top_song", - inputSchema: schema, - description: "Get the most popular song played on a radio station." - ) - -var stream: AsyncThrowingStream -var assistantMessage: Message = Message("empty") - -// start a loop to interact with the user -while true { - var prompt: String = "" - var indexes: [Int] = [] - var toolRequests: [ToolUseBlock] = [] - - // create the stream by calling the converseStream function - stream = try await bedrock.converseStream(with: builder) - - // process the stream - for try await element in stream { - switch element { - case .contentSegment(let contentSegment): - switch contentSegment { - case .text(let index, let text): - if !indexes.contains(index) { - indexes.append(index) - print("\nAssistant: ") - } - print(text, terminator: "") - default: - break - } - case .contentBlockComplete(_, let content): - print("\n") - if case .toolUse(let toolUse) = content { - toolRequests.append(toolUse) - } - case .messageComplete(let message): - assistantMessage = message - } - } - - // if a request to use a tool was made by the model, use the information in the input to return the correct information back to the model in a ToolResultBlock - if !toolRequests.isEmpty { - for toolUse in toolRequests { - print("found tool use") - print(toolUse) - if toolUse.name == "top_song" { - let sign: String? = toolUse.input["sign"] - if let sign { - let song = try await getMostPopularSong(sign: sign) - builder = try ConverseRequestBuilder(from: builder, with: assistantMessage) - .withToolResult(song) - } - } - } - } else { - // if no request to use a tool was made, no ToolResultBlock needs to be returned and the user can ask the next question - print("\nYou: ") - prompt = readLine()! - if prompt == "done" { - break - } - - builder = try ConverseRequestBuilder(from: builder, with: assistantMessage) - .withPrompt(prompt) - } -} -``` - -### Reasoning - -To not only get a text reply but to also follow the model's reasoning, enable reasoning by using the `withReasoning` and optionally set the maximum length of the reasoning with `withMaxReasoningTokens`. These functions can be combined using the `withReasoning(maxReasoningTokens: Int)` function. - -```swift -let model: BedrockModel = .claudev3_7_sonnet - -guard model.hasConverseModality() else { - throw MyError.incorrectModality("\(model.name) does not support converse") -} -guard model.hasConverseModality(.reasoning) else { - throw MyError.incorrectModality("\(model.name) does not support reasoning") -} - -var prompt = "Introduce yourself in one sentence" - -var builder = try ConverseRequestBuilder(with: model) - .withPrompt(prompt) - .withReasoning() - .withMaxReasoningTokens(1024) // Optional - -var reply = try await bedrock.converse(with: builder) - -if let reasoning = try? reply.getReasoningBlock() { - print("\nReasoning: \(reasoning.reasoning)") -} -print("\nAssistant: \(reply)") -``` - -To combine reasoning and streaming, use the same `ConverseRequestBuilder`, but use the `converseStream` function instead of the `converse` function. A `ContentSegment` can then contain `reasoning`. - -```swift -let model: BedrockModel = .claudev3_7_sonnet - -guard model.hasConverseModality() else { - throw MyError.incorrectModality("\(model.name) does not support converse") -} -guard model.hasConverseModality(.streaming) else { - throw MyError.incorrectModality("\(model.name) does not support streaming") -} -guard model.hasConverseModality(.reasoning) else { - throw MyError.incorrectModality("\(model.name) does not support reasoning") -} - -var builder = try ConverseRequestBuilder(from: builder, with: reply) - .withPrompt("Tell me more about the birds in Paris") - .withReasoning(maxReasoningTokens: 1024) - -let stream = try await bedrock.converseStream(with: builder) - -var indexes: [Int] = [] - -for try await element in stream { - switch element { - case .contentSegment(let contentSegment): - switch contentSegment { - case .text(let index, let text): - if !indexes.contains(index) { - indexes.append(index) - print("\nAssistant: ") - } - print(text, terminator: "") - case .reasoning(let index, let text, _): - if !indexes.contains(index) { - indexes.append(index) - print("\nReasoning: ") - } - print(text, terminator: "") - default: - break - } - case .contentBlockComplete: - print("\n\n") - case .messageComplete(let message): - assistantMessage = message - } -} - -builder = try ConverseRequestBuilder(from: builder, with: assistantMessage) - .withPrompt("And what about the rats?") -``` - -### Make your own `Message` - -Alternatively use the `converse` function that does not take a `prompt`, `toolResult` or `image` and construct the `Message` yourself. - -```swift -// Message with prompt -let replyMessage = try await bedrock.converse( - with: model, - conversation: [Message("What day of the week is it?")] -) - -// Optionally add inference parameters -let replyMessage = try await bedrock.converse( - with: model, - conversation: [Message("What day of the week is it?")], - maxTokens: 512, - temperature: 1, - topP: 0.8, - stopSequences: ["THE END"], - systemPrompts: ["Today is Wednesday, make sure to mention that."] -) - -// Message with an image and prompt -let replyMessage = try await bedrock.converse( - with: model, - conversation: [Message("What is in the this teacup?", imageFormat: .jpeg, imageBytes: base64EncodedImage)], -) - -// Message with toolResult -let replyMessage = try await bedrock.converse( - with: model, - conversation: [Message(toolResult)], - tools: [toolA, toolB] -) -``` - -### JSON - -The `JSON` struct is a lightweight and flexible wrapper for working with JSON-like data in Swift. It provides convenient methods and initializers to parse, access, and manipulate JSON data while maintaining type safety and versatility. - -#### Creating a JSON Object - -You can create a `JSON` object by wrapping raw values or constructing nested structures: -```swift -let json = JSON([ - "name": JSON("Jane Doe"), - "age": JSON(30), - "isMember": JSON(true), -]) -``` -#### Creating JSON object from String - -The `JSON` struct provides an initializer to parse valid JSON strings into a `JSON` object: - -```swift -let validJSONString = """ -{ - "name": "Jane Doe", - "age": 30, - "isMember": true -} -""" - -do { - let json = try JSON(from: validJSONString) - print(json.getValue("name") ?? "No name") // Output: Jane Doe -} catch { - print("Failed to parse JSON:", error) -} -``` - -#### Accessing values using `getValue` - -The `getValue(_ key: String)` method retrieves values of the specified type from the JSON object: - -```swift -if let name: String? = json.getValue("name") { - print("Name:", name) // Output: Name: Jane Doe -} - -if let age: Int? = json.getValue("age") { - print("Age:", age) // Output: Age: 30 -} - -if let isMember: Bool? = json.getValue("isMember") { - print("Is Member:", isMember) // Output: Is Member: true -} -``` - -#### Accessing values using subscripts - -You can also access values dynamically using subscripts: - -```swift -let name: String? = json["name"] -print("Name:", name ?? "Unknown") // Output: Name: Jane Doe - -let nonExistent: String? = json["nonExistentKey"] -print(nonExistent == nil) // Output: true -``` - -Note that the subscript method is also able to handle nested objects. - -```swift -let json = JSON([ - "name": JSON("Jane Doe"), - "age": JSON(30), - "isMember": JSON(true), - "address": JSON([ - "street": JSON("123 Main St"), - "city": JSON("Anytown"), - "postalCode": JSON(12345), - ]), -]) - -let street: String = json["address"]?["street"] -print("Street:", name ?? "Unknown") // Street: 123 Main St -``` - -## Generating an image using the InvokeModel API - -Choose a BedrockModel that supports image generation - you can verify this using the `hasImageModality` and the `hasTextToImageModality` function. The `generateImage` function allows you to create images from text descriptions with various optional parameters: - -- `prompt`: Text description of the desired image -- `negativePrompt`: Text describing what to avoid in the generated image -- `nrOfImages`: Number of images to generate -- `cfgScale`: Classifier free guidance scale to control how closely the image follows the prompt -- `seed`: Seed for reproducible image generation -- `quality`: Parameter to control the quality of generated images -- `resolution`: Desired image resolution for the generated images - -The function returns an ImageGenerationOutput object containing an array of generated images in base64 format. - -```swift -let model: BedrockModel = .nova_canvas - -guard model.hasImageModality(), - model.hasTextToImageModality() else { - throw MyError.incorrectModality("\(model.name) does not support image generation") -} - -let imageGeneration = try await bedrock.generateImage( - "A serene landscape with mountains at sunset", - with: model -) -``` - -Optionally add inference parameters. - -```swift -let imageGeneration = try await bedrock.generateImage( - "A serene landscape with mountains at sunset", - with: model, - negativePrompt: "dark, stormy, people", - nrOfImages: 3, - cfgScale: 7.0, - seed: 42, - quality: .standard, - resolution: ImageResolution(width: 100, height: 100) -) -``` - -Note that the minimum, maximum and default values for each parameter are model specific and defined when the BedrockModel is created. Some parameters might not be supported by certain models. - -## Generating image variations using the InvokeModel API -Choose a BedrockModel that supports image variations - you can verify this using the `hasImageModality` and the `hasImageVariationModality` function. The `generateImageVariation` function allows you to create variations of an existing image with these parameters: - -- `images`: The base64-encoded source images used to create variations from -- `negativePrompt`: Text describing what to avoid in the generated image -- `similarity`: Controls how similar the variations will be to the source images -- `nrOfImages`: Number of variations to generate -- `cfgScale`: Classifier free guidance scale to control how closely variations follow the original image -- `seed`: Seed for reproducible variation generation -- `quality`: Parameter to control the quality of generated variations -- `resolution`: Desired resolution for the output variations - -This function returns an `ImageGenerationOutput` object containing an array of generated image variations in base64 format. Each variation will maintain key characteristics of the source images while introducing creative differences. - -```swift -let model: BedrockModel = .nova_canvas - -guard model.hasImageVariationModality(), - model.hasImageVariationModality() else { - throw MyError.incorrectModality("\(model.name) does not support image variation generation") -} - -let imageVariations = try await bedrock.generateImageVariation( - images: [base64EncodedImage], - prompt: "A dog drinking out of this teacup", - with: model -) -``` - -Optionally add inference parameters. - -```swift -let imageVariations = try await bedrock.generateImageVariation( - images: [base64EncodedImage], - prompt: "A dog drinking out of this teacup", - with: model, - negativePrompt: "Cats, worms, rain", - similarity: 0.8, - nrOfVariations: 4, - cfgScale: 7.0, - seed: 42, - quality: .standard, - resolution: ImageResolution(width: 100, height: 100) -) -``` - -Note that the minimum, maximum and default values for each parameter are model specific and defined when the BedrockModel is created. Some parameters might not be supported by certain models. - -## Generating text using the InvokeModel API - -Choose a BedrockModel that supports text generation, you can verify this using the `hasTextModality` function. When calling the `completeText` function you can provide some inference parameters: - -- `maxTokens`: The maximum amount of tokens that the model is allowed to return -- `temperature`: Controls the randomness of the model's output -- `topP`: Nucleus sampling, this parameter controls the cumulative probability threshold for token selection -- `topK`: Limits the number of tokens the model considers for each step of text generation to the K most likely ones -- `stopSequences`: An array of strings that will cause the model to stop generating further text when encountered - -The function returns a `TextCompletion` object containing the generated text. - -```swift -let model: BedrockModel = .nova_micro - -guard model.hasTextModality() else { - throw MyError.incorrectModality("\(model.name) does not support text generation") -} - -let textCompletion = try await bedrock.completeText( - "Write a story about a space adventure", - with: model -) - -print(textCompletion.completion) -``` - -Optionally add inference parameters. - -```swift -let textCompletion = try await bedrock.completeText( - "Write a story about a space adventure", - with: model, - maxTokens: 1000, - temperature: 0.7, - topP: 0.9, - topK: 250, - stopSequences: ["THE END"] -) -``` - -Note that the minimum, maximum and default values for each parameter are model specific and defined when the BedrockModel is created. Some parameters might not be supported by certain models. - -## Generating embeddings using the InvokeModel API - -Choose a BedrockModel that supports embeddings generation - you can verify this using the `hasEmbeddingsModality` function. The `embed` function allows you to convert text into numerical vector representations that capture semantic meaning: - -```swift -let model: BedrockModel = .titan_embed_text_v1 - -let embeddings = try await bedrock.embed( - "Swift is a powerful programming language", - with: model -) - -print("Generated embeddings with \(embeddings.count) dimensions") -``` - -Optionally specify the vector size: - -```swift -let embeddings = try await bedrock.embed( - "Swift is a powerful programming language", - with: model, - vectorSize: 512 -) -``` - -Embeddings are returned as an array of Double values (`Embeddings` typealias) that can be used for: -- Semantic similarity comparisons -- Text clustering and classification -- Retrieval-augmented generation (RAG) systems -- Machine learning feature vectors - -Note that the available vector sizes and other parameters are model specific and defined when the BedrockModel is created. - -## How to add a BedrockModel - -### Converse - -To add a new model that only needs the ConverseModality, simply use the `StandardConverse` and add the correct [inference parameters](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html) and [supported converse features](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html). - -```swift -extension BedrockModel { - public static let new_bedrock_model = BedrockModel( - id: "family.model-id-v1:0", - name: "New Model Name", - modality: StandardConverse( - parameters: ConverseParameters( - temperature: Parameter(.temperature, minValue: 0, maxValue: 1, defaultValue: 0.3), - maxTokens: Parameter(.maxTokens, minValue: 1, maxValue: nil, defaultValue: nil), - topP: Parameter(.topP, minValue: 0.01, maxValue: 0.99, defaultValue: 0.75), - stopSequences: StopSequenceParams(maxSequences: nil, defaultValue: []), - maxPromptSize: nil - ), - features: [.textGeneration, .systemPrompts, .document, .toolUse] - ) - ) -} -``` - -If the model also implements other modalities you might need to create you own `Modality` and make sure it conforms to `ConverseModality` by implementing the `getConverseParameters` and `getConverseFeatures` functions. Note that the `ConverseParameters` can be extracted from `TextGenerationParameters` by using the public initializer. - -```swift -struct ModelFamilyModality: TextModality, ConverseModality { - func getName() -> String { "Model Family Text and Converse Modality" } - - let parameters: TextGenerationParameters - let converseFeatures: [ConverseFeature] - let converseParameters: ConverseParameters - - init(parameters: TextGenerationParameters, features: [ConverseFeature] = [.textGeneration]) { - self.parameters = parameters - self.converseFeatures = features - - // public initializer to extract `ConverseParameters` from `TextGenerationParameters` - self.converseParameters = ConverseParameters(textGenerationParameters: parameters) - } - - // ... -} -``` - -### Text - -If you need to add a model from a model family that is not supported at all by the library, follow these steps: - -#### Step 1: Create family-specific request and response struct - -Make sure to create a struct that reflects exactly how the body of the request for an invokeModel call to this family should look. Make sure to add the public initializer with parameters `prompt`, `maxTokens` and `temperature` to comply to the `BedrockBodyCodable` protocol. Take a look at the documentation to apply best practices or specific formatting. - -```json -{ - "prompt": "\(prompt)", - "temperature": 1, - "top_p": 0.9, - "max_tokens": 200, - "stop": ["END"] -} -``` - -```swift -public struct LlamaRequestBody: BedrockBodyCodable { - let prompt: String - let max_gen_len: Int - let temperature: Double - let top_p: Double - - public init(prompt: String, maxTokens: Int = 512, temperature: Double = 0.5) { - self.prompt = - "<|begin_of_text|><|start_header_id|>user<|end_header_id|>\(prompt)<|eot_id|><|start_header_id|>assistant<|end_header_id|>" - self.max_gen_len = maxTokens - self.temperature = temperature - self.top_p = 0.9 - } -} -``` - -Do the same for the response and ensure to add the `getTextCompletion` method to extract the completion from the response body and to comply to the `ContainsTextCompletion` protocol. - -```json -{ - "generation": "\n\n", - "prompt_token_count": int, - "generation_token_count": int, - "stop_reason" : string -} -``` - -```swift -struct LlamaResponseBody: ContainsTextCompletion { - let generation: String - let prompt_token_count: Int - let generation_token_count: Int - let stop_reason: String - - public func getTextCompletion() throws -> TextCompletion { - TextCompletion(generation) - } -} -``` - -#### Step 2: Create the Modality - -For a text generation create a struct conforming to TextModality. Use the request body and response body you created in [the previous step](#step-1-create-family-specific-request-and-response-struct). Make sure to check for model(family) specific rules or parameters that are not supported here. - -```swift -struct LlamaText: TextModality { - let parameters: TextGenerationParameters - - init(parameters: TextGenerationParameters) { - self.parameters = parameters - } - - func getName() -> String { "Llama Text Generation" } - - func getParameters() -> TextGenerationParameters { - parameters - } - - func getTextRequestBody( - prompt: String, - maxTokens: Int?, - temperature: Double?, - topP: Double?, - topK: Int?, - stopSequences: [String]? - ) throws -> BedrockBodyCodable { - guard topK == nil else { - throw BedrockLibraryError.notSupported("TopK is not supported for Llama text completion") - } - guard stopSequences == nil else { - throw BedrockLibraryError.notSupported("stopSequences is not supported for Llama text completion") - } - return LlamaRequestBody( - prompt: prompt, - maxTokens: maxTokens ?? parameters.maxTokens.defaultValue, - temperature: temperature ?? parameters.temperature.defaultValue, - topP: topP ?? parameters.topP.defaultValue - ) - } - - func getTextResponseBody(from data: Data) throws -> ContainsTextCompletion { - let decoder = JSONDecoder() - return try decoder.decode(LlamaResponseBody.self, from: data) - } -} -``` - -#### Step 3: Create BedrockModel instance - -You can now create instances for any of the models that follow the request and response structure you defined. Make sure to check the allowed and default values for the inference parameters, especially if some parameters are not supported by the model. Know that these parameters may differ significantly for models from the same family. - -```swift -extension BedrockModel { - public static let llama3_3_70b_instruct: BedrockModel = BedrockModel( - id: "meta.llama3-3-70b-instruct-v1:0", - name: "Llama 3.3 70B Instruct", - modality: LlamaText( - parameters: TextGenerationParameters( - temperature: Parameter(.temperature, minValue: 0, maxValue: 1, defaultValue: 0.5), - maxTokens: Parameter(.maxTokens, minValue: 0, maxValue: 2_048, defaultValue: 512), - topP: Parameter(.topP, minValue: 0, maxValue: 1, defaultValue: 0.9), - topK: Parameter.notSupported(.topK), - stopSequences: StopSequenceParams.notSupported(), - maxPromptSize: nil - ) - ) ) -} +] ``` -### Image +Requires: `platforms: [.macOS(.v15), .iOS(.v18), .tvOS(.v18)]` -To add an image generation model from a model family that is not supported at all by the library, the steps are much alike to the text completion models. +## Documentation -#### Step 1: Create family-specific request and response struct +📖 **[Complete Documentation](https://swiftpackageindex.com/build-on-aws/swift-bedrock-library/documentation)** - Comprehensive guides and API reference -Make sure to create a struct that reflects exactly how the body of the request for an invokeModel call to this family should look. Take a look at the documentation to apply best practices or specific formatting. - -```swift -public struct AmazonImageRequestBody: BedrockBodyCodable { - let taskType: TaskType - private let textToImageParams: TextToImageParams? - private let imageGenerationConfig: ImageGenerationConfig +Key topics: +- [Authentication](https://swiftpackageindex.com/build-on-aws/swift-bedrock-library/documentation/bedrockservice/authentication) - Configure AWS credentials +- [Converse API](https://swiftpackageindex.com/build-on-aws/swift-bedrock-library/documentation/bedrockservice/converse) - Conversational AI +- [Image Generation](https://swiftpackageindex.com/build-on-aws/swift-bedrock-library/documentation/bedrockservice/imagegeneration) - Create and modify images +- [Tools](https://swiftpackageindex.com/build-on-aws/swift-bedrock-library/documentation/bedrockservice/tools) - Function calling +- [Streaming](https://swiftpackageindex.com/build-on-aws/swift-bedrock-library/documentation/bedrockservice/streaming) - Real-time responses - // MARK: - Initialization +## Examples - /// Creates a text-to-image generation request body - /// - Parameters: - /// - prompt: The text description of the image to generate - /// - nrOfImages: The number of images to generate - /// - negativeText: The text description of what to exclude from the generated image - /// - Returns: A configured AmazonImageRequestBody for text-to-image generation - public static func textToImage( - prompt: String, - negativeText: String?, - nrOfImages: Int?, - cfgScale: Double?, - seed: Int?, - quality: ImageQuality?, - resolution: ImageResolution? - ) -> Self { - AmazonImageRequestBody( - prompt: prompt, - negativeText: negativeText, - nrOfImages: nrOfImages, - cfgScale: cfgScale, - seed: seed, - quality: quality, - resolution: resolution - ) - } +Explore the [Examples](./Examples/) directory for complete sample applications including: +- Basic conversation chat +- Streaming responses +- Image generation +- iOS math solver app +- Web playground with frontend/backend - private init( - prompt: String, - negativeText: String?, - nrOfImages: Int?, - cfgScale: Double?, - seed: Int?, - quality: ImageQuality?, - resolution: ImageResolution? - ) { - self.taskType = .textToImage - self.textToImageParams = TextToImageParams.textToImage(prompt: prompt, negativeText: negativeText) - self.imageGenerationConfig = ImageGenerationConfig( - nrOfImages: nrOfImages, - cfgScale: cfgScale, - seed: seed, - quality: quality, - resolution: resolution - ) - } -} -``` - -Do the same for the response and ensure to add the `getGeneratedImage` method to extract the image from the response body and to comply to the `ContainsImageGeneration` protocol. - -```swift -public struct AmazonImageResponseBody: ContainsImageGeneration { - let images: [Data] - - public func getGeneratedImage() -> ImageGenerationOutput { - ImageGenerationOutput(images: images) - } -} -``` - -#### Step 2: Create the Modality - -Determine the exact functionality and make sure to comply to the correct modality protocol. In this case we will use `TextToImageModality`. -Create a struct conforming to `ImageModality` and the specific functionality protocol. Use the request body and response body you created in [the previous step](#step-1-create-family-specific-request-and-response-struct). Make sure to check for model(family) specific rules or parameters that are not supported here. - -```swift -struct AmazonImage: ImageModality, TextToImageModality { - func getName() -> String { "Amazon Image Generation" } - - let parameters: ImageGenerationParameters - let resolutionValidator: any ImageResolutionValidator - let textToImageParameters: TextToImageParameters - - init( - parameters: ImageGenerationParameters, - resolutionValidator: any ImageResolutionValidator, - textToImageParameters: TextToImageParameters - ) { - self.parameters = parameters - self.textToImageParameters = textToImageParameters - self.conditionedTextToImageParameters = conditionedTextToImageParameters - self.imageVariationParameters = imageVariationParameters - self.resolutionValidator = resolutionValidator - } - - func getParameters() -> ImageGenerationParameters { parameters } - func getTextToImageParameters() -> TextToImageParameters { textToImageParameters } - - func validateResolution(_ resolution: ImageResolution) throws { - try resolutionValidator.validateResolution(resolution) - } - - func getImageResponseBody(from data: Data) throws -> ContainsImageGeneration { - let decoder = JSONDecoder() - return try decoder.decode(AmazonImageResponseBody.self, from: data) - } - - func getTextToImageRequestBody( - prompt: String, - negativeText: String?, - nrOfImages: Int?, - cfgScale: Double?, - seed: Int?, - quality: ImageQuality?, - resolution: ImageResolution? - ) throws -> BedrockBodyCodable { - AmazonImageRequestBody.textToImage( - prompt: prompt, - negativeText: negativeText, - nrOfImages: nrOfImages, - cfgScale: cfgScale, - seed: seed, - quality: quality, - resolution: resolution - ) - } -} -``` +## Acknowledgment -#### Step 3: Create BedrockModel instance +This library and playground have been written by [Mona Dierickx](https://www.linkedin.com/in/mona-dierickx/), during her last year of studies at [HoGent](https://www.hogent.be/), Belgium. -You can now create instances for any of the models that follow the request and response structure you defined. Make sure to check the allowed and default values for the inference parameters, especially if some parameters are not supported by the model. Know that these parameters may differ significantly for models from the same family. +Thank you for your enthusiasm and positive attitude during the three months we worked together (February 2025 - May 2025). -```swift -extension BedrockModel { - public static let nova_canvas: BedrockModel = BedrockModel( - id: "amazon.nova-canvas-v1:0", - name: "Nova Canvas", - modality: AmazonImage( - parameters: ImageGenerationParameters( - nrOfImages: Parameter(.nrOfImages, minValue: 1, maxValue: 5, defaultValue: 1), - cfgScale: Parameter(.cfgScale, minValue: 1.1, maxValue: 10, defaultValue: 6.5), - seed: Parameter(.seed, minValue: 0, maxValue: 858_993_459, defaultValue: 12) - ), - resolutionValidator: NovaImageResolutionValidator(), - textToImageParameters: TextToImageParameters(maxPromptSize: 1024, maxNegativePromptSize: 1024), - ) - ) -} -``` +Thank you Professor Steven Van Impe for allowing us to work with these young talents. diff --git a/Sources/BedrockAuthentication+JWT.swift b/Sources/BedrockService/BedrockAuthentication+JWT.swift similarity index 100% rename from Sources/BedrockAuthentication+JWT.swift rename to Sources/BedrockService/BedrockAuthentication+JWT.swift diff --git a/Sources/BedrockAuthentication.swift b/Sources/BedrockService/BedrockAuthentication.swift similarity index 100% rename from Sources/BedrockAuthentication.swift rename to Sources/BedrockService/BedrockAuthentication.swift diff --git a/Sources/BedrockModel.swift b/Sources/BedrockService/BedrockModel.swift similarity index 99% rename from Sources/BedrockModel.swift rename to Sources/BedrockService/BedrockModel.swift index 4ddc0166..15bea094 100644 --- a/Sources/BedrockModel.swift +++ b/Sources/BedrockService/BedrockModel.swift @@ -37,6 +37,7 @@ public struct BedrockModel: Hashable, Sendable, Equatable, RawRepresentable { /// Creates a new BedrockModel instance /// - Parameters: /// - id: The unique identifier for the model + /// - name: The human-readable name of the model /// - modality: The modality of the model public init( id: String, diff --git a/Sources/BedrockService.swift b/Sources/BedrockService/BedrockService.swift similarity index 100% rename from Sources/BedrockService.swift rename to Sources/BedrockService/BedrockService.swift diff --git a/Sources/BedrockServiceError.swift b/Sources/BedrockService/BedrockServiceError.swift similarity index 100% rename from Sources/BedrockServiceError.swift rename to Sources/BedrockService/BedrockServiceError.swift diff --git a/Sources/Converse/BedrockService+Converse.swift b/Sources/BedrockService/Converse/BedrockService+Converse.swift similarity index 96% rename from Sources/Converse/BedrockService+Converse.swift rename to Sources/BedrockService/Converse/BedrockService+Converse.swift index 59ee8e97..7563fd96 100644 --- a/Sources/Converse/BedrockService+Converse.swift +++ b/Sources/BedrockService/Converse/BedrockService+Converse.swift @@ -34,6 +34,8 @@ extension BedrockService { /// - stopSequences: Optional array of sequences where generation should stop /// - systemPrompts: Optional array of system prompts to guide the conversation /// - tools: Optional array of tools the model can use + /// - enableReasoning: Optional flag to enable reasoning output + /// - maxReasoningTokens: Optional maximum number of reasoning tokens to generate /// - Throws: BedrockLibraryError.notSupported for parameters or functionalities that are not supported /// BedrockLibraryError.invalidParameter for invalid parameters /// BedrockLibraryError.invalidPrompt if the prompt is empty or too long @@ -83,6 +85,8 @@ extension BedrockService { /// - stopSequences: Optional array of sequences where generation should stop /// - systemPrompts: Optional array of system prompts to guide the conversation /// - tools: Optional array of tools the model can use + /// - enableReasoning: Optional flag to enable reasoning output + /// - maxReasoningTokens: Optional maximum number of reasoning tokens to generate /// - Throws: BedrockLibraryError.notSupported for parameters or functionalities that are not supported /// BedrockLibraryError.invalidParameter for invalid parameters /// BedrockLibraryError.invalidPrompt if the prompt is empty or too long diff --git a/Sources/Converse/BedrockService+ConverseStreaming.swift b/Sources/BedrockService/Converse/BedrockService+ConverseStreaming.swift similarity index 97% rename from Sources/Converse/BedrockService+ConverseStreaming.swift rename to Sources/BedrockService/Converse/BedrockService+ConverseStreaming.swift index 73df10a6..d475eec0 100644 --- a/Sources/Converse/BedrockService+ConverseStreaming.swift +++ b/Sources/BedrockService/Converse/BedrockService+ConverseStreaming.swift @@ -34,6 +34,8 @@ extension BedrockService { /// - stopSequences: Optional array of sequences where generation should stop /// - systemPrompts: Optional array of system prompts to guide the conversation /// - tools: Optional array of tools the model can use + /// - enableReasoning: Optional flag to enable reasoning output + /// - maxReasoningTokens: Optional maximum number of reasoning tokens to generate /// - Throws: BedrockLibraryError.notSupported for parameters or functionalities that are not supported /// BedrockLibraryError.invalidParameter for invalid parameters /// BedrockLibraryError.invalidPrompt if the prompt is empty or too long @@ -86,6 +88,8 @@ extension BedrockService { /// - stopSequences: Optional array of sequences where generation should stop /// - systemPrompts: Optional array of system prompts to guide the conversation /// - tools: Optional array of tools the model can use + /// - enableReasoning: Optional flag to enable reasoning capabilities + /// - maxReasoningTokens: Optional maximum number of tokens for reasoning /// - Throws: BedrockLibraryError.notSupported for parameters or functionalities that are not supported /// BedrockLibraryError.invalidParameter for invalid parameters /// BedrockLibraryError.invalidPrompt if the prompt is empty or too long diff --git a/Sources/Converse/ContentBlocks/Content.swift b/Sources/BedrockService/Converse/ContentBlocks/Content.swift similarity index 100% rename from Sources/Converse/ContentBlocks/Content.swift rename to Sources/BedrockService/Converse/ContentBlocks/Content.swift diff --git a/Sources/Converse/ContentBlocks/DocumentBlock.swift b/Sources/BedrockService/Converse/ContentBlocks/DocumentBlock.swift similarity index 100% rename from Sources/Converse/ContentBlocks/DocumentBlock.swift rename to Sources/BedrockService/Converse/ContentBlocks/DocumentBlock.swift diff --git a/Sources/Converse/ContentBlocks/DocumentToJSON.swift b/Sources/BedrockService/Converse/ContentBlocks/DocumentToJSON.swift similarity index 100% rename from Sources/Converse/ContentBlocks/DocumentToJSON.swift rename to Sources/BedrockService/Converse/ContentBlocks/DocumentToJSON.swift diff --git a/Sources/Converse/ContentBlocks/ImageBlock.swift b/Sources/BedrockService/Converse/ContentBlocks/ImageBlock.swift similarity index 100% rename from Sources/Converse/ContentBlocks/ImageBlock.swift rename to Sources/BedrockService/Converse/ContentBlocks/ImageBlock.swift diff --git a/Sources/Converse/ContentBlocks/JSONtoDocument.swift b/Sources/BedrockService/Converse/ContentBlocks/JSONtoDocument.swift similarity index 100% rename from Sources/Converse/ContentBlocks/JSONtoDocument.swift rename to Sources/BedrockService/Converse/ContentBlocks/JSONtoDocument.swift diff --git a/Sources/Converse/ContentBlocks/ReasoningBlock.swift b/Sources/BedrockService/Converse/ContentBlocks/ReasoningBlock.swift similarity index 100% rename from Sources/Converse/ContentBlocks/ReasoningBlock.swift rename to Sources/BedrockService/Converse/ContentBlocks/ReasoningBlock.swift diff --git a/Sources/Converse/ContentBlocks/S3Location.swift b/Sources/BedrockService/Converse/ContentBlocks/S3Location.swift similarity index 100% rename from Sources/Converse/ContentBlocks/S3Location.swift rename to Sources/BedrockService/Converse/ContentBlocks/S3Location.swift diff --git a/Sources/Converse/ContentBlocks/ToolResultBlock.swift b/Sources/BedrockService/Converse/ContentBlocks/ToolResultBlock.swift similarity index 100% rename from Sources/Converse/ContentBlocks/ToolResultBlock.swift rename to Sources/BedrockService/Converse/ContentBlocks/ToolResultBlock.swift diff --git a/Sources/Converse/ContentBlocks/ToolUseBlock.swift b/Sources/BedrockService/Converse/ContentBlocks/ToolUseBlock.swift similarity index 100% rename from Sources/Converse/ContentBlocks/ToolUseBlock.swift rename to Sources/BedrockService/Converse/ContentBlocks/ToolUseBlock.swift diff --git a/Sources/Converse/ContentBlocks/VideoBlock.swift b/Sources/BedrockService/Converse/ContentBlocks/VideoBlock.swift similarity index 100% rename from Sources/Converse/ContentBlocks/VideoBlock.swift rename to Sources/BedrockService/Converse/ContentBlocks/VideoBlock.swift diff --git a/Sources/Converse/ConverseReply.swift b/Sources/BedrockService/Converse/ConverseReply.swift similarity index 100% rename from Sources/Converse/ConverseReply.swift rename to Sources/BedrockService/Converse/ConverseReply.swift diff --git a/Sources/Converse/ConverseRequest.swift b/Sources/BedrockService/Converse/ConverseRequest.swift similarity index 100% rename from Sources/Converse/ConverseRequest.swift rename to Sources/BedrockService/Converse/ConverseRequest.swift diff --git a/Sources/Converse/ConverseRequestBuilder.swift b/Sources/BedrockService/Converse/ConverseRequestBuilder.swift similarity index 100% rename from Sources/Converse/ConverseRequestBuilder.swift rename to Sources/BedrockService/Converse/ConverseRequestBuilder.swift diff --git a/Sources/Converse/ConverseRequestStreaming.swift b/Sources/BedrockService/Converse/ConverseRequestStreaming.swift similarity index 100% rename from Sources/Converse/ConverseRequestStreaming.swift rename to Sources/BedrockService/Converse/ConverseRequestStreaming.swift diff --git a/Sources/Converse/ConverseResponseStreaming.swift b/Sources/BedrockService/Converse/ConverseResponseStreaming.swift similarity index 100% rename from Sources/Converse/ConverseResponseStreaming.swift rename to Sources/BedrockService/Converse/ConverseResponseStreaming.swift diff --git a/Sources/Converse/History.swift b/Sources/BedrockService/Converse/History.swift similarity index 100% rename from Sources/Converse/History.swift rename to Sources/BedrockService/Converse/History.swift diff --git a/Sources/Converse/JSON.swift b/Sources/BedrockService/Converse/JSON.swift similarity index 100% rename from Sources/Converse/JSON.swift rename to Sources/BedrockService/Converse/JSON.swift diff --git a/Sources/Converse/Message.swift b/Sources/BedrockService/Converse/Message.swift similarity index 100% rename from Sources/Converse/Message.swift rename to Sources/BedrockService/Converse/Message.swift diff --git a/Sources/Converse/Role.swift b/Sources/BedrockService/Converse/Role.swift similarity index 100% rename from Sources/Converse/Role.swift rename to Sources/BedrockService/Converse/Role.swift diff --git a/Sources/Converse/Streaming/ConverseReplyStream.swift b/Sources/BedrockService/Converse/Streaming/ConverseReplyStream.swift similarity index 100% rename from Sources/Converse/Streaming/ConverseReplyStream.swift rename to Sources/BedrockService/Converse/Streaming/ConverseReplyStream.swift diff --git a/Sources/Converse/Streaming/ConverseStreamElement.swift b/Sources/BedrockService/Converse/Streaming/ConverseStreamElement.swift similarity index 100% rename from Sources/Converse/Streaming/ConverseStreamElement.swift rename to Sources/BedrockService/Converse/Streaming/ConverseStreamElement.swift diff --git a/Sources/Converse/Streaming/ResponseMetaData.swift b/Sources/BedrockService/Converse/Streaming/ResponseMetaData.swift similarity index 100% rename from Sources/Converse/Streaming/ResponseMetaData.swift rename to Sources/BedrockService/Converse/Streaming/ResponseMetaData.swift diff --git a/Sources/Converse/Streaming/ToolUseStart.swift b/Sources/BedrockService/Converse/Streaming/ToolUseStart.swift similarity index 100% rename from Sources/Converse/Streaming/ToolUseStart.swift rename to Sources/BedrockService/Converse/Streaming/ToolUseStart.swift diff --git a/Sources/Converse/Tool.swift b/Sources/BedrockService/Converse/Tool.swift similarity index 100% rename from Sources/Converse/Tool.swift rename to Sources/BedrockService/Converse/Tool.swift diff --git a/Sources/BedrockService/Docs.docc/AddingModels.md b/Sources/BedrockService/Docs.docc/AddingModels.md new file mode 100644 index 00000000..d2d40cda --- /dev/null +++ b/Sources/BedrockService/Docs.docc/AddingModels.md @@ -0,0 +1,299 @@ +# Adding Models + +Extend BedrockService with new foundation models + +## Overview + +BedrockService is designed to be extensible. You can add support for new foundation models by implementing the appropriate modality protocols and creating BedrockModel instances. + +## Adding Converse-Only Models + +For models that only support the Converse API, use `StandardConverse`: + +```swift +extension BedrockModel { + public static let new_bedrock_model = BedrockModel( + id: "family.model-id-v1:0", + name: "New Model Name", + modality: StandardConverse( + parameters: ConverseParameters( + temperature: Parameter(.temperature, minValue: 0, maxValue: 1, defaultValue: 0.3), + maxTokens: Parameter(.maxTokens, minValue: 1, maxValue: nil, defaultValue: nil), + topP: Parameter(.topP, minValue: 0.01, maxValue: 0.99, defaultValue: 0.75), + stopSequences: StopSequenceParams(maxSequences: nil, defaultValue: []), + maxPromptSize: nil + ), + features: [.textGeneration, .systemPrompts, .document, .toolUse] + ) + ) +} +``` + +### Converse Features + +Specify which features the model supports: + +- `.textGeneration` - Basic text generation +- `.systemPrompts` - System message support +- `.vision` - Image input processing +- `.document` - Document input processing +- `.toolUse` - Function calling +- `.streaming` - Real-time response streaming +- `.reasoning` - Reasoning output + +## Adding Text Generation Models + +For models that need custom InvokeModel support, implement the required protocols: + +### Step 1: Create Request/Response Structures + +```swift +public struct LlamaRequestBody: BedrockBodyCodable { + let prompt: String + let max_gen_len: Int + let temperature: Double + let top_p: Double + + public init(prompt: String, maxTokens: Int = 512, temperature: Double = 0.5) { + self.prompt = "<|begin_of_text|><|start_header_id|>user<|end_header_id|>\(prompt)<|eot_id|><|start_header_id|>assistant<|end_header_id|>" + self.max_gen_len = maxTokens + self.temperature = temperature + self.top_p = 0.9 + } +} + +struct LlamaResponseBody: ContainsTextCompletion { + let generation: String + let prompt_token_count: Int + let generation_token_count: Int + let stop_reason: String + + public func getTextCompletion() throws -> TextCompletion { + TextCompletion(generation) + } +} +``` + +### Step 2: Implement TextModality + +```swift +struct LlamaText: TextModality { + let parameters: TextGenerationParameters + + init(parameters: TextGenerationParameters) { + self.parameters = parameters + } + + func getName() -> String { "Llama Text Generation" } + + func getParameters() -> TextGenerationParameters { + parameters + } + + func getTextRequestBody( + prompt: String, + maxTokens: Int?, + temperature: Double?, + topP: Double?, + topK: Int?, + stopSequences: [String]? + ) throws -> BedrockBodyCodable { + guard topK == nil else { + throw BedrockLibraryError.notSupported("TopK is not supported for Llama") + } + guard stopSequences == nil else { + throw BedrockLibraryError.notSupported("Stop sequences not supported for Llama") + } + + return LlamaRequestBody( + prompt: prompt, + maxTokens: maxTokens ?? parameters.maxTokens.defaultValue, + temperature: temperature ?? parameters.temperature.defaultValue + ) + } + + func getTextResponseBody(from data: Data) throws -> ContainsTextCompletion { + let decoder = JSONDecoder() + return try decoder.decode(LlamaResponseBody.self, from: data) + } +} +``` + +### Step 3: Create BedrockModel Instance + +```swift +extension BedrockModel { + public static let llama3_3_70b_instruct: BedrockModel = BedrockModel( + id: "meta.llama3-3-70b-instruct-v1:0", + name: "Llama 3.3 70B Instruct", + modality: LlamaText( + parameters: TextGenerationParameters( + temperature: Parameter(.temperature, minValue: 0, maxValue: 1, defaultValue: 0.5), + maxTokens: Parameter(.maxTokens, minValue: 0, maxValue: 2_048, defaultValue: 512), + topP: Parameter(.topP, minValue: 0, maxValue: 1, defaultValue: 0.9), + topK: Parameter.notSupported(.topK), + stopSequences: StopSequenceParams.notSupported(), + maxPromptSize: nil + ) + ) + ) +} +``` + +## Adding Image Generation Models + +For image generation models, implement `ImageModality`: + +### Step 1: Create Request/Response Structures + +```swift +public struct AmazonImageRequestBody: BedrockBodyCodable { + let taskType: TaskType + private let textToImageParams: TextToImageParams? + private let imageGenerationConfig: ImageGenerationConfig + + public static func textToImage( + prompt: String, + negativeText: String?, + nrOfImages: Int?, + cfgScale: Double?, + seed: Int?, + quality: ImageQuality?, + resolution: ImageResolution? + ) -> Self { + // Implementation details... + } +} + +struct AmazonImageResponseBody: ContainsImageGeneration { + let images: [String] + + func getImageGenerationOutput() throws -> ImageGenerationOutput { + ImageGenerationOutput(images: images) + } +} +``` + +### Step 2: Implement ImageModality + +```swift +struct AmazonImage: ImageModality { + let parameters: ImageGenerationParameters + + func getName() -> String { "Amazon Image Generation" } + + func getImageGenerationParameters() -> ImageGenerationParameters { + parameters + } + + func hasTextToImageModality() -> Bool { true } + func hasImageVariationModality() -> Bool { false } + + func getTextToImageRequestBody(/* parameters */) throws -> BedrockBodyCodable { + // Implementation... + } + + func getImageResponseBody(from data: Data) throws -> ContainsImageGeneration { + let decoder = JSONDecoder() + return try decoder.decode(AmazonImageResponseBody.self, from: data) + } +} +``` + +## Hybrid Modalities + +For models supporting multiple capabilities, create custom modalities: + +```swift +struct ModelFamilyModality: TextModality, ConverseModality { + let parameters: TextGenerationParameters + let converseFeatures: [ConverseFeature] + let converseParameters: ConverseParameters + + init(parameters: TextGenerationParameters, features: [ConverseFeature] = [.textGeneration]) { + self.parameters = parameters + self.converseFeatures = features + self.converseParameters = ConverseParameters(textGenerationParameters: parameters) + } + + func getName() -> String { "Model Family Text and Converse" } + + // Implement TextModality methods + func getParameters() -> TextGenerationParameters { parameters } + func getTextRequestBody(/* ... */) throws -> BedrockBodyCodable { /* ... */ } + func getTextResponseBody(from data: Data) throws -> ContainsTextCompletion { /* ... */ } + + // Implement ConverseModality methods + func getConverseParameters() -> ConverseParameters { converseParameters } + func getConverseFeatures() -> [ConverseFeature] { converseFeatures } +} +``` + +## Parameter Validation + +Define parameter constraints carefully: + +```swift +// Supported parameter with range +Parameter(.temperature, minValue: 0.0, maxValue: 2.0, defaultValue: 1.0) + +// Supported parameter with no upper limit +Parameter(.maxTokens, minValue: 1, maxValue: nil, defaultValue: 1000) + +// Unsupported parameter +Parameter.notSupported(.topK) + +// Stop sequences with limits +StopSequenceParams(maxSequences: 4, defaultValue: []) + +// Stop sequences not supported +StopSequenceParams.notSupported() +``` + +## Testing New Models + +Test your model implementation: + +```swift +func testNewModel() async throws { + let bedrock = try await BedrockService() + let model = BedrockModel.new_bedrock_model + + // Test basic functionality + if model.hasTextModality() { + let completion = try await bedrock.completeText("Hello", with: model) + print("Text completion: \(completion.completion)") + } + + if model.hasConverseModality() { + let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Hello") + let reply = try await bedrock.converse(with: builder) + print("Converse reply: \(reply)") + } + + // Test parameter validation + do { + let _ = try await bedrock.completeText( + "Test", + with: model, + temperature: 5.0 // Should fail if max is < 5.0 + ) + } catch BedrockServiceError.parameterOutOfRange(let param, let value, let range) { + print("Expected parameter error: \(param) = \(value) not in \(range)") + } +} +``` + +## Best Practices + +1. **Follow AWS Documentation**: Check the official model documentation for exact request/response formats +2. **Validate Parameters**: Implement proper parameter validation based on model capabilities +3. **Handle Errors**: Provide clear error messages for unsupported features +4. **Test Thoroughly**: Test all supported features and parameter combinations +5. **Document Limitations**: Clearly document what features are and aren't supported + +## See Also + +- [AWS Bedrock Model Parameters](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html) +- [Converse API Supported Features](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html) \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/Authentication.md b/Sources/BedrockService/Docs.docc/Authentication.md new file mode 100644 index 00000000..716fd5a5 --- /dev/null +++ b/Sources/BedrockService/Docs.docc/Authentication.md @@ -0,0 +1,96 @@ +# Authentication + +Configure authentication for Amazon Bedrock access + +## Overview + +BedrockService supports multiple authentication methods to work with Amazon Bedrock. Choose the method that best fits your application's deployment environment and security requirements. + +## Default Authentication + +Uses the standard AWS credential provider chain: + +```swift +let bedrock = try await BedrockService( + region: .uswest2 + // authentication defaults to .default +) +``` + +The credential chain checks for credentials in this order: +1. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`) +2. AWS credentials file (`~/.aws/credentials`) +3. AWS config file (`~/.aws/config`) +4. IAM roles for Amazon EC2 instances +5. IAM roles for tasks (Amazon ECS) +6. IAM roles for Lambda functions + +## Profile-based Authentication + +Use a specific profile from your AWS credentials file: + +```swift +let bedrock = try await BedrockService( + region: .uswest2, + authentication: .profile(profileName: "my-profile") +) +``` + +## SSO Authentication + +Use AWS Single Sign-On authentication. Run `aws sso login --profile ` first: + +```swift +let bedrock = try await BedrockService( + region: .uswest2, + authentication: .sso(profileName: "my-sso-profile") +) +``` + +## Web Identity Token Authentication + +Use JWT tokens from external identity providers (ideal for iOS/macOS apps): + +```swift +let bedrock = try await BedrockService( + region: .uswest2, + authentication: .webIdentity( + token: jwtToken, + roleARN: "arn:aws:iam::123456789012:role/MyAppRole", + region: .uswest2, + notification: { + print("AWS credentials updated") + } + ) +) +``` + +## API Key Authentication + +Use API keys generated in the AWS console: + +```swift +let bedrock = try await BedrockService( + region: .uswest2, + authentication: .apiKey(key: "your-api-key-here") +) +``` + +> Important: Never hardcode API keys in your application. Use secure storage or environment variables. + +## Static Credentials (Testing Only) + +For testing and debugging purposes only: + +```swift +let bedrock = try await BedrockService( + region: .uswest2, + authentication: .static( + accessKey: "AKIAIOSFODNN7EXAMPLE", + secretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + sessionToken: "optional-session-token" + ) +) +``` + +> Warning: Never use static credentials in production or commit them to version control. \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/BedrockService.md b/Sources/BedrockService/Docs.docc/BedrockService.md new file mode 100644 index 00000000..7b37610c --- /dev/null +++ b/Sources/BedrockService/Docs.docc/BedrockService.md @@ -0,0 +1,40 @@ +# ``BedrockService`` + +@Metadata { + @PageKind(article) + @PageColor(green) + @SupportedLanguage(swift) + @PageImage(source: "bedrock.png", alt: "BedrockService", purpose: icon) +} + +A Swift library for interacting with Amazon Bedrock foundation models + +## Overview + +BedrockService is a lightweight layer on top of the AWS SDK for Swift that provides convenient access to Amazon Bedrock's capabilities. With support for text generation, image creation, embeddings, and conversational AI, this library makes it easy to integrate foundation models into your Swift applications. + +## Topics + +### Getting Started + +- +- + +### Core Features + +- +- +- +- + +### Advanced Topics + +- +- +- +- +- + +### Extending the Library + +- \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/Converse.md b/Sources/BedrockService/Docs.docc/Converse.md new file mode 100644 index 00000000..a69f3c17 --- /dev/null +++ b/Sources/BedrockService/Docs.docc/Converse.md @@ -0,0 +1,92 @@ +# Converse API + +Build conversational AI applications with the Converse API + +## Overview + +The Converse API provides a unified interface for text-based interactions with foundation models. It supports multi-turn conversations, system prompts, and maintains conversation history automatically. + +## Basic Text Conversation + +Start a simple conversation with a foundation model: + +```swift +let model: BedrockModel = .nova_lite + +guard model.hasConverseModality() else { + throw MyError.incorrectModality("\(model.name) does not support converse") +} + +var builder = try ConverseRequestBuilder(with: model) + .withPrompt("Tell me about rainbows") + +var reply = try await bedrock.converse(with: builder) +print("Assistant: \(reply)") + +// Continue the conversation +builder = try ConverseRequestBuilder(from: builder, with: reply) + .withPrompt("Do you think birds can see them too?") + +reply = try await bedrock.converse(with: builder) +print("Assistant: \(reply)") +``` + +## Inference Parameters + +Control the model's behavior with inference parameters: + +```swift +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Tell me about rainbows") + .withMaxTokens(512) + .withTemperature(0.2) + .withStopSequences(["END", "STOP"]) + .withSystemPrompts(["Be concise", "Use simple language"]) + +let reply = try await bedrock.converse(with: builder) +``` + +## System Prompts + +Guide the model's behavior with system prompts: + +```swift +let builder = try ConverseRequestBuilder(with: model) + .withSystemPrompts([ + "You are a helpful assistant", + "Always provide accurate information", + "Be concise in your responses" + ]) + .withPrompt("What is machine learning?") +``` + +## Custom Messages + +Build messages manually for more control: + +```swift +// Simple text message +let reply = try await bedrock.converse( + with: model, + conversation: [Message("What day of the week is it?")] +) + +// With inference parameters +let reply = try await bedrock.converse( + with: model, + conversation: [Message("What day of the week is it?")], + maxTokens: 512, + temperature: 1, + topP: 0.8, + stopSequences: ["THE END"], + systemPrompts: ["Today is Wednesday, make sure to mention that."] +) +``` + +## See Also + +- +- +- +- +- \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/Documents.md b/Sources/BedrockService/Docs.docc/Documents.md new file mode 100644 index 00000000..3d0ec28d --- /dev/null +++ b/Sources/BedrockService/Docs.docc/Documents.md @@ -0,0 +1,139 @@ +# Documents + +Process documents with foundation models + +## Overview + +Document processing allows you to send PDF, text, and other document formats to foundation models for analysis, summarization, and question answering. + +## Basic Document Processing + +Send a document for analysis: + +```swift +let model: BedrockModel = .nova_lite + +guard model.hasConverseModality(.document) else { + throw MyError.incorrectModality("\(model.name) does not support documents") +} + +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Can you give me a summary of this chapter?") + .withDocument(name: "Chapter 1", format: .pdf, source: base64EncodedDocument) + +let reply = try await bedrock.converse(with: builder) +print("Assistant: \(reply)") +``` + +## Supported Document Formats + +BedrockService supports various document formats: +- PDF (`.pdf`) +- Plain text (`.txt`) +- Markdown (`.md`) +- CSV (`.csv`) +- Microsoft Word (`.docx`) + +## Document with Parameters + +Combine document processing with inference parameters: + +```swift +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Summarize the key points from this document") + .withDocument(name: "Report", format: .pdf, source: base64EncodedDocument) + .withMaxTokens(512) + .withTemperature(0.4) + +let reply = try await bedrock.converse(with: builder) +``` + +## Multi-turn Document Conversations + +Continue conversations about the same document: + +```swift +var builder = try ConverseRequestBuilder(with: model) + .withPrompt("What are the main conclusions in this research paper?") + .withDocument(name: "Research Paper", format: .pdf, source: base64EncodedDocument) + +var reply = try await bedrock.converse(with: builder) +print("Assistant: \(reply)") + +// Ask follow-up questions without re-sending the document +builder = try ConverseRequestBuilder(from: builder, with: reply) + .withPrompt("What methodology did they use?") + +reply = try await bedrock.converse(with: builder) +print("Assistant: \(reply)") +``` + +## Using DocumentBlock + +Create `DocumentBlock` objects for more control: + +```swift +let documentBlock = DocumentBlock( + name: "Financial Report Q4", + format: .pdf, + source: base64EncodedDocument +) + +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Analyze the financial trends in this report") + .withDocument(documentBlock) +``` + +## Document Analysis Tasks + +Common document processing tasks: + +### Summarization +```swift +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Provide a concise summary of this document") + .withDocument(name: "Article", format: .pdf, source: documentData) +``` + +### Question Answering +```swift +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("What is the author's main argument about climate change?") + .withDocument(name: "Climate Paper", format: .pdf, source: documentData) +``` + +### Translation +```swift +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Translate this document to French") + .withDocument(name: "Contract", format: .pdf, source: documentData) +``` + +## Streaming with Documents + +Document processing works with streaming responses: + +```swift +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Analyze this legal document and highlight key clauses") + .withDocument(name: "Contract", format: .pdf, source: base64EncodedDocument) + +let stream = try await bedrock.converseStream(with: builder) + +for try await element in stream { + switch element { + case .text(_, let text): + print(text, terminator: "") + case .messageComplete(_): + print("\n") + default: + break + } +} +``` + +## See Also + +- +- +- \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/Embeddings.md b/Sources/BedrockService/Docs.docc/Embeddings.md new file mode 100644 index 00000000..73f92c47 --- /dev/null +++ b/Sources/BedrockService/Docs.docc/Embeddings.md @@ -0,0 +1,274 @@ +# Embeddings + +Generate vector embeddings for semantic analysis + +## Overview + +Embeddings convert text into numerical vector representations that capture semantic meaning. These vectors enable similarity comparisons, clustering, and retrieval-augmented generation (RAG) systems. + +## Basic Embeddings + +Generate embeddings from text: + +```swift +let model: BedrockModel = .titan_embed_text_v2 + +guard model.hasEmbeddingsModality() else { + throw MyError.incorrectModality("\(model.name) does not support embeddings") +} + +let embeddings = try await bedrock.embed( + "Swift is a powerful programming language", + with: model +) + +print("Generated embeddings with \(embeddings.count) dimensions") +print("First few values: \(Array(embeddings.prefix(5)))") +``` + +## Vector Size Control + +Specify the embedding vector size: + +```swift +let embeddings = try await bedrock.embed( + "Machine learning and artificial intelligence", + with: model, + vectorSize: 512 +) + +print("Embedding dimensions: \(embeddings.count)") +``` + +## Semantic Similarity + +Compare text similarity using embeddings: + +```swift +let text1 = "The cat sat on the mat" +let text2 = "A feline rested on the rug" +let text3 = "Quantum computing uses qubits" + +let embedding1 = try await bedrock.embed(text1, with: model) +let embedding2 = try await bedrock.embed(text2, with: model) +let embedding3 = try await bedrock.embed(text3, with: model) + +// Calculate cosine similarity +func cosineSimilarity(_ a: [Double], _ b: [Double]) -> Double { + let dotProduct = zip(a, b).map(*).reduce(0, +) + let magnitudeA = sqrt(a.map { $0 * $0 }.reduce(0, +)) + let magnitudeB = sqrt(b.map { $0 * $0 }.reduce(0, +)) + return dotProduct / (magnitudeA * magnitudeB) +} + +let similarity12 = cosineSimilarity(embedding1, embedding2) +let similarity13 = cosineSimilarity(embedding1, embedding3) + +print("Similarity between text1 and text2: \(similarity12)") +print("Similarity between text1 and text3: \(similarity13)") +// text1 and text2 should have higher similarity than text1 and text3 +``` + +## Batch Processing + +Process multiple texts efficiently: + +```swift +let texts = [ + "Apple is a technology company", + "Bananas are yellow fruits", + "Microsoft develops software", + "Oranges are citrus fruits", + "Google creates search engines" +] + +var embeddings: [[Double]] = [] + +for text in texts { + let embedding = try await bedrock.embed(text, with: model) + embeddings.append(embedding) +} + +// Find most similar texts +func findMostSimilar(to queryIndex: Int, in embeddings: [[Double]]) -> Int { + var maxSimilarity = -1.0 + var mostSimilarIndex = 0 + + for (index, embedding) in embeddings.enumerated() { + guard index != queryIndex else { continue } + + let similarity = cosineSimilarity(embeddings[queryIndex], embedding) + if similarity > maxSimilarity { + maxSimilarity = similarity + mostSimilarIndex = index + } + } + + return mostSimilarIndex +} + +let queryIndex = 0 // "Apple is a technology company" +let similarIndex = findMostSimilar(to: queryIndex, in: embeddings) +print("Most similar to '\(texts[queryIndex])': '\(texts[similarIndex])'") +``` + +## Document Retrieval System + +Build a simple RAG system: + +```swift +struct Document { + let id: String + let content: String + let embedding: [Double] +} + +class DocumentStore { + private var documents: [Document] = [] + private let bedrock: BedrockService + private let model: BedrockModel + + init(bedrock: BedrockService, model: BedrockModel) { + self.bedrock = bedrock + self.model = model + } + + func addDocument(_ content: String, id: String) async throws { + let embedding = try await bedrock.embed(content, with: model) + let document = Document(id: id, content: content, embedding: embedding) + documents.append(document) + } + + func search(_ query: String, topK: Int = 3) async throws -> [Document] { + let queryEmbedding = try await bedrock.embed(query, with: model) + + let similarities = documents.map { doc in + (doc, cosineSimilarity(queryEmbedding, doc.embedding)) + } + + return similarities + .sorted { $0.1 > $1.1 } + .prefix(topK) + .map { $0.0 } + } +} + +// Usage +let store = DocumentStore(bedrock: bedrock, model: model) + +try await store.addDocument("Swift is a programming language developed by Apple", id: "doc1") +try await store.addDocument("Python is popular for data science and machine learning", id: "doc2") +try await store.addDocument("JavaScript runs in web browsers and Node.js", id: "doc3") + +let results = try await store.search("Apple programming language") +for doc in results { + print("Found: \(doc.content)") +} +``` + +## Text Clustering + +Group similar texts using embeddings: + +```swift +struct TextCluster { + let centroid: [Double] + var texts: [String] + var embeddings: [[Double]] +} + +func kMeansClustering(texts: [String], embeddings: [[Double]], k: Int, iterations: Int = 10) -> [TextCluster] { + // Simple k-means implementation + var clusters = Array(0.. +- \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/ImageGeneration.md b/Sources/BedrockService/Docs.docc/ImageGeneration.md new file mode 100644 index 00000000..93a6a258 --- /dev/null +++ b/Sources/BedrockService/Docs.docc/ImageGeneration.md @@ -0,0 +1,190 @@ +# Image Generation + +Create and modify images with foundation models + +## Overview + +BedrockService supports image generation and variation capabilities, allowing you to create images from text descriptions and generate variations of existing images. + +## Text-to-Image Generation + +Generate images from text descriptions: + +```swift +let model: BedrockModel = .nova_canvas + +guard model.hasImageModality(), + model.hasTextToImageModality() else { + throw MyError.incorrectModality("\(model.name) does not support image generation") +} + +let imageGeneration = try await bedrock.generateImage( + "A serene landscape with mountains at sunset", + with: model +) + +// Access generated images +for (index, image) in imageGeneration.images.enumerated() { + print("Generated image \(index + 1): \(image.prefix(50))...") +} +``` + +## Generation Parameters + +Control image generation with various parameters: + +```swift +let imageGeneration = try await bedrock.generateImage( + "A futuristic city skyline at night", + with: model, + negativePrompt: "dark, gloomy, abandoned", + nrOfImages: 3, + cfgScale: 7.0, + seed: 42, + quality: .standard, + resolution: ImageResolution(width: 1024, height: 1024) +) +``` + +### Available Parameters + +- **negativePrompt**: Describe what to avoid in the image +- **nrOfImages**: Number of images to generate (1-4 typically) +- **cfgScale**: How closely to follow the prompt (1.0-20.0) +- **seed**: For reproducible results +- **quality**: Image quality setting (`.standard`, `.premium`) +- **resolution**: Output image dimensions + +## Image Variations + +Create variations of existing images: + +```swift +let model: BedrockModel = .nova_canvas + +guard model.hasImageModality(), + model.hasImageVariationModality() else { + throw MyError.incorrectModality("\(model.name) does not support image variations") +} + +let imageVariations = try await bedrock.generateImageVariation( + images: [base64EncodedImage], + prompt: "A dog drinking from this teacup", + with: model +) +``` + +## Variation Parameters + +Fine-tune image variations: + +```swift +let imageVariations = try await bedrock.generateImageVariation( + images: [base64EncodedImage], + prompt: "Transform this into a watercolor painting", + with: model, + negativePrompt: "photorealistic, sharp edges", + similarity: 0.8, + nrOfVariations: 4, + cfgScale: 7.0, + seed: 123, + quality: .premium, + resolution: ImageResolution(width: 512, height: 512) +) +``` + +### Variation-Specific Parameters + +- **similarity**: How similar variations should be to source (0.0-1.0) +- **nrOfVariations**: Number of variations to create + +## Working with Image Data + +Handle base64-encoded image data: + +```swift +// Convert image file to base64 +func loadImageAsBase64(from path: String) -> String? { + guard let imageData = FileManager.default.contents(atPath: path) else { + return nil + } + return imageData.base64EncodedString() +} + +// Save generated image +func saveBase64Image(_ base64String: String, to path: String) { + guard let imageData = Data(base64Encoded: base64String) else { + print("Invalid base64 data") + return + } + + do { + try imageData.write(to: URL(fileURLWithPath: path)) + print("Image saved to \(path)") + } catch { + print("Failed to save image: \(error)") + } +} + +// Usage +if let sourceImage = loadImageAsBase64(from: "input.jpg") { + let variations = try await bedrock.generateImageVariation( + images: [sourceImage], + prompt: "Make this image look like a vintage photograph", + with: model + ) + + for (index, image) in variations.images.enumerated() { + saveBase64Image(image, to: "variation_\(index).jpg") + } +} +``` + +## Model-Specific Capabilities + +Different models support different features: + +```swift +// Check model capabilities +let model: BedrockModel = .nova_canvas + +if model.hasTextToImageModality() { + print("Supports text-to-image generation") +} + +if model.hasImageVariationModality() { + print("Supports image variations") +} + +// Get model-specific parameter limits +if let imageModality = model.modality as? ImageModality { + let params = imageModality.getImageGenerationParameters() + print("Max images: \(params.nrOfImages.maxValue ?? "unlimited")") + print("CFG scale range: \(params.cfgScale.minValue)-\(params.cfgScale.maxValue ?? 20)") +} +``` + +## Error Handling + +Handle common image generation errors: + +```swift +do { + let images = try await bedrock.generateImage( + "A beautiful sunset over the ocean", + with: model, + nrOfImages: 5 // Might exceed model limit + ) +} catch BedrockServiceError.parameterOutOfRange(let parameter, let value, let range) { + print("Parameter \(parameter) value \(value) is outside allowed range: \(range)") +} catch BedrockServiceError.notSupported(let feature) { + print("Feature not supported: \(feature)") +} catch { + print("Image generation failed: \(error)") +} +``` + +## See Also + +- +- \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/QuickStart.md b/Sources/BedrockService/Docs.docc/QuickStart.md new file mode 100644 index 00000000..39fe2d78 --- /dev/null +++ b/Sources/BedrockService/Docs.docc/QuickStart.md @@ -0,0 +1,65 @@ +# Quick Start + +Get up and running with BedrockService in minutes + +## Overview + +This guide will help you quickly set up and start using BedrockService in your Swift project. + +## Installation + +Add BedrockService to your Swift package: + +```bash +swift package add-dependency https://github.com/build-on-aws/swift-bedrock-library.git --branch main +swift package add-target-dependency BedrockService TargetName --package swift-bedrock-library +``` + +Update your `Package.swift` to include platform requirements: + +```swift +import PackageDescription + +let package = Package( + name: "ProjectName", + platforms: [.macOS(.v15), .iOS(.v18), .tvOS(.v18)], + dependencies: [ + .package(url: "https://github.com/build-on-aws/swift-bedrock-library.git", branch: "main"), + ], + targets: [ + .executableTarget( + name: "TargetName", + dependencies: [ + .product(name: "BedrockService", package: "swift-bedrock-library"), + ] + ) + ] +) +``` + +## Basic Usage + +```swift +import BedrockService + +// Initialize the service +let bedrock = try await BedrockService(region: .uswest2) + +// List available models +let models = try await bedrock.listModels() + +// Send a simple text prompt +let model: BedrockModel = .nova_lite +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Tell me about rainbows") + +let reply = try await bedrock.converse(with: builder) +print("Assistant: \(reply)") +``` + +## Next Steps + +- Learn about different methods +- Explore for conversational AI +- Try for creating images +- Check out for function calling \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/Reasoning.md b/Sources/BedrockService/Docs.docc/Reasoning.md new file mode 100644 index 00000000..df391bc6 --- /dev/null +++ b/Sources/BedrockService/Docs.docc/Reasoning.md @@ -0,0 +1,186 @@ +# Reasoning + +Access the model's reasoning process + +## Overview + +Reasoning capabilities allow you to see how foundation models think through problems, providing transparency into their decision-making process. + +## Basic Reasoning + +Enable reasoning to see the model's thought process: + +```swift +let model: BedrockModel = .claudev3_7_sonnet + +guard model.hasConverseModality(.reasoning) else { + throw MyError.incorrectModality("\(model.name) does not support reasoning") +} + +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Solve this math problem: If a train travels 60 mph for 2.5 hours, how far does it go?") + .withReasoning() + +let reply = try await bedrock.converse(with: builder) + +if let reasoning = try? reply.getReasoningBlock() { + print("Reasoning: \(reasoning.reasoning)") +} +print("Answer: \(reply)") +``` + +## Reasoning with Token Limits + +Control the length of reasoning output: + +```swift +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Explain the causes of World War I") + .withReasoning(maxReasoningTokens: 1024) + +let reply = try await bedrock.converse(with: builder) +``` + +## Streaming Reasoning + +See reasoning unfold in real-time: + +```swift +let builder = try ConverseRequestBuilder(with model) + .withPrompt("Plan a 7-day trip to Japan") + .withReasoning(maxReasoningTokens: 2048) + +let stream = try await bedrock.converseStream(with: builder) + +var reasoningIndexes: [Int] = [] +var textIndexes: [Int] = [] + +for try await element in stream { + switch element { + case .reasoning(let index, let reasoning): + if !reasoningIndexes.contains(index) { + reasoningIndexes.append(index) + print("\n🤔 Reasoning: ") + } + print(reasoning, terminator: "") + + case .text(let index, let text): + if !textIndexes.contains(index) { + textIndexes.append(index) + print("\n💬 Response: ") + } + print(text, terminator: "") + + case .messageComplete(_): + print("\n") + + default: + break + } +} +``` + +## Complex Problem Solving + +Use reasoning for multi-step problems: + +```swift +let builder = try ConverseRequestBuilder(with: model) + .withPrompt(""" + A company has 150 employees. 60% work in engineering, 25% in sales, + and the rest in administration. If engineering gets a 10% budget increase + and sales gets a 5% increase, what's the total percentage increase + in employee-related costs? + """) + .withReasoning() + .withTemperature(0.1) // Lower temperature for more focused reasoning + +let reply = try await bedrock.converse(with: builder) + +if let reasoning = try? reply.getReasoningBlock() { + print("Step-by-step reasoning:") + print(reasoning.reasoning) + print("\nFinal answer:") +} +print(reply) +``` + +## Reasoning in Conversations + +Maintain reasoning across conversation turns: + +```swift +var builder = try ConverseRequestBuilder(with: model) + .withPrompt("I need to choose between two job offers. Can you help me think through this?") + .withReasoning() + +var reply = try await bedrock.converse(with: builder) + +if let reasoning = try? reply.getReasoningBlock() { + print("Initial reasoning: \(reasoning.reasoning)") +} +print("Assistant: \(reply)") + +// Continue with more details +builder = try ConverseRequestBuilder(from: builder, with: reply) + .withPrompt(""" + Job A: $80k salary, great benefits, 30-minute commute, startup environment + Job B: $75k salary, okay benefits, 10-minute commute, established company + """) + .withReasoning() + +reply = try await bedrock.converse(with: builder) + +if let reasoning = try? reply.getReasoningBlock() { + print("Analysis reasoning: \(reasoning.reasoning)") +} +print("Assistant: \(reply)") +``` + +## Reasoning with Tools + +Combine reasoning with function calling: + +```swift +let calculatorTool = try Tool( + name: "calculate", + inputSchema: JSON([ + "type": "object", + "properties": [ + "expression": ["type": "string"] + ], + "required": ["expression"] + ]), + description: "Perform mathematical calculations" +) + +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Calculate the compound interest on $1000 at 5% annually for 3 years") + .withTool(calculatorTool) + .withReasoning() + +let reply = try await bedrock.converse(with: builder) + +// The model will reason about the problem and potentially use the calculator tool +if let reasoning = try? reply.getReasoningBlock() { + print("Reasoning: \(reasoning.reasoning)") +} + +if let toolUse = try? reply.getToolUse() { + let expression: String? = toolUse.input["expression"] + let result = calculate(expression ?? "") + + let finalBuilder = try ConverseRequestBuilder(from: builder, with: reply) + .withToolResult(result) + .withReasoning() + + let finalReply = try await bedrock.converse(with: finalBuilder) + print("Final answer: \(finalReply)") +} +``` + +## See Also + +- +- +- \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/Resources/bedrock.png b/Sources/BedrockService/Docs.docc/Resources/bedrock.png new file mode 100644 index 00000000..38050a7a Binary files /dev/null and b/Sources/BedrockService/Docs.docc/Resources/bedrock.png differ diff --git a/Sources/BedrockService/Docs.docc/Streaming.md b/Sources/BedrockService/Docs.docc/Streaming.md new file mode 100644 index 00000000..3051c883 --- /dev/null +++ b/Sources/BedrockService/Docs.docc/Streaming.md @@ -0,0 +1,103 @@ +# Streaming + +Get real-time responses with streaming + +## Overview + +Streaming allows you to receive model responses in real-time as they're generated, providing a better user experience for interactive applications. + +## Basic Streaming + +Use the same `ConverseRequestBuilder` with `converseStream`: + +```swift +let model: BedrockModel = .nova_lite + +guard model.hasConverseModality(.streaming) else { + throw MyError.incorrectModality("\(model.name) does not support streaming") +} + +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Tell me about rainbows") + +let reply = try await bedrock.converseStream(with: builder) + +for try await element in reply.stream { + switch element { + case .messageStart(let role): + print("Message started with role: \(role)") + + case .text(_, let text): + print(text, terminator: "") + + case .messageComplete(_): + print("\n") + + case .metaData(let metaData): + print("Metadata: \(metaData)") + + default: + break + } +} +``` + +## Stream Elements + +The stream provides different types of elements: + +- `.messageStart(Role)` - Beginning of a message +- `.text(Int, String)` - Partial text content with index +- `.reasoning(Int, String)` - Partial reasoning content with index +- `.toolUse(Int, ToolUseBlock)` - Complete tool use response +- `.messageComplete(Message)` - Complete message with all content +- `.metaData(ResponseMetadata)` - Response metadata including token usage + +## Interactive Chat Loop + +Build an interactive chat application: + +```swift +var builder = try ConverseRequestBuilder(with: model) + .withPrompt("Introduce yourself") + +while true { + let stream = try await bedrock.converseStream(with: builder) + var assistantMessage: Message = Message("empty") + + for try await element in stream { + switch element { + case .text(_, let text): + print(text, terminator: "") + + case .messageComplete(let message): + assistantMessage = message + print("\n") + + default: + break + } + } + + print("You: ") + guard let prompt = readLine(), prompt != "quit" else { break } + + builder = try ConverseRequestBuilder(from: builder, with: assistantMessage) + .withPrompt(prompt) +} +``` + +## Low-Level Stream Access + +For maximum control, access the raw AWS SDK stream: + +```swift +let reply = try await bedrock.converseStream(with: builder) +// Access reply.rawStream for the low-level AWS SDK stream +``` + +## See Also + +- +- +- \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/TextGeneration.md b/Sources/BedrockService/Docs.docc/TextGeneration.md new file mode 100644 index 00000000..45fa2439 --- /dev/null +++ b/Sources/BedrockService/Docs.docc/TextGeneration.md @@ -0,0 +1,167 @@ +# Text Generation + +Generate text using the InvokeModel API + +## Overview + +The InvokeModel API provides direct access to foundation models for text completion tasks. This is useful for simple text generation without the conversational context of the Converse API. + +## Basic Text Generation + +Generate text from a prompt: + +```swift +let model: BedrockModel = .nova_micro + +guard model.hasTextModality() else { + throw MyError.incorrectModality("\(model.name) does not support text generation") +} + +let textCompletion = try await bedrock.completeText( + "Write a story about a space adventure", + with: model +) + +print(textCompletion.completion) +``` + +## Generation Parameters + +Control text generation behavior: + +```swift +let textCompletion = try await bedrock.completeText( + "Explain quantum computing in simple terms", + with: model, + maxTokens: 1000, + temperature: 0.7, + topP: 0.9, + topK: 250, + stopSequences: ["THE END", "CONCLUSION"] +) + +print(textCompletion.completion) +``` + +### Parameter Descriptions + +- **maxTokens**: Maximum number of tokens to generate +- **temperature**: Controls randomness (0.0 = deterministic, 1.0 = very random) +- **topP**: Nucleus sampling threshold (0.0-1.0) +- **topK**: Limits vocabulary to top K tokens +- **stopSequences**: Strings that stop generation when encountered + +## Model-Specific Parameters + +Different models support different parameters: + +```swift +// Check what parameters a model supports +if let textModality = model.modality as? TextModality { + let params = textModality.getParameters() + + if params.temperature.isSupported { + print("Temperature range: \(params.temperature.minValue)-\(params.temperature.maxValue ?? 1.0)") + } + + if params.topK.isSupported { + print("TopK supported with max: \(params.topK.maxValue ?? "unlimited")") + } else { + print("TopK not supported by this model") + } +} +``` + +## Use Cases + +### Creative Writing +```swift +let story = try await bedrock.completeText( + "Once upon a time in a magical forest", + with: model, + temperature: 0.9, // High creativity + maxTokens: 500 +) +``` + +### Technical Documentation +```swift +let documentation = try await bedrock.completeText( + "## API Reference\n\nThe authenticate() function", + with: model, + temperature: 0.3, // Low creativity, more factual + maxTokens: 800 +) +``` + +### Code Generation +```swift +let code = try await bedrock.completeText( + "// Swift function to calculate fibonacci numbers\nfunc fibonacci(", + with: model, + temperature: 0.1, // Very deterministic + stopSequences: ["\n\n", "// End"] +) +``` + +### Structured Output +```swift +let jsonOutput = try await bedrock.completeText( + "Generate a JSON object representing a user profile:", + with: model, + temperature: 0.2, + stopSequences: ["}"], + maxTokens: 200 +) +``` + +## Error Handling + +Handle parameter validation and model errors: + +```swift +do { + let completion = try await bedrock.completeText( + "Generate text", + with: model, + temperature: 2.0 // Invalid temperature + ) +} catch BedrockServiceError.parameterOutOfRange(let param, let value, let range) { + print("Parameter \(param) value \(value) outside range: \(range)") +} catch BedrockServiceError.notSupported(let feature) { + print("Feature not supported: \(feature)") +} catch { + print("Text generation failed: \(error)") +} +``` + +## Comparing with Converse API + +| Feature | InvokeModel (Text) | Converse API | +|---------|-------------------|--------------| +| Conversation history | ❌ | ✅ | +| System prompts | ❌ | ✅ | +| Tool calling | ❌ | ✅ | +| Vision support | ❌ | ✅ | +| Streaming | ❌ | ✅ | +| Simple text completion | ✅ | ✅ | +| Lower latency | ✅ | ❌ | + +## When to Use Text Generation + +Use the InvokeModel text generation API when you need: +- Simple, one-shot text completion +- Lower latency responses +- Direct control over model parameters +- No conversation context required + +Use the Converse API when you need: +- Multi-turn conversations +- System prompts and instructions +- Tool calling capabilities +- Vision or document processing + +## See Also + +- +- \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/Tools.md b/Sources/BedrockService/Docs.docc/Tools.md new file mode 100644 index 00000000..cb017b76 --- /dev/null +++ b/Sources/BedrockService/Docs.docc/Tools.md @@ -0,0 +1,215 @@ +# Tools + +Enable function calling with foundation models + +## Overview + +Tools allow foundation models to call external functions, enabling them to access real-time data, perform calculations, and interact with external systems. + +## Basic Tool Usage + +Define and use a simple tool: + +```swift +let model: BedrockModel = .nova_lite + +guard model.hasConverseModality(.toolUse) else { + throw MyError.incorrectModality("\(model.name) does not support tools") +} + +// Define the tool's input schema +let inputSchema = JSON([ + "type": "object", + "properties": [ + "sign": [ + "type": "string", + "description": "Radio station call sign (e.g., WZPZ, WKRP)" + ] + ], + "required": ["sign"] +]) + +// Create the tool +let tool = try Tool( + name: "top_song", + inputSchema: inputSchema, + description: "Get the most popular song on a radio station" +) + +// Use the tool in a conversation +var builder = try ConverseRequestBuilder(with: model) + .withPrompt("What is the most popular song on WZPZ?") + .withTool(tool) + +var reply = try await bedrock.converse(with: builder) + +// Handle tool use request +if let toolUse = try? reply.getToolUse() { + let sign: String? = toolUse.input["sign"] + let result = getMostPopularSong(for: sign ?? "") + + builder = try ConverseRequestBuilder(from: builder, with: reply) + .withToolResult(result) + + reply = try await bedrock.converse(with: builder) +} + +print("Assistant: \(reply)") +``` + +## Multiple Tools + +Add multiple tools to expand capabilities: + +```swift +let weatherTool = try Tool( + name: "get_weather", + inputSchema: JSON([ + "type": "object", + "properties": [ + "location": ["type": "string", "description": "City name"] + ], + "required": ["location"] + ]), + description: "Get current weather for a location" +) + +let calculatorTool = try Tool( + name: "calculate", + inputSchema: JSON([ + "type": "object", + "properties": [ + "expression": ["type": "string", "description": "Math expression to evaluate"] + ], + "required": ["expression"] + ]), + description: "Perform mathematical calculations" +) + +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("What's the weather in Paris and what's 15 * 23?") + .withTools([weatherTool, calculatorTool]) +``` + +## Tool Result Types + +Return different types of data as tool results: + +```swift +// String result +builder = try ConverseRequestBuilder(from: builder, with: reply) + .withToolResult("Sunny, 22°C") + +// JSON result +let weatherData = ["temperature": 22, "condition": "sunny"] +builder = try ConverseRequestBuilder(from: builder, with: reply) + .withToolResult(weatherData) + +// Custom Codable type +struct WeatherInfo: Codable { + let temperature: Int + let condition: String +} + +let weather = WeatherInfo(temperature: 22, condition: "sunny") +builder = try ConverseRequestBuilder(from: builder, with: reply) + .withToolResult(weather) +``` + +## Interactive Tool Usage + +Build an interactive system with multiple tool calls: + +```swift +var builder = try ConverseRequestBuilder(with: model) + .withPrompt("Introduce yourself and mention your available tools") + .withTools([weatherTool, calculatorTool]) + +while true { + let reply = try await bedrock.converse(with: builder) + + if let toolUse = try? reply.getToolUse() { + let result = handleToolUse(toolUse) + builder = try ConverseRequestBuilder(from: builder, with: reply) + .withToolResult(result) + } else { + print("Assistant: \(reply)") + print("You: ") + guard let prompt = readLine(), prompt != "quit" else { break } + + builder = try ConverseRequestBuilder(from: builder, with: reply) + .withPrompt(prompt) + } +} + +func handleToolUse(_ toolUse: ToolUseBlock) -> String { + switch toolUse.name { + case "get_weather": + let location: String? = toolUse.input["location"] + return getWeather(for: location ?? "") + case "calculate": + let expression: String? = toolUse.input["expression"] + return calculate(expression ?? "") + default: + return "Unknown tool" + } +} +``` + +## Streaming with Tools + +Tools work seamlessly with streaming: + +```swift +let stream = try await bedrock.converseStream(with: builder) + +for try await element in stream { + switch element { + case .text(_, let text): + print(text, terminator: "") + case .toolUse(let index, let toolUse): + print("Tool requested: \(toolUse.name)") + case .messageComplete(let message): + // Handle tool use from complete message + break + default: + break + } +} +``` + +## JSON Schema Helper + +The `JSON` struct provides convenient schema creation: + +```swift +// From dictionary +let schema = JSON([ + "type": "object", + "properties": [ + "query": ["type": "string"] + ] +]) + +// From JSON string +let jsonString = """ +{ + "type": "object", + "properties": { + "location": {"type": "string"}, + "units": {"type": "string", "enum": ["celsius", "fahrenheit"]} + }, + "required": ["location"] +} +""" +let schema = try JSON(from: jsonString) + +// Access values +let location: String? = toolUse.input["location"] +let units: String? = toolUse.input["units"] +``` + +## See Also + +- +- \ No newline at end of file diff --git a/Sources/BedrockService/Docs.docc/Vision.md b/Sources/BedrockService/Docs.docc/Vision.md new file mode 100644 index 00000000..5a863c81 --- /dev/null +++ b/Sources/BedrockService/Docs.docc/Vision.md @@ -0,0 +1,109 @@ +# Vision + +Add images to your conversations + +## Overview + +Vision capabilities allow you to send images to foundation models for analysis, description, and question answering about visual content. + +## Basic Image Analysis + +Send an image with your prompt: + +```swift +let model: BedrockModel = .nova_lite + +guard model.hasConverseModality(.vision) else { + throw MyError.incorrectModality("\(model.name) does not support vision") +} + +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Can you tell me about this plant?") + .withImage(format: .jpeg, source: base64EncodedImage) + +let reply = try await bedrock.converse(with: builder) +print("Assistant: \(reply)") +``` + +## Supported Image Formats + +BedrockService supports common image formats: +- JPEG (`.jpeg`) +- PNG (`.png`) +- GIF (`.gif`) +- WebP (`.webp`) + +## Image with Parameters + +Combine images with inference parameters: + +```swift +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Describe this image in detail") + .withImage(format: .jpeg, source: base64EncodedImage) + .withTemperature(0.8) + .withMaxTokens(1000) + +let reply = try await bedrock.converse(with: builder) +``` + +## Multi-turn Vision Conversations + +Continue conversations that include images: + +```swift +var builder = try ConverseRequestBuilder(with: model) + .withPrompt("What type of flower is this?") + .withImage(format: .jpeg, source: base64EncodedImage) + +var reply = try await bedrock.converse(with: builder) +print("Assistant: \(reply)") + +// Continue without sending the image again +builder = try ConverseRequestBuilder(from: builder, with: reply) + .withPrompt("Where can I find those flowers?") + +reply = try await bedrock.converse(with: builder) +print("Assistant: \(reply)") +``` + +## Using ImageBlock + +For more control, create `ImageBlock` objects directly: + +```swift +let imageBlock = ImageBlock(format: .jpeg, source: base64EncodedImage) + +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Analyze this image") + .withImage(imageBlock) +``` + +## Streaming with Vision + +Vision works seamlessly with streaming: + +```swift +let builder = try ConverseRequestBuilder(with: model) + .withPrompt("Describe what you see in this image") + .withImage(format: .png, source: base64EncodedImage) + +let stream = try await bedrock.converseStream(with: builder) + +for try await element in stream { + switch element { + case .text(_, let text): + print(text, terminator: "") + case .messageComplete(_): + print("\n") + default: + break + } +} +``` + +## See Also + +- +- +- \ No newline at end of file diff --git a/Sources/InvokeModel/BedrockService+ImageParameterValidation.swift b/Sources/BedrockService/InvokeModel/BedrockService+ImageParameterValidation.swift similarity index 91% rename from Sources/InvokeModel/BedrockService+ImageParameterValidation.swift rename to Sources/BedrockService/InvokeModel/BedrockService+ImageParameterValidation.swift index 78182e13..738f9b85 100644 --- a/Sources/InvokeModel/BedrockService+ImageParameterValidation.swift +++ b/Sources/BedrockService/InvokeModel/BedrockService+ImageParameterValidation.swift @@ -65,7 +65,11 @@ extension BedrockService { /// Validates parameters for text-to-image generation requests /// - Parameters: - /// - modality: The text-to-image modality of the model to use + /// - model: The text-to-image model to use + /// - nrOfImages: Optional number of images to generate + /// - cfgScale: Optional configuration scale parameter + /// - resolution: Optional image resolution + /// - seed: Optional seed for reproducible generation /// - prompt: The input text prompt describing the desired image /// - negativePrompt: Optional text describing what to avoid in the generated image /// - Throws: BedrockLibraryError if the parameters are invalid or exceed model constraints @@ -103,7 +107,11 @@ extension BedrockService { /// Validates image variation generation parameters /// - Parameters: - /// - modality: The image variation modality of the model + /// - model: The image variation model + /// - nrOfImages: Optional number of images to generate + /// - cfgScale: Optional configuration scale parameter + /// - resolution: Optional image resolution + /// - seed: Optional seed for reproducible generation /// - images: Array of base64 encoded images to use as reference /// - prompt: Text prompt describing desired variations /// - similarity: Optional parameter controlling variation similarity diff --git a/Sources/InvokeModel/BedrockService+InvokeModelEmbeddings.swift b/Sources/BedrockService/InvokeModel/BedrockService+InvokeModelEmbeddings.swift similarity index 98% rename from Sources/InvokeModel/BedrockService+InvokeModelEmbeddings.swift rename to Sources/BedrockService/InvokeModel/BedrockService+InvokeModelEmbeddings.swift index 68c6546e..61e76127 100644 --- a/Sources/InvokeModel/BedrockService+InvokeModelEmbeddings.swift +++ b/Sources/BedrockService/InvokeModel/BedrockService+InvokeModelEmbeddings.swift @@ -29,6 +29,7 @@ extension BedrockService { /// - text: The input text to generate embeddings for /// - model: The Bedrock model to use for embeddings generation /// - vectorSize: The size of the output vector (default: 1024) + /// - normalize: Whether to normalize the output vectors (default: true) /// - Returns: A TextCompletion containing the generated embeddings /// - Throws: BedrockLibraryError if the model doesn't support embeddings or if the request fails public func embed( diff --git a/Sources/InvokeModel/BedrockService+InvokeModelImage.swift b/Sources/BedrockService/InvokeModel/BedrockService+InvokeModelImage.swift similarity index 100% rename from Sources/InvokeModel/BedrockService+InvokeModelImage.swift rename to Sources/BedrockService/InvokeModel/BedrockService+InvokeModelImage.swift diff --git a/Sources/InvokeModel/BedrockService+InvokeModelText.swift b/Sources/BedrockService/InvokeModel/BedrockService+InvokeModelText.swift similarity index 100% rename from Sources/InvokeModel/BedrockService+InvokeModelText.swift rename to Sources/BedrockService/InvokeModel/BedrockService+InvokeModelText.swift diff --git a/Sources/InvokeModel/ContentType.swift b/Sources/BedrockService/InvokeModel/ContentType.swift similarity index 100% rename from Sources/InvokeModel/ContentType.swift rename to Sources/BedrockService/InvokeModel/ContentType.swift diff --git a/Sources/InvokeModel/Embeddings.swift b/Sources/BedrockService/InvokeModel/Embeddings.swift similarity index 100% rename from Sources/InvokeModel/Embeddings.swift rename to Sources/BedrockService/InvokeModel/Embeddings.swift diff --git a/Sources/InvokeModel/ImageGenerationOutput.swift b/Sources/BedrockService/InvokeModel/ImageGenerationOutput.swift similarity index 100% rename from Sources/InvokeModel/ImageGenerationOutput.swift rename to Sources/BedrockService/InvokeModel/ImageGenerationOutput.swift diff --git a/Sources/InvokeModel/ImageResolution.swift b/Sources/BedrockService/InvokeModel/ImageResolution.swift similarity index 100% rename from Sources/InvokeModel/ImageResolution.swift rename to Sources/BedrockService/InvokeModel/ImageResolution.swift diff --git a/Sources/InvokeModel/InvokeModelRequest.swift b/Sources/BedrockService/InvokeModel/InvokeModelRequest.swift similarity index 100% rename from Sources/InvokeModel/InvokeModelRequest.swift rename to Sources/BedrockService/InvokeModel/InvokeModelRequest.swift diff --git a/Sources/InvokeModel/InvokeModelResponse.swift b/Sources/BedrockService/InvokeModel/InvokeModelResponse.swift similarity index 100% rename from Sources/InvokeModel/InvokeModelResponse.swift rename to Sources/BedrockService/InvokeModel/InvokeModelResponse.swift diff --git a/Sources/InvokeModel/Protocols.swift b/Sources/BedrockService/InvokeModel/Protocols.swift similarity index 100% rename from Sources/InvokeModel/Protocols.swift rename to Sources/BedrockService/InvokeModel/Protocols.swift diff --git a/Sources/InvokeModel/TextCompletion.swift b/Sources/BedrockService/InvokeModel/TextCompletion.swift similarity index 100% rename from Sources/InvokeModel/TextCompletion.swift rename to Sources/BedrockService/InvokeModel/TextCompletion.swift diff --git a/Sources/ListModels/ModelSummary.swift b/Sources/BedrockService/ListModels/ModelSummary.swift similarity index 100% rename from Sources/ListModels/ModelSummary.swift rename to Sources/BedrockService/ListModels/ModelSummary.swift diff --git a/Sources/Modalities/ConverseFeature.swift b/Sources/BedrockService/Modalities/ConverseFeature.swift similarity index 100% rename from Sources/Modalities/ConverseFeature.swift rename to Sources/BedrockService/Modalities/ConverseFeature.swift diff --git a/Sources/Modalities/ConverseModality.swift b/Sources/BedrockService/Modalities/ConverseModality.swift similarity index 100% rename from Sources/Modalities/ConverseModality.swift rename to Sources/BedrockService/Modalities/ConverseModality.swift diff --git a/Sources/Modalities/CrossRegionInference.swift b/Sources/BedrockService/Modalities/CrossRegionInference.swift similarity index 100% rename from Sources/Modalities/CrossRegionInference.swift rename to Sources/BedrockService/Modalities/CrossRegionInference.swift diff --git a/Sources/Modalities/EmbeddingsModality.swift b/Sources/BedrockService/Modalities/EmbeddingsModality.swift similarity index 100% rename from Sources/Modalities/EmbeddingsModality.swift rename to Sources/BedrockService/Modalities/EmbeddingsModality.swift diff --git a/Sources/Modalities/ImageModality.swift b/Sources/BedrockService/Modalities/ImageModality.swift similarity index 100% rename from Sources/Modalities/ImageModality.swift rename to Sources/BedrockService/Modalities/ImageModality.swift diff --git a/Sources/Modalities/Modality.swift b/Sources/BedrockService/Modalities/Modality.swift similarity index 100% rename from Sources/Modalities/Modality.swift rename to Sources/BedrockService/Modalities/Modality.swift diff --git a/Sources/Modalities/StandardConverse.swift b/Sources/BedrockService/Modalities/StandardConverse.swift similarity index 100% rename from Sources/Modalities/StandardConverse.swift rename to Sources/BedrockService/Modalities/StandardConverse.swift diff --git a/Sources/Modalities/TextModality.swift b/Sources/BedrockService/Modalities/TextModality.swift similarity index 100% rename from Sources/Modalities/TextModality.swift rename to Sources/BedrockService/Modalities/TextModality.swift diff --git a/Sources/Models/Amazon/AmazonImage.swift b/Sources/BedrockService/Models/Amazon/AmazonImage.swift similarity index 100% rename from Sources/Models/Amazon/AmazonImage.swift rename to Sources/BedrockService/Models/Amazon/AmazonImage.swift diff --git a/Sources/Models/Amazon/AmazonImageRequestBody.swift b/Sources/BedrockService/Models/Amazon/AmazonImageRequestBody.swift similarity index 90% rename from Sources/Models/Amazon/AmazonImageRequestBody.swift rename to Sources/BedrockService/Models/Amazon/AmazonImageRequestBody.swift index 8f436363..ae841ff8 100644 --- a/Sources/Models/Amazon/AmazonImageRequestBody.swift +++ b/Sources/BedrockService/Models/Amazon/AmazonImageRequestBody.swift @@ -31,8 +31,12 @@ public struct AmazonImageRequestBody: BedrockBodyCodable { /// Creates a text-to-image generation request body /// - Parameters: /// - prompt: The text description of the image to generate - /// - nrOfImages: The number of images to generate /// - negativeText: The text description of what to exclude from the generated image + /// - nrOfImages: The number of images to generate + /// - cfgScale: Configuration scale parameter for generation control + /// - seed: Seed for reproducible generation + /// - quality: Quality setting for the generated image + /// - resolution: Resolution of the generated image /// - Returns: A configured AmazonImageRequestBody for text-to-image generation public static func textToImage( prompt: String, @@ -79,8 +83,12 @@ public struct AmazonImageRequestBody: BedrockBodyCodable { /// Creates a text-to-image conditioned generation request body /// - Parameters: /// - prompt: The text description of the image to generate - /// - nrOfImages: The number of images to generate /// - negativeText: The text description of what to exclude from the generated image + /// - nrOfImages: The number of images to generate + /// - cfgScale: Configuration scale parameter for generation control + /// - seed: Seed for reproducible generation + /// - quality: Quality setting for the generated image + /// - resolution: Resolution of the generated image /// - Returns: A configured AmazonImageRequestBody for text-to-image generation public static func conditionedTextToImage( prompt: String, @@ -135,10 +143,15 @@ public struct AmazonImageRequestBody: BedrockBodyCodable { /// Creates an image variation generation request /// - Parameters: + /// - referenceImages: Array of base64-encoded strings of the source images /// - prompt: The text description to guide the variation generation - /// - referenceImage: The base64-encoded string of the source image + /// - negativeText: Optional text describing what to avoid /// - similarity: How similar the variations should be to the source image (0.2-1.0) /// - nrOfImages: The number of variations to generate (default: 1) + /// - seed: Seed for reproducible generation + /// - quality: Quality setting for the generated image + /// - cfgScale: Configuration scale parameter for generation control + /// - resolution: Resolution of the generated image /// - Returns: A configured AmazonImageRequestBody for image variation generation public static func imageVariation( referenceImages: [String], @@ -200,6 +213,10 @@ public struct AmazonImageRequestBody: BedrockBodyCodable { /// - colors: A list of color codes that will be used in the image, expressed as hexadecimal values in the form “#RRGGBB”. /// - negativeText: The text description of what to exclude from the generated image /// - referenceImage: The base64-encoded string of the source image (colors in this image will also be used in the generated image) + /// - cfgScale: Configuration scale parameter for generation control + /// - seed: Seed for reproducible generation + /// - quality: Quality setting for the generated image + /// - resolution: Resolution of the generated image /// - Returns: A configured AmazonImageRequestBody for color guided image generation public static func colorGuidedGeneration( prompt: String, diff --git a/Sources/Models/Amazon/AmazonImageResponseBody.swift b/Sources/BedrockService/Models/Amazon/AmazonImageResponseBody.swift similarity index 100% rename from Sources/Models/Amazon/AmazonImageResponseBody.swift rename to Sources/BedrockService/Models/Amazon/AmazonImageResponseBody.swift diff --git a/Sources/Models/Amazon/Nova/Nova.swift b/Sources/BedrockService/Models/Amazon/Nova/Nova.swift similarity index 100% rename from Sources/Models/Amazon/Nova/Nova.swift rename to Sources/BedrockService/Models/Amazon/Nova/Nova.swift diff --git a/Sources/Models/Amazon/Nova/NovaBedrockModels.swift b/Sources/BedrockService/Models/Amazon/Nova/NovaBedrockModels.swift similarity index 100% rename from Sources/Models/Amazon/Nova/NovaBedrockModels.swift rename to Sources/BedrockService/Models/Amazon/Nova/NovaBedrockModels.swift diff --git a/Sources/Models/Amazon/Nova/NovaImageResolutionValidator.swift b/Sources/BedrockService/Models/Amazon/Nova/NovaImageResolutionValidator.swift similarity index 100% rename from Sources/Models/Amazon/Nova/NovaImageResolutionValidator.swift rename to Sources/BedrockService/Models/Amazon/Nova/NovaImageResolutionValidator.swift diff --git a/Sources/Models/Amazon/Nova/NovaRequestBody.swift b/Sources/BedrockService/Models/Amazon/Nova/NovaRequestBody.swift similarity index 100% rename from Sources/Models/Amazon/Nova/NovaRequestBody.swift rename to Sources/BedrockService/Models/Amazon/Nova/NovaRequestBody.swift diff --git a/Sources/Models/Amazon/Nova/NovaResponseBody.swift b/Sources/BedrockService/Models/Amazon/Nova/NovaResponseBody.swift similarity index 100% rename from Sources/Models/Amazon/Nova/NovaResponseBody.swift rename to Sources/BedrockService/Models/Amazon/Nova/NovaResponseBody.swift diff --git a/Sources/Models/Amazon/TaskType.swift b/Sources/BedrockService/Models/Amazon/TaskType.swift similarity index 100% rename from Sources/Models/Amazon/TaskType.swift rename to Sources/BedrockService/Models/Amazon/TaskType.swift diff --git a/Sources/Models/Amazon/Titan/TitanImageResolutionValidator.swift b/Sources/BedrockService/Models/Amazon/Titan/TitanImageResolutionValidator.swift similarity index 100% rename from Sources/Models/Amazon/Titan/TitanImageResolutionValidator.swift rename to Sources/BedrockService/Models/Amazon/Titan/TitanImageResolutionValidator.swift diff --git a/Sources/Models/Amazon/Titan/TitanRequestBody.swift b/Sources/BedrockService/Models/Amazon/Titan/TitanRequestBody.swift similarity index 100% rename from Sources/Models/Amazon/Titan/TitanRequestBody.swift rename to Sources/BedrockService/Models/Amazon/Titan/TitanRequestBody.swift diff --git a/Sources/Models/Amazon/Titan/TitanResponseBody.swift b/Sources/BedrockService/Models/Amazon/Titan/TitanResponseBody.swift similarity index 100% rename from Sources/Models/Amazon/Titan/TitanResponseBody.swift rename to Sources/BedrockService/Models/Amazon/Titan/TitanResponseBody.swift diff --git a/Sources/Models/Amazon/Titan/TitanText.swift b/Sources/BedrockService/Models/Amazon/Titan/TitanText.swift similarity index 100% rename from Sources/Models/Amazon/Titan/TitanText.swift rename to Sources/BedrockService/Models/Amazon/Titan/TitanText.swift diff --git a/Sources/Models/Amazon/Titan/TitanTextBedrockModels.swift b/Sources/BedrockService/Models/Amazon/Titan/TitanTextBedrockModels.swift similarity index 100% rename from Sources/Models/Amazon/Titan/TitanTextBedrockModels.swift rename to Sources/BedrockService/Models/Amazon/Titan/TitanTextBedrockModels.swift diff --git a/Sources/Models/Amazon/TitanEmbeddings/TitanEmbeddings.swift b/Sources/BedrockService/Models/Amazon/TitanEmbeddings/TitanEmbeddings.swift similarity index 100% rename from Sources/Models/Amazon/TitanEmbeddings/TitanEmbeddings.swift rename to Sources/BedrockService/Models/Amazon/TitanEmbeddings/TitanEmbeddings.swift diff --git a/Sources/Models/Amazon/TitanEmbeddings/TitanEmbeddingsBedrockModels.swift b/Sources/BedrockService/Models/Amazon/TitanEmbeddings/TitanEmbeddingsBedrockModels.swift similarity index 100% rename from Sources/Models/Amazon/TitanEmbeddings/TitanEmbeddingsBedrockModels.swift rename to Sources/BedrockService/Models/Amazon/TitanEmbeddings/TitanEmbeddingsBedrockModels.swift diff --git a/Sources/Models/Amazon/TitanEmbeddings/TitanEmbeddingsRequestBody.swift b/Sources/BedrockService/Models/Amazon/TitanEmbeddings/TitanEmbeddingsRequestBody.swift similarity index 100% rename from Sources/Models/Amazon/TitanEmbeddings/TitanEmbeddingsRequestBody.swift rename to Sources/BedrockService/Models/Amazon/TitanEmbeddings/TitanEmbeddingsRequestBody.swift diff --git a/Sources/Models/Amazon/TitanEmbeddings/TitanEmbeddingsResponseBody.swift b/Sources/BedrockService/Models/Amazon/TitanEmbeddings/TitanEmbeddingsResponseBody.swift similarity index 100% rename from Sources/Models/Amazon/TitanEmbeddings/TitanEmbeddingsResponseBody.swift rename to Sources/BedrockService/Models/Amazon/TitanEmbeddings/TitanEmbeddingsResponseBody.swift diff --git a/Sources/Models/Anthropic/Anthropic.swift b/Sources/BedrockService/Models/Anthropic/Anthropic.swift similarity index 100% rename from Sources/Models/Anthropic/Anthropic.swift rename to Sources/BedrockService/Models/Anthropic/Anthropic.swift diff --git a/Sources/Models/Anthropic/AnthropicBedrockModels.swift b/Sources/BedrockService/Models/Anthropic/AnthropicBedrockModels.swift similarity index 100% rename from Sources/Models/Anthropic/AnthropicBedrockModels.swift rename to Sources/BedrockService/Models/Anthropic/AnthropicBedrockModels.swift diff --git a/Sources/Models/Anthropic/AnthropicRequestBody.swift b/Sources/BedrockService/Models/Anthropic/AnthropicRequestBody.swift similarity index 100% rename from Sources/Models/Anthropic/AnthropicRequestBody.swift rename to Sources/BedrockService/Models/Anthropic/AnthropicRequestBody.swift diff --git a/Sources/Models/Anthropic/AnthropicResponseBody.swift b/Sources/BedrockService/Models/Anthropic/AnthropicResponseBody.swift similarity index 100% rename from Sources/Models/Anthropic/AnthropicResponseBody.swift rename to Sources/BedrockService/Models/Anthropic/AnthropicResponseBody.swift diff --git a/Sources/Models/Cohere/CohereBedrockModels.swift b/Sources/BedrockService/Models/Cohere/CohereBedrockModels.swift similarity index 100% rename from Sources/Models/Cohere/CohereBedrockModels.swift rename to Sources/BedrockService/Models/Cohere/CohereBedrockModels.swift diff --git a/Sources/Models/DeepSeek/DeepSeek.swift b/Sources/BedrockService/Models/DeepSeek/DeepSeek.swift similarity index 100% rename from Sources/Models/DeepSeek/DeepSeek.swift rename to Sources/BedrockService/Models/DeepSeek/DeepSeek.swift diff --git a/Sources/Models/DeepSeek/DeepSeekBedrockModels.swift b/Sources/BedrockService/Models/DeepSeek/DeepSeekBedrockModels.swift similarity index 100% rename from Sources/Models/DeepSeek/DeepSeekBedrockModels.swift rename to Sources/BedrockService/Models/DeepSeek/DeepSeekBedrockModels.swift diff --git a/Sources/Models/DeepSeek/DeepSeekRequestBody.swift b/Sources/BedrockService/Models/DeepSeek/DeepSeekRequestBody.swift similarity index 100% rename from Sources/Models/DeepSeek/DeepSeekRequestBody.swift rename to Sources/BedrockService/Models/DeepSeek/DeepSeekRequestBody.swift diff --git a/Sources/Models/DeepSeek/DeepSeekResponseBody.swift b/Sources/BedrockService/Models/DeepSeek/DeepSeekResponseBody.swift similarity index 100% rename from Sources/Models/DeepSeek/DeepSeekResponseBody.swift rename to Sources/BedrockService/Models/DeepSeek/DeepSeekResponseBody.swift diff --git a/Sources/Models/Jamba/JambaBedrockModels.swift b/Sources/BedrockService/Models/Jamba/JambaBedrockModels.swift similarity index 100% rename from Sources/Models/Jamba/JambaBedrockModels.swift rename to Sources/BedrockService/Models/Jamba/JambaBedrockModels.swift diff --git a/Sources/Models/Llama/Llama.swift b/Sources/BedrockService/Models/Llama/Llama.swift similarity index 100% rename from Sources/Models/Llama/Llama.swift rename to Sources/BedrockService/Models/Llama/Llama.swift diff --git a/Sources/Models/Llama/LlamaBedrockModels.swift b/Sources/BedrockService/Models/Llama/LlamaBedrockModels.swift similarity index 100% rename from Sources/Models/Llama/LlamaBedrockModels.swift rename to Sources/BedrockService/Models/Llama/LlamaBedrockModels.swift diff --git a/Sources/Models/Llama/LlamaRequestBody.swift b/Sources/BedrockService/Models/Llama/LlamaRequestBody.swift similarity index 100% rename from Sources/Models/Llama/LlamaRequestBody.swift rename to Sources/BedrockService/Models/Llama/LlamaRequestBody.swift diff --git a/Sources/Models/Llama/LlamaResponseBody.swift b/Sources/BedrockService/Models/Llama/LlamaResponseBody.swift similarity index 100% rename from Sources/Models/Llama/LlamaResponseBody.swift rename to Sources/BedrockService/Models/Llama/LlamaResponseBody.swift diff --git a/Sources/Models/Mistral/MistralBedrockModels.swift b/Sources/BedrockService/Models/Mistral/MistralBedrockModels.swift similarity index 100% rename from Sources/Models/Mistral/MistralBedrockModels.swift rename to Sources/BedrockService/Models/Mistral/MistralBedrockModels.swift diff --git a/Sources/Models/OpenAI/OpenAI.swift b/Sources/BedrockService/Models/OpenAI/OpenAI.swift similarity index 100% rename from Sources/Models/OpenAI/OpenAI.swift rename to Sources/BedrockService/Models/OpenAI/OpenAI.swift diff --git a/Sources/Models/OpenAI/OpenAIBedrockModels.swift b/Sources/BedrockService/Models/OpenAI/OpenAIBedrockModels.swift similarity index 100% rename from Sources/Models/OpenAI/OpenAIBedrockModels.swift rename to Sources/BedrockService/Models/OpenAI/OpenAIBedrockModels.swift diff --git a/Sources/Models/OpenAI/OpenAIRequestBody.swift b/Sources/BedrockService/Models/OpenAI/OpenAIRequestBody.swift similarity index 100% rename from Sources/Models/OpenAI/OpenAIRequestBody.swift rename to Sources/BedrockService/Models/OpenAI/OpenAIRequestBody.swift diff --git a/Sources/Models/OpenAI/OpenAIResponseBody.swift b/Sources/BedrockService/Models/OpenAI/OpenAIResponseBody.swift similarity index 100% rename from Sources/Models/OpenAI/OpenAIResponseBody.swift rename to Sources/BedrockService/Models/OpenAI/OpenAIResponseBody.swift diff --git a/Sources/Parameters/ConverseParameters.swift b/Sources/BedrockService/Parameters/ConverseParameters.swift similarity index 100% rename from Sources/Parameters/ConverseParameters.swift rename to Sources/BedrockService/Parameters/ConverseParameters.swift diff --git a/Sources/Parameters/EmbeddingsParameters.swift b/Sources/BedrockService/Parameters/EmbeddingsParameters.swift similarity index 100% rename from Sources/Parameters/EmbeddingsParameters.swift rename to Sources/BedrockService/Parameters/EmbeddingsParameters.swift diff --git a/Sources/Parameters/ImageGenerationParameters.swift b/Sources/BedrockService/Parameters/ImageGenerationParameters.swift similarity index 100% rename from Sources/Parameters/ImageGenerationParameters.swift rename to Sources/BedrockService/Parameters/ImageGenerationParameters.swift diff --git a/Sources/Parameters/ParameterName.swift b/Sources/BedrockService/Parameters/ParameterName.swift similarity index 100% rename from Sources/Parameters/ParameterName.swift rename to Sources/BedrockService/Parameters/ParameterName.swift diff --git a/Sources/Parameters/Parameters.swift b/Sources/BedrockService/Parameters/Parameters.swift similarity index 100% rename from Sources/Parameters/Parameters.swift rename to Sources/BedrockService/Parameters/Parameters.swift diff --git a/Sources/Parameters/TextGenerationParameters.swift b/Sources/BedrockService/Parameters/TextGenerationParameters.swift similarity index 100% rename from Sources/Parameters/TextGenerationParameters.swift rename to Sources/BedrockService/Parameters/TextGenerationParameters.swift diff --git a/Sources/Protocols/BedrockClientProtocol.swift b/Sources/BedrockService/Protocols/BedrockClientProtocol.swift similarity index 100% rename from Sources/Protocols/BedrockClientProtocol.swift rename to Sources/BedrockService/Protocols/BedrockClientProtocol.swift diff --git a/Sources/Protocols/BedrockConfigProtocol.swift b/Sources/BedrockService/Protocols/BedrockConfigProtocol.swift similarity index 100% rename from Sources/Protocols/BedrockConfigProtocol.swift rename to Sources/BedrockService/Protocols/BedrockConfigProtocol.swift diff --git a/Sources/Protocols/BedrockRuntimeClientProtocol.swift b/Sources/BedrockService/Protocols/BedrockRuntimeClientProtocol.swift similarity index 100% rename from Sources/Protocols/BedrockRuntimeClientProtocol.swift rename to Sources/BedrockService/Protocols/BedrockRuntimeClientProtocol.swift diff --git a/Sources/Region.swift b/Sources/BedrockService/Region.swift similarity index 100% rename from Sources/Region.swift rename to Sources/BedrockService/Region.swift