|
11 | 11 | use Cortex\LLM\Data\Usage; |
12 | 12 | use Cortex\LLM\AbstractLLM; |
13 | 13 | use Illuminate\Support\Arr; |
| 14 | +use Cortex\LLM\Data\ToolCall; |
14 | 15 | use Cortex\LLM\Contracts\Tool; |
15 | 16 | use Cortex\Events\ChatModelEnd; |
16 | 17 | use Cortex\LLM\Data\ChatResult; |
|
19 | 20 | use Cortex\Events\ChatModelError; |
20 | 21 | use Cortex\Events\ChatModelStart; |
21 | 22 | use Cortex\LLM\Contracts\Message; |
| 23 | +use Cortex\LLM\Data\FunctionCall; |
22 | 24 | use Cortex\LLM\Enums\MessageRole; |
23 | 25 | use Cortex\LLM\Enums\FinishReason; |
24 | 26 | use Cortex\Exceptions\LLMException; |
25 | 27 | use Cortex\LLM\Data\ChatGeneration; |
26 | 28 | use Cortex\LLM\Data\ChatStreamResult; |
27 | 29 | use Cortex\LLM\Data\ResponseMetadata; |
28 | 30 | use Anthropic\Contracts\ClientContract; |
| 31 | +use Cortex\LLM\Data\ToolCallCollection; |
29 | 32 | use Cortex\LLM\Data\ChatGenerationChunk; |
30 | | -use Cortex\JsonSchema\Types\ObjectSchema; |
31 | 33 | use Cortex\LLM\Data\Messages\ToolMessage; |
32 | 34 | use Cortex\ModelInfo\Enums\ModelProvider; |
33 | 35 | use Cortex\LLM\Data\Messages\SystemMessage; |
| 36 | +use Cortex\Tasks\Enums\StructuredOutputMode; |
34 | 37 | use Cortex\LLM\Data\Messages\AssistantMessage; |
35 | 38 | use Cortex\LLM\Data\Messages\MessageCollection; |
36 | 39 | use Anthropic\Responses\Messages\CreateResponse; |
37 | 40 | use Anthropic\Responses\Messages\StreamResponse; |
38 | 41 | use Cortex\LLM\Data\Messages\Content\TextContent; |
39 | | -use Cortex\LLM\Data\Messages\Content\ImageContent; |
40 | 42 | use Anthropic\Responses\Messages\CreateResponseUsage; |
41 | | -use Cortex\LLM\Data\Messages\Content\DocumentContent; |
42 | 43 | use Anthropic\Responses\Messages\CreateResponseContent; |
43 | 44 | use Anthropic\Responses\Messages\CreateStreamedResponseUsage; |
44 | 45 | use Anthropic\Testing\Responses\Fixtures\Messages\CreateResponseFixture; |
@@ -106,40 +107,47 @@ public function invoke( |
106 | 107 | */ |
107 | 108 | protected function mapResponse(CreateResponse $response): ChatResult |
108 | 109 | { |
109 | | - $content = $response->content[0]; |
110 | | - // $toolCalls = $content->toolCalls === [] ? null : new ToolCallCollection( |
111 | | - // collect($content->toolCalls) |
112 | | - // ->map(fn(CreateResponseToolCall $toolCall): ToolCall => new ToolCall( |
113 | | - // $toolCall->id, |
114 | | - // new FunctionCall( |
115 | | - // $toolCall->function->name, |
116 | | - // json_decode($toolCall->function->arguments, true, flags: JSON_THROW_ON_ERROR), |
117 | | - // ), |
118 | | - // )) |
119 | | - // ->values() |
120 | | - // ->all(), |
121 | | - // ); |
| 110 | + $toolCalls = array_filter( |
| 111 | + $response->content, |
| 112 | + fn(CreateResponseContent $content): bool => $content->type === 'tool_use', |
| 113 | + ); |
| 114 | + |
| 115 | + $toolCalls = collect($toolCalls) |
| 116 | + ->map(fn(CreateResponseContent $content): ToolCall => new ToolCall( |
| 117 | + $content->id, |
| 118 | + new FunctionCall($content->name, $content->input ?? []), |
| 119 | + )) |
| 120 | + ->values() |
| 121 | + ->all(); |
| 122 | + |
| 123 | + $toolCalls = $toolCalls !== [] |
| 124 | + ? new ToolCallCollection($toolCalls) |
| 125 | + : null; |
122 | 126 |
|
123 | 127 | $usage = $this->mapUsage($response->usage); |
124 | 128 | $finishReason = static::mapFinishReason($response->stop_reason ?? null); |
125 | 129 |
|
126 | 130 | $generations = collect($response->content) |
127 | | - ->map(fn(CreateResponseContent $content): ChatGeneration => new ChatGeneration( |
128 | | - message: new AssistantMessage( |
129 | | - content: $content->text, |
130 | | - // toolCalls: $toolCalls, |
131 | | - metadata: new ResponseMetadata( |
132 | | - id: $response->id, |
133 | | - model: $response->model, |
134 | | - provider: $this->modelProvider, |
135 | | - finishReason: $finishReason, |
136 | | - usage: $usage, |
| 131 | + ->map(function (CreateResponseContent $content) use ($toolCalls, $finishReason, $usage, $response): ChatGeneration { |
| 132 | + $generation = new ChatGeneration( |
| 133 | + message: new AssistantMessage( |
| 134 | + content: $content->text, |
| 135 | + toolCalls: $toolCalls, |
| 136 | + metadata: new ResponseMetadata( |
| 137 | + id: $response->id, |
| 138 | + model: $response->model, |
| 139 | + provider: $this->modelProvider, |
| 140 | + finishReason: $finishReason, |
| 141 | + usage: $usage, |
| 142 | + ), |
137 | 143 | ), |
138 | | - ), |
139 | | - index: 0, |
140 | | - createdAt: new DateTimeImmutable(), |
141 | | - finishReason: $finishReason, |
142 | | - )) |
| 144 | + index: 0, |
| 145 | + createdAt: new DateTimeImmutable(), |
| 146 | + finishReason: $finishReason, |
| 147 | + ); |
| 148 | + |
| 149 | + return $this->applyOutputParserIfApplicable($generation); |
| 150 | + }) |
143 | 151 | ->all(); |
144 | 152 |
|
145 | 153 | $result = new ChatResult( |
@@ -319,16 +327,16 @@ protected static function mapMessagesForInput(MessageCollection $messages): arra |
319 | 327 | 'type' => 'text', |
320 | 328 | 'text' => $content->text, |
321 | 329 | ], |
322 | | - $content instanceof ImageContent => [ |
323 | | - 'type' => 'image_url', |
324 | | - 'image_url' => [ |
325 | | - 'url' => $content->urlOrBase64, |
326 | | - ], |
327 | | - ], |
328 | | - $content instanceof DocumentContent => [ |
329 | | - 'type' => 'document', |
330 | | - 'document' => $content->data, |
331 | | - ], |
| 330 | + // $content instanceof ImageContent => [ |
| 331 | + // 'type' => 'image_url', |
| 332 | + // 'image_url' => [ |
| 333 | + // 'url' => $content->urlOrBase64, |
| 334 | + // ], |
| 335 | + // ], |
| 336 | + // $content instanceof DocumentContent => [ |
| 337 | + // 'type' => 'document', |
| 338 | + // 'document' => $content->data, |
| 339 | + // ], |
332 | 340 | default => $content, |
333 | 341 | }; |
334 | 342 | }, $formattedMessage['content']); |
@@ -367,56 +375,39 @@ protected function buildParams(array $additionalParameters): array |
367 | 375 | ]; |
368 | 376 |
|
369 | 377 | if ($this->structuredOutputConfig !== null) { |
370 | | - $schema = $this->structuredOutputConfig->schema; |
371 | | - $params['response_format'] = [ |
372 | | - 'type' => 'json_schema', |
373 | | - 'json_schema' => [ |
374 | | - 'name' => $this->structuredOutputConfig->name, |
375 | | - 'description' => $this->structuredOutputConfig->description ?? $schema->getDescription(), |
376 | | - 'schema' => $schema instanceof ObjectSchema |
377 | | - ? $schema->additionalProperties(false)->toArray() |
378 | | - : $schema, |
379 | | - 'strict' => $this->structuredOutputConfig->strict, |
380 | | - ], |
381 | | - ]; |
| 378 | + $this->structuredOutputMode = StructuredOutputMode::Tool; |
382 | 379 | } elseif ($this->forceJsonOutput) { |
383 | | - $params['response_format'] = [ |
384 | | - 'type' => 'json_object', |
385 | | - ]; |
| 380 | + $this->structuredOutputMode = StructuredOutputMode::Json; |
386 | 381 | } |
387 | 382 |
|
388 | 383 | if ($this->toolConfig !== null) { |
389 | | - if (is_string($this->toolConfig->toolChoice)) { |
390 | | - $toolChoice = [ |
391 | | - 'type' => 'function', |
392 | | - 'function' => [ |
393 | | - 'name' => $this->toolConfig->toolChoice, |
394 | | - ], |
395 | | - ]; |
396 | | - } else { |
397 | | - $toolChoice = $this->toolConfig->toolChoice->value; |
398 | | - } |
| 384 | + $choice = $this->toolConfig->toolChoice; |
399 | 385 |
|
400 | | - $params['tool_choice'] = match ($toolChoice) { |
401 | | - ToolChoice::Required->value => 'any', |
402 | | - default => $toolChoice, |
| 386 | + $params['tool_choice'] = match (true) { |
| 387 | + is_string($choice) => [ |
| 388 | + 'type' => 'tool', |
| 389 | + 'name' => $choice, |
| 390 | + ], |
| 391 | + default => [ |
| 392 | + 'type' => match ($choice) { |
| 393 | + ToolChoice::Required => 'any', |
| 394 | + default => $choice, |
| 395 | + }, |
| 396 | + 'disable_parallel_tool_use' => ! $this->toolConfig->allowParallelToolCalls, |
| 397 | + ], |
403 | 398 | }; |
404 | 399 |
|
| 400 | + // TODO: add ProviderTool support for Anthropic e.g. web_search, etc. |
405 | 401 | $params['tools'] = collect($this->toolConfig->tools) |
406 | 402 | ->map(fn(Tool $tool): array => [ |
407 | | - 'type' => 'function', |
408 | | - 'function' => $tool->format(), |
| 403 | + 'type' => 'custom', |
| 404 | + 'name' => $tool->name(), |
| 405 | + 'description' => $tool->description(), |
| 406 | + 'input_schema' => $tool->schema()->additionalProperties(false)->toArray(), |
409 | 407 | ]) |
410 | 408 | ->toArray(); |
411 | 409 | } |
412 | 410 |
|
413 | | - // Ensure the usage information is returned when streaming |
414 | | - if ($this->streaming) { |
415 | | - $params['stream_options'] = [ |
416 | | - 'include_usage' => true, |
417 | | - ]; |
418 | | - } |
419 | | - |
420 | 411 | return [ |
421 | 412 | ...$params, |
422 | 413 | ...$this->parameters, |
|
0 commit comments