Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add openai compatibility support for google gemini #502

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/Responses/Chat/CreateResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use OpenAI\Testing\Responses\Concerns\Fakeable;

/**
* @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}}>
* @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}}>
*/
final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract
{
Expand All @@ -28,20 +28,20 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati
* @param array<int, CreateResponseChoice> $choices
*/
private function __construct(
public readonly string $id,
public readonly ?string $id,
public readonly string $object,
public readonly int $created,
public readonly string $model,
public readonly ?string $systemFingerprint,
public readonly array $choices,
public readonly CreateResponseUsage $usage,
public readonly ?CreateResponseUsage $usage,
private readonly MetaInformation $meta,
) {}

/**
* Acts as static factory, and returns a new Response instance.
*
* @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
* @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
*/
public static function from(array $attributes, MetaInformation $meta): self
{
Expand All @@ -50,13 +50,13 @@ public static function from(array $attributes, MetaInformation $meta): self
), $attributes['choices']);

return new self(
$attributes['id'],
$attributes['id'] ?? null,
$attributes['object'],
$attributes['created'],
$attributes['model'],
$attributes['system_fingerprint'] ?? null,
$choices,
CreateResponseUsage::from($attributes['usage']),
isset($attributes['usage']) ? CreateResponseUsage::from($attributes['usage']) : null,
$meta,
);
}
Expand All @@ -76,7 +76,7 @@ public function toArray(): array
static fn (CreateResponseChoice $result): array => $result->toArray(),
$this->choices,
),
'usage' => $this->usage->toArray(),
'usage' => $this->usage?->toArray(),
], fn (mixed $value): bool => ! is_null($value));
}
}
19 changes: 7 additions & 12 deletions src/Responses/Chat/CreateStreamedResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use OpenAI\Testing\Responses\Concerns\FakeableForStreamedResponse;

/**
* @implements ResponseContract<array{id: string, object: string, created: int, model: string, choices: array<int, array{index: int, delta: array{role?: string, content?: string}|array{role?: string, content: null, function_call: array{name?: string, arguments?: string}}, finish_reason: string|null}>, usage?: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}>
* @implements ResponseContract<array{id?: string, object: string, created: int, model: string, choices: array<int, array{index: int, delta: array{role?: string, content?: string}|array{role?: string, content: null, function_call: array{name?: string, arguments?: string}}, finish_reason: string|null}>, usage?: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}>
*/
final class CreateStreamedResponse implements ResponseContract
{
Expand All @@ -24,7 +24,7 @@ final class CreateStreamedResponse implements ResponseContract
* @param array<int, CreateStreamedResponseChoice> $choices
*/
private function __construct(
public readonly string $id,
public readonly ?string $id,
public readonly string $object,
public readonly int $created,
public readonly string $model,
Expand All @@ -35,7 +35,7 @@ private function __construct(
/**
* Acts as static factory, and returns a new Response instance.
*
* @param array{id: string, object: string, created: int, model: string, choices: array<int, array{index: int, delta: array{role?: string, content?: string}, finish_reason: string|null}>, usage?: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}} $attributes
* @param array{id?: string, object: string, created: int, model: string, choices: array<int, array{index: int, delta: array{role?: string, content?: string}, finish_reason: string|null}>, usage?: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}} $attributes
*/
public static function from(array $attributes): self
{
Expand All @@ -44,7 +44,7 @@ public static function from(array $attributes): self
), $attributes['choices']);

return new self(
$attributes['id'],
$attributes['id'] ?? null,
$attributes['object'],
$attributes['created'],
$attributes['model'],
Expand All @@ -58,7 +58,7 @@ public static function from(array $attributes): self
*/
public function toArray(): array
{
$data = [
return array_filter([
'id' => $this->id,
'object' => $this->object,
'created' => $this->created,
Expand All @@ -67,12 +67,7 @@ public function toArray(): array
static fn (CreateStreamedResponseChoice $result): array => $result->toArray(),
$this->choices,
),
];

if ($this->usage instanceof \OpenAI\Responses\Chat\CreateResponseUsage) {
$data['usage'] = $this->usage->toArray();
}

return $data;
'usage' => $this->usage?->toArray(),
], fn (mixed $value): bool => ! is_null($value));
}
}
16 changes: 8 additions & 8 deletions src/Responses/Embeddings/CreateResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
use OpenAI\Testing\Responses\Concerns\Fakeable;

/**
* @implements ResponseContract<array{object: string, data: array<int, array{object: string, embedding: array<int, float>, index: int}>, usage: array{prompt_tokens: int, total_tokens: int}}>
* @implements ResponseContract<array{object: string, data: array<int, array{object: string, embedding: array<int, float>, index?: int}>, usage?: array{prompt_tokens: int, total_tokens: int}}>
*/
final class CreateResponse implements ResponseContract, ResponseHasMetaInformationContract
{
/**
* @use ArrayAccessible<array{object: string, data: array<int, array{object: string, embedding: array<int, float>, index: int}>, usage: array{prompt_tokens: int, total_tokens: int}}>
* @use ArrayAccessible<array{object: string, data: array<int, array{object: string, embedding: array<int, float>, index?: int}>, usage?: array{prompt_tokens: int, total_tokens: int}}>
*/
use ArrayAccessible;

Expand All @@ -30,14 +30,14 @@ final class CreateResponse implements ResponseContract, ResponseHasMetaInformati
private function __construct(
public readonly string $object,
public readonly array $embeddings,
public readonly CreateResponseUsage $usage,
public readonly ?CreateResponseUsage $usage,
private readonly MetaInformation $meta,
) {}

/**
* Acts as static factory, and returns a new Response instance.
*
* @param array{object: string, data: array<int, array{object: string, embedding: array<int, float>, index: int}>, usage: array{prompt_tokens: int, total_tokens: int}} $attributes
* @param array{object: string, data: array<int, array{object: string, embedding: array<int, float>, index?: int}>, usage?: array{prompt_tokens: int, total_tokens: int}} $attributes
*/
public static function from(array $attributes, MetaInformation $meta): self
{
Expand All @@ -48,7 +48,7 @@ public static function from(array $attributes, MetaInformation $meta): self
return new self(
$attributes['object'],
$embeddings,
CreateResponseUsage::from($attributes['usage']),
isset($attributes['usage']) ? CreateResponseUsage::from($attributes['usage']) : null,
$meta,
);
}
Expand All @@ -58,13 +58,13 @@ public static function from(array $attributes, MetaInformation $meta): self
*/
public function toArray(): array
{
return [
return array_filter([
'object' => $this->object,
'data' => array_map(
static fn (CreateResponseEmbedding $result): array => $result->toArray(),
$this->embeddings,
),
'usage' => $this->usage->toArray(),
];
'usage' => $this->usage?->toArray(),
], fn (mixed $value): bool => ! is_null($value));
}
}
12 changes: 6 additions & 6 deletions src/Responses/Embeddings/CreateResponseEmbedding.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,31 @@ final class CreateResponseEmbedding
*/
private function __construct(
public readonly string $object,
public readonly int $index,
public readonly ?int $index,
public readonly array $embedding,
) {}

/**
* @param array{object: string, index: int, embedding: array<int, float>} $attributes
* @param array{object: string, index?: int, embedding: array<int, float>} $attributes
*/
public static function from(array $attributes): self
{
return new self(
$attributes['object'],
$attributes['index'],
$attributes['index'] ?? null,
$attributes['embedding'],
);
}

/**
* @return array{object: string, index: int, embedding: array<int, float>}
* @return array{object: string, index?: int, embedding: array<int, float>}
*/
public function toArray(): array
{
return [
return array_filter([
'object' => $this->object,
'index' => $this->index,
'embedding' => $this->embedding,
];
], fn (mixed $value): bool => ! is_null($value));
}
}
76 changes: 76 additions & 0 deletions tests/Fixtures/Chat.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,64 @@ function chatCompletion(): array
];
}

/**
* @return array<string, mixed>
*/
function chatCompletionWithoutId(): array
{
return [
'object' => 'chat.completion',
'created' => 1677652288,
'model' => 'gpt-3.5-turbo',
'choices' => [
[
'index' => 0,
'message' => [
'role' => 'assistant',
'content' => "\n\nHello there, how may I assist you today?",
],
'finish_reason' => 'stop',
],
],
'usage' => [
'prompt_tokens' => 9,
'completion_tokens' => 12,
'total_tokens' => 21,
'prompt_tokens_details' => [
'cached_tokens' => 5,
],
'completion_tokens_details' => [
'reasoning_tokens' => 0,
'accepted_prediction_tokens' => 0,
'rejected_prediction_tokens' => 0,
],
],
];
}

/**
* @return array<string, mixed>
*/
function chatCompletionWithoutUsage(): array
{
return [
'id' => 'chatcmpl-123',
'object' => 'chat.completion',
'created' => 1677652288,
'model' => 'gpt-3.5-turbo',
'choices' => [
[
'index' => 0,
'message' => [
'role' => 'assistant',
'content' => "\n\nHello there, how may I assist you today?",
],
'finish_reason' => 'stop',
],
],
];
}

/**
* @return array<string, mixed>
*/
Expand Down Expand Up @@ -195,6 +253,24 @@ function chatCompletionStreamFirstChunk(): array
];
}

function chatCompletionStreamFirstChunkWithoutId(): array
{
return [
'object' => 'chat.completion.chunk',
'created' => 1679432086,
'model' => 'gpt-4-0314',
'choices' => [
[
'index' => 0,
'delta' => [
'role' => 'assistant',
],
'finish_reason' => null,
],
],
];
}

function chatCompletionStreamContentChunk(): array
{
return [
Expand Down
29 changes: 29 additions & 0 deletions tests/Fixtures/Embedding.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ function embedding(): array
];
}

/**
* @return array<string, mixed>
*/
function embeddingWithoutIndex(): array
{
return [
'object' => 'embedding',
'embedding' => [
-0.008906792,
-0.013743395,
0.009874112,
],
];
}

/**
* @return array<string, mixed>
*/
Expand All @@ -33,3 +48,17 @@ function embeddingList(): array
],
];
}

/**
* @return array<string, mixed>
*/
function embeddingListWithoutUsage(): array
{
return [
'object' => 'list',
'data' => [
embedding(),
embedding(),
],
];
}
32 changes: 32 additions & 0 deletions tests/Responses/Chat/CreateResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,38 @@
->meta()->toBeInstanceOf(MetaInformation::class);
});

test('from without id', function () {
$completion = CreateResponse::from(chatCompletionWithoutId(), meta());

expect($completion)
->toBeInstanceOf(CreateResponse::class)
->id->toBeNull()
->object->toBe('chat.completion')
->created->toBe(1677652288)
->model->toBe('gpt-3.5-turbo')
->systemFingerprint->toBeNull()
->choices->toBeArray()->toHaveCount(1)
->choices->each->toBeInstanceOf(CreateResponseChoice::class)
->usage->toBeInstanceOf(CreateResponseUsage::class)
->meta()->toBeInstanceOf(MetaInformation::class);
});

test('from without usage', function () {
$completion = CreateResponse::from(chatCompletionWithoutUsage(), meta());

expect($completion)
->toBeInstanceOf(CreateResponse::class)
->id->toBe('chatcmpl-123')
->object->toBe('chat.completion')
->created->toBe(1677652288)
->model->toBe('gpt-3.5-turbo')
->systemFingerprint->toBeNull()
->choices->toBeArray()->toHaveCount(1)
->choices->each->toBeInstanceOf(CreateResponseChoice::class)
->usage->toBeNull()
->meta()->toBeInstanceOf(MetaInformation::class);
});

test('from with system fingerprint', function () {
$completion = CreateResponse::from(chatCompletionWithSystemFingerprint(), meta());

Expand Down
Loading
Loading