Skip to content
Open
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: 2 additions & 3 deletions examples/openrouter/chat-gemini.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
use Symfony\AI\Platform\Bridge\OpenRouter\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\AI\Platform\Model;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('OPENROUTER_KEY'), http_client());
// In case free is running into 429 rate limit errors, you can use the paid model:
// $model = 'google/gemini-2.0-flash-lite-001';
$model = 'google/gemini-2.0-flash-exp:free';
// In case free is running into 429 rate limit errors (note: it is a 404 error code in openrouter api), you can use the paid model:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if 429 is not accurate, just adopt it

Suggested change
// In case free is running into 429 rate limit errors (note: it is a 404 error code in openrouter api), you can use the paid model:
// In case free is running into 404 errors, you can use the paid model:

// $model = 'google/gemini-2.0-flash-lite-001';

$messages = new MessageBag(
Message::forSystem('You are a helpful assistant.'),
Expand Down
24 changes: 24 additions & 0 deletions examples/openrouter/embeddings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\AI\Platform\Bridge\OpenRouter\PlatformFactory;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('OPENROUTER_KEY'), http_client());

$result = $platform->invoke('openai/text-embedding-3-small', <<<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);

print_vectors($result);
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/

namespace Symfony\AI\Platform\Bridge\OpenRouter;
namespace Symfony\AI\Platform\Bridge\OpenRouter\Completions;

use Symfony\AI\Platform\Exception\InvalidArgumentException;
use Symfony\AI\Platform\Model;
Expand All @@ -30,6 +30,7 @@ public function __construct(
#[\SensitiveParameter] private readonly string $apiKey,
) {
$this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);

if ('' === $apiKey) {
throw new InvalidArgumentException('The API key must not be empty.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
* file that was distributed with this source code.
*/

namespace Symfony\AI\Platform\Bridge\OpenRouter;
namespace Symfony\AI\Platform\Bridge\OpenRouter\Completions;

use Symfony\AI\Platform\Exception\AuthenticationException;
use Symfony\AI\Platform\Exception\BadRequestException;
use Symfony\AI\Platform\Exception\RuntimeException;
use Symfony\AI\Platform\Model;
use Symfony\AI\Platform\Result\RawResultInterface;
Expand All @@ -30,8 +32,19 @@ public function supports(Model $model): bool

public function convert(RawResultInterface $result, array $options = []): ResultInterface
{
$response = $result->getObject();
$data = $result->getData();

if (401 === $response->getStatusCode()) {
$errorMessage = json_decode($response->getContent(false), true)['error']['message'];
throw new AuthenticationException($errorMessage);
}

if (400 === $response->getStatusCode() || 404 === $response->getStatusCode()) {
$errorMessage = json_decode($response->getContent(false), true)['error']['message'] ?? 'Bad Request';
throw new BadRequestException($errorMessage);
}

if (!isset($data['choices'][0]['message'])) {
throw new RuntimeException('Response does not contain message.');
}
Expand Down
18 changes: 18 additions & 0 deletions src/platform/src/Bridge/OpenRouter/Embeddings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Platform\Bridge\OpenRouter;

use Symfony\AI\Platform\Model;

class Embeddings extends Model
{
}
53 changes: 53 additions & 0 deletions src/platform/src/Bridge/OpenRouter/Embeddings/ModelClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Platform\Bridge\OpenRouter\Embeddings;

use Symfony\AI\Platform\Bridge\OpenRouter\Embeddings;
use Symfony\AI\Platform\Exception\InvalidArgumentException;
use Symfony\AI\Platform\Model;
use Symfony\AI\Platform\ModelClientInterface;
use Symfony\AI\Platform\Result\RawHttpResult;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
* @author Valtteri R <valtzu@gmail.com>
*/
final class ModelClient implements ModelClientInterface
{
public function __construct(
private readonly HttpClientInterface $httpClient,
#[\SensitiveParameter] private readonly string $apiKey,
) {
if ('' === $apiKey) {
throw new InvalidArgumentException('The API key must not be empty.');
}
if (!str_starts_with($apiKey, 'sk-')) {
throw new InvalidArgumentException('The API key must start with "sk-".');
}
}

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

public function request(Model $model, array|string $payload, array $options = []): RawHttpResult
{
return new RawHttpResult($this->httpClient->request('POST', 'https://openrouter.ai/api/v1/embeddings', [
'auth_bearer' => $this->apiKey,
'json' => [
'model' => $model->getName(),
'input' => $payload,
],
]));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Platform\Bridge\OpenRouter\Embeddings;

use Symfony\AI\Platform\Bridge\OpenRouter\Embeddings;
use Symfony\AI\Platform\Exception\RuntimeException;
use Symfony\AI\Platform\Model;
use Symfony\AI\Platform\Result\RawResultInterface;
use Symfony\AI\Platform\Result\VectorResult;
use Symfony\AI\Platform\ResultConverterInterface;
use Symfony\AI\Platform\Vector\Vector;

/**
* @author Valtteri R <valtzu@gmail.com>
*/
final class ResultConverter implements ResultConverterInterface
{
public function supports(Model $model): bool
{
return $model instanceof Embeddings;
}

public function convert(RawResultInterface $result, array $options = []): VectorResult
{
$data = $result->getData();
if (!isset($data['data'][0]['embedding'])) {
throw new RuntimeException('Response does not contain data.');
}

return new VectorResult(
...array_map(
static fn (array $item): Vector => new Vector($item['embedding']),
$data['data'],
),
);
}
}
Loading
Loading