Skip to content

feat: implement Bedrock and model clients for Claude, Llama, and Nova #312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 26, 2025
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
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ AZURE_OPENAI_WHISPER_API_VERSION=
AZURE_LLAMA_BASEURL=
AZURE_LLAMA_KEY=

# For using Bedrock
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=

# Hugging Face Access Token
HUGGINGFACE_KEY=

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ $embeddings = new Embeddings();

* Language Models
* [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
* [Anthropic's Claude](https://www.anthropic.com/claude) with [Anthropic](https://www.anthropic.com/) as Platform
* [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
* [Anthropic's Claude](https://www.anthropic.com/claude) with [Anthropic](https://www.anthropic.com/) and [AWS](https://aws.amazon.com/bedrock/) as Platform
* [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
* [Google's Gemini](https://gemini.google.com/) with [Google](https://ai.google.dev/) and [OpenRouter](https://www.openrouter.com/) as Platform
* [DeepSeek's R1](https://www.deepseek.com/) with [OpenRouter](https://www.openrouter.com/) as Platform
* [Amazon's Nova](https://nova.amazon.com) with [AWS](https://aws.amazon.com/bedrock/) as Platform
* Embeddings Models
* [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
* [Voyage's Embeddings](https://docs.voyageai.com/docs/embeddings) with [Voyage](https://www.voyageai.com/) as Platform
Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"require-dev": {
"codewithkyrian/chromadb-php": "^0.2.1 || ^0.3 || ^0.4",
"codewithkyrian/transformers": "^0.5.3",
"async-aws/bedrock-runtime": "^0.1.0",
"mongodb/mongodb": "^1.21",
"php-cs-fixer/shim": "^3.70",
"phpstan/phpstan": "^2.0",
Expand All @@ -59,6 +60,7 @@
"symfony/var-dumper": "^6.4 || ^7.1"
},
"suggest": {
"async-aws/bedrock-runtime": "For using the Bedrock platform.",
"codewithkyrian/chromadb-php": "For using the ChromaDB as retrieval vector store.",
"codewithkyrian/transformers": "For using the TransformersPHP with FFI to run models in PHP.",
"mongodb/mongodb": "For using MongoDB Atlas as retrieval vector store.",
Expand Down
32 changes: 32 additions & 0 deletions examples/bedrock/chat-claude.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
use PhpLlm\LlmChain\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
use PhpLlm\LlmChain\Chain;
use PhpLlm\LlmChain\Model\Message\Message;
use PhpLlm\LlmChain\Model\Message\MessageBag;
use Symfony\Component\Dotenv\Dotenv;

require_once dirname(__DIR__, 2).'/vendor/autoload.php';
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');

if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
) {
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create(
new BedrockRuntimeClient()
);
$llm = new Claude();

$chain = new Chain($platform, $llm);
$messages = new MessageBag(
Message::forSystem('You are a pirate and you write funny.'),
Message::ofUser('What is the Symfony framework?'),
);
$response = $chain->call($messages);

echo $response->getContent().PHP_EOL;
32 changes: 32 additions & 0 deletions examples/bedrock/chat-llama.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
use PhpLlm\LlmChain\Bridge\Meta\Llama;
use PhpLlm\LlmChain\Chain;
use PhpLlm\LlmChain\Model\Message\Message;
use PhpLlm\LlmChain\Model\Message\MessageBag;
use Symfony\Component\Dotenv\Dotenv;

require_once dirname(__DIR__, 2).'/vendor/autoload.php';
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');

if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
) {
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create(
new BedrockRuntimeClient()
);
$llm = new Llama('llama-3.2-3b-instruct');

$chain = new Chain($platform, $llm);
$messages = new MessageBag(
Message::forSystem('You are a pirate and you write funny.'),
Message::ofUser('What is the Symfony framework?'),
);
$response = $chain->call($messages);

echo $response->getContent().PHP_EOL;
32 changes: 32 additions & 0 deletions examples/bedrock/chat-nova.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
use PhpLlm\LlmChain\Bridge\Bedrock\Nova\Nova;
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
use PhpLlm\LlmChain\Chain;
use PhpLlm\LlmChain\Model\Message\Message;
use PhpLlm\LlmChain\Model\Message\MessageBag;
use Symfony\Component\Dotenv\Dotenv;

require_once dirname(__DIR__, 2).'/vendor/autoload.php';
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');

if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
) {
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create(
new BedrockRuntimeClient()
);
$llm = new Nova(Nova::PRO);

$chain = new Chain($platform, $llm);
$messages = new MessageBag(
Message::forSystem('You are a pirate and you write funny.'),
Message::ofUser('What is the Symfony framework?'),
);
$response = $chain->call($messages);

echo $response->getContent().PHP_EOL;
36 changes: 36 additions & 0 deletions examples/bedrock/image-claude-binary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
use PhpLlm\LlmChain\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
use PhpLlm\LlmChain\Chain;
use PhpLlm\LlmChain\Model\Message\Content\Image;
use PhpLlm\LlmChain\Model\Message\Message;
use PhpLlm\LlmChain\Model\Message\MessageBag;
use Symfony\Component\Dotenv\Dotenv;

require_once dirname(__DIR__, 2).'/vendor/autoload.php';
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');

if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
) {
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create(
new BedrockRuntimeClient()
);
$llm = new Claude();

$chain = new Chain($platform, $llm);
$messages = new MessageBag(
Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
Message::ofUser(
'Describe the image as a comedian would do it.',
Image::fromFile(dirname(__DIR__, 2).'/tests/Fixture/image.jpg'),
),
);
$response = $chain->call($messages);

echo $response->getContent().PHP_EOL;
36 changes: 36 additions & 0 deletions examples/bedrock/image-nova.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
use PhpLlm\LlmChain\Bridge\Bedrock\Nova\Nova;
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
use PhpLlm\LlmChain\Chain;
use PhpLlm\LlmChain\Model\Message\Content\Image;
use PhpLlm\LlmChain\Model\Message\Message;
use PhpLlm\LlmChain\Model\Message\MessageBag;
use Symfony\Component\Dotenv\Dotenv;

require_once dirname(__DIR__, 2).'/vendor/autoload.php';
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');

if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
) {
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create(
new BedrockRuntimeClient()
);
$llm = new Nova(Nova::PRO);

$chain = new Chain($platform, $llm);
$messages = new MessageBag(
Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
Message::ofUser(
'Describe the image as a comedian would do it.',
Image::fromFile(dirname(__DIR__, 2).'/tests/Fixture/image.jpg'),
),
);
$response = $chain->call($messages);

echo $response->getContent().PHP_EOL;
37 changes: 37 additions & 0 deletions examples/bedrock/toolcall-claude.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
use PhpLlm\LlmChain\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Bridge\Bedrock\PlatformFactory;
use PhpLlm\LlmChain\Chain;
use PhpLlm\LlmChain\Chain\Toolbox\ChainProcessor;
use PhpLlm\LlmChain\Chain\Toolbox\Tool\Wikipedia;
use PhpLlm\LlmChain\Chain\Toolbox\Toolbox;
use PhpLlm\LlmChain\Model\Message\Message;
use PhpLlm\LlmChain\Model\Message\MessageBag;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\HttpClient\HttpClient;

require_once dirname(__DIR__, 2).'/vendor/autoload.php';
(new Dotenv())->loadEnv(dirname(__DIR__, 2).'/.env');

if (empty($_ENV['AWS_ACCESS_KEY_ID']) || empty($_ENV['AWS_SECRET_ACCESS_KEY']) || empty($_ENV['AWS_DEFAULT_REGION'])
) {
echo 'Please set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables.'.PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create(
new BedrockRuntimeClient()
);
$llm = new Claude();

$wikipedia = new Wikipedia(HttpClient::create());
$toolbox = Toolbox::create($wikipedia);
$processor = new ChainProcessor($toolbox);
$chain = new Chain($platform, $llm, [$processor], [$processor]);

$messages = new MessageBag(Message::ofUser('Who is the current chancellor of Germany?'));
$response = $chain->call($messages);

echo $response->getContent().PHP_EOL;
2 changes: 1 addition & 1 deletion src/Bridge/Anthropic/Claude.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function supportsAudioInput(): bool

public function supportsImageInput(): bool
{
return false; // it does, but implementation here is still open.
return true;
}

public function supportsStreaming(): bool
Expand Down
25 changes: 25 additions & 0 deletions src/Bridge/Anthropic/ModelHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
use PhpLlm\LlmChain\Chain\Toolbox\Metadata;
use PhpLlm\LlmChain\Exception\RuntimeException;
use PhpLlm\LlmChain\Model\Message\AssistantMessage;
use PhpLlm\LlmChain\Model\Message\Content\Content;
use PhpLlm\LlmChain\Model\Message\Content\Image;
use PhpLlm\LlmChain\Model\Message\MessageBagInterface;
use PhpLlm\LlmChain\Model\Message\MessageInterface;
use PhpLlm\LlmChain\Model\Message\ToolCallMessage;
use PhpLlm\LlmChain\Model\Message\UserMessage;
use PhpLlm\LlmChain\Model\Model;
use PhpLlm\LlmChain\Model\Response\ResponseInterface as LlmResponse;
use PhpLlm\LlmChain\Model\Response\StreamResponse;
Expand All @@ -25,6 +28,8 @@
use Symfony\Contracts\HttpClient\ResponseInterface;
use Webmozart\Assert\Assert;

use function Symfony\Component\String\u;

final readonly class ModelHandler implements ModelClient, ResponseConverter
{
private EventSourceHttpClient $httpClient;
Expand Down Expand Up @@ -92,6 +97,26 @@ public function request(Model $model, object|array|string $input, array $options
}, $message->toolCalls),
];
}
if ($message instanceof UserMessage && $message->hasImageContent()) {
// make sure images are encoded for Bedrock invocation
return [
'role' => 'user',
'content' => array_map(static function (Content $content) {
if ($content instanceof Image) {
return [
'type' => 'image',
'source' => [
'type' => 'base64',
'media_type' => u($content->getFormat())->replace('jpg', 'jpeg')->toString(),
'data' => $content->asBase64(),
],
];
}

return $content;
}, $message->content),
];
}

return $message;
}, $body['messages']);
Expand Down
Loading
Loading