From 4f3018ab53a9934b143ce9d662c11884e9ec7434 Mon Sep 17 00:00:00 2001 From: gehrisandro Date: Mon, 28 Aug 2023 21:26:08 +0200 Subject: [PATCH 1/2] [0.7] add fine tuning resource --- README.md | 106 ++++++++++- composer.json | 2 +- src/Client.php | 13 ++ src/Contracts/ClientContract.php | 12 ++ .../Resources/FineTuningContract.php | 51 ++++++ src/Enums/FineTuning/FineTuningEventLevel.php | 10 + src/Resources/FineTuning.php | 98 ++++++++++ .../FineTuning/ListJobEventsResponse.php | 68 +++++++ .../FineTuning/ListJobEventsResponseEvent.php | 65 +++++++ .../ListJobEventsResponseEventData.php | 52 ++++++ src/Responses/FineTuning/ListJobsResponse.php | 69 +++++++ .../FineTuning/RetrieveJobResponse.php | 94 ++++++++++ .../RetrieveJobResponseHyperparameters.php | 46 +++++ src/Testing/ClientFake.php | 6 + .../Resources/FineTuningTestResource.php | 45 +++++ .../ListJobEventsResponseFixture.php | 21 +++ .../FineTuning/ListJobsResponseFixture.php | 13 ++ .../FineTuning/RetrieveJobResponseFixture.php | 26 +++ src/ValueObjects/Transporter/Payload.php | 16 +- tests/Fixtures/FineTuning.php | 115 ++++++++++++ tests/Resources/FineTuning.php | 171 ++++++++++++++++++ .../FineTuning/ListJobEventsResponse.php | 54 ++++++ .../ListJobsEventsResponseEvent.php | 40 ++++ .../ListJobsEventsResponseEventData.php | 20 ++ .../Responses/FineTuning/ListJobsResponse.php | 54 ++++++ .../FineTuning/RetrieveJobResponse.php | 77 ++++++++ .../RetrieveJobResponseHyperparameters.php | 18 ++ .../Resources/FineTuningTestResource.php | 75 ++++++++ 28 files changed, 1431 insertions(+), 6 deletions(-) create mode 100644 src/Contracts/Resources/FineTuningContract.php create mode 100644 src/Enums/FineTuning/FineTuningEventLevel.php create mode 100644 src/Resources/FineTuning.php create mode 100644 src/Responses/FineTuning/ListJobEventsResponse.php create mode 100644 src/Responses/FineTuning/ListJobEventsResponseEvent.php create mode 100644 src/Responses/FineTuning/ListJobEventsResponseEventData.php create mode 100644 src/Responses/FineTuning/ListJobsResponse.php create mode 100644 src/Responses/FineTuning/RetrieveJobResponse.php create mode 100644 src/Responses/FineTuning/RetrieveJobResponseHyperparameters.php create mode 100644 src/Testing/Resources/FineTuningTestResource.php create mode 100644 src/Testing/Responses/Fixtures/FineTuning/ListJobEventsResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/FineTuning/ListJobsResponseFixture.php create mode 100644 src/Testing/Responses/Fixtures/FineTuning/RetrieveJobResponseFixture.php create mode 100644 tests/Fixtures/FineTuning.php create mode 100644 tests/Resources/FineTuning.php create mode 100644 tests/Responses/FineTuning/ListJobEventsResponse.php create mode 100644 tests/Responses/FineTuning/ListJobsEventsResponseEvent.php create mode 100644 tests/Responses/FineTuning/ListJobsEventsResponseEventData.php create mode 100644 tests/Responses/FineTuning/ListJobsResponse.php create mode 100644 tests/Responses/FineTuning/RetrieveJobResponse.php create mode 100644 tests/Responses/FineTuning/RetrieveJobResponseHyperparameters.php create mode 100644 tests/Testing/Resources/FineTuningTestResource.php diff --git a/README.md b/README.md index b6f9828a..00d41f2b 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,10 @@ - [Audio Resource](#audio-resource) - [Embeddings Resource](#embeddings-resource) - [Files Resource](#files-resource) - - [FineTunes Resource](#finetunes-resource) + - [FineTuning Resource](#finetuning-resource) - [Moderations Resource](#moderations-resource) - [Images Resource](#images-resource) + - [FineTunes Resource (deprecated)](#finetunes-resource) - [Edits Resource (deprecated)](#edits-resource) - [Meta Information](#meta-information) - [Testing](#testing) @@ -481,6 +482,109 @@ Returns the contents of the specified file. $client->files()->download($file); // '{"prompt": "", ...' ``` +### `FineTuning` Resource (deprecated) + +#### `create job` + +Creates a job that fine-tunes a specified model from a given dataset. + +```php +$response = $client->fineTuning()->createJob([ + 'training_file' => 'file-abc123', + 'validation_file' => null, + 'model' => 'gpt-3.5-turbo', + 'hyperparameters' => [ + 'n_epochs' => 4, + ], + 'suffix' => null, +]); + +$response->id; // 'ft-AF1WoRqd3aJAHsqc9NY7iL8F' +$response->object; // 'fine_tuning.job' +$response->model; // 'gpt-3.5-turbo-0613' +$response->fineTunedModel; // null +// ... + +$response->toArray(); // ['id' => 'ft-AF1WoRqd3aJAHsqc9NY7iL8F', ...] +``` + +#### `list jobs` + +List your organization's fine-tuning jobs. + +```php +$response = $client->fineTuning()->listJobs(); + +$response->object; // 'list' + +foreach ($response->data as $result) { + $result->id; // 'ft-AF1WoRqd3aJAHsqc9NY7iL8F' + $result->object; // 'fine_tuning.job' + // ... +} + +$response->toArray(); // ['object' => 'list', 'data' => [...]] +``` + +#### `retrieve jobs` + +Get info about a fine-tuning job. + +```php +$response = $client->fineTuning()->retrieve('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); + +$response->id; // 'ft-AF1WoRqd3aJAHsqc9NY7iL8F' +$response->object; // 'fine_tuning.job' +$response->model; // 'gpt-3.5-turbo-0613' +$response->createdAt; // 1614807352 +$response->finishedAt; // 1692819450 +$response->fineTunedModel; // 'ft:gpt-3.5-turbo-0613:jwe-dev::7qnxQ0sQ' +$response->organizationId; // 'org-jwe45798ASN82s' +$response->resultFiles[0]; // 'file-1bl05WrhsKDDEdg8XSP617QF' +$response->status; // 'succeeded' +$response->validation_file; // null +$response->training_file; // 'file-abc123' +$response->trained_tokens; // 5049 + +$response->hyperparameters->nEpochs; // 9 + +$response->toArray(); // ['id' => 'ft-AF1WoRqd3aJAHsqc9NY7iL8F', ...] +``` + +#### `cancel job` + +Immediately cancel a fine-tune job. + +```php +$response = $client->fineTuning()->cancelJob('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); + +$response->id; // 'ft-AF1WoRqd3aJAHsqc9NY7iL8F' +$response->object; // 'fine_tuning.job' +// ... +$response->status; // 'cancelled' +// ... + +$response->toArray(); // ['id' => 'ft-AF1WoRqd3aJAHsqc9NY7iL8F', ...] +``` + +#### `list job events` + +Get status updates for a fine-tuning job. + +```php +$response = $client->fineTuning()->listJobEvents('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); + +$response->object; // 'list' + +foreach ($response->data as $result) { + $result->object; // 'fine_tuning.job.event' + $result->createdAt; // 1614807352 + // ... +} + +$response->toArray(); // ['object' => 'list', 'data' => [...]] +``` + ### `FineTunes` Resource #### `create` diff --git a/composer.json b/composer.json index 2556348b..3143a00f 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "guzzlehttp/psr7": "^2.5.0", "laravel/pint": "^1.10.3", "nunomaduro/collision": "^7.7.0", - "pestphp/pest": "dev-develop as 2.6.2", + "pestphp/pest": "^2.16.0", "pestphp/pest-plugin-arch": "^2.2.1", "pestphp/pest-plugin-mock": "^2.0.0", "pestphp/pest-plugin-type-coverage": "^2.0.0", diff --git a/src/Client.php b/src/Client.php index 83e388cd..9c5df261 100644 --- a/src/Client.php +++ b/src/Client.php @@ -13,6 +13,7 @@ use OpenAI\Resources\Embeddings; use OpenAI\Resources\Files; use OpenAI\Resources\FineTunes; +use OpenAI\Resources\FineTuning; use OpenAI\Resources\Images; use OpenAI\Resources\Models; use OpenAI\Resources\Moderations; @@ -98,10 +99,22 @@ public function models(): Models return new Models($this->transporter); } + /** + * Manage fine-tuning jobs to tailor a model to your specific training data. + * + * @see https://platform.openai.com/docs/api-reference/fine-tuning + */ + public function fineTuning(): FineTuning + { + return new FineTuning($this->transporter); + } + /** * Manage fine-tuning jobs to tailor a model to your specific training data. * * @see https://platform.openai.com/docs/api-reference/fine-tunes + * @deprecated OpenAI has deprecated this endpoint and will stop working by January 4, 2024. + * https://openai.com/blog/gpt-3-5-turbo-fine-tuning-and-api-updates#updated-gpt-3-models */ public function fineTunes(): FineTunes { diff --git a/src/Contracts/ClientContract.php b/src/Contracts/ClientContract.php index 87dd67fc..5d9b2ac0 100644 --- a/src/Contracts/ClientContract.php +++ b/src/Contracts/ClientContract.php @@ -9,6 +9,7 @@ use OpenAI\Contracts\Resources\EmbeddingsContract; use OpenAI\Contracts\Resources\FilesContract; use OpenAI\Contracts\Resources\FineTunesContract; +use OpenAI\Contracts\Resources\FineTuningContract; use OpenAI\Contracts\Resources\ImagesContract; use OpenAI\Contracts\Resources\ModelsContract; use OpenAI\Contracts\Resources\ModerationsContract; @@ -48,6 +49,8 @@ public function audio(): AudioContract; * Given a prompt and an instruction, the model will return an edited version of the prompt. * * @see https://platform.openai.com/docs/api-reference/edits + * @deprecated OpenAI has deprecated this endpoint and will stop working by January 4, 2024. + * https://openai.com/blog/gpt-4-api-general-availability#deprecation-of-the-edits-api */ public function edits(): EditsContract; @@ -65,10 +68,19 @@ public function files(): FilesContract; */ public function models(): ModelsContract; + /** + * Manage fine-tuning jobs to tailor a model to your specific training data. + * + * @see https://platform.openai.com/docs/api-reference/fine-tuning + */ + public function fineTuning(): FineTuningContract; + /** * Manage fine-tuning jobs to tailor a model to your specific training data. * * @see https://platform.openai.com/docs/api-reference/fine-tunes + * @deprecated OpenAI has deprecated this endpoint and will stop working by January 4, 2024. + * https://openai.com/blog/gpt-3-5-turbo-fine-tuning-and-api-updates#updated-gpt-3-models */ public function fineTunes(): FineTunesContract; diff --git a/src/Contracts/Resources/FineTuningContract.php b/src/Contracts/Resources/FineTuningContract.php new file mode 100644 index 00000000..7c71950a --- /dev/null +++ b/src/Contracts/Resources/FineTuningContract.php @@ -0,0 +1,51 @@ + $parameters + */ + public function createJob(array $parameters): RetrieveJobResponse; + + /** + * List your organization's fine-tuning jobs. + * + * @see TODO: There is no official documentation yet + */ + public function listJobs(): ListJobsResponse; + + /** + * Get info about a fine-tuning job. + * + * @see https://platform.openai.com/docs/api-reference/fine-tuning/retrieve + */ + public function retrieveJob(string $jobId): RetrieveJobResponse; + + /** + * Immediately cancel a fine-tune job. + * + * @see https://platform.openai.com/docs/api-reference/fine-tuning/cancel + */ + public function cancelJob(string $jobId): RetrieveJobResponse; + + /** + * Get status updates for a fine-tuning job. + * + * @see https://platform.openai.com/docs/api-reference/fine-tuning/list-events + * + * @param array $parameters + */ + public function listJobEvents(string $jobId, array $parameters = []): ListJobEventsResponse; +} diff --git a/src/Enums/FineTuning/FineTuningEventLevel.php b/src/Enums/FineTuning/FineTuningEventLevel.php new file mode 100644 index 00000000..548887dd --- /dev/null +++ b/src/Enums/FineTuning/FineTuningEventLevel.php @@ -0,0 +1,10 @@ + $parameters + */ + public function createJob(array $parameters): RetrieveJobResponse + { + $payload = Payload::create('fine_tuning/jobs', $parameters); + + /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int}> $response */ + $response = $this->transporter->requestObject($payload); + + return RetrieveJobResponse::from($response->data(), $response->meta()); + } + + /** + * List your organization's fine-tuning jobs. + * + * @see TODO: There is no official documentation yet + */ + public function listJobs(): ListJobsResponse + { + $payload = Payload::list('fine_tuning/jobs'); + + /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int}>}> $response */ + $response = $this->transporter->requestObject($payload); + + return ListJobsResponse::from($response->data(), $response->meta()); + } + + /** + * Gets info about the fine-tune job. + * + * @see https://platform.openai.com/docs/api-reference/fine-tunes/list + */ + public function retrieveJob(string $jobId): RetrieveJobResponse + { + $payload = Payload::retrieve('fine_tuning/jobs', $jobId); + + /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int}> $response */ + $response = $this->transporter->requestObject($payload); + + return RetrieveJobResponse::from($response->data(), $response->meta()); + } + + /** + * Immediately cancel a fine-tune job. + * + * @see https://platform.openai.com/docs/api-reference/fine-tuning/cancel + */ + public function cancelJob(string $jobId): RetrieveJobResponse + { + $payload = Payload::cancel('fine_tuning/jobs', $jobId); + + /** @var Response, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int}> $response */ + $response = $this->transporter->requestObject($payload); + + return RetrieveJobResponse::from($response->data(), $response->meta()); + } + + /** + * Get status updates for a fine-tuning job. + * + * @see https://platform.openai.com/docs/api-reference/fine-tuning/list-events + * + * @param array $parameters + */ + public function listJobEvents(string $jobId, array $parameters = []): ListJobEventsResponse + { + $payload = Payload::retrieve('fine_tuning/jobs', $jobId, '/events', $parameters); + + /** @var Response}> $response */ + $response = $this->transporter->requestObject($payload); + + return ListJobEventsResponse::from($response->data(), $response->meta()); + } +} diff --git a/src/Responses/FineTuning/ListJobEventsResponse.php b/src/Responses/FineTuning/ListJobEventsResponse.php new file mode 100644 index 00000000..9229a17a --- /dev/null +++ b/src/Responses/FineTuning/ListJobEventsResponse.php @@ -0,0 +1,68 @@ +}> + */ +final class ListJobEventsResponse implements ResponseContract, ResponseHasMetaInformationContract +{ + /** + * @use ArrayAccessible}> + */ + use ArrayAccessible; + + use HasMetaInformation; + use Fakeable; + + /** + * @param array $data + */ + private function __construct( + public readonly string $object, + public readonly array $data, + private readonly MetaInformation $meta, + ) { + } + + /** + * Acts as static factory, and returns a new Response instance. + * + * @param array{object: string, data: array} $attributes + */ + public static function from(array $attributes, MetaInformation $meta): self + { + $data = array_map(fn (array $result): ListJobEventsResponseEvent => ListJobEventsResponseEvent::from( + $result + ), $attributes['data']); + + return new self( + $attributes['object'], + $data, + $meta, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'object' => $this->object, + 'data' => array_map( + static fn (ListJobEventsResponseEvent $response): array => $response->toArray(), + $this->data, + ), + ]; + } +} diff --git a/src/Responses/FineTuning/ListJobEventsResponseEvent.php b/src/Responses/FineTuning/ListJobEventsResponseEvent.php new file mode 100644 index 00000000..6dc97091 --- /dev/null +++ b/src/Responses/FineTuning/ListJobEventsResponseEvent.php @@ -0,0 +1,65 @@ + + */ +final class ListJobEventsResponseEvent implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + private function __construct( + public readonly string $object, + public readonly string $id, + public readonly int $createdAt, + public readonly FineTuningEventLevel $level, + public readonly string $message, + public readonly ?ListJobEventsResponseEventData $data, + public readonly string $type, + ) { + } + + /** + * Acts as static factory, and returns a new Response instance. + * + * @param array{object: string, id: string, created_at: int, level: string, message: string, data: array{step: int, train_loss: float, train_mean_token_accuracy: float}|null, type: string} $attributes + */ + public static function from(array $attributes): self + { + return new self( + $attributes['object'], + $attributes['id'], + $attributes['created_at'], + FineTuningEventLevel::from($attributes['level']), + $attributes['message'], + $attributes['data'] ? ListJobEventsResponseEventData::from($attributes['data']) : null, + $attributes['type'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'object' => $this->object, + 'id' => $this->id, + 'created_at' => $this->createdAt, + 'level' => $this->level->value, + 'message' => $this->message, + 'data' => $this->data?->toArray(), + 'type' => $this->type, + ]; + } +} diff --git a/src/Responses/FineTuning/ListJobEventsResponseEventData.php b/src/Responses/FineTuning/ListJobEventsResponseEventData.php new file mode 100644 index 00000000..4f907c9b --- /dev/null +++ b/src/Responses/FineTuning/ListJobEventsResponseEventData.php @@ -0,0 +1,52 @@ + + */ +final class ListJobEventsResponseEventData implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + private function __construct( + public readonly int $step, + public readonly float $trainLoss, + public readonly float $trainMeanTokenAccuracy, + ) { + } + + /** + * Acts as static factory, and returns a new Response instance. + * + * @param array{step: int, train_loss: float, train_mean_token_accuracy: float} $attributes + */ + public static function from(array $attributes): self + { + return new self( + $attributes['step'], + $attributes['train_loss'], + $attributes['train_mean_token_accuracy'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'step' => $this->step, + 'train_loss' => $this->trainLoss, + 'train_mean_token_accuracy' => $this->trainMeanTokenAccuracy, + ]; + } +} diff --git a/src/Responses/FineTuning/ListJobsResponse.php b/src/Responses/FineTuning/ListJobsResponse.php new file mode 100644 index 00000000..915a0c7e --- /dev/null +++ b/src/Responses/FineTuning/ListJobsResponse.php @@ -0,0 +1,69 @@ +, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int}>}> + */ +final class ListJobsResponse implements ResponseContract, ResponseHasMetaInformationContract +{ + /** + * @use ArrayAccessible, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int}>}> + */ + use ArrayAccessible; + + use HasMetaInformation; + use Fakeable; + + /** + * @param array $data + */ + private function __construct( + public readonly string $object, + public readonly array $data, + private readonly MetaInformation $meta, + ) { + } + + /** + * Acts as static factory, and returns a new Response instance. + * + * @param array{object: string, data: array, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int}>} $attributes + */ + public static function from(array $attributes, MetaInformation $meta): self + { + $data = array_map(fn (array $result): RetrieveJobResponse => RetrieveJobResponse::from( + $result, + $meta, + ), $attributes['data']); + + return new self( + $attributes['object'], + $data, + $meta, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'object' => $this->object, + 'data' => array_map( + static fn (RetrieveJobResponse $response): array => $response->toArray(), + $this->data, + ), + ]; + } +} diff --git a/src/Responses/FineTuning/RetrieveJobResponse.php b/src/Responses/FineTuning/RetrieveJobResponse.php new file mode 100644 index 00000000..6f2fcb46 --- /dev/null +++ b/src/Responses/FineTuning/RetrieveJobResponse.php @@ -0,0 +1,94 @@ +, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int}> + */ +final class RetrieveJobResponse implements ResponseContract, ResponseHasMetaInformationContract +{ + /** + * @use ArrayAccessible, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int}> + */ + use ArrayAccessible; + + use HasMetaInformation; + use Fakeable; + + /** + * @param array $resultFiles + */ + private function __construct( + public readonly string $id, + public readonly string $object, + public readonly string $model, + public readonly int $createdAt, + public readonly ?int $finishedAt, + public readonly ?string $fineTunedModel, + public readonly RetrieveJobResponseHyperparameters $hyperparameters, + public readonly string $organizationId, + public readonly array $resultFiles, + public readonly string $status, + public readonly ?string $validationFile, + public readonly string $trainingFile, + public readonly ?int $trainedTokens, + private readonly MetaInformation $meta, + ) { + } + + /** + * Acts as static factory, and returns a new Response instance. + * + * @param array{id: string, object: string, model: string, created_at: int, finished_at: ?int, fine_tuned_model: ?string, hyperparameters: array{n_epochs: int}, organization_id: string, result_files: array, status: string, validation_file: ?string, training_file: string, trained_tokens: ?int} $attributes + */ + public static function from(array $attributes, MetaInformation $meta): self + { + return new self( + $attributes['id'], + $attributes['object'], + $attributes['model'], + $attributes['created_at'], + $attributes['finished_at'], + $attributes['fine_tuned_model'], + RetrieveJobResponseHyperparameters::from($attributes['hyperparameters']), + $attributes['organization_id'], + $attributes['result_files'], + $attributes['status'], + $attributes['validation_file'], + $attributes['training_file'], + $attributes['trained_tokens'], + $meta, + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'object' => $this->object, + 'model' => $this->model, + 'created_at' => $this->createdAt, + 'finished_at' => $this->finishedAt, + 'fine_tuned_model' => $this->fineTunedModel, + 'hyperparameters' => $this->hyperparameters->toArray(), + 'organization_id' => $this->organizationId, + 'result_files' => $this->resultFiles, + 'status' => $this->status, + 'validation_file' => $this->validationFile, + 'training_file' => $this->trainingFile, + 'trained_tokens' => $this->trainedTokens, + ]; + } +} diff --git a/src/Responses/FineTuning/RetrieveJobResponseHyperparameters.php b/src/Responses/FineTuning/RetrieveJobResponseHyperparameters.php new file mode 100644 index 00000000..c4698317 --- /dev/null +++ b/src/Responses/FineTuning/RetrieveJobResponseHyperparameters.php @@ -0,0 +1,46 @@ + + */ +final class RetrieveJobResponseHyperparameters implements ResponseContract +{ + /** + * @use ArrayAccessible + */ + use ArrayAccessible; + + private function __construct( + public readonly int $nEpochs, + ) { + } + + /** + * Acts as static factory, and returns a new Response instance. + * + * @param array{n_epochs: int} $attributes + */ + public static function from(array $attributes): self + { + return new self( + $attributes['n_epochs'], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'n_epochs' => $this->nEpochs, + ]; + } +} diff --git a/src/Testing/ClientFake.php b/src/Testing/ClientFake.php index e1106913..e96fd83d 100644 --- a/src/Testing/ClientFake.php +++ b/src/Testing/ClientFake.php @@ -13,6 +13,7 @@ use OpenAI\Testing\Resources\EmbeddingsTestResource; use OpenAI\Testing\Resources\FilesTestResource; use OpenAI\Testing\Resources\FineTunesTestResource; +use OpenAI\Testing\Resources\FineTuningTestResource; use OpenAI\Testing\Resources\ImagesTestResource; use OpenAI\Testing\Resources\ModelsTestResource; use OpenAI\Testing\Resources\ModerationsTestResource; @@ -167,6 +168,11 @@ public function fineTunes(): FineTunesTestResource return new FineTunesTestResource($this); } + public function fineTuning(): FineTuningTestResource + { + return new FineTuningTestResource($this); + } + public function moderations(): ModerationsTestResource { return new ModerationsTestResource($this); diff --git a/src/Testing/Resources/FineTuningTestResource.php b/src/Testing/Resources/FineTuningTestResource.php new file mode 100644 index 00000000..28e67c98 --- /dev/null +++ b/src/Testing/Resources/FineTuningTestResource.php @@ -0,0 +1,45 @@ +record(__FUNCTION__, $parameters); + } + + public function listJobs(): ListJobsResponse + { + return $this->record(__FUNCTION__); + } + + public function retrieveJob(string $jobId): RetrieveJobResponse + { + return $this->record(__FUNCTION__, $jobId); + } + + public function cancelJob(string $jobId): RetrieveJobResponse + { + return $this->record(__FUNCTION__, $jobId); + } + + public function listJobEvents(string $jobId, array $parameters = []): ListJobEventsResponse + { + return $this->record(__FUNCTION__, $jobId); + } +} diff --git a/src/Testing/Responses/Fixtures/FineTuning/ListJobEventsResponseFixture.php b/src/Testing/Responses/Fixtures/FineTuning/ListJobEventsResponseFixture.php new file mode 100644 index 00000000..67a9345e --- /dev/null +++ b/src/Testing/Responses/Fixtures/FineTuning/ListJobEventsResponseFixture.php @@ -0,0 +1,21 @@ + 'list', + 'data' => [ + [ + 'object' => 'fine_tuning.job.event', + 'id' => 'ft-event-ddTJfwuMVpfLXseO0Am0Gqjm', + 'created_at' => 1_692_407_401, + 'level' => 'info', + 'message' => 'Fine tuning job successfully completed', + 'data' => null, + 'type' => 'message', + ], + ], + ]; +} diff --git a/src/Testing/Responses/Fixtures/FineTuning/ListJobsResponseFixture.php b/src/Testing/Responses/Fixtures/FineTuning/ListJobsResponseFixture.php new file mode 100644 index 00000000..7fb18133 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FineTuning/ListJobsResponseFixture.php @@ -0,0 +1,13 @@ + 'list', + 'data' => [ + RetrieveJobResponseFixture::ATTRIBUTES, + ], + ]; +} diff --git a/src/Testing/Responses/Fixtures/FineTuning/RetrieveJobResponseFixture.php b/src/Testing/Responses/Fixtures/FineTuning/RetrieveJobResponseFixture.php new file mode 100644 index 00000000..5047ba89 --- /dev/null +++ b/src/Testing/Responses/Fixtures/FineTuning/RetrieveJobResponseFixture.php @@ -0,0 +1,26 @@ + 'ft-AF1WoRqd3aJAHsqc9NY7iL8F', + 'object' => 'fine_tuning.job', + 'model' => 'gpt-3.5-turbo-0613', + 'created_at' => 1_614_807_352, + 'finished_at' => 1_692_819_450, + 'fine_tuned_model' => 'ft:gpt-3.5-turbo-0613:gehri-dev::7qnxQ0sQ', + 'hyperparameters' => [ + 'n_epochs' => 9, + ], + 'organization_id' => 'org-jwe45798ASN82s', + 'result_files' => [ + 'file-1bl05WrhsKDDEdg8XSP617QF', + ], + 'status' => 'succeeded', + 'validation_file' => null, + 'training_file' => 'file-abc123', + 'trained_tokens' => 5049, + ]; +} diff --git a/src/ValueObjects/Transporter/Payload.php b/src/ValueObjects/Transporter/Payload.php index f06b0195..5d5497bf 100644 --- a/src/ValueObjects/Transporter/Payload.php +++ b/src/ValueObjects/Transporter/Payload.php @@ -46,14 +46,16 @@ public static function list(string $resource): self /** * Creates a new Payload value object from the given parameters. + * + * @param array $parameters */ - public static function retrieve(string $resource, string $id, string $suffix = ''): self + public static function retrieve(string $resource, string $id, string $suffix = '', array $parameters = []): self { $contentType = ContentType::JSON; $method = Method::GET; $uri = ResourceUri::retrieve($resource, $id, $suffix); - return new self($contentType, $method, $uri); + return new self($contentType, $method, $uri, $parameters); } /** @@ -130,8 +132,14 @@ public function toRequest(BaseUri $baseUri, Headers $headers, QueryParams $query $body = null; $uri = $baseUri->toString().$this->uri->toString(); - if (! empty($queryParams->toArray())) { - $uri .= '?'.http_build_query($queryParams->toArray()); + + $queryParams = $queryParams->toArray(); + if ($this->method === Method::GET) { + $queryParams = [...$queryParams, ...$this->parameters]; + } + + if ($queryParams !== []) { + $uri .= '?'.http_build_query($queryParams); } $headers = $headers->withContentType($this->contentType); diff --git a/tests/Fixtures/FineTuning.php b/tests/Fixtures/FineTuning.php new file mode 100644 index 00000000..46a0e33d --- /dev/null +++ b/tests/Fixtures/FineTuning.php @@ -0,0 +1,115 @@ + + */ +function fineTuningJobCreateResource(): array +{ + return [ + 'id' => 'ft-AF1WoRqd3aJAHsqc9NY7iL8F', + 'object' => 'fine_tuning.job', + 'model' => 'gpt-3.5-turbo-0613', + 'created_at' => 1614807352, + 'finished_at' => null, + 'fine_tuned_model' => null, + 'hyperparameters' => [ + 'n_epochs' => 9, + ], + 'organization_id' => 'org-jwe45798ASN82s', + 'result_files' => [], + 'status' => 'created', + 'validation_file' => null, + 'training_file' => 'file-abc123', + 'trained_tokens' => null, + ]; +} + +/** + * @return array + */ +function fineTuningJobRetrieveResource(): array +{ + return [ + 'id' => 'ft-AF1WoRqd3aJAHsqc9NY7iL8F', + 'object' => 'fine_tuning.job', + 'model' => 'gpt-3.5-turbo-0613', + 'created_at' => 1614807352, + 'finished_at' => 1692819450, + 'fine_tuned_model' => 'ft:gpt-3.5-turbo-0613:jwe-dev::7qnxQ0sQ', + 'hyperparameters' => [ + 'n_epochs' => 9, + ], + 'organization_id' => 'org-jwe45798ASN82s', + 'result_files' => [ + 'file-1bl05WrhsKDDEdg8XSP617QF', + ], + 'status' => 'succeeded', + 'validation_file' => null, + 'training_file' => 'file-abc123', + 'trained_tokens' => 5049, + ]; +} + +/** + * @return array + */ +function fineTuningJobListResource(): array +{ + return [ + 'object' => 'list', + 'data' => [ + fineTuningJobRetrieveResource(), + fineTuningJobRetrieveResource(), + ], + ]; +} + +/** + * @return array + */ +function fineTuningJobMessageEventResource(): array +{ + return [ + 'object' => 'fine_tuning.job.event', + 'id' => 'ft-event-ddTJfwuMVpfLXseO0Am0Gqjm', + 'created_at' => 1692407401, + 'level' => 'info', + 'message' => 'Fine tuning job successfully completed', + 'data' => null, + 'type' => 'message', + ]; +} + +/** + * @return array + */ +function fineTuningJobMetricsEventResource(): array +{ + return [ + 'object' => 'fine_tuning.job.event', + 'id' => 'ftevent-kLPSMIcsqshEUEJVOVBVcHlP', + 'created_at' => 1692887003, + 'level' => 'info', + 'message' => 'Step 99/99: training loss=0.11', + 'data' => [ + 'step' => 99, + 'train_loss' => 0.11462418735027, + 'train_mean_token_accuracy' => 0.94999998807907, + ], + 'type' => 'metrics', + ]; +} + +/** + * @return array + */ +function fineTuningJobListEventsResource(): array +{ + return [ + 'object' => 'list', + 'data' => [ + fineTuningJobMessageEventResource(), + fineTuningJobMetricsEventResource(), + ], + ]; +} diff --git a/tests/Resources/FineTuning.php b/tests/Resources/FineTuning.php new file mode 100644 index 00000000..4c5e9026 --- /dev/null +++ b/tests/Resources/FineTuning.php @@ -0,0 +1,171 @@ + 'file-abc123', + 'validation_file' => null, + 'model' => 'gpt-3.5-turbo-0613', + 'hyperparameters' => [ + 'n_epochs' => 4, + ], + 'suffix' => null, + ], \OpenAI\ValueObjects\Transporter\Response::from(fineTuningJobCreateResource(), metaHeaders())); + + $result = $client->fineTuning()->createJob([ + 'training_file' => 'file-abc123', + 'validation_file' => null, + 'model' => 'gpt-3.5-turbo-0613', + 'hyperparameters' => [ + 'n_epochs' => 4, + ], + 'suffix' => null, + ]); + + expect($result) + ->toBeInstanceOf(RetrieveJobResponse::class) + ->id->toBe('ft-AF1WoRqd3aJAHsqc9NY7iL8F') + ->object->toBe('fine_tuning.job') + ->model->toBe('gpt-3.5-turbo-0613') + ->createdAt->toBe(1614807352) + ->finishedAt->toBeNull() + ->fineTunedModel->toBeNull() + ->hyperparameters->toBeInstanceOf(RetrieveJobResponseHyperparameters::class) + ->organizationId->toBe('org-jwe45798ASN82s') + ->resultFiles->toBeArray()->toBeEmpty() + ->status->toBe('created') + ->validationFile->toBeNull() + ->trainingFile->toBe('file-abc123') + ->trainedTokens->toBeNull(); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('list jobs', function () { + $client = mockClient('GET', 'fine_tuning/jobs', [], \OpenAI\ValueObjects\Transporter\Response::from(fineTuningJobListResource(), metaHeaders())); + + $result = $client->fineTuning()->listJobs(); + + expect($result) + ->toBeInstanceOf(ListJobsResponse::class) + ->data->toBeArray()->toHaveCount(2) + ->data->each->toBeInstanceOf(RetrieveJobResponse::class); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('retrieve job', function () { + $client = mockClient('GET', 'fine_tuning/jobs/ft-AF1WoRqd3aJAHsqc9NY7iL8F', [], \OpenAI\ValueObjects\Transporter\Response::from(fineTuningJobRetrieveResource(), metaHeaders())); + + $result = $client->fineTuning()->retrieveJob('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); + + expect($result) + ->toBeInstanceOf(RetrieveJobResponse::class) + ->id->toBe('ft-AF1WoRqd3aJAHsqc9NY7iL8F') + ->object->toBe('fine_tuning.job') + ->model->toBe('gpt-3.5-turbo-0613') + ->createdAt->toBe(1614807352) + ->finishedAt->toBe(1692819450) + ->fineTunedModel->toBe('ft:gpt-3.5-turbo-0613:jwe-dev::7qnxQ0sQ') + ->hyperparameters->toBeInstanceOf(RetrieveJobResponseHyperparameters::class) + ->organizationId->toBe('org-jwe45798ASN82s') + ->resultFiles->toBeArray()->toHaveCount(1) + ->resultFiles->{0}->toBe('file-1bl05WrhsKDDEdg8XSP617QF') + ->status->toBe('succeeded') + ->validationFile->toBeNull() + ->trainingFile->toBe('file-abc123') + ->trainedTokens->toBe(5049); + + expect($result->hyperparameters) + ->toBeInstanceOf(RetrieveJobResponseHyperparameters::class) + ->nEpochs->toBe(9); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('cancel job', function () { + $client = mockClient('POST', 'fine_tuning/jobs/ft-AF1WoRqd3aJAHsqc9NY7iL8F/cancel', [], \OpenAI\ValueObjects\Transporter\Response::from([...fineTuningJobRetrieveResource(), 'status' => 'cancelled'], metaHeaders())); + + $result = $client->fineTuning()->cancelJob('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); + + expect($result) + ->toBeInstanceOf(RetrieveJobResponse::class) + ->id->toBe('ft-AF1WoRqd3aJAHsqc9NY7iL8F') + ->object->toBe('fine_tuning.job') + ->model->toBe('gpt-3.5-turbo-0613') + ->createdAt->toBe(1614807352) + ->finishedAt->toBe(1692819450) + ->fineTunedModel->toBe('ft:gpt-3.5-turbo-0613:jwe-dev::7qnxQ0sQ') + ->hyperparameters->toBeInstanceOf(RetrieveJobResponseHyperparameters::class) + ->organizationId->toBe('org-jwe45798ASN82s') + ->resultFiles->toBeArray()->toHaveCount(1) + ->resultFiles->{0}->toBe('file-1bl05WrhsKDDEdg8XSP617QF') + ->status->toBe('cancelled') + ->validationFile->toBeNull() + ->trainingFile->toBe('file-abc123') + ->trainedTokens->toBe(5049); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('list job events', function () { + $client = mockClient('GET', 'fine_tuning/jobs/ft-AF1WoRqd3aJAHsqc9NY7iL8F/events', [], \OpenAI\ValueObjects\Transporter\Response::from(fineTuningJobListEventsResource(), metaHeaders())); + + $result = $client->fineTuning()->listJobEvents('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); + + expect($result) + ->toBeInstanceOf(ListJobEventsResponse::class) + ->data->toBeArray()->toHaveCount(2) + ->data->each->toBeInstanceOf(ListJobEventsResponseEvent::class); + + expect($result->data[0]) + ->toBeInstanceOf(ListJobEventsResponseEvent::class) + ->object->toBe('fine_tuning.job.event') + ->id->toBe('ft-event-ddTJfwuMVpfLXseO0Am0Gqjm') + ->createdAt->toBe(1692407401) + ->level->toBe(\OpenAI\Enums\FineTuning\FineTuningEventLevel::Info) + ->message->toBe('Fine tuning job successfully completed') + ->data->toBeNull() + ->type->toBe('message'); + + expect($result->data[1]) + ->toBeInstanceOf(ListJobEventsResponseEvent::class) + ->object->toBe('fine_tuning.job.event') + ->id->toBe('ftevent-kLPSMIcsqshEUEJVOVBVcHlP') + ->createdAt->toBe(1692887003) + ->level->toBe(\OpenAI\Enums\FineTuning\FineTuningEventLevel::Info) + ->message->toBe('Step 99/99: training loss=0.11') + ->data->toBeInstanceOf(ListJobEventsResponseEventData::class) + ->type->toBe('metrics'); + + expect($result->data[1]->data) + ->toBeInstanceOf(ListJobEventsResponseEventData::class) + ->step->toBe(99) + ->trainLoss->toBe(0.11462418735027) + ->trainMeanTokenAccuracy->toBe(0.94999998807907); + + expect($result->meta()) + ->toBeInstanceOf(MetaInformation::class); +}); + +test('list job events with params', function () { + $client = mockClient('GET', 'fine_tuning/jobs/ft-AF1WoRqd3aJAHsqc9NY7iL8F/events', [], \OpenAI\ValueObjects\Transporter\Response::from(fineTuningJobListEventsResource(), metaHeaders())); + + $result = $client->fineTuning()->listJobEvents('ft-AF1WoRqd3aJAHsqc9NY7iL8F', ['limit' => 3]); + + expect($result) + ->toBeInstanceOf(ListJobEventsResponse::class) + ->data->toBeArray()->toHaveCount(2) + ->data->each->toBeInstanceOf(ListJobEventsResponseEvent::class); +}); diff --git a/tests/Responses/FineTuning/ListJobEventsResponse.php b/tests/Responses/FineTuning/ListJobEventsResponse.php new file mode 100644 index 00000000..e52e381b --- /dev/null +++ b/tests/Responses/FineTuning/ListJobEventsResponse.php @@ -0,0 +1,54 @@ +toBeInstanceOf(ListJobEventsResponse::class) + ->object->toBe('list') + ->data->toBeArray()->toHaveCount(2) + ->data->each->toBeInstanceOf(ListJobEventsResponseEvent::class) + ->meta()->toBeInstanceOf(MetaInformation::class); +}); + +test('as array accessible', function () { + $response = ListJobEventsResponse::from(fineTuningJobListEventsResource(), meta()); + + expect($response['object'])->toBe('list'); +}); + +test('to array', function () { + $response = ListJobEventsResponse::from(fineTuningJobListEventsResource(), meta()); + + expect($response->toArray()) + ->toBeArray() + ->toBe(fineTuningJobListEventsResource()); +}); + +test('fake', function () { + $response = ListJobEventsResponse::fake(); + + expect($response) + ->object->toBe('list') + ->and($response['data'][0]) + ->level->toBe('info'); +}); + +test('fake with override', function () { + $response = ListJobEventsResponse::fake([ + 'data' => [ + [ + 'level' => 'error', + ], + ], + ]); + + expect($response) + ->object->toBe('list') + ->and($response['data'][0]) + ->level->toBe('error'); +}); diff --git a/tests/Responses/FineTuning/ListJobsEventsResponseEvent.php b/tests/Responses/FineTuning/ListJobsEventsResponseEvent.php new file mode 100644 index 00000000..8a9f51a6 --- /dev/null +++ b/tests/Responses/FineTuning/ListJobsEventsResponseEvent.php @@ -0,0 +1,40 @@ +toBeInstanceOf(ListJobEventsResponseEvent::class) + ->object->toBe('fine_tuning.job.event') + ->id->toBe('ft-event-ddTJfwuMVpfLXseO0Am0Gqjm') + ->createdAt->toBe(1692407401) + ->level->toBe(FineTuningEventLevel::Info) + ->message->toBe('Fine tuning job successfully completed') + ->data->toBeNull() + ->type->toBe('message'); +}); + +test('from metrics event', function () { + $result = ListJobEventsResponseEvent::from(fineTuningJobMetricsEventResource()); + + expect($result) + ->toBeInstanceOf(ListJobEventsResponseEvent::class) + ->object->toBe('fine_tuning.job.event') + ->id->toBe('ftevent-kLPSMIcsqshEUEJVOVBVcHlP') + ->createdAt->toBe(1692887003) + ->level->toBe(FineTuningEventLevel::Info) + ->message->toBe('Step 99/99: training loss=0.11') + ->data->toBeInstanceOf(ListJobEventsResponseEventData::class) + ->type->toBe('metrics'); +}); + +test('to array', function () { + $result = ListJobEventsResponseEvent::from(fineTuningJobMessageEventResource()); + + expect($result->toArray()) + ->toBe(fineTuningJobMessageEventResource()); +}); diff --git a/tests/Responses/FineTuning/ListJobsEventsResponseEventData.php b/tests/Responses/FineTuning/ListJobsEventsResponseEventData.php new file mode 100644 index 00000000..89c0b438 --- /dev/null +++ b/tests/Responses/FineTuning/ListJobsEventsResponseEventData.php @@ -0,0 +1,20 @@ +toBeInstanceOf(ListJobEventsResponseEventData::class) + ->step->toBe(99) + ->trainLoss->toBe(0.11462418735027) + ->trainMeanTokenAccuracy->toBe(0.94999998807907); +}); + +test('to array', function () { + $result = ListJobEventsResponseEventData::from(fineTuningJobMetricsEventResource()['data']); + + expect($result->toArray()) + ->toBe(fineTuningJobMetricsEventResource()['data']); +}); diff --git a/tests/Responses/FineTuning/ListJobsResponse.php b/tests/Responses/FineTuning/ListJobsResponse.php new file mode 100644 index 00000000..090ce0dd --- /dev/null +++ b/tests/Responses/FineTuning/ListJobsResponse.php @@ -0,0 +1,54 @@ +toBeInstanceOf(ListJobsResponse::class) + ->object->toBe('list') + ->data->toBeArray()->toHaveCount(2) + ->data->each->toBeInstanceOf(RetrieveJobResponse::class) + ->meta()->toBeInstanceOf(MetaInformation::class); +}); + +test('as array accessible', function () { + $response = ListJobsResponse::from(fineTuningJobListResource(), meta()); + + expect($response['object'])->toBe('list'); +}); + +test('to array', function () { + $response = ListJobsResponse::from(fineTuningJobListResource(), meta()); + + expect($response->toArray()) + ->toBeArray() + ->toBe(fineTuningJobListResource()); +}); + +test('fake', function () { + $response = ListJobsResponse::fake(); + + expect($response) + ->object->toBe('list') + ->and($response['data'][0]) + ->id->toBe('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); +}); + +test('fake with override', function () { + $response = ListJobsResponse::fake([ + 'data' => [ + [ + 'id' => 'ft-1234', + ], + ], + ]); + + expect($response) + ->object->toBe('list') + ->and($response['data'][0]) + ->id->toBe('ft-1234'); +}); diff --git a/tests/Responses/FineTuning/RetrieveJobResponse.php b/tests/Responses/FineTuning/RetrieveJobResponse.php new file mode 100644 index 00000000..8d4ec470 --- /dev/null +++ b/tests/Responses/FineTuning/RetrieveJobResponse.php @@ -0,0 +1,77 @@ +toBeInstanceOf(RetrieveJobResponse::class) + ->id->toBe('ft-AF1WoRqd3aJAHsqc9NY7iL8F') + ->object->toBe('fine_tuning.job') + ->model->toBe('gpt-3.5-turbo-0613') + ->createdAt->toBe(1614807352) + ->finishedAt->toBeNull() + ->fineTunedModel->toBeNull() + ->hyperparameters->toBeInstanceOf(RetrieveJobResponseHyperparameters::class) + ->organizationId->toBe('org-jwe45798ASN82s') + ->resultFiles->toBeArray()->toBeEmpty() + ->status->toBe('created') + ->validationFile->toBeNull() + ->trainingFile->toBe('file-abc123') + ->trainedTokens->toBeNull() + ->meta()->toBeInstanceOf(MetaInformation::class); +}); + +test('from retrieve response', function () { + $result = RetrieveJobResponse::from(fineTuningJobRetrieveResource(), meta()); + + expect($result) + ->toBeInstanceOf(RetrieveJobResponse::class) + ->id->toBe('ft-AF1WoRqd3aJAHsqc9NY7iL8F') + ->object->toBe('fine_tuning.job') + ->model->toBe('gpt-3.5-turbo-0613') + ->createdAt->toBe(1614807352) + ->finishedAt->toBe(1692819450) + ->fineTunedModel->toBe('ft:gpt-3.5-turbo-0613:jwe-dev::7qnxQ0sQ') + ->hyperparameters->toBeInstanceOf(RetrieveJobResponseHyperparameters::class) + ->organizationId->toBe('org-jwe45798ASN82s') + ->resultFiles->toBeArray()->toHaveCount(1) + ->resultFiles->{0}->toBe('file-1bl05WrhsKDDEdg8XSP617QF') + ->status->toBe('succeeded') + ->validationFile->toBeNull() + ->trainingFile->toBe('file-abc123') + ->trainedTokens->toBe(5049) + ->meta()->toBeInstanceOf(MetaInformation::class); +}); + +test('as array accessible', function () { + $result = RetrieveJobResponse::from(fineTuningJobCreateResource(), meta()); + + expect($result['id'])->toBe('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); +}); + +test('to array', function () { + $result = RetrieveJobResponse::from(fineTuningJobRetrieveResource(), meta()); + + expect($result->toArray()) + ->toBe(fineTuningJobRetrieveResource()); +}); + +test('fake', function () { + $response = RetrieveJobResponse::fake(); + + expect($response) + ->id->toBe('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); +}); + +test('fake with override', function () { + $response = RetrieveJobResponse::fake([ + 'id' => 'ft-1234', + ]); + + expect($response) + ->id->toBe('ft-1234'); +}); diff --git a/tests/Responses/FineTuning/RetrieveJobResponseHyperparameters.php b/tests/Responses/FineTuning/RetrieveJobResponseHyperparameters.php new file mode 100644 index 00000000..8d7eb961 --- /dev/null +++ b/tests/Responses/FineTuning/RetrieveJobResponseHyperparameters.php @@ -0,0 +1,18 @@ +toBeInstanceOf(RetrieveJobResponseHyperparameters::class) + ->nEpochs->toBe(9); +}); + +test('to array', function () { + $result = RetrieveJobResponseHyperparameters::from(fineTuningJobRetrieveResource()['hyperparameters']); + + expect($result->toArray()) + ->toBe(fineTuningJobRetrieveResource()['hyperparameters']); +}); diff --git a/tests/Testing/Resources/FineTuningTestResource.php b/tests/Testing/Resources/FineTuningTestResource.php new file mode 100644 index 00000000..142e5c9f --- /dev/null +++ b/tests/Testing/Resources/FineTuningTestResource.php @@ -0,0 +1,75 @@ +fineTuning()->createJob([ + 'training_file' => 'file-abc123', + 'model' => 'gpt-3.5-turbo-0613', + ]); + + $fake->assertSent(FineTuning::class, function ($method, $parameters) { + return $method === 'createJob' && + $parameters['model'] === 'gpt-3.5-turbo-0613' && + $parameters['training_file'] === 'file-abc123'; + }); +}); + +it('records a fine tuning job retrieve request', function () { + $fake = new ClientFake([ + RetrieveJobResponse::fake(), + ]); + + $fake->fineTuning()->retrieveJob('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); + + $fake->assertSent(FineTuning::class, function ($method, $parameters) { + return $method === 'retrieveJob' && + $parameters === 'ft-AF1WoRqd3aJAHsqc9NY7iL8F'; + }); +}); + +it('records a fine tuning job cancel request', function () { + $fake = new ClientFake([ + RetrieveJobResponse::fake(), + ]); + + $fake->fineTuning()->cancelJob('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); + + $fake->assertSent(FineTuning::class, function ($method, $parameters) { + return $method === 'cancelJob' && + $parameters === 'ft-AF1WoRqd3aJAHsqc9NY7iL8F'; + }); +}); + +it('records a fine tuning job list request', function () { + $fake = new ClientFake([ + ListJobsResponse::fake(), + ]); + + $fake->fineTuning()->listJobs(); + + $fake->assertSent(FineTuning::class, function ($method) { + return $method === 'listJobs'; + }); +}); + +it('records a fine tuning list job events request', function () { + $fake = new ClientFake([ + ListJobEventsResponse::fake(), + ]); + + $fake->fineTuning()->listJobEvents('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); + + $fake->assertSent(FineTuning::class, function ($method, $parameters) { + return $method === 'listJobEvents' && + $parameters === 'ft-AF1WoRqd3aJAHsqc9NY7iL8F'; + }); +}); From 832270f9cabfdf4ac489030b4a7af2fb80adb6a7 Mon Sep 17 00:00:00 2001 From: gehrisandro Date: Mon, 28 Aug 2023 21:43:06 +0200 Subject: [PATCH 2/2] fix docs --- README.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 00d41f2b..8ad65705 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ - [FineTuning Resource](#finetuning-resource) - [Moderations Resource](#moderations-resource) - [Images Resource](#images-resource) - - [FineTunes Resource (deprecated)](#finetunes-resource) - - [Edits Resource (deprecated)](#edits-resource) + - [FineTunes Resource (deprecated)](#finetunes-resource-deprecated) + - [Edits Resource (deprecated)](#edits-resource-deprecated) - [Meta Information](#meta-information) - [Testing](#testing) - [Services](#services) @@ -482,7 +482,7 @@ Returns the contents of the specified file. $client->files()->download($file); // '{"prompt": "", ...' ``` -### `FineTuning` Resource (deprecated) +### `FineTuning` Resource #### `create job` @@ -526,12 +526,12 @@ foreach ($response->data as $result) { $response->toArray(); // ['object' => 'list', 'data' => [...]] ``` -#### `retrieve jobs` +#### `retrieve job` Get info about a fine-tuning job. ```php -$response = $client->fineTuning()->retrieve('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); +$response = $client->fineTuning()->retrieveJob('ft-AF1WoRqd3aJAHsqc9NY7iL8F'); $response->id; // 'ft-AF1WoRqd3aJAHsqc9NY7iL8F' $response->object; // 'fine_tuning.job' @@ -542,9 +542,9 @@ $response->fineTunedModel; // 'ft:gpt-3.5-turbo-0613:jwe-dev::7qnxQ0sQ' $response->organizationId; // 'org-jwe45798ASN82s' $response->resultFiles[0]; // 'file-1bl05WrhsKDDEdg8XSP617QF' $response->status; // 'succeeded' -$response->validation_file; // null -$response->training_file; // 'file-abc123' -$response->trained_tokens; // 5049 +$response->validationFile; // null +$response->trainingFile; // 'file-abc123' +$response->trainedTokens; // 5049 $response->hyperparameters->nEpochs; // 9 @@ -585,7 +585,16 @@ foreach ($response->data as $result) { $response->toArray(); // ['object' => 'list', 'data' => [...]] ``` -### `FineTunes` Resource +You can pass additional parameters to the `listJobEvents` method to narrow down the results. + +```php +$response = $client->fineTuning()->listJobEvents('ft-AF1WoRqd3aJAHsqc9NY7iL8F', [ + 'limit' => 3, // Number of events to retrieve (Default: 20) + 'after' => 'ftevent-kLPSMIcsqshEUEJVOVBVcHlP', // Identifier for the last event from the previous pagination request. +]); +``` + +### `FineTunes` Resource (deprecated) #### `create`