Skip to content

Commit dfde61c

Browse files
committed
Merge prism-php#1000 — fix(gemini): add Usage to StepFinishEvent
2 parents 834c15f + 5d823ac commit dfde61c

5 files changed

Lines changed: 110 additions & 4 deletions

File tree

src/Providers/Gemini/Handlers/Stream.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,8 @@ protected function processStream(Response $response, Request $request, int $dept
241241
$this->state->markStepFinished();
242242
yield new StepFinishEvent(
243243
id: EventID::generate(),
244-
timestamp: time()
244+
timestamp: time(),
245+
usage: $this->state->usage(),
245246
);
246247

247248
yield $this->emitStreamEndEvent();
@@ -341,7 +342,8 @@ protected function handleToolCalls(
341342
$this->state->markStepFinished();
342343
yield new StepFinishEvent(
343344
id: EventID::generate(),
344-
timestamp: time()
345+
timestamp: time(),
346+
usage: $this->state->usage(),
345347
);
346348

347349
$request->addMessage(new AssistantMessage($this->state->currentText(), $mappedToolCalls));

src/Streaming/Events/StepFinishEvent.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@
55
namespace Prism\Prism\Streaming\Events;
66

77
use Prism\Prism\Enums\StreamEventType;
8+
use Prism\Prism\ValueObjects\Usage;
89

910
readonly class StepFinishEvent extends StreamEvent
1011
{
12+
public function __construct(
13+
string $id,
14+
int $timestamp,
15+
public ?Usage $usage = null, // Token usage information
16+
) {
17+
parent::__construct($id, $timestamp);
18+
}
19+
1120
public function type(): StreamEventType
1221
{
1322
return StreamEventType::StepFinish;
@@ -18,6 +27,13 @@ public function toArray(): array
1827
return [
1928
'id' => $this->id,
2029
'timestamp' => $this->timestamp,
30+
'usage' => $this->usage instanceof Usage ? [
31+
'prompt_tokens' => $this->usage->promptTokens,
32+
'completion_tokens' => $this->usage->completionTokens,
33+
'cache_write_input_tokens' => $this->usage->cacheWriteInputTokens,
34+
'cache_read_input_tokens' => $this->usage->cacheReadInputTokens,
35+
'thought_tokens' => $this->usage->thoughtTokens,
36+
] : null,
2137
];
2238
}
2339
}

src/Testing/PrismFake.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,8 @@ protected function streamEventsFromTextResponse(TextResponse $response, TextRequ
336336

337337
yield new StepFinishEvent(
338338
id: EventID::generate(),
339-
timestamp: time()
339+
timestamp: time(),
340+
usage: $response->usage
340341
);
341342

342343
yield new StreamEndEvent(

tests/Providers/Gemini/GeminiStreamTest.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,14 @@
310310
expect($stepStartEvents)->toHaveCount(1);
311311

312312
// Check for StepFinishEvent before StreamEndEvent
313-
$stepFinishEvents = array_filter($events, fn (StreamEvent $e): bool => $e instanceof StepFinishEvent);
313+
$stepFinishEvents = array_values(array_filter($events, fn (StreamEvent $e): bool => $e instanceof StepFinishEvent));
314314
expect($stepFinishEvents)->toHaveCount(1);
315315

316+
// Verify StepFinishEvent contains usage data
317+
expect($stepFinishEvents[0]->usage)->not->toBeNull();
318+
expect($stepFinishEvents[0]->usage->promptTokens)->toBe(21);
319+
expect($stepFinishEvents[0]->usage->completionTokens)->toBe(47);
320+
316321
// Verify order: StreamStart -> StepStart -> ... -> StepFinish -> StreamEnd
317322
$eventTypes = array_map(get_class(...), $events);
318323
$streamStartIndex = array_search(StreamStartEvent::class, $eventTypes);

tests/Streaming/PrismStreamIntegrationTest.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,4 +1011,86 @@
10111011
expect($array)->toHaveKey('timestamp');
10121012
}
10131013
});
1014+
1015+
it('step finish event has default zero usage when fake response has no explicit usage', function (): void {
1016+
Prism::fake([
1017+
TextResponseFake::make()->withText('Test'),
1018+
]);
1019+
1020+
$events = iterator_to_array(
1021+
Prism::text()
1022+
->using('openai', 'gpt-4')
1023+
->withPrompt('Test')
1024+
->asStream()
1025+
);
1026+
1027+
$stepFinishEvents = array_values(array_filter(
1028+
$events,
1029+
fn (StreamEvent $e): bool => $e instanceof StepFinishEvent
1030+
));
1031+
1032+
expect($stepFinishEvents)->not->toBeEmpty();
1033+
expect($stepFinishEvents[0]->usage)->not->toBeNull();
1034+
expect($stepFinishEvents[0]->usage->promptTokens)->toBe(0);
1035+
expect($stepFinishEvents[0]->usage->completionTokens)->toBe(0);
1036+
});
1037+
1038+
it('step finish event contains usage when fake response has usage', function (): void {
1039+
Prism::fake([
1040+
TextResponseFake::make()
1041+
->withText('Test')
1042+
->withUsage(new Usage(100, 50)),
1043+
]);
1044+
1045+
$events = iterator_to_array(
1046+
Prism::text()
1047+
->using('openai', 'gpt-4')
1048+
->withPrompt('Test')
1049+
->asStream()
1050+
);
1051+
1052+
$stepFinishEvents = array_values(array_filter(
1053+
$events,
1054+
fn (StreamEvent $e): bool => $e instanceof StepFinishEvent
1055+
));
1056+
1057+
expect($stepFinishEvents)->not->toBeEmpty();
1058+
expect($stepFinishEvents[0]->usage)->not->toBeNull();
1059+
expect($stepFinishEvents[0]->usage->promptTokens)->toBe(100);
1060+
expect($stepFinishEvents[0]->usage->completionTokens)->toBe(50);
1061+
});
1062+
1063+
it('step finish event toArray includes usage when set', function (): void {
1064+
Prism::fake([
1065+
TextResponseFake::make()
1066+
->withText('Test')
1067+
->withUsage(new Usage(20, 10)),
1068+
]);
1069+
1070+
$events = iterator_to_array(
1071+
Prism::text()
1072+
->using('openai', 'gpt-4')
1073+
->withPrompt('Test')
1074+
->asStream()
1075+
);
1076+
1077+
$stepFinishEvents = array_values(array_filter(
1078+
$events,
1079+
fn (StreamEvent $e): bool => $e instanceof StepFinishEvent
1080+
));
1081+
1082+
expect($stepFinishEvents)->not->toBeEmpty();
1083+
$array = $stepFinishEvents[0]->toArray();
1084+
expect($array)->toHaveKey('usage');
1085+
expect($array['usage'])->not->toBeNull();
1086+
expect($array['usage']['prompt_tokens'])->toBe(20);
1087+
expect($array['usage']['completion_tokens'])->toBe(10);
1088+
});
1089+
1090+
it('step finish event toArray has null usage when event is constructed without usage', function (): void {
1091+
$event = new StepFinishEvent(id: 'test-id', timestamp: 1234567890);
1092+
$array = $event->toArray();
1093+
expect($array)->toHaveKey('usage');
1094+
expect($array['usage'])->toBeNull();
1095+
});
10141096
});

0 commit comments

Comments
 (0)