indoxrouter 0.1.26__py3-none-any.whl → 0.1.28__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.
- indoxrouter/__init__.py +1 -1
- indoxrouter/client.py +311 -20
- indoxrouter/constants.py +4 -1
- {indoxrouter-0.1.26.dist-info → indoxrouter-0.1.28.dist-info}/METADATA +30 -1
- indoxrouter-0.1.28.dist-info/RECORD +9 -0
- indoxrouter-0.1.26.dist-info/RECORD +0 -9
- {indoxrouter-0.1.26.dist-info → indoxrouter-0.1.28.dist-info}/WHEEL +0 -0
- {indoxrouter-0.1.26.dist-info → indoxrouter-0.1.28.dist-info}/licenses/LICENSE +0 -0
- {indoxrouter-0.1.26.dist-info → indoxrouter-0.1.28.dist-info}/top_level.txt +0 -0
indoxrouter/__init__.py
CHANGED
indoxrouter/client.py
CHANGED
@@ -14,6 +14,7 @@ The Client class offers methods for:
|
|
14
14
|
- Accessing AI capabilities: chat completions, text completions, embeddings, image generation, and text-to-speech
|
15
15
|
- Retrieving information about available providers and models
|
16
16
|
- Monitoring usage statistics and credit consumption
|
17
|
+
- BYOK (Bring Your Own Key) support for using your own provider API keys
|
17
18
|
|
18
19
|
Usage example:
|
19
20
|
```python
|
@@ -37,6 +38,14 @@ Usage example:
|
|
37
38
|
# Generate text-to-speech audio
|
38
39
|
audio = client.text_to_speech("Hello, welcome to IndoxRouter!", model="openai/tts-1", voice="alloy")
|
39
40
|
|
41
|
+
# Transcribe audio to text using speech-to-text
|
42
|
+
transcription = client.speech_to_text("path/to/audio.mp3", model="openai/whisper-1")
|
43
|
+
|
44
|
+
# Using BYOK (Bring Your Own Key)
|
45
|
+
response = client.chat([
|
46
|
+
{"role": "user", "content": "Hello!"}
|
47
|
+
], model="openai/gpt-4", byok_api_key="sk-your-openai-key-here")
|
48
|
+
|
40
49
|
# Clean up resources when done
|
41
50
|
client.close()
|
42
51
|
```
|
@@ -46,6 +55,21 @@ The client can also be used as a context manager:
|
|
46
55
|
with Client(api_key="your_api_key") as client:
|
47
56
|
response = client.chat([{"role": "user", "content": "Hello!"}], model="openai/gpt-4o-mini")
|
48
57
|
```
|
58
|
+
|
59
|
+
BYOK (Bring Your Own Key) Support:
|
60
|
+
The client supports BYOK, allowing you to use your own API keys for AI providers:
|
61
|
+
|
62
|
+
- No credit deduction from your IndoxRouter account
|
63
|
+
- No rate limiting from the platform
|
64
|
+
- Direct provider access with your own API keys
|
65
|
+
- Cost control - you pay providers directly at their rates
|
66
|
+
|
67
|
+
Example:
|
68
|
+
response = client.chat(
|
69
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
70
|
+
model="openai/gpt-4",
|
71
|
+
byok_api_key="sk-your-openai-key-here"
|
72
|
+
)
|
49
73
|
"""
|
50
74
|
|
51
75
|
import os
|
@@ -76,11 +100,14 @@ from .constants import (
|
|
76
100
|
DEFAULT_EMBEDDING_MODEL,
|
77
101
|
DEFAULT_IMAGE_MODEL,
|
78
102
|
DEFAULT_TTS_MODEL,
|
103
|
+
DEFAULT_STT_MODEL,
|
79
104
|
CHAT_ENDPOINT,
|
80
105
|
COMPLETION_ENDPOINT,
|
81
106
|
EMBEDDING_ENDPOINT,
|
82
107
|
IMAGE_ENDPOINT,
|
83
108
|
TTS_ENDPOINT,
|
109
|
+
STT_ENDPOINT,
|
110
|
+
STT_TRANSLATION_ENDPOINT,
|
84
111
|
MODEL_ENDPOINT,
|
85
112
|
USAGE_ENDPOINT,
|
86
113
|
USE_COOKIES,
|
@@ -236,6 +263,7 @@ class Client:
|
|
236
263
|
endpoint: str,
|
237
264
|
data: Optional[Dict[str, Any]] = None,
|
238
265
|
stream: bool = False,
|
266
|
+
files: Optional[Dict[str, Any]] = None,
|
239
267
|
) -> Any:
|
240
268
|
"""
|
241
269
|
Make a request to the API.
|
@@ -245,6 +273,7 @@ class Client:
|
|
245
273
|
endpoint: API endpoint
|
246
274
|
data: Request data
|
247
275
|
stream: Whether to stream the response
|
276
|
+
files: Files to upload (for multipart/form-data requests)
|
248
277
|
|
249
278
|
Returns:
|
250
279
|
Response data
|
@@ -258,7 +287,14 @@ class Client:
|
|
258
287
|
endpoint = endpoint[1:]
|
259
288
|
|
260
289
|
url = f"{self.base_url}/{endpoint}"
|
261
|
-
|
290
|
+
|
291
|
+
# Set headers based on whether we're uploading files
|
292
|
+
if files:
|
293
|
+
# For multipart/form-data, don't set Content-Type header
|
294
|
+
# requests will set it automatically with boundary
|
295
|
+
headers = {}
|
296
|
+
else:
|
297
|
+
headers = {"Content-Type": "application/json"}
|
262
298
|
|
263
299
|
# Add Authorization header if we have an access token
|
264
300
|
if hasattr(self, "access_token") and self.access_token:
|
@@ -268,8 +304,8 @@ class Client:
|
|
268
304
|
# if data:
|
269
305
|
# logger.debug(f"Request data: {json.dumps(data, indent=2)}")
|
270
306
|
|
271
|
-
# Diagnose potential issues with the request
|
272
|
-
if method == "POST" and data:
|
307
|
+
# Diagnose potential issues with the request (only for non-file uploads)
|
308
|
+
if method == "POST" and data and not files:
|
273
309
|
diagnosis = self.diagnose_request(endpoint, data)
|
274
310
|
if not diagnosis["is_valid"]:
|
275
311
|
issues_str = "\n".join([f"- {issue}" for issue in diagnosis["issues"]])
|
@@ -277,14 +313,25 @@ class Client:
|
|
277
313
|
# We'll still send the request, but log the issues
|
278
314
|
|
279
315
|
try:
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
timeout
|
286
|
-
stream
|
287
|
-
|
316
|
+
# Prepare request parameters
|
317
|
+
request_params = {
|
318
|
+
"method": method,
|
319
|
+
"url": url,
|
320
|
+
"headers": headers,
|
321
|
+
"timeout": self.timeout,
|
322
|
+
"stream": stream,
|
323
|
+
}
|
324
|
+
|
325
|
+
# Add data based on request type
|
326
|
+
if files:
|
327
|
+
# For file uploads, use form data
|
328
|
+
request_params["data"] = data
|
329
|
+
request_params["files"] = files
|
330
|
+
else:
|
331
|
+
# For regular requests, use JSON
|
332
|
+
request_params["json"] = data
|
333
|
+
|
334
|
+
response = self.session.request(**request_params)
|
288
335
|
|
289
336
|
if stream:
|
290
337
|
return response
|
@@ -297,16 +344,10 @@ class Client:
|
|
297
344
|
# Update Authorization header with new token if available
|
298
345
|
if hasattr(self, "access_token") and self.access_token:
|
299
346
|
headers["Authorization"] = f"Bearer {self.access_token}"
|
347
|
+
request_params["headers"] = headers
|
300
348
|
|
301
349
|
# Retry the request after reauthentication
|
302
|
-
response = self.session.request(
|
303
|
-
method,
|
304
|
-
url,
|
305
|
-
headers=headers,
|
306
|
-
json=data,
|
307
|
-
timeout=self.timeout,
|
308
|
-
stream=stream,
|
309
|
-
)
|
350
|
+
response = self.session.request(**request_params)
|
310
351
|
|
311
352
|
if stream:
|
312
353
|
return response
|
@@ -459,6 +500,7 @@ class Client:
|
|
459
500
|
temperature: float = 0.7,
|
460
501
|
max_tokens: Optional[int] = None,
|
461
502
|
stream: bool = False,
|
503
|
+
byok_api_key: Optional[str] = None,
|
462
504
|
**kwargs,
|
463
505
|
) -> Dict[str, Any]:
|
464
506
|
"""
|
@@ -470,6 +512,7 @@ class Client:
|
|
470
512
|
temperature: Sampling temperature
|
471
513
|
max_tokens: Maximum number of tokens to generate
|
472
514
|
stream: Whether to stream the response
|
515
|
+
byok_api_key: Your own API key for the provider (BYOK - Bring Your Own Key)
|
473
516
|
**kwargs: Additional parameters to pass to the API
|
474
517
|
|
475
518
|
Returns:
|
@@ -490,6 +533,7 @@ class Client:
|
|
490
533
|
"temperature": temperature,
|
491
534
|
"max_tokens": max_tokens,
|
492
535
|
"stream": stream,
|
536
|
+
"byok_api_key": byok_api_key,
|
493
537
|
"additional_params": filtered_kwargs,
|
494
538
|
}
|
495
539
|
|
@@ -506,6 +550,7 @@ class Client:
|
|
506
550
|
temperature: float = 0.7,
|
507
551
|
max_tokens: Optional[int] = None,
|
508
552
|
stream: bool = False,
|
553
|
+
byok_api_key: Optional[str] = None,
|
509
554
|
**kwargs,
|
510
555
|
) -> Dict[str, Any]:
|
511
556
|
"""
|
@@ -517,6 +562,7 @@ class Client:
|
|
517
562
|
temperature: Sampling temperature
|
518
563
|
max_tokens: Maximum number of tokens to generate
|
519
564
|
stream: Whether to stream the response
|
565
|
+
byok_api_key: Your own API key for the provider (BYOK - Bring Your Own Key)
|
520
566
|
**kwargs: Additional parameters to pass to the API
|
521
567
|
|
522
568
|
Returns:
|
@@ -537,6 +583,7 @@ class Client:
|
|
537
583
|
"temperature": temperature,
|
538
584
|
"max_tokens": max_tokens,
|
539
585
|
"stream": stream,
|
586
|
+
"byok_api_key": byok_api_key,
|
540
587
|
"additional_params": filtered_kwargs,
|
541
588
|
}
|
542
589
|
|
@@ -550,6 +597,7 @@ class Client:
|
|
550
597
|
self,
|
551
598
|
text: Union[str, List[str]],
|
552
599
|
model: str = DEFAULT_EMBEDDING_MODEL,
|
600
|
+
byok_api_key: Optional[str] = None,
|
553
601
|
**kwargs,
|
554
602
|
) -> Dict[str, Any]:
|
555
603
|
"""
|
@@ -558,6 +606,7 @@ class Client:
|
|
558
606
|
Args:
|
559
607
|
text: Text to embed (string or list of strings)
|
560
608
|
model: Model to use in the format "provider/model" (e.g., "openai/text-embedding-ada-002")
|
609
|
+
byok_api_key: Your own API key for the provider (BYOK - Bring Your Own Key)
|
561
610
|
**kwargs: Additional parameters to pass to the API
|
562
611
|
|
563
612
|
Returns:
|
@@ -575,6 +624,7 @@ class Client:
|
|
575
624
|
data = {
|
576
625
|
"text": text if isinstance(text, list) else [text],
|
577
626
|
"model": formatted_model,
|
627
|
+
"byok_api_key": byok_api_key,
|
578
628
|
"additional_params": filtered_kwargs,
|
579
629
|
}
|
580
630
|
|
@@ -610,6 +660,7 @@ class Client:
|
|
610
660
|
enhance_prompt: Optional[bool] = None,
|
611
661
|
# Google-specific direct parameters
|
612
662
|
aspect_ratio: Optional[str] = None,
|
663
|
+
byok_api_key: Optional[str] = None,
|
613
664
|
**kwargs,
|
614
665
|
) -> Dict[str, Any]:
|
615
666
|
"""
|
@@ -650,6 +701,8 @@ class Client:
|
|
650
701
|
enhance_prompt: Whether to use prompt rewriting logic
|
651
702
|
aspect_ratio: Aspect ratio for Google models (e.g., "1:1", "16:9") - preferred over size
|
652
703
|
|
704
|
+
byok_api_key: Your own API key for the provider (BYOK - Bring Your Own Key)
|
705
|
+
|
653
706
|
**kwargs: Additional parameters to pass to the API
|
654
707
|
|
655
708
|
Returns:
|
@@ -674,6 +727,7 @@ class Client:
|
|
674
727
|
data = {
|
675
728
|
"prompt": prompt,
|
676
729
|
"model": formatted_model,
|
730
|
+
"byok_api_key": byok_api_key,
|
677
731
|
}
|
678
732
|
|
679
733
|
# Add optional parameters only if they are explicitly provided
|
@@ -793,6 +847,7 @@ class Client:
|
|
793
847
|
response_format: Optional[str] = None,
|
794
848
|
speed: Optional[float] = None,
|
795
849
|
instructions: Optional[str] = None,
|
850
|
+
byok_api_key: Optional[str] = None,
|
796
851
|
**kwargs,
|
797
852
|
) -> Dict[str, Any]:
|
798
853
|
"""
|
@@ -805,6 +860,7 @@ class Client:
|
|
805
860
|
response_format: Format of the audio response (e.g., "mp3", "opus", "aac", "flac")
|
806
861
|
speed: Speed of the generated audio (0.25 to 4.0)
|
807
862
|
instructions: Optional instructions for the TTS generation
|
863
|
+
byok_api_key: Your own API key for the provider (BYOK - Bring Your Own Key)
|
808
864
|
**kwargs: Additional parameters to pass to the API
|
809
865
|
|
810
866
|
Returns:
|
@@ -829,6 +885,13 @@ class Client:
|
|
829
885
|
model="provider/model-name",
|
830
886
|
voice="provider-specific-voice"
|
831
887
|
)
|
888
|
+
|
889
|
+
Using BYOK (Bring Your Own Key):
|
890
|
+
response = client.text_to_speech(
|
891
|
+
"Hello, world!",
|
892
|
+
model="openai/tts-1",
|
893
|
+
byok_api_key="sk-your-openai-key-here"
|
894
|
+
)
|
832
895
|
"""
|
833
896
|
# Format the model string
|
834
897
|
formatted_model = self._format_model_string(model)
|
@@ -859,8 +922,237 @@ class Client:
|
|
859
922
|
if filtered_kwargs:
|
860
923
|
data["additional_params"] = filtered_kwargs
|
861
924
|
|
925
|
+
# Add BYOK API key if provided
|
926
|
+
if byok_api_key:
|
927
|
+
data["byok_api_key"] = byok_api_key
|
928
|
+
|
862
929
|
return self._request("POST", TTS_ENDPOINT, data)
|
863
930
|
|
931
|
+
def speech_to_text(
|
932
|
+
self,
|
933
|
+
file: Union[str, bytes],
|
934
|
+
model: str = DEFAULT_STT_MODEL,
|
935
|
+
language: Optional[str] = None,
|
936
|
+
prompt: Optional[str] = None,
|
937
|
+
response_format: Optional[str] = "json",
|
938
|
+
temperature: Optional[float] = 0.0,
|
939
|
+
timestamp_granularities: Optional[List[str]] = None,
|
940
|
+
byok_api_key: Optional[str] = None,
|
941
|
+
**kwargs,
|
942
|
+
) -> Dict[str, Any]:
|
943
|
+
"""
|
944
|
+
Transcribe audio to text using speech-to-text models.
|
945
|
+
|
946
|
+
Args:
|
947
|
+
file: Audio file path (str) or audio file data (bytes)
|
948
|
+
model: Model to use in the format "provider/model" (e.g., "openai/whisper-1")
|
949
|
+
language: Language code for the audio (e.g., "en", "es", "fr")
|
950
|
+
prompt: Optional text to guide the model's style
|
951
|
+
response_format: Format of the response ("json", "text", "srt", "verbose_json", "vtt")
|
952
|
+
temperature: Temperature for transcription (0.0 to 1.0)
|
953
|
+
timestamp_granularities: List of timestamp granularities (["word", "segment"])
|
954
|
+
byok_api_key: Your own API key for the provider (BYOK - Bring Your Own Key)
|
955
|
+
**kwargs: Additional parameters to pass to the API
|
956
|
+
|
957
|
+
Returns:
|
958
|
+
Response data with transcription text
|
959
|
+
|
960
|
+
Examples:
|
961
|
+
Basic usage with file path:
|
962
|
+
response = client.speech_to_text("path/to/audio.mp3")
|
963
|
+
|
964
|
+
Basic usage with file bytes:
|
965
|
+
with open("audio.mp3", "rb") as f:
|
966
|
+
audio_data = f.read()
|
967
|
+
response = client.speech_to_text(audio_data)
|
968
|
+
|
969
|
+
With specific model and language:
|
970
|
+
response = client.speech_to_text(
|
971
|
+
"path/to/audio.wav",
|
972
|
+
model="openai/whisper-1",
|
973
|
+
language="en",
|
974
|
+
response_format="json"
|
975
|
+
)
|
976
|
+
|
977
|
+
With timestamps for detailed analysis:
|
978
|
+
response = client.speech_to_text(
|
979
|
+
"path/to/audio.mp3",
|
980
|
+
model="openai/whisper-1",
|
981
|
+
response_format="verbose_json",
|
982
|
+
timestamp_granularities=["word", "segment"]
|
983
|
+
)
|
984
|
+
|
985
|
+
Using BYOK (Bring Your Own Key):
|
986
|
+
response = client.speech_to_text(
|
987
|
+
"path/to/audio.mp3",
|
988
|
+
model="openai/whisper-1",
|
989
|
+
byok_api_key="sk-your-openai-key-here"
|
990
|
+
)
|
991
|
+
"""
|
992
|
+
# Format the model string
|
993
|
+
formatted_model = self._format_model_string(model)
|
994
|
+
|
995
|
+
# Handle file input - can be a file path (str) or file data (bytes)
|
996
|
+
if isinstance(file, str):
|
997
|
+
# It's a file path, read the file
|
998
|
+
try:
|
999
|
+
with open(file, "rb") as f:
|
1000
|
+
file_data = f.read()
|
1001
|
+
filename = os.path.basename(file)
|
1002
|
+
except FileNotFoundError:
|
1003
|
+
raise InvalidParametersError(f"File not found: {file}")
|
1004
|
+
except Exception as e:
|
1005
|
+
raise InvalidParametersError(f"Error reading file {file}: {str(e)}")
|
1006
|
+
elif isinstance(file, bytes):
|
1007
|
+
# It's file data
|
1008
|
+
file_data = file
|
1009
|
+
filename = kwargs.get("filename", "audio_file")
|
1010
|
+
else:
|
1011
|
+
raise InvalidParametersError(
|
1012
|
+
"File must be either a file path (str) or file data (bytes)"
|
1013
|
+
)
|
1014
|
+
|
1015
|
+
# Prepare form data for multipart upload
|
1016
|
+
files = {"file": (filename, file_data, "audio/*")}
|
1017
|
+
|
1018
|
+
# Create the form data with required parameters
|
1019
|
+
data = {
|
1020
|
+
"model": formatted_model,
|
1021
|
+
}
|
1022
|
+
|
1023
|
+
# Add optional parameters only if they are provided
|
1024
|
+
if language is not None:
|
1025
|
+
data["language"] = language
|
1026
|
+
if prompt is not None:
|
1027
|
+
data["prompt"] = prompt
|
1028
|
+
if response_format is not None:
|
1029
|
+
data["response_format"] = response_format
|
1030
|
+
if temperature is not None:
|
1031
|
+
data["temperature"] = temperature
|
1032
|
+
if timestamp_granularities is not None:
|
1033
|
+
# Convert to JSON string as expected by the API
|
1034
|
+
data["timestamp_granularities"] = json.dumps(timestamp_granularities)
|
1035
|
+
|
1036
|
+
# Add BYOK API key if provided
|
1037
|
+
if byok_api_key:
|
1038
|
+
data["byok_api_key"] = byok_api_key
|
1039
|
+
|
1040
|
+
# Filter out problematic parameters from kwargs
|
1041
|
+
filtered_kwargs = {}
|
1042
|
+
for key, value in kwargs.items():
|
1043
|
+
if key not in [
|
1044
|
+
"filename",
|
1045
|
+
"return_generator",
|
1046
|
+
]: # List of parameters to exclude
|
1047
|
+
filtered_kwargs[key] = value
|
1048
|
+
|
1049
|
+
# Add any additional parameters from kwargs
|
1050
|
+
if filtered_kwargs:
|
1051
|
+
data.update(filtered_kwargs)
|
1052
|
+
|
1053
|
+
return self._request("POST", STT_ENDPOINT, data, files=files)
|
1054
|
+
|
1055
|
+
def translate_audio(
|
1056
|
+
self,
|
1057
|
+
file: Union[str, bytes],
|
1058
|
+
model: str = DEFAULT_STT_MODEL,
|
1059
|
+
prompt: Optional[str] = None,
|
1060
|
+
response_format: Optional[str] = "json",
|
1061
|
+
temperature: Optional[float] = 0.0,
|
1062
|
+
byok_api_key: Optional[str] = None,
|
1063
|
+
**kwargs,
|
1064
|
+
) -> Dict[str, Any]:
|
1065
|
+
"""
|
1066
|
+
Translate audio to English text using speech-to-text models.
|
1067
|
+
|
1068
|
+
Args:
|
1069
|
+
file: Audio file path (str) or audio file data (bytes)
|
1070
|
+
model: Model to use in the format "provider/model" (e.g., "openai/whisper-1")
|
1071
|
+
prompt: Optional text to guide the model's style
|
1072
|
+
response_format: Format of the response ("json", "text", "srt", "verbose_json", "vtt")
|
1073
|
+
temperature: Temperature for translation (0.0 to 1.0)
|
1074
|
+
byok_api_key: Your own API key for the provider (BYOK - Bring Your Own Key)
|
1075
|
+
**kwargs: Additional parameters to pass to the API
|
1076
|
+
|
1077
|
+
Returns:
|
1078
|
+
Response data with translated text in English
|
1079
|
+
|
1080
|
+
Examples:
|
1081
|
+
Basic usage with file path:
|
1082
|
+
response = client.translate_audio("path/to/spanish_audio.mp3")
|
1083
|
+
|
1084
|
+
With specific response format:
|
1085
|
+
response = client.translate_audio(
|
1086
|
+
"path/to/french_audio.wav",
|
1087
|
+
model="openai/whisper-1",
|
1088
|
+
response_format="text"
|
1089
|
+
)
|
1090
|
+
|
1091
|
+
Using BYOK (Bring Your Own Key):
|
1092
|
+
response = client.translate_audio(
|
1093
|
+
"path/to/audio.mp3",
|
1094
|
+
model="openai/whisper-1",
|
1095
|
+
byok_api_key="sk-your-openai-key-here"
|
1096
|
+
)
|
1097
|
+
"""
|
1098
|
+
# Format the model string
|
1099
|
+
formatted_model = self._format_model_string(model)
|
1100
|
+
|
1101
|
+
# Handle file input - can be a file path (str) or file data (bytes)
|
1102
|
+
if isinstance(file, str):
|
1103
|
+
# It's a file path, read the file
|
1104
|
+
try:
|
1105
|
+
with open(file, "rb") as f:
|
1106
|
+
file_data = f.read()
|
1107
|
+
filename = os.path.basename(file)
|
1108
|
+
except FileNotFoundError:
|
1109
|
+
raise InvalidParametersError(f"File not found: {file}")
|
1110
|
+
except Exception as e:
|
1111
|
+
raise InvalidParametersError(f"Error reading file {file}: {str(e)}")
|
1112
|
+
elif isinstance(file, bytes):
|
1113
|
+
# It's file data
|
1114
|
+
file_data = file
|
1115
|
+
filename = kwargs.get("filename", "audio_file")
|
1116
|
+
else:
|
1117
|
+
raise InvalidParametersError(
|
1118
|
+
"File must be either a file path (str) or file data (bytes)"
|
1119
|
+
)
|
1120
|
+
|
1121
|
+
# Prepare form data for multipart upload
|
1122
|
+
files = {"file": (filename, file_data, "audio/*")}
|
1123
|
+
|
1124
|
+
# Create the form data with required parameters
|
1125
|
+
data = {
|
1126
|
+
"model": formatted_model,
|
1127
|
+
}
|
1128
|
+
|
1129
|
+
# Add optional parameters only if they are provided
|
1130
|
+
if prompt is not None:
|
1131
|
+
data["prompt"] = prompt
|
1132
|
+
if response_format is not None:
|
1133
|
+
data["response_format"] = response_format
|
1134
|
+
if temperature is not None:
|
1135
|
+
data["temperature"] = temperature
|
1136
|
+
|
1137
|
+
# Add BYOK API key if provided
|
1138
|
+
if byok_api_key:
|
1139
|
+
data["byok_api_key"] = byok_api_key
|
1140
|
+
|
1141
|
+
# Filter out problematic parameters from kwargs
|
1142
|
+
filtered_kwargs = {}
|
1143
|
+
for key, value in kwargs.items():
|
1144
|
+
if key not in [
|
1145
|
+
"filename",
|
1146
|
+
"return_generator",
|
1147
|
+
]: # List of parameters to exclude
|
1148
|
+
filtered_kwargs[key] = value
|
1149
|
+
|
1150
|
+
# Add any additional parameters from kwargs
|
1151
|
+
if filtered_kwargs:
|
1152
|
+
data.update(filtered_kwargs)
|
1153
|
+
|
1154
|
+
return self._request("POST", STT_TRANSLATION_ENDPOINT, data, files=files)
|
1155
|
+
|
864
1156
|
def _get_supported_parameters_for_model(
|
865
1157
|
self, provider: str, model_name: str
|
866
1158
|
) -> List[str]:
|
@@ -875,7 +1167,6 @@ class Client:
|
|
875
1167
|
Returns:
|
876
1168
|
List of parameter names supported by the model
|
877
1169
|
"""
|
878
|
-
# Define supported parameters for specific models
|
879
1170
|
if provider.lower() == "openai" and "gpt-image" in model_name.lower():
|
880
1171
|
return [
|
881
1172
|
"prompt",
|
indoxrouter/constants.py
CHANGED
@@ -5,7 +5,7 @@ Constants for the IndoxRouter client.
|
|
5
5
|
# API settings
|
6
6
|
DEFAULT_API_VERSION = "v1"
|
7
7
|
DEFAULT_BASE_URL = "https://api.indoxrouter.com" # Production server URL with HTTPS
|
8
|
-
# DEFAULT_BASE_URL = "http://localhost:
|
8
|
+
# DEFAULT_BASE_URL = "http://localhost:9050" # Local development server
|
9
9
|
DEFAULT_TIMEOUT = 60
|
10
10
|
USE_COOKIES = True # Always use cookie-based authentication
|
11
11
|
|
@@ -14,6 +14,7 @@ DEFAULT_MODEL = "openai/gpt-4o-mini"
|
|
14
14
|
DEFAULT_EMBEDDING_MODEL = "openai/text-embedding-3-small"
|
15
15
|
DEFAULT_IMAGE_MODEL = "openai/dall-e-3"
|
16
16
|
DEFAULT_TTS_MODEL = "openai/tts-1"
|
17
|
+
DEFAULT_STT_MODEL = "openai/whisper-1"
|
17
18
|
GOOGLE_IMAGE_MODEL = "google/imagen-3.0-generate-002"
|
18
19
|
XAI_IMAGE_MODEL = "xai/grok-2-image"
|
19
20
|
XAI_IMAGE_LATEST_MODEL = "xai/grok-2-image-latest"
|
@@ -25,6 +26,8 @@ COMPLETION_ENDPOINT = "completions"
|
|
25
26
|
EMBEDDING_ENDPOINT = "embeddings"
|
26
27
|
IMAGE_ENDPOINT = "images/generations"
|
27
28
|
TTS_ENDPOINT = "tts/generations"
|
29
|
+
STT_ENDPOINT = "audio/stt/transcriptions"
|
30
|
+
STT_TRANSLATION_ENDPOINT = "audio/stt/translations"
|
28
31
|
MODEL_ENDPOINT = "models"
|
29
32
|
USAGE_ENDPOINT = "user/usage"
|
30
33
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: indoxrouter
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.28
|
4
4
|
Summary: A unified client for various AI providers
|
5
5
|
Author-email: indoxRouter Team <ashkan.eskandari.dev@gmail.com>
|
6
6
|
License: MIT
|
@@ -33,6 +33,7 @@ A unified client for various AI providers, including OpenAI, anthropic, Google,
|
|
33
33
|
- **Simple Interface**: Easy-to-use methods for chat, completion, embeddings, image generation, and text-to-speech
|
34
34
|
- **Error Handling**: Standardized error handling across providers
|
35
35
|
- **Authentication**: Secure cookie-based authentication
|
36
|
+
- **BYOK Support**: Bring Your Own Key support for using your own provider API keys
|
36
37
|
|
37
38
|
## Installation
|
38
39
|
|
@@ -131,6 +132,34 @@ if "b64_json" in response["data"][0]:
|
|
131
132
|
# Use the base64 data (e.g., to display in HTML or save to file)
|
132
133
|
```
|
133
134
|
|
135
|
+
### BYOK (Bring Your Own Key) Support
|
136
|
+
|
137
|
+
IndoxRouter supports BYOK, allowing you to use your own API keys for AI providers:
|
138
|
+
|
139
|
+
```python
|
140
|
+
# Use your own OpenAI API key
|
141
|
+
response = client.chat(
|
142
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
143
|
+
model="openai/gpt-4",
|
144
|
+
byok_api_key="sk-your-openai-key-here"
|
145
|
+
)
|
146
|
+
|
147
|
+
# Use your own Google API key for image generation
|
148
|
+
response = client.images(
|
149
|
+
prompt="A beautiful sunset",
|
150
|
+
model="google/imagen-3.0-generate-002",
|
151
|
+
aspect_ratio="16:9",
|
152
|
+
byok_api_key="your-google-api-key-here"
|
153
|
+
)
|
154
|
+
```
|
155
|
+
|
156
|
+
**BYOK Benefits:**
|
157
|
+
|
158
|
+
- No credit deduction from your IndoxRouter account
|
159
|
+
- No platform rate limiting
|
160
|
+
- Direct provider access with your own API keys
|
161
|
+
- Cost control - pay providers directly at their rates
|
162
|
+
|
134
163
|
### Text-to-Speech
|
135
164
|
|
136
165
|
```python
|
@@ -0,0 +1,9 @@
|
|
1
|
+
indoxrouter/__init__.py,sha256=P_2eiiAi-DrkkeeJndQdEMqnVheWzOZIjWkDayrwxuk,1561
|
2
|
+
indoxrouter/client.py,sha256=dzeLN-DKHiHORhXA0zI_fFg5_Rvrd98uNXqpIFKobBQ,55234
|
3
|
+
indoxrouter/constants.py,sha256=Ti-XzQRgFQlNkfmaCKtfBhReb5nIkdnfuTvH0Zu7GHI,1584
|
4
|
+
indoxrouter/exceptions.py,sha256=cGlXNF8dd4X8UBWgxTA099nRhEkIjcqE_4iGOlIVVp8,1632
|
5
|
+
indoxrouter-0.1.28.dist-info/licenses/LICENSE,sha256=5n28CfoynFakg-QJIHnecEXcveN8gq-ZwhC0h7ATse0,24232
|
6
|
+
indoxrouter-0.1.28.dist-info/METADATA,sha256=1FhWrzOdVaSLO6H29KYJBLG5kO_MQqbutpkhvh669Vc,5740
|
7
|
+
indoxrouter-0.1.28.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
8
|
+
indoxrouter-0.1.28.dist-info/top_level.txt,sha256=v6FGWkw0QAnXhyYtnXLI1cxzna0iveNvZUotVzCWabM,12
|
9
|
+
indoxrouter-0.1.28.dist-info/RECORD,,
|
@@ -1,9 +0,0 @@
|
|
1
|
-
indoxrouter/__init__.py,sha256=NhFlmJxbxcM3XCF21YZv9QsOk-k50rsqzw5Bk3mmr6E,1561
|
2
|
-
indoxrouter/client.py,sha256=KQpOFaTntNYmPgYjBJHq0z921vgKlyeUcaTYgZX7axM,43764
|
3
|
-
indoxrouter/constants.py,sha256=HqTXfNpL_UvWsR5mod8YZgWOs1W6x6itr1BSbmyYEHY,1451
|
4
|
-
indoxrouter/exceptions.py,sha256=cGlXNF8dd4X8UBWgxTA099nRhEkIjcqE_4iGOlIVVp8,1632
|
5
|
-
indoxrouter-0.1.26.dist-info/licenses/LICENSE,sha256=5n28CfoynFakg-QJIHnecEXcveN8gq-ZwhC0h7ATse0,24232
|
6
|
-
indoxrouter-0.1.26.dist-info/METADATA,sha256=nzfcnbrzEJznKEPA1gAG34-C5KjemPqUavXO2e8-3fs,4909
|
7
|
-
indoxrouter-0.1.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
8
|
-
indoxrouter-0.1.26.dist-info/top_level.txt,sha256=v6FGWkw0QAnXhyYtnXLI1cxzna0iveNvZUotVzCWabM,12
|
9
|
-
indoxrouter-0.1.26.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|