Skip to content

WIP: AWS Bedrock Support, Google Gemini Function calling, and file support, and more. #320

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

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
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
7 changes: 7 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,10 @@ RUN_EXPENSIVE_EXAMPLES=false

# For using Gemini
GOOGLE_API_KEY=

# For using Aws BedRock

AWS_ACCESS_KEY=
AWS_ACCESS_SECRET=
AWS_REGION=
AWS_INFERENCE_PROFILE_REGION=
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,17 @@
"symfony/event-dispatcher": "^6.4 || ^7.1",
"symfony/finder": "^6.4 || ^7.1",
"symfony/process": "^6.4 || ^7.1",
"symfony/var-dumper": "^6.4 || ^7.1"
"symfony/var-dumper": "^6.4 || ^7.1",
"aws/aws-sdk-php": "^3"
},
"suggest": {
"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.",
"probots-io/pinecone-php": "For using the Pinecone as retrieval vector store.",
"symfony/css-selector": "For using the YouTube transcription tool.",
"symfony/dom-crawler": "For using the YouTube transcription tool."
"symfony/dom-crawler": "For using the YouTube transcription tool.",
"aws/aws-sdk-php": "For using AWS Bedrock"
},
"config": {
"allow-plugins": {
Expand Down
34 changes: 34 additions & 0 deletions examples/awsbedrock/embeddings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

use PhpLlm\LlmChain\Bridge\AwsBedrock\Embeddings;
use PhpLlm\LlmChain\Bridge\AwsBedrock\PlatformFactory;
use PhpLlm\LlmChain\Document\Vector;
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']) || empty($_ENV['AWS_ACCESS_SECRET']) || empty($_ENV['AWS_REGION'])) {
echo 'Please set the AWS_ACCESS_KEY, AWS_ACCESS_SECRET, AWS_REGION environment variables.'.PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create(
[
'key' => $_ENV['AWS_ACCESS_KEY'],
'secret' => $_ENV['AWS_ACCESS_SECRET'],
],
$_ENV['AWS_REGION'],
);

$embeddings = new Embeddings();

$response = $platform->request($embeddings, <<<TEXT
Once upon a time, there was a country called Japan. It was a beautiful country with a lot of mountains and rivers.
The people of Japan were very kind and hardworking. They loved their country very much and took care of it. The
country was very peaceful and prosperous. The people lived happily ever after.
TEXT);

assert($response->getContent()[0] instanceof Vector);

echo 'Dimensions: '.$response->getContent()[0]->getDimensions().PHP_EOL;
33 changes: 33 additions & 0 deletions examples/google/toolcall.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use PhpLlm\LlmChain\Bridge\Google\Gemini;
use PhpLlm\LlmChain\Bridge\Google\PlatformFactory;
use PhpLlm\LlmChain\Chain;
use PhpLlm\LlmChain\Chain\Toolbox\ChainProcessor;
use PhpLlm\LlmChain\Chain\Toolbox\Tool\YouTubeTranscriber;
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['GOOGLE_API_KEY'])) {
echo 'Please set the GOOGLE_API_KEY environment variable.'.PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create($_ENV['GOOGLE_API_KEY']);
$llm = new Gemini(Gemini::GEMINI_2_FLASH);

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

$messages = new MessageBag(Message::ofUser('Please summarize this video for me https://www.youtube.com/watch?v=6uXW-ulpj0s with the video ID as 6uXW-ulpj0s'));
$response = $chain->call($messages);

echo $response->getContent().PHP_EOL;
64 changes: 64 additions & 0 deletions src/Bridge/AwsBedrock/AmazonNova.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Bridge\AwsBedrock;

final readonly class AmazonNova implements BedrockLanguageModel
{
public const NOVA_MICRO_V1 = 'amazon.nova-micro-v1:0';

public const NOVA_LITE_V1 = 'amazon.nova-lite-v1:0';

public const NOVA_PRO_V1 = 'amazon.nova-pro-v1:0';

/**
* @param array<string, mixed> $options
* @param string|null $inferenceProfileRegion if you need to use an inference profile just add the region here
*/
public function __construct(
private string $name = self::NOVA_LITE_V1,
private array $options = [],
private ?string $inferenceProfileRegion = null,
) {
}

public function getName(): string
{
if ($this->inferenceProfileRegion) {
return $this->inferenceProfileRegion.'.'.$this->name;
}

return $this->name;
}

public function getOptions(): array
{
return $this->options;
}

public function supportsAudioInput(): bool
{
return false; // Only videos (!?)
}

public function supportsImageInput(): bool
{
return true;
}

public function supportsStreaming(): bool
{
return false; // It does, but it's not implemented yet.
}

public function supportsToolCalling(): bool
{
return true;
}

public function supportsStructuredOutput(): bool
{
return false;
}
}
9 changes: 9 additions & 0 deletions src/Bridge/AwsBedrock/BedrockLanguageModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace PhpLlm\LlmChain\Bridge\AwsBedrock;

use PhpLlm\LlmChain\Model\LanguageModel;

interface BedrockLanguageModel extends LanguageModel
{
}
48 changes: 48 additions & 0 deletions src/Bridge/AwsBedrock/BedrockRequestSigner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace PhpLlm\LlmChain\Bridge\AwsBedrock;

use Aws\Credentials\Credentials;
use Aws\Signature\SignatureV4;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;

final class BedrockRequestSigner
{
public function __construct(#[\SensitiveParameter] private Credentials $credentials, private string $region)
{
}

public function signRequest(string $method, string $endpoint, array $jsonBody, array $extraHeaders = [])
{
$signature = new SignatureV4('bedrock', $this->region);

$uri = new Uri($endpoint);

$finalHeaders = array_merge([
'Host' => $uri->getHost(),
'Content-Type' => 'application/json',
], $extraHeaders);

$request = new Request(
$method,
$uri,
$finalHeaders,
$encodedBody = json_encode($jsonBody)
);

$signedRequest = $signature->signRequest($request, $this->credentials);

$signedHeaders = [];
foreach ($signedRequest->getHeaders() as $name => $values) {
$signedHeaders[$name] = $signedRequest->getHeaderLine($name);
}

unset($request, $finalHeaders, $uri, $signature);

return [
'headers' => $signedHeaders,
'body' => $encodedBody,
];
}
}
37 changes: 37 additions & 0 deletions src/Bridge/AwsBedrock/Embeddings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace PhpLlm\LlmChain\Bridge\AwsBedrock;

use PhpLlm\LlmChain\Model\EmbeddingsModel;

final readonly class Embeddings implements EmbeddingsModel
{
public const TITAN_EMBED_TEXT_V2 = 'amazon.titan-embed-text-v2:0';
public const TITAN_MULTIMODAL_G1 = 'amazon.titan-embed-image-v1';

public function __construct(
private string $name = self::TITAN_EMBED_TEXT_V2,
private array $options = [],
private ?string $inferenceProfileRegion = null,
) {
}

public function supportsMultipleInputs(): bool
{
return false;
}

public function getName(): string
{
if ($this->inferenceProfileRegion) {
return $this->inferenceProfileRegion.'.'.$this->name;
}

return $this->name;
}

public function getOptions(): array
{
return $this->options;
}
}
51 changes: 51 additions & 0 deletions src/Bridge/AwsBedrock/Embeddings/ModelClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Bridge\AwsBedrock\Embeddings;

use PhpLlm\LlmChain\Bridge\AwsBedrock\BedrockRequestSigner;
use PhpLlm\LlmChain\Bridge\AwsBedrock\Embeddings;
use PhpLlm\LlmChain\Model\Model;
use PhpLlm\LlmChain\Platform\ModelClient as PlatformResponseFactory;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Webmozart\Assert\Assert;

final readonly class ModelClient implements PlatformResponseFactory
{
public function __construct(
private HttpClientInterface $httpClient,
#[\SensitiveParameter] private BedrockRequestSigner $requestSigner,
private string $region,
) {
Assert::stringNotEmpty($region, 'The region must not be empty.');
}

public function supports(Model $model, array|string|object $input): bool
{
return $model instanceof Embeddings;
}

public function request(Model $model, object|array|string $input, array $options = []): ResponseInterface
{
$signedParameters = $this->requestSigner->signRequest(
method: 'POST',
endpoint: $bedrockEndpoint = sprintf(
'https://bedrock-runtime.%s.amazonaws.com/model/%s/invoke',
$this->region,
$model->getName(),
),
jsonBody: is_string($input) ? array_merge(
$model->getOptions(), [
'inputText' => $input,
]
) : (
is_array($input) ?
array_merge($model->getOptions(), $options, $input) : $input
)
);

return $this->httpClient->request('POST', $bedrockEndpoint, $signedParameters);
}
}
34 changes: 34 additions & 0 deletions src/Bridge/AwsBedrock/Embeddings/ResponseConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Bridge\AwsBedrock\Embeddings;

use PhpLlm\LlmChain\Bridge\AwsBedrock\Embeddings;
use PhpLlm\LlmChain\Document\Vector;
use PhpLlm\LlmChain\Exception\RuntimeException;
use PhpLlm\LlmChain\Model\Model;
use PhpLlm\LlmChain\Model\Response\VectorResponse;
use PhpLlm\LlmChain\Platform\ResponseConverter as PlatformResponseConverter;
use Symfony\Contracts\HttpClient\ResponseInterface;

final class ResponseConverter implements PlatformResponseConverter
{
public function supports(Model $model, array|string|object $input): bool
{
return $model instanceof Embeddings;
}

public function convert(ResponseInterface $response, array $options = []): VectorResponse
{
$data = $response->toArray();

if (!isset($data['embedding'])) {
throw new RuntimeException('Response does not contain data');
}

return new VectorResponse(
new Vector($data['embedding'])
);
}
}
Loading
Loading