diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d2df255..e178d7e 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -3,13 +3,19 @@ name: Build, tests & soundness checks on: [pull_request, workflow_dispatch] jobs: - swift-bedrock-library: + swift-bedrock-library-build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Build run: swift build --configuration release + + swift-bedrock-library-test: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 - name: Tests run: swift test diff --git a/Sources/Converse/ContentBlocks/DocumentToJSON.swift b/Sources/Converse/ContentBlocks/DocumentToJSON.swift index a70ed84..d91ed5f 100644 --- a/Sources/Converse/ContentBlocks/DocumentToJSON.swift +++ b/Sources/Converse/ContentBlocks/DocumentToJSON.swift @@ -23,31 +23,34 @@ import Foundation // FIXME: avoid extensions on structs you do not control extension SmithyDocument { - public func toJSON() throws -> JSON { + private func toJSONValue() throws -> JSONValue { switch self.type { case .string: - return JSON(with: try self.asString()) + return try JSONValue(self.asString()) case .boolean: - return JSON(with: try self.asBoolean()) + return try JSONValue(self.asBoolean()) case .integer: - return JSON(with: try self.asInteger()) + return try JSONValue(self.asInteger()) case .double, .float: - return JSON(with: try self.asDouble()) + return try JSONValue(self.asDouble()) case .list: - let array = try self.asList().map { try $0.toJSON() } - return JSON(with: array) + let array = try self.asList().map { try $0.toJSONValue() } + return JSONValue(array) case .map: let map = try self.asStringMap() - var result: [String: JSON] = [:] - for (key, value) in map { - result[key] = try value.toJSON() - } - return JSON(with: result) + let newMap = try map.mapValues({ try $0.toJSONValue() }) + return JSONValue(newMap) case .blob: let data = try self.asBlob() - return JSON(with: data) + let json = try JSON(from: data) + return json.value default: throw DocumentError.typeMismatch("Unsupported type for JSON conversion: \(self.type)") } } + + public func toJSON() throws -> JSON { + let value = try self.toJSONValue() + return JSON(with: value) + } } diff --git a/Sources/Converse/ContentBlocks/JSON.swift b/Sources/Converse/ContentBlocks/JSON.swift index 5d17cc8..a6e76a6 100644 --- a/Sources/Converse/ContentBlocks/JSON.swift +++ b/Sources/Converse/ContentBlocks/JSON.swift @@ -19,57 +19,156 @@ import FoundationEssentials import Foundation #endif -public struct JSON: Codable, @unchecked Sendable { // FIXME: make Sendable - public var value: Any? +public enum JSONValue: Codable, Sendable { + case null + case int(Int) + case double(Double) + case string(String) + case bool(Bool) + case array([JSONValue]) + case object([String: JSONValue]) + + public init(_ value: Any?) { + + guard let value else { + self = .null + return + } + switch value { + case let v as Int: + self = .int(v) + break + case let v as Double: + self = .double(v) + break + case let v as String: + self = .string(v) + break + case let v as Bool: + self = .bool(v) + break + case let v as [Any]: + self = .array(v.map { JSONValue($0) }) + break + case let v as [String: JSONValue]: + self = .object(v) + break + case let v as [JSONValue]: + self = .array(v) + break + case let v as JSONValue: + self = v + break + default: + fatalError("JSONValue: Unsupported type: \(type(of: value))") + } + } - /// Returns the value inside the JSON object defined by the given key. public subscript(key: String) -> T? { get { - if let dictionary = value as? [String: JSON] { - let json: JSON? = dictionary[key] - let nestedValue: Any? = json?.getValue() - return nestedValue as? T + if case let .object(dictionary) = self { + guard let jsonValue = dictionary[key] else { + return nil + } + switch jsonValue { + case .int(let v): return v as? T + case .double(let v): return v as? T + case .string(let v): return v as? T + case .bool(let v): return v as? T + case .array(let v): return v as? T + case .object(let v): return v as? T + case .null: return nil + } } return nil } } - /// Returns the JSON object defined by the given key. - public subscript(key: String) -> JSON? { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if container.decodeNil() { + self = .null + } else if let intValue = try? container.decode(Int.self) { + self = .int(intValue) + } else if let doubleValue = try? container.decode(Double.self) { + self = .double(doubleValue) + } else if let stringValue = try? container.decode(String.self) { + self = .string(stringValue) + } else if let boolValue = try? container.decode(Bool.self) { + self = .bool(boolValue) + } else if let arrayValue = try? container.decode([JSONValue].self) { + self = .array(arrayValue) + } else if let dictionaryValue = try? container.decode([String: JSONValue].self) { + self = .object(dictionaryValue) + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported type") + } + } + + // MARK: Public Methods + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .null: + try container.encodeNil() + case .int(let v): + try container.encode(v) + case .double(let v): + try container.encode(v) + case .string(let v): + try container.encode(v) + case .bool(let v): + try container.encode(v) + case .array(let v): + try container.encode(v) + case .object(let v): + try container.encode(v) + } + } + +} + +public struct JSON: Codable, Sendable { + public let value: JSONValue + + public subscript(key: String) -> T? { get { - if let dictionary = value as? [String: JSON] { - return dictionary[key] - } - return nil + value[key] } } - /// Returns the value inside the JSON object defined by the given key. - public func getValue(_ key: String) -> T? { - if let dictionary = value as? [String: JSON] { - return dictionary[key]?.value as? T + public subscript(key: String) -> JSONValue? { + get { + if case let .object(dictionary) = value { + if let v = dictionary[key] { + return v + } + } + return nil } - return nil } - /// Returns the value inside the JSON object. public func getValue() -> T? { - value as? T + switch value { + case .int(let v): return v as? T + case .double(let v): return v as? T + case .string(let v): return v as? T + case .bool(let v): return v as? T + case .array(let v): return v as? T + case .object(let v): return v as? T + case .null: return nil + } } // MARK: Initializers - public init(with value: Any?) { + public init(with value: JSONValue) { self.value = value } public init(from string: String) throws { - var s: String! - if string.isEmpty { - s = "{}" - } else { - s = string - } + let s = string.isEmpty ? "{}" : string guard let data = s.data(using: .utf8) else { throw BedrockLibraryError.encodingError("Could not encode String to Data") } @@ -78,59 +177,18 @@ public struct JSON: Codable, @unchecked Sendable { // FIXME: make Sendable public init(from data: Data) throws { do { + print(String(decoding: data, as: UTF8.self)) self = try JSONDecoder().decode(JSON.self, from: data) } catch { throw BedrockLibraryError.decodingError("Failed to decode JSON: \(error)") } } + // Codable + public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() - if container.decodeNil() { - self.value = nil - } else if let intValue = try? container.decode(Int.self) { - self.value = intValue - } else if let doubleValue = try? container.decode(Double.self) { - self.value = doubleValue - } else if let stringValue = try? container.decode(String.self) { - self.value = stringValue - } else if let boolValue = try? container.decode(Bool.self) { - self.value = boolValue - } else if let arrayValue = try? container.decode([JSON].self) { - self.value = arrayValue.map { JSON(with: $0.value) } - } else if let dictionaryValue = try? container.decode([String: JSON].self) { - self.value = dictionaryValue.mapValues { JSON(with: $0.value) } - } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported type") - } + value = try container.decode(JSONValue.self) } - // MARK: Public Methods - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - if let jsonValue = value as? JSON { - try jsonValue.encode(to: encoder) - } else if let intValue = value as? Int { - try container.encode(intValue) - } else if let doubleValue = value as? Double { - try container.encode(doubleValue) - } else if let stringValue = value as? String { - try container.encode(stringValue) - } else if let boolValue = value as? Bool { - try container.encode(boolValue) - } else if let arrayValue = value as? [Any] { - let jsonArray = arrayValue.map { JSON(with: $0) } - try container.encode(jsonArray) - } else if let dictionaryValue = value as? [String: Any] { - let jsonDictionary = dictionaryValue.mapValues { JSON(with: $0) } - try container.encode(jsonDictionary) - } else { - // try container.encode(String(describing: value ?? "nil")) - throw EncodingError.invalidValue( - value ?? "nil", - EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Unsupported type") - ) - } - } } diff --git a/Sources/Converse/ConverseRequest.swift b/Sources/Converse/ConverseRequest.swift index 69f39c8..84879e6 100644 --- a/Sources/Converse/ConverseRequest.swift +++ b/Sources/Converse/ConverseRequest.swift @@ -70,13 +70,21 @@ public struct ConverseRequest { } func getAdditionalModelRequestFields() throws -> Smithy.Document? { + //FIXME: this is incorrect. We should check for all Claude models if model == .claudev3_7_sonnet, let maxReasoningTokens { - let reasoningConfigJSON = JSON(with: [ - "thinking": [ - "type": "enabled", - "budget_tokens": maxReasoningTokens, - ] - ]) + let reasoningConfigJSON = JSON( + with: .array( + [ + .object([ + "thinking": .object([ + "type": .string("enabled"), + "budget_tokens": .int(maxReasoningTokens), + ]) + ]) + ] + ) + ) + return try reasoningConfigJSON.toDocument() } return nil diff --git a/Tests/Converse/ConverseToolTests.swift b/Tests/Converse/ConverseToolTests.swift index 9ff042c..68a0222 100644 --- a/Tests/Converse/ConverseToolTests.swift +++ b/Tests/Converse/ConverseToolTests.swift @@ -24,7 +24,7 @@ extension BedrockServiceTests { func converseRequestTool() async throws { let tool = try Tool( name: "toolName", - inputSchema: JSON(with: ["code": "string"]), + inputSchema: JSON(with: .object(["code": .string("string")])), description: "toolDescription" ) let builder = try ConverseRequestBuilder(with: .nova_lite) @@ -32,28 +32,25 @@ extension BedrockServiceTests { .withTool(tool) let reply = try await bedrock.converse(with: builder) #expect(reply.textReply == nil) - let id: String - let name: String - let input: JSON + if let toolUse = reply.toolUse { - id = toolUse.id - name = toolUse.name - input = toolUse.input + #expect(toolUse.id == "toolId") + #expect(toolUse.name == "toolName") + #expect(toolUse.input["value"]?["code"] == "string") } else { - id = "" - name = "" - input = JSON(with: ["code": "wrong"]) + Issue.record("Tool use is nil") } - #expect(id == "toolId") - #expect(name == "toolName") - #expect(input.getValue("code") == "abc") } @Test("Request tool usage with reused builder") func converseToolWithReusedBuilder() async throws { var builder = try ConverseRequestBuilder(with: .nova_lite) .withPrompt("Use tool") - .withTool(name: "toolName", inputSchema: JSON(with: ["code": "string"]), description: "toolDescription") + .withTool( + name: "toolName", + inputSchema: JSON(with: .object(["code": .string("string")])), + description: "toolDescription" + ) #expect(builder.prompt != nil) #expect(builder.prompt! == "Use tool") @@ -63,23 +60,15 @@ extension BedrockServiceTests { #expect(reply.textReply == nil) - let id: String - let name: String - let input: JSON if let toolUse = reply.toolUse { - id = toolUse.id - name = toolUse.name - input = toolUse.input + print(toolUse) + #expect(toolUse.id == "toolId") + #expect(toolUse.name == "toolName") + #expect(toolUse.input["value"]?["code"] == "string") } else { - id = "" - name = "" - input = JSON(with: ["code": "wrong"]) + Issue.record("ToolUse is nil") } - #expect(id == "toolId") - #expect(name == "toolName") - #expect(input.getValue("code") == "abc") - builder = try ConverseRequestBuilder(from: builder, with: reply) .withToolResult("Information from Tool") @@ -109,7 +98,7 @@ extension BedrockServiceTests { #expect(throws: BedrockLibraryError.self) { let tool = try Tool( name: "toolName", - inputSchema: JSON(with: ["code": "string"]), + inputSchema: JSON(with: .object(["code": .string("string")])), description: "toolDescription" ) let _ = try ConverseRequestBuilder(with: .titan_text_g1_express) @@ -130,11 +119,11 @@ extension BedrockServiceTests { func converseToolResult() async throws { let tool = try Tool( name: "toolName", - inputSchema: JSON(with: ["code": "string"]), + inputSchema: JSON(with: .object(["code": .string("string")])), description: "toolDescription" ) let id = "toolId" - let toolUse = ToolUseBlock(id: id, name: "toolName", input: JSON(with: ["code": "abc"])) + let toolUse = ToolUseBlock(id: id, name: "toolName", input: JSON(with: .object(["code": .string("string")]))) let history = [Message("Use tool"), Message(toolUse)] let builder = try ConverseRequestBuilder(with: .nova_lite) @@ -151,7 +140,7 @@ extension BedrockServiceTests { func converseToolResultWithoutToolUse() async throws { let tool = try Tool( name: "toolName", - inputSchema: JSON(with: ["code": "string"]), + inputSchema: JSON(with: .object(["code": .string("string")])), description: "toolDescription" ) let id = "toolId" @@ -167,7 +156,7 @@ extension BedrockServiceTests { @Test("Tool result without tools") func converseToolResultWithoutTools() async throws { let id = "toolId" - let toolUse = ToolUseBlock(id: id, name: "toolName", input: JSON(with: ["code": "abc"])) + let toolUse = ToolUseBlock(id: id, name: "toolName", input: JSON(with: .object(["code": .string("string")]))) let history = [Message("Use tool"), Message(toolUse)] #expect(throws: BedrockLibraryError.self) { let _ = try ConverseRequestBuilder(with: .nova_lite) @@ -180,11 +169,11 @@ extension BedrockServiceTests { func converseToolResultInvalidModel() async throws { let tool = try Tool( name: "toolName", - inputSchema: JSON(with: ["code": "string"]), + inputSchema: JSON(with: .object(["code": .string("string")])), description: "toolDescription" ) let id = "toolId" - let toolUse = ToolUseBlock(id: id, name: "toolName", input: JSON(with: ["code": "abc"])) + let toolUse = ToolUseBlock(id: id, name: "toolName", input: JSON(with: .object(["code": .string("string")]))) let history = [Message("Use tool"), Message(toolUse)] #expect(throws: BedrockLibraryError.self) { let _ = try ConverseRequestBuilder(with: .titan_text_g1_express) @@ -197,7 +186,7 @@ extension BedrockServiceTests { @Test("Tool result with invalid model without tools") func converseToolResultInvalidModelWithoutTools() async throws { let id = "toolId" - let toolUse = ToolUseBlock(id: id, name: "toolName", input: JSON(with: ["code": "abc"])) + let toolUse = ToolUseBlock(id: id, name: "toolName", input: JSON(with: .object(["code": .string("abc")]))) let history = [Message("Use tool"), Message(toolUse)] #expect(throws: BedrockLibraryError.self) { @@ -211,7 +200,7 @@ extension BedrockServiceTests { func converseToolResultInvalidModelWithoutToolUse() async throws { let tool = try Tool( name: "toolName", - inputSchema: JSON(with: ["code": "string"]), + inputSchema: JSON(with: .object(["code": .string("string")])), description: "toolDescription" ) let history = [Message("Use tool"), Message(from: .assistant, content: [.text("No need for a tool")])] diff --git a/Tests/Converse/JSONTests.swift b/Tests/Converse/JSONTests.swift index 409108a..707ac2f 100644 --- a/Tests/Converse/JSONTests.swift +++ b/Tests/Converse/JSONTests.swift @@ -21,98 +21,79 @@ import Testing @Suite("JSONTests") struct JSONTests { - @Test("JSON getValue") - func jsonGetValue() async throws { - let json = JSON(with: [ - "name": JSON(with: "Jane Doe"), - "age": JSON(with: 30), - "isMember": JSON(with: true), - ]) - #expect(json.getValue("name") == "Jane Doe") - #expect(json.getValue("age") == 30) - #expect(json.getValue("isMember") == true) - #expect(json.getValue("nonExistentKey") == nil) + @Test("JSON getValue from valid JSON string") + func jsonGetValueFromValidJSONString() async throws { + + let json = try jsonFromString() + #expect(json["name"] == "Jane Doe") + #expect(json["age"] == 30) + #expect(json["isMember"] == true) + let t: String? = json["nonExistentKey"] + #expect(t == nil) + } + + @Test("JSON getValue from [String:JSONValue]") + func jsonGetValueFromDictionary() async throws { + let json = try jsonFromDictionary() + + #expect(json["name"] == "Jane Doe") + #expect(json["age"] == 30) + #expect(json["isMember"] == true) + let t: String? = json["nonExistentKey"] + #expect(t == nil) } @Test("JSON getValue nested") func jsonGetValueNested() async throws { - let json = JSON(with: [ - "name": JSON(with: "Jane Doe"), - "age": JSON(with: 30), - "isMember": JSON(with: true), - "address": JSON(with: [ - "street": JSON(with: "123 Main St"), - "city": JSON(with: "Anytown"), - "state": JSON(with: "CA"), - "zip": JSON(with: "12345"), - "isSomething": JSON(with: true), - ]), - ]) - #expect(json.getValue("name") == "Jane Doe") - #expect(json.getValue("age") == 30) - #expect(json.getValue("isMember") == true) - #expect(json.getValue("nonExistentKey") == nil) - #expect(json["address"]?.getValue("street") == "123 Main St") - #expect(json["address"]?.getValue("city") == "Anytown") - #expect(json["address"]?.getValue("state") == "CA") - #expect(json["address"]?.getValue("zip") == "12345") - #expect(json["address"]?.getValue("isSomething") == true) - #expect(json["address"]?.getValue("nonExistentKey") == nil) - } + let json = try jsonFromDictionaryWithNested() + #expect(json["name"] == "Jane Doe") + #expect(json["age"] == 30) + #expect(json["isMember"] == true) + let t: String? = json["nonExistentKey"] + #expect(t == nil) + #expect(json["address"]?["street"] == "123 Main St") + #expect(json["address"]?["city"] == "Anytown") + #expect(json["address"]?["state"] == "CA") + #expect(json["address"]?["zip"] == 12345) + #expect(json["address"]?["isSomething"] == true) + let t2: String? = json["address"]?["nonExistentKey"] + #expect(t2 == nil) + } + // @Test("JSON Subscript") func jsonSubscript() async throws { - let json = JSON(with: [ - "name": JSON(with: "Jane Doe"), - "age": JSON(with: 30), - "isMember": JSON(with: true), - ]) + let json = try JSON( + from: """ + { + "name": "Jane Doe", + "age": 30, + "isMember": true + } + """ + ) #expect(json["name"] == "Jane Doe") #expect(json["age"] == 30) #expect(json["isMember"] == true) - #expect(json["nonExistentKey"] == nil) + let t: String? = json["nonExistentKey"] + #expect(t == nil) } @Test("JSON Subscript nested") func jsonSubscriptNested() async throws { - let json = JSON(with: [ - "name": JSON(with: "Jane Doe"), - "age": JSON(with: 30), - "isMember": JSON(with: true), - "address": JSON(with: [ - "street": JSON(with: "123 Main St"), - "city": JSON(with: "Anytown"), - "state": JSON(with: "CA"), - "zip": JSON(with: 12345), - "isSomething": JSON(with: true), - ]), - ]) + let json = try jsonFromDictionaryWithNested() #expect(json["name"] == "Jane Doe") #expect(json["age"] == 30) #expect(json["isMember"] == true) - #expect(json["nonExistentKey"] == nil) + let t: String? = json["nonExistentKey"] + #expect(t == nil) #expect(json["address"]?["street"] == "123 Main St") #expect(json["address"]?["city"] == "Anytown") #expect(json["address"]?["state"] == "CA") #expect(json["address"]?["zip"] == 12345) #expect(json["address"]?["isSomething"] == true) - #expect(json["address"]?.getValue("nonExistentKey") == nil) - } - - @Test("JSON String Initializer with Valid String") - func jsonStringInitializer() async throws { - let validJSONString = """ - { - "name": "Jane Doe", - "age": 30, - "isMember": true - } - """ - - let json = try JSON(from: validJSONString) - #expect(json.getValue("name") == "Jane Doe") - #expect(json.getValue("age") == 30) - #expect(json.getValue("isMember") == true) + let t2: String? = json["address"]?["nonExistentKey"] + #expect(t2 == nil) } @Test("JSON String Initializer with Invalid String") @@ -122,7 +103,8 @@ struct JSONTests { "name": "Jane Doe", "age": 30, "isMember": true, - """ // Note: trailing comma, making this invalid + + """ // Note: trailing comma and no closing brace, making this invalid #expect(throws: BedrockLibraryError.self) { let _ = try JSON(from: invalidJSONString) } @@ -132,7 +114,56 @@ struct JSONTests { func emptyJSON() async throws { #expect(throws: Never.self) { let json = try JSON(from: "") - #expect(json.getValue("nonExistentKey") == nil) + let t: String? = json["nonExistentKey"] + #expect(t == nil) } } + + @Test("Nested JSONValue") + func nestedJSONValue() { + JSON( + with: JSONValue([ + "name": JSONValue("Jane Doe"), + "age": JSONValue(30), + "isMember": JSONValue(true), + ]) + ) + + } +} + +extension JSONTests { + private func jsonFromString() throws -> JSON { + try JSON( + from: """ + { + "name": "Jane Doe", + "age": 30, + "isMember": true + } + """ + ) + } + + private func jsonFromDictionary() throws -> JSON { + let value: [String: JSONValue] = ["name": .string("Jane Doe"), "age": .int(30), "isMember": .bool(true)] + return JSON(with: .object(value)) + } + + private func jsonFromDictionaryWithNested() throws -> JSON { + JSON( + with: JSONValue([ + "name": JSONValue("Jane Doe"), + "age": JSONValue(30), + "isMember": JSONValue(true), + "address": JSONValue([ + "street": JSONValue("123 Main St"), + "city": JSONValue("Anytown"), + "state": JSONValue("CA"), + "zip": JSONValue(12345), + "isSomething": JSONValue(true), + ]), + ]) + ) + } } diff --git a/Tests/Converse/ToolResultBlockTests.swift b/Tests/Converse/ToolResultBlockTests.swift index 927394a..797906e 100644 --- a/Tests/Converse/ToolResultBlockTests.swift +++ b/Tests/Converse/ToolResultBlockTests.swift @@ -39,15 +39,13 @@ extension BedrockServiceTests { @Test("ToolResultBlock Initializer with ID and JSON Content") func toolResultBlockInitializerWithJSON() async throws { - let json = JSON(with: ["key": JSON(with: "value")]) + let json = JSON(with: .object(["key": .string("string")])) let block = ToolResultBlock(json, id: "block2") #expect(block.id == "block2") #expect(block.content.count == 1) - var value = "" if case .json(let json) = block.content.first { - value = json.getValue("key") ?? "" + #expect(json["key"] == "string") } - #expect(value == "value") #expect(block.status == .success) } @@ -90,11 +88,9 @@ extension BedrockServiceTests { #expect(block.id == "block5") #expect(block.content.count == 1) - var value = "" if case .json(let json) = block.content.first { - value = json.getValue("key") ?? "" + #expect(json["key"] == "value") } - #expect(value == "value") #expect(block.status == block.status) } @@ -108,14 +104,10 @@ extension BedrockServiceTests { let block = try ToolResultBlock(object, id: "block6") #expect(block.id == "block6") #expect(block.content.count == 1) - var name = "" - var age = 0 if case .json(let jsonContent) = block.content.first { - name = jsonContent.getValue("name") ?? "" - age = jsonContent.getValue("age") ?? 0 + #expect(jsonContent["name"] == "Jane") + #expect(jsonContent["age"] == 30) } - #expect(name == "Jane") - #expect(age == 30) } @Test("ToolResultBlock Initializer with Invalid Data Throws Error") diff --git a/Tests/ConverseStream/ConverseStreamToolTests.swift b/Tests/ConverseStream/ConverseStreamToolTests.swift index d02d2f9..1547417 100644 --- a/Tests/ConverseStream/ConverseStreamToolTests.swift +++ b/Tests/ConverseStream/ConverseStreamToolTests.swift @@ -24,7 +24,7 @@ extension ConverseReplyStreamTests { func converseStreamWithToolUse() async throws { let tool = try Tool( name: "toolName", - inputSchema: JSON(with: ["code": "string"]), + inputSchema: JSON(with: .object(["code": .string("string")])), description: "toolDescription" ) var builder = try ConverseRequestBuilder(with: .nova_lite) diff --git a/Tests/Mock/MockBedrockRuntimeClient.swift b/Tests/Mock/MockBedrockRuntimeClient.swift index 844e1b8..764caf7 100644 --- a/Tests/Mock/MockBedrockRuntimeClient.swift +++ b/Tests/Mock/MockBedrockRuntimeClient.swift @@ -36,7 +36,7 @@ public struct MockBedrockRuntimeClient: BedrockRuntimeClientProtocol { var maxReasoningTokens: Int? if let additionalModelRequestFields = input.additionalModelRequestFields { - let json: JSON = JSON(with: additionalModelRequestFields) + let json = try additionalModelRequestFields.toJSON() reasoningEnabled = json["thinking"]?["enabled"] maxReasoningTokens = json["thinking"]?["budget_tokens"] } @@ -117,7 +117,7 @@ public struct MockBedrockRuntimeClient: BedrockRuntimeClientProtocol { switch content { case .text(let prompt): if prompt == "Use tool", let _ = input.toolConfig?.tools { - let toolInputJson = JSON(with: ["code": "abc"]) + let toolInputJson = JSON(with: .object(["code": .string("string")])) let toolInput = try? toolInputJson.toDocument() replyContent.append( .tooluse(