PHPackages                             sourceability/portal - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. sourceability/portal

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

sourceability/portal
====================

A CLI, PHP Library and Symfony Bundle that helps getting structured data out from GPT.

0.1.0(3y ago)106MITPHP

Since Apr 7Pushed 3y ago1 watchersCompare

[ Source](https://github.com/sourceability/portal)[ Packagist](https://packagist.org/packages/sourceability/portal)[ RSS](/packages/sourceability-portal/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (20)Versions (2)Used By (0)

sourceability/portal
====================

[](#sourceabilityportal)

[![](https://user-images.githubusercontent.com/611271/227591267-815bc626-5a78-4332-9129-11b341b6d4ae.png)](https://user-images.githubusercontent.com/611271/227591267-815bc626-5a78-4332-9129-11b341b6d4ae.png)

A CLI and PHP Library that helps getting structured data out from GPT.

Given a [JSON Schema](https://json-schema.org), GPT is [perfectly capable of outputting JSON that conforms to the schema](https://blog.humphd.org/pouring-language-through-shape/). 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"
    }
}
```

⚠️ Note that this library is experimental, and the API will change.

You are welcome to contribute by submitting issues, ideas, PRs, etc 🙂.

Installation
------------

[](#installation)

```
composer require sourceability/portal

```

### Trying out

[](#trying-out)

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

```

### Symfony support

[](#symfony-support)

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.yaml
sourceability_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:

```
use Sourceability\Portal\Bundle\DependencyInjection\Attribute\AutoconfigureSpell;

#[AutoconfigureSpell('Categorize')]
class CategorizeSpell implements Spell
{
```

And invoke the spell with `bin/console portal:cast Categorize`

Static YAML
-----------

[](#static-yaml)

You can invoke portal with the path to a `.yaml` with the following format:

```
schema:
    properties:
        barbar:
            type: string
examples:
    - foobar: hello
    - foobar: world
prompt: |
    Do something.

    {{ foobar }}
```

```
vendor/bin/portal my_spell.yaml

```

Spell
-----

[](#spell)

The [Spell](src/Spell/Spell.php) 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 = new StaticSpell(
    schema: [
        'type' => 'array',
        'items' => ['type' => 'string']
    ],
    prompt: 'Synonyms of {{ input }}'
);

/** @var callable(string): array $generateSynonyms */
$generateSynonyms = $portal->callableFromSpell($spell);

dump($generateSynonyms('car'));

array:5 [▼
  0 => "automobile"
  1 => "vehicle"
  2 => "motorcar"
  3 => "machine"
  4 => "transport"
]
```

```
use Sourceability\Portal\Spell\Spell;

/*
 * @implements Spell
 */
class MySpell implements Spell
```

A 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`.

### `getSchema`

[](#getschema)

With the `getSchema` you return a JSON Schema:

```
/**
 * @return string|array|JsonSerializable The JSON-Schema of the desired completion output.
 */
public function getSchema(): string|array|JsonSerializable;
```

Make sure to leverage the [description](https://www.learnjsonschema.com/2020-12/meta-data/description/) and [examples](https://www.learnjsonschema.com/2020-12/meta-data/examples/) properties to give GPT more context and instructions:

```
public function getSchema()
{
    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](https://github.com/goldspecdigital/oooas) - see [examples/goldspecdigital-oooas](./examples/goldspecdigital-oooas)
- [swaggest/json-schema](https://github.com/swaggest/php-json-schema) - see [examples/swaggest](./examples/swaggest)

### `getPrompt`

[](#getprompt)

The `getPrompt` method is where you describe the desired behaviour:

```
/**
 * @param TInput $input
 */
public function getPrompt($input): string
{
    return sprintf('Do something with ' . $input);
}
```

### `transcribe`

[](#transcribe)

Finally, you can transform the json decoded GPT output into your output type:

```
/**
 * @param array $completionValue
 * @return array
 */
public function transcribe(array $completionValue): array
{
    return array_map(fn ($item) => new Money($item), $completionValue);
}
```

### `getExamples`

[](#getexamples)

The `getExamples` method returns 0 or many inputs examples. This is very useful when iterating on a prompt.

```
/**
 * @return array
 */
public function getExamples(): array;
```

### Casting

[](#casting)

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 = new Portal(...);

$result = $portal->cast(
    new FraudSpell(),
    ['user' => $user->toArray()] // This contains TInput
);

// $result->value contains array
actOnThe($result->value);
```

`$portal->transfer`
-------------------

[](#portal-transfer)

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 value
```

CLI
---

[](#cli)

You 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.

ApiPlatformSpell
----------------

[](#apiplatformspell)

The `ApiPlatformSpell` uses [API Platform](https://api-platform.com)'s to generate the JSON Schema but also to deserialize the JSON result.

You must implement the following methods:

- `getClass`
- `getPrompt`

The following are optional:

- `isCollection` is false by default, you can return true instead
- `getExamples` is empty by default, you can add your examples

```
use Sourceability\Portal\Spell\ApiPlatformSpell;

/**
 * @extends ApiPlatformSpell
 */
class PartListSpell extends ApiPlatformSpell
{
    public function getExamples(): array
    {
        return [
            'smartwatch',
            'bookshelf speaker',
        ];
    }

    public function getPrompt($input): string
    {
        return sprintf('A list of parts to build a %s.', $input);
    }

    protected function isCollection(): bool
    {
        return true;
    }

    protected function getClass(): string
    {
        return Part::class;
    }
}
```

You can then use the `#[ApiProperty]` attribute to add context to your schema:

```
use ApiPlatform\Metadata\ApiProperty;

class Part
{
    #[ApiProperty(
        description: 'Product description',
        schema: ['maxLength' => 100],
    )]
    public string $description;
}
```

Examples
--------

[](#examples)

See [./examples/](./examples).

###  Health Score

19

—

LowBetter than 10% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity10

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity34

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

1135d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/014b3653b1f38cb85fa553f23e32a10a9584fee5dc80c7be2d01900202045033?d=identicon)[AdrienBrault](/maintainers/AdrienBrault)

![](https://avatars.githubusercontent.com/u/70618696?v=4)[Brendan Schlosser](/maintainers/brensch)[@brensch](https://github.com/brensch)

---

Top Contributors

[![adrienbrault](https://avatars.githubusercontent.com/u/611271?v=4)](https://github.com/adrienbrault "adrienbrault (25 commits)")

---

Tags

phpsymfonyjson-schemaopenaigptstructured

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StyleECS

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sourceability-portal/health.svg)

```
[![Health](https://phpackages.com/badges/sourceability-portal/health.svg)](https://phpackages.com/packages/sourceability-portal)
```

###  Alternatives

[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.3M152](/packages/sulu-sulu)[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[contao/core-bundle

Contao Open Source CMS

1231.6M2.4k](/packages/contao-core-bundle)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.2M386](/packages/shopware-core)[mahocommerce/maho

Free and open source ecommerce platform, created in 2024 on the M1 platform, PHP 8.3+

1322.1k12](/packages/mahocommerce-maho)[deepseek-php/deepseek-php-client

deepseek PHP client is a robust and community-driven PHP client library for seamless integration with the Deepseek API, offering efficient access to advanced AI and data processing capabilities.

47073.9k5](/packages/deepseek-php-deepseek-php-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
