@@ -23,9 +23,11 @@ Utopia Framework requires PHP 8.0 or later. We recommend using the latest PHP ve
2323
2424- ** Multiple AI Providers** - Support for OpenAI, Anthropic, Deepseek, Perplexity, XAI, Gemini, and OpenRouter APIs
2525- ** Flexible Message Types** - Support for text and structured content in messages
26+ - ** Message Attachments** - Attach files (for example images) directly to conversation turns
2627- ** Conversation Management** - Easy-to-use conversation handling between agents and users
2728- ** Model Selection** - Choose from various AI models (GPT-4, Claude 3, Deepseek Chat, Sonar, Grok, etc.)
2829- ** Parameter Control** - Fine-tune model behavior with temperature and token controls
30+ - ** Streaming Output** - Consume incremental model output through callback-driven Server-Sent Events (SSE) streams
2931
3032## Usage
3133
@@ -35,8 +37,8 @@ Utopia Framework requires PHP 8.0 or later. We recommend using the latest PHP ve
3537<?php
3638
3739use Utopia\Agents\Agent;
40+ use Utopia\Agents\Message;
3841use Utopia\Agents\Roles\User;
39- use Utopia\Agents\Messages\Text;
4042use Utopia\Agents\Conversation;
4143use Utopia\Agents\Adapters\OpenAI;
4244
@@ -50,7 +52,7 @@ $user = new User('user-1', 'John');
5052// Start a conversation
5153$conversation = new Conversation($agent);
5254$conversation
53- ->message($user, new Text ('What is artificial intelligence?'))
55+ ->message($user, new Message ('What is artificial intelligence?'))
5456 ->send();
5557```
5658
@@ -182,7 +184,7 @@ $openrouter = new OpenRouter(
182184``` php
183185use Utopia\Agents\Roles\User;
184186use Utopia\Agents\Roles\Assistant;
185- use Utopia\Agents\Messages\Text ;
187+ use Utopia\Agents\Message ;
186188
187189// Create a conversation with system instructions
188190$agent = new Agent($adapter);
@@ -197,26 +199,190 @@ $assistant = new Assistant('assistant-1');
197199
198200$conversation = new Conversation($agent);
199201$conversation
200- ->message($user, new Text('Hello!'))
201- ->message($assistant, new Text('Hi! How can I help you today?'))
202- ->message($user, new Text('What is the capital of France?'));
202+ ->message($user, new Message('Hello!'))
203+ ->message($assistant, new Message('Hi! How can I help you today?'))
204+ ->message($user, new Message('What is the capital of France?'));
205+
206+ // Add a user message with attachments
207+ $conversation->message(
208+ $user,
209+ new Message('Please summarize this screenshot'),
210+ [new Message($imageBinaryContent)]
211+ );
203212
204213// Send and get response
205214$response = $conversation->send();
206215```
207216
217+ ### Streaming Responses (SSE)
218+
219+ The conversation layer supports incremental output streaming through ` Conversation::listen(callable $listener) ` .
220+ The callback receives each text delta as it arrives from the provider's SSE stream, while ` send() ` still returns the final aggregated ` Message ` .
221+
222+ #### Streaming in CLI / Worker Contexts
223+
224+ ``` php
225+ use Utopia\Agents\Agent;
226+ use Utopia\Agents\Conversation;
227+ use Utopia\Agents\Adapters\OpenAI;
228+ use Utopia\Agents\Message;
229+ use Utopia\Agents\Roles\User;
230+
231+ $agent = new Agent(new OpenAI('your-api-key', OpenAI::MODEL_GPT_4O));
232+ $conversation = new Conversation($agent);
233+ $user = new User('user-1', 'John');
234+
235+ $conversation
236+ ->listen(function (string $chunk): void {
237+ echo $chunk; // render partial output as soon as it is received
238+ })
239+ ->message($user, new Message('Explain vector databases in one paragraph.'));
240+
241+ $final = $conversation->send(); // final, complete assistant message
242+ ```
243+
244+ #### Exposing Model Output as HTTP SSE
245+
246+ ``` php
247+ use Utopia\Agents\Agent;
248+ use Utopia\Agents\Conversation;
249+ use Utopia\Agents\Adapters\OpenAI;
250+ use Utopia\Agents\Message;
251+ use Utopia\Agents\Roles\User;
252+
253+ header('Content-Type: text/event-stream');
254+ header('Cache-Control: no-cache');
255+ header('Connection: keep-alive');
256+
257+ $agent = new Agent(new OpenAI('your-api-key', OpenAI::MODEL_GPT_4O));
258+ $conversation = new Conversation($agent);
259+ $user = new User('user-1', 'John');
260+
261+ $conversation
262+ ->listen(function (string $chunk): void {
263+ // Send each token delta as an SSE frame
264+ echo 'data: '.json_encode(['delta' => $chunk], JSON_UNESCAPED_UNICODE)."\n\n";
265+
266+ if (function_exists('ob_flush')) {
267+ @ob_flush();
268+ }
269+ flush();
270+ })
271+ ->message($user, new Message('Write a short release note for today''s deployment.'));
272+
273+ $final = $conversation->send();
274+
275+ // Optional terminal event with complete text
276+ echo 'event: done'."\n";
277+ echo 'data: '.json_encode(['message' => $final->getContent()], JSON_UNESCAPED_UNICODE)."\n\n";
278+ echo 'data: [DONE]'."\n\n";
279+ flush();
280+ ```
281+
282+ #### Operational Notes
283+
284+ - Streaming is adapter-dependent and available for chat-capable providers that expose incremental output.
285+ - The listener is optional; if omitted, responses are still collected and returned as a single final message.
286+ - Keep callbacks non-blocking and lightweight to avoid slowing downstream token delivery.
287+ - When serving SSE over HTTP, send ` Content-Type: text/event-stream ` , flush frequently, and disable intermediary buffering where applicable.
288+ - Usage metrics (input/output tokens and cache counters, where supported) remain available after ` send() ` completes.
289+
208290### Working with Messages
209291
210292``` php
211- use Utopia\Agents\Messages\Text;
212- use Utopia\Agents\Messages\Image;
293+ use Utopia\Agents\Message;
213294
214- // Text message
215- $textMessage = new Text ('Hello, how are you?');
295+ // Message content is always text
296+ $textMessage = new Message ('Hello, how are you?');
216297
217- // Image message
218- $imageMessage = new Image ($imageBinaryContent);
298+ // Attachments are binary payloads (for example images)
299+ $imageMessage = new Message ($imageBinaryContent);
219300$mimeType = $imageMessage->getMimeType(); // Get the MIME type of the image
301+
302+ // Attach image to a text prompt
303+ $message = (new Message('Describe this image'))->addAttachment($imageMessage);
304+ ```
305+
306+ ### Attachment Examples
307+
308+ ``` php
309+ use Utopia\Agents\Conversation;
310+ use Utopia\Agents\Message;
311+ use Utopia\Agents\Roles\User;
312+
313+ $conversation = new Conversation($agent);
314+ $user = new User('user-1', 'John');
315+
316+ // 1) Attach a single image in the same turn
317+ $conversation->message(
318+ $user,
319+ new Message('What is shown here?'),
320+ [new Message(file_get_contents(__DIR__.'/images/screenshot.png'))]
321+ );
322+
323+ // 2) Attach multiple images in one turn
324+ $conversation->message(
325+ $user,
326+ new Message('Compare these two images and list differences.'),
327+ [
328+ new Message(file_get_contents(__DIR__.'/images/before.png')),
329+ new Message(file_get_contents(__DIR__.'/images/after.png')),
330+ ]
331+ );
332+
333+ // 3) Build and reuse a message object with attachments
334+ $prompt = (new Message('Extract visible text from this receipt'))
335+ ->addAttachment(new Message(file_get_contents(__DIR__.'/images/receipt.jpg')));
336+
337+ $conversation->message($user, $prompt);
338+ ```
339+
340+ ### Attachment Limits and Validation
341+
342+ Attachment validation is enforced by default in ` Conversation::message(...) ` .
343+ Guardrail values come from the selected adapter (not from conversation-level user configuration).
344+
345+ Default adapter guardrails:
346+
347+ - Max attachments per message: ` 10 `
348+ - Max binary size per attachment: ` 5_000_000 ` bytes (~ 5 MB)
349+ - Max total attachment payload per turn: ` 20_000_000 ` bytes (~ 20 MB)
350+ - MIME allowlist: ` image/png ` , ` image/jpeg ` , ` image/webp ` , ` image/gif `
351+ - Reject empty or unreadable payloads
352+ - Adapter compatibility checks (attachment type must be supported by the selected adapter)
353+
354+ To customize limits, create an adapter subclass and override limit methods:
355+
356+ ``` php
357+ <?php
358+
359+ use Utopia\Agents\Adapters\OpenAI;
360+
361+ class StrictOpenAI extends OpenAI
362+ {
363+ public function getMaxAttachmentsPerMessage(): ?int
364+ {
365+ return 3;
366+ }
367+
368+ public function getMaxAttachmentBytes(): ?int
369+ {
370+ return 2_000_000;
371+ }
372+
373+ public function getMaxTotalAttachmentBytes(): ?int
374+ {
375+ return 6_000_000;
376+ }
377+
378+ /**
379+ * @return list<string >|null
380+ */
381+ public function getAllowedAttachmentMimeTypes(): ?array
382+ {
383+ return ['image/png', 'image/jpeg'];
384+ }
385+ }
220386```
221387
222388## Schema and Schema Objects
0 commit comments