diff --git a/Sources/BedrockServiceError.swift b/Sources/BedrockServiceError.swift index 6ce4e9a6..aa5271ab 100644 --- a/Sources/BedrockServiceError.swift +++ b/Sources/BedrockServiceError.swift @@ -66,7 +66,8 @@ public enum BedrockLibraryError: Error { case .invalidSDKResponse(let message): return "Invalid SDK response: \(message)" case .invalidSDKResponseBody(let value): - return "Invalid SDK response body: \(String(describing: value))" + let valueAsString = value != nil ? String(data: value!, encoding: .utf8) ?? "" : "nil" + return "Invalid SDK response body: \(valueAsString)" case .completionNotFound(let message): return "Completion not found: \(message)" case .encodingError(let message): diff --git a/Sources/Converse/Role.swift b/Sources/Converse/Role.swift index d4c13d6a..d1601607 100644 --- a/Sources/Converse/Role.swift +++ b/Sources/Converse/Role.swift @@ -16,14 +16,18 @@ @preconcurrency import AWSBedrockRuntime import Foundation -public enum Role: String, Codable, Sendable { - case user - case assistant +public struct Role: Codable, Sendable, Equatable { + private enum RoleType: Codable, Sendable, Equatable { + case user + case assistant + } + + private let type: RoleType public init(from sdkConversationRole: BedrockRuntimeClientTypes.ConversationRole) throws { switch sdkConversationRole { - case .user: self = .user - case .assistant: self = .assistant + case .user: self.type = .user + case .assistant: self.type = .assistant case .sdkUnknown(let unknownRole): throw BedrockLibraryError.notImplemented( "Role \(unknownRole) is not implemented by BedrockRuntimeClientTypes" @@ -32,9 +36,47 @@ public enum Role: String, Codable, Sendable { } public func getSDKConversationRole() -> BedrockRuntimeClientTypes.ConversationRole { - switch self { + switch self.type { case .user: return .user case .assistant: return .assistant } } + + // custom encoding and decoding to handle string value with a "type" field + // + // "message":{ + // "content":[ + // {"text":"This is the textcompletion for: This is a test"} + // ], + // "role":"assistant" + // }}, + // + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + let role = try container.decode(String.self) + switch role { + case "user": self.type = .user + case "assistant": self.type = .assistant + default: + throw BedrockLibraryError.decodingError( + "Role \(role) is not a valid role" + ) + } + } + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + switch self.type { + case .user: try container.encode("user") + case .assistant: try container.encode("assistant") + } + } + /// Returns the type of the role as a string. + public static func == (lhs: Role, rhs: Role) -> Bool { + lhs.type == rhs.type + } + private init(_ type: RoleType) { + self.type = type + } + public static let user = Role(.user) + public static let assistant = Role(.assistant) } diff --git a/Tests/InvokeModel/TextGenerationTests.swift b/Tests/InvokeModel/TextGenerationTests.swift index 83003485..9c73e349 100644 --- a/Tests/InvokeModel/TextGenerationTests.swift +++ b/Tests/InvokeModel/TextGenerationTests.swift @@ -27,11 +27,13 @@ extension BedrockServiceTests { arguments: NovaTestConstants.textCompletionModels ) func completeTextWithValidModel(model: BedrockModel) async throws { - let completion: TextCompletion = try await bedrock.completeText( - "This is a test", - with: model - ) - #expect(completion.completion == "This is the textcompletion for: This is a test") + await #expect(throws: Never.self) { + let completion: TextCompletion = try await bedrock.completeText( + "This is a test", + with: model + ) + #expect(completion.completion == "This is the textcompletion for: This is a test") + } } @Test(