diff --git a/src/Concerns/HasSuccessHandlers.php b/src/Concerns/HasSuccessHandlers.php new file mode 100644 index 00000000..cb8d3082 --- /dev/null +++ b/src/Concerns/HasSuccessHandlers.php @@ -0,0 +1,29 @@ + */ + protected array $handlers = []; + + public function onSuccess(callable $callable): self + { + $this->handlers[] = $callable; + + return $this; + } + + public function processSuccessHandlers(PrismRequest $request, TextResponse|StructuredResponse $response): void + { + foreach ($this->handlers as $handler) { + $handler($request, $response); + } + } +} diff --git a/src/Structured/PendingRequest.php b/src/Structured/PendingRequest.php index 66684638..3d894f8e 100644 --- a/src/Structured/PendingRequest.php +++ b/src/Structured/PendingRequest.php @@ -13,6 +13,7 @@ use Prism\Prism\Concerns\HasPrompts; use Prism\Prism\Concerns\HasProviderOptions; use Prism\Prism\Concerns\HasSchema; +use Prism\Prism\Concerns\HasSuccessHandlers; use Prism\Prism\Exceptions\PrismException; use Prism\Prism\ValueObjects\Messages\UserMessage; @@ -26,6 +27,7 @@ class PendingRequest use HasPrompts; use HasProviderOptions; use HasSchema; + use HasSuccessHandlers; /** * @deprecated Use `asStructured` instead. @@ -40,7 +42,10 @@ public function asStructured(): Response $request = $this->toRequest(); try { - return $this->provider->structured($request); + $response = $this->provider->structured($request); + $this->processSuccessHandlers($request, $response); + + return $response; } catch (RequestException $e) { $this->provider->handleRequestException($request->model(), $e); } diff --git a/src/Text/PendingRequest.php b/src/Text/PendingRequest.php index 71df0131..8b5a74c9 100644 --- a/src/Text/PendingRequest.php +++ b/src/Text/PendingRequest.php @@ -15,6 +15,7 @@ use Prism\Prism\Concerns\HasPrompts; use Prism\Prism\Concerns\HasProviderOptions; use Prism\Prism\Concerns\HasProviderTools; +use Prism\Prism\Concerns\HasSuccessHandlers; use Prism\Prism\Concerns\HasTools; use Prism\Prism\Exceptions\PrismException; use Prism\Prism\Tool; @@ -31,6 +32,7 @@ class PendingRequest use HasPrompts; use HasProviderOptions; use HasProviderTools; + use HasSuccessHandlers; use HasTools; /** @@ -46,7 +48,10 @@ public function asText(): Response $request = $this->toRequest(); try { - return $this->provider->text($request); + $response = $this->provider->text($request); + $this->processSuccessHandlers($request, $response); + + return $response; } catch (RequestException $e) { $this->provider->handleRequestException($request->model(), $e); } diff --git a/tests/Testing/AssertRequestTest.php b/tests/Testing/AssertRequestTest.php index 5de757f9..bc55da56 100644 --- a/tests/Testing/AssertRequestTest.php +++ b/tests/Testing/AssertRequestTest.php @@ -6,6 +6,7 @@ use Prism\Prism\Prism; use Prism\Prism\Testing\TextResponseFake; use Prism\Prism\ValueObjects\Usage; +use Tests\Fixtures\FixtureResponse; it('can generate text and assert provider', function (): void { $fakeResponse = TextResponseFake::make() @@ -52,3 +53,49 @@ expect($requests[0]->model())->toBe('gpt-4'); }); }); + +describe('Success Handlers', function (): void { + it('calls success handlers on successful response', function (): void { + + $fakeResponse = TextResponseFake::make() + ->withText('Hello, I am Claude!') + ->withUsage(new Usage(10, 20)); + + // Set up the fake + $fake = Prism::fake([$fakeResponse]); + + // Run your code + $response = Prism::text() + ->using(Provider::Anthropic, 'claude-3-5-sonnet-latest') + ->onSuccess(function ($request, $response): void { + expect($response->usage->promptTokens)->toBe(10); + expect($response->usage->completionTokens)->toBe(20); + expect($request->model())->toBe('claude-3-5-sonnet-latest'); + expect($request->provider())->toBe('anthropic'); + }) + ->withPrompt('Who are you?') + ->asText(); + + // Make assertions + expect($response->text)->toBe('Hello, I am Claude!'); + + $fake->assertRequest(function (array $requests): void { + expect($requests[0]->provider())->toBe('anthropic'); + expect($requests[0]->model())->toBe('claude-3-5-sonnet-latest'); + }); + }); + + it('does not call success handlers on unsuccessful response', function (): void { + + FixtureResponse::fakeResponseSequence('v1/messages', 'anthropic/generate-text-with-a-prompt', [], 404); + + $response = Prism::text() + ->using(Provider::Anthropic, 'claude-3-5-sonnet-latest') + ->onSuccess(function ($request, $response): void { + $this->fail('Reached success function, on failure'); + }) + ->withPrompt('Who are you?') + ->asText(); + + })->throws(\Prism\Prism\Exceptions\PrismException::class); +});