From 00959d5cba60d5619f9dd26f7f25719f66af9107 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Thu, 4 Sep 2025 16:35:26 +0200 Subject: [PATCH] [Platform][Bedrock] Add tests for result converters --- .../Anthropic/ClaudeResultConverterTest.php | 243 +++++++++++++++ .../Bedrock/Meta/LlamaResultConverterTest.php | 219 +++++++++++++ .../Bedrock/Nova/NovaResultConverterTest.php | 289 ++++++++++++++++++ 3 files changed, 751 insertions(+) create mode 100644 src/platform/tests/Bridge/Bedrock/Anthropic/ClaudeResultConverterTest.php create mode 100644 src/platform/tests/Bridge/Bedrock/Meta/LlamaResultConverterTest.php create mode 100644 src/platform/tests/Bridge/Bedrock/Nova/NovaResultConverterTest.php diff --git a/src/platform/tests/Bridge/Bedrock/Anthropic/ClaudeResultConverterTest.php b/src/platform/tests/Bridge/Bedrock/Anthropic/ClaudeResultConverterTest.php new file mode 100644 index 000000000..24cb303a6 --- /dev/null +++ b/src/platform/tests/Bridge/Bedrock/Anthropic/ClaudeResultConverterTest.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\Bedrock\Anthropic; + +use AsyncAws\BedrockRuntime\Result\InvokeModelResponse; +use AsyncAws\Core\Test\ResultMockFactory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\Anthropic\Claude; +use Symfony\AI\Platform\Bridge\Bedrock\Anthropic\ClaudeResultConverter; +use Symfony\AI\Platform\Bridge\Bedrock\RawBedrockResult; +use Symfony\AI\Platform\Exception\RuntimeException; +use Symfony\AI\Platform\Result\TextResult; +use Symfony\AI\Platform\Result\ToolCall; +use Symfony\AI\Platform\Result\ToolCallResult; + +/** + * @author Oskar Stark + */ +#[CoversClass(ClaudeResultConverter::class)] +#[Small] +#[UsesClass(RawBedrockResult::class)] +#[UsesClass(TextResult::class)] +#[UsesClass(ToolCall::class)] +#[UsesClass(ToolCallResult::class)] +final class ClaudeResultConverterTest extends TestCase +{ + #[TestDox('Supports Claude model')] + public function testSupports() + { + $converter = new ClaudeResultConverter(); + $model = new Claude('claude-3-5-sonnet-20241022'); + + $this->assertTrue($converter->supports($model)); + } + + #[TestDox('Converts response with text content to TextResult')] + public function testConvertTextResult() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'content' => [ + [ + 'type' => 'text', + 'text' => 'Hello, world!', + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new ClaudeResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(TextResult::class, $result); + $this->assertSame('Hello, world!', $result->getContent()); + } + + #[TestDox('Converts response with tool use to ToolCallResult')] + public function testConvertToolCallResult() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'content' => [ + [ + 'type' => 'tool_use', + 'id' => 'toolu_01UM4PcTjC1UDiorSXVHSVFM', + 'name' => 'get_weather', + 'input' => ['location' => 'Paris'], + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new ClaudeResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(ToolCallResult::class, $result); + $toolCalls = $result->getContent(); + $this->assertCount(1, $toolCalls); + $this->assertSame('toolu_01UM4PcTjC1UDiorSXVHSVFM', $toolCalls[0]->id); + $this->assertSame('get_weather', $toolCalls[0]->name); + $this->assertSame(['location' => 'Paris'], $toolCalls[0]->arguments); + } + + #[TestDox('Converts response with multiple tool calls to ToolCallResult')] + public function testConvertMultipleToolCalls() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'content' => [ + [ + 'type' => 'tool_use', + 'id' => 'toolu_01', + 'name' => 'get_weather', + 'input' => ['location' => 'Paris'], + ], + [ + 'type' => 'tool_use', + 'id' => 'toolu_02', + 'name' => 'get_time', + 'input' => ['timezone' => 'UTC'], + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new ClaudeResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(ToolCallResult::class, $result); + $toolCalls = $result->getContent(); + $this->assertCount(2, $toolCalls); + + $this->assertSame('toolu_01', $toolCalls[0]->id); + $this->assertSame('get_weather', $toolCalls[0]->name); + $this->assertSame(['location' => 'Paris'], $toolCalls[0]->arguments); + + $this->assertSame('toolu_02', $toolCalls[1]->id); + $this->assertSame('get_time', $toolCalls[1]->name); + $this->assertSame(['timezone' => 'UTC'], $toolCalls[1]->arguments); + } + + #[TestDox('Prioritizes tool calls over text in mixed content')] + public function testConvertMixedContentWithToolUse() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'content' => [ + [ + 'type' => 'text', + 'text' => 'I will get the weather for you.', + ], + [ + 'type' => 'tool_use', + 'id' => 'toolu_01', + 'name' => 'get_weather', + 'input' => ['location' => 'Paris'], + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new ClaudeResultConverter(); + $result = $converter->convert($rawResult); + + // When tool calls are present, should return ToolCallResult regardless of text + $this->assertInstanceOf(ToolCallResult::class, $result); + $toolCalls = $result->getContent(); + $this->assertCount(1, $toolCalls); + $this->assertSame('toolu_01', $toolCalls[0]->id); + } + + #[TestDox('Throws RuntimeException when response has no content')] + public function testConvertThrowsExceptionWhenNoContent() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response does not contain any content.'); + + $converter = new ClaudeResultConverter(); + $converter->convert($rawResult); + } + + #[TestDox('Throws RuntimeException when response has empty content array')] + public function testConvertThrowsExceptionWhenEmptyContent() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'content' => [], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response does not contain any content.'); + + $converter = new ClaudeResultConverter(); + $converter->convert($rawResult); + } + + #[TestDox('Throws RuntimeException when content has no text or type field')] + public function testConvertThrowsExceptionWhenNoTextOrType() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'content' => [ + [ + 'invalid' => 'data', + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response content does not contain any text or type.'); + + $converter = new ClaudeResultConverter(); + $converter->convert($rawResult); + } + + #[TestDox('Converts text content successfully')] + public function testConvertWithValidTypeButNoText() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'content' => [ + [ + 'type' => 'text', + 'text' => 'Valid text content', + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new ClaudeResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(TextResult::class, $result); + $this->assertSame('Valid text content', $result->getContent()); + } +} diff --git a/src/platform/tests/Bridge/Bedrock/Meta/LlamaResultConverterTest.php b/src/platform/tests/Bridge/Bedrock/Meta/LlamaResultConverterTest.php new file mode 100644 index 000000000..e5d89fb8e --- /dev/null +++ b/src/platform/tests/Bridge/Bedrock/Meta/LlamaResultConverterTest.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\Bedrock\Meta; + +use AsyncAws\BedrockRuntime\Result\InvokeModelResponse; +use AsyncAws\Core\Test\ResultMockFactory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\Bedrock\Meta\LlamaResultConverter; +use Symfony\AI\Platform\Bridge\Bedrock\RawBedrockResult; +use Symfony\AI\Platform\Bridge\Meta\Llama; +use Symfony\AI\Platform\Exception\RuntimeException; +use Symfony\AI\Platform\Result\TextResult; + +/** + * @author Oskar Stark + */ +#[CoversClass(LlamaResultConverter::class)] +#[Small] +#[UsesClass(RawBedrockResult::class)] +#[UsesClass(TextResult::class)] +final class LlamaResultConverterTest extends TestCase +{ + #[TestDox('Supports Llama model')] + public function testSupports() + { + $model = new Llama('llama3-8b-instruct'); + + $converter = new LlamaResultConverter(); + $this->assertTrue($converter->supports($model)); + } + + #[TestDox('Converts response with generation text to TextResult')] + public function testConvertTextResult() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'generation' => 'Hello from Llama!', + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new LlamaResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(TextResult::class, $result); + $this->assertSame('Hello from Llama!', $result->getContent()); + } + + #[TestDox('Converts empty generation to TextResult with empty content')] + public function testConvertEmptyGeneration() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'generation' => '', + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new LlamaResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(TextResult::class, $result); + $this->assertSame('', $result->getContent()); + } + + #[TestDox('Throws RuntimeException when generation field is null')] + public function testConvertThrowsExceptionWhenGenerationIsNull() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'generation' => null, + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + // Looking at the LlamaResultConverter, it checks for isset() which returns false for null + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response does not contain any content.'); + + $converter = new LlamaResultConverter(); + $converter->convert($rawResult); + } + + #[TestDox('Converts long generation text successfully')] + public function testConvertLongGeneration() + { + $longText = str_repeat('This is a long text. ', 100); + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'generation' => $longText, + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new LlamaResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(TextResult::class, $result); + $this->assertSame($longText, $result->getContent()); + } + + #[TestDox('Ignores additional response fields and extracts generation text')] + public function testConvertWithAdditionalFields() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'generation' => 'Hello from Llama!', + 'prompt_token_count' => 10, + 'generation_token_count' => 5, + 'stop_reason' => 'end_turn', + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new LlamaResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(TextResult::class, $result); + $this->assertSame('Hello from Llama!', $result->getContent()); + } + + #[TestDox('Throws RuntimeException when response has no generation field')] + public function testConvertThrowsExceptionWhenNoGeneration() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response does not contain any content.'); + + $converter = new LlamaResultConverter(); + $converter->convert($rawResult); + } + + #[TestDox('Throws RuntimeException when generation key is missing')] + public function testConvertThrowsExceptionWhenGenerationKeyMissing() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'prompt_token_count' => 10, + 'generation_token_count' => 5, + 'stop_reason' => 'end_turn', + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response does not contain any content.'); + + $converter = new LlamaResultConverter(); + $converter->convert($rawResult); + } + + #[TestDox('Converts response with options parameter successfully')] + public function testConvertWithOptions() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'generation' => 'Hello with options!', + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new LlamaResultConverter(); + $result = $converter->convert($rawResult, ['some' => 'option']); + + $this->assertInstanceOf(TextResult::class, $result); + $this->assertSame('Hello with options!', $result->getContent()); + } + + #[TestDox('Converts numeric generation as string')] + public function testConvertWithNumericGeneration() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'generation' => '42', + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new LlamaResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(TextResult::class, $result); + $this->assertSame('42', $result->getContent()); + } + + #[TestDox('Converts JSON string generation successfully')] + public function testConvertWithJsonGeneration() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'generation' => '{"response": "JSON formatted text"}', + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new LlamaResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(TextResult::class, $result); + $this->assertSame('{"response": "JSON formatted text"}', $result->getContent()); + } +} diff --git a/src/platform/tests/Bridge/Bedrock/Nova/NovaResultConverterTest.php b/src/platform/tests/Bridge/Bedrock/Nova/NovaResultConverterTest.php new file mode 100644 index 000000000..0c5054086 --- /dev/null +++ b/src/platform/tests/Bridge/Bedrock/Nova/NovaResultConverterTest.php @@ -0,0 +1,289 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\Bedrock\Nova; + +use AsyncAws\BedrockRuntime\Result\InvokeModelResponse; +use AsyncAws\Core\Test\ResultMockFactory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\Bedrock\Nova\Nova; +use Symfony\AI\Platform\Bridge\Bedrock\Nova\NovaResultConverter; +use Symfony\AI\Platform\Bridge\Bedrock\RawBedrockResult; +use Symfony\AI\Platform\Exception\RuntimeException; +use Symfony\AI\Platform\Result\TextResult; +use Symfony\AI\Platform\Result\ToolCall; +use Symfony\AI\Platform\Result\ToolCallResult; + +/** + * @author Oskar Stark + */ +#[CoversClass(NovaResultConverter::class)] +#[Small] +#[UsesClass(RawBedrockResult::class)] +#[UsesClass(TextResult::class)] +#[UsesClass(ToolCall::class)] +#[UsesClass(ToolCallResult::class)] +final class NovaResultConverterTest extends TestCase +{ + #[TestDox('Supports Nova model')] + public function testSupports() + { + $model = new Nova(Nova::PRO); + + $converter = new NovaResultConverter(); + $this->assertTrue($converter->supports($model)); + } + + #[TestDox('Converts response with text content to TextResult')] + public function testConvertTextResult() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'output' => [ + 'message' => [ + 'content' => [ + [ + 'text' => 'Hello from Nova!', + ], + ], + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new NovaResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(TextResult::class, $result); + $this->assertSame('Hello from Nova!', $result->getContent()); + } + + #[TestDox('Converts response with tool use to ToolCallResult')] + public function testConvertToolCallResult() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'output' => [ + 'message' => [ + 'content' => [ + [ + 'text' => 'I will calculate this for you.', + ], + [ + 'toolUse' => [ + 'toolUseId' => 'nova-tool-123', + 'name' => 'calculate', + 'input' => ['expression' => '2+2'], + ], + ], + ], + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new NovaResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(ToolCallResult::class, $result); + $toolCalls = $result->getContent(); + $this->assertCount(1, $toolCalls); + $this->assertSame('nova-tool-123', $toolCalls[0]->id); + $this->assertSame('calculate', $toolCalls[0]->name); + $this->assertSame(['expression' => '2+2'], $toolCalls[0]->arguments); + } + + #[TestDox('Converts response with multiple tool calls to ToolCallResult')] + public function testConvertMultipleToolCalls() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'output' => [ + 'message' => [ + 'content' => [ + [ + 'text' => 'I will help you with both requests.', + ], + [ + 'toolUse' => [ + 'toolUseId' => 'nova-tool-1', + 'name' => 'get_weather', + 'input' => ['location' => 'New York'], + ], + ], + [ + 'toolUse' => [ + 'toolUseId' => 'nova-tool-2', + 'name' => 'get_time', + 'input' => ['timezone' => 'EST'], + ], + ], + ], + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new NovaResultConverter(); + $result = $converter->convert($rawResult); + + $this->assertInstanceOf(ToolCallResult::class, $result); + $toolCalls = $result->getContent(); + $this->assertCount(2, $toolCalls); + + $this->assertSame('nova-tool-1', $toolCalls[0]->id); + $this->assertSame('get_weather', $toolCalls[0]->name); + $this->assertSame(['location' => 'New York'], $toolCalls[0]->arguments); + + $this->assertSame('nova-tool-2', $toolCalls[1]->id); + $this->assertSame('get_time', $toolCalls[1]->name); + $this->assertSame(['timezone' => 'EST'], $toolCalls[1]->arguments); + } + + #[TestDox('Prioritizes tool calls over text in mixed content')] + public function testConvertMixedContentWithToolUse() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'output' => [ + 'message' => [ + 'content' => [ + [ + 'text' => 'I will calculate this for you.', + ], + [ + 'toolUse' => [ + 'toolUseId' => 'nova-tool-123', + 'name' => 'calculate', + 'input' => ['expression' => '5*10'], + ], + ], + ], + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $converter = new NovaResultConverter(); + $result = $converter->convert($rawResult); + + // When tool calls are present, should return ToolCallResult regardless of text + $this->assertInstanceOf(ToolCallResult::class, $result); + $toolCalls = $result->getContent(); + $this->assertCount(1, $toolCalls); + $this->assertSame('nova-tool-123', $toolCalls[0]->id); + } + + #[TestDox('Throws RuntimeException when response has no output')] + public function testConvertThrowsExceptionWhenNoOutput() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response does not contain any content.'); + + $converter = new NovaResultConverter(); + $converter->convert($rawResult); + } + + #[TestDox('Throws RuntimeException when response has empty output')] + public function testConvertThrowsExceptionWhenEmptyOutput() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'output' => [], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response does not contain any content.'); + + $converter = new NovaResultConverter(); + $converter->convert($rawResult); + } + + #[TestDox('Throws RuntimeException when content has no text')] + public function testConvertThrowsExceptionWhenNoText() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'output' => [ + 'message' => [ + 'content' => [ + [ + 'invalid' => 'data', + ], + ], + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response content does not contain any text.'); + + $converter = new NovaResultConverter(); + $converter->convert($rawResult); + } + + #[TestDox('Throws RuntimeException when message structure is missing')] + public function testConvertThrowsExceptionWhenMissingMessageStructure() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'output' => [ + 'message' => [], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response content does not contain any text.'); + + $converter = new NovaResultConverter(); + $converter->convert($rawResult); + } + + #[TestDox('Throws RuntimeException when content array is missing')] + public function testConvertThrowsExceptionWhenMissingContent() + { + $invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [ + 'body' => json_encode([ + 'output' => [ + 'message' => [ + 'content' => [], + ], + ], + ]), + ]); + $rawResult = new RawBedrockResult($invokeResponse); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Response content does not contain any text.'); + + $converter = new NovaResultConverter(); + $converter->convert($rawResult); + } +}