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
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,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: "[ 'converse', 'converse-stream' ]"
examples: "[ 'converse', 'converse-stream', 'text_chat' ]"

swift-6-language-mode:
name: Swift 6 Language Mode
Expand Down
8 changes: 8 additions & 0 deletions Examples/text_chat/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
30 changes: 30 additions & 0 deletions Examples/text_chat/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "TextChat",
platforms: [.macOS(.v15), .iOS(.v18), .tvOS(.v18)],
products: [
.executable(name: "TextChat", targets: ["TextChat"])
],
dependencies: [
// for production use, uncomment the following line
// .package(url: "https://github.com/build-on-aws/swift-bedrock-library.git", branch: "main"),

// for local development, use the following line
.package(name: "swift-bedrock-library", path: "../.."),

.package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"),
],
targets: [
.executableTarget(
name: "TextChat",
dependencies: [
.product(name: "BedrockService", package: "swift-bedrock-library"),
.product(name: "Logging", package: "swift-log"),
]
)
]
)
102 changes: 102 additions & 0 deletions Examples/text_chat/Sources/TextChat.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Bedrock Library open source project
//
// Copyright (c) 2025 Amazon.com, Inc. or its affiliates
// and the Swift Bedrock Library project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Bedrock Library project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import BedrockService
import Logging

@main
struct TextChat {
static func main() async throws {
do {
try await TextChat.run()
} catch {
print("Error:\n\(error)")
Comment on lines +22 to +25
Copy link

Copilot AI Jun 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider using 'logger.error' instead of 'print' for error messages to maintain consistent logging and support structured logs.

Suggested change
do {
try await TextChat.run()
} catch {
print("Error:\n\(error)")
var logger = Logger(label: "TextChat.Main")
do {
try await TextChat.run()
} catch {
logger.error("Error:\n\(error)")

Copilot uses AI. Check for mistakes.
}
}
static func run() async throws {
var logger = Logger(label: "TextChat")
logger.logLevel = .debug

let bedrock = try await BedrockService(
region: .useast1,
logger: logger
// uncomment if you use SSO with AWS Identity Center
// authentication: .sso
)

// select a model that supports the converse modality
// models must be enabled in your AWS account
let model: BedrockModel = .claudev3_7_sonnet

guard model.hasConverseModality() else {
throw MyError.incorrectModality("\(model.name) does not support converse")
}

// a reusable var to build the requests
var request: ConverseRequestBuilder? = nil

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

// while the user doesn't type "exit" or "quit"
while true {

print("\nYou: ", terminator: "")
let prompt: String = readLine() ?? ""
guard prompt.isEmpty == false else { continue }
if ["exit", "quit"].contains(prompt.lowercased()) {
break
}

print("\nAssistant: ", terminator: "")

if request == nil {
// create a new request
request = try ConverseRequestBuilder(with: model)
.withPrompt(prompt)
} else {
// append the new prompt to the existing request
// ConverseRequestBuilder is stateless, it doesn't keep track of the history
// thanks to the `if` above, we're sure `request` is not nil
request = try ConverseRequestBuilder(from: request!)
Copy link

Copilot AI Jun 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force-unwrapping 'request' may cause a runtime crash if it's nil; consider using a guard let to safely unwrap or refactor the logic to avoid optionals here.

Suggested change
request = try ConverseRequestBuilder(from: request!)
guard let existingRequest = request else {
throw MyError.incorrectModality("Request is unexpectedly nil")
}
request = try ConverseRequestBuilder(from: existingRequest)

Copilot uses AI. Check for mistakes.
.withHistory(history)
.withPrompt(prompt)
}

// keep track of the history of the conversation
history.append(Message(prompt))

// send the request. We are sure `request` is not nil
let reply = try await bedrock.converseStream(with: request!)
Copy link

Copilot AI Jun 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force-unwrapping 'request' here may crash if nil; ensure it's non-nil with a guard or unwrap earlier.

Suggested change
let reply = try await bedrock.converseStream(with: request!)
guard let unwrappedRequest = request else {
throw MyError.incorrectModality("Request is unexpectedly nil")
}
let reply = try await bedrock.converseStream(with: unwrappedRequest)

Copilot uses AI. Check for mistakes.

for try await element in reply.stream {
// process the stream elements
switch element {
case .text(_, let text):
print(text, terminator: "")
case .messageComplete(let message):
print("\n")
history.append(message)
default:
break
}
}
}
}

enum MyError: Error {
case incorrectModality(String)
}
}