Skip to content
Merged
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
7 changes: 3 additions & 4 deletions src/Schemas/Anthropic/Maps/MessageMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Prism\Bedrock\Schemas\Anthropic\Maps;

use BackedEnum;
use Exception;
use Prism\Prism\Contracts\Message;
use Prism\Prism\Exceptions\PrismException;
use Prism\Prism\ValueObjects\Media\Image;
Expand Down Expand Up @@ -55,7 +54,7 @@ protected static function mapMessage(Message $message): array
UserMessage::class => self::mapUserMessage($message),
AssistantMessage::class => self::mapAssistantMessage($message),
ToolResultMessage::class => self::mapToolResultMessage($message),
default => throw new Exception('Could not map message type '.$message::class),
default => throw new PrismException('Anthropic: Could not map message type '.$message::class),
};
}

Expand Down Expand Up @@ -101,7 +100,7 @@ protected static function mapUserMessage(UserMessage $message): array
$cache_control = $cacheType ? ['type' => $cacheType instanceof BackedEnum ? $cacheType->value : $cacheType] : null;

if ($message->documents() !== []) {
throw new Exception('Documents are not yet supported by Anthropic on Bedrock.');
throw new PrismException('Anthropic: Documents are not yet supported by Anthropic on Bedrock.');
}

return [
Expand Down Expand Up @@ -129,7 +128,7 @@ protected static function mapAssistantMessage(AssistantMessage $message): array
$content = [];

if (isset($message->additionalContent['messagePartsWithCitations'])) {
throw new Exception('Citations are not yet supported by Anthropic on Bedrock.');
throw new PrismException('Anthropic: Citations are not yet supported by Anthropic on Bedrock.');
// TODO: update once citation support is supported by Anthropic on Bedrock
// foreach ($message->additionalContent['messagePartsWithCitations'] as $part) {
// $content[] = array_filter([
Expand Down
22 changes: 22 additions & 0 deletions src/Schemas/Converse/Concerns/ExtractsText.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Prism\Bedrock\Schemas\Converse\Concerns;

trait ExtractsText
{
/**
* @param array<string, mixed> $data
*/
protected function extractText(array $data): string
{
$content = data_get($data, 'output.message.content', []);

foreach ($content as $item) {
if ($text = data_get($item, 'text')) {
return $text;
}
}

return '';
}
}
11 changes: 6 additions & 5 deletions src/Schemas/Converse/ConverseTextHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Http\Client\Response;
use Illuminate\Support\Collection;
use Prism\Bedrock\Contracts\BedrockTextHandler;
use Prism\Bedrock\Schemas\Converse\Concerns\ExtractsText;
use Prism\Bedrock\Schemas\Converse\Concerns\ExtractsToolCalls;
use Prism\Bedrock\Schemas\Converse\Maps\FinishReasonMap;
use Prism\Bedrock\Schemas\Converse\Maps\MessageMap;
Expand All @@ -26,7 +27,7 @@

class ConverseTextHandler extends BedrockTextHandler
{
use CallsTools, ExtractsToolCalls;
use CallsTools, ExtractsText, ExtractsToolCalls;

protected TextResponse $tempResponse;

Expand Down Expand Up @@ -59,7 +60,7 @@ public function handle(Request $request): TextResponse
return match ($this->tempResponse->finishReason) {
FinishReason::ToolCalls => $this->handleToolCalls($request),
FinishReason::Stop, FinishReason::Length => $this->handleStop($request),
default => throw new PrismException('Anthropic: unknown finish reason'),
default => throw new PrismException('Converse: unknown finish reason'),
};
}

Expand Down Expand Up @@ -109,16 +110,16 @@ protected function prepareTempResponse(): void

$this->tempResponse = new TextResponse(
steps: new Collection,
text: data_get($data, 'output.message.content.0.text', ''),
text: $this->extractText($data),
finishReason: FinishReasonMap::map(data_get($data, 'stopReason')),
toolCalls: $this->extractToolCalls($data),
toolResults: [],
usage: new Usage(
promptTokens: data_get($data, 'usage.inputTokens'),
completionTokens: data_get($data, 'usage.outputTokens')
),
meta: new Meta(id: '', model: ''), // Not provided in Converse response.
messages: new Collection
meta: new Meta(id: '', model: ''),
messages: new Collection, // Not provided in Converse response.
);
}

Expand Down
3 changes: 1 addition & 2 deletions src/Schemas/Converse/Maps/MessageMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Prism\Bedrock\Schemas\Converse\Maps;

use Exception;
use Prism\Prism\Contracts\Message;
use Prism\Prism\Exceptions\PrismException;
use Prism\Prism\ValueObjects\Media\Document;
Expand Down Expand Up @@ -65,7 +64,7 @@ protected static function mapMessage(Message $message): array
AssistantMessage::class => self::mapAssistantMessage($message),
ToolResultMessage::class => self::mapToolResultMessage($message),
SystemMessage::class => self::mapSystemMessage($message),
default => throw new Exception('Could not map message type '.$message::class),
default => throw new PrismException('Converse: Could not map message type '.$message::class),
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"metrics":{"latencyMs":8274},"output":{"message":{"content":[{"reasoningContent":{"reasoningText":{"text":"We need to respond. The user asks \"Tell me a short story about a brave knight.\" Ask for a short story. Need to follow formatting \"role... content?\". The guideli\n\nThus we produce:\n\nrole: assistant\ncontent: |-\n ... story.\n\nWill ensure it's a short story about a brave knight. Should be creative.\n\nWe can produce a short story of maybe few paragraphs. Likely 3-5 paragraphs.\n\nLet's do.\n\n"}}},{"text":"role: assistant\ncontent: |-\n In the mist‑shrouded kingdom of Eldoria, tales of dragons and dark sorcery were as common as the sunrise. Yet none captured the hearts of the people quite li\n\n Alden had not earned his reputation through grand tournaments or lavish banquets. He grew up as the youngest son of a humble blacksmith, learning the clang o\n\n Without hesitation, Alden strapped his father's modest sword to his belt, took his battered shield—etched with a single oak leaf—and set out into the dying w\n\n At the heart of the forest, beneath a gnarled oak, Alden found the source of the curse: a crystal orb pulsing with black mist. As he approached, a specter of\n\n Light burst forth, bathing the forest in golden hues. Trees unfurled fresh leaves, and the sickly fog dissolved like a dream at dawn. The kingdom rejoiced, a\n\n Sir Alden's legend endured not because he wielded a mighty sword, but because his bravery was rooted in humility and love for his people. And when the wind r"}],"role":"assistant"}},"stopReason":"end_turn","usage":{"inputTokens":21,"outputTokens":765,"totalTokens":786}}
10 changes: 7 additions & 3 deletions tests/Schemas/Anthropic/AnthropicStructuredHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@
->usingTemperature(0)
->asStructured();

Http::assertSent(fn (Request $request): \Pest\Mixins\Expectation|\Pest\Expectation => expect($request->data())->toMatchArray([
'temperature' => 0,
]));
Http::assertSent(function (Request $request): bool {
expect($request->data())->toMatchArray([
'temperature' => 0,
]);

return true;
});
});
10 changes: 7 additions & 3 deletions tests/Schemas/Anthropic/AnthropicTextHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,11 @@
->usingTemperature(0)
->asText();

Http::assertSent(fn (Request $request): \Pest\Mixins\Expectation|\Pest\Expectation => expect($request->data())->toMatchArray([
'temperature' => 0,
]));
Http::assertSent(function (Request $request): bool {
expect($request->data())->toMatchArray([
'temperature' => 0,
]);

return true;
});
});
18 changes: 12 additions & 6 deletions tests/Schemas/Converse/ConverseStructuredHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,16 @@
->usingTemperature(0)
->asStructured();

Http::assertSent(fn (Request $request): \Pest\Mixins\Expectation|\Pest\Expectation => expect($request->data())->toMatchArray([
'inferenceConfig' => [
'maxTokens' => 2048,
'temperature' => 0,
],
])->not()->toHaveKey('guardRailConfig'));
Http::assertSent(function (Request $request): bool {
expect($request->data())->toMatchArray([
'inferenceConfig' => [
'maxTokens' => 2048,
'temperature' => 0,
],
])
->not()
->toHaveKey('guardRailConfig');

return true;
});
});
31 changes: 25 additions & 6 deletions tests/Schemas/Converse/ConverseTextHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@
expect($response->text)->toBe("I'm an AI system created by a team of inventors at Amazon. My purpose is to assist and provide information to the best of my ability. If you have any questions or need assistance, feel free to ask!");
});

it('can generate text with reasoning content', function (): void {
FixtureResponse::fakeResponseSequence('converse', 'converse/generate-text-with-reasoning-content');

$response = Prism::text()
->using('bedrock', 'openai.gpt-oss-120b-1:0')
->withPrompt('Tell me a short story about a brave knight.')
->asText();

expect($response->usage->promptTokens)
->toBe(21)
->and($response->usage->completionTokens)->toBe(765)
->and($response->text)->toContain('In the mist‑shrouded kingdom of Eldoria')
->and($response->text)->toContain('Sir Alden\'s legend endured');
});

it('can generate text with a system prompt', function (): void {
FixtureResponse::fakeResponseSequence('converse', 'converse/generate-text-with-system-prompt');

Expand Down Expand Up @@ -283,10 +298,14 @@
->usingTemperature(0)
->asText();

Http::assertSent(fn (Request $request): \Pest\Mixins\Expectation|\Pest\Expectation => expect($request->data())->toMatchArray([
'inferenceConfig' => [
'temperature' => 0,
'maxTokens' => 2048,
],
]));
Http::assertSent(function (Request $request): bool {
expect($request->data())->toMatchArray([
'inferenceConfig' => [
'temperature' => 0,
'maxTokens' => 2048,
],
]);

return true;
});
});