Skip to content

Commit

Permalink
Added support for OpenRouter & Generic Models
Browse files Browse the repository at this point in the history
  • Loading branch information
rglozman committed Jan 31, 2025
1 parent 80fd016 commit da697c2
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ AZURE_OPENAI_DEPLOYMENT=
AZURE_OPENAI_VERSION=
AZURE_OPENAI_KEY=

# For using OpenRouter
OPENROUTER_KEY=

# For using SerpApi (tool)
SERP_API_KEY=

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ $embeddings = new Embeddings();
* [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 [Ollama](https://ollama.com/) and [Replicate](https://replicate.com/) as Platform
* [Google's Gemini](https://gemini.google.com/) with [OpenRouter](https://www.openrouter.com/) as Platform
* [DeepSeek's R1](https://www.deepseek.com/) with [OpenRouter](https://www.openrouter.com/) 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 Expand Up @@ -125,6 +127,7 @@ $response = $chain->call($messages, [
1. **OpenAI's o1**: [chat-o1-openai.php](examples/chat-o1-openai.php)
1. **Meta's Llama with Ollama**: [chat-llama-ollama.php](examples/chat-llama-ollama.php)
1. **Meta's Llama with Replicate**: [chat-llama-replicate.php](examples/chat-llama-replicate.php)
1. **Google's Gemini with OpenRouter**: [chat-gemini-openrouter.php](examples/chat-gemini-openrouter.php)

### Tools

Expand Down
28 changes: 28 additions & 0 deletions examples/chat-gemini-openrouter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use PhpLlm\LlmChain\Bridge\OpenRouter\GenericModel;
use PhpLlm\LlmChain\Bridge\OpenRouter\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__).'/vendor/autoload.php';
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');

if (empty($_ENV['OPENROUTER_KEY'])) {
echo 'Please set the OPENROUTER_KEY environment variable.'.PHP_EOL;
exit(1);
}

$platform = PlatformFactory::create($_ENV['OPENROUTER_KEY']);
$llm = new GenericModel('google/gemini-2.0-flash-thinking-exp:free');

$chain = new Chain($platform, $llm);
$messages = new MessageBag(
Message::forSystem('You are a helpful assistant.'),
Message::ofUser('Tina has one brother and one sister. How many sisters do Tina\'s siblings have?'),
);
$response = $chain->call($messages);

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

declare(strict_types=1);

namespace PhpLlm\LlmChain\Bridge\OpenRouter;

use PhpLlm\LlmChain\Model\Message\MessageBagInterface;
use PhpLlm\LlmChain\Model\Model;
use PhpLlm\LlmChain\Platform\ModelClient;
use PhpLlm\LlmChain\Platform\ResponseConverter;
use Symfony\Component\HttpClient\EventSourceHttpClient;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Webmozart\Assert\Assert;
use PhpLlm\LlmChain\Model\Response\ResponseInterface as LlmResponse;
use PhpLlm\LlmChain\Model\Response\TextResponse;
use PhpLlm\LlmChain\Exception\RuntimeException;

final readonly class Client implements ModelClient, ResponseConverter
{
private EventSourceHttpClient $httpClient;

public function __construct(
HttpClientInterface $httpClient,
#[\SensitiveParameter] private string $apiKey,
) {
$this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
Assert::stringNotEmpty($apiKey, 'The API key must not be empty.');
Assert::startsWith($apiKey, 'sk-', 'The API key must start with "sk-".');
}

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

public function request(Model $model, object|array|string $input, array $options = []): ResponseInterface
{
return $this->httpClient->request('POST', 'https://openrouter.ai/api/v1/chat/completions', [
'auth_bearer' => $this->apiKey,
'json' => array_merge($options, [
'model' => $model->getVersion(),
'messages' => $input,
]),
]);
}


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

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

if (!isset($data['choices'][0]['message']['content'])) {
throw new RuntimeException('Message does not contain content');
}

return new TextResponse($data['choices'][0]['message']['content']);
}

}
56 changes: 56 additions & 0 deletions src/Bridge/OpenRouter/GenericModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Bridge\OpenRouter;

use PhpLlm\LlmChain\Bridge\Meta\Llama;
use PhpLlm\LlmChain\Model\LanguageModel;

final readonly class GenericModel implements LanguageModel
{

/**
* @param array<string, mixed> $options
*/
public function __construct(
private string $version = Llama::LLAMA_3_2_90B_VISION_INSTRUCT,
private array $options = [],
) {
}

public function getVersion(): string
{
return $this->version;
}

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

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

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

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

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

public function supportsStructuredOutput(): bool
{
return false; // it does, but implementation here is still open.
}
}
23 changes: 23 additions & 0 deletions src/Bridge/OpenRouter/PlatformFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace PhpLlm\LlmChain\Bridge\OpenRouter;

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

final class PlatformFactory
{
public static function create(
#[\SensitiveParameter]
string $apiKey,
?HttpClientInterface $httpClient = null,
): Platform {
$httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
$handler = new Client($httpClient, $apiKey);

return new Platform([$handler], [$handler]);
}
}

0 comments on commit da697c2

Please sign in to comment.