Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
matrix:
os: [ubuntu-latest]
php: [8.2, 8.3, 8.4]
laravel: [11.*, 12.*]
laravel: [11.31.*, 12.*]
stability: [prefer-lowest, prefer-stable]
include:
- laravel: 11.*
- laravel: 11.31.*
testbench: 9.*
carbon: ^2.63
- laravel: 12.*
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"require": {
"php": "^8.2",
"ext-fileinfo": "*",
"laravel/framework": "^11.0|^12.0"
"laravel/framework": "^11.31|^12.0"
},
"config": {
"allow-plugins": {
Expand Down
9 changes: 9 additions & 0 deletions src/Concerns/CallsTools.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

use Illuminate\Support\ItemNotFoundException;
use Illuminate\Support\MultipleItemsFoundException;
use Prism\Prism\Events\ToolCallCompleted;
use Prism\Prism\Events\ToolCallStarted;
use Prism\Prism\Exceptions\PrismException;
use Prism\Prism\Support\Trace;
use Prism\Prism\Tool;
use Prism\Prism\ValueObjects\ToolCall;
use Prism\Prism\ValueObjects\ToolResult;
Expand All @@ -25,12 +28,16 @@ protected function callTools(array $tools, array $toolCalls): array
function (ToolCall $toolCall) use ($tools): ToolResult {
$tool = $this->resolveTool($toolCall->name, $tools);

Trace::begin('tool_call', fn () => event(new ToolCallStarted($toolCall->name, $toolCall->arguments())));

try {
$result = call_user_func_array(
$tool->handle(...),
$toolCall->arguments()
);

Trace::end(fn () => event(new ToolCallCompleted(attributes: ['result' => $result])));

return new ToolResult(
toolCallId: $toolCall->id,
toolCallResultId: $toolCall->resultId,
Expand All @@ -39,6 +46,8 @@ function (ToolCall $toolCall) use ($tools): ToolResult {
result: $result,
);
} catch (Throwable $e) {
Trace::end(fn () => event(new ToolCallCompleted(exception: $e)));

if ($e instanceof PrismException) {
throw $e;
}
Expand Down
52 changes: 49 additions & 3 deletions src/Concerns/InitializesClient.php
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont know if I like the configuration method approach for this. I think I'd rather like to just return a base client with the middleware defined and then let the providers can customize the client as needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I submitted a PR #427 just for the client trait using a baseClient where we can add middlewares and the rest of configuration happens in provider client. Is that what you meant?

I separated in another PR because I refactored exceptions that were scattered in all handlers and that added a lot of noise that I'd like to keep separate from this PR.

// Provider class
protected function client(array $options = [], array $retry = [], ?string $baseUrl = null): PendingRequest
{
    return $this->baseClient()
        // configuration for that provider
        ->baseUrl($baseUrl ?? $this->url);
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,62 @@
namespace Prism\Prism\Concerns;

use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Prism\Prism\Events\HttpRequestCompleted;
use Prism\Prism\Events\HttpRequestStarted;
use Prism\Prism\Http\Stream\StreamWrapper;
use Prism\Prism\Support\Trace;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

trait InitializesClient
{
protected function baseClient(): PendingRequest
{
return Http::withRequestMiddleware(fn (RequestInterface $request): RequestInterface => $request)
->withResponseMiddleware(fn (ResponseInterface $response): ResponseInterface => $response)
->throw();
return Http::throw()
->withRequestMiddleware(
function (RequestInterface $request): RequestInterface {
Trace::begin(
'http',
fn () => event(new HttpRequestStarted(
method: $request->getMethod(),
url: (string) $request->getUri(),
headers: Arr::mapWithKeys(
$request->getHeaders(),
fn ($value, $key) => [
$key => in_array($key, ['Authentication', 'x-api-key', 'x-goog-api-key'])
? Str::mask($value[0], '*', 3)
: $value,
]
),
attributes: json_decode((string) $request->getBody(), true),
)),
);

return $request;
}
)
->withResponseMiddleware(
function (ResponseInterface $response): ResponseInterface {
if (! str_contains($response->getHeaderLine('Content-Type'), 'text/event-stream')) {
Trace::end(
fn () => event(new HttpRequestCompleted(
statusCode: $response->getStatusCode(),
headers: $response->getHeaders(),
attributes: json_decode((string) $response->getBody(), true) ?? [],
))
);

return $response;
}

// Wrap the stream to enable logging preserving the stream
$loggingStream = new StreamWrapper($response);

return $response->withBody($loggingStream);
}
);
}
}
13 changes: 12 additions & 1 deletion src/Embeddings/PendingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
use Prism\Prism\Concerns\ConfiguresClient;
use Prism\Prism\Concerns\ConfiguresProviders;
use Prism\Prism\Concerns\HasProviderOptions;
use Prism\Prism\Events\PrismRequestCompleted;
use Prism\Prism\Events\PrismRequestStarted;
use Prism\Prism\Exceptions\PrismException;
use Prism\Prism\Support\Trace;

class PendingRequest
{
Expand Down Expand Up @@ -69,9 +72,17 @@ public function asEmbeddings(): Response

$request = $this->toRequest();

Trace::begin('embeddings', fn () => event(new PrismRequestStarted($this->providerKey(), ['request' => $request])));

try {
return $this->provider->embeddings($request);
$response = $this->provider->embeddings($request);

Trace::end(fn () => event(new PrismRequestCompleted($this->providerKey(), ['response' => $response])));

return $response;
} catch (RequestException $e) {
Trace::end(fn () => event(new PrismRequestCompleted(exception: $e)));

$this->provider->handleRequestException($request->model(), $e);
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/Events/HttpRequestCompleted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Prism\Prism\Events;

class HttpRequestCompleted extends TraceEvent
{
/**
* @param array<string, mixed> $attributes
* @param string[][] $headers
*/
public function __construct(
public readonly int $statusCode,
public readonly array $headers,
public readonly array $attributes,
public readonly ?\Throwable $exception = null
) {
parent::__construct();
}
}
21 changes: 21 additions & 0 deletions src/Events/HttpRequestStarted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Prism\Prism\Events;

class HttpRequestStarted extends TraceEvent
{
/**
* @param array<string, mixed> $attributes
* @param string[][] $headers
*/
public function __construct(
public readonly string $method,
public readonly string $url,
public readonly array $headers,
public readonly array $attributes,
) {
parent::__construct();
}
}
19 changes: 19 additions & 0 deletions src/Events/PrismRequestCompleted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Prism\Prism\Events;

class PrismRequestCompleted extends TraceEvent
{
/**
* @param array<string, mixed> $attributes
*/
public function __construct(
public readonly string $provider = '',
public readonly array $attributes = [],
public readonly ?\Throwable $exception = null
) {
parent::__construct();
}
}
18 changes: 18 additions & 0 deletions src/Events/PrismRequestStarted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Prism\Prism\Events;

class PrismRequestStarted extends TraceEvent
{
/**
* @param array<string, mixed> $attributes
*/
public function __construct(
public readonly string $provider,
public readonly array $attributes
) {
parent::__construct();
}
}
18 changes: 18 additions & 0 deletions src/Events/ToolCallCompleted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Prism\Prism\Events;

class ToolCallCompleted extends TraceEvent
{
/**
* @param array<string, mixed> $attributes
*/
public function __construct(
public readonly array $attributes = [],
public readonly ?\Throwable $exception = null
) {
parent::__construct();
}
}
18 changes: 18 additions & 0 deletions src/Events/ToolCallStarted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Prism\Prism\Events;

class ToolCallStarted extends TraceEvent
{
/**
* @param array<string, mixed> $attributes
*/
public function __construct(
public readonly string $toolName,
public readonly array $attributes
) {
parent::__construct();
}
}
25 changes: 25 additions & 0 deletions src/Events/TraceEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Prism\Prism\Events;

use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Prism\Prism\Support\Trace;

/**
* @property-read array{traceId:string, parentTraceId:string|null, traceName:string, startTime:float, endTime:float|null}|null $trace
*/
class TraceEvent
{
use Dispatchable, SerializesModels;

/** @var array{traceId:string, parentTraceId:string|null, traceName:string, startTime:float, endTime:float|null}|null */
public readonly ?array $trace;

public function __construct()
{
$this->trace = Trace::get();
}
}
Loading