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