From b0869f192d8787625c09ccdfbab9dfb0746840b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Mon, 2 Jun 2025 23:20:54 +0200 Subject: [PATCH 1/3] Transform Role to a struct --- Sources/BedrockServiceError.swift | 3 +- Sources/Converse/Role.swift | 54 ++++++++++++++++++--- Tests/InvokeModel/TextGenerationTests.swift | 12 +++-- 3 files changed, 57 insertions(+), 12 deletions(-) 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..ed1ce6fd 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( From bb2ab3e44b63d36755b4c97678a470d7734abb69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Tue, 3 Jun 2025 14:52:50 +0200 Subject: [PATCH 2/3] fix swift format --- Sources/Converse/Role.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Converse/Role.swift b/Sources/Converse/Role.swift index ed1ce6fd..c966815e 100644 --- a/Sources/Converse/Role.swift +++ b/Sources/Converse/Role.swift @@ -43,14 +43,14 @@ public struct Role: Codable, Sendable, Equatable { } // 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" - }}, - */ + // + // "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) From 9e564195a4506ef69a97bf3fff46c137fb72550a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Tue, 3 Jun 2025 15:09:06 +0200 Subject: [PATCH 3/3] fix swift-format --- Sources/Converse/Role.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Converse/Role.swift b/Sources/Converse/Role.swift index c966815e..d1601607 100644 --- a/Sources/Converse/Role.swift +++ b/Sources/Converse/Role.swift @@ -43,14 +43,14 @@ public struct Role: Codable, Sendable, Equatable { } // 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)