{"id":20284854,"url":"https://github.com/sourceability/portal","last_synced_at":"2025-04-11T08:38:39.430Z","repository":{"id":151779242,"uuid":"618477722","full_name":"sourceability/portal","owner":"sourceability","description":"A CLI and PHP Library that helps getting structured data out from GPT.","archived":false,"fork":false,"pushed_at":"2023-04-07T09:43:14.000Z","size":98,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-25T06:11:12.616Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sourceability.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-24T14:54:26.000Z","updated_at":"2023-04-19T21:03:49.000Z","dependencies_parsed_at":"2023-05-02T23:15:37.512Z","dependency_job_id":null,"html_url":"https://github.com/sourceability/portal","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourceability%2Fportal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourceability%2Fportal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourceability%2Fportal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourceability%2Fportal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sourceability","download_url":"https://codeload.github.com/sourceability/portal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248362139,"owners_count":21091059,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T14:22:29.413Z","updated_at":"2025-04-11T08:38:39.422Z","avatar_url":"https://github.com/sourceability.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sourceability/portal\n\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://user-images.githubusercontent.com/611271/227591267-815bc626-5a78-4332-9129-11b341b6d4ae.png\" width=\"150\" /\u003e\n\u003c/p\u003e\n\nA CLI and PHP Library that helps getting structured data out from GPT.\n\nGiven a [JSON Schema][json_schema], GPT is [perfectly capable of outputting JSON that conforms to the schema][blog_gpt_json_schema].\nThis approach enables GPT to be used programmatically for non-conversational use cases.\n\nFor example, before parsing a user uploaded CSV, you could ask GPT to map its headers to the ones your code supports:\n```bash\n$ bin/portal ./examples/csv_headers.yaml '{\n  \"supportedHeaders\":[\"firstName\",\"age\"], \n  \"headers\":[\"Prenom\",\"Nom Famille\",\"Annees\"]}'\n...\n\nCompletion Results:\n===================\n\n{\n    \"mappedHeaders\": {\n        \"Prenom\": \"firstName\",\n        \"Nom Famille\": null,\n        \"Annees\": \"age\"\n    }\n}\n```\n\n⚠️ Note that this library is experimental, and the API will change.\n\nYou are welcome to contribute by submitting issues, ideas, PRs, etc 🙂.\n\n## Installation\n\n```\ncomposer require sourceability/portal\n```\n\n### Trying out\n\nYou can try out YAML spells with docker:\n\n```\ngit clone https://github.com/sourceability/portal.git\ncd portal\nmake php\nbin/portal ./examples/csv_headers.yaml\n```\n\n### Symfony support\n\nThe library includes a Symfony bundle.\n\nAdd the bundle to `config/bundles.php`:\n```php\nreturn [\n    // ...\n    Sourceability\\Portal\\Bundle\\SourceabilityPortalBundle::class =\u003e ['all' =\u003e true],\n];\n```\n\nThen define the `OPENAI_API_KEY=sk-XXX` environment variable, for example in `.env.local`.\n\nYou can also configure the bundle:\n```yaml\n# config/packages/sourceability_portal.yaml\nsourceability_portal:\n    openai_api_key: '%my_openai_api_key%'\n```\n\nYou can invoke your service spells using their FQCN with the cast command (don't forget the quotes):\n```\nbin/console portal:cast 'App\\Portal\\MySpell'\n```\n\nYou can also define a short name with the `#[AutoconfigureSpell]` attribute:\n```php\nuse Sourceability\\Portal\\Bundle\\DependencyInjection\\Attribute\\AutoconfigureSpell;\n\n#[AutoconfigureSpell('Categorize')]\nclass CategorizeSpell implements Spell\n{\n```\n\nAnd invoke the spell with `bin/console portal:cast Categorize`\n\n## Static YAML\n\nYou can invoke portal with the path to a `.yaml` with the following format:\n```yaml\nschema:\n    properties:\n        barbar:\n            type: string\nexamples:\n    - foobar: hello\n    - foobar: world\nprompt: |\n    Do something.\n  \n    {{ foobar }}\n```\n\n```\nvendor/bin/portal my_spell.yaml\n```\n\n## Spell\n\nThe [Spell][Spell.php] interface is the main way to interact with this library.\n\nYou can think of a Spell as a way to create a function whose \"implementation\" is a GPT prompt:\n```php\n$spell = new StaticSpell(\n    schema: [\n        'type' =\u003e 'array',\n        'items' =\u003e ['type' =\u003e 'string']\n    ],\n    prompt: 'Synonyms of {{ input }}'\n);\n\n/** @var callable(string): array\u003cstring\u003e $generateSynonyms */\n$generateSynonyms = $portal-\u003ecallableFromSpell($spell);\n\ndump($generateSynonyms('car'));\n\narray:5 [▼\n  0 =\u003e \"automobile\"\n  1 =\u003e \"vehicle\"\n  2 =\u003e \"motorcar\"\n  3 =\u003e \"machine\"\n  4 =\u003e \"transport\"\n]\n```\n\n```php\nuse Sourceability\\Portal\\Spell\\Spell;\n\n/*\n * @implements Spell\u003cTInput, TOutput\u003e\n */\nclass MySpell implements Spell\n```\nA spell is defined by its Input/Output types `TInput` and `TOutput`.\nSo for example, a spell that accepts a number and returns an array of string, would use `Spell\u003cint, string\u003cstring\u003e\u003e`.\n\n### `getSchema`\n\nWith the `getSchema` you return a JSON Schema:\n```php\n/**\n * @return string|array\u003cstring, mixed\u003e|JsonSerializable The JSON-Schema of the desired completion output.\n */\npublic function getSchema(): string|array|JsonSerializable;\n```\n\nMake sure to leverage the [description][json_schema_description] and [examples][json_schema_examples] properties to give GPT more context and instructions:\n```php\npublic function getSchema()\n{\n    return [\n        'type' =\u003e 'object',\n        'properties' =\u003e [\n            'release' =\u003e [\n                'description' =\u003e 'The release reference/key.',\n                'examples' =\u003e ['v1.0.1', 'rc3', '2022.48.2'],\n            ]\n        ],\n    ];\n}\n```\n\nNote that you can also leverage libraries that define a DSL to build schemas:\n- [goldspecdigital/oooas][goldspecdigital/oooas] - see [examples/goldspecdigital-oooas](./examples/goldspecdigital-oooas)\n- [swaggest/json-schema][swaggest/php-json-schema] - see [examples/swaggest](./examples/swaggest)\n\n### `getPrompt`\n\nThe `getPrompt` method is where you describe the desired behaviour:\n```php\n/**\n * @param TInput $input\n */\npublic function getPrompt($input): string\n{\n    return sprintf('Do something with ' . $input);\n}\n```\n\n### `transcribe`\nFinally, you can transform the json decoded GPT output into your output type:\n```php\n/**\n * @param array\u003cmixed\u003e $completionValue\n * @return array\u003cTOutput\u003e\n */\npublic function transcribe(array $completionValue): array\n{\n    return array_map(fn ($item) =\u003e new Money($item), $completionValue);\n}\n```\n\n### `getExamples`\n\nThe `getExamples` method returns 0 or many inputs examples. This is very useful when iterating on a prompt.\n```php\n/**\n * @return array\u003cTInput\u003e\n */\npublic function getExamples(): array;\n```\n\n### Casting\n\nOnce you've done all that, you can cast try your spell examples:\n```\nvendor/bin/portal 'App\\Portal\\FraudSpell'\n```\n\nOr invoke your spell with the PHP Api:\n```php\n$portal = new Portal(...);\n\n$result = $portal-\u003ecast(\n    new FraudSpell(),\n    ['user' =\u003e $user-\u003etoArray()] // This contains TInput\n);\n\n// $result-\u003evalue contains array\u003cTOutput\u003e\nactOnThe($result-\u003evalue);\n```\n\n## `$portal-\u003etransfer`\n\nIf you don't need the Spell `getExamples` and `transcribe`, you can use `transfer`:\n```php\n$transferResult = $portal-\u003etransfer(\n    ['type' =\u003e 'string'], // output schema\n    'The prompt'\n);\n$transferResult-\u003evalue; // the json decoded value\n```\n\n## CLI\n\nYou can pass your own JSON example to the portal cli:\n```\nbin/portal spell.yaml '[{\"hello\":[\"worlds\"]},{\"hello\":[]}]'\n```\n\nUse `-v`, `-vv`, `-vvv` to print more information like the prompts or the OpenAI API requests/responses.\n\n## ApiPlatformSpell\n\nThe `ApiPlatformSpell` uses [API Platform][api-platform]'s to generate the JSON Schema but also to deserialize the JSON result.\n\nYou must implement the following methods:\n- `getClass`\n- `getPrompt`\n\nThe following are optional:\n- `isCollection` is false by default, you can return true instead\n- `getExamples` is empty by default, you can add your examples\n\n```php\nuse Sourceability\\Portal\\Spell\\ApiPlatformSpell;\n\n/**\n * @extends ApiPlatformSpell\u003cstring, array\u003cPart\u003e\u003e\n */\nclass PartListSpell extends ApiPlatformSpell\n{\n    public function getExamples(): array\n    {\n        return [\n            'smartwatch',\n            'bookshelf speaker',\n        ];\n    }\n\n    public function getPrompt($input): string\n    {\n        return sprintf('A list of parts to build a %s.', $input);\n    }\n    \n    protected function isCollection(): bool\n    {\n        return true;\n    }\n    \n    protected function getClass(): string\n    {\n        return Part::class;\n    }\n}\n```\n\nYou can then use the `#[ApiProperty]` attribute to add context to your schema:\n```php\nuse ApiPlatform\\Metadata\\ApiProperty;\n\nclass Part\n{\n    #[ApiProperty(\n        description: 'Product description',\n        schema: ['maxLength' =\u003e 100],\n    )]\n    public string $description;\n}\n```\n\n## Examples\n\nSee [./examples/](./examples).\n\n[json_schema]: https://json-schema.org\n[json_schema_description]: https://www.learnjsonschema.com/2020-12/meta-data/description/\n[json_schema_examples]: https://www.learnjsonschema.com/2020-12/meta-data/examples/\n[blog_gpt_json_schema]: https://blog.humphd.org/pouring-language-through-shape/\n[Spell.php]: src/Spell/Spell.php\n[api-platform]: https://api-platform.com\n[goldspecdigital/oooas]: https://github.com/goldspecdigital/oooas\n[swaggest/php-json-schema]: https://github.com/swaggest/php-json-schema\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsourceability%2Fportal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsourceability%2Fportal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsourceability%2Fportal/lists"}