Skip to content

Commit 1f3270a

Browse files
committed
refactor anthropic handlers
1 parent 6a835cc commit 1f3270a

File tree

7 files changed

+165
-114
lines changed

7 files changed

+165
-114
lines changed

src/Contracts/PrismRequest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace EchoLabs\Prism\Contracts;
6+
7+
interface PrismRequest {}

src/Embeddings/Request.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
namespace EchoLabs\Prism\Embeddings;
66

77
use Closure;
8+
use EchoLabs\Prism\Contracts\PrismRequest;
89

9-
class Request
10+
class Request implements PrismRequest
1011
{
1112
/**
1213
* @param array<string, mixed> $clientOptions
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace EchoLabs\Prism\Providers\Anthropic\Handlers;
6+
7+
use EchoLabs\Prism\Contracts\PrismRequest;
8+
use EchoLabs\Prism\Exceptions\PrismException;
9+
use EchoLabs\Prism\ValueObjects\ProviderResponse;
10+
use Illuminate\Http\Client\PendingRequest;
11+
use Illuminate\Http\Client\Response;
12+
use Throwable;
13+
14+
abstract class AnthropicHandlerAbstract
15+
{
16+
protected PrismRequest $request;
17+
18+
protected Response $httpResponse;
19+
20+
public function __construct(protected PendingRequest $client) {}
21+
22+
/**
23+
* @return array<string, mixed>
24+
*/
25+
abstract public static function buildHttpRequestPayload(PrismRequest $request): array;
26+
27+
public function handle(PrismRequest $request): ProviderResponse
28+
{
29+
$this->request = $request;
30+
31+
try {
32+
$this->prepareRequest();
33+
$this->httpResponse = $this->sendRequest();
34+
} catch (Throwable $e) {
35+
throw PrismException::providerRequestError($this->request->model, $e); // @phpstan-ignore property.notFound
36+
}
37+
38+
$data = $this->httpResponse->json();
39+
40+
if (data_get($data, 'type') === 'error') {
41+
throw PrismException::providerResponseError(vsprintf(
42+
'Anthropic Error: [%s] %s',
43+
[
44+
data_get($data, 'error.type', 'unknown'),
45+
data_get($data, 'error.message'),
46+
]
47+
));
48+
}
49+
50+
return $this->buildProviderResponse();
51+
}
52+
53+
abstract protected function prepareRequest(): void;
54+
55+
abstract protected function buildProviderResponse(): ProviderResponse;
56+
57+
protected function sendRequest(): Response
58+
{
59+
return $this->client->post(
60+
'messages',
61+
static::buildHttpRequestPayload($this->request)
62+
);
63+
}
64+
65+
/**
66+
* @param array<string, mixed> $data
67+
*/
68+
protected function extractText(array $data): string
69+
{
70+
return array_reduce(data_get($data, 'content', []), function (string $text, array $content): string {
71+
if (data_get($content, 'type') === 'text') {
72+
$text .= data_get($content, 'text');
73+
}
74+
75+
return $text;
76+
}, '');
77+
}
78+
}

src/Providers/Anthropic/Handlers/Structured.php

Lines changed: 40 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,57 @@
44

55
namespace EchoLabs\Prism\Providers\Anthropic\Handlers;
66

7-
use EchoLabs\Prism\Exceptions\PrismException;
7+
use EchoLabs\Prism\Contracts\PrismRequest;
88
use EchoLabs\Prism\Providers\Anthropic\Maps\FinishReasonMap;
99
use EchoLabs\Prism\Providers\Anthropic\Maps\MessageMap;
10-
use EchoLabs\Prism\Structured\Request;
10+
use EchoLabs\Prism\Structured\Request as StructuredRequest;
1111
use EchoLabs\Prism\ValueObjects\Messages\UserMessage;
1212
use EchoLabs\Prism\ValueObjects\ProviderResponse;
1313
use EchoLabs\Prism\ValueObjects\ResponseMeta;
1414
use EchoLabs\Prism\ValueObjects\Usage;
1515
use Illuminate\Http\Client\PendingRequest;
16-
use Illuminate\Http\Client\Response;
17-
use Throwable;
1816

19-
class Structured
17+
class Structured extends AnthropicHandlerAbstract
2018
{
19+
/**
20+
* @var StructuredRequest
21+
*/
22+
protected PrismRequest $request; // Redeclared for type hinting
23+
2124
public function __construct(protected PendingRequest $client) {}
2225

23-
public function handle(Request $request): ProviderResponse
26+
/**
27+
* @param StructuredRequest $request
28+
* @return array<string, mixed>
29+
*/
30+
#[\Override]
31+
public static function buildHttpRequestPayload(PrismRequest $request): array
2432
{
25-
try {
26-
$request = $this->appendMessageForJsonMode($request);
27-
$response = $this->sendRequest($request);
28-
$data = $response->json();
29-
30-
} catch (Throwable $e) {
31-
throw PrismException::providerRequestError($request->model, $e);
33+
if (! $request instanceof StructuredRequest) {
34+
throw new \InvalidArgumentException('Request must be an instance of '.StructuredRequest::class);
3235
}
3336

34-
if (data_get($data, 'type') === 'error') {
35-
throw PrismException::providerResponseError(vsprintf(
36-
'Anthropic Error: [%s] %s',
37-
[
38-
data_get($data, 'error.type', 'unknown'),
39-
data_get($data, 'error.message'),
40-
]
41-
));
42-
}
37+
return array_merge([
38+
'model' => $request->model,
39+
'messages' => MessageMap::map($request->messages),
40+
'max_tokens' => $request->maxTokens ?? 2048,
41+
], array_filter([
42+
'system' => MessageMap::mapSystemMessages($request->messages, $request->systemPrompt),
43+
'temperature' => $request->temperature,
44+
'top_p' => $request->topP,
45+
]));
46+
}
47+
48+
#[\Override]
49+
protected function prepareRequest(): void
50+
{
51+
$this->appendMessageForJsonMode();
52+
}
53+
54+
#[\Override]
55+
protected function buildProviderResponse(): ProviderResponse
56+
{
57+
$data = $this->httpResponse->json();
4358

4459
return new ProviderResponse(
4560
text: $this->extractText($data),
@@ -58,41 +73,11 @@ public function handle(Request $request): ProviderResponse
5873
);
5974
}
6075

61-
public function sendRequest(Request $request): Response
62-
{
63-
return $this->client->post(
64-
'messages',
65-
array_merge([
66-
'model' => $request->model,
67-
'messages' => MessageMap::map($request->messages),
68-
'max_tokens' => $request->maxTokens ?? 2048,
69-
], array_filter([
70-
'system' => MessageMap::mapSystemMessages($request->messages, $request->systemPrompt),
71-
'temperature' => $request->temperature,
72-
'top_p' => $request->topP,
73-
]))
74-
);
75-
}
76-
77-
/**
78-
* @param array<string, mixed> $data
79-
*/
80-
protected function extractText(array $data): string
81-
{
82-
return array_reduce(data_get($data, 'content', []), function (string $text, array $content): string {
83-
if (data_get($content, 'type') === 'text') {
84-
$text .= data_get($content, 'text');
85-
}
86-
87-
return $text;
88-
}, '');
89-
}
90-
91-
protected function appendMessageForJsonMode(Request $request): Request
76+
protected function appendMessageForJsonMode(): void
9277
{
93-
return $request->addMessage(new UserMessage(sprintf(
78+
$this->request->addMessage(new UserMessage(sprintf(
9479
"Respond with ONLY JSON that matches the following schema: \n %s",
95-
json_encode($request->schema->toArray(), JSON_PRETTY_PRINT)
80+
json_encode($this->request->schema->toArray(), JSON_PRETTY_PRINT)
9681
)));
9782
}
9883
}

src/Providers/Anthropic/Handlers/Text.php

Lines changed: 34 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,53 @@
44

55
namespace EchoLabs\Prism\Providers\Anthropic\Handlers;
66

7-
use EchoLabs\Prism\Exceptions\PrismException;
7+
use EchoLabs\Prism\Contracts\PrismRequest;
88
use EchoLabs\Prism\Providers\Anthropic\Maps\FinishReasonMap;
99
use EchoLabs\Prism\Providers\Anthropic\Maps\MessageMap;
1010
use EchoLabs\Prism\Providers\Anthropic\Maps\ToolChoiceMap;
1111
use EchoLabs\Prism\Providers\Anthropic\Maps\ToolMap;
12-
use EchoLabs\Prism\Text\Request;
12+
use EchoLabs\Prism\Text\Request as TextRequest;
1313
use EchoLabs\Prism\ValueObjects\ProviderResponse;
1414
use EchoLabs\Prism\ValueObjects\ResponseMeta;
1515
use EchoLabs\Prism\ValueObjects\ToolCall;
1616
use EchoLabs\Prism\ValueObjects\Usage;
17-
use Illuminate\Http\Client\PendingRequest;
18-
use Illuminate\Http\Client\Response;
19-
use Throwable;
2017

21-
class Text
18+
/**
19+
* @template TRequest of TextRequest
20+
*/
21+
class Text extends AnthropicHandlerAbstract
2222
{
23-
public function __construct(protected PendingRequest $client) {}
24-
25-
public function handle(Request $request): ProviderResponse
23+
/**
24+
* @param TextRequest $request
25+
* @return array<string, mixed>
26+
*/
27+
#[\Override]
28+
public static function buildHttpRequestPayload(PrismRequest $request): array
2629
{
27-
try {
28-
$response = $this->sendRequest($request);
29-
$data = $response->json();
30-
31-
} catch (Throwable $e) {
32-
throw PrismException::providerRequestError($request->model, $e);
30+
if (! $request instanceof TextRequest) {
31+
throw new \InvalidArgumentException('Request must be an instance of '.TextRequest::class);
3332
}
3433

35-
if (data_get($data, 'type') === 'error') {
36-
throw PrismException::providerResponseError(vsprintf(
37-
'Anthropic Error: [%s] %s',
38-
[
39-
data_get($data, 'error.type', 'unknown'),
40-
data_get($data, 'error.message'),
41-
]
42-
));
43-
}
34+
return array_merge([
35+
'model' => $request->model,
36+
'messages' => MessageMap::map($request->messages),
37+
'max_tokens' => $request->maxTokens ?? 2048,
38+
], array_filter([
39+
'system' => MessageMap::mapSystemMessages($request->messages, $request->systemPrompt),
40+
'temperature' => $request->temperature,
41+
'top_p' => $request->topP,
42+
'tools' => ToolMap::map($request->tools),
43+
'tool_choice' => ToolChoiceMap::map($request->toolChoice),
44+
]));
45+
}
46+
47+
#[\Override]
48+
protected function prepareRequest(): void {}
49+
50+
#[\Override]
51+
protected function buildProviderResponse(): ProviderResponse
52+
{
53+
$data = $this->httpResponse->json();
4454

4555
return new ProviderResponse(
4656
text: $this->extractText($data),
@@ -59,38 +69,6 @@ public function handle(Request $request): ProviderResponse
5969
);
6070
}
6171

62-
public function sendRequest(Request $request): Response
63-
{
64-
return $this->client->post(
65-
'messages',
66-
array_merge([
67-
'model' => $request->model,
68-
'messages' => MessageMap::map($request->messages),
69-
'max_tokens' => $request->maxTokens ?? 2048,
70-
], array_filter([
71-
'system' => MessageMap::mapSystemMessages($request->messages, $request->systemPrompt),
72-
'temperature' => $request->temperature,
73-
'top_p' => $request->topP,
74-
'tools' => ToolMap::map($request->tools),
75-
'tool_choice' => ToolChoiceMap::map($request->toolChoice),
76-
]))
77-
);
78-
}
79-
80-
/**
81-
* @param array<string, mixed> $data
82-
*/
83-
protected function extractText(array $data): string
84-
{
85-
return array_reduce(data_get($data, 'content', []), function (string $text, array $content): string {
86-
if (data_get($content, 'type') === 'text') {
87-
$text .= data_get($content, 'text');
88-
}
89-
90-
return $text;
91-
}, '');
92-
}
93-
9472
/**
9573
* @param array<string, mixed> $data
9674
* @return ToolCall[]

src/Structured/Request.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66

77
use Closure;
88
use EchoLabs\Prism\Contracts\Message;
9+
use EchoLabs\Prism\Contracts\PrismRequest;
910
use EchoLabs\Prism\Contracts\Schema;
1011
use EchoLabs\Prism\Enums\Provider;
1112
use EchoLabs\Prism\Enums\StructuredMode;
1213
use EchoLabs\Prism\ValueObjects\Messages\SystemMessage;
1314
use EchoLabs\Prism\ValueObjects\Messages\UserMessage;
1415

15-
class Request
16+
class Request implements PrismRequest
1617
{
1718
/**
1819
* @param array<int, Message> $messages

src/Text/Request.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66

77
use Closure;
88
use EchoLabs\Prism\Contracts\Message;
9+
use EchoLabs\Prism\Contracts\PrismRequest;
910
use EchoLabs\Prism\Enums\ToolChoice;
1011
use EchoLabs\Prism\Tool;
1112

12-
class Request
13+
class Request implements PrismRequest
1314
{
1415
/**
1516
* @param array<int, Message> $messages

0 commit comments

Comments
 (0)