diff --git a/src/Contracts/Response.php b/src/Contracts/Response.php new file mode 100644 index 00000000..1dec786a --- /dev/null +++ b/src/Contracts/Response.php @@ -0,0 +1,18 @@ + + */ + public function toArray(): array; +} diff --git a/src/Enums/Moderation/Category.php b/src/Enums/Moderation/Category.php new file mode 100644 index 00000000..61a7100c --- /dev/null +++ b/src/Enums/Moderation/Category.php @@ -0,0 +1,16 @@ +>>|string> $attributes + */ + public static function new(array $attributes): CreateResponse + { + return (new self)->make( + attributes: $attributes, + ); + } + + /** + * @param array>>|string> $attributes + */ + public function make(array $attributes): CreateResponse + { + return new CreateResponse( + id: strval($attributes['id']), + model: strval($attributes['model']), + results: CreateResponseModerationResultFactory::collection($attributes['results']), /** @phpstan-ignore-line */ + ); + } +} diff --git a/src/Factories/Responses/Moderations/CreateResponseModerationCategoryFactory.php b/src/Factories/Responses/Moderations/CreateResponseModerationCategoryFactory.php new file mode 100644 index 00000000..a21e4e05 --- /dev/null +++ b/src/Factories/Responses/Moderations/CreateResponseModerationCategoryFactory.php @@ -0,0 +1,42 @@ +> $results + * @return CreateResponseModerationCategory[] + */ + public static function collection(array $results): array + { + return array_map(fn ($result): CreateResponseModerationCategory => static::new($result), $results); + } + + /** + * @param array $attributes + */ + public static function new(array $attributes): CreateResponseModerationCategory + { + return (new self)->make( + attributes: $attributes, + ); + } + + /** + * @param array $attributes + */ + public function make(array $attributes): CreateResponseModerationCategory + { + return new CreateResponseModerationCategory( + category: Category::from((string) $attributes['category']), + violated: (bool) $attributes['violated'], + score: (float) $attributes['score'], + ); + } +} diff --git a/src/Factories/Responses/Moderations/CreateResponseModerationResultFactory.php b/src/Factories/Responses/Moderations/CreateResponseModerationResultFactory.php new file mode 100644 index 00000000..15062345 --- /dev/null +++ b/src/Factories/Responses/Moderations/CreateResponseModerationResultFactory.php @@ -0,0 +1,47 @@ +>> $results + * @return CreateResponseModerationResult[] + */ + public static function collection(array $results): array + { + return array_map(fn ($result): CreateResponseModerationResult => static::new($result), $results); + } + + /** + * @param array> $attributes + */ + public static function new(array $attributes): CreateResponseModerationResult + { + return (new self)->make( + attributes: $attributes, + ); + } + + /** + * @param array> $attributes + */ + public function make(array $attributes): CreateResponseModerationResult + { + $categories = array_map(fn (Category $category): array => [ + 'category' => $category->value, + 'violated' => $attributes['categories'][$category->value], + 'score' => $attributes['category_scores'][$category->value], + ], Category::cases()); + + return new CreateResponseModerationResult( + categories: CreateResponseModerationCategoryFactory::collection($categories), + flagged: (bool) ($attributes['flagged']), + ); + } +} diff --git a/src/Resources/Moderations.php b/src/Resources/Moderations.php index 685b0392..ece82bb2 100644 --- a/src/Resources/Moderations.php +++ b/src/Resources/Moderations.php @@ -4,6 +4,8 @@ namespace OpenAI\Resources; +use OpenAI\Factories\Responses\Moderations\CreateResponseFactory; +use OpenAI\Responses\Moderations\CreateResponse; use OpenAI\ValueObjects\Transporter\Payload; final class Moderations @@ -16,15 +18,14 @@ final class Moderations * @see https://beta.openai.com/docs/api-reference/moderations/create * * @param array $parameters - * @return array */ - public function create(array $parameters): array + public function create(array $parameters): CreateResponse { $payload = Payload::create('moderations', $parameters); - /** @var array $result */ + /** @var array>>|string> $result */ $result = $this->transporter->requestObject($payload); - return $result; + return CreateResponseFactory::new($result); } } diff --git a/src/Responses/Moderations/CreateResponse.php b/src/Responses/Moderations/CreateResponse.php new file mode 100644 index 00000000..df8c8e1e --- /dev/null +++ b/src/Responses/Moderations/CreateResponse.php @@ -0,0 +1,32 @@ + $results + */ + public function __construct( + public readonly string $id, + public readonly string $model, + public readonly array $results, + ) { + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'id' => $this->id, + 'model' => $this->model, + 'results' => array_map(fn (CreateResponseModerationResult $result): array => $result->toArray(), $this->results), + ]; + } +} diff --git a/src/Responses/Moderations/CreateResponseModerationCategory.php b/src/Responses/Moderations/CreateResponseModerationCategory.php new file mode 100644 index 00000000..c24cdb08 --- /dev/null +++ b/src/Responses/Moderations/CreateResponseModerationCategory.php @@ -0,0 +1,17 @@ + $categories + */ + public function __construct( + public readonly array $categories, + public readonly bool $flagged, + ) { + } + + /** + * @return array + */ + public function toArray(): array + { + $categories = []; + $category_scores = []; + foreach ($this->categories as $category) { + $categories[$category->category->value] = $category->violated; + $category_scores[$category->category->value] = $category->score; + } + + return [ + 'categories' => $categories, + 'category_scores' => $category_scores, + 'flagged' => $this->flagged, + ]; + } +} diff --git a/src/ValueObjects/Transporter/Payload.php b/src/ValueObjects/Transporter/Payload.php index 2a413351..298b20b3 100644 --- a/src/ValueObjects/Transporter/Payload.php +++ b/src/ValueObjects/Transporter/Payload.php @@ -6,6 +6,7 @@ use GuzzleHttp\Psr7\MultipartStream; use GuzzleHttp\Psr7\Request as Psr7Request; +use OpenAI\Contracts\Request; use OpenAI\Enums\Transporter\ContentType; use OpenAI\Enums\Transporter\Method; use OpenAI\ValueObjects\ResourceUri; diff --git a/tests/Fixtures/Completion.php b/tests/Fixtures/Completion.php index cce1b5d8..5d9734d2 100644 --- a/tests/Fixtures/Completion.php +++ b/tests/Fixtures/Completion.php @@ -10,12 +10,13 @@ function completion(): array 'object' => 'text_completion', 'created' => 1664136088, 'model' => 'davinci', - 'choices' => [[ - 'text' => "el, she elaborates more on the Corruptor's role, suggesting K", - 'index' => 0, - 'logprobs' => null, - 'finish_reason' => 'length', - ], + 'choices' => [ + [ + 'text' => "el, she elaborates more on the Corruptor's role, suggesting K", + 'index' => 0, + 'logprobs' => null, + 'finish_reason' => 'length', + ], ], 'usage' => [ 'prompt_tokens' => 1, diff --git a/tests/Resources/Moderations.php b/tests/Resources/Moderations.php index 46ed61fc..6e92c271 100644 --- a/tests/Resources/Moderations.php +++ b/tests/Resources/Moderations.php @@ -1,5 +1,10 @@ 'text-moderation-latest', @@ -11,5 +16,20 @@ 'input' => 'I want to kill them.', ]); - expect($result)->toBeArray()->toBe(moderationResource()); + expect($result) + ->toBeInstanceOf(CreateResponse::class) + ->id->toBe('modr-5MWoLO') + ->model->toBe('text-moderation-001') + ->results->toBeArray()->toHaveCount(1) + ->results->each->toBeInstanceOf(CreateResponseModerationResult::class); + + expect($result->results[0]) + ->flagged->toBeTrue() + ->categories->toHaveCount(7) + ->each->toBeInstanceOf(CreateResponseModerationCategory::class); + + expect($result->results[0]->categories[0]) + ->category->toBe(Category::Hate) + ->violated->toBe(false) + ->score->toBe(0.22714105248451233); }); diff --git a/tests/Responses/Moderations/CreateResponse.php b/tests/Responses/Moderations/CreateResponse.php new file mode 100644 index 00000000..7947a0a3 --- /dev/null +++ b/tests/Responses/Moderations/CreateResponse.php @@ -0,0 +1,24 @@ +toBeInstanceOf(CreateResponse::class) + ->id->toBe('modr-5MWoLO') + ->model->toBe('text-moderation-001') + ->results->toBeArray()->toHaveCount(1) + ->results->each->toBeInstanceOf(CreateResponseModerationResult::class); +}); + +test('returns the original api response', function () { + $moderation = CreateResponseFactory::new(moderationResource()); + + expect($moderation->toArray()) + ->toBeArray() + ->toBe(moderationResource()); +}); diff --git a/tests/Responses/Moderations/CreateResponseModerationCategory.php b/tests/Responses/Moderations/CreateResponseModerationCategory.php new file mode 100644 index 00000000..1b479d79 --- /dev/null +++ b/tests/Responses/Moderations/CreateResponseModerationCategory.php @@ -0,0 +1,17 @@ + Category::Hate->value, + 'violated' => true, + 'score' => 0.1234, + ]); + + expect($category) + ->category->toBe(Category::Hate) + ->violated->toBe(true) + ->score->toBe(0.1234); +}); diff --git a/tests/Responses/Moderations/CreateResponseModerationResult.php b/tests/Responses/Moderations/CreateResponseModerationResult.php new file mode 100644 index 00000000..950d13fb --- /dev/null +++ b/tests/Responses/Moderations/CreateResponseModerationResult.php @@ -0,0 +1,20 @@ +flagged->toBeTrue() + ->categories->toHaveCount(7) + ->each->toBeInstanceOf(CreateResponseModerationCategory::class); +}); + +test('create array from create response moderation result', function () { + $result = CreateResponseModerationResultFactory::new(moderationResource()['results'][0]); + + expect($result->toArray()) + ->toBe(moderationResource()['results'][0]); +});