Skip to content

Commit ba83973

Browse files
committed
feat: add Requesty LLM provider
Add RequestyLLM mirroring the existing OpenRouterLLM, using Requesty's OpenAI-compatible router endpoint (https://router.requesty.ai/v1). - New semantic_router/llms/requesty.py with RequestyLLM - Export RequestyLLM from semantic_router.llms - Unit tests in tests/unit/llms/test_llm_requesty.py
1 parent df9722a commit ba83973

3 files changed

Lines changed: 143 additions & 0 deletions

File tree

semantic_router/llms/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
from semantic_router.llms.mistral import MistralAILLM
55
from semantic_router.llms.openai import OpenAILLM
66
from semantic_router.llms.openrouter import OpenRouterLLM
7+
from semantic_router.llms.requesty import RequestyLLM
78
from semantic_router.llms.zure import AzureOpenAILLM
89

910
__all__ = [
1011
"BaseLLM",
1112
"OpenAILLM",
1213
"LlamaCppLLM",
1314
"OpenRouterLLM",
15+
"RequestyLLM",
1416
"CohereLLM",
1517
"AzureOpenAILLM",
1618
"MistralAILLM",

semantic_router/llms/requesty.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import os
2+
from typing import List, Optional
3+
4+
import openai
5+
from pydantic import PrivateAttr
6+
7+
from semantic_router.llms import BaseLLM
8+
from semantic_router.schema import Message
9+
from semantic_router.utils.logger import logger
10+
11+
12+
class RequestyLLM(BaseLLM):
13+
"""LLM for Requesty. Requires a Requesty API key, see here for more information
14+
https://docs.requesty.ai/quickstart"""
15+
16+
_client: Optional[openai.OpenAI] = PrivateAttr(default=None)
17+
_base_url: str = PrivateAttr(default="https://router.requesty.ai/v1")
18+
19+
def __init__(
20+
self,
21+
name: Optional[str] = None,
22+
requesty_api_key: Optional[str] = None,
23+
base_url: str = "https://router.requesty.ai/v1",
24+
temperature: float = 0.01,
25+
max_tokens: int = 200,
26+
):
27+
"""Initialize the RequestyLLM.
28+
29+
:param name: The name of the Requesty model to use.
30+
:type name: Optional[str]
31+
:param requesty_api_key: The Requesty API key.
32+
:type requesty_api_key: Optional[str]
33+
:param base_url: The base URL for the Requesty API.
34+
:type base_url: str
35+
:param temperature: The temperature of the LLM.
36+
:type temperature: float
37+
:param max_tokens: The maximum number of tokens to generate.
38+
:type max_tokens: int
39+
"""
40+
if name is None:
41+
name = os.getenv("REQUESTY_CHAT_MODEL_NAME", "openai/gpt-4o-mini")
42+
super().__init__(name=name)
43+
self._base_url = base_url
44+
api_key = requesty_api_key or os.getenv("REQUESTY_API_KEY")
45+
if api_key is None:
46+
raise ValueError("Requesty API key cannot be 'None'.")
47+
try:
48+
self._client = openai.OpenAI(api_key=api_key, base_url=self._base_url)
49+
except Exception as e:
50+
raise ValueError(
51+
f"Requesty API client failed to initialize. Error: {e}"
52+
) from e
53+
self.temperature = temperature
54+
self.max_tokens = max_tokens
55+
56+
def __call__(self, messages: List[Message]) -> str:
57+
"""Call the RequestyLLM.
58+
59+
:param messages: The messages to pass to the RequestyLLM.
60+
:type messages: List[Message]
61+
:return: The response from the RequestyLLM.
62+
:rtype: str
63+
"""
64+
if self._client is None:
65+
raise ValueError("Requesty client is not initialized.")
66+
try:
67+
completion = self._client.chat.completions.create(
68+
model=self.name,
69+
messages=[m.to_openai() for m in messages],
70+
temperature=self.temperature,
71+
max_tokens=self.max_tokens,
72+
)
73+
74+
output = completion.choices[0].message.content
75+
76+
if not output:
77+
raise Exception("No output generated")
78+
return output
79+
except Exception as e:
80+
logger.error(f"LLM error: {e}")
81+
raise Exception(f"LLM error: {e}") from e
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import pytest
2+
3+
from semantic_router.llms import RequestyLLM
4+
from semantic_router.schema import Message
5+
6+
7+
@pytest.fixture
8+
def requesty_llm(mocker):
9+
mocker.patch("openai.Client")
10+
return RequestyLLM(requesty_api_key="test_api_key")
11+
12+
13+
class TestRequestyLLM:
14+
def test_requesty_llm_init_with_api_key(self, requesty_llm):
15+
assert requesty_llm._client is not None, "Client should be initialized"
16+
assert (
17+
requesty_llm.name == "openai/gpt-4o-mini"
18+
), "Default name not set correctly"
19+
20+
def test_requesty_llm_init_success(self, mocker):
21+
mocker.patch("os.getenv", return_value="fake-api-key")
22+
llm = RequestyLLM()
23+
assert llm._client is not None
24+
25+
def test_requesty_llm_init_without_api_key(self, mocker):
26+
mocker.patch("os.getenv", return_value=None)
27+
with pytest.raises(ValueError) as _:
28+
RequestyLLM()
29+
30+
def test_requesty_llm_call_uninitialized_client(self, requesty_llm):
31+
# Set the client to None to simulate an uninitialized client
32+
requesty_llm._client = None
33+
with pytest.raises(ValueError) as e:
34+
llm_input = [Message(role="user", content="test")]
35+
requesty_llm(llm_input)
36+
assert "Requesty client is not initialized." in str(e.value)
37+
38+
def test_requesty_llm_init_exception(self, mocker):
39+
mocker.patch("os.getenv", return_value="fake-api-key")
40+
mocker.patch("openai.OpenAI", side_effect=Exception("Initialization error"))
41+
with pytest.raises(ValueError) as e:
42+
RequestyLLM()
43+
assert (
44+
"Requesty API client failed to initialize. Error: Initialization error"
45+
in str(e.value)
46+
)
47+
48+
def test_requesty_llm_call_success(self, requesty_llm, mocker):
49+
mock_completion = mocker.MagicMock()
50+
mock_completion.choices[0].message.content = "test"
51+
52+
mocker.patch("os.getenv", return_value="fake-api-key")
53+
mocker.patch.object(
54+
requesty_llm._client.chat.completions,
55+
"create",
56+
return_value=mock_completion,
57+
)
58+
llm_input = [Message(role="user", content="test")]
59+
output = requesty_llm(llm_input)
60+
assert output == "test"

0 commit comments

Comments
 (0)