Skip to content

Commit 2eefc52

Browse files
committed
update tests
1 parent fea5c1a commit 2eefc52

File tree

6 files changed

+185
-49
lines changed

6 files changed

+185
-49
lines changed

src/Agents/Agent.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class Agent implements Pipeable
8383
* @param array<int, \Cortex\LLM\Contracts\Tool|\Closure|string>|\Cortex\Contracts\ToolKit $tools
8484
* @param array<string, mixed> $initialPromptVariables
8585
* @param array<int, BeforePromptMiddleware|BeforeModelMiddleware|AfterModelMiddleware> $middleware
86+
* @param array<string, mixed> $metadata
8687
*/
8788
public function __construct(
8889
protected string $id,

src/SDK/Anthropic/Resources/Messages.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public function stream(array $parameters, bool $cacheEnabled = false): Response
5858
], $cacheEnabled);
5959
}
6060

61+
/**
62+
* @param array<string, mixed> $parameters
63+
*/
6164
public function countTokens(array $parameters, bool $cacheEnabled = false): int
6265
{
6366
$request = new CountTokens(

src/SDK/OpenAI/Data/Responses/OutputItems/FunctionToolCallOutputItem.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ final class FunctionToolCallOutputItem implements OutputItem
1212

1313
/**
1414
* @param 'in_progress'|'completed'|'incomplete' $status
15-
* @param array<string, mixed> $arguments
1615
*/
1716
public function __construct(
1817
public string $id,
1918
public string $name,
20-
public array $arguments,
19+
public string $arguments,
2120
public string $callId,
2221
public string $status,
2322
) {}
@@ -27,7 +26,7 @@ public static function from(array $payload): self
2726
return new self(
2827
id: $payload['id'],
2928
name: $payload['name'],
30-
arguments: json_decode($payload['arguments'], true, flags: JSON_THROW_ON_ERROR),
29+
arguments: $payload['arguments'],
3130
callId: $payload['call_id'],
3231
status: $payload['status'],
3332
);

tests/Unit/SDK/OpenAI/OpenAIResponsesTest.php

Lines changed: 125 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,18 @@
1616
use Cortex\SDK\OpenAI\Data\Responses\Message\OutputText;
1717
use Cortex\SDK\OpenAI\Data\Responses\OutputItems\MessageOutputItem;
1818
use Cortex\SDK\OpenAI\Data\Responses\OutputItems\ReasoningOutputItem;
19-
use Cortex\SDK\OpenAI\Data\Responses\OutputItems\FunctionToolCallOutputItem;
2019
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseCreated;
2120
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseCompleted;
2221
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseInProgress;
22+
use Cortex\SDK\OpenAI\Data\Responses\OutputItems\FunctionToolCallOutputItem;
2323
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseOutputItemDone;
2424
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseOutputTextDone;
2525
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseContentPartDone;
2626
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseOutputItemAdded;
2727
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseOutputTextDelta;
2828
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseContentPartAdded;
29-
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseReasoningTextDelta;
30-
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseReasoningTextDone;
31-
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseReasoningContentPartAdded;
32-
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseFunctionCallArgumentsDelta;
3329
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseFunctionCallArgumentsDone;
30+
use Cortex\SDK\OpenAI\Data\Responses\Streaming\Events\ResponseFunctionCallArgumentsDelta;
3431

3532
describe('Non-streaming', function (): void {
3633
test('it can create a response', function (): void {
@@ -139,16 +136,18 @@
139136
->and($response->output[0]->name)->toBe('get_weather')
140137
->and($response->output[0]->status)->toBe('completed')
141138
->and($response->output[0]->callId)->toBe('call_sXyT5eGTVisSbFWEBSjquORK')
142-
->and($response->output[0]->arguments)->toBeArray()
143-
->and($response->output[0]->arguments)->toHaveKey('location')
144-
->and($response->output[0]->arguments['location'])->toBe('Manchester, UK')
145-
->and($response->output[0]->arguments)->toHaveKey('unit')
146-
->and($response->output[0]->arguments['unit'])->toBe('celsius')
139+
->and($response->output[0]->arguments)->toBe('{"location":"Manchester, UK","unit":"celsius"}')
147140
->and($response->usage)->toBeInstanceOf(Usage::class)
148141
->and($response->usage->inputTokens)->toBe(86)
149142
->and($response->usage->outputTokens)->toBe(22)
150143
->and($response->usage->totalTokens)->toBe(108);
151144

145+
// Verify the arguments can be decoded
146+
$arguments = json_decode((string) $response->output[0]->arguments, true);
147+
expect($arguments)->toBeArray()
148+
->and($arguments['location'])->toBe('Manchester, UK')
149+
->and($arguments['unit'])->toBe('celsius');
150+
152151
MockClient::getGlobal()->assertSentCount(1, CreateResponse::class);
153152
});
154153

@@ -168,10 +167,18 @@
168167
'schema' => [
169168
'type' => 'object',
170169
'properties' => [
171-
'name' => ['type' => 'string'],
172-
'email' => ['type' => 'string'],
173-
'plan_interest' => ['type' => 'string'],
174-
'demo_requested' => ['type' => 'boolean'],
170+
'name' => [
171+
'type' => 'string',
172+
],
173+
'email' => [
174+
'type' => 'string',
175+
],
176+
'plan_interest' => [
177+
'type' => 'string',
178+
],
179+
'demo_requested' => [
180+
'type' => 'boolean',
181+
],
175182
],
176183
'required' => ['name', 'email', 'plan_interest', 'demo_requested'],
177184
'additionalProperties' => false,
@@ -197,7 +204,7 @@
197204
->and($response->usage->totalTokens)->toBe(111);
198205

199206
// Verify the JSON can be decoded and has expected structure
200-
$decoded = json_decode($response->output[0]->content[0]->text, true);
207+
$decoded = json_decode((string) $response->output[0]->content[0]->text, true);
201208

202209
expect($decoded)->toBeArray()
203210
->and($decoded['name'])->toBe('John Smith')
@@ -359,24 +366,51 @@
359366

360367
$events = iterator_to_array($response->getIterator());
361368

362-
expect($events)->toBeArray()
369+
expect($events)->toHaveCount(18)
363370
->and($events[0])->toBeInstanceOf(ResponseCreated::class)
364-
->and($events[1])->toBeInstanceOf(ResponseInProgress::class);
371+
->and($events[1])->toBeInstanceOf(ResponseInProgress::class)
365372

366-
// Check for function call related events
367-
$functionCallArgumentsDeltaEvents = array_filter($events, fn ($event) => $event instanceof ResponseFunctionCallArgumentsDelta);
368-
expect($functionCallArgumentsDeltaEvents)->not->toBeEmpty();
373+
// Function call output item added (in progress with empty arguments)
374+
->and($events[2])->toBeInstanceOf(ResponseOutputItemAdded::class)
375+
->and($events[2]->item)->toBeInstanceOf(FunctionToolCallOutputItem::class)
376+
->and($events[2]->item->id)->toBe('fc_0f51f16555c4726b00696f40ef8d6c819a9fd0c583afa621fa')
377+
->and($events[2]->item->name)->toBe('get_weather')
378+
->and($events[2]->item->callId)->toBe('call_mpisVwMpENDmDdyLFejlSBkS')
379+
->and($events[2]->item->status)->toBe('in_progress')
380+
->and($events[2]->item->arguments)->toBe('')
381+
382+
// Function call arguments deltas
383+
->and($events[3])->toBeInstanceOf(ResponseFunctionCallArgumentsDelta::class)
384+
->and($events[3]->delta)->toBe('{"')
385+
->and($events[3]->itemId)->toBe('fc_0f51f16555c4726b00696f40ef8d6c819a9fd0c583afa621fa')
386+
->and($events[4])->toBeInstanceOf(ResponseFunctionCallArgumentsDelta::class)
387+
->and($events[4]->delta)->toBe('location')
388+
->and($events[6])->toBeInstanceOf(ResponseFunctionCallArgumentsDelta::class)
389+
->and($events[6]->delta)->toBe('Manchester')
390+
391+
// Function call arguments done
392+
->and($events[15])->toBeInstanceOf(ResponseFunctionCallArgumentsDone::class)
393+
->and($events[15]->arguments)->toBe('{"location":"Manchester, UK","unit":"celsius"}')
394+
->and($events[15]->itemId)->toBe('fc_0f51f16555c4726b00696f40ef8d6c819a9fd0c583afa621fa')
369395

370-
$functionCallArgumentsDoneEvents = array_filter($events, fn ($event) => $event instanceof ResponseFunctionCallArgumentsDone);
371-
expect($functionCallArgumentsDoneEvents)->not->toBeEmpty();
396+
// Output item done
397+
->and($events[16])->toBeInstanceOf(ResponseOutputItemDone::class)
398+
->and($events[16]->item)->toBeInstanceOf(FunctionToolCallOutputItem::class)
399+
->and($events[16]->item->id)->toBe('fc_0f51f16555c4726b00696f40ef8d6c819a9fd0c583afa621fa')
400+
->and($events[16]->item->name)->toBe('get_weather')
401+
->and($events[16]->item->callId)->toBe('call_mpisVwMpENDmDdyLFejlSBkS')
402+
->and($events[16]->item->status)->toBe('completed')
403+
->and($events[16]->item->arguments)->toBe('{"location":"Manchester, UK","unit":"celsius"}')
372404

373-
// Check that we have a function call output item
374-
$functionCallOutputItemEvents = array_filter($events, fn ($event) => $event instanceof ResponseOutputItemAdded && $event->item instanceof FunctionToolCallOutputItem);
375-
expect($functionCallOutputItemEvents)->not->toBeEmpty();
405+
// Completed
406+
->and($events[17])->toBeInstanceOf(ResponseCompleted::class);
376407

377-
// Last event should be ResponseCompleted
378-
expect(end($events))->toBeInstanceOf(ResponseCompleted::class);
379-
})->todo();
408+
// Verify the arguments can be decoded
409+
$arguments = json_decode((string) $events[16]->item->arguments, true);
410+
expect($arguments)->toBeArray()
411+
->and($arguments['location'])->toBe('Manchester, UK')
412+
->and($arguments['unit'])->toBe('celsius');
413+
});
380414

381415
test('it can create a streamed response with structured output', function (): void {
382416
$openai = OpenAI::fake([
@@ -388,21 +422,29 @@
388422
'model' => 'gpt-4o-mini',
389423
'max_output_tokens' => 1024,
390424
'text' => [
391-
'type' => 'json_schema',
392-
'json_schema' => [
425+
'format' => [
426+
'type' => 'json_schema',
393427
'name' => 'contact_info',
394-
'strict' => true,
395428
'schema' => [
396429
'type' => 'object',
397430
'properties' => [
398-
'name' => ['type' => 'string'],
399-
'email' => ['type' => 'string'],
400-
'plan_interest' => ['type' => 'string'],
401-
'demo_requested' => ['type' => 'boolean'],
431+
'name' => [
432+
'type' => 'string',
433+
],
434+
'email' => [
435+
'type' => 'string',
436+
],
437+
'plan_interest' => [
438+
'type' => 'string',
439+
],
440+
'demo_requested' => [
441+
'type' => 'boolean',
442+
],
402443
],
403444
'required' => ['name', 'email', 'plan_interest', 'demo_requested'],
404445
'additionalProperties' => false,
405446
],
447+
'strict' => true,
406448
],
407449
],
408450
'input' => '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.',
@@ -413,19 +455,56 @@
413455

414456
$events = iterator_to_array($response->getIterator());
415457

416-
expect($events)->toBeArray()
458+
expect($events)->toHaveCount(30)
417459
->and($events[0])->toBeInstanceOf(ResponseCreated::class)
418-
->and($events[1])->toBeInstanceOf(ResponseInProgress::class);
460+
->and($events[1])->toBeInstanceOf(ResponseInProgress::class)
461+
462+
// Message output item added
463+
->and($events[2])->toBeInstanceOf(ResponseOutputItemAdded::class)
464+
->and($events[2]->item)->toBeInstanceOf(MessageOutputItem::class)
465+
->and($events[2]->item->id)->toBe('msg_0f76877a6d87a6af00696f42cf11e4819b914a39fff034fd50')
466+
467+
// Content part added
468+
->and($events[3])->toBeInstanceOf(ResponseContentPartAdded::class)
469+
->and($events[3]->part)->toBeInstanceOf(OutputText::class)
470+
471+
// Text deltas building JSON
472+
->and($events[4])->toBeInstanceOf(ResponseOutputTextDelta::class)
473+
->and($events[4]->delta)->toBe('{"')
474+
->and($events[5])->toBeInstanceOf(ResponseOutputTextDelta::class)
475+
->and($events[5]->delta)->toBe('name')
476+
->and($events[7])->toBeInstanceOf(ResponseOutputTextDelta::class)
477+
->and($events[7]->delta)->toBe('John')
478+
->and($events[19])->toBeInstanceOf(ResponseOutputTextDelta::class)
479+
->and($events[19]->delta)->toBe('Enterprise')
419480

420-
// Check for text delta events (structured output comes as text)
421-
$textDeltaEvents = array_filter($events, fn ($event) => $event instanceof ResponseOutputTextDelta);
422-
expect($textDeltaEvents)->not->toBeEmpty();
481+
// Text done
482+
->and($events[26])->toBeInstanceOf(ResponseOutputTextDone::class)
483+
->and($events[26]->text)->toBe('{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}')
423484

424-
// Check that we have a message output item
425-
$messageOutputItemEvents = array_filter($events, fn ($event) => $event instanceof ResponseOutputItemAdded && $event->item instanceof MessageOutputItem);
426-
expect($messageOutputItemEvents)->not->toBeEmpty();
485+
// Content part done
486+
->and($events[27])->toBeInstanceOf(ResponseContentPartDone::class)
487+
->and($events[27]->part)->toBeArray()
488+
->and($events[27]->part['text'])->toBe('{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}')
427489

428-
// Last event should be ResponseCompleted
429-
expect(end($events))->toBeInstanceOf(ResponseCompleted::class);
430-
})->todo();
490+
// Output item done
491+
->and($events[28])->toBeInstanceOf(ResponseOutputItemDone::class)
492+
->and($events[28]->item)->toBeInstanceOf(MessageOutputItem::class)
493+
->and($events[28]->item->id)->toBe('msg_0f76877a6d87a6af00696f42cf11e4819b914a39fff034fd50')
494+
->and($events[28]->item->status)->toBe('completed')
495+
->and($events[28]->item->content)->toHaveCount(1)
496+
->and($events[28]->item->content[0])->toBeInstanceOf(OutputText::class)
497+
->and($events[28]->item->content[0]->text)->toBe('{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}')
498+
499+
// Completed
500+
->and($events[29])->toBeInstanceOf(ResponseCompleted::class);
501+
502+
// Verify the JSON can be decoded and has expected structure
503+
$decoded = json_decode((string) $events[28]->item->content[0]->text, true);
504+
expect($decoded)->toBeArray()
505+
->and($decoded['name'])->toBe('John Smith')
506+
->and($decoded['email'])->toBe('john@example.com')
507+
->and($decoded['plan_interest'])->toBe('Enterprise')
508+
->and($decoded['demo_requested'])->toBeTrue();
509+
});
431510
});

0 commit comments

Comments
 (0)