Skip to main content

Events

Instructor dispatches events throughout the extraction and inference lifecycle. These events are automatically bridged to Laravel’s event system by the LaravelEventDispatcher, allowing you to listen and respond using standard Laravel patterns — listeners, subscribers, closures, and queued handlers. The bridge is implemented by Cognesy\Instructor\Laravel\Events\LaravelEventDispatcher, which lives in the packages/laravel package. It wraps Laravel’s native Illuminate\Contracts\Events\Dispatcher and forwards Instructor events to it based on your configuration.

Event Bridge Configuration

Configure event bridging in config/instructor.php:
return [
    'events' => [
        // Enable bridging to Laravel's event dispatcher
        'dispatch_to_laravel' => env('INSTRUCTOR_DISPATCH_EVENTS', true),
        // Specify which events to bridge (empty = all events)
        'bridge_events' => [
            \Cognesy\Instructor\Events\Extraction\ExtractionCompleted::class,
            \Cognesy\Instructor\Events\Extraction\ExtractionFailed::class,
        ],
    ],
];
// @doctest id="a7ec"
When bridge_events is empty (the default), every Instructor event is forwarded to Laravel’s dispatcher. To reduce overhead in production, list only the event classes your listeners actually need. The bridge uses instanceof matching, so listing a parent event class also bridges its subclasses.

Available Events

All events extend Cognesy\Instructor\Events\StructuredOutputEvent (which extends Cognesy\Events\Event). Events carry data in the $data property (an array or mixed value) rather than typed properties.

Extraction Events

Namespace: Cognesy\Instructor\Events\Extraction
EventDescription
ExtractionStartedExtraction pipeline has begun processing
ExtractionCompletedExtraction completed successfully
ExtractionFailedAll extraction strategies failed
ExtractionStrategyAttemptedAn extraction strategy was attempted
ExtractionStrategyFailedAn extraction strategy failed
ExtractionStrategySucceededAn extraction strategy succeeded

Response Events

Namespace: Cognesy\Instructor\Events\Response
EventDescription
ResponseValidationFailedResponse failed validation
ResponseValidatedResponse passed validation
ResponseDeserializedResponse was deserialized into an object
ResponseDeserializationFailedResponse deserialization failed
ResponseTransformedResponse was transformed
ResponseTransformationFailedResponse transformation failed
ResponseGenerationFailedResponse generation failed

Request Events

Namespace: Cognesy\Instructor\Events\Request
EventDescription
NewValidationRecoveryAttemptA validation recovery retry attempt is being made
StructuredOutputRecoveryLimitReachedMaximum retries exhausted
ResponseModelRequestedResponse model was requested
ResponseModelBuiltResponse model schema was built

Streaming Events

Namespace: Cognesy\Instructor\Events\PartialsGenerator
EventDescription
StreamedResponseReceivedStreaming response started
ChunkReceivedReceived a chunk of streaming data
StreamedResponseFinishedStreaming completed
PartialResponseGeneratedA partial response object was generated
StreamedToolCallStartedA streamed tool call started
StreamedToolCallUpdatedA streamed tool call was updated
StreamedToolCallCompletedA streamed tool call completed

Listening to Events

Using Event Listeners

Create a dedicated listener class and register it with Laravel’s event system.
// app/Listeners/LogExtractionCompleted.php
namespace App\Listeners;

use Cognesy\Instructor\Events\Extraction\ExtractionCompleted;
use Illuminate\Support\Facades\Log;

class LogExtractionCompleted
{
    public function handle(ExtractionCompleted $event): void
    {
        Log::info('Extraction completed', [
            'event' => $event->name(),
            'data' => $event->data,
        ]);
    }
}
// @doctest id="5370"
Register in EventServiceProvider:
// app/Providers/EventServiceProvider.php
namespace App\Providers;

use App\Listeners\LogExtractionCompleted;
use Cognesy\Instructor\Events\Extraction\ExtractionCompleted;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        ExtractionCompleted::class => [
            LogExtractionCompleted::class,
        ],
    ];
}
// @doctest id="fdc6"

Using Closures

For lightweight listeners, register closures directly in a service provider’s boot method.
// app/Providers/AppServiceProvider.php
namespace App\Providers;

use Cognesy\Instructor\Events\Extraction\ExtractionCompleted;
use Cognesy\Instructor\Events\Extraction\ExtractionFailed;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Event;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::listen(ExtractionCompleted::class, function ($event) {
            // Handle successful extraction
        });

        Event::listen(ExtractionFailed::class, function ($event) {
            // Handle failed extraction
        });
    }
}
// @doctest id="f43e"

Using Event Subscribers

Group related event handlers into a single subscriber class. This is convenient when you need to handle multiple Instructor events together.
// app/Listeners/InstructorEventSubscriber.php
namespace App\Listeners;

use Cognesy\Instructor\Events\Extraction\ExtractionCompleted;
use Cognesy\Instructor\Events\Extraction\ExtractionFailed;
use Cognesy\Instructor\Events\Extraction\ExtractionStarted;
use Illuminate\Events\Dispatcher;

class InstructorEventSubscriber
{
    public function handleStart(ExtractionStarted $event): void
    {
        // Log start
    }

    public function handleComplete(ExtractionCompleted $event): void
    {
        // Log completion
    }

    public function handleFailed(ExtractionFailed $event): void
    {
        // Alert on failure
    }

    public function subscribe(Dispatcher $events): array
    {
        return [
            ExtractionStarted::class => 'handleStart',
            ExtractionCompleted::class => 'handleComplete',
            ExtractionFailed::class => 'handleFailed',
        ];
    }
}
// @doctest id="8904"
Register it in EventServiceProvider:
// app/Providers/EventServiceProvider.php
namespace App\Providers;

use App\Listeners\InstructorEventSubscriber;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $subscribe = [
        InstructorEventSubscriber::class,
    ];
}
// @doctest id="8fc1"

Common Use Cases

Logging and Monitoring

All events carry data in the $data property (typically an array). Use the name() method to get the event class short name.
use Cognesy\Instructor\Events\Extraction\ExtractionCompleted;
use Cognesy\Instructor\Events\Extraction\ExtractionFailed;
use Illuminate\Support\Facades\Log;

Event::listen(ExtractionCompleted::class, function ($event) {
    Log::channel('llm')->info('Extraction successful', [
        'event' => $event->name(),
        'data' => $event->data,
    ]);
});

Event::listen(ExtractionFailed::class, function ($event) {
    Log::channel('llm')->error('Extraction failed', [
        'event' => $event->name(),
        'data' => $event->data,
    ]);
});
// @doctest id="2815"

Metrics and Analytics

use Cognesy\Instructor\Events\Extraction\ExtractionCompleted;
use App\Services\MetricsService;

Event::listen(ExtractionCompleted::class, function ($event) {
    app(MetricsService::class)->recordExtraction([
        'event' => $event->name(),
        'data' => $event->data,
    ]);
});
// @doctest id="5918"

Alerting on Failures

use Cognesy\Instructor\Events\Extraction\ExtractionFailed;
use Illuminate\Support\Facades\Notification;
use App\Notifications\ExtractionFailedNotification;

Event::listen(ExtractionFailed::class, function ($event) {
    Notification::route('slack', config('services.slack.webhook'))
        ->notify(new ExtractionFailedNotification($event));
});
// @doctest id="9f87"

Queued Event Listeners

For CPU-intensive or I/O-heavy processing, implement ShouldQueue to push the work onto a queue instead of running it inline.
// app/Listeners/ProcessExtractionAnalytics.php
namespace App\Listeners;

use Cognesy\Instructor\Events\Extraction\ExtractionCompleted;
use Illuminate\Contracts\Queue\ShouldQueue;

class ProcessExtractionAnalytics implements ShouldQueue
{
    public $queue = 'analytics';

    public function handle(ExtractionCompleted $event): void
    {
        // Heavy analytics processing runs on the queue
    }
}
// @doctest id="25f1"

Wiretap (Direct Event Handling)

The wiretap method provides direct access to the raw event stream without going through Laravel’s dispatcher. This is useful for low-level debugging or when you need to observe every internal event.
use Cognesy\Instructor\Laravel\Facades\StructuredOutput;
use Cognesy\Instructor\StructuredOutputRuntime;
use Cognesy\Polyglot\Inference\LLMProvider;

$runtime = StructuredOutputRuntime::fromProvider(LLMProvider::new())
    ->wiretap(function ($event) {
        // Called for every event in the pipeline
        logger()->debug('Event: ' . get_class($event));
    });

$person = StructuredOutput::withRuntime($runtime)->with(
    messages: 'Extract person data...',
    responseModel: PersonData::class,
)
->get();
// @doctest id="61cc"
The LaravelEventDispatcher itself also supports wiretap for registering global listeners that receive every event, regardless of class. These listeners run at the lowest priority after all class-specific and bridged listeners have executed.

Disabling Event Bridge

To disable event bridging entirely (for example, in high-throughput scenarios where the overhead is unacceptable):
return [
    'events' => [
        'dispatch_to_laravel' => false,
    ],
];
// @doctest id="4325"
Or via environment variable:
INSTRUCTOR_DISPATCH_EVENTS=false
// @doctest id="4644"
Disabling the bridge only stops events from being forwarded to Laravel’s dispatcher. Internal Instructor event listeners and wiretaps continue to work normally.

Testing Events

Use Laravel’s Event::fake() to assert that specific events were dispatched during a test.
use Cognesy\Instructor\Events\Extraction\ExtractionCompleted;
use Cognesy\Instructor\Laravel\Facades\StructuredOutput;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

final class EventBridgeTest extends TestCase
{
    public function test_dispatches_extraction_event(): void
    {
        Event::fake([ExtractionCompleted::class]);

        StructuredOutput::with(
            messages: 'John is 30',
            responseModel: PersonData::class,
        )->get();

        Event::assertDispatched(ExtractionCompleted::class);
    }
}
// @doctest id="bef6"
Assert event data with a closure:
Event::assertDispatched(ExtractionCompleted::class, function ($event) {
    return !empty($event->data);
});
// @doctest id="e013"