Skip to content

Releases: prism-php/prism

v0.97.2

03 Nov 13:57
a5540b0

Choose a tag to compare

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

01 Nov 20:48
2fe0256

Choose a tag to compare

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

28 Oct 22:15
9114a78

Choose a tag to compare

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.

New Contributors

Full Changelog: v0.96.1...v0.97.0

v0.96.1

23 Oct 13:42
18d880c

Choose a tag to compare

What's Changed

Full Changelog: v0.96.0...v0.96.1

v0.96.0

21 Oct 22:22
be37e6f

Choose a tag to compare

What's Changed

  • feat(openaI): Add response ID to stream end event by @sixlive in #680
  • feat(mistral): Support thinking models by @sixlive in #681
  • fix(gemini): YouTube URL media handling by @sixlive in #682

Full Changelog: v0.95.0...v0.96.0

v0.95.0

20 Oct 12:19
258bae2

Choose a tag to compare

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

16 Oct 15:38
9e388ea

Choose a tag to compare

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

Full Changelog: v0.93.0...v0.94.0

v0.93.0

15 Oct 16:51
06e38db

Choose a tag to compare

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

14 Oct 20:13
90cd46f

Choose a tag to compare

What's Changed

⚠️ BREAKING CHANGES

1. Removed Chunk Class and ChunkType Enum

What changed:

  • The Prism\Prism\Text\Chunk class has been removed
  • The Prism\Prism\Enums\ChunkType enum has been removed
  • PendingRequest::asStream() now returns Generator<StreamEvent> instead of Generator<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:

  • PrismChunkDecodeException renamed to PrismStreamDecodeException

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 format

Data Protocol (Vercel AI SDK compatible)

return $prism->text()
    ->withPrompt('Tell me a story')
    ->asDataStreamResponse(); // Returns StreamedResponse with data protocol

Laravel 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 original PendingRequest instance
  • $messages: Collection of final Message objects

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., OllamaStreamState for 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

13 Oct 16:10
509586f

Choose a tag to compare

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

Full Changelog: v0.91.0...v0.91.1