A CLI and PHP Library that helps getting structured data out from GPT.
Given a JSON Schema, GPT is perfectly capable of outputting JSON that conforms to the schema. This approach enables GPT to be used programmatically for non-conversational use cases.
For example, before parsing a user uploaded CSV, you could ask GPT to map its headers to the ones your code supports:
$ bin/portal ./examples/csv_headers.yaml '{ "supportedHeaders":["firstName","age"], "headers":["Prenom","Nom Famille","Annees"]}'
...
Completion Results:
===================
{
"mappedHeaders": {
"Prenom": "firstName",
"Nom Famille": null,
"Annees": "age"
}
}You are welcome to contribute by submitting issues, ideas, PRs, etc 🙂.
composer require sourceability/portal
You can try out YAML spells with docker:
git clone https://github.com/sourceability/portal.git
cd portal
make php
bin/portal ./examples/csv_headers.yaml
The library includes a Symfony bundle.
Add the bundle to config/bundles.php:
return [
// ...Sourceability\Portal\Bundle\SourceabilityPortalBundle::class => ['all' => true],
];Then define the OPENAI_API_KEY=sk-XXX environment variable, for example in .env.local.
You can also configure the bundle:
# config/packages/sourceability_portal.yamlsourceability_portal:
openai_api_key: '%my_openai_api_key%'You can invoke your service spells using their FQCN with the cast command (don't forget the quotes):
bin/console portal:cast 'App\Portal\MySpell'
You can also define a short name with the #[AutoconfigureSpell] attribute:
useSourceability\Portal\Bundle\DependencyInjection\Attribute\AutoconfigureSpell;
#[AutoconfigureSpell('Categorize')]
class CategorizeSpell implements Spell
{And invoke the spell with bin/console portal:cast Categorize
You can invoke portal with the path to a .yaml with the following format:
schema:
properties:
barbar:
type: stringexamples:
- foobar: hello
- foobar: worldprompt: | Do something. {{ foobar }}vendor/bin/portal my_spell.yaml
The Spell interface is the main way to interact with this library.
You can think of a Spell as a way to create a function whose "implementation" is a GPT prompt:
$spell = newStaticSpell(
schema: [
'type' => 'array',
'items' => ['type' => 'string']
],
prompt: 'Synonyms of {{ input }}'
);
/** @var callable(string): array<string> $generateSynonyms */$generateSynonyms = $portal->callableFromSpell($spell);
dump($generateSynonyms('car'));
array:5 [▼
0 => "automobile"1 => "vehicle"
2 => "motorcar"3 => "machine"
4 => "transport"
]useSourceability\Portal\Spell\Spell;
/* * @implements Spell<TInput, TOutput> */class MySpell implements SpellA spell is defined by its Input/Output types TInput and TOutput.
So for example, a spell that accepts a number and returns an array of string, would use Spell<int, string<string>>.
With the getSchema you return a JSON Schema:
/** * @return string|array<string, mixed>|JsonSerializable The JSON-Schema of the desired completion output. */publicfunction getSchema(): string|array|JsonSerializable;Make sure to leverage the description and examples properties to give GPT more context and instructions:
publicfunctiongetSchema()
{
return [
'type' => 'object',
'properties' => [
'release' => [
'description' => 'The release reference/key.',
'examples' => ['v1.0.1', 'rc3', '2022.48.2'],
]
],
];
}Note that you can also leverage libraries that define a DSL to build schemas:
- goldspecdigital/oooas - see examples/goldspecdigital-oooas
- swaggest/json-schema - see examples/swaggest
The getPrompt method is where you describe the desired behaviour:
/** * @param TInput $input */publicfunctiongetPrompt($input): string
{
returnsprintf('Do something with ' . $input);
}Finally, you can transform the json decoded GPT output into your output type:
/** * @param array<mixed> $completionValue * @return array<TOutput> */publicfunctiontranscribe(array$completionValue): array
{
returnarray_map(fn ($item) => newMoney($item), $completionValue);
}The getExamples method returns 0 or many inputs examples. This is very useful when iterating on a prompt.
/** * @return array<TInput> */publicfunction getExamples(): array;Once you've done all that, you can cast try your spell examples:
vendor/bin/portal 'App\Portal\FraudSpell'
Or invoke your spell with the PHP Api:
$portal = newPortal(...);
$result = $portal->cast(
newFraudSpell(),
['user' => $user->toArray()] // This contains TInput
);
// $result->value contains array<TOutput>actOnThe($result->value);If you don't need the Spell getExamples and transcribe, you can use transfer:
$transferResult = $portal->transfer(
['type' => 'string'], // output schema'The prompt'
);
$transferResult->value; // the json decoded valueYou can pass your own JSON example to the portal cli:
bin/portal spell.yaml '[{"hello":["worlds"]},{"hello":[]}]'
Use -v, -vv, -vvv to print more information like the prompts or the OpenAI API requests/responses.
The ApiPlatformSpell uses API Platform's to generate the JSON Schema but also to deserialize the JSON result.
You must implement the following methods:
getClassgetPrompt
The following are optional:
isCollectionis false by default, you can return true insteadgetExamplesis empty by default, you can add your examples
useSourceability\Portal\Spell\ApiPlatformSpell;
/** * @extends ApiPlatformSpell<string, array<Part>> */class PartListSpell extends ApiPlatformSpell
{
publicfunctiongetExamples(): array
{
return [
'smartwatch',
'bookshelf speaker',
];
}
publicfunctiongetPrompt($input): string
{
returnsprintf('A list of parts to build a %s.', $input);
}
protectedfunctionisCollection(): bool
{
returntrue;
}
protectedfunctiongetClass(): string
{
return Part::class;
}
}You can then use the #[ApiProperty] attribute to add context to your schema:
useApiPlatform\Metadata\ApiProperty;
class Part
{
#[ApiProperty(
description: 'Product description',
schema: ['maxLength' => 100],
)]
publicstring$description;
}See ./examples/.
