Skip to content

Commit babb3e1

Browse files
authored
feat: Chat Response - Add logprobs (#533)
* Chat Response: Add class for logprobs content * Chat response: add logprobs Only add content for now. * Chat response: add logprobs to CreateResponse Now that we have our classes for logprobs (CreateResponseChoiceLogprobs and CreateResponseChoiceLogbropsContent), let's actually add logprobs in the CreateResponseChoice / CreateResponse objects. Update types, fixtures, tests. * Update readme
1 parent a6849a5 commit babb3e1

11 files changed

+212
-6
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ foreach ($response->choices as $choice) {
177177
$choice->index; // 0
178178
$choice->message->role; // 'assistant'
179179
$choice->message->content; // '\n\nHello there! How can I assist you today?'
180+
$choice->logprobs; // null
180181
$choice->finishReason; // 'stop'
181182
}
182183

src/Resources/Chat.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function create(array $parameters): CreateResponse
2929

3030
$payload = Payload::create('chat/completions', $parameters);
3131

32-
/** @var Response<array{id: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: ?string, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}> $response */
32+
/** @var Response<array{id: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: ?string, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, logprobs: ?array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}> $response */
3333
$response = $this->transporter->requestObject($payload);
3434

3535
return CreateResponse::from($response->data(), $response->meta());

src/Responses/Chat/CreateResponse.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
use OpenAI\Testing\Responses\Concerns\Fakeable;
1313

1414
/**
15-
* @implements ResponseContract<array{id: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: string|null, function_call?: array{name: string, arguments: string}, tool_calls?: array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}>
15+
* @implements ResponseContract<array{id: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: string|null, function_call?: array{name: string, arguments: string}, tool_calls?: array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, logprobs: ?array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}>
1616
*/
1717
final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract
1818
{
1919
/**
20-
* @use ArrayAccessible<array{id: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: string|null, function_call?: array{name: string, arguments: string}, tool_calls?: array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}>
20+
* @use ArrayAccessible<array{id: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: string|null, function_call?: array{name: string, arguments: string}, tool_calls?: array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, logprobs: ?array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}>
2121
*/
2222
use ArrayAccessible;
2323

@@ -41,7 +41,7 @@ private function __construct(
4141
/**
4242
* Acts as static factory, and returns a new Response instance.
4343
*
44-
* @param array{id: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: ?string, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int, prompt_tokens_details?:array{cached_tokens:int}, completion_tokens_details?:array{audio_tokens?:int, reasoning_tokens:int, accepted_prediction_tokens:int, rejected_prediction_tokens:int}}} $attributes
44+
* @param array{id: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: ?string, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, logprobs: ?array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int, prompt_tokens_details?:array{cached_tokens:int}, completion_tokens_details?:array{audio_tokens?:int, reasoning_tokens:int, accepted_prediction_tokens:int, rejected_prediction_tokens:int}}} $attributes
4545
*/
4646
public static function from(array $attributes, MetaInformation $meta): self
4747
{

src/Responses/Chat/CreateResponseChoice.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,32 @@ final class CreateResponseChoice
99
private function __construct(
1010
public readonly int $index,
1111
public readonly CreateResponseMessage $message,
12+
public readonly ?CreateResponseChoiceLogprobs $logprobs,
1213
public readonly ?string $finishReason,
1314
) {}
1415

1516
/**
16-
* @param array{index: int, message: array{role: string, content: ?string, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, finish_reason: string|null} $attributes
17+
* @param array{index: int, message: array{role: string, content: ?string, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, logprobs: ?array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}, finish_reason: string|null} $attributes
1718
*/
1819
public static function from(array $attributes): self
1920
{
2021
return new self(
2122
$attributes['index'],
2223
CreateResponseMessage::from($attributes['message']),
24+
$attributes['logprobs'] ? CreateResponseChoiceLogprobs::from($attributes['logprobs']) : null,
2325
$attributes['finish_reason'] ?? null,
2426
);
2527
}
2628

2729
/**
28-
* @return array{index: int, message: array{role: string, content: string|null, function_call?: array{name: string, arguments: string}, tool_calls?: array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, finish_reason: string|null}
30+
* @return array{index: int, message: array{role: string, content: string|null, function_call?: array{name: string, arguments: string}, tool_calls?: array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, logprobs: ?array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}, finish_reason: string|null}
2931
*/
3032
public function toArray(): array
3133
{
3234
return [
3335
'index' => $this->index,
3436
'message' => $this->message->toArray(),
37+
'logprobs' => $this->logprobs?->toArray(),
3538
'finish_reason' => $this->finishReason,
3639
];
3740
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenAI\Responses\Chat;
6+
7+
final class CreateResponseChoiceLogprobs
8+
{
9+
/**
10+
* @param ?array<int, CreateResponseChoiceLogprobsContent> $content
11+
*/
12+
private function __construct(
13+
public readonly ?array $content,
14+
) {}
15+
16+
/**
17+
* @param array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>} $attributes
18+
*/
19+
public static function from(array $attributes): self
20+
{
21+
$content = null;
22+
if (isset($attributes['content'])) {
23+
$content = array_map(fn (array $result): CreateResponseChoiceLogprobsContent => CreateResponseChoiceLogprobsContent::from(
24+
$result
25+
), $attributes['content']);
26+
}
27+
28+
return new self(
29+
$content,
30+
);
31+
}
32+
33+
/**
34+
* @return array{content: ?array<int, array{token: string, logprob: float, bytes: ?array<int, int>}>}
35+
*/
36+
public function toArray(): array
37+
{
38+
return [
39+
'content' => $this->content ? array_map(
40+
static fn (CreateResponseChoiceLogprobsContent $result): array => $result->toArray(),
41+
$this->content,
42+
) : null,
43+
];
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenAI\Responses\Chat;
6+
7+
final class CreateResponseChoiceLogprobsContent
8+
{
9+
/**
10+
* @param ?array<int, int> $bytes
11+
*/
12+
private function __construct(
13+
public readonly string $token,
14+
public readonly float $logprob,
15+
public readonly ?array $bytes,
16+
) {}
17+
18+
/**
19+
* @param array{
20+
* token: string,
21+
* logprob: float,
22+
* bytes: ?array<int, int>
23+
* } $attributes
24+
*/
25+
public static function from(array $attributes): self
26+
{
27+
return new self(
28+
$attributes['token'],
29+
$attributes['logprob'],
30+
$attributes['bytes'],
31+
);
32+
}
33+
34+
/**
35+
* @return array{
36+
* token: string,
37+
* logprob: float,
38+
* bytes: ?array<int, int>
39+
* }
40+
*/
41+
public function toArray(): array
42+
{
43+
return [
44+
'token' => $this->token,
45+
'logprob' => $this->logprob,
46+
'bytes' => $this->bytes,
47+
];
48+
}
49+
}

src/Testing/Responses/Fixtures/Chat/CreateResponseFixture.php

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ final class CreateResponseFixture
1919
'function_call' => null,
2020
'tool_calls' => [],
2121
],
22+
'logprobs' => null,
2223
'finish_reason' => 'stop',
2324
],
2425
],

tests/Fixtures/Chat.php

+47
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ function chatCompletion(): array
1717
'role' => 'assistant',
1818
'content' => "\n\nHello there, how may I assist you today?",
1919
],
20+
'logprobs' => null,
2021
'finish_reason' => 'stop',
2122
],
2223
],
@@ -36,6 +37,48 @@ function chatCompletion(): array
3637
];
3738
}
3839

40+
/**
41+
* @return array<string, mixed>
42+
*/
43+
function chatCompletionWithLogprobs(): array
44+
{
45+
return [
46+
'id' => 'chatcmpl-123',
47+
'object' => 'chat.completion',
48+
'created' => 1677652288,
49+
'model' => 'gpt-3.5-turbo',
50+
'choices' => [
51+
[
52+
'index' => 0,
53+
'message' => [
54+
'role' => 'assistant',
55+
'content' => 'Hello!',
56+
],
57+
'logprobs' => [
58+
'content' => [
59+
[
60+
'token' => 'Hello',
61+
'logprob' => 0.0,
62+
'bytes' => [72, 101, 108, 108, 111],
63+
],
64+
[
65+
'token' => '!',
66+
'logprob' => -0.0005715019651688635,
67+
'bytes' => [33],
68+
],
69+
],
70+
],
71+
'finish_reason' => 'stop',
72+
],
73+
],
74+
'usage' => [
75+
'prompt_tokens' => 18,
76+
'completion_tokens' => 3,
77+
'total_tokens' => 21,
78+
],
79+
];
80+
}
81+
3982
/**
4083
* @return array<string, mixed>
4184
*/
@@ -54,6 +97,7 @@ function chatCompletionWithSystemFingerprint(): array
5497
'role' => 'assistant',
5598
'content' => "\n\nHello there, how may I assist you today?",
5699
],
100+
'logprobs' => null,
57101
'finish_reason' => 'stop',
58102
],
59103
],
@@ -86,6 +130,7 @@ function chatCompletionWithFunction(): array
86130
'arguments' => "{\n \"location\": \"Boston, MA\"\n}",
87131
],
88132
],
133+
'logprobs' => null,
89134
'finish_reason' => 'function_call',
90135
],
91136
],
@@ -124,6 +169,7 @@ function chatCompletionWithToolCalls(): array
124169
],
125170
],
126171
],
172+
'logprobs' => null,
127173
'finish_reason' => 'tool_calls',
128174
],
129175
],
@@ -166,6 +212,7 @@ function chatCompletionFromVision(): array
166212
'role' => 'assistant',
167213
'content' => 'The image shows a beautiful, tranquil natural landscape. A wooden boardwalk path stretches',
168214
],
215+
'logprobs' => null,
169216
],
170217
],
171218
'usage' => [

tests/Responses/Chat/CreateResponseChoice.php

+20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use OpenAI\Responses\Chat\CreateResponseChoice;
4+
use OpenAI\Responses\Chat\CreateResponseChoiceLogprobs;
45
use OpenAI\Responses\Chat\CreateResponseMessage;
56

67
test('from', function () {
@@ -9,6 +10,17 @@
910
expect($result)
1011
->index->toBe(0)
1112
->message->toBeInstanceOf(CreateResponseMessage::class)
13+
->logprobs->toBeNull()
14+
->finishReason->toBeIn(['stop', null]);
15+
});
16+
17+
test('from with logprobs', function () {
18+
$result = CreateResponseChoice::from(chatCompletionWithLogprobs()['choices'][0]);
19+
20+
expect($result)
21+
->index->toBe(0)
22+
->message->toBeInstanceOf(CreateResponseMessage::class)
23+
->logprobs->toBeInstanceOf(CreateResponseChoiceLogprobs::class)
1224
->finishReason->toBeIn(['stop', null]);
1325
});
1426

@@ -18,6 +30,7 @@
1830
expect($result)
1931
->index->toBe(0)
2032
->message->toBeInstanceOf(CreateResponseMessage::class)
33+
->logprobs->toBeNull()
2134
->finishReason->toBeNull();
2235
});
2336

@@ -27,3 +40,10 @@
2740
expect($result->toArray())
2841
->toBe(chatCompletion()['choices'][0]);
2942
});
43+
44+
test('to array with logprobs', function () {
45+
$result = CreateResponseChoice::from(chatCompletionWithLogprobs()['choices'][0]);
46+
47+
expect($result->toArray())
48+
->toBe(chatCompletionWithLogprobs()['choices'][0]);
49+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
use OpenAI\Responses\Chat\CreateResponseChoiceLogprobs;
4+
use OpenAI\Responses\Chat\CreateResponseChoiceLogprobsContent;
5+
6+
test('from', function () {
7+
$result = CreateResponseChoiceLogprobs::from(chatCompletionWithLogprobs()['choices'][0]['logprobs']);
8+
9+
expect($result)
10+
->toBeInstanceOf(CreateResponseChoiceLogprobs::class)
11+
->content->toBeArray()
12+
->content->toHaveCount(2)
13+
->content->each->toBeInstanceOf(CreateResponseChoiceLogprobsContent::class);
14+
});
15+
16+
test('to array', function () {
17+
$result = CreateResponseChoiceLogprobs::from(chatCompletionWithLogprobs()['choices'][0]['logprobs']);
18+
19+
expect($result->toArray())
20+
->toBe(chatCompletionWithLogprobs()['choices'][0]['logprobs']);
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
use OpenAI\Responses\Chat\CreateResponseChoiceLogprobsContent;
4+
5+
test('from', function () {
6+
$result = CreateResponseChoiceLogprobsContent::from(chatCompletionWithLogprobs()['choices'][0]['logprobs']['content'][0]);
7+
8+
expect($result)
9+
->token->toBe('Hello')
10+
->logprob->toBe(0.0)
11+
->bytes->toBe([72, 101, 108, 108, 111]);
12+
});
13+
14+
test('to array', function () {
15+
$result = CreateResponseChoiceLogprobsContent::from(chatCompletionWithLogprobs()['choices'][0]['logprobs']['content'][0]);
16+
17+
expect($result->toArray())
18+
->toBe(chatCompletionWithLogprobs()['choices'][0]['logprobs']['content'][0]);
19+
});

0 commit comments

Comments
 (0)