Releases: prism-php/prism
v0.97.2
What's Changed
- feat: filter livewire morph markers by @sixlive in #710
- feat(streaming|openai): Add provider tool event type by @sixlive in #707
- feat(streaming|anthropic): implement Anthropic provider tool call handling in streaming responses by @roymckenzie in #712
Full Changelog: v0.97.1...v0.97.2
v0.97.1
What's Changed
- fix(formatting): run formatter for new rector formatting guides by @roymckenzie in #705
- Update OpenAI error handling by @barryvdh in #704
- fix(openai): correct usage field path for reasoning tokens by @sixlive in #708
- feat(openai): add support for text_verbosity parameter by @sixlive in #709
New Contributors
Full Changelog: v0.97.0...v0.97.1
v0.97.0
What's Changed
- Apply latest rector formatting updates by @sixlive in #688
- fix(stream/anthropic): replace TollResultEvent::toolResult with correct ToolResult instance by @roymckenzie in #694
- refactor: use proper facade pattern by @sixlive in #699
- feat(api) Use HasPrompts to pass system prompt to image requests by @aimeos in #696
- feat(gemini): add full support for gemini-2.5-flash-image by @senecolas in #700
- feat:(api): Add support for OpenAI service_tier (Priority) configuration by @robertmarney in #695
- feat(api): Optional model parameter by @aimeos in #684
Breaking Changes
\Prism\Prism\Prism::text()is now\Prism\Prism\Facades\Prism::text()- see notes in #699- You can use
find . -path ./vendor -prune -o -type f -name "*.php" -exec perl -i -pe 's/Prism\\Prism\\Prism/Prism\\Prism\\Facades\\Prism/g' {} +as a quick fix.
- You can use
New Contributors
- @senecolas made their first contribution in #700
- @robertmarney made their first contribution in #695
Full Changelog: v0.96.1...v0.97.0
v0.96.1
v0.96.0
v0.95.0
What's Changed
- docs(anthropic): update prompt caching examples by @sixlive in #672
- docs(guidelines): update guidelines by @pushpak1300 in #675
- docs(openrouter): Improve documentation for OpenRouter provider options and model routing by @kargnas in #673
Full Changelog: v0.94.0...v0.95.0
v0.94.0
What's Changed
- Feat/add cache support openrouter provider by @BoweFrankema in #670
- feat(openrouter): Add missing features - more parameters and accepts video/audio/image inputs by @kargnas in #671
New Contributors
- @BoweFrankema made their first contribution in #670
Full Changelog: v0.93.0...v0.94.0
v0.93.0
What's Changed
- feat(openai) Extend Schema's to allow for newly added values for the OpenAI API by @mysticseagull in #639
- Use media value object for generated audio and image by @aimeos in #621
- feat(OpenRouter): configure OpenRouter site headers and streamline header filtering by @kargnas in #669
- fix(OpenRouter):fix the exception caused by calling the gpt-5 model when there are no parameter tools by @hipig in #666
- [Bug] Image handling in Gemini ImageRequestMap by @aimeos in #667
Full Changelog: v0.92.0...v0.93.0
v0.92.0
What's Changed
⚠️ BREAKING CHANGES
1. Removed Chunk Class and ChunkType Enum
What changed:
- The
Prism\Prism\Text\Chunkclass has been removed - The
Prism\Prism\Enums\ChunkTypeenum has been removed PendingRequest::asStream()now returnsGenerator<StreamEvent>instead ofGenerator<Chunk>
Before:
use Prism\Prism\Text\Chunk;
use Prism\Prism\Enums\ChunkType;
foreach ($prism->text()->asStream() as $chunk) {
// $chunk is instance of Chunk
echo $chunk->text;
if ($chunk->chunkType === ChunkType::ToolCall) {
// Handle tool calls
}
if ($chunk->finishReason) {
// Stream complete
}
}After:
use Prism\Prism\Streaming\Events\{
StreamStartEvent,
TextDeltaEvent,
TextCompleteEvent,
ToolCallEvent,
StreamEndEvent
};
foreach ($prism->text()->asStream() as $event) {
// $event is instance of StreamEvent
match ($event::class) {
StreamStartEvent::class => /* stream started */,
TextDeltaEvent::class => echo $event->delta,
ToolCallEvent::class => /* handle tool call */,
StreamEndEvent::class => /* stream complete */,
default => /* other events */
};
}2. Exception Renamed
What changed:
PrismChunkDecodeExceptionrenamed toPrismStreamDecodeException
Migration:
// Before
use Prism\Prism\Exceptions\PrismChunkDecodeException;
try {
// ...
} catch (PrismChunkDecodeException $e) {
// ...
}
// After
use Prism\Prism\Exceptions\PrismStreamDecodeException;
try {
// ...
} catch (PrismStreamDecodeException $e) {
// ...
}3. Stream Event Type Changes
The streaming output is now composed of 12 distinct event types, each with specific purposes:
| Event | Purpose |
|---|---|
StreamStartEvent |
Emitted once at stream initialization |
TextStartEvent |
Emitted before first text delta |
TextDeltaEvent |
Contains incremental text chunks |
TextCompleteEvent |
Text generation complete |
ThinkingStartEvent |
Reasoning/thinking block started |
ThinkingEvent |
Incremental thinking content |
ThinkingCompleteEvent |
Thinking block complete |
ToolCallEvent |
Tool invocation requested |
ToolResultEvent |
Tool execution result |
CitationEvent |
Source citation (Anthropic) |
ErrorEvent |
Recoverable or fatal error |
StreamEndEvent |
Stream complete with final metadata |
Migration Guide
Basic Streaming
Before (Chunk-based):
$response = '';
foreach ($prism->text()->asStream() as $chunk) {
$response .= $chunk->text;
}
echo $response;After (Event-based):
use Prism\Prism\Streaming\Events\TextDeltaEvent;
$response = '';
foreach ($prism->text()->asStream() as $event) {
if ($event instanceof TextDeltaEvent) {
$response .= $event->delta;
}
}
echo $response;Handling Tool Calls
Before:
foreach ($prism->text()->asStream() as $chunk) {
if ($chunk->chunkType === ChunkType::ToolCall) {
foreach ($chunk->toolCalls as $toolCall) {
// Handle tool call
}
}
}After:
use Prism\Prism\Streaming\Events\ToolCallEvent;
foreach ($prism->text()->asStream() as $event) {
if ($event instanceof ToolCallEvent) {
// Handle tool call
$toolCall = $event->toolCall;
}
}Detecting Stream Completion
Before:
foreach ($prism->text()->asStream() as $chunk) {
if ($chunk->finishReason) {
echo "Stream finished: {$chunk->finishReason->value}";
}
}After:
use Prism\Prism\Streaming\Events\StreamEndEvent;
foreach ($prism->text()->asStream() as $event) {
if ($event instanceof StreamEndEvent) {
echo "Stream finished: {$event->finishReason->value}";
// Access usage metadata: $event->usage
}
}Handling Thinking/Reasoning
Before:
foreach ($prism->text()->asStream() as $chunk) {
if ($chunk->chunkType === ChunkType::Thinking) {
echo "Thinking: {$chunk->text}";
}
}After:
use Prism\Prism\Streaming\Events\ThinkingEvent;
foreach ($prism->text()->asStream() as $event) {
if ($event instanceof ThinkingEvent) {
echo "Thinking: {$event->delta}";
}
}New Features
1. Granular Stream Events
Each phase of streaming now emits specific events, enabling fine-grained control:
use Prism\Prism\Streaming\Events\{
StreamStartEvent,
TextStartEvent,
TextDeltaEvent,
TextCompleteEvent,
StreamEndEvent
};
foreach ($prism->text()->asStream() as $event) {
match ($event::class) {
StreamStartEvent::class => log('Stream started', [
'model' => $event->model,
'provider' => $event->provider,
]),
TextStartEvent::class => log('Text generation started'),
TextDeltaEvent::class => echo $event->delta,
TextCompleteEvent::class => log('Text generation complete'),
StreamEndEvent::class => log('Stream ended', [
'finish_reason' => $event->finishReason->value,
'usage' => $event->usage,
]),
};
}2. Streaming Adapters
Three new adapters for different streaming protocols:
Server-Sent Events (SSE)
return $prism->text()
->withPrompt('Tell me a story')
->asEventStreamResponse(); // Returns StreamedResponse with SSE formatData Protocol (Vercel AI SDK compatible)
return $prism->text()
->withPrompt('Tell me a story')
->asDataStreamResponse(); // Returns StreamedResponse with data protocolLaravel Broadcasting
use Illuminate\Broadcasting\Channel;
return $prism->text()
->withPrompt('Tell me a story')
->asBroadcastResponse(new Channel('prism-stream'));3. onComplete Callback
Register callbacks that execute when streaming completes:
$prism->text()
->withPrompt('Analyze this data')
->onComplete(function ($request, $messages) {
// Save conversation to database
Conversation::create([
'messages' => $messages,
'model' => $request->model,
]);
})
->asStream();The callback receives:
$request: The originalPendingRequestinstance$messages: Collection of finalMessageobjects
4. StreamCollector Utility
Automatically collects stream events into final messages:
use Prism\Prism\Streaming\StreamCollector;
$stream = $prism->text()->asStream();
$collector = new StreamCollector($stream, $request, $callback);
foreach ($collector->collect() as $event) {
// Events are yielded while being collected
}
// After iteration, access collected messages
$messages = $collector->messages();5. Testing Improvements
New fake methods for testing streaming:
use Prism\Prism\Testing\PrismFake;
PrismFake::fake([
'mistral-large-latest' => PrismFake::streamResponse([
'Text response content',
]),
]);
foreach ($prism->text()->asStream() as $event) {
// Test against stream events
}Internal Improvements
Unified StreamState Architecture
All 9 streaming providers now use a consistent StreamState object for managing streaming state:
Providers Refactored:
- Anthropic (AnthropicStreamState extension)
- OpenAI (base StreamState)
- Gemini (base StreamState)
- Groq (base StreamState)
- Mistral (base StreamState)
- Ollama (OllamaStreamState extension)
- XAI (base StreamState)
- DeepSeek (base StreamState)
- OpenRouter (base StreamState)
Benefits:
- Consistent state management across all providers
- Fluent API for state mutations (
withMessageId(),appendText(), etc.) - Provider extensibility via inheritance (e.g.,
OllamaStreamStatefor token accumulation) - Type-safe operations with strong typing throughout
- Easier debugging with centralized state tracking
Example (before/after):
// Before: Scattered instance properties
protected string $messageId = '';
protected bool $streamStarted = false;
protected string $currentText = '';
$this->messageId = EventID::generate();
$this->streamStarted = true;
$this->currentText .= $delta;
// After: Unified state object with fluent API
protected StreamState $state;
$this->state
->withMessageId(EventID::generate())
->markStreamStarted()
->appendText($delta);Full Changelog: v0.91.1...v0.92.0
v0.92.1
What's Changed
- fix(openrouter): structured response_type value by @sixlive in #657
- fix(gemini): adding fallbacks to Gemini Streaming by @jrking4 in #653
- fix(DeepSeek|OpenRouter): Fix exceptions during tool conversion without parameters by @hipig in #661
- fix(Anthropic MessageMap): ensure empty toolCall arguments dictionary not array by @roymckenzie in #660
- doc: use correct model name for provider config in configuration page by @alaminfirdows in #662
New Contributors
- @alaminfirdows made their first contribution in #662
Full Changelog: v0.91.0...v0.91.1