Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -824,3 +824,4 @@ For testing multi-modal features, the repository contains binary media content,

* `tests/Fixture/image.jpg`: Chris F., Creative Commons, see [pexels.com](https://www.pexels.com/photo/blauer-und-gruner-elefant-mit-licht-1680755/)
* `tests/Fixture/audio.mp3`: davidbain, Creative Commons, see [freesound.org](https://freesound.org/people/davidbain/sounds/136777/)
* `tests/Fixture/document.pdf`: Chem8240ja, Public Domain, see [Wikipedia](https://en.m.wikipedia.org/wiki/File:Re_example.pdf)
32 changes: 32 additions & 0 deletions examples/anthropic/image-input-binary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use PhpLlm\LlmChain\Chain\Chain;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\PlatformFactory;
use PhpLlm\LlmChain\Platform\Message\Content\Image;
use PhpLlm\LlmChain\Platform\Message\Message;
use PhpLlm\LlmChain\Platform\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['ANTHROPIC_API_KEY'])) {
echo 'Please set the ANTHROPIC_API_KEY environment variable.'.\PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create($_ENV['ANTHROPIC_API_KEY']);
$llm = new Claude(Claude::SONNET_37);

$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(
Image::fromFile(dirname(__DIR__, 2).'/tests/Fixture/image.jpg'),
'Describe this image.',
),
);
$response = $chain->call($messages);

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

use PhpLlm\LlmChain\Chain\Chain;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\PlatformFactory;
use PhpLlm\LlmChain\Platform\Message\Content\ImageUrl;
use PhpLlm\LlmChain\Platform\Message\Message;
use PhpLlm\LlmChain\Platform\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['ANTHROPIC_API_KEY'])) {
echo 'Please set the ANTHROPIC_API_KEY environment variable.'.\PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create($_ENV['ANTHROPIC_API_KEY']);
$llm = new Claude(Claude::SONNET_37);

$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(
new ImageUrl('https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg'),
'Describe this image.',
),
);
$response = $chain->call($messages);

echo $response->getContent().\PHP_EOL;
31 changes: 31 additions & 0 deletions examples/anthropic/pdf-input-binary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

use PhpLlm\LlmChain\Chain\Chain;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\PlatformFactory;
use PhpLlm\LlmChain\Platform\Message\Content\Document;
use PhpLlm\LlmChain\Platform\Message\Message;
use PhpLlm\LlmChain\Platform\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['ANTHROPIC_API_KEY'])) {
echo 'Please set the ANTHROPIC_API_KEY environment variable.'.\PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create($_ENV['ANTHROPIC_API_KEY']);
$llm = new Claude(Claude::SONNET_37);

$chain = new Chain($platform, $llm);
$messages = new MessageBag(
Message::ofUser(
Document::fromFile(dirname(__DIR__, 2).'/tests/Fixture/document.pdf'),
'What is this document about?',
),
);
$response = $chain->call($messages);

echo $response->getContent().\PHP_EOL;
31 changes: 31 additions & 0 deletions examples/anthropic/pdf-input-url.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

use PhpLlm\LlmChain\Chain\Chain;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\PlatformFactory;
use PhpLlm\LlmChain\Platform\Message\Content\DocumentUrl;
use PhpLlm\LlmChain\Platform\Message\Message;
use PhpLlm\LlmChain\Platform\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['ANTHROPIC_API_KEY'])) {
echo 'Please set the ANTHROPIC_API_KEY environment variable.'.\PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create($_ENV['ANTHROPIC_API_KEY']);
$llm = new Claude(Claude::SONNET_37);

$chain = new Chain($platform, $llm);
$messages = new MessageBag(
Message::ofUser(
new DocumentUrl('https://upload.wikimedia.org/wikipedia/commons/2/20/Re_example.pdf'),
'What is this document about?',
),
);
$response = $chain->call($messages);

echo $response->getContent().\PHP_EOL;
38 changes: 38 additions & 0 deletions src/Platform/Bridge/Anthropic/Contract/DocumentNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract;

use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Platform\Contract\Normalizer\ModelContractNormalizer;
use PhpLlm\LlmChain\Platform\Message\Content\Document;
use PhpLlm\LlmChain\Platform\Model;

class DocumentNormalizer extends ModelContractNormalizer
{
protected function supportedDataClass(): string
{
return Document::class;
}

protected function supportsModel(Model $model): bool
{
return $model instanceof Claude;
}

/**
* @param Document $data
*
* @return array{type: 'document', source: array{type: 'base64', media_type: string, data: string}}
*/
public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
return [
'type' => 'document',
'source' => [
'type' => 'base64',
'media_type' => $data->getFormat(),
'data' => $data->asBase64(),
],
];
}
}
39 changes: 39 additions & 0 deletions src/Platform/Bridge/Anthropic/Contract/DocumentUrlNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract;

use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Platform\Contract\Normalizer\ModelContractNormalizer;
use PhpLlm\LlmChain\Platform\Message\Content\DocumentUrl;
use PhpLlm\LlmChain\Platform\Model;

final class DocumentUrlNormalizer extends ModelContractNormalizer
{
protected function supportedDataClass(): string
{
return DocumentUrl::class;
}

protected function supportsModel(Model $model): bool
{
return $model instanceof Claude;
}

/**
* @param DocumentUrl $data
*
* @return array{type: 'document', source: array{type: 'url', url: string}}
*/
public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
return [
'type' => 'document',
'source' => [
'type' => 'url',
'url' => $data->url,
],
];
}
}
9 changes: 1 addition & 8 deletions src/Platform/Bridge/Anthropic/Contract/ImageNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,7 @@ protected function supportsModel(Model $model): bool
/**
* @param Image $data
*
* @return array{
* type: 'image',
* source: array{
* type: 'base64',
* media_type: string,
* data: string
* }
* }
* @return array{type: 'image', source: array{type: 'base64', media_type: string, data: string}}
*/
public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
Expand Down
40 changes: 40 additions & 0 deletions src/Platform/Bridge/Anthropic/Contract/ImageUrlNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract;

use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Claude;
use PhpLlm\LlmChain\Platform\Contract\Normalizer\ModelContractNormalizer;
use PhpLlm\LlmChain\Platform\Message\Content\Image;
use PhpLlm\LlmChain\Platform\Message\Content\ImageUrl;
use PhpLlm\LlmChain\Platform\Model;

final class ImageUrlNormalizer extends ModelContractNormalizer
{
protected function supportedDataClass(): string
{
return ImageUrl::class;
}

protected function supportsModel(Model $model): bool
{
return $model instanceof Claude;
}

/**
* @param ImageUrl $data
*
* @return array{type: 'image', source: array{type: 'url', url: string}}
*/
public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
return [
'type' => 'image',
'source' => [
'type' => 'url',
'url' => $data->url,
],
];
}
}
44 changes: 44 additions & 0 deletions src/Platform/Bridge/Anthropic/ModelClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Platform\Bridge\Anthropic;

use PhpLlm\LlmChain\Platform\Model;
use PhpLlm\LlmChain\Platform\ModelClientInterface;
use Symfony\Component\HttpClient\EventSourceHttpClient;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

final readonly class ModelClient implements ModelClientInterface
{
private EventSourceHttpClient $httpClient;

public function __construct(
HttpClientInterface $httpClient,
#[\SensitiveParameter] private string $apiKey,
private string $version = '2023-06-01',
) {
$this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
}

public function supports(Model $model): bool
{
return $model instanceof Claude;
}

public function request(Model $model, array|string $payload, array $options = []): ResponseInterface
{
if (isset($options['tools'])) {
$options['tool_choice'] = ['type' => 'auto'];
}

return $this->httpClient->request('POST', 'https://api.anthropic.com/v1/messages', [
'headers' => [
'x-api-key' => $this->apiKey,
'anthropic-version' => $this->version,
],
'json' => array_merge($options, $payload),
]);
}
}
25 changes: 18 additions & 7 deletions src/Platform/Bridge/Anthropic/PlatformFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
namespace PhpLlm\LlmChain\Platform\Bridge\Anthropic;

use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract\AssistantMessageNormalizer;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract\DocumentNormalizer;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract\DocumentUrlNormalizer;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract\ImageNormalizer;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract\ImageUrlNormalizer;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract\MessageBagNormalizer;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract\ToolCallMessageNormalizer;
use PhpLlm\LlmChain\Platform\Bridge\Anthropic\Contract\ToolNormalizer;
Expand All @@ -22,13 +26,20 @@ public static function create(
?HttpClientInterface $httpClient = null,
): Platform {
$httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
$responseHandler = new ModelHandler($httpClient, $apiKey, $version);

return new Platform([$responseHandler], [$responseHandler], Contract::create(
new AssistantMessageNormalizer(),
new MessageBagNormalizer(),
new ToolCallMessageNormalizer(),
new ToolNormalizer(),
));
return new Platform(
[new ModelClient($httpClient, $apiKey, $version)],
[new ResponseConverter()],
Contract::create(
new AssistantMessageNormalizer(),
new DocumentNormalizer(),
new DocumentUrlNormalizer(),
new ImageNormalizer(),
new ImageUrlNormalizer(),
new MessageBagNormalizer(),
new ToolCallMessageNormalizer(),
new ToolNormalizer(),
)
);
}
}
Loading