indoxrouter 0.1.26__tar.gz → 0.1.28__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: indoxrouter
3
- Version: 0.1.26
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
@@ -8,6 +8,7 @@ A unified client for various AI providers, including OpenAI, anthropic, Google,
8
8
  - **Simple Interface**: Easy-to-use methods for chat, completion, embeddings, image generation, and text-to-speech
9
9
  - **Error Handling**: Standardized error handling across providers
10
10
  - **Authentication**: Secure cookie-based authentication
11
+ - **BYOK Support**: Bring Your Own Key support for using your own provider API keys
11
12
 
12
13
  ## Installation
13
14
 
@@ -106,6 +107,34 @@ if "b64_json" in response["data"][0]:
106
107
  # Use the base64 data (e.g., to display in HTML or save to file)
107
108
  ```
108
109
 
110
+ ### BYOK (Bring Your Own Key) Support
111
+
112
+ IndoxRouter supports BYOK, allowing you to use your own API keys for AI providers:
113
+
114
+ ```python
115
+ # Use your own OpenAI API key
116
+ response = client.chat(
117
+ messages=[{"role": "user", "content": "Hello!"}],
118
+ model="openai/gpt-4",
119
+ byok_api_key="sk-your-openai-key-here"
120
+ )
121
+
122
+ # Use your own Google API key for image generation
123
+ response = client.images(
124
+ prompt="A beautiful sunset",
125
+ model="google/imagen-3.0-generate-002",
126
+ aspect_ratio="16:9",
127
+ byok_api_key="your-google-api-key-here"
128
+ )
129
+ ```
130
+
131
+ **BYOK Benefits:**
132
+
133
+ - No credit deduction from your IndoxRouter account
134
+ - No platform rate limiting
135
+ - Direct provider access with your own API keys
136
+ - Cost control - pay providers directly at their rates
137
+
109
138
  ### Text-to-Speech
110
139
 
111
140
  ```python
@@ -0,0 +1,141 @@
1
+ """
2
+ Example script demonstrating speech-to-text functionality in IndoxRouter.
3
+
4
+ This script shows how to use the new speech-to-text capabilities added to the IndoxRouter client.
5
+ """
6
+
7
+ from indoxrouter import Client
8
+
9
+
10
+ def main():
11
+ # Initialize the client with your API key
12
+ client = Client(api_key="your_api_key_here")
13
+
14
+ try:
15
+ print("=== IndoxRouter Speech-to-Text Examples ===\n")
16
+
17
+ # Example 1: Basic transcription with file path
18
+ print("1. Transcribing audio file:")
19
+ try:
20
+ response = client.speech_to_text(
21
+ "path/to/your/audio.mp3", model="openai/whisper-1"
22
+ )
23
+ if response["success"]:
24
+ print(f" Transcription: {response['text']}")
25
+ else:
26
+ print(f" Error: {response['message']}")
27
+ except Exception as e:
28
+ print(f" Example 1 Error: {e}")
29
+
30
+ print()
31
+
32
+ # Example 2: Transcription with specific language and format
33
+ print("2. Transcription with language specification:")
34
+ try:
35
+ response = client.speech_to_text(
36
+ "path/to/your/audio.wav",
37
+ model="openai/whisper-1",
38
+ language="en",
39
+ response_format="verbose_json",
40
+ temperature=0.2,
41
+ )
42
+ if response["success"]:
43
+ print(f" Transcription: {response['text']}")
44
+ if "language" in response:
45
+ print(f" Detected Language: {response['language']}")
46
+ else:
47
+ print(f" Error: {response['message']}")
48
+ except Exception as e:
49
+ print(f" Example 2 Error: {e}")
50
+
51
+ print()
52
+
53
+ # Example 3: Transcription with timestamps
54
+ print("3. Transcription with detailed timestamps:")
55
+ try:
56
+ response = client.speech_to_text(
57
+ "path/to/your/audio.mp3",
58
+ model="openai/whisper-1",
59
+ response_format="verbose_json",
60
+ timestamp_granularities=["word", "segment"],
61
+ )
62
+ if response["success"]:
63
+ print(f" Transcription: {response['text']}")
64
+ if "segments" in response:
65
+ print(f" Number of segments: {len(response['segments'])}")
66
+ else:
67
+ print(f" Error: {response['message']}")
68
+ except Exception as e:
69
+ print(f" Example 3 Error: {e}")
70
+
71
+ print()
72
+
73
+ # Example 4: Audio translation to English
74
+ print("4. Translating foreign audio to English:")
75
+ try:
76
+ response = client.translate_audio(
77
+ "path/to/your/foreign_audio.mp3",
78
+ model="openai/whisper-1",
79
+ response_format="text",
80
+ )
81
+ if response["success"]:
82
+ print(f" Translation: {response['text']}")
83
+ else:
84
+ print(f" Error: {response['message']}")
85
+ except Exception as e:
86
+ print(f" Example 4 Error: {e}")
87
+
88
+ print()
89
+
90
+ # Example 5: Using audio data bytes instead of file path
91
+ print("5. Transcription using audio bytes:")
92
+ try:
93
+ # Read audio file as bytes
94
+ with open("path/to/your/audio.mp3", "rb") as f:
95
+ audio_data = f.read()
96
+
97
+ response = client.speech_to_text(
98
+ audio_data,
99
+ model="openai/whisper-1",
100
+ filename="my_audio.mp3", # Optional filename hint
101
+ )
102
+ if response["success"]:
103
+ print(f" Transcription: {response['text']}")
104
+ else:
105
+ print(f" Error: {response['message']}")
106
+ except FileNotFoundError:
107
+ print(
108
+ " Example 5 Note: Audio file not found - this is expected for the example"
109
+ )
110
+ except Exception as e:
111
+ print(f" Example 5 Error: {e}")
112
+
113
+ print()
114
+
115
+ # Example 6: Using BYOK (Bring Your Own Key)
116
+ print("6. Using BYOK (Bring Your Own Key):")
117
+ try:
118
+ response = client.speech_to_text(
119
+ "path/to/your/audio.mp3",
120
+ model="openai/whisper-1",
121
+ byok_api_key="sk-your-openai-key-here",
122
+ )
123
+ if response["success"]:
124
+ print(f" Transcription: {response['text']}")
125
+ print(
126
+ " Note: This used your own OpenAI API key (no IndoxRouter credits used)"
127
+ )
128
+ else:
129
+ print(f" Error: {response['message']}")
130
+ except Exception as e:
131
+ print(f" Example 6 Error: {e}")
132
+
133
+ print("\n=== Examples completed ===")
134
+
135
+ finally:
136
+ # Clean up the client
137
+ client.close()
138
+
139
+
140
+ if __name__ == "__main__":
141
+ main()
@@ -47,7 +47,7 @@ from .exceptions import (
47
47
  APIError,
48
48
  )
49
49
 
50
- __version__ = "0.1.26"
50
+ __version__ = "0.1.27"
51
51
  __all__ = [
52
52
  "Client",
53
53
  "IndoxRouter",
@@ -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
- headers = {"Content-Type": "application/json"}
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
- response = self.session.request(
281
- method,
282
- url,
283
- headers=headers,
284
- json=data,
285
- timeout=self.timeout,
286
- stream=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",
@@ -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:8000" # Local development server
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.26
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
@@ -2,6 +2,7 @@ LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
4
  pyproject.toml
5
+ examples/speech_to_text_example.py
5
6
  indoxrouter/__init__.py
6
7
  indoxrouter/client.py
7
8
  indoxrouter/constants.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "indoxrouter"
7
- version = "0.1.26"
7
+ version = "0.1.28"
8
8
  authors = [
9
9
  {name = "indoxRouter Team", email = "ashkan.eskandari.dev@gmail.com"},
10
10
  ]
File without changes
File without changes
File without changes