diff --git a/examples/openrouter/chat-gemini.php b/examples/openrouter/chat-gemini.php index 699341e42..d5112ee1c 100644 --- a/examples/openrouter/chat-gemini.php +++ b/examples/openrouter/chat-gemini.php @@ -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 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.'), diff --git a/examples/openrouter/embeddings.php b/examples/openrouter/embeddings.php new file mode 100644 index 000000000..c4171ba3d --- /dev/null +++ b/examples/openrouter/embeddings.php @@ -0,0 +1,24 @@ + + * + * 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', <<httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); + if ('' === $apiKey) { throw new InvalidArgumentException('The API key must not be empty.'); } diff --git a/src/platform/src/Bridge/OpenRouter/ResultConverter.php b/src/platform/src/Bridge/OpenRouter/Completions/ResultConverter.php similarity index 63% rename from src/platform/src/Bridge/OpenRouter/ResultConverter.php rename to src/platform/src/Bridge/OpenRouter/Completions/ResultConverter.php index 586b3d22d..66b4741c4 100644 --- a/src/platform/src/Bridge/OpenRouter/ResultConverter.php +++ b/src/platform/src/Bridge/OpenRouter/Completions/ResultConverter.php @@ -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; @@ -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.'); } diff --git a/src/platform/src/Bridge/OpenRouter/Embeddings.php b/src/platform/src/Bridge/OpenRouter/Embeddings.php new file mode 100644 index 000000000..dd5c16fd6 --- /dev/null +++ b/src/platform/src/Bridge/OpenRouter/Embeddings.php @@ -0,0 +1,18 @@ + + * + * 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 +{ +} diff --git a/src/platform/src/Bridge/OpenRouter/Embeddings/ModelClient.php b/src/platform/src/Bridge/OpenRouter/Embeddings/ModelClient.php new file mode 100644 index 000000000..688797d7e --- /dev/null +++ b/src/platform/src/Bridge/OpenRouter/Embeddings/ModelClient.php @@ -0,0 +1,53 @@ + + * + * 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 Tim Lochmüller + */ +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, + ], + ])); + } +} diff --git a/src/platform/src/Bridge/OpenRouter/Embeddings/ResultConverter.php b/src/platform/src/Bridge/OpenRouter/Embeddings/ResultConverter.php new file mode 100644 index 000000000..34f042ed1 --- /dev/null +++ b/src/platform/src/Bridge/OpenRouter/Embeddings/ResultConverter.php @@ -0,0 +1,46 @@ + + * + * 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 Tim Lochmüller + */ +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'], + ), + ); + } +} diff --git a/src/platform/src/Bridge/OpenRouter/ModelCatalog.php b/src/platform/src/Bridge/OpenRouter/ModelCatalog.php index 14fb9c7c7..e80b0c414 100644 --- a/src/platform/src/Bridge/OpenRouter/ModelCatalog.php +++ b/src/platform/src/Bridge/OpenRouter/ModelCatalog.php @@ -11,13 +11,406 @@ namespace Symfony\AI\Platform\Bridge\OpenRouter; -use Symfony\AI\Platform\ModelCatalog\FallbackModelCatalog; +use Symfony\AI\Platform\Capability; +use Symfony\AI\Platform\Model; +use Symfony\AI\Platform\ModelCatalog\AbstractModelCatalog; /** * @author Oskar Stark */ -final class ModelCatalog extends FallbackModelCatalog +final class ModelCatalog extends AbstractModelCatalog { - // OpenRouter provides access to many different models from various providers - // Models are dynamically available and identified by provider/model format + /** + * @param array}> $additionalModels + */ + public function __construct(array $additionalModels = []) + { + $defaultModels = [ + // OpenRouter provides access to many different models from various providers + // Models are dynamically available and identified by provider/model format + // Capabilities are not set in this model catalog + + // Models + 'openrouter/polaris-alpha' => ['class' => Model::class, 'capabilities' => []], + 'moonshotai/kimi-k2-thinking' => ['class' => Model::class, 'capabilities' => []], + 'amazon/nova-premier-v1' => ['class' => Model::class, 'capabilities' => []], + 'perplexity/sonar-pro-search' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/voxtral-small-24b-2507' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-oss-safeguard-20b' => ['class' => Model::class, 'capabilities' => []], + 'nvidia/nemotron-nano-12b-v2-vl:free' => ['class' => Model::class, 'capabilities' => []], + 'nvidia/nemotron-nano-12b-v2-vl' => ['class' => Model::class, 'capabilities' => []], + 'minimax/minimax-m2:free' => ['class' => Model::class, 'capabilities' => []], + 'minimax/minimax-m2' => ['class' => Model::class, 'capabilities' => []], + 'liquid/lfm2-8b-a1b' => ['class' => Model::class, 'capabilities' => []], + 'liquid/lfm-2.2-6b' => ['class' => Model::class, 'capabilities' => []], + 'ibm-granite/granite-4.0-h-micro' => ['class' => Model::class, 'capabilities' => []], + 'deepcogito/cogito-v2-preview-llama-405b' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-5-image-mini' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-haiku-4.5' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-vl-8b-thinking' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-vl-8b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-5-image' => ['class' => Model::class, 'capabilities' => []], + 'inclusionai/ring-1t' => ['class' => Model::class, 'capabilities' => []], + 'inclusionai/ling-1t' => ['class' => Model::class, 'capabilities' => []], + 'openai/o3-deep-research' => ['class' => Model::class, 'capabilities' => []], + 'openai/o4-mini-deep-research' => ['class' => Model::class, 'capabilities' => []], + 'nvidia/llama-3.3-nemotron-super-49b-v1.5' => ['class' => Model::class, 'capabilities' => []], + 'baidu/ernie-4.5-21b-a3b-thinking' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.5-flash-image' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-vl-30b-a3b-thinking' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-vl-30b-a3b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-5-pro' => ['class' => Model::class, 'capabilities' => []], + 'z-ai/glm-4.6' => ['class' => Model::class, 'capabilities' => []], + 'z-ai/glm-4.6:exacto' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-sonnet-4.5' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-v3.2-exp' => ['class' => Model::class, 'capabilities' => []], + 'thedrummer/cydonia-24b-v4.1' => ['class' => Model::class, 'capabilities' => []], + 'relace/relace-apply-3' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.5-flash-preview-09-2025' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.5-flash-lite-preview-09-2025' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-vl-235b-a22b-thinking' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-vl-235b-a22b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-max' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-coder-plus' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-5-codex' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-v3.1-terminus' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-v3.1-terminus:exacto' => ['class' => Model::class, 'capabilities' => []], + 'x-ai/grok-4-fast' => ['class' => Model::class, 'capabilities' => []], + 'alibaba/tongyi-deepresearch-30b-a3b:free' => ['class' => Model::class, 'capabilities' => []], + 'alibaba/tongyi-deepresearch-30b-a3b' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-coder-flash' => ['class' => Model::class, 'capabilities' => []], + 'arcee-ai/afm-4.5b' => ['class' => Model::class, 'capabilities' => []], + 'opengvlab/internvl3-78b' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-next-80b-a3b-thinking' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-next-80b-a3b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'meituan/longcat-flash-chat:free' => ['class' => Model::class, 'capabilities' => []], + 'meituan/longcat-flash-chat' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-plus-2025-07-28' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-plus-2025-07-28:thinking' => ['class' => Model::class, 'capabilities' => []], + 'nvidia/nemotron-nano-9b-v2:free' => ['class' => Model::class, 'capabilities' => []], + 'nvidia/nemotron-nano-9b-v2' => ['class' => Model::class, 'capabilities' => []], + 'moonshotai/kimi-k2-0905' => ['class' => Model::class, 'capabilities' => []], + 'moonshotai/kimi-k2-0905:exacto' => ['class' => Model::class, 'capabilities' => []], + 'deepcogito/cogito-v2-preview-llama-70b' => ['class' => Model::class, 'capabilities' => []], + 'deepcogito/cogito-v2-preview-llama-109b-moe' => ['class' => Model::class, 'capabilities' => []], + 'deepcogito/cogito-v2-preview-deepseek-671b' => ['class' => Model::class, 'capabilities' => []], + 'stepfun-ai/step3' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-30b-a3b-thinking-2507' => ['class' => Model::class, 'capabilities' => []], + 'x-ai/grok-code-fast-1' => ['class' => Model::class, 'capabilities' => []], + 'nousresearch/hermes-4-70b' => ['class' => Model::class, 'capabilities' => []], + 'nousresearch/hermes-4-405b' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.5-flash-image-preview' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-chat-v3.1:free' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-chat-v3.1' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4o-audio-preview' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-medium-3.1' => ['class' => Model::class, 'capabilities' => []], + 'baidu/ernie-4.5-21b-a3b' => ['class' => Model::class, 'capabilities' => []], + 'baidu/ernie-4.5-vl-28b-a3b' => ['class' => Model::class, 'capabilities' => []], + 'z-ai/glm-4.5v' => ['class' => Model::class, 'capabilities' => []], + 'ai21/jamba-mini-1.7' => ['class' => Model::class, 'capabilities' => []], + 'ai21/jamba-large-1.7' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-5-chat' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-5' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-5-mini' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-5-nano' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-oss-120b' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-oss-120b:exacto' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-oss-20b:free' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-oss-20b' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-opus-4.1' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/codestral-2508' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-coder-30b-a3b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-30b-a3b-instruct-2507' => ['class' => Model::class, 'capabilities' => []], + 'z-ai/glm-4.5' => ['class' => Model::class, 'capabilities' => []], + 'z-ai/glm-4.5-air:free' => ['class' => Model::class, 'capabilities' => []], + 'z-ai/glm-4.5-air' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-235b-a22b-thinking-2507' => ['class' => Model::class, 'capabilities' => []], + 'z-ai/glm-4-32b' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-coder:free' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-coder' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-coder:exacto' => ['class' => Model::class, 'capabilities' => []], + 'bytedance/ui-tars-1.5-7b' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.5-flash-lite' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-235b-a22b-2507' => ['class' => Model::class, 'capabilities' => []], + 'switchpoint/router' => ['class' => Model::class, 'capabilities' => []], + 'moonshotai/kimi-k2:free' => ['class' => Model::class, 'capabilities' => []], + 'moonshotai/kimi-k2' => ['class' => Model::class, 'capabilities' => []], + 'thudm/glm-4.1v-9b-thinking' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/devstral-medium' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/devstral-small' => ['class' => Model::class, 'capabilities' => []], + 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free' => ['class' => Model::class, 'capabilities' => []], + 'x-ai/grok-4' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-3n-e2b-it:free' => ['class' => Model::class, 'capabilities' => []], + 'tencent/hunyuan-a13b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'tngtech/deepseek-r1t2-chimera:free' => ['class' => Model::class, 'capabilities' => []], + 'tngtech/deepseek-r1t2-chimera' => ['class' => Model::class, 'capabilities' => []], + 'morph/morph-v3-large' => ['class' => Model::class, 'capabilities' => []], + 'morph/morph-v3-fast' => ['class' => Model::class, 'capabilities' => []], + 'baidu/ernie-4.5-vl-424b-a47b' => ['class' => Model::class, 'capabilities' => []], + 'baidu/ernie-4.5-300b-a47b' => ['class' => Model::class, 'capabilities' => []], + 'thedrummer/anubis-70b-v1.1' => ['class' => Model::class, 'capabilities' => []], + 'inception/mercury' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-small-3.2-24b-instruct:free' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-small-3.2-24b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'minimax/minimax-m1' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.5-flash-lite-preview-06-17' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.5-flash' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.5-pro' => ['class' => Model::class, 'capabilities' => []], + 'moonshotai/kimi-dev-72b' => ['class' => Model::class, 'capabilities' => []], + 'openai/o3-pro' => ['class' => Model::class, 'capabilities' => []], + 'x-ai/grok-3-mini' => ['class' => Model::class, 'capabilities' => []], + 'x-ai/grok-3' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/magistral-small-2506' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/magistral-medium-2506:thinking' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/magistral-medium-2506' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.5-pro-preview' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-r1-0528-qwen3-8b:free' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-r1-0528-qwen3-8b' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-r1-0528:free' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-r1-0528' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-opus-4' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-sonnet-4' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/devstral-small-2505' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-3n-e4b-it:free' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-3n-e4b-it' => ['class' => Model::class, 'capabilities' => []], + 'openai/codex-mini' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.3-8b-instruct:free' => ['class' => Model::class, 'capabilities' => []], + 'nousresearch/deephermes-3-mistral-24b-preview' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-medium-3' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.5-pro-preview-05-06' => ['class' => Model::class, 'capabilities' => []], + 'arcee-ai/spotlight' => ['class' => Model::class, 'capabilities' => []], + 'arcee-ai/maestro-reasoning' => ['class' => Model::class, 'capabilities' => []], + 'arcee-ai/virtuoso-large' => ['class' => Model::class, 'capabilities' => []], + 'arcee-ai/coder-large' => ['class' => Model::class, 'capabilities' => []], + 'microsoft/phi-4-reasoning-plus' => ['class' => Model::class, 'capabilities' => []], + 'inception/mercury-coder' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-4b:free' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-prover-v2' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-guard-4-12b' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-30b-a3b:free' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-30b-a3b' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-8b' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-14b:free' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-14b' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-32b' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-235b-a22b:free' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen3-235b-a22b' => ['class' => Model::class, 'capabilities' => []], + 'tngtech/deepseek-r1t-chimera:free' => ['class' => Model::class, 'capabilities' => []], + 'tngtech/deepseek-r1t-chimera' => ['class' => Model::class, 'capabilities' => []], + 'microsoft/mai-ds-r1:free' => ['class' => Model::class, 'capabilities' => []], + 'microsoft/mai-ds-r1' => ['class' => Model::class, 'capabilities' => []], + 'openai/o4-mini-high' => ['class' => Model::class, 'capabilities' => []], + 'openai/o3' => ['class' => Model::class, 'capabilities' => []], + 'openai/o4-mini' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen2.5-coder-7b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4.1' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4.1-mini' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4.1-nano' => ['class' => Model::class, 'capabilities' => []], + 'eleutherai/llemma_7b' => ['class' => Model::class, 'capabilities' => []], + 'alfredpros/codellama-7b-instruct-solidity' => ['class' => Model::class, 'capabilities' => []], + 'arliai/qwq-32b-arliai-rpr-v1:free' => ['class' => Model::class, 'capabilities' => []], + 'arliai/qwq-32b-arliai-rpr-v1' => ['class' => Model::class, 'capabilities' => []], + 'agentica-org/deepcoder-14b-preview:free' => ['class' => Model::class, 'capabilities' => []], + 'agentica-org/deepcoder-14b-preview' => ['class' => Model::class, 'capabilities' => []], + 'x-ai/grok-3-mini-beta' => ['class' => Model::class, 'capabilities' => []], + 'x-ai/grok-3-beta' => ['class' => Model::class, 'capabilities' => []], + 'nvidia/llama-3.1-nemotron-ultra-253b-v1' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-4-maverick:free' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-4-maverick' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-4-scout:free' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-4-scout' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen2.5-vl-32b-instruct:free' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen2.5-vl-32b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-chat-v3-0324:free' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-chat-v3-0324' => ['class' => Model::class, 'capabilities' => []], + 'openai/o1-pro' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-small-3.1-24b-instruct:free' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-small-3.1-24b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'allenai/olmo-2-0325-32b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-3-4b-it:free' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-3-4b-it' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-3-12b-it:free' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-3-12b-it' => ['class' => Model::class, 'capabilities' => []], + 'cohere/command-a' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4o-mini-search-preview' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4o-search-preview' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-3-27b-it:free' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-3-27b-it' => ['class' => Model::class, 'capabilities' => []], + 'thedrummer/skyfall-36b-v2' => ['class' => Model::class, 'capabilities' => []], + 'microsoft/phi-4-multimodal-instruct' => ['class' => Model::class, 'capabilities' => []], + 'perplexity/sonar-reasoning-pro' => ['class' => Model::class, 'capabilities' => []], + 'perplexity/sonar-pro' => ['class' => Model::class, 'capabilities' => []], + 'perplexity/sonar-deep-research' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwq-32b' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.0-flash-lite-001' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-3.7-sonnet:thinking' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-3.7-sonnet' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-saba' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-guard-3-8b' => ['class' => Model::class, 'capabilities' => []], + 'openai/o3-mini-high' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.0-flash-001' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-vl-plus' => ['class' => Model::class, 'capabilities' => []], + 'aion-labs/aion-1.0' => ['class' => Model::class, 'capabilities' => []], + 'aion-labs/aion-1.0-mini' => ['class' => Model::class, 'capabilities' => []], + 'aion-labs/aion-rp-llama-3.1-8b' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-vl-max' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-turbo' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen2.5-vl-72b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-plus' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-max' => ['class' => Model::class, 'capabilities' => []], + 'openai/o3-mini' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-small-24b-instruct-2501:free' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-small-24b-instruct-2501' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-r1-distill-qwen-32b' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-r1-distill-qwen-14b' => ['class' => Model::class, 'capabilities' => []], + 'perplexity/sonar-reasoning' => ['class' => Model::class, 'capabilities' => []], + 'perplexity/sonar' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-r1-distill-llama-70b:free' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-r1-distill-llama-70b' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-r1:free' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-r1' => ['class' => Model::class, 'capabilities' => []], + 'minimax/minimax-01' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/codestral-2501' => ['class' => Model::class, 'capabilities' => []], + 'microsoft/phi-4' => ['class' => Model::class, 'capabilities' => []], + 'sao10k/l3.1-70b-hanami-x1' => ['class' => Model::class, 'capabilities' => []], + 'deepseek/deepseek-chat' => ['class' => Model::class, 'capabilities' => []], + 'sao10k/l3.3-euryale-70b' => ['class' => Model::class, 'capabilities' => []], + 'openai/o1' => ['class' => Model::class, 'capabilities' => []], + 'cohere/command-r7b-12-2024' => ['class' => Model::class, 'capabilities' => []], + 'google/gemini-2.0-flash-exp:free' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.3-70b-instruct:free' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.3-70b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'amazon/nova-lite-v1' => ['class' => Model::class, 'capabilities' => []], + 'amazon/nova-micro-v1' => ['class' => Model::class, 'capabilities' => []], + 'amazon/nova-pro-v1' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4o-2024-11-20' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-large-2411' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-large-2407' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/pixtral-large-2411' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-2.5-coder-32b-instruct:free' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-2.5-coder-32b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'raifle/sorcererlm-8x22b' => ['class' => Model::class, 'capabilities' => []], + 'thedrummer/unslopnemo-12b' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-3.5-haiku' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-3.5-haiku-20241022' => ['class' => Model::class, 'capabilities' => []], + 'anthracite-org/magnum-v4-72b' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-3.5-sonnet' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/ministral-8b' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/ministral-3b' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-2.5-7b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'nvidia/llama-3.1-nemotron-70b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'inflection/inflection-3-productivity' => ['class' => Model::class, 'capabilities' => []], + 'inflection/inflection-3-pi' => ['class' => Model::class, 'capabilities' => []], + 'thedrummer/rocinante-12b' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.2-3b-instruct:free' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.2-3b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.2-1b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.2-90b-vision-instruct' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.2-11b-vision-instruct' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-2.5-72b-instruct:free' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-2.5-72b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'neversleep/llama-3.1-lumimaid-8b' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/pixtral-12b' => ['class' => Model::class, 'capabilities' => []], + 'cohere/command-r-plus-08-2024' => ['class' => Model::class, 'capabilities' => []], + 'cohere/command-r-08-2024' => ['class' => Model::class, 'capabilities' => []], + 'qwen/qwen-2.5-vl-7b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'sao10k/l3.1-euryale-70b' => ['class' => Model::class, 'capabilities' => []], + 'microsoft/phi-3.5-mini-128k-instruct' => ['class' => Model::class, 'capabilities' => []], + 'nousresearch/hermes-3-llama-3.1-70b' => ['class' => Model::class, 'capabilities' => []], + 'nousresearch/hermes-3-llama-3.1-405b:free' => ['class' => Model::class, 'capabilities' => []], + 'nousresearch/hermes-3-llama-3.1-405b' => ['class' => Model::class, 'capabilities' => []], + 'openai/chatgpt-4o-latest' => ['class' => Model::class, 'capabilities' => []], + 'sao10k/l3-lunaris-8b' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4o-2024-08-06' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.1-405b' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.1-8b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.1-405b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3.1-70b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-nemo:free' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-nemo' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4o-mini' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4o-mini-2024-07-18' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-2-27b-it' => ['class' => Model::class, 'capabilities' => []], + 'google/gemma-2-9b-it' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-3.5-sonnet-20240620' => ['class' => Model::class, 'capabilities' => []], + 'sao10k/l3-euryale-70b' => ['class' => Model::class, 'capabilities' => []], + 'nousresearch/hermes-2-pro-llama-3-8b' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-7b-instruct:free' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-7b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-7b-instruct-v0.3' => ['class' => Model::class, 'capabilities' => []], + 'microsoft/phi-3-mini-128k-instruct' => ['class' => Model::class, 'capabilities' => []], + 'microsoft/phi-3-medium-128k-instruct' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4o' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4o:extended' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-guard-2-8b' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4o-2024-05-13' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3-8b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'meta-llama/llama-3-70b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mixtral-8x22b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'microsoft/wizardlm-2-8x22b' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4-turbo' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-3-haiku' => ['class' => Model::class, 'capabilities' => []], + 'anthropic/claude-3-opus' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-large' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-3.5-turbo-0613' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4-turbo-preview' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-small' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-tiny' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-7b-instruct-v0.2' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mixtral-8x7b-instruct' => ['class' => Model::class, 'capabilities' => []], + 'neversleep/noromaid-20b' => ['class' => Model::class, 'capabilities' => []], + 'alpindale/goliath-120b' => ['class' => Model::class, 'capabilities' => []], + 'openrouter/auto' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4-1106-preview' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-3.5-turbo-instruct' => ['class' => Model::class, 'capabilities' => []], + 'mistralai/mistral-7b-instruct-v0.1' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-3.5-turbo-16k' => ['class' => Model::class, 'capabilities' => []], + 'mancer/weaver' => ['class' => Model::class, 'capabilities' => []], + 'undi95/remm-slerp-l2-13b' => ['class' => Model::class, 'capabilities' => []], + 'gryphe/mythomax-l2-13b' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-3.5-turbo' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4' => ['class' => Model::class, 'capabilities' => []], + 'openai/gpt-4-0314' => ['class' => Model::class, 'capabilities' => []], + + // Embeddings + 'qwen/qwen3-embedding-0.6b' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'mistralai/mistral-embed-2312' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'google/gemini-embedding-001' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'openai/text-embedding-ada-002' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'mistralai/codestral-embed-2505' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'openai/text-embedding-3-large' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'openai/text-embedding-3-small' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'qwen/qwen3-embedding-8b' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'qwen/qwen3-embedding-4b' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + ]; + + $this->models = array_merge($defaultModels, $additionalModels); + } } diff --git a/src/platform/src/Bridge/OpenRouter/PlatformFactory.php b/src/platform/src/Bridge/OpenRouter/PlatformFactory.php index 4cfdfbf13..9261a7cce 100644 --- a/src/platform/src/Bridge/OpenRouter/PlatformFactory.php +++ b/src/platform/src/Bridge/OpenRouter/PlatformFactory.php @@ -15,6 +15,10 @@ use Symfony\AI\Platform\Bridge\Gemini\Contract\AssistantMessageNormalizer; use Symfony\AI\Platform\Bridge\Gemini\Contract\MessageBagNormalizer; use Symfony\AI\Platform\Bridge\Gemini\Contract\UserMessageNormalizer; +use Symfony\AI\Platform\Bridge\OpenRouter\Completions\ModelClient as CompletionsModelClient; +use Symfony\AI\Platform\Bridge\OpenRouter\Completions\ResultConverter as CompletionsResultConverter; +use Symfony\AI\Platform\Bridge\OpenRouter\Embeddings\ModelClient as EmbeddingsModelClient; +use Symfony\AI\Platform\Bridge\OpenRouter\Embeddings\ResultConverter as EmbeddingsResultConverter; use Symfony\AI\Platform\Contract; use Symfony\AI\Platform\ModelCatalog\ModelCatalogInterface; use Symfony\AI\Platform\Platform; @@ -36,8 +40,8 @@ public static function create( $httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); return new Platform( - [new ModelClient($httpClient, $apiKey)], - [new ResultConverter()], + [new EmbeddingsModelClient($httpClient, $apiKey), new CompletionsModelClient($httpClient, $apiKey)], + [new EmbeddingsResultConverter(), new CompletionsResultConverter()], $modelCatalog, $contract ?? Contract::create( new AssistantMessageNormalizer(),