|
101 | 101 | }); |
102 | 102 |
|
103 | 103 | test('it can use structured output', function (): void { |
104 | | - $response = createAnthropicResponse([ |
105 | | - 'stop_reason' => 'end_turn', |
106 | | - 'content' => [ |
107 | | - [ |
108 | | - 'type' => 'text', |
109 | | - 'text' => '{"name":"John Doe","age":30}', |
110 | | - ], |
111 | | - ], |
112 | | - ]); |
113 | | - |
114 | 104 | $llm = AnthropicChat::fake([ |
115 | | - $response, |
116 | | - ], 'claude-3-5-sonnet-20241022'); |
| 105 | + CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output'), |
| 106 | + ]); |
117 | 107 |
|
118 | 108 | $llm->addFeature(ModelFeature::StructuredOutput); |
119 | | - |
120 | 109 | $llm->withStructuredOutput( |
121 | | - Schema::object()->properties( |
122 | | - Schema::string('name'), |
123 | | - Schema::integer('age'), |
124 | | - ), |
| 110 | + Schema::object() |
| 111 | + ->properties( |
| 112 | + Schema::string('name')->required(), |
| 113 | + Schema::string('email')->required(), |
| 114 | + Schema::string('plan_interest')->required(), |
| 115 | + Schema::boolean('demo_requested')->required(), |
| 116 | + ) |
| 117 | + ->additionalProperties(false), |
125 | 118 | name: 'Person', |
126 | 119 | description: 'A person with a name and age', |
127 | 120 | ); |
128 | 121 |
|
129 | 122 | $result = $llm->invoke([ |
130 | | - new UserMessage('Tell me about a person'), |
| 123 | + new UserMessage('Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.'), |
131 | 124 | ]); |
132 | 125 |
|
133 | 126 | expect($result->generation->message->content) |
134 | 127 | ->toBeArray() |
135 | 128 | ->toContainOnlyInstancesOf(TextContent::class) |
136 | 129 | ->and($result->generation->message->content[0]->text) |
137 | | - ->toBe('{"name":"John Doe","age":30}'); |
| 130 | + ->toBe('{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}'); |
138 | 131 |
|
139 | 132 | expect($result->generation->parsedOutput)->toBe([ |
140 | | - 'name' => 'John Doe', |
141 | | - 'age' => 30, |
| 133 | + 'name' => 'John Smith', |
| 134 | + 'email' => 'john@example.com', |
| 135 | + 'plan_interest' => 'Enterprise', |
| 136 | + 'demo_requested' => true, |
142 | 137 | ]); |
143 | 138 | }); |
144 | 139 |
|
145 | 140 | test('it can use structured output using the schema tool', function (): void { |
146 | | - $response = createAnthropicResponse([ |
147 | | - 'stop_reason' => 'tool_use', |
148 | | - 'content' => [ |
149 | | - [ |
150 | | - 'type' => 'tool_use', |
151 | | - 'id' => 'call_123', |
152 | | - 'name' => SchemaTool::NAME, |
153 | | - 'input' => [ |
154 | | - 'name' => 'John Doe', |
155 | | - 'age' => 30, |
156 | | - ], |
157 | | - ], |
158 | | - ], |
159 | | - ]); |
160 | | - |
161 | 141 | $llm = AnthropicChat::fake([ |
162 | | - $response, |
163 | | - ], 'claude-3-5-sonnet-20241022'); |
| 142 | + CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output-tool-use'), |
| 143 | + ]); |
164 | 144 |
|
165 | 145 | $llm->addFeature(ModelFeature::ToolCalling); |
166 | 146 |
|
|
171 | 151 |
|
172 | 152 | $llm->withStructuredOutput($schema, outputMode: StructuredOutputMode::Tool); |
173 | 153 |
|
174 | | - $result = $llm->invoke([ |
| 154 | + $result = $llm->withMaxTokens(1024)->invoke([ |
175 | 155 | new UserMessage('Tell me about a person'), |
176 | 156 | ]); |
177 | 157 |
|
178 | 158 | expect($result->parsedOutput) |
179 | 159 | ->toBe([ |
180 | | - 'name' => 'John Doe', |
181 | | - 'age' => 30, |
| 160 | + 'name' => 'John Smith', |
| 161 | + 'age' => 35, |
182 | 162 | ]); |
183 | 163 |
|
184 | | - /** @var \Anthropic\Testing\ClientFake $client */ |
185 | | - $client = $llm->getClient(); |
| 164 | + expect($result->generation->message->content) |
| 165 | + ->toBeArray() |
| 166 | + ->toContainOnlyInstancesOf(TextContent::class); |
186 | 167 |
|
187 | | - $client->messages()->assertSent(function (string $method, array $parameters): bool { |
188 | | - return $parameters['model'] === 'claude-3-5-sonnet-20241022' |
189 | | - && $parameters['messages'][0]['role'] === 'user' |
190 | | - && $parameters['messages'][0]['content'] === 'Tell me about a person' |
191 | | - && $parameters['tools'][0]['type'] === 'custom' |
192 | | - && $parameters['tools'][0]['name'] === SchemaTool::NAME |
193 | | - && $parameters['tools'][0]['input_schema']['properties']['name']['type'] === 'string' |
194 | | - && $parameters['tools'][0]['input_schema']['properties']['age']['type'] === 'integer'; |
195 | | - }); |
| 168 | + expect($result->generation->message->toolCalls) |
| 169 | + ->toBeInstanceOf(ToolCallCollection::class) |
| 170 | + ->and($result->generation->message->toolCalls) |
| 171 | + ->toHaveCount(1) |
| 172 | + ->and($result->generation->message->toolCalls[0]) |
| 173 | + ->toBeInstanceOf(ToolCall::class) |
| 174 | + ->and($result->generation->message->toolCalls[0]->function) |
| 175 | + ->toBeInstanceOf(FunctionCall::class) |
| 176 | + ->and($result->generation->message->toolCalls[0]->function->name) |
| 177 | + ->toBe(SchemaTool::NAME); |
196 | 178 | }); |
197 | 179 |
|
198 | 180 | test('it can stream with structured output', function (): void { |
|
0 commit comments