Skip to content

Commit 73d90dd

Browse files
committed
Add prism events
- Add Trace class that uses Laravel Context to keep track of operations stack - Add events - Add middlewares to trace http request and response. - Streams are traced with a `StreamWrapper` to keep stream intact. - Tracing of all not nested Prism operations is done in shared `PendingRequest` classes. - Tracing of tool calls is done in `CallsTools` if called or where tools are called. - Removed "'Maximum tool call" Exception in Anthropic Stream Handler and implemented `shouldContinue` check that does not raise an exception. - Added `$step` property in Anthropic Stream Handler so that we are not passing `$depth` everywhere. - Remove `isToolUseFinish` in `handleMessageStop`for ANthropic Stream Handler - Added `Content-Type` headers for Gemini and OpenAI stream tests, `Content-type` is used in client middleware to recognize if it's a streamed response or normal.
1 parent 8930da1 commit 73d90dd

File tree

20 files changed

+767
-34
lines changed

20 files changed

+767
-34
lines changed

src/Concerns/CallsTools.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
use Illuminate\Support\ItemNotFoundException;
88
use Illuminate\Support\MultipleItemsFoundException;
9+
use Prism\Prism\Events\ToolCallCompleted;
10+
use Prism\Prism\Events\ToolCallStarted;
911
use Prism\Prism\Exceptions\PrismException;
12+
use Prism\Prism\Support\Trace;
1013
use Prism\Prism\Tool;
1114
use Prism\Prism\ValueObjects\ToolCall;
1215
use Prism\Prism\ValueObjects\ToolResult;
@@ -25,12 +28,16 @@ protected function callTools(array $tools, array $toolCalls): array
2528
function (ToolCall $toolCall) use ($tools): ToolResult {
2629
$tool = $this->resolveTool($toolCall->name, $tools);
2730

31+
Trace::begin('tool_call', fn () => event(new ToolCallStarted($toolCall->name, $toolCall->arguments())));
32+
2833
try {
2934
$result = call_user_func_array(
3035
$tool->handle(...),
3136
$toolCall->arguments()
3237
);
3338

39+
Trace::end(fn () => event(new ToolCallCompleted(attributes: ['result' => $result])));
40+
3441
return new ToolResult(
3542
toolCallId: $toolCall->id,
3643
toolCallResultId: $toolCall->resultId,
@@ -39,6 +46,8 @@ function (ToolCall $toolCall) use ($tools): ToolResult {
3946
result: $result,
4047
);
4148
} catch (Throwable $e) {
49+
Trace::end(fn () => event(new ToolCallCompleted(exception: $e)));
50+
4251
if ($e instanceof PrismException) {
4352
throw $e;
4453
}

src/Concerns/InitializesClient.php

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,62 @@
55
namespace Prism\Prism\Concerns;
66

77
use Illuminate\Http\Client\PendingRequest;
8+
use Illuminate\Support\Arr;
89
use Illuminate\Support\Facades\Http;
10+
use Illuminate\Support\Str;
11+
use Prism\Prism\Events\HttpRequestCompleted;
12+
use Prism\Prism\Events\HttpRequestStarted;
13+
use Prism\Prism\Http\Stream\StreamWrapper;
14+
use Prism\Prism\Support\Trace;
915
use Psr\Http\Message\RequestInterface;
1016
use Psr\Http\Message\ResponseInterface;
1117

1218
trait InitializesClient
1319
{
1420
protected function baseClient(): PendingRequest
1521
{
16-
return Http::withRequestMiddleware(fn (RequestInterface $request): RequestInterface => $request)
17-
->withResponseMiddleware(fn (ResponseInterface $response): ResponseInterface => $response)
18-
->throw();
22+
return Http::throw()
23+
->withRequestMiddleware(
24+
function (RequestInterface $request): RequestInterface {
25+
Trace::begin(
26+
'http',
27+
fn () => event(new HttpRequestStarted(
28+
method: $request->getMethod(),
29+
url: (string) $request->getUri(),
30+
headers: Arr::mapWithKeys(
31+
$request->getHeaders(),
32+
fn ($value, $key) => [
33+
$key => in_array($key, ['Authentication', 'x-api-key', 'x-goog-api-key'])
34+
? Str::mask($value[0], '*', 3)
35+
: $value,
36+
]
37+
),
38+
attributes: json_decode((string) $request->getBody(), true),
39+
)),
40+
);
41+
42+
return $request;
43+
}
44+
)
45+
->withResponseMiddleware(
46+
function (ResponseInterface $response): ResponseInterface {
47+
if (! str_contains($response->getHeaderLine('Content-Type'), 'text/event-stream')) {
48+
Trace::end(
49+
fn () => event(new HttpRequestCompleted(
50+
statusCode: $response->getStatusCode(),
51+
headers: $response->getHeaders(),
52+
attributes: json_decode((string) $response->getBody(), true) ?? [],
53+
))
54+
);
55+
56+
return $response;
57+
}
58+
59+
// Wrap the stream to enable logging preserving the stream
60+
$loggingStream = new StreamWrapper($response);
61+
62+
return $response->withBody($loggingStream);
63+
}
64+
);
1965
}
2066
}

src/Embeddings/PendingRequest.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
use Prism\Prism\Concerns\ConfiguresClient;
88
use Prism\Prism\Concerns\ConfiguresProviders;
99
use Prism\Prism\Concerns\HasProviderOptions;
10+
use Prism\Prism\Events\PrismRequestCompleted;
11+
use Prism\Prism\Events\PrismRequestStarted;
1012
use Prism\Prism\Exceptions\PrismException;
13+
use Prism\Prism\Support\Trace;
1114
use Throwable;
1215

1316
class PendingRequest
@@ -69,9 +72,17 @@ public function asEmbeddings(): Response
6972

7073
$request = $this->toRequest();
7174

75+
Trace::begin('embeddings', fn () => event(new PrismRequestStarted($this->providerKey(), ['request' => $request])));
76+
7277
try {
73-
return $this->provider->embeddings($request);
78+
$response = $this->provider->embeddings($request);
79+
80+
Trace::end(fn () => event(new PrismRequestCompleted($this->providerKey(), ['response' => $response])));
81+
82+
return $response;
7483
} catch (Throwable $e) {
84+
Trace::end(fn () => event(new PrismRequestCompleted(exception: $e)));
85+
7586
$this->provider->handleRequestExceptions($request->model(), $e);
7687
}
7788
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Prism\Prism\Events;
6+
7+
class HttpRequestCompleted extends TraceEvent
8+
{
9+
/**
10+
* @param array<string, mixed> $attributes
11+
* @param string[][] $headers
12+
*/
13+
public function __construct(
14+
public readonly int $statusCode,
15+
public readonly array $headers,
16+
public readonly array $attributes,
17+
public readonly ?\Throwable $exception = null
18+
) {
19+
parent::__construct();
20+
}
21+
}

src/Events/HttpRequestStarted.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Prism\Prism\Events;
6+
7+
class HttpRequestStarted extends TraceEvent
8+
{
9+
/**
10+
* @param array<string, mixed> $attributes
11+
* @param string[][] $headers
12+
*/
13+
public function __construct(
14+
public readonly string $method,
15+
public readonly string $url,
16+
public readonly array $headers,
17+
public readonly array $attributes,
18+
) {
19+
parent::__construct();
20+
}
21+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Prism\Prism\Events;
6+
7+
class PrismRequestCompleted extends TraceEvent
8+
{
9+
/**
10+
* @param array<string, mixed> $attributes
11+
*/
12+
public function __construct(
13+
public readonly string $provider = '',
14+
public readonly array $attributes = [],
15+
public readonly ?\Throwable $exception = null
16+
) {
17+
parent::__construct();
18+
}
19+
}

src/Events/PrismRequestStarted.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Prism\Prism\Events;
6+
7+
class PrismRequestStarted extends TraceEvent
8+
{
9+
/**
10+
* @param array<string, mixed> $attributes
11+
*/
12+
public function __construct(
13+
public readonly string $provider,
14+
public readonly array $attributes
15+
) {
16+
parent::__construct();
17+
}
18+
}

src/Events/ToolCallCompleted.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Prism\Prism\Events;
6+
7+
class ToolCallCompleted extends TraceEvent
8+
{
9+
/**
10+
* @param array<string, mixed> $attributes
11+
*/
12+
public function __construct(
13+
public readonly array $attributes = [],
14+
public readonly ?\Throwable $exception = null
15+
) {
16+
parent::__construct();
17+
}
18+
}

src/Events/ToolCallStarted.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Prism\Prism\Events;
6+
7+
class ToolCallStarted extends TraceEvent
8+
{
9+
/**
10+
* @param array<string, mixed> $attributes
11+
*/
12+
public function __construct(
13+
public readonly string $toolName,
14+
public readonly array $attributes
15+
) {
16+
parent::__construct();
17+
}
18+
}

src/Events/TraceEvent.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Prism\Prism\Events;
6+
7+
use Illuminate\Foundation\Events\Dispatchable;
8+
use Illuminate\Queue\SerializesModels;
9+
use Prism\Prism\Support\Trace;
10+
11+
/**
12+
* @property-read array{traceId:string, parentTraceId:string|null, traceName:string, startTime:float, endTime:float|null}|null $trace
13+
*/
14+
class TraceEvent
15+
{
16+
use Dispatchable, SerializesModels;
17+
18+
/** @var array{traceId:string, parentTraceId:string|null, traceName:string, startTime:float, endTime:float|null}|null */
19+
public readonly ?array $trace;
20+
21+
public function __construct()
22+
{
23+
$this->trace = Trace::get();
24+
}
25+
}

0 commit comments

Comments
 (0)