Skip to content

Instantly share code, notes, and snippets.

@leroy
Last active January 8, 2025 19:16
Show Gist options
  • Save leroy/fd3c081c3f9ccd667192bc652565f40f to your computer and use it in GitHub Desktop.
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
<?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();
}
<?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