Skip to content

Commit e6385cc

Browse files
authored
feat: implement Bedrock and model clients for Claude, Llama, and Nova (#312)
- [x] Test for Prompt Converters - [x] Documentation for limits of invokeModel with tool calling using Nova models - [x] ~~Documentation for configuration~~ should be part of the bundle - [x] Image support for Claude and Nova using Bedrock - [x] Remove local model validation - [x] Example usage for Bedrock
1 parent 2cc5230 commit e6385cc

20 files changed

+938
-3
lines changed

.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ AZURE_OPENAI_WHISPER_API_VERSION=
2929
AZURE_LLAMA_BASEURL=
3030
AZURE_LLAMA_KEY=
3131

32+
# For using Bedrock
33+
AWS_ACCESS_KEY_ID=
34+
AWS_SECRET_ACCESS_KEY=
35+
AWS_DEFAULT_REGION=
36+
3237
# Hugging Face Access Token
3338
HUGGINGFACE_KEY=
3439

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,11 @@ $embeddings = new Embeddings();
6363

6464
* Language Models
6565
* [OpenAI's GPT](https://platform.openai.com/docs/models/overview) with [OpenAI](https://platform.openai.com/docs/overview) and [Azure](https://learn.microsoft.com/azure/ai-services/openai/concepts/models) as Platform
66-
* [Anthropic's Claude](https://www.anthropic.com/claude) with [Anthropic](https://www.anthropic.com/) as Platform
67-
* [Meta's Llama](https://www.llama.com/) with [Azure](https://learn.microsoft.com/azure/machine-learning/how-to-deploy-models-llama), [Ollama](https://ollama.com/) and [Replicate](https://replicate.com/) as Platform
66+
* [Anthropic's Claude](https://www.anthropic.com/claude) with [Anthropic](https://www.anthropic.com/) and [AWS](https://aws.amazon.com/bedrock/) as Platform
67+
* [Meta's Llama](https://www.llama.com/) with [Azure](https://learn.microsoft.com/azure/machine-learning/how-to-deploy-models-llama), [Ollama](https://ollama.com/), [Replicate](https://replicate.com/) and [AWS](https://aws.amazon.com/bedrock/) as Platform
6868
* [Google's Gemini](https://gemini.google.com/) with [Google](https://ai.google.dev/) and [OpenRouter](https://www.openrouter.com/) as Platform
6969
* [DeepSeek's R1](https://www.deepseek.com/) with [OpenRouter](https://www.openrouter.com/) as Platform
70+
* [Amazon's Nova](https://nova.amazon.com) with [AWS](https://aws.amazon.com/bedrock/) as Platform
7071
* Embeddings Models
7172
* [OpenAI's Text Embeddings](https://platform.openai.com/docs/guides/embeddings/embedding-models) with [OpenAI](https://platform.openai.com/docs/overview) and [Azure](https://learn.microsoft.com/azure/ai-services/openai/concepts/models) as Platform
7273
* [Voyage's Embeddings](https://docs.voyageai.com/docs/embeddings) with [Voyage](https://www.voyageai.com/) as Platform

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"require-dev": {
4343
"codewithkyrian/chromadb-php": "^0.2.1 || ^0.3 || ^0.4",
4444
"codewithkyrian/transformers": "^0.5.3",
45+
"async-aws/bedrock-runtime": "^0.1.0",
4546
"mongodb/mongodb": "^1.21",
4647
"php-cs-fixer/shim": "^3.70",
4748
"phpstan/phpstan": "^2.0",
@@ -59,6 +60,7 @@
5960
"symfony/var-dumper": "^6.4 || ^7.1"
6061
},
6162
"suggest": {
63+
"async-aws/bedrock-runtime": "For using the Bedrock platform.",
6264
"codewithkyrian/chromadb-php": "For using the ChromaDB as retrieval vector store.",
6365
"codewithkyrian/transformers": "For using the TransformersPHP with FFI to run models in PHP.",
6466
"mongodb/mongodb": "For using MongoDB Atlas as retrieval vector store.",

examples/bedrock/chat-claude.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
4+
use PhpLlm\LlmChain\Bridge\Anthropic\Claude;
5+
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
6+
use PhpLlm\LlmChain\Chain;
7+
use PhpLlm\LlmChain\Model\Message\Message;
8+
use PhpLlm\LlmChain\Model\Message\MessageBag;
9+
use Symfony\Component\Dotenv\Dotenv;
10+
11+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
12+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
13+
14+
if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
15+
) {
16+
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
17+
exit(1);
18+
}
19+
20+
$platform = PlatformFactory::create(
21+
new BedrockRuntimeClient()
22+
);
23+
$llm = new Claude();
24+
25+
$chain = new Chain($platform, $llm);
26+
$messages = new MessageBag(
27+
Message::forSystem('You are a pirate and you write funny.'),
28+
Message::ofUser('What is the Symfony framework?'),
29+
);
30+
$response = $chain->call($messages);
31+
32+
echo $response->getContent().PHP_EOL;

examples/bedrock/chat-llama.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
4+
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
5+
use PhpLlm\LlmChain\Bridge\Meta\Llama;
6+
use PhpLlm\LlmChain\Chain;
7+
use PhpLlm\LlmChain\Model\Message\Message;
8+
use PhpLlm\LlmChain\Model\Message\MessageBag;
9+
use Symfony\Component\Dotenv\Dotenv;
10+
11+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
12+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
13+
14+
if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
15+
) {
16+
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
17+
exit(1);
18+
}
19+
20+
$platform = PlatformFactory::create(
21+
new BedrockRuntimeClient()
22+
);
23+
$llm = new Llama('llama-3.2-3b-instruct');
24+
25+
$chain = new Chain($platform, $llm);
26+
$messages = new MessageBag(
27+
Message::forSystem('You are a pirate and you write funny.'),
28+
Message::ofUser('What is the Symfony framework?'),
29+
);
30+
$response = $chain->call($messages);
31+
32+
echo $response->getContent().PHP_EOL;

examples/bedrock/chat-nova.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
4+
use PhpLlm\LlmChain\Bridge\Bedrock\Nova\Nova;
5+
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
6+
use PhpLlm\LlmChain\Chain;
7+
use PhpLlm\LlmChain\Model\Message\Message;
8+
use PhpLlm\LlmChain\Model\Message\MessageBag;
9+
use Symfony\Component\Dotenv\Dotenv;
10+
11+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
12+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
13+
14+
if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
15+
) {
16+
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
17+
exit(1);
18+
}
19+
20+
$platform = PlatformFactory::create(
21+
new BedrockRuntimeClient()
22+
);
23+
$llm = new Nova(Nova::PRO);
24+
25+
$chain = new Chain($platform, $llm);
26+
$messages = new MessageBag(
27+
Message::forSystem('You are a pirate and you write funny.'),
28+
Message::ofUser('What is the Symfony framework?'),
29+
);
30+
$response = $chain->call($messages);
31+
32+
echo $response->getContent().PHP_EOL;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
4+
use PhpLlm\LlmChain\Bridge\Anthropic\Claude;
5+
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
6+
use PhpLlm\LlmChain\Chain;
7+
use PhpLlm\LlmChain\Model\Message\Content\Image;
8+
use PhpLlm\LlmChain\Model\Message\Message;
9+
use PhpLlm\LlmChain\Model\Message\MessageBag;
10+
use Symfony\Component\Dotenv\Dotenv;
11+
12+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
13+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
14+
15+
if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
16+
) {
17+
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
18+
exit(1);
19+
}
20+
21+
$platform = PlatformFactory::create(
22+
new BedrockRuntimeClient()
23+
);
24+
$llm = new Claude();
25+
26+
$chain = new Chain($platform, $llm);
27+
$messages = new MessageBag(
28+
Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
29+
Message::ofUser(
30+
'Describe the image as a comedian would do it.',
31+
Image::fromFile(dirname(__DIR__, 2).'/tests/Fixture/image.jpg'),
32+
),
33+
);
34+
$response = $chain->call($messages);
35+
36+
echo $response->getContent().PHP_EOL;

examples/bedrock/image-nova.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
4+
use PhpLlm\LlmChain\Bridge\Bedrock\Nova\Nova;
5+
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
6+
use PhpLlm\LlmChain\Chain;
7+
use PhpLlm\LlmChain\Model\Message\Content\Image;
8+
use PhpLlm\LlmChain\Model\Message\Message;
9+
use PhpLlm\LlmChain\Model\Message\MessageBag;
10+
use Symfony\Component\Dotenv\Dotenv;
11+
12+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
13+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
14+
15+
if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
16+
) {
17+
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
18+
exit(1);
19+
}
20+
21+
$platform = PlatformFactory::create(
22+
new BedrockRuntimeClient()
23+
);
24+
$llm = new Nova(Nova::PRO);
25+
26+
$chain = new Chain($platform, $llm);
27+
$messages = new MessageBag(
28+
Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
29+
Message::ofUser(
30+
'Describe the image as a comedian would do it.',
31+
Image::fromFile(dirname(__DIR__, 2).'/tests/Fixture/image.jpg'),
32+
),
33+
);
34+
$response = $chain->call($messages);
35+
36+
echo $response->getContent().PHP_EOL;

examples/bedrock/toolcall-claude.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
4+
use PhpLlm\LlmChain\Bridge\Anthropic\Claude;
5+
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
6+
use PhpLlm\LlmChain\Chain;
7+
use PhpLlm\LlmChain\Chain\Toolbox\ChainProcessor;
8+
use PhpLlm\LlmChain\Chain\Toolbox\Tool\Wikipedia;
9+
use PhpLlm\LlmChain\Chain\Toolbox\Toolbox;
10+
use PhpLlm\LlmChain\Model\Message\Message;
11+
use PhpLlm\LlmChain\Model\Message\MessageBag;
12+
use Symfony\Component\Dotenv\Dotenv;
13+
use Symfony\Component\HttpClient\HttpClient;
14+
15+
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
16+
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');
17+
18+
if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
19+
) {
20+
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
21+
exit(1);
22+
}
23+
24+
$platform = PlatformFactory::create(
25+
new BedrockRuntimeClient()
26+
);
27+
$llm = new Claude();
28+
29+
$wikipedia = new Wikipedia(HttpClient::create());
30+
$toolbox = Toolbox::create($wikipedia);
31+
$processor = new ChainProcessor($toolbox);
32+
$chain = new Chain($platform, $llm, [$processor], [$processor]);
33+
34+
$messages = new MessageBag(Message::ofUser('Who is the current chancellor of Germany?'));
35+
$response = $chain->call($messages);
36+
37+
echo $response->getContent().PHP_EOL;

src/Bridge/Anthropic/Claude.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function supportsAudioInput(): bool
4242

4343
public function supportsImageInput(): bool
4444
{
45-
return false; // it does, but implementation here is still open.
45+
return true;
4646
}
4747

4848
public function supportsStreaming(): bool

src/Bridge/Anthropic/ModelHandler.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
use PhpLlm\LlmChain\Chain\Toolbox\Metadata;
88
use PhpLlm\LlmChain\Exception\RuntimeException;
99
use PhpLlm\LlmChain\Model\Message\AssistantMessage;
10+
use PhpLlm\LlmChain\Model\Message\Content\Content;
11+
use PhpLlm\LlmChain\Model\Message\Content\Image;
1012
use PhpLlm\LlmChain\Model\Message\MessageBagInterface;
1113
use PhpLlm\LlmChain\Model\Message\MessageInterface;
1214
use PhpLlm\LlmChain\Model\Message\ToolCallMessage;
15+
use PhpLlm\LlmChain\Model\Message\UserMessage;
1316
use PhpLlm\LlmChain\Model\Model;
1417
use PhpLlm\LlmChain\Model\Response\ResponseInterface as LlmResponse;
1518
use PhpLlm\LlmChain\Model\Response\StreamResponse;
@@ -25,6 +28,8 @@
2528
use Symfony\Contracts\HttpClient\ResponseInterface;
2629
use Webmozart\Assert\Assert;
2730

31+
use function Symfony\Component\String\u;
32+
2833
final readonly class ModelHandler implements ModelClient, ResponseConverter
2934
{
3035
private EventSourceHttpClient $httpClient;
@@ -92,6 +97,26 @@ public function request(Model $model, object|array|string $input, array $options
9297
}, $message->toolCalls),
9398
];
9499
}
100+
if ($message instanceof UserMessage && $message->hasImageContent()) {
101+
// make sure images are encoded for Bedrock invocation
102+
return [
103+
'role' => 'user',
104+
'content' => array_map(static function (Content $content) {
105+
if ($content instanceof Image) {
106+
return [
107+
'type' => 'image',
108+
'source' => [
109+
'type' => 'base64',
110+
'media_type' => u($content->getFormat())->replace('jpg', 'jpeg')->toString(),
111+
'data' => $content->asBase64(),
112+
],
113+
];
114+
}
115+
116+
return $content;
117+
}, $message->content),
118+
];
119+
}
95120

96121
return $message;
97122
}, $body['messages']);

0 commit comments

Comments
 (0)