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 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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.24
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any