Skip to content

[RFC] Provider-centric, service-first API #80

Open
@javiereguiluz

Description

@javiereguiluz

Hi team!

Thank you for the impressive work so far. After reading the code for several days, I believe the user-facing API can be made more ergonomic. My proposal:

Replace "Platform + Model + Agent" with "Provider + Service + Model/Options"

This mirrors the mental model developers already see in vendor SDKs and eliminates boilerplate objects for common tasks while still allowing advanced builders when needed:

// Before
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);
$model = new GPT(GPT::GPT_4O_MINI, [
    'temperature' => 0.5, // default options for the model
]);

$agent = new Agent($platform, $model);
$messages = new MessageBag(
    Message::forSystem('You are a pirate and you write funny.'),
    Message::ofUser('What is the Symfony framework?'),
);
$response = $agent->call($messages, [
    'max_tokens' => 500, // specific options just for this call
]);

echo $response->getContent().\PHP_EOL;
// After
$response = OpenAI::chat(model: OpenAI::Gpt4oMini, options: [
        'max_tokens' => 500,
        'temperature' => 0.5,
    ])
    ->instructions('You are a pirate and you write funny')
    ->prompt('What is the Symfony framework?')
    ->ask();

echo $response->getContent().\PHP_EOL;

The "provider + service + model/options" flow feel more straightforward to me. Conceptually:

  Provider (OpenAI/Google/...)
+ service (chat/audio/image/code/...)
+ options (model and config options)

Also, the MessageBag idea looks "too Symfony-ish". And terms like forSystem and ofUser are OK but instructions and prompt look more natural for AI services.


File and URL helpers

Dedicated helpers make the code more readable:

// Before
$messages = new MessageBag(
    Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
    Message::ofUser(
        'Describe the image as a comedian would do it.',
        new ImageUrl('https://upload.wikimedia.org/.../Elephpant.svg.png'),
    ),
);

// After
// ...
->instructions('You are an image analyzer bot that helps identify the content of images')
->prompt('Describe the image as a comedian would do it')
->url('https://upload.wikimedia.org/.../Elephpant.svg.png')
// Before
$messages = new MessageBag(
    Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
    Message::ofUser(
        'Describe the image as a comedian would do it.',
        Image::fromFile(dirname(__DIR__, 2).'/fixtures/image.jpg'),
    ),
);

// After
// ...
->instructions('You are an image analyzer bot that helps identify the content of images')
->prompt('Describe the image as a comedian would do it')
// attach(string|File $pathOrFile)
->attach(dirname(__DIR__, 2).'/fixtures/image.jpg')

Consistency across services

The "provider + service + model/options" pattern scales across all OpenAI endpoints and to other providers:

Transcribe audio files:

// Before
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);
$model = new Whisper();
$file = Audio::fromFile(dirname(__DIR__, 2).'/fixtures/audio.mp3');

$response = $platform->request($model, $file);

echo $response->getContent().\PHP_EOL;
// After
$response = OpenAI::audio(model: OpenAI::Whisper1)
    // (optional)
    // ->instructions('...')
    // ->prompt('...')
    ->attach(__DIR__.'/some/path/file.m4a')
    // also available: ->translate()
    ->transcribe();

echo $response->getContent().\PHP_EOL;

Creating images:

// Before
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);

$response = $platform->request(
    model: new DallE(name: DallE::DALL_E_3),
    input: 'A cartoon-style elephant with a long trunk and large ears.',
    options: [
        'response_format' => 'url', // Generate response as URL
    ],
);

assert($response instanceof ImageResponse);

echo 'Revised Prompt: '.$response->revisedPrompt.\PHP_EOL.\PHP_EOL;

foreach ($response->getContent() as $index => $image) {
    echo 'Image '.$index.': '.$image->url.\PHP_EOL;
}
// After
$response = OpenAI::image(model: OpenAI::Dalle3, options: ['response_format' => 'url'])
    // (optional)
    // ->instructions('...')
    ->prompt('A cartoon-style elephant with a long trunk and large ears')
    ->generate();

echo 'Revised Prompt: '.$response->revisedPrompt.\PHP_EOL.\PHP_EOL;

foreach ($response->getContent() as $index => $image) {
    echo 'Image '.$index.': '.$image->url.\PHP_EOL;
}

Chatting with Google's AI:

// Before
$platform = PlatformFactory::create($_ENV['GOOGLE_API_KEY']);
$model = new Gemini(Gemini::GEMINI_2_FLASH);

$agent = new Agent($platform, $model);
$messages = new MessageBag(
    Message::forSystem('You are a pirate and you write funny.'),
    Message::ofUser('What is the Symfony framework?'),
);
$response = $agent->call($messages);

echo $response->getContent().\PHP_EOL;
// After
$response = Google::chat(model: Google::Gemini2Flash)
    ->instructions('You are a pirate and you write funny')
    ->prompt('What is the Symfony framework?')
    ->ask();

echo $response->getContent().\PHP_EOL;

Chatting with Anthropic's Claude:

// Before
$model = new Claude(Claude::SONNET_37);

$agent = new Agent($platform, $model);
$messages = new MessageBag(
    Message::ofUser(
        Document::fromFile(dirname(__DIR__, 2).'/fixtures/document.pdf'),
        'What is this document about?',
    ),
);
$response = $agent->call($messages);

echo $response->getContent().\PHP_EOL;
// After
$response = Anthropic::chat(model: Anthropic::ClaudeSonnet37)
    ->prompt('What is this document about?')
    ->attach(dirname(__DIR__, 2).'/fixtures/document.pdf')
    ->ask();

echo $response->getContent().\PHP_EOL;

Using the company name (Google, Anthropic, OpenAI) as the provider keeps the API stable even if product names (ChatGPT, Google AI Studio, Claude, ...) change.


TL;DR

I think the public API could be more concise and closer to the APIs of these AI services ... instead of doing things in the traditional Symfony way.

Metadata

Metadata

Assignees

No one assigned

    Labels

    RFCRFC = Request For Comments (proposals about features that you want to be discussed)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions