Last active
January 8, 2025 19:16
-
-
Save leroy/fd3c081c3f9ccd667192bc652565f40f to your computer and use it in GitHub Desktop.
Laravel/PHP helper to create structured output (json schema) for OpenAI api's
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Support\Schema; | |
use App\Support\Schema\Attributes\Description; | |
use Illuminate\Support\Str; | |
use ReflectionAttribute; | |
use ReflectionClass; | |
use ReflectionProperty; | |
use Illuminate\Support\Arr; | |
use App\Support\Schema\Attributes\Type; | |
use Spatie\LaravelData\Attributes\DataCollectionOf; | |
function structured_output(string $type): array | |
{ | |
$name = Str::snake(Arr::last(explode("\\", $type))); | |
return [ | |
'type' => 'json_schema', | |
'json_schema' => [ | |
'name' => $name, | |
'schema' => schema($type) | |
] | |
]; | |
} | |
function schema(string $type): array | |
{ | |
if ($type === 'array') { | |
return schema_array($type); | |
} | |
if (is_builtin_type($type)) { | |
return schema_builtin($type); | |
} | |
return schema_object($type); | |
} | |
function schema_object(string $type): array | |
{ | |
$r = new ReflectionClass($type); | |
$properties = collect($r->getProperties()) | |
->filter(fn(ReflectionProperty $property) => $property->isPublic()) | |
->mapWithKeys(function (ReflectionProperty $property) { | |
$name = $property->getName(); | |
return [$name => schema_property($property)]; | |
}); | |
return [ | |
'type' => 'object', | |
'properties' => $properties->toArray() | |
]; | |
} | |
function schema_property(ReflectionProperty $property): array | |
{ | |
$type = $property->getType()->getName(); | |
if ($type === 'array') { | |
$type = Arr::first(attributes($property, [Type::class, DataCollectionOf::class])); | |
$schema = schema_array($type instanceof Type ? $type->type : $type->class); | |
goto schema; | |
} | |
if (is_builtin_type($type)) { | |
$schema = schema_builtin($type); | |
goto schema; | |
} | |
$schema = schema_object($type); | |
schema: | |
$description = Arr::first(attributes($property, [Description::class]))?->description; | |
if ($description !== null) { | |
$schema = schema_description($schema, $description); | |
} | |
return $schema; | |
} | |
function schema_array(string|null $type): array | |
{ | |
return array_filter([ | |
'type' => 'array', | |
'items' => $type ? schema($type) : null | |
]); | |
} | |
function schema_builtin(string $type): array | |
{ | |
return [ | |
'type' => $type | |
]; | |
} | |
function schema_description(array $schema, string $description): array | |
{ | |
return [ | |
'description' => $description, | |
...$schema | |
]; | |
} | |
function is_builtin_type(string $type): bool | |
{ | |
return in_array($type, ['string', 'int', 'float', 'bool', 'array']); | |
} | |
/** | |
* @param ReflectionProperty $property | |
* @param array<string> $attributes | |
* @return array<mixed> | |
*/ | |
function attributes(ReflectionProperty $property, array $attributes): array | |
{ | |
return collect($attributes) | |
->map(fn(string $attribute) => $property->getAttributes($attribute)) | |
->flatten() | |
->map(fn(ReflectionAttribute $attribute) => $attribute->newInstance()) | |
->toArray(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Muscle extends Data | |
{ | |
public function __construct( | |
#[Description('Human readable name familiar to most')] | |
public string $name, | |
#[Description('Unique snaked_cased key of the name of the muscle')] | |
public string $key, | |
#[Description('Unique snaked_cased key of the group of muscles')] | |
public string $muscle_group | |
) { | |
} | |
} | |
class MuscleList extends Data | |
{ | |
public function __construct( | |
#[DataCollectionOf(Muscle::class)] | |
/** | |
* @var array<Muscle> $muscles | |
*/ | |
public array $muscles | |
) {} | |
} | |
OpenAI::chat()->create([ | |
"model" => "gpt-4o-mini", | |
"messages" => [ | |
[ | |
"role" => "system", | |
"content" => "Make a list of muscle used in strength training" | |
] | |
], | |
"response_format" => structured_output(MuscleList::class) | |
]); | |
MuscleList::from($result->choices[0]->message->content); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment