indoxrouter 0.1.0__py3-none-any.whl → 0.1.3__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.
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.2
2
+ Name: indoxrouter
3
+ Version: 0.1.3
4
+ Summary: A unified client for various AI providers
5
+ Home-page: https://github.com/indoxrouter/indoxrouter
6
+ Author: indoxRouter Team
7
+ Author-email: ashkan.eskandari.dev@gmail.com
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: requests>=2.25.0
19
+ Requires-Dist: python-dotenv>=1.0.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
22
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
23
+ Requires-Dist: black>=23.0.0; extra == "dev"
24
+ Requires-Dist: isort>=5.0.0; extra == "dev"
25
+ Requires-Dist: flake8>=6.0.0; extra == "dev"
26
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: description-content-type
32
+ Dynamic: home-page
33
+ Dynamic: provides-extra
34
+ Dynamic: requires-dist
35
+ Dynamic: requires-python
36
+ Dynamic: summary
37
+
38
+ # IndoxRouter Client
39
+
40
+ A unified client for various AI providers, including OpenAI, Anthropic, Cohere, Google, and Mistral.
41
+
42
+ ## Features
43
+
44
+ - **Unified API**: Access multiple AI providers through a single API
45
+ - **Simple Interface**: Easy-to-use methods for chat, completion, embeddings, and image generation
46
+ - **Error Handling**: Standardized error handling across providers
47
+ - **Authentication**: Automatic token management
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install indoxrouter
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ ### Initialization
58
+
59
+ ```python
60
+ from indoxrouter import Client
61
+
62
+ # Initialize with API key
63
+ client = Client(api_key="your_api_key", base_url="http://your-server-url:8000")
64
+
65
+ # Or initialize with username and password
66
+ client = Client(
67
+ username="your_username",
68
+ password="your_password",
69
+ base_url="http://your-server-url:8000"
70
+ )
71
+
72
+ # Using environment variables
73
+ # Set INDOXROUTER_API_KEY or INDOXROUTER_USERNAME and INDOXROUTER_PASSWORD
74
+ client = Client(base_url="http://your-server-url:8000")
75
+ ```
76
+
77
+ ### Chat Completions
78
+
79
+ ```python
80
+ response = client.chat(
81
+ messages=[
82
+ {"role": "system", "content": "You are a helpful assistant."},
83
+ {"role": "user", "content": "Tell me a joke."}
84
+ ],
85
+ provider="openai",
86
+ model="gpt-3.5-turbo",
87
+ temperature=0.7
88
+ )
89
+
90
+ print(response["choices"][0]["message"]["content"])
91
+ ```
92
+
93
+ ### Text Completions
94
+
95
+ ```python
96
+ response = client.completion(
97
+ prompt="Once upon a time,",
98
+ provider="openai",
99
+ model="gpt-3.5-turbo-instruct",
100
+ max_tokens=100
101
+ )
102
+
103
+ print(response["choices"][0]["text"])
104
+ ```
105
+
106
+ ### Embeddings
107
+
108
+ ```python
109
+ response = client.embeddings(
110
+ text=["Hello world", "AI is amazing"],
111
+ provider="openai",
112
+ model="text-embedding-ada-002"
113
+ )
114
+
115
+ print(f"Dimensions: {response['dimensions']}")
116
+ print(f"First embedding: {response['embeddings'][0][:5]}...")
117
+ ```
118
+
119
+ ### Image Generation
120
+
121
+ ```python
122
+ response = client.images(
123
+ prompt="A serene landscape with mountains and a lake",
124
+ provider="openai",
125
+ model="dall-e-3",
126
+ size="1024x1024"
127
+ )
128
+
129
+ print(f"Image URL: {response['images'][0]['url']}")
130
+ ```
131
+
132
+ ### Streaming Responses
133
+
134
+ ```python
135
+ for chunk in client.chat(
136
+ messages=[{"role": "user", "content": "Write a short story."}],
137
+ stream=True
138
+ ):
139
+ if "choices" in chunk and len(chunk["choices"]) > 0:
140
+ content = chunk["choices"][0].get("delta", {}).get("content", "")
141
+ print(content, end="", flush=True)
142
+ ```
143
+
144
+ ### Getting Available Models
145
+
146
+ ```python
147
+ # Get all providers and models
148
+ providers = client.models()
149
+ for provider in providers:
150
+ print(f"Provider: {provider['name']}")
151
+ for model in provider["models"]:
152
+ print(f" - {model['id']}: {model['name']}")
153
+
154
+ # Get models for a specific provider
155
+ openai_provider = client.models("openai")
156
+ print(f"OpenAI models: {[m['id'] for m in openai_provider['models']]}")
157
+ ```
158
+
159
+ ## Error Handling
160
+
161
+ ```python
162
+ from indoxrouter import Client, ModelNotFoundError, ProviderError
163
+
164
+ try:
165
+ client = Client(api_key="your_api_key", base_url="http://your-server-url:8000")
166
+ response = client.chat(
167
+ messages=[{"role": "user", "content": "Hello"}],
168
+ provider="nonexistent",
169
+ model="nonexistent-model"
170
+ )
171
+ except ModelNotFoundError as e:
172
+ print(f"Model not found: {e}")
173
+ except ProviderError as e:
174
+ print(f"Provider error: {e}")
175
+ ```
176
+
177
+ ## Context Manager
178
+
179
+ ```python
180
+ with Client(api_key="your_api_key", base_url="http://your-server-url:8000") as client:
181
+ response = client.chat([{"role": "user", "content": "Hello!"}])
182
+ print(response["choices"][0]["message"]["content"])
183
+ # Client is automatically closed when exiting the block
184
+ ```
185
+
186
+ ## License
187
+
188
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,4 @@
1
+ indoxrouter-0.1.3.dist-info/METADATA,sha256=7efp8b9jiVIK0-EnavBfiufhCNy-MdmW2tPvPRm7Isk,5122
2
+ indoxrouter-0.1.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
3
+ indoxrouter-0.1.3.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
4
+ indoxrouter-0.1.3.dist-info/RECORD,,
@@ -0,0 +1 @@
1
+
indoxRouter/__init__.py DELETED
File without changes
@@ -1,336 +0,0 @@
1
- """
2
- API endpoints for the IndoxRouter client package.
3
- """
4
-
5
- import logging
6
- from typing import Dict, Any, Optional, List
7
- from datetime import datetime
8
-
9
- from fastapi import APIRouter, Depends, HTTPException, Request
10
- from pydantic import BaseModel, Field
11
-
12
- from .utils.database import execute_query
13
- from .utils.auth import get_current_user
14
- from .models.database import User, RequestLog, Credit
15
- from .utils.config import get_config
16
- from .providers import get_provider
17
-
18
- # Configure logging
19
- logger = logging.getLogger(__name__)
20
-
21
- # Create API router
22
- router = APIRouter(prefix="/api/v1", tags=["Client API"])
23
-
24
-
25
- # Model definitions
26
- class GenerateRequest(BaseModel):
27
- """Request model for text generation."""
28
-
29
- prompt: str = Field(..., description="The prompt to send to the model")
30
- model: Optional[str] = Field(None, description="The model to use")
31
- provider: Optional[str] = Field(None, description="The provider to use")
32
- temperature: Optional[float] = Field(0.7, description="Temperature for sampling")
33
- max_tokens: Optional[int] = Field(
34
- 1000, description="Maximum number of tokens to generate"
35
- )
36
- top_p: Optional[float] = Field(1.0, description="Top-p sampling parameter")
37
- stop: Optional[List[str]] = Field(
38
- None, description="Sequences where the API will stop generating"
39
- )
40
- stream: Optional[bool] = Field(False, description="Whether to stream the response")
41
-
42
-
43
- class GenerateResponse(BaseModel):
44
- """Response model for text generation."""
45
-
46
- id: str = Field(..., description="Unique identifier for the completion")
47
- text: str = Field(..., description="The generated text")
48
- provider: str = Field(..., description="The provider used")
49
- model: str = Field(..., description="The model used")
50
- usage: Dict[str, int] = Field(..., description="Token usage information")
51
- created_at: datetime = Field(..., description="Timestamp of creation")
52
-
53
-
54
- class ModelInfo(BaseModel):
55
- """Model for LLM model information."""
56
-
57
- id: str = Field(..., description="Model identifier")
58
- name: str = Field(..., description="Model name")
59
- provider: str = Field(..., description="Provider name")
60
- description: Optional[str] = Field(None, description="Model description")
61
- max_tokens: Optional[int] = Field(None, description="Maximum tokens supported")
62
- pricing: Optional[Dict[str, float]] = Field(None, description="Pricing information")
63
-
64
-
65
- class ProviderInfo(BaseModel):
66
- """Model for LLM provider information."""
67
-
68
- id: str = Field(..., description="Provider identifier")
69
- name: str = Field(..., description="Provider name")
70
- description: Optional[str] = Field(None, description="Provider description")
71
- website: Optional[str] = Field(None, description="Provider website")
72
- models: List[str] = Field(..., description="Available models")
73
-
74
-
75
- class UserInfo(BaseModel):
76
- """Model for user information."""
77
-
78
- id: int = Field(..., description="User ID")
79
- email: str = Field(..., description="User email")
80
- first_name: Optional[str] = Field(None, description="User first name")
81
- last_name: Optional[str] = Field(None, description="User last name")
82
- is_admin: bool = Field(False, description="Whether the user is an admin")
83
- created_at: datetime = Field(..., description="Account creation timestamp")
84
-
85
-
86
- class BalanceInfo(BaseModel):
87
- """Model for user balance information."""
88
-
89
- credits: float = Field(..., description="Available credits")
90
- usage: Dict[str, float] = Field(..., description="Usage statistics")
91
- last_updated: datetime = Field(..., description="Last updated timestamp")
92
-
93
-
94
- # API endpoints
95
- @router.post("/generate", response_model=GenerateResponse)
96
- async def generate(
97
- request: GenerateRequest,
98
- user_data: Dict[str, Any] = Depends(get_current_user),
99
- req: Request = None,
100
- ):
101
- """Generate text using the specified model and provider."""
102
- try:
103
- # Set default model and provider if not specified
104
- provider_name = request.provider or get_config().get(
105
- "default_provider", "openai"
106
- )
107
- model_name = request.model or get_config().get("default_model", "gpt-4o-mini")
108
-
109
- # Get the provider
110
- provider = get_provider(provider_name)
111
- if not provider:
112
- raise HTTPException(
113
- status_code=400, detail=f"Provider '{provider_name}' not found"
114
- )
115
-
116
- # Generate the completion
117
- completion = await provider.generate(
118
- model=model_name,
119
- prompt=request.prompt,
120
- max_tokens=request.max_tokens,
121
- temperature=request.temperature,
122
- top_p=request.top_p,
123
- stop=request.stop,
124
- stream=request.stream,
125
- )
126
-
127
- # Log the request
128
- log_entry = RequestLog(
129
- user_id=user_data["user_id"],
130
- provider=provider_name,
131
- model=model_name,
132
- prompt_tokens=completion.get("usage", {}).get("prompt_tokens", 0),
133
- completion_tokens=completion.get("usage", {}).get("completion_tokens", 0),
134
- total_tokens=completion.get("usage", {}).get("total_tokens", 0),
135
- created_at=datetime.utcnow(),
136
- )
137
- execute_query(lambda session: session.add(log_entry))
138
-
139
- # Return the response
140
- return {
141
- "id": completion.get("id", ""),
142
- "text": completion.get("text", ""),
143
- "provider": provider_name,
144
- "model": model_name,
145
- "usage": completion.get("usage", {}),
146
- "created_at": datetime.utcnow(),
147
- }
148
- except Exception as e:
149
- logger.error(f"Error generating completion: {str(e)}")
150
- raise HTTPException(status_code=500, detail=str(e))
151
-
152
-
153
- @router.get("/models", response_model=List[ModelInfo])
154
- async def list_models(
155
- provider: Optional[str] = None,
156
- user_data: Dict[str, Any] = Depends(get_current_user),
157
- ):
158
- """List available models, optionally filtered by provider."""
159
- try:
160
- # Get configuration
161
- config = get_config()
162
- models = []
163
-
164
- # If provider is specified, only get models for that provider
165
- if provider:
166
- provider_obj = get_provider(provider)
167
- if not provider_obj:
168
- raise HTTPException(
169
- status_code=400, detail=f"Provider '{provider}' not found"
170
- )
171
- provider_models = provider_obj.list_models()
172
- for model in provider_models:
173
- models.append(
174
- {
175
- "id": model.get("id", ""),
176
- "name": model.get("name", ""),
177
- "provider": provider,
178
- "description": model.get("description", ""),
179
- "max_tokens": model.get("max_tokens", 0),
180
- "pricing": model.get("pricing", {}),
181
- }
182
- )
183
- else:
184
- # Get models for all providers
185
- for provider_name in config.get("providers", {}).keys():
186
- try:
187
- provider_obj = get_provider(provider_name)
188
- if provider_obj:
189
- provider_models = provider_obj.list_models()
190
- for model in provider_models:
191
- models.append(
192
- {
193
- "id": model.get("id", ""),
194
- "name": model.get("name", ""),
195
- "provider": provider_name,
196
- "description": model.get("description", ""),
197
- "max_tokens": model.get("max_tokens", 0),
198
- "pricing": model.get("pricing", {}),
199
- }
200
- )
201
- except Exception as e:
202
- logger.error(
203
- f"Error getting models for provider {provider_name}: {str(e)}"
204
- )
205
-
206
- return models
207
- except Exception as e:
208
- logger.error(f"Error listing models: {str(e)}")
209
- raise HTTPException(status_code=500, detail=str(e))
210
-
211
-
212
- @router.get("/providers", response_model=List[ProviderInfo])
213
- async def list_providers(
214
- user_data: Dict[str, Any] = Depends(get_current_user),
215
- ):
216
- """List available providers."""
217
- try:
218
- # Get configuration
219
- config = get_config()
220
- providers = []
221
-
222
- # Get all providers
223
- for provider_name, provider_config in config.get("providers", {}).items():
224
- try:
225
- provider_obj = get_provider(provider_name)
226
- if provider_obj:
227
- provider_models = provider_obj.list_models()
228
- providers.append(
229
- {
230
- "id": provider_name,
231
- "name": provider_config.get("name", provider_name),
232
- "description": provider_config.get("description", ""),
233
- "website": provider_config.get("website", ""),
234
- "models": [
235
- model.get("id", "") for model in provider_models
236
- ],
237
- }
238
- )
239
- except Exception as e:
240
- logger.error(f"Error getting provider {provider_name}: {str(e)}")
241
-
242
- return providers
243
- except Exception as e:
244
- logger.error(f"Error listing providers: {str(e)}")
245
- raise HTTPException(status_code=500, detail=str(e))
246
-
247
-
248
- @router.get("/user", response_model=UserInfo)
249
- async def get_user(
250
- user_data: Dict[str, Any] = Depends(get_current_user),
251
- ):
252
- """Get information about the authenticated user."""
253
- try:
254
- # Get user from database
255
- user = None
256
-
257
- def get_user_from_db(session):
258
- nonlocal user
259
- user = session.query(User).filter(User.id == user_data["user_id"]).first()
260
- return user
261
-
262
- execute_query(get_user_from_db)
263
-
264
- if not user:
265
- raise HTTPException(status_code=404, detail="User not found")
266
-
267
- return {
268
- "id": user.id,
269
- "email": user.email,
270
- "first_name": user.first_name,
271
- "last_name": user.last_name,
272
- "is_admin": user.is_admin,
273
- "created_at": user.created_at,
274
- }
275
- except Exception as e:
276
- logger.error(f"Error getting user: {str(e)}")
277
- raise HTTPException(status_code=500, detail=str(e))
278
-
279
-
280
- @router.get("/user/balance", response_model=BalanceInfo)
281
- async def get_balance(
282
- user_data: Dict[str, Any] = Depends(get_current_user),
283
- ):
284
- """Get the user's current balance."""
285
- try:
286
- # Get user's credit from database
287
- credit = None
288
- usage_data = {}
289
-
290
- def get_credit_from_db(session):
291
- nonlocal credit
292
- credit = (
293
- session.query(Credit)
294
- .filter(Credit.user_id == user_data["user_id"])
295
- .first()
296
- )
297
- return credit
298
-
299
- def get_usage_from_db(session):
300
- nonlocal usage_data
301
- # Get total usage by provider
302
- usage_by_provider = {}
303
- logs = (
304
- session.query(RequestLog)
305
- .filter(RequestLog.user_id == user_data["user_id"])
306
- .all()
307
- )
308
- for log in logs:
309
- provider = log.provider
310
- if provider not in usage_by_provider:
311
- usage_by_provider[provider] = 0
312
- usage_by_provider[provider] += log.total_tokens
313
-
314
- usage_data = usage_by_provider
315
- return usage_by_provider
316
-
317
- execute_query(get_credit_from_db)
318
- execute_query(get_usage_from_db)
319
-
320
- if not credit:
321
- # Create a new credit entry with default values
322
- credit = Credit(
323
- user_id=user_data["user_id"],
324
- amount=0.0,
325
- last_updated=datetime.utcnow(),
326
- )
327
- execute_query(lambda session: session.add(credit))
328
-
329
- return {
330
- "credits": credit.amount,
331
- "usage": usage_data,
332
- "last_updated": credit.last_updated,
333
- }
334
- except Exception as e:
335
- logger.error(f"Error getting balance: {str(e)}")
336
- raise HTTPException(status_code=500, detail=str(e))