rsroute 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rsroute/__init__.py +47 -0
- rsroute/agents/__init__.py +16 -0
- rsroute/agents/gemini_agent.py +77 -0
- rsroute/agents/groq_agent.py +54 -0
- rsroute/agents/mistral_agent.py +53 -0
- rsroute/agents/openai_agent.py +88 -0
- rsroute/agents/openrouter_agent.py +88 -0
- rsroute/base.py +324 -0
- rsroute/chat_models/__init__.py +17 -0
- rsroute/chat_models/gemini_client.py +46 -0
- rsroute/chat_models/groq_client.py +43 -0
- rsroute/chat_models/mistral_client.py +46 -0
- rsroute/chat_models/openai_client.py +55 -0
- rsroute/chat_models/openrouter_client.py +56 -0
- rsroute/urls.py +44 -0
- rsroute-0.1.0.dist-info/METADATA +104 -0
- rsroute-0.1.0.dist-info/RECORD +18 -0
- rsroute-0.1.0.dist-info/WHEEL +4 -0
rsroute/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from . import chat_models, agents
|
|
2
|
+
from .base import BaseEstimator, BaseEstimatorAgent
|
|
3
|
+
from .chat_models import (
|
|
4
|
+
ChatGemini,
|
|
5
|
+
ChatMistral,
|
|
6
|
+
ChatMistralAI,
|
|
7
|
+
MistralAI,
|
|
8
|
+
ChatGroq,
|
|
9
|
+
ChatOpenRouter,
|
|
10
|
+
OpenRouter,
|
|
11
|
+
ChatGPT,
|
|
12
|
+
ChatOpenAI,
|
|
13
|
+
)
|
|
14
|
+
from .agents import (
|
|
15
|
+
GeminiAgent,
|
|
16
|
+
MistralAgent,
|
|
17
|
+
GroqAgent,
|
|
18
|
+
OpenRouterAgent,
|
|
19
|
+
ChatOpenRouterAgent,
|
|
20
|
+
GPTAgent,
|
|
21
|
+
OpenAIAgent,
|
|
22
|
+
ChatGPTAgent,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"chat_models",
|
|
27
|
+
"agents",
|
|
28
|
+
"BaseEstimator",
|
|
29
|
+
"BaseEstimatorAgent",
|
|
30
|
+
"ChatGemini",
|
|
31
|
+
"ChatMistral",
|
|
32
|
+
"ChatMistralAI",
|
|
33
|
+
"MistralAI",
|
|
34
|
+
"ChatGroq",
|
|
35
|
+
"ChatOpenRouter",
|
|
36
|
+
"OpenRouter",
|
|
37
|
+
"ChatGPT",
|
|
38
|
+
"ChatOpenAI",
|
|
39
|
+
"GeminiAgent",
|
|
40
|
+
"MistralAgent",
|
|
41
|
+
"GroqAgent",
|
|
42
|
+
"OpenRouterAgent",
|
|
43
|
+
"ChatOpenRouterAgent",
|
|
44
|
+
"GPTAgent",
|
|
45
|
+
"OpenAIAgent",
|
|
46
|
+
"ChatGPTAgent",
|
|
47
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .gemini_agent import GeminiAgent
|
|
2
|
+
from .mistral_agent import MistralAgent
|
|
3
|
+
from .groq_agent import GroqAgent
|
|
4
|
+
from .openrouter_agent import OpenRouterAgent, ChatOpenRouterAgent
|
|
5
|
+
from .openai_agent import GPTAgent, OpenAIAgent, ChatGPTAgent
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"GeminiAgent",
|
|
9
|
+
"MistralAgent",
|
|
10
|
+
"GroqAgent",
|
|
11
|
+
"OpenRouterAgent",
|
|
12
|
+
"ChatOpenRouterAgent",
|
|
13
|
+
"GPTAgent",
|
|
14
|
+
"OpenAIAgent",
|
|
15
|
+
"ChatGPTAgent",
|
|
16
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Implementation of Gemini Agent Integration by RSRoute"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional, List
|
|
4
|
+
|
|
5
|
+
from rsroute.base import BaseEstimatorAgent
|
|
6
|
+
from rsroute.urls import END_POINTS, URLS
|
|
7
|
+
from langchain_core.tools import StructuredTool
|
|
8
|
+
import warnings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GeminiAgent(BaseEstimatorAgent):
|
|
12
|
+
"""GeminiAgent client for RSRoute."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
model: Optional[str] = None,
|
|
17
|
+
tools: List[StructuredTool] = None,
|
|
18
|
+
master_key: Optional[str] = None,
|
|
19
|
+
rsroute_url: Optional[str] = None,
|
|
20
|
+
temperature: Optional[float] = None,
|
|
21
|
+
max_tokens: Optional[int] = None,
|
|
22
|
+
system_prompt: Optional[Any] = None,
|
|
23
|
+
max_tool_calls: Optional[int] = 10,
|
|
24
|
+
timeout: int = 120,
|
|
25
|
+
end_point: Optional[str] = None,
|
|
26
|
+
thinking_level: Optional[Any] = None,
|
|
27
|
+
thinking_budget: Optional[int] = None,
|
|
28
|
+
**kwargs: Any,
|
|
29
|
+
):
|
|
30
|
+
# Determine the target endpoint URL
|
|
31
|
+
if rsroute_url is None:
|
|
32
|
+
gemini_url = URLS.get("gemini_agent")
|
|
33
|
+
else:
|
|
34
|
+
gemini_url = f"{rsroute_url.rstrip('/')}/{END_POINTS.get('gemini_agent').lstrip('/')}"
|
|
35
|
+
|
|
36
|
+
super().__init__(
|
|
37
|
+
rsroute_url=gemini_url,
|
|
38
|
+
model=model,
|
|
39
|
+
max_tool_calls=max_tool_calls,
|
|
40
|
+
tools=tools,
|
|
41
|
+
master_key=master_key,
|
|
42
|
+
temperature=temperature,
|
|
43
|
+
max_tokens=max_tokens,
|
|
44
|
+
system_prompt=system_prompt,
|
|
45
|
+
timeout=timeout,
|
|
46
|
+
end_point=end_point,
|
|
47
|
+
thinking_level=thinking_level,
|
|
48
|
+
thinking_budget=thinking_budget,
|
|
49
|
+
**kwargs,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def ainvoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
53
|
+
"""Send a request to RSRoute Gemini Agent endpoint with the given query."""
|
|
54
|
+
return super().ainvoke(query=query)
|
|
55
|
+
|
|
56
|
+
def invoke(self, query=None):
|
|
57
|
+
"""Generate response from Agent with ReAct Loop"""
|
|
58
|
+
results: Dict[str, Any] = super().invoke(query)
|
|
59
|
+
if not isinstance(results.get("content"), list):
|
|
60
|
+
return results
|
|
61
|
+
if len(results.get("content", [])) != 1:
|
|
62
|
+
return results
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
result = results.copy() # Safety copy
|
|
66
|
+
content: Dict[str, Any] = result.get("content")[0]
|
|
67
|
+
text_result = content.get("text")
|
|
68
|
+
extras = content.get("extras")
|
|
69
|
+
|
|
70
|
+
result["content"] = text_result
|
|
71
|
+
result["extras"] = extras
|
|
72
|
+
|
|
73
|
+
return result
|
|
74
|
+
except Exception as E:
|
|
75
|
+
warnings.warn(f"Got Error while extracting the information, {str(E)}")
|
|
76
|
+
|
|
77
|
+
return results # after Exception
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Implementation of Groq Agent Integration by RSRoute"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional, List
|
|
4
|
+
|
|
5
|
+
from rsroute.base import BaseEstimatorAgent
|
|
6
|
+
from rsroute.urls import END_POINTS, URLS
|
|
7
|
+
from langchain_core.tools import StructuredTool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GroqAgent(BaseEstimatorAgent):
|
|
11
|
+
"""GroqAgent client for RSRoute."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
model: Optional[str] = None,
|
|
16
|
+
tools: List[StructuredTool] = None,
|
|
17
|
+
master_key: Optional[str] = None,
|
|
18
|
+
rsroute_url: Optional[str] = None,
|
|
19
|
+
temperature: Optional[float] = None,
|
|
20
|
+
max_tokens: Optional[int] = None,
|
|
21
|
+
system_prompt: Optional[Any] = None,
|
|
22
|
+
max_tool_calls: Optional[int] = 10,
|
|
23
|
+
timeout: int = 120,
|
|
24
|
+
end_point: Optional[str] = None,
|
|
25
|
+
**kwargs: Any,
|
|
26
|
+
):
|
|
27
|
+
# Determine the target endpoint URL
|
|
28
|
+
if rsroute_url is None:
|
|
29
|
+
groq_url = URLS.get("groq_agent")
|
|
30
|
+
else:
|
|
31
|
+
groq_url = f"{rsroute_url.rstrip('/')}/{END_POINTS.get('groq_agent').lstrip('/')}"
|
|
32
|
+
|
|
33
|
+
super().__init__(
|
|
34
|
+
rsroute_url=groq_url,
|
|
35
|
+
model=model,
|
|
36
|
+
max_tool_calls=max_tool_calls,
|
|
37
|
+
tools=tools,
|
|
38
|
+
master_key=master_key,
|
|
39
|
+
temperature=temperature,
|
|
40
|
+
max_tokens=max_tokens,
|
|
41
|
+
system_prompt=system_prompt,
|
|
42
|
+
timeout=timeout,
|
|
43
|
+
end_point=end_point,
|
|
44
|
+
**kwargs,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def ainvoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
48
|
+
"""Send a request to RSRoute Groq Agent endpoint with the given query."""
|
|
49
|
+
return super().ainvoke(query=query)
|
|
50
|
+
|
|
51
|
+
def invoke(self, query=None):
|
|
52
|
+
"""Generate response from Agent with ReAct Loop"""
|
|
53
|
+
return super().invoke(query)
|
|
54
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Implementation of Mistral Agent Integration by RSRoute"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional, List
|
|
4
|
+
|
|
5
|
+
from rsroute.base import BaseEstimatorAgent
|
|
6
|
+
from rsroute.urls import END_POINTS, URLS
|
|
7
|
+
from langchain_core.tools import StructuredTool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MistralAgent(BaseEstimatorAgent):
|
|
11
|
+
"""MistralAgent client for RSRoute."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
model: Optional[str] = None,
|
|
16
|
+
tools: List[StructuredTool] = None,
|
|
17
|
+
master_key: Optional[str] = None,
|
|
18
|
+
rsroute_url: Optional[str] = None,
|
|
19
|
+
temperature: Optional[float] = None,
|
|
20
|
+
max_tokens: Optional[int] = None,
|
|
21
|
+
system_prompt: Optional[Any] = None,
|
|
22
|
+
max_tool_calls: Optional[int] = 10,
|
|
23
|
+
timeout: int = 120,
|
|
24
|
+
end_point: Optional[str] = None,
|
|
25
|
+
**kwargs: Any,
|
|
26
|
+
):
|
|
27
|
+
# Determine the target endpoint URL
|
|
28
|
+
if rsroute_url is None:
|
|
29
|
+
mistral_url = URLS.get("mistral_agent")
|
|
30
|
+
else:
|
|
31
|
+
mistral_url = f"{rsroute_url.rstrip('/')}/{END_POINTS.get('mistral_agent').lstrip('/')}"
|
|
32
|
+
|
|
33
|
+
super().__init__(
|
|
34
|
+
rsroute_url=mistral_url,
|
|
35
|
+
model=model,
|
|
36
|
+
max_tool_calls=max_tool_calls,
|
|
37
|
+
tools=tools,
|
|
38
|
+
master_key=master_key,
|
|
39
|
+
temperature=temperature,
|
|
40
|
+
max_tokens=max_tokens,
|
|
41
|
+
system_prompt=system_prompt,
|
|
42
|
+
timeout=timeout,
|
|
43
|
+
end_point=end_point,
|
|
44
|
+
**kwargs,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def ainvoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
48
|
+
"""Send a request to RSRoute Mistral Agent endpoint with the given query."""
|
|
49
|
+
return super().ainvoke(query=query)
|
|
50
|
+
|
|
51
|
+
def invoke(self, query=None):
|
|
52
|
+
"""Generate response from Agent with ReAct Loop"""
|
|
53
|
+
return super().invoke(query)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Implementation of OpenAI Agent Integration by RSRoute"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from rsroute.base import BaseEstimatorAgent, get_org_id
|
|
7
|
+
from rsroute.urls import END_POINTS, URLS
|
|
8
|
+
from langchain_core.tools import StructuredTool
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GPTAgent(BaseEstimatorAgent):
|
|
12
|
+
"""GPTAgent (OpenAIAgent) client for RSRoute."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
model: Optional[str] = None,
|
|
17
|
+
tools: List[StructuredTool] = None,
|
|
18
|
+
master_key: Optional[str] = None,
|
|
19
|
+
rsroute_url: Optional[str] = None,
|
|
20
|
+
temperature: Optional[float] = None,
|
|
21
|
+
max_tokens: Optional[int] = None,
|
|
22
|
+
system_prompt: Optional[Any] = None,
|
|
23
|
+
max_tool_calls: Optional[int] = 10,
|
|
24
|
+
timeout: int = 120,
|
|
25
|
+
end_point: Optional[str] = None,
|
|
26
|
+
max_retries: Optional[int] = None,
|
|
27
|
+
use_response_api: Optional[bool] = None,
|
|
28
|
+
organization_id: Optional[str] = None,
|
|
29
|
+
**kwargs: Any,
|
|
30
|
+
):
|
|
31
|
+
# Determine the target endpoint URL
|
|
32
|
+
if rsroute_url is None:
|
|
33
|
+
openai_url = URLS.get("openai_agent")
|
|
34
|
+
else:
|
|
35
|
+
openai_url = f"{rsroute_url.rstrip('/')}/{END_POINTS.get('openai_agent').lstrip('/')}"
|
|
36
|
+
|
|
37
|
+
# Resolve organization_id: explicit arg > org_id alias > .env lookup
|
|
38
|
+
resolved_org_id = organization_id or get_org_id()
|
|
39
|
+
|
|
40
|
+
super().__init__(
|
|
41
|
+
rsroute_url=openai_url,
|
|
42
|
+
model=model,
|
|
43
|
+
max_tool_calls=max_tool_calls,
|
|
44
|
+
tools=tools,
|
|
45
|
+
master_key=master_key,
|
|
46
|
+
temperature=temperature,
|
|
47
|
+
max_tokens=max_tokens,
|
|
48
|
+
system_prompt=system_prompt,
|
|
49
|
+
timeout=timeout,
|
|
50
|
+
end_point=end_point,
|
|
51
|
+
max_retries=max_retries,
|
|
52
|
+
use_response_api=use_response_api,
|
|
53
|
+
organization_id=resolved_org_id,
|
|
54
|
+
**kwargs,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def ainvoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
58
|
+
"""Send a request to RSRoute OpenAI Agent endpoint with the given query."""
|
|
59
|
+
return super().ainvoke(query=query)
|
|
60
|
+
|
|
61
|
+
def invoke(self, query=None):
|
|
62
|
+
"""Generate response from Agent with ReAct Loop"""
|
|
63
|
+
results: Dict[str, Any] = super().invoke(query)
|
|
64
|
+
if not isinstance(results.get("content"), list):
|
|
65
|
+
return results
|
|
66
|
+
if len(results.get("content", [])) != 1:
|
|
67
|
+
return results
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
result = results.copy() # Safety copy
|
|
71
|
+
content: Dict[str, Any] = result.get("content")[0]
|
|
72
|
+
if isinstance(content, dict) and "text" in content:
|
|
73
|
+
text_result = content.get("text")
|
|
74
|
+
extras = content.get("extras")
|
|
75
|
+
|
|
76
|
+
result["content"] = text_result
|
|
77
|
+
result["extras"] = extras
|
|
78
|
+
|
|
79
|
+
return result
|
|
80
|
+
except Exception as E:
|
|
81
|
+
warnings.warn(f"Got Error while extracting the information, {str(E)}")
|
|
82
|
+
|
|
83
|
+
return results
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Aliases
|
|
87
|
+
OpenAIAgent = GPTAgent
|
|
88
|
+
ChatGPTAgent = GPTAgent
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Implementation of OpenRouter Agent Integration by RSRoute"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from rsroute.base import BaseEstimatorAgent
|
|
7
|
+
from rsroute.urls import END_POINTS, URLS
|
|
8
|
+
from langchain_core.tools import StructuredTool
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OpenRouterAgent(BaseEstimatorAgent):
|
|
12
|
+
"""OpenRouterAgent client for RSRoute."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
model: Optional[str] = None,
|
|
17
|
+
tools: List[StructuredTool] = None,
|
|
18
|
+
master_key: Optional[str] = None,
|
|
19
|
+
rsroute_url: Optional[str] = None,
|
|
20
|
+
temperature: Optional[float] = None,
|
|
21
|
+
max_tokens: Optional[int] = None,
|
|
22
|
+
system_prompt: Optional[Any] = None,
|
|
23
|
+
max_tool_calls: Optional[int] = 10,
|
|
24
|
+
timeout: int = 120,
|
|
25
|
+
end_point: Optional[str] = None,
|
|
26
|
+
app_title: Optional[str] = None,
|
|
27
|
+
app_url: Optional[str] = None,
|
|
28
|
+
app_categories: Optional[List[str]] = None,
|
|
29
|
+
max_retries: Optional[int] = None,
|
|
30
|
+
providers: Optional[Dict[str, Any]] = None,
|
|
31
|
+
**kwargs: Any,
|
|
32
|
+
):
|
|
33
|
+
# Determine the target endpoint URL
|
|
34
|
+
if rsroute_url is None:
|
|
35
|
+
openrouter_url = URLS.get("openrouter_agent")
|
|
36
|
+
else:
|
|
37
|
+
openrouter_url = f"{rsroute_url.rstrip('/')}/{END_POINTS.get('openrouter_agent').lstrip('/')}"
|
|
38
|
+
|
|
39
|
+
super().__init__(
|
|
40
|
+
rsroute_url=openrouter_url,
|
|
41
|
+
model=model,
|
|
42
|
+
max_tool_calls=max_tool_calls,
|
|
43
|
+
tools=tools,
|
|
44
|
+
master_key=master_key,
|
|
45
|
+
temperature=temperature,
|
|
46
|
+
max_tokens=max_tokens,
|
|
47
|
+
system_prompt=system_prompt,
|
|
48
|
+
timeout=timeout,
|
|
49
|
+
end_point=end_point,
|
|
50
|
+
app_title=app_title,
|
|
51
|
+
app_url=app_url,
|
|
52
|
+
app_categories=app_categories,
|
|
53
|
+
max_retries=max_retries,
|
|
54
|
+
providers=providers,
|
|
55
|
+
**kwargs,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def ainvoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
59
|
+
"""Send a request to RSRoute OpenRouter Agent endpoint with the given query."""
|
|
60
|
+
return super().ainvoke(query=query)
|
|
61
|
+
|
|
62
|
+
def invoke(self, query=None):
|
|
63
|
+
"""Generate response from Agent with ReAct Loop"""
|
|
64
|
+
results: Dict[str, Any] = super().invoke(query)
|
|
65
|
+
if not isinstance(results.get("content"), list):
|
|
66
|
+
return results
|
|
67
|
+
if len(results.get("content", [])) != 1:
|
|
68
|
+
return results
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
result = results.copy() # Safety copy
|
|
72
|
+
content: Dict[str, Any] = result.get("content")[0]
|
|
73
|
+
if isinstance(content, dict) and "text" in content:
|
|
74
|
+
text_result = content.get("text")
|
|
75
|
+
extras = content.get("extras")
|
|
76
|
+
|
|
77
|
+
result["content"] = text_result
|
|
78
|
+
result["extras"] = extras
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
except Exception as E:
|
|
82
|
+
warnings.warn(f"Got Error while extracting the information, {str(E)}")
|
|
83
|
+
|
|
84
|
+
return results
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
ChatOpenRouterAgent = OpenRouterAgent
|
|
88
|
+
|
rsroute/base.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any, Dict, Optional, List, Iterable
|
|
4
|
+
import requests
|
|
5
|
+
from langchain_core.tools import StructuredTool
|
|
6
|
+
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
|
|
7
|
+
from rich import print
|
|
8
|
+
import warnings
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
|
|
11
|
+
def _load_env_file(dotenv_path: str = ".env") -> None:
|
|
12
|
+
"""Fallback .env file loader if python-dotenv is not installed."""
|
|
13
|
+
if os.path.exists(dotenv_path):
|
|
14
|
+
try:
|
|
15
|
+
with open(dotenv_path, "r", encoding="utf-8") as f:
|
|
16
|
+
for line in f:
|
|
17
|
+
line = line.strip()
|
|
18
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
19
|
+
continue
|
|
20
|
+
k, v = line.split("=", 1)
|
|
21
|
+
k = k.strip()
|
|
22
|
+
v = v.strip().strip("'\"")
|
|
23
|
+
if k and k not in os.environ:
|
|
24
|
+
os.environ[k] = v
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def get_org_id():
|
|
29
|
+
"""automatically pick OpenAI Org Id from .env"""
|
|
30
|
+
try:
|
|
31
|
+
load_dotenv()
|
|
32
|
+
|
|
33
|
+
candidates = [
|
|
34
|
+
"org_id", "ORG_ID",
|
|
35
|
+
"organization_id", "ORGANIZATION_ID",
|
|
36
|
+
"openai_org_id", "OPENAI_ORG_ID"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
for candidate in candidates:
|
|
40
|
+
val = os.getenv(candidate)
|
|
41
|
+
|
|
42
|
+
if val:
|
|
43
|
+
return val
|
|
44
|
+
|
|
45
|
+
return None
|
|
46
|
+
except ModuleNotFoundError:
|
|
47
|
+
raise "No Module Found dotenv"
|
|
48
|
+
|
|
49
|
+
def find_master_key(master_key: Optional[str] = None) -> Optional[str]:
|
|
50
|
+
"""Search for rsroute_token, master_key, or rsroute_key in arguments, environment, or .env."""
|
|
51
|
+
if master_key:
|
|
52
|
+
return master_key
|
|
53
|
+
|
|
54
|
+
# Attempt to load .env using python-dotenv if installed, otherwise use fallback
|
|
55
|
+
try:
|
|
56
|
+
from dotenv import find_dotenv, load_dotenv
|
|
57
|
+
load_dotenv(find_dotenv(usecwd=True))
|
|
58
|
+
except ImportError:
|
|
59
|
+
_load_env_file()
|
|
60
|
+
|
|
61
|
+
# Search candidates in priority order (both lower and upper case)
|
|
62
|
+
candidates = [
|
|
63
|
+
"rsroute_token", "RSROUTE_TOKEN",
|
|
64
|
+
"master_key", "MASTER_KEY",
|
|
65
|
+
"rsroute_key", "RSROUTE_KEY",
|
|
66
|
+
"rsroute_master_key", "RSROUTE_MASTER_KEY",
|
|
67
|
+
]
|
|
68
|
+
for candidate in candidates:
|
|
69
|
+
val = os.getenv(candidate)
|
|
70
|
+
if val:
|
|
71
|
+
return val
|
|
72
|
+
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class BaseEstimator(ABC):
|
|
77
|
+
"""Base estimator class for RSRoute chat models."""
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def find_master_key(master_key: Optional[str] = None) -> Optional[str]:
|
|
81
|
+
"""Resolve the master key from parameter, environment, or .env file."""
|
|
82
|
+
return find_master_key(master_key)
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
rsroute_url: str = None,
|
|
87
|
+
model: Optional[str] = None,
|
|
88
|
+
timeout: int = 30,
|
|
89
|
+
master_key: str = None,
|
|
90
|
+
max_tokens: Optional[int] = None,
|
|
91
|
+
system_prompt: Optional[Any] = None,
|
|
92
|
+
**kwargs: Any,
|
|
93
|
+
):
|
|
94
|
+
if rsroute_url is None:
|
|
95
|
+
raise AttributeError("`rsroute_url` got `None`")
|
|
96
|
+
|
|
97
|
+
self.rsroute_url = rsroute_url
|
|
98
|
+
self.request_data: dict[str, Any] = {}
|
|
99
|
+
self.timeout = timeout
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if system_prompt is None:
|
|
103
|
+
self.system_prompt = False
|
|
104
|
+
elif isinstance(system_prompt, str):
|
|
105
|
+
self.system_prompt = {"role": "system", "content" : system_prompt}
|
|
106
|
+
elif isinstance(system_prompt, SystemMessage):
|
|
107
|
+
self.system_prompt = {"role": "system", "content" : system_prompt.content}
|
|
108
|
+
else:
|
|
109
|
+
self.system_prompt = {"role": "system", "content" : f"{system_prompt}"}
|
|
110
|
+
|
|
111
|
+
resolved_key = self.find_master_key(master_key)
|
|
112
|
+
if resolved_key is not None:
|
|
113
|
+
self.master_key = resolved_key
|
|
114
|
+
self.request_data["master_key"] = resolved_key
|
|
115
|
+
else:
|
|
116
|
+
raise ValueError(
|
|
117
|
+
"`master_key` Can't be None. Please pass master_key or set "
|
|
118
|
+
"`rsroute_token`, `master_key`, or `rsroute_key` in a .env file or environment variable."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if model is not None:
|
|
122
|
+
self.model = model
|
|
123
|
+
self.request_data["model"] = model
|
|
124
|
+
if max_tokens is not None:
|
|
125
|
+
self.max_tokens = max_tokens
|
|
126
|
+
self.request_data["max_tokens"] = max_tokens
|
|
127
|
+
|
|
128
|
+
for key, value in kwargs.items():
|
|
129
|
+
if value is not None:
|
|
130
|
+
self.request_data[key] = value
|
|
131
|
+
|
|
132
|
+
def get_params(self) -> Dict[str, Any]:
|
|
133
|
+
"""Return the initialized parameters for the model (excluding master_key)."""
|
|
134
|
+
params = self.request_data.copy()
|
|
135
|
+
params.pop("master_key", None)
|
|
136
|
+
return params
|
|
137
|
+
|
|
138
|
+
def __call__(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
139
|
+
"""Callable interface forwarding to invoke."""
|
|
140
|
+
return self.invoke(query=query)
|
|
141
|
+
|
|
142
|
+
def generate(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
143
|
+
"""Generate response forwarding to invoke."""
|
|
144
|
+
return self.invoke(query=query)
|
|
145
|
+
|
|
146
|
+
def invoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
147
|
+
"""Invoke the model endpoint with a query."""
|
|
148
|
+
request_data = self.request_data.copy()
|
|
149
|
+
|
|
150
|
+
if query is not None:
|
|
151
|
+
if isinstance(query, list):
|
|
152
|
+
request_data["query"] = query
|
|
153
|
+
elif self.system_prompt == False:
|
|
154
|
+
request_data["query"] = [
|
|
155
|
+
{"role": "user", "content": f"{query}"}
|
|
156
|
+
]
|
|
157
|
+
else:
|
|
158
|
+
request_data["query"] = [
|
|
159
|
+
self.system_prompt,
|
|
160
|
+
{
|
|
161
|
+
"role": "user",
|
|
162
|
+
"content": f"{query}"
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
return requests.post(url=self.rsroute_url, json=request_data, timeout=self.timeout).json()
|
|
168
|
+
except requests.exceptions.RequestException as exc:
|
|
169
|
+
raise RuntimeError(
|
|
170
|
+
f"RSRoute request failed ({type(exc).__name__}). "
|
|
171
|
+
"Check the server URL, connectivity, and timeout."
|
|
172
|
+
) from exc
|
|
173
|
+
|
|
174
|
+
class BaseEstimatorAgent(BaseEstimator):
|
|
175
|
+
def __init__(
|
|
176
|
+
self,
|
|
177
|
+
rsroute_url: str = None,
|
|
178
|
+
model: Optional[str] = None,
|
|
179
|
+
max_tool_calls : Optional[int] = 10,
|
|
180
|
+
timeout: int = 120,
|
|
181
|
+
system_prompt: Optional[Any] = None,
|
|
182
|
+
master_key: str = None,
|
|
183
|
+
max_tokens: Optional[int] = None,
|
|
184
|
+
tools: List[StructuredTool] = None,
|
|
185
|
+
**kwargs: Any,
|
|
186
|
+
):
|
|
187
|
+
|
|
188
|
+
if tools is None or len(tools) == 0:
|
|
189
|
+
raise ValueError("Tools can't be Empty")
|
|
190
|
+
|
|
191
|
+
# Safety Check
|
|
192
|
+
for tool in tools:
|
|
193
|
+
if not isinstance(tool, StructuredTool):
|
|
194
|
+
raise TypeError("Tool must be Langchain StructuredTool")
|
|
195
|
+
else:
|
|
196
|
+
...
|
|
197
|
+
|
|
198
|
+
# Internal Error Probably
|
|
199
|
+
if not rsroute_url.endswith("/agent"):
|
|
200
|
+
raise RuntimeError(f"Agent calls can only work with `/agent` end points, endpoint get = {rsroute_url}")
|
|
201
|
+
|
|
202
|
+
# tools information conversion
|
|
203
|
+
self.tools_info = self.__convert_tool_to_dict(tools=tools)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
super().__init__(
|
|
207
|
+
rsroute_url=rsroute_url,
|
|
208
|
+
model=model,
|
|
209
|
+
timeout=timeout,
|
|
210
|
+
master_key=master_key,
|
|
211
|
+
max_tokens=max_tokens,
|
|
212
|
+
tools_info=self.tools_info,
|
|
213
|
+
system_prompt=system_prompt,
|
|
214
|
+
**kwargs,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Important Info to Save
|
|
218
|
+
self.max_tool_calls = max_tool_calls
|
|
219
|
+
self.tools = tools
|
|
220
|
+
|
|
221
|
+
def ainvoke(self, query = None):
|
|
222
|
+
return super().invoke(query)
|
|
223
|
+
|
|
224
|
+
def invoke(self, query = None):
|
|
225
|
+
result = super().invoke(query)
|
|
226
|
+
tool_used_names = []
|
|
227
|
+
|
|
228
|
+
if not result["tool_calls"]:
|
|
229
|
+
return result
|
|
230
|
+
# tools mapping
|
|
231
|
+
tools_map = self.__map_tools(tools=self.tools)
|
|
232
|
+
messages = []
|
|
233
|
+
if self.system_prompt:
|
|
234
|
+
messages.append(self.system_prompt)
|
|
235
|
+
messages.extend([
|
|
236
|
+
{"role": "user", "content": f"{query}"},
|
|
237
|
+
{"role": "assistant", "content": str(result.get("content", "")), "tool_calls": result.get("tool_calls", [])}
|
|
238
|
+
])
|
|
239
|
+
usage = result.get("usage_metadata")
|
|
240
|
+
if isinstance(usage, dict):
|
|
241
|
+
try:
|
|
242
|
+
input_tokens = usage.get("input_tokens", 0)
|
|
243
|
+
output_tokens = usage.get("output_tokens", 0)
|
|
244
|
+
total_tokens = input_tokens + output_tokens
|
|
245
|
+
except (TypeError, ValueError):
|
|
246
|
+
warnings.warn("Error while extracting tokens from usage_metadata.")
|
|
247
|
+
input_tokens = output_tokens = total_tokens = None
|
|
248
|
+
else:
|
|
249
|
+
input_tokens = output_tokens = total_tokens = None
|
|
250
|
+
|
|
251
|
+
tool_used = 0
|
|
252
|
+
while result.get("tool_calls"):
|
|
253
|
+
if tool_used >= self.max_tool_calls:
|
|
254
|
+
raise BufferError(f"Agent Called More Tool than max_tool_calls which is set to {self.max_tool_calls}")
|
|
255
|
+
|
|
256
|
+
tool_call_info = result["tool_calls"][0]
|
|
257
|
+
|
|
258
|
+
# choosed tool
|
|
259
|
+
chosen_tool = tools_map.get(tool_call_info.get("name"))
|
|
260
|
+
if chosen_tool is None:
|
|
261
|
+
raise ValueError(f"Tool '{tool_call_info.get('name')}' not found in mapped tools")
|
|
262
|
+
|
|
263
|
+
if not tool_call_info.get("args"):
|
|
264
|
+
raise ValueError("Model provided no parameters for Tool call.")
|
|
265
|
+
|
|
266
|
+
tool_result = chosen_tool.invoke(tool_call_info.get("args"))
|
|
267
|
+
|
|
268
|
+
tool_used_names.append(chosen_tool.name)
|
|
269
|
+
messages.append(
|
|
270
|
+
{
|
|
271
|
+
"role": "tool",
|
|
272
|
+
"content": f"{tool_result}",
|
|
273
|
+
"tool_call_id": tool_call_info["id"]
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
result = super().invoke(messages)
|
|
278
|
+
messages.append(
|
|
279
|
+
{"role": "assistant", "content": str(result.get("content", "")), "tool_calls": result.get("tool_calls", [])}
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# usage update from new step
|
|
283
|
+
step_usage = result.get("usage_metadata")
|
|
284
|
+
if isinstance(step_usage, dict):
|
|
285
|
+
try:
|
|
286
|
+
input_tokens = (input_tokens or 0) + step_usage.get("input_tokens", 0)
|
|
287
|
+
output_tokens = (output_tokens or 0) + step_usage.get("output_tokens", 0)
|
|
288
|
+
total_tokens = input_tokens + output_tokens
|
|
289
|
+
except (TypeError, ValueError):
|
|
290
|
+
warnings.warn("Error while updating tokens from usage_metadata.")
|
|
291
|
+
|
|
292
|
+
tool_used += 1
|
|
293
|
+
|
|
294
|
+
if input_tokens is not None:
|
|
295
|
+
if not isinstance(result.get("usage_metadata"), dict):
|
|
296
|
+
result["usage_metadata"] = {}
|
|
297
|
+
result["usage_metadata"]["input_tokens"] = input_tokens
|
|
298
|
+
result["usage_metadata"]["output_tokens"] = output_tokens
|
|
299
|
+
result["usage_metadata"]["total_tokens"] = total_tokens
|
|
300
|
+
|
|
301
|
+
result["tools_used"] = tool_used_names
|
|
302
|
+
return result
|
|
303
|
+
|
|
304
|
+
def __convert_tool_to_dict(self, tools: List[StructuredTool]) -> List[Dict[str, str]]:
|
|
305
|
+
tools_info = []
|
|
306
|
+
|
|
307
|
+
for tool in tools:
|
|
308
|
+
tool_info = {
|
|
309
|
+
"name": tool.name,
|
|
310
|
+
"description": tool.description
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
tools_info.append(tool_info)
|
|
314
|
+
|
|
315
|
+
return tools_info
|
|
316
|
+
|
|
317
|
+
def __map_tools(self, tools: List[StructuredTool]) -> Dict[str, StructuredTool]:
|
|
318
|
+
tool_map = {}
|
|
319
|
+
|
|
320
|
+
for tool in tools:
|
|
321
|
+
tool_map[tool.name] = tool
|
|
322
|
+
|
|
323
|
+
return tool_map
|
|
324
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from rsroute.chat_models.gemini_client import ChatGemini
|
|
2
|
+
from rsroute.chat_models.mistral_client import ChatMistral, ChatMistralAI, MistralAI
|
|
3
|
+
from rsroute.chat_models.groq_client import ChatGroq
|
|
4
|
+
from rsroute.chat_models.openrouter_client import ChatOpenRouter, OpenRouter
|
|
5
|
+
from rsroute.chat_models.openai_client import ChatGPT, ChatOpenAI
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ChatGemini",
|
|
9
|
+
"ChatMistral",
|
|
10
|
+
"ChatMistralAI",
|
|
11
|
+
"MistralAI",
|
|
12
|
+
"ChatGroq",
|
|
13
|
+
"ChatOpenRouter",
|
|
14
|
+
"OpenRouter",
|
|
15
|
+
"ChatGPT",
|
|
16
|
+
"ChatOpenAI",
|
|
17
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from rsroute.base import BaseEstimator
|
|
4
|
+
from rsroute.urls import END_POINTS, URLS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChatGemini(BaseEstimator):
|
|
8
|
+
"""ChatGemini client for RSRoute."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
model: Optional[str] = None,
|
|
13
|
+
master_key: Optional[str] = None,
|
|
14
|
+
rsroute_url: Optional[str] = None,
|
|
15
|
+
temperature: Optional[float] = None,
|
|
16
|
+
max_tokens: Optional[int] = None,
|
|
17
|
+
system_prompt: Optional[Any] = None,
|
|
18
|
+
timeout: int = 30,
|
|
19
|
+
end_point: Optional[str] = None,
|
|
20
|
+
thinking_level: Optional[Any] = None,
|
|
21
|
+
thinking_budget: Optional[int] = None,
|
|
22
|
+
**kwargs: Any,
|
|
23
|
+
):
|
|
24
|
+
# Determine the target endpoint URL
|
|
25
|
+
if rsroute_url is None:
|
|
26
|
+
gemini_url = URLS.get("gemini")
|
|
27
|
+
else:
|
|
28
|
+
gemini_url = f"{rsroute_url.rstrip('/')}/{END_POINTS.get('gemini').lstrip('/')}"
|
|
29
|
+
|
|
30
|
+
super().__init__(
|
|
31
|
+
rsroute_url=gemini_url,
|
|
32
|
+
model=model,
|
|
33
|
+
master_key=master_key,
|
|
34
|
+
temperature=temperature,
|
|
35
|
+
max_tokens=max_tokens,
|
|
36
|
+
system_prompt=system_prompt,
|
|
37
|
+
timeout=timeout,
|
|
38
|
+
end_point=end_point,
|
|
39
|
+
thinking_level=thinking_level,
|
|
40
|
+
thinking_budget=thinking_budget,
|
|
41
|
+
**kwargs,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def invoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
45
|
+
"""Send a request to RSRoute Gemini chat endpoint with the given query."""
|
|
46
|
+
return super().invoke(query=query)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from rsroute.base import BaseEstimator
|
|
4
|
+
from rsroute.urls import END_POINTS, URLS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChatGroq(BaseEstimator):
|
|
8
|
+
"""ChatGroq client for RSRoute."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
model: Optional[str] = None,
|
|
13
|
+
master_key: Optional[str] = None,
|
|
14
|
+
rsroute_url: Optional[str] = None,
|
|
15
|
+
system_prompt: Optional[Any] = None,
|
|
16
|
+
temperature: Optional[float] = None,
|
|
17
|
+
max_tokens: Optional[int] = None,
|
|
18
|
+
timeout: int = 30,
|
|
19
|
+
end_point: Optional[str] = None,
|
|
20
|
+
**kwargs: Any,
|
|
21
|
+
):
|
|
22
|
+
# Determine the target endpoint URL
|
|
23
|
+
if rsroute_url is None:
|
|
24
|
+
groq_url = URLS.get("groq")
|
|
25
|
+
else:
|
|
26
|
+
groq_url = f"{rsroute_url.rstrip('/')}/{END_POINTS.get('groq').lstrip('/')}"
|
|
27
|
+
|
|
28
|
+
super().__init__(
|
|
29
|
+
rsroute_url=groq_url,
|
|
30
|
+
model=model,
|
|
31
|
+
master_key=master_key,
|
|
32
|
+
system_prompt=system_prompt,
|
|
33
|
+
temperature=temperature,
|
|
34
|
+
max_tokens=max_tokens,
|
|
35
|
+
timeout=timeout,
|
|
36
|
+
end_point=end_point,
|
|
37
|
+
**kwargs,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def invoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
41
|
+
"""Send a request to RSRoute Groq chat endpoint with the given query."""
|
|
42
|
+
return super().invoke(query=query)
|
|
43
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from rsroute.base import BaseEstimator
|
|
4
|
+
from rsroute.urls import END_POINTS, URLS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChatMistral(BaseEstimator):
|
|
8
|
+
"""ChatMistral client for RSRoute."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
model: Optional[str] = None,
|
|
13
|
+
master_key: Optional[str] = None,
|
|
14
|
+
rsroute_url: Optional[str] = None,
|
|
15
|
+
system_prompt: Optional[Any] = None,
|
|
16
|
+
temperature: Optional[float] = None,
|
|
17
|
+
max_tokens: Optional[int] = None,
|
|
18
|
+
timeout: int = 30,
|
|
19
|
+
end_point: Optional[str] = None,
|
|
20
|
+
**kwargs: Any,
|
|
21
|
+
):
|
|
22
|
+
# Determine the target endpoint URL
|
|
23
|
+
if rsroute_url is None:
|
|
24
|
+
mistral_url = URLS.get("mistral")
|
|
25
|
+
else:
|
|
26
|
+
mistral_url = f"{rsroute_url.rstrip('/')}/{END_POINTS.get('mistral').lstrip('/')}"
|
|
27
|
+
|
|
28
|
+
super().__init__(
|
|
29
|
+
rsroute_url=mistral_url,
|
|
30
|
+
model=model,
|
|
31
|
+
master_key=master_key,
|
|
32
|
+
system_prompt=system_prompt,
|
|
33
|
+
temperature=temperature,
|
|
34
|
+
max_tokens=max_tokens,
|
|
35
|
+
timeout=timeout,
|
|
36
|
+
end_point=end_point,
|
|
37
|
+
**kwargs,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def invoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
41
|
+
"""Send a request to RSRoute Mistral chat endpoint with the given query."""
|
|
42
|
+
return super().invoke(query=query)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
ChatMistralAI = ChatMistral
|
|
46
|
+
MistralAI = ChatMistral
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from rsroute.base import BaseEstimator, get_org_id
|
|
4
|
+
from rsroute.urls import END_POINTS, URLS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChatGPT(BaseEstimator):
|
|
8
|
+
"""ChatGPT (ChatOpenAI) client for RSRoute."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
model: Optional[str] = None,
|
|
13
|
+
master_key: Optional[str] = None,
|
|
14
|
+
rsroute_url: Optional[str] = None,
|
|
15
|
+
system_prompt: Optional[Any] = None,
|
|
16
|
+
temperature: Optional[float] = None,
|
|
17
|
+
max_tokens: Optional[int] = None,
|
|
18
|
+
timeout: int = 60,
|
|
19
|
+
end_point: Optional[str] = None,
|
|
20
|
+
max_retries: Optional[int] = None,
|
|
21
|
+
use_response_api: Optional[bool] = None,
|
|
22
|
+
organization_id: Optional[str] = None,
|
|
23
|
+
**kwargs: Any,
|
|
24
|
+
):
|
|
25
|
+
# Determine the target endpoint URL
|
|
26
|
+
if rsroute_url is None:
|
|
27
|
+
openai_url = URLS.get("openai")
|
|
28
|
+
else:
|
|
29
|
+
openai_url = f"{rsroute_url.rstrip('/')}/{END_POINTS.get('openai').lstrip('/')}"
|
|
30
|
+
|
|
31
|
+
# Resolve organization_id: explicit arg > org_id alias > .env lookup
|
|
32
|
+
resolved_org_id = organization_id or get_org_id()
|
|
33
|
+
|
|
34
|
+
super().__init__(
|
|
35
|
+
rsroute_url=openai_url,
|
|
36
|
+
model=model,
|
|
37
|
+
master_key=master_key,
|
|
38
|
+
system_prompt=system_prompt,
|
|
39
|
+
temperature=temperature,
|
|
40
|
+
max_tokens=max_tokens,
|
|
41
|
+
timeout=timeout,
|
|
42
|
+
end_point=end_point,
|
|
43
|
+
max_retries=max_retries,
|
|
44
|
+
use_response_api=use_response_api,
|
|
45
|
+
organization_id=resolved_org_id,
|
|
46
|
+
**kwargs,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def invoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
50
|
+
"""Send a request to RSRoute OpenAI chat endpoint with the given query."""
|
|
51
|
+
return super().invoke(query=query)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Alias
|
|
55
|
+
ChatOpenAI = ChatGPT
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from rsroute.base import BaseEstimator
|
|
4
|
+
from rsroute.urls import END_POINTS, URLS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChatOpenRouter(BaseEstimator):
|
|
8
|
+
"""ChatOpenRouter client for RSRoute."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
model: Optional[str] = None,
|
|
13
|
+
master_key: Optional[str] = None,
|
|
14
|
+
rsroute_url: Optional[str] = None,
|
|
15
|
+
system_prompt: Optional[Any] = None,
|
|
16
|
+
temperature: Optional[float] = None,
|
|
17
|
+
max_tokens: Optional[int] = None,
|
|
18
|
+
timeout: int = 30,
|
|
19
|
+
end_point: Optional[str] = None,
|
|
20
|
+
app_title: Optional[str] = None,
|
|
21
|
+
app_url: Optional[str] = None,
|
|
22
|
+
app_categories: Optional[List[str]] = None,
|
|
23
|
+
max_retries: Optional[int] = None,
|
|
24
|
+
providers: Optional[Dict[str, Any]] = None,
|
|
25
|
+
**kwargs: Any,
|
|
26
|
+
):
|
|
27
|
+
# Determine the target endpoint URL
|
|
28
|
+
if rsroute_url is None:
|
|
29
|
+
openrouter_url = URLS.get("openrouter")
|
|
30
|
+
else:
|
|
31
|
+
openrouter_url = f"{rsroute_url.rstrip('/')}/{END_POINTS.get('openrouter').lstrip('/')}"
|
|
32
|
+
|
|
33
|
+
super().__init__(
|
|
34
|
+
rsroute_url=openrouter_url,
|
|
35
|
+
model=model,
|
|
36
|
+
master_key=master_key,
|
|
37
|
+
system_prompt=system_prompt,
|
|
38
|
+
temperature=temperature,
|
|
39
|
+
max_tokens=max_tokens,
|
|
40
|
+
timeout=timeout,
|
|
41
|
+
end_point=end_point,
|
|
42
|
+
app_title=app_title,
|
|
43
|
+
app_url=app_url,
|
|
44
|
+
app_categories=app_categories,
|
|
45
|
+
max_retries=max_retries,
|
|
46
|
+
providers=providers,
|
|
47
|
+
**kwargs,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def invoke(self, query: Optional[Any] = None) -> Dict[Any, Any]:
|
|
51
|
+
"""Send a request to RSRoute OpenRouter chat endpoint with the given query."""
|
|
52
|
+
return super().invoke(query=query)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
OpenRouter = ChatOpenRouter
|
|
56
|
+
|
rsroute/urls.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
ROOT_URL = "http://0.0.0.0:8000/"
|
|
2
|
+
GEMINI_URL = "http://0.0.0.0:8000/v1/gemini/chat"
|
|
3
|
+
MISTRAL_URL = "http://0.0.0.0:8000/v1/mistral/chat"
|
|
4
|
+
GROQ_URL = "http://0.0.0.0:8000/v1/groq/chat"
|
|
5
|
+
OPENROUTER_URL = "http://0.0.0.0:8000/v1/openrouter/chat"
|
|
6
|
+
OPENAI_URL = "http://0.0.0.0:8000/v1/openai/chat"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Agentic Routes
|
|
10
|
+
GEMINI_AGENT = "http://0.0.0.0:8000/v1/gemini/agent"
|
|
11
|
+
MISTRAL_AGENT = "http://0.0.0.0:8000/v1/mistral/agent"
|
|
12
|
+
GROQ_AGENT = "http://0.0.0.0:8000/v1/groq/agent"
|
|
13
|
+
OPENROUTER_AGENT = "http://0.0.0.0:8000/v1/openrouter/agent"
|
|
14
|
+
OPENAI_AGENT = "http://0.0.0.0:8000/v1/openai/agent"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
URLS = {
|
|
18
|
+
"root": ROOT_URL,
|
|
19
|
+
"gemini": GEMINI_URL,
|
|
20
|
+
"mistral": MISTRAL_URL,
|
|
21
|
+
"groq": GROQ_URL,
|
|
22
|
+
"openrouter": OPENROUTER_URL,
|
|
23
|
+
"gemini_agent": GEMINI_AGENT,
|
|
24
|
+
"mistral_agent": MISTRAL_AGENT,
|
|
25
|
+
"groq_agent": GROQ_AGENT,
|
|
26
|
+
"openrouter_agent": OPENROUTER_AGENT,
|
|
27
|
+
"openai": OPENAI_URL,
|
|
28
|
+
"openai_agent": OPENAI_AGENT
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
END_POINTS = {
|
|
33
|
+
"root": "/",
|
|
34
|
+
"gemini": "/v1/gemini/chat",
|
|
35
|
+
"mistral": "/v1/mistral/chat",
|
|
36
|
+
"groq": "/v1/groq/chat",
|
|
37
|
+
"openrouter": "/v1/openrouter/chat",
|
|
38
|
+
"gemini_agent": "/v1/gemini/agent",
|
|
39
|
+
"mistral_agent": "/v1/mistral/agent",
|
|
40
|
+
"groq_agent": "/v1/groq/agent",
|
|
41
|
+
"openrouter_agent": "/v1/openrouter/agent",
|
|
42
|
+
"openai": "/v1/openai/chat",
|
|
43
|
+
"openai_agent": "/v1/openai/agent"
|
|
44
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: rsroute
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python Package Wrapper for RSRoute server
|
|
5
|
+
Author: ItzRustam
|
|
6
|
+
Author-email: ItzRustam <rustambhadouriya7@gmail.com>
|
|
7
|
+
Requires-Dist: requests
|
|
8
|
+
Requires-Dist: langchain-core
|
|
9
|
+
Requires-Dist: rich
|
|
10
|
+
Requires-Dist: python-dotenv
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# rsroute
|
|
15
|
+
|
|
16
|
+
### What it is
|
|
17
|
+
`rsroute` is a lightweight Python client for [RSRoute](https://www.github.com/ItzRustam/rsroute) — a unified gateway that routes requests across 10+ LLM providers seamlessly.
|
|
18
|
+
|
|
19
|
+
### Why it is
|
|
20
|
+
Instead of managing disparate SDKs, raw HTTP requests, query parameters, and custom endpoints manually for each provider, `rsroute` provides a simple, unified wrapper so you can connect to your RSRoute server with minimal code.
|
|
21
|
+
|
|
22
|
+
### Code Example - ChatModel
|
|
23
|
+
```python
|
|
24
|
+
from rsroute.chat_models import ChatGemini, ChatMistral, ChatGroq, ChatOpenRouter
|
|
25
|
+
|
|
26
|
+
# Gemini
|
|
27
|
+
gemini_model = ChatGemini(
|
|
28
|
+
model="gemini-3.5-flash-lite",
|
|
29
|
+
rsroute_url="http://0.0.0.0:8000", # your URL always default to this.
|
|
30
|
+
)
|
|
31
|
+
response = gemini_model.invoke(query="What is GitHub?")
|
|
32
|
+
print(response)
|
|
33
|
+
|
|
34
|
+
# Groq
|
|
35
|
+
groq_model = ChatGroq(
|
|
36
|
+
model="openai/gpt-oss-120b",
|
|
37
|
+
temperature=0.7,
|
|
38
|
+
)
|
|
39
|
+
response = groq_model.invoke(query="Hello Groq!")
|
|
40
|
+
print(response)
|
|
41
|
+
|
|
42
|
+
# OpenRouter
|
|
43
|
+
openrouter_model = ChatOpenRouter(
|
|
44
|
+
model="deepseek/deepseek-v4.1-flash",
|
|
45
|
+
temperature=0.7,
|
|
46
|
+
)
|
|
47
|
+
response = openrouter_model.invoke(query="Hello OpenRouter!")
|
|
48
|
+
print(response)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Code Example - Agents
|
|
52
|
+
```python
|
|
53
|
+
from rsroute.agents import GeminiAgent, MistralAgent, GroqAgent, OpenRouterAgent
|
|
54
|
+
|
|
55
|
+
# Gemini Agent
|
|
56
|
+
gemini_agent = GeminiAgent(
|
|
57
|
+
tools=[web_search], # your tools
|
|
58
|
+
max_tool_calls=12 # Default to 10
|
|
59
|
+
)
|
|
60
|
+
response = gemini_agent.invoke(query="Latest News of GPT-6 Astra")
|
|
61
|
+
print(response)
|
|
62
|
+
|
|
63
|
+
# Mistral Agent
|
|
64
|
+
mistral_agent = MistralAgent(
|
|
65
|
+
tools=[web_search],
|
|
66
|
+
model="mistral-small-2603"
|
|
67
|
+
)
|
|
68
|
+
response = mistral_agent.invoke(query="What is the weather in Paris?")
|
|
69
|
+
print(response)
|
|
70
|
+
|
|
71
|
+
# Groq Agent
|
|
72
|
+
groq_agent = GroqAgent(
|
|
73
|
+
tools=[web_search],
|
|
74
|
+
model="openai/gpt-oss-120b"
|
|
75
|
+
)
|
|
76
|
+
response = groq_agent.invoke(query="Compute 42 * 42")
|
|
77
|
+
print(response)
|
|
78
|
+
|
|
79
|
+
# OpenRouter Agent
|
|
80
|
+
openrouter_agent = OpenRouterAgent(
|
|
81
|
+
tools=[web_search],
|
|
82
|
+
model="deepseek/deepseek-v4.1-flash"
|
|
83
|
+
)
|
|
84
|
+
response = openrouter_agent.invoke(query="Search latest news")
|
|
85
|
+
print(response)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Feature
|
|
89
|
+
* automatic `master_key` loading from .env
|
|
90
|
+
* proper request handling
|
|
91
|
+
* Agentic power
|
|
92
|
+
|
|
93
|
+
### Valid `.env` name
|
|
94
|
+
Any of the following variable names can be used in your `.env` file:
|
|
95
|
+
* `rsroute_token` (or `RSROUTE_TOKEN`)
|
|
96
|
+
* `master_key` (or `MASTER_KEY`)
|
|
97
|
+
* `rsroute_key` (or `RSROUTE_KEY`)
|
|
98
|
+
* `rsroute_master_key` (or `RSROUTE_MASTER_KEY`)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
### Server
|
|
102
|
+
RSRoute Server repository: [RSRoute GitHub](https://www.github.com/ItzRustam/rsroute)
|
|
103
|
+
|
|
104
|
+
RSRoute website: [RSRoute Web](https://www.rsroute.freedev.app)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
rsroute/__init__.py,sha256=uPM6GKh79-XtBvQaPJu4rk7oj0QMf6ZKurDuJue9GHw,850
|
|
2
|
+
rsroute/agents/__init__.py,sha256=Gs1uPqjbkQQHIx99L3G8wSO6UYA4-2Nu-yIOJoGyI6U,417
|
|
3
|
+
rsroute/agents/gemini_agent.py,sha256=ymgEgiDcnLq3rbrADXCc_3PbQWUqefS6JcIjl-qLR2o,2625
|
|
4
|
+
rsroute/agents/groq_agent.py,sha256=ZBzmVpaDig8YT7FuY4SBppsm8wP-3DJ211E_XyuIPgQ,1748
|
|
5
|
+
rsroute/agents/mistral_agent.py,sha256=XNL-W8NqZAeMIm0D8g_4CWr9pVUeZTNTpnWZGDAoaLo,1774
|
|
6
|
+
rsroute/agents/openai_agent.py,sha256=u_pOc3k38nCdLEJxDl7vBAwX3pVCi_TKoyGvBr6rjgM,2992
|
|
7
|
+
rsroute/agents/openrouter_agent.py,sha256=7ebWKWNruvvbMUn9QNlkTdQqF5VgO353QH7_-xAo8IM,2988
|
|
8
|
+
rsroute/base.py,sha256=vpoxy0zF7m81gzjWWMEFWYxyOrqalW4NPbj3yjuA7Bk,11365
|
|
9
|
+
rsroute/chat_models/__init__.py,sha256=aEyFNykW73fyBC8VN1a1RYSnhAscps-yxN0ajjVY3YQ,517
|
|
10
|
+
rsroute/chat_models/gemini_client.py,sha256=IeYCcGnFMIA1QMhUS3SrxUBImCDQA_g7FjiffFjaiHE,1516
|
|
11
|
+
rsroute/chat_models/groq_client.py,sha256=_x8snenxthJgUCURki5NjP_S-9GluhcflyKRwbaGzMo,1320
|
|
12
|
+
rsroute/chat_models/mistral_client.py,sha256=tTF15FBReGVAGTYTQ1lIZEmpdJfFVvgqR4pWLC1C3go,1397
|
|
13
|
+
rsroute/chat_models/openai_client.py,sha256=yiNoD5n0pKFEZXoTM6H89EIF5ItkabNu6rkIrwNjH_4,1789
|
|
14
|
+
rsroute/chat_models/openrouter_client.py,sha256=hrDMe9HZogxpXIYLlIDZVIRMPUOnkrFXAnS2P95SnQA,1806
|
|
15
|
+
rsroute/urls.py,sha256=xlxO1DeXFWJILCvPkaiYTyzOS8ts32G_qk1gIwjTNgg,1338
|
|
16
|
+
rsroute-0.1.0.dist-info/WHEEL,sha256=3iQx15CyXwGi4dbygedzA4M5T1eqO_SjwdPfUmqt2Cw,81
|
|
17
|
+
rsroute-0.1.0.dist-info/METADATA,sha256=jfm7DI3HP9kgLCjIB_yXSJcqxF-j68Svfr7raaWwWLI,2873
|
|
18
|
+
rsroute-0.1.0.dist-info/RECORD,,
|