Skip to content

Commit c7fdb2a

Browse files
committed
minor #454 [Platform][Bedrock] Add tests for result converters (OskarStark)
This PR was squashed before being merged into the main branch. Discussion ---------- [Platform][Bedrock] Add tests for result converters | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | Docs? | no | Issues | -- | License | MIT Commits ------- 00959d5 [Platform][Bedrock] Add tests for result converters
2 parents 9e7e7e0 + 00959d5 commit c7fdb2a

File tree

3 files changed

+751
-0
lines changed

3 files changed

+751
-0
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Platform\Tests\Bridge\Bedrock\Anthropic;
13+
14+
use AsyncAws\BedrockRuntime\Result\InvokeModelResponse;
15+
use AsyncAws\Core\Test\ResultMockFactory;
16+
use PHPUnit\Framework\Attributes\CoversClass;
17+
use PHPUnit\Framework\Attributes\Small;
18+
use PHPUnit\Framework\Attributes\TestDox;
19+
use PHPUnit\Framework\Attributes\UsesClass;
20+
use PHPUnit\Framework\TestCase;
21+
use Symfony\AI\Platform\Bridge\Anthropic\Claude;
22+
use Symfony\AI\Platform\Bridge\Bedrock\Anthropic\ClaudeResultConverter;
23+
use Symfony\AI\Platform\Bridge\Bedrock\RawBedrockResult;
24+
use Symfony\AI\Platform\Exception\RuntimeException;
25+
use Symfony\AI\Platform\Result\TextResult;
26+
use Symfony\AI\Platform\Result\ToolCall;
27+
use Symfony\AI\Platform\Result\ToolCallResult;
28+
29+
/**
30+
* @author Oskar Stark <[email protected]>
31+
*/
32+
#[CoversClass(ClaudeResultConverter::class)]
33+
#[Small]
34+
#[UsesClass(RawBedrockResult::class)]
35+
#[UsesClass(TextResult::class)]
36+
#[UsesClass(ToolCall::class)]
37+
#[UsesClass(ToolCallResult::class)]
38+
final class ClaudeResultConverterTest extends TestCase
39+
{
40+
#[TestDox('Supports Claude model')]
41+
public function testSupports()
42+
{
43+
$converter = new ClaudeResultConverter();
44+
$model = new Claude('claude-3-5-sonnet-20241022');
45+
46+
$this->assertTrue($converter->supports($model));
47+
}
48+
49+
#[TestDox('Converts response with text content to TextResult')]
50+
public function testConvertTextResult()
51+
{
52+
$invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [
53+
'body' => json_encode([
54+
'content' => [
55+
[
56+
'type' => 'text',
57+
'text' => 'Hello, world!',
58+
],
59+
],
60+
]),
61+
]);
62+
$rawResult = new RawBedrockResult($invokeResponse);
63+
64+
$converter = new ClaudeResultConverter();
65+
$result = $converter->convert($rawResult);
66+
67+
$this->assertInstanceOf(TextResult::class, $result);
68+
$this->assertSame('Hello, world!', $result->getContent());
69+
}
70+
71+
#[TestDox('Converts response with tool use to ToolCallResult')]
72+
public function testConvertToolCallResult()
73+
{
74+
$invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [
75+
'body' => json_encode([
76+
'content' => [
77+
[
78+
'type' => 'tool_use',
79+
'id' => 'toolu_01UM4PcTjC1UDiorSXVHSVFM',
80+
'name' => 'get_weather',
81+
'input' => ['location' => 'Paris'],
82+
],
83+
],
84+
]),
85+
]);
86+
$rawResult = new RawBedrockResult($invokeResponse);
87+
88+
$converter = new ClaudeResultConverter();
89+
$result = $converter->convert($rawResult);
90+
91+
$this->assertInstanceOf(ToolCallResult::class, $result);
92+
$toolCalls = $result->getContent();
93+
$this->assertCount(1, $toolCalls);
94+
$this->assertSame('toolu_01UM4PcTjC1UDiorSXVHSVFM', $toolCalls[0]->id);
95+
$this->assertSame('get_weather', $toolCalls[0]->name);
96+
$this->assertSame(['location' => 'Paris'], $toolCalls[0]->arguments);
97+
}
98+
99+
#[TestDox('Converts response with multiple tool calls to ToolCallResult')]
100+
public function testConvertMultipleToolCalls()
101+
{
102+
$invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [
103+
'body' => json_encode([
104+
'content' => [
105+
[
106+
'type' => 'tool_use',
107+
'id' => 'toolu_01',
108+
'name' => 'get_weather',
109+
'input' => ['location' => 'Paris'],
110+
],
111+
[
112+
'type' => 'tool_use',
113+
'id' => 'toolu_02',
114+
'name' => 'get_time',
115+
'input' => ['timezone' => 'UTC'],
116+
],
117+
],
118+
]),
119+
]);
120+
$rawResult = new RawBedrockResult($invokeResponse);
121+
122+
$converter = new ClaudeResultConverter();
123+
$result = $converter->convert($rawResult);
124+
125+
$this->assertInstanceOf(ToolCallResult::class, $result);
126+
$toolCalls = $result->getContent();
127+
$this->assertCount(2, $toolCalls);
128+
129+
$this->assertSame('toolu_01', $toolCalls[0]->id);
130+
$this->assertSame('get_weather', $toolCalls[0]->name);
131+
$this->assertSame(['location' => 'Paris'], $toolCalls[0]->arguments);
132+
133+
$this->assertSame('toolu_02', $toolCalls[1]->id);
134+
$this->assertSame('get_time', $toolCalls[1]->name);
135+
$this->assertSame(['timezone' => 'UTC'], $toolCalls[1]->arguments);
136+
}
137+
138+
#[TestDox('Prioritizes tool calls over text in mixed content')]
139+
public function testConvertMixedContentWithToolUse()
140+
{
141+
$invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [
142+
'body' => json_encode([
143+
'content' => [
144+
[
145+
'type' => 'text',
146+
'text' => 'I will get the weather for you.',
147+
],
148+
[
149+
'type' => 'tool_use',
150+
'id' => 'toolu_01',
151+
'name' => 'get_weather',
152+
'input' => ['location' => 'Paris'],
153+
],
154+
],
155+
]),
156+
]);
157+
$rawResult = new RawBedrockResult($invokeResponse);
158+
159+
$converter = new ClaudeResultConverter();
160+
$result = $converter->convert($rawResult);
161+
162+
// When tool calls are present, should return ToolCallResult regardless of text
163+
$this->assertInstanceOf(ToolCallResult::class, $result);
164+
$toolCalls = $result->getContent();
165+
$this->assertCount(1, $toolCalls);
166+
$this->assertSame('toolu_01', $toolCalls[0]->id);
167+
}
168+
169+
#[TestDox('Throws RuntimeException when response has no content')]
170+
public function testConvertThrowsExceptionWhenNoContent()
171+
{
172+
$invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [
173+
'body' => json_encode([]),
174+
]);
175+
$rawResult = new RawBedrockResult($invokeResponse);
176+
177+
$this->expectException(RuntimeException::class);
178+
$this->expectExceptionMessage('Response does not contain any content.');
179+
180+
$converter = new ClaudeResultConverter();
181+
$converter->convert($rawResult);
182+
}
183+
184+
#[TestDox('Throws RuntimeException when response has empty content array')]
185+
public function testConvertThrowsExceptionWhenEmptyContent()
186+
{
187+
$invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [
188+
'body' => json_encode([
189+
'content' => [],
190+
]),
191+
]);
192+
$rawResult = new RawBedrockResult($invokeResponse);
193+
194+
$this->expectException(RuntimeException::class);
195+
$this->expectExceptionMessage('Response does not contain any content.');
196+
197+
$converter = new ClaudeResultConverter();
198+
$converter->convert($rawResult);
199+
}
200+
201+
#[TestDox('Throws RuntimeException when content has no text or type field')]
202+
public function testConvertThrowsExceptionWhenNoTextOrType()
203+
{
204+
$invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [
205+
'body' => json_encode([
206+
'content' => [
207+
[
208+
'invalid' => 'data',
209+
],
210+
],
211+
]),
212+
]);
213+
$rawResult = new RawBedrockResult($invokeResponse);
214+
215+
$this->expectException(RuntimeException::class);
216+
$this->expectExceptionMessage('Response content does not contain any text or type.');
217+
218+
$converter = new ClaudeResultConverter();
219+
$converter->convert($rawResult);
220+
}
221+
222+
#[TestDox('Converts text content successfully')]
223+
public function testConvertWithValidTypeButNoText()
224+
{
225+
$invokeResponse = ResultMockFactory::create(InvokeModelResponse::class, [
226+
'body' => json_encode([
227+
'content' => [
228+
[
229+
'type' => 'text',
230+
'text' => 'Valid text content',
231+
],
232+
],
233+
]),
234+
]);
235+
$rawResult = new RawBedrockResult($invokeResponse);
236+
237+
$converter = new ClaudeResultConverter();
238+
$result = $converter->convert($rawResult);
239+
240+
$this->assertInstanceOf(TextResult::class, $result);
241+
$this->assertSame('Valid text content', $result->getContent());
242+
}
243+
}

0 commit comments

Comments
 (0)