Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,43 @@ jobs:
license_header_check_project_name: "Swift Bedrock Library"
shell_check_enabled: false
python_lint_check_enabled: false
# do not use this sub-action because the build requires
# 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)
api_breakage_check_enabled: false
# api_breakage_check_container_image: "swift:6.1-noble"
docs_check_container_image: "swift:6.1-noble"
format_check_container_image: "swift:6.1-noble"
api_breakage_check_container_image: "swift:6.2-noble"
docs_check_container_image: "swift:6.2-noble"
format_check_container_image: "swift:6.2-noble"
yamllint_check_enabled: true

# api-breakage-check:
# name: API breakage check
# runs-on: ubuntu-latest
# timeout-minutes: 20
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# with:
# # This is set to true since swift package diagnose-api-breaking-changes is
# # cloning the repo again and without it being set to true this job won't work for
# # private repos.
# persist-credentials: true
# submodules: true
# fetch-tags: true
# fetch-depth: 0 # Fetching tags requires fetch-depth: 0 (https://github.com/actions/checkout/issues/1471)
# - name: Mark the workspace as safe
# # https://github.com/actions/checkout/issues/766
# run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
# - name: Run API breakage check
# shell: bash
# run: |
# git fetch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} ${GITHUB_BASE_REF}:pull-base-ref
# BASELINE_REF='pull-base-ref'
# echo "Using baseline: $BASELINE_REF"
# swift package diagnose-api-breaking-changes "$BASELINE_REF"


integration-tests:
name: Integration Tests
uses: ./.github/workflows/integration_tests.yml
Expand Down
2 changes: 1 addition & 1 deletion Examples/ios-math-solver/Sources/MathSolverViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ final class MathSolverViewModel: ObservableObject, @unchecked Sendable {

// Make the streaming request
Task {
var messages: [Message] = []
var messages: History = []
do {
// Process the stream
let response = try await bedrockService.converseStream(with: promptBuilder)
Expand Down
2 changes: 1 addition & 1 deletion Examples/text_chat/Sources/TextChat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ struct TextChat {
var request: ConverseRequestBuilder? = nil

// we keep track of the history of the conversation
var history: [Message] = []
var history: History = []

// while the user doesn't type "exit" or "quit"
while true {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ extension Message: @retroactive ResponseCodable {}

struct ChatInput: Codable {
let prompt: String?
let history: [Message]?
let history: History?
let imageFormat: ImageBlock.Format?
let imageBytes: String?
let documentName: String?
Expand Down
49 changes: 49 additions & 0 deletions Sources/Converse/BedrockService+Converse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ extension BedrockService {
/// BedrockLibraryError.invalidModality for invalid modality from the selected model
/// BedrockLibraryError.invalidSDKResponse if the response body is missing
/// - Returns: A Message containing the model's response
@available(
*,
deprecated,
message:
"Use converse(with:conversation:...) that takes History instead of [Message]. This func will be removed in the next major version."
)
public func converse(
with model: BedrockModel,
conversation: [Message],
Expand All @@ -51,6 +57,49 @@ extension BedrockService {
tools: [Tool]? = nil,
enableReasoning: Bool? = false,
maxReasoningTokens: Int? = nil
) async throws -> Message {
let history = History(conversation)
return try await converse(
with: model,
conversation: history,
maxTokens: maxTokens,
temperature: temperature,
topP: topP,
stopSequences: stopSequences,
systemPrompts: systemPrompts,
tools: tools,
enableReasoning: enableReasoning,
maxReasoningTokens: maxReasoningTokens
)
}

/// Converse with a model using the Bedrock Converse API
/// - Parameters:
/// - model: The BedrockModel to converse with
/// - conversation: Array of previous messages in the conversation
/// - maxTokens: Optional maximum number of tokens to generate
/// - temperature: Optional temperature parameter for controlling randomness
/// - topP: Optional top-p parameter for nucleus sampling
/// - 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
/// - 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
/// BedrockLibraryError.invalidModality for invalid modality from the selected model
/// BedrockLibraryError.invalidSDKResponse if the response body is missing
/// - Returns: A Message containing the model's response
public func converse(
with model: BedrockModel,
conversation: History,
maxTokens: Int? = nil,
temperature: Double? = nil,
topP: Double? = nil,
stopSequences: [String]? = nil,
systemPrompts: [String]? = nil,
tools: [Tool]? = nil,
enableReasoning: Bool? = false,
maxReasoningTokens: Int? = nil
) async throws -> Message {
do {
let modality = try model.getConverseModality()
Expand Down
52 changes: 52 additions & 0 deletions Sources/Converse/BedrockService+ConverseStreaming.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ extension BedrockService {
/// BedrockLibraryError.invalidSDKResponse if the response body is missing
/// - Returns: A ConverseReplyStream object that gives access to the high-level stream of ConverseStreamElements objects
/// or the low-level stream provided by the AWS SDK.
@available(
*,
deprecated,
message:
"Use converseStream(with:conversation:...) that takes History instead of [Message]. This func will be removed in the next major version."
)
public func converseStream(
with model: BedrockModel,
conversation: [Message],
Expand All @@ -52,6 +58,52 @@ extension BedrockService {
tools: [Tool]? = nil,
enableReasoning: Bool? = false,
maxReasoningTokens: Int? = nil
) async throws -> ConverseReplyStream {
// Convert [Message] array to History
let history = History(conversation)

return try await converseStream(
with: model,
conversation: history,
maxTokens: maxTokens,
temperature: temperature,
topP: topP,
stopSequences: stopSequences,
systemPrompts: systemPrompts,
tools: tools,
enableReasoning: enableReasoning,
maxReasoningTokens: maxReasoningTokens
)
}

/// Converse with a model using the Bedrock Converse Streaming API
/// - Parameters:
/// - model: The BedrockModel to converse with
/// - conversation: Array of previous messages in the conversation
/// - maxTokens: Optional maximum number of tokens to generate
/// - temperature: Optional temperature parameter for controlling randomness
/// - topP: Optional top-p parameter for nucleus sampling
/// - 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
/// - 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
/// BedrockLibraryError.invalidModality for invalid modality from the selected model
/// BedrockLibraryError.invalidSDKResponse if the response body is missing
/// - Returns: A ConverseReplyStream object that gives access to the high-level stream of ConverseStreamElements objects
/// or the low-level stream provided by the AWS SDK.
public func converseStream(
with model: BedrockModel,
conversation: History,
maxTokens: Int? = nil,
temperature: Double? = nil,
topP: Double? = nil,
stopSequences: [String]? = nil,
systemPrompts: [String]? = nil,
tools: [Tool]? = nil,
enableReasoning: Bool? = false,
maxReasoningTokens: Int? = nil
) async throws -> ConverseReplyStream {
do {
guard model.hasConverseStreamingModality() else {
Expand Down
26 changes: 0 additions & 26 deletions Sources/Converse/ContentBlocks/History.swift

This file was deleted.

34 changes: 32 additions & 2 deletions Sources/Converse/ConverseRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,45 @@ import Foundation

public struct ConverseRequest {
let model: BedrockModel
let messages: [Message]
let messages: History
let inferenceConfig: InferenceConfig?
let toolConfig: ToolConfig?
let systemPrompts: [String]?
let maxReasoningTokens: Int?

@available(
*,
deprecated,
message:
"Use the initializer that accepts a History instead of [Message]. This func will be removed in the next major version."
)
init(
model: BedrockModel,
messages: [Message] = [],
messages: [Message],
maxTokens: Int?,
temperature: Double?,
topP: Double?,
stopSequences: [String]?,
systemPrompts: [String]?,
tools: [Tool]?,
maxReasoningTokens: Int?
) {
self.init(
model: model,
messages: History(messages),
maxTokens: maxTokens,
temperature: temperature,
topP: topP,
stopSequences: stopSequences,
systemPrompts: systemPrompts,
tools: tools,
maxReasoningTokens: maxReasoningTokens
)
}

init(
model: BedrockModel,
messages: History,
maxTokens: Int?,
temperature: Double?,
topP: Double?,
Expand Down
13 changes: 10 additions & 3 deletions Sources/Converse/ConverseRequestBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import FoundationEssentials
import Foundation
#endif

public struct ConverseRequestBuilder {
public struct ConverseRequestBuilder: Sendable {

public private(set) var model: BedrockModel
private var parameters: ConverseParameters

public private(set) var history: [Message]
public private(set) var history: History
public private(set) var tools: [Tool]?

public private(set) var prompt: String?
Expand Down Expand Up @@ -88,8 +88,15 @@ public struct ConverseRequestBuilder {
// MARK - builder methods

// MARK - builder methods - history

@available(
*,
deprecated,
message: "Use withHistory(_: [History])instead. This func will be removed in the next major version."
)
public func withHistory(_ history: [Message]) throws -> ConverseRequestBuilder {
try withHistory(History(history))
}
public func withHistory(_ history: History) throws -> ConverseRequestBuilder {
if let lastMessage = history.last {
guard lastMessage.role == .assistant else {
throw BedrockLibraryError.ConverseRequestBuilder("Last message in history must be from assistant.")
Expand Down
78 changes: 78 additions & 0 deletions Sources/Converse/History.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Bedrock Library open source project
//
// Copyright (c) 2025 Amazon.com, Inc. or its affiliates
// and the Swift Bedrock Library project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Bedrock Library project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

public struct History: Codable, Sendable {
private var messages: [Message] = []

public init() {}

public init(_ message: Message) {
messages = [message]
}

public init(_ messages: [Message]) {
self.messages = messages
}

public init(_ messages: Message...) {
self.messages = messages
}

public mutating func append(_ message: Message) {
messages.append(message)
}

/// Essentials functions from Array that History needs
public var count: Int { messages.count }

public subscript(index: Int) -> Message {
messages[index]
}

public var last: Message? {
messages.last
}

public static func + (lhs: History, rhs: [Message]) -> History {
var result = lhs
result.messages.append(contentsOf: rhs)
return result
}
}

/// Collection
extension History: Collection {
public var startIndex: Int { messages.startIndex }
public var endIndex: Int { messages.endIndex }
public func index(after i: Int) -> Int { messages.index(after: i) }
}

/// CustomString Convertible
extension History: CustomStringConvertible {
public var description: String {
var result = "\(self.count) turns:\n"
for message in self {
result += "\(message)\n"
}
return result
}
}

/// ExpressibleByArrayLiteral
extension History: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: Message...) {
self.messages = elements
}
}
File renamed without changes.
File renamed without changes.