|
13 | 13 | use Prism\Prism\Schema\ObjectSchema; |
14 | 14 | use Prism\Prism\Schema\StringSchema; |
15 | 15 | use Prism\Prism\Tool; |
| 16 | +use Prism\Prism\ValueObjects\ProviderTool; |
16 | 17 | use Tests\Fixtures\FixtureResponse; |
17 | 18 |
|
18 | 19 | beforeEach(function (): void { |
|
120 | 121 | expect($finalStep->structured)->toBeArray(); |
121 | 122 | }); |
122 | 123 |
|
123 | | - it('can use JSON mode strategy with custom tools', function (): void { |
| 124 | + it('can use native output format strategy with custom tools', function (): void { |
124 | 125 | FixtureResponse::fakeResponseSequence('*', 'anthropic/structured-with-tools-json-mode'); |
125 | 126 |
|
126 | 127 | $schema = new ObjectSchema( |
|
143 | 144 | ->withSchema($schema) |
144 | 145 | ->withTools([$searchTool]) |
145 | 146 | ->withMaxSteps(3) |
146 | | - ->withProviderOptions(['use_tool_calling' => false]) // Use JSON mode instead |
147 | 147 | ->withPrompt('Search for "Prism PHP" and give me a summary') |
148 | 148 | ->asStructured(); |
149 | 149 |
|
150 | | - // Verify structured output |
151 | 150 | expect($response->structured)->toBeArray() |
152 | 151 | ->and($response->structured)->toHaveKey('result') |
153 | 152 | ->and($response->structured['result'])->toBeString(); |
154 | 153 |
|
155 | | - // JSON mode should still support tools |
156 | 154 | expect($response->toolCalls)->toBeArray(); |
157 | 155 | expect($response->toolResults)->toBeArray(); |
158 | 156 | }); |
|
377 | 375 |
|
378 | 376 | expect($payload['tool_choice'])->toBe(['type' => 'tool', 'name' => 'output_structured_data']); |
379 | 377 | }); |
| 378 | + |
| 379 | + it('can generate structured output with provider tools using native output format', function (): void { |
| 380 | + FixtureResponse::fakeResponseSequence('*', 'anthropic/structured-with-provider-tool-native'); |
| 381 | + |
| 382 | + $schema = new ObjectSchema( |
| 383 | + 'weather_analysis', |
| 384 | + 'Analysis of weather conditions', |
| 385 | + [ |
| 386 | + new StringSchema('summary', 'A summary of the weather', true), |
| 387 | + new StringSchema('recommendation', 'A recommendation based on weather', true), |
| 388 | + ], |
| 389 | + ['summary', 'recommendation'] |
| 390 | + ); |
| 391 | + |
| 392 | + $response = Prism::structured() |
| 393 | + ->using(Provider::Anthropic, 'claude-sonnet-4-6') |
| 394 | + ->withSchema($schema) |
| 395 | + ->withProviderTools([new ProviderTool(type: 'web_search_20250305', name: 'web_search')]) |
| 396 | + ->withPrompt('What is the weather in San Francisco and should I wear a coat?') |
| 397 | + ->asStructured(); |
| 398 | + |
| 399 | + expect($response->structured)->toBeArray() |
| 400 | + ->and($response->structured)->toHaveKeys(['summary', 'recommendation']) |
| 401 | + ->and($response->structured['summary'])->toBeString() |
| 402 | + ->and($response->structured['recommendation'])->toBeString(); |
| 403 | + }); |
| 404 | + |
| 405 | + it('can generate structured output with provider tools using tool calling mode', function (): void { |
| 406 | + FixtureResponse::fakeResponseSequence('*', 'anthropic/structured-with-provider-tool-toolcalling'); |
| 407 | + |
| 408 | + $schema = new ObjectSchema( |
| 409 | + 'weather_analysis', |
| 410 | + 'Analysis of weather conditions', |
| 411 | + [ |
| 412 | + new StringSchema('summary', 'A summary of the weather', true), |
| 413 | + new StringSchema('recommendation', 'A recommendation based on weather', true), |
| 414 | + ], |
| 415 | + ['summary', 'recommendation'] |
| 416 | + ); |
| 417 | + |
| 418 | + $response = Prism::structured() |
| 419 | + ->using(Provider::Anthropic, 'claude-sonnet-4-6') |
| 420 | + ->withSchema($schema) |
| 421 | + ->withProviderTools([new ProviderTool(type: 'web_search_20250305', name: 'web_search')]) |
| 422 | + ->withProviderOptions(['use_tool_calling' => true]) |
| 423 | + ->withPrompt('What is the weather in San Francisco and should I wear a coat?') |
| 424 | + ->asStructured(); |
| 425 | + |
| 426 | + expect($response->structured)->toBeArray() |
| 427 | + ->and($response->structured)->toHaveKeys(['summary', 'recommendation']) |
| 428 | + ->and($response->structured['summary'])->toBeString() |
| 429 | + ->and($response->structured['recommendation'])->toBeString(); |
| 430 | + }); |
| 431 | + |
| 432 | + it('includes provider tools in native output format payload', function (): void { |
| 433 | + Prism::fake(); |
| 434 | + |
| 435 | + $schema = new ObjectSchema( |
| 436 | + 'output', |
| 437 | + 'the output object', |
| 438 | + [new StringSchema('result', 'The result', true)], |
| 439 | + ['result'] |
| 440 | + ); |
| 441 | + |
| 442 | + $request = Prism::structured() |
| 443 | + ->using(Provider::Anthropic, 'claude-sonnet-4-6') |
| 444 | + ->withSchema($schema) |
| 445 | + ->withProviderTools([new ProviderTool(type: 'web_search_20250305', name: 'web_search')]); |
| 446 | + |
| 447 | + $payload = Structured::buildHttpRequestPayload( |
| 448 | + $request->toRequest() |
| 449 | + ); |
| 450 | + |
| 451 | + expect($payload)->toHaveKey('tools') |
| 452 | + ->and($payload)->toHaveKey('output_config') |
| 453 | + ->and($payload['tools'])->toHaveCount(1) |
| 454 | + ->and($payload['tools'][0]['type'])->toBe('web_search_20250305') |
| 455 | + ->and($payload['tools'][0]['name'])->toBe('web_search'); |
| 456 | + }); |
| 457 | + |
| 458 | + it('includes provider tools alongside structured tool in tool calling mode', function (): void { |
| 459 | + Prism::fake(); |
| 460 | + |
| 461 | + $schema = new ObjectSchema( |
| 462 | + 'output', |
| 463 | + 'the output object', |
| 464 | + [new StringSchema('result', 'The result', true)], |
| 465 | + ['result'] |
| 466 | + ); |
| 467 | + |
| 468 | + $request = Prism::structured() |
| 469 | + ->using(Provider::Anthropic, 'claude-sonnet-4-6') |
| 470 | + ->withSchema($schema) |
| 471 | + ->withProviderTools([new ProviderTool(type: 'web_search_20250305', name: 'web_search')]) |
| 472 | + ->withProviderOptions(['use_tool_calling' => true]); |
| 473 | + |
| 474 | + $payload = Structured::buildHttpRequestPayload( |
| 475 | + $request->toRequest() |
| 476 | + ); |
| 477 | + |
| 478 | + expect($payload)->toHaveKey('tools'); |
| 479 | + |
| 480 | + $toolTypes = array_column($payload['tools'], 'type'); |
| 481 | + $toolNames = array_column($payload['tools'], 'name'); |
| 482 | + |
| 483 | + expect($toolNames)->toContain('web_search') |
| 484 | + ->and($toolNames)->toContain('output_structured_data') |
| 485 | + ->and($toolTypes)->toContain('web_search_20250305'); |
| 486 | + }); |
380 | 487 | }); |
0 commit comments