webscout 8.3__py3-none-any.whl → 8.3.2__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.

Potentially problematic release.


This version of webscout might be problematic. Click here for more details.

Files changed (120) hide show
  1. webscout/AIauto.py +4 -4
  2. webscout/AIbase.py +61 -1
  3. webscout/AIutel.py +46 -53
  4. webscout/Bing_search.py +418 -0
  5. webscout/Extra/YTToolkit/ytapi/patterns.py +45 -45
  6. webscout/Extra/YTToolkit/ytapi/stream.py +1 -1
  7. webscout/Extra/YTToolkit/ytapi/video.py +10 -10
  8. webscout/Extra/autocoder/autocoder_utiles.py +1 -1
  9. webscout/Extra/gguf.py +706 -177
  10. webscout/Litlogger/formats.py +9 -0
  11. webscout/Litlogger/handlers.py +18 -0
  12. webscout/Litlogger/logger.py +43 -1
  13. webscout/Provider/AISEARCH/genspark_search.py +7 -7
  14. webscout/Provider/AISEARCH/scira_search.py +3 -2
  15. webscout/Provider/GeminiProxy.py +140 -0
  16. webscout/Provider/LambdaChat.py +7 -1
  17. webscout/Provider/MCPCore.py +78 -75
  18. webscout/Provider/OPENAI/BLACKBOXAI.py +1046 -1017
  19. webscout/Provider/OPENAI/GeminiProxy.py +328 -0
  20. webscout/Provider/OPENAI/Qwen3.py +303 -303
  21. webscout/Provider/OPENAI/README.md +5 -0
  22. webscout/Provider/OPENAI/README_AUTOPROXY.md +238 -0
  23. webscout/Provider/OPENAI/TogetherAI.py +355 -0
  24. webscout/Provider/OPENAI/__init__.py +16 -1
  25. webscout/Provider/OPENAI/autoproxy.py +332 -0
  26. webscout/Provider/OPENAI/base.py +101 -14
  27. webscout/Provider/OPENAI/chatgpt.py +15 -2
  28. webscout/Provider/OPENAI/chatgptclone.py +14 -3
  29. webscout/Provider/OPENAI/deepinfra.py +339 -328
  30. webscout/Provider/OPENAI/e2b.py +295 -74
  31. webscout/Provider/OPENAI/mcpcore.py +109 -70
  32. webscout/Provider/OPENAI/opkfc.py +18 -6
  33. webscout/Provider/OPENAI/scirachat.py +59 -50
  34. webscout/Provider/OPENAI/toolbaz.py +2 -10
  35. webscout/Provider/OPENAI/writecream.py +166 -166
  36. webscout/Provider/OPENAI/x0gpt.py +367 -367
  37. webscout/Provider/OPENAI/xenai.py +514 -0
  38. webscout/Provider/OPENAI/yep.py +389 -383
  39. webscout/Provider/STT/__init__.py +3 -0
  40. webscout/Provider/STT/base.py +281 -0
  41. webscout/Provider/STT/elevenlabs.py +265 -0
  42. webscout/Provider/TTI/__init__.py +4 -1
  43. webscout/Provider/TTI/aiarta.py +399 -365
  44. webscout/Provider/TTI/base.py +74 -2
  45. webscout/Provider/TTI/bing.py +231 -0
  46. webscout/Provider/TTI/fastflux.py +63 -30
  47. webscout/Provider/TTI/gpt1image.py +149 -0
  48. webscout/Provider/TTI/imagen.py +196 -0
  49. webscout/Provider/TTI/magicstudio.py +60 -29
  50. webscout/Provider/TTI/piclumen.py +43 -32
  51. webscout/Provider/TTI/pixelmuse.py +232 -225
  52. webscout/Provider/TTI/pollinations.py +43 -32
  53. webscout/Provider/TTI/together.py +287 -0
  54. webscout/Provider/TTI/utils.py +2 -1
  55. webscout/Provider/TTS/README.md +1 -0
  56. webscout/Provider/TTS/__init__.py +2 -1
  57. webscout/Provider/TTS/freetts.py +140 -0
  58. webscout/Provider/TTS/speechma.py +45 -39
  59. webscout/Provider/TogetherAI.py +366 -0
  60. webscout/Provider/UNFINISHED/ChutesAI.py +314 -0
  61. webscout/Provider/UNFINISHED/fetch_together_models.py +95 -0
  62. webscout/Provider/XenAI.py +324 -0
  63. webscout/Provider/__init__.py +8 -0
  64. webscout/Provider/deepseek_assistant.py +378 -0
  65. webscout/Provider/scira_chat.py +3 -2
  66. webscout/Provider/toolbaz.py +0 -1
  67. webscout/auth/__init__.py +44 -0
  68. webscout/auth/api_key_manager.py +189 -0
  69. webscout/auth/auth_system.py +100 -0
  70. webscout/auth/config.py +76 -0
  71. webscout/auth/database.py +400 -0
  72. webscout/auth/exceptions.py +67 -0
  73. webscout/auth/middleware.py +248 -0
  74. webscout/auth/models.py +130 -0
  75. webscout/auth/providers.py +257 -0
  76. webscout/auth/rate_limiter.py +254 -0
  77. webscout/auth/request_models.py +127 -0
  78. webscout/auth/request_processing.py +226 -0
  79. webscout/auth/routes.py +526 -0
  80. webscout/auth/schemas.py +103 -0
  81. webscout/auth/server.py +312 -0
  82. webscout/auth/static/favicon.svg +11 -0
  83. webscout/auth/swagger_ui.py +203 -0
  84. webscout/auth/templates/components/authentication.html +237 -0
  85. webscout/auth/templates/components/base.html +103 -0
  86. webscout/auth/templates/components/endpoints.html +750 -0
  87. webscout/auth/templates/components/examples.html +491 -0
  88. webscout/auth/templates/components/footer.html +75 -0
  89. webscout/auth/templates/components/header.html +27 -0
  90. webscout/auth/templates/components/models.html +286 -0
  91. webscout/auth/templates/components/navigation.html +70 -0
  92. webscout/auth/templates/static/api.js +455 -0
  93. webscout/auth/templates/static/icons.js +168 -0
  94. webscout/auth/templates/static/main.js +784 -0
  95. webscout/auth/templates/static/particles.js +201 -0
  96. webscout/auth/templates/static/styles.css +3353 -0
  97. webscout/auth/templates/static/ui.js +374 -0
  98. webscout/auth/templates/swagger_ui.html +170 -0
  99. webscout/client.py +49 -3
  100. webscout/litagent/Readme.md +12 -3
  101. webscout/litagent/agent.py +99 -62
  102. webscout/scout/core/scout.py +104 -26
  103. webscout/scout/element.py +139 -18
  104. webscout/swiftcli/core/cli.py +14 -3
  105. webscout/swiftcli/decorators/output.py +59 -9
  106. webscout/update_checker.py +31 -49
  107. webscout/version.py +1 -1
  108. webscout/webscout_search.py +4 -12
  109. webscout/webscout_search_async.py +3 -10
  110. webscout/yep_search.py +2 -11
  111. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/METADATA +41 -11
  112. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/RECORD +116 -68
  113. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/entry_points.txt +1 -1
  114. webscout/Provider/HF_space/__init__.py +0 -0
  115. webscout/Provider/HF_space/qwen_qwen2.py +0 -206
  116. webscout/Provider/OPENAI/api.py +0 -1035
  117. webscout/Provider/TTI/artbit.py +0 -0
  118. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/WHEEL +0 -0
  119. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/licenses/LICENSE.md +0 -0
  120. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,12 @@
1
1
  ##################################################################################
2
2
  ## Modified version of code written by t.me/infip1217 ##
3
3
  ##################################################################################
4
- import time
5
4
  import requests
6
5
  import pathlib
7
6
  import tempfile
8
- from io import BytesIO
9
7
  from webscout import exceptions
10
8
  from webscout.litagent import LitAgent
11
- from concurrent.futures import ThreadPoolExecutor, as_completed
12
- from webscout.Provider.TTS import utils
9
+ from webscout.Litlogger import Logger, LogLevel
13
10
  from webscout.Provider.TTS.base import BaseTTSProvider
14
11
 
15
12
  class SpeechMaTTS(BaseTTSProvider):
@@ -18,12 +15,11 @@ class SpeechMaTTS(BaseTTSProvider):
18
15
  """
19
16
  # Request headers
20
17
  headers = {
21
- "accept": "*/*",
22
- "accept-language": "en-IN,en-GB;q=0.9,en-US;q=0.8,en;q=0.7,en-AU;q=0.6",
23
- "content-type": "application/json",
18
+ "authority": "speechma.com",
24
19
  "origin": "https://speechma.com",
25
- "priority": "u=1, i",
26
- "User-Agent": LitAgent().random()
20
+ "referer": "https://speechma.com/",
21
+ "content-type": "application/json",
22
+ **LitAgent().generate_fingerprint()
27
23
  }
28
24
 
29
25
  # Available voices with their IDs
@@ -496,8 +492,9 @@ class SpeechMaTTS(BaseTTSProvider):
496
492
  if proxies:
497
493
  self.session.proxies.update(proxies)
498
494
  self.timeout = timeout
495
+ self.logger = Logger(name="SpeechMaTTS", level=LogLevel.INFO)
499
496
 
500
- def tts(self, text: str, voice: str = "Emma", pitch: int = 0, rate: int = 0) -> str:
497
+ def tts(self, text: str, voice: str = "Emma", pitch: int = 0, rate: int = 0, verbose: bool = False) -> str:
501
498
  """
502
499
  Converts text to speech using the SpeechMa API and saves it to a file.
503
500
 
@@ -506,6 +503,7 @@ class SpeechMaTTS(BaseTTSProvider):
506
503
  voice (str): The voice to use for TTS (default: "Emma")
507
504
  pitch (int): Voice pitch adjustment (-10 to 10, default: 0)
508
505
  rate (int): Voice rate/speed adjustment (-10 to 10, default: 0)
506
+ verbose (bool): Whether to print debug information (default: False)
509
507
 
510
508
  Returns:
511
509
  str: Path to the generated audio file
@@ -517,10 +515,13 @@ class SpeechMaTTS(BaseTTSProvider):
517
515
  voice in self.all_voices
518
516
  ), f"Voice '{voice}' not one of [{', '.join(self.all_voices.keys())}]"
519
517
 
518
+ if not text or text.strip() == '':
519
+ raise exceptions.FailedToGenerateResponseError("Text is empty")
520
+
520
521
  filename = pathlib.Path(tempfile.mktemp(suffix=".mp3", dir=self.temp_dir))
521
522
  voice_id = self.all_voices[voice]
522
523
 
523
- # Prepare payload for the job-based API
524
+ # Prepare payload for the API
524
525
  payload = {
525
526
  "text": text,
526
527
  "voice": voice_id,
@@ -530,44 +531,49 @@ class SpeechMaTTS(BaseTTSProvider):
530
531
  }
531
532
 
532
533
  try:
534
+ # Set logger level based on verbose flag
535
+ if verbose:
536
+ self.logger.level = LogLevel.DEBUG
537
+ self.logger.debug(f"Generating audio for voice: {voice} ({voice_id})")
538
+ self.logger.debug(f"Text length: {len(text)} characters")
539
+ else:
540
+ self.logger.level = LogLevel.INFO
541
+
542
+ # Make the request to the SpeechMa API
533
543
  response = self.session.post(
534
544
  self.api_url,
535
545
  headers=self.headers,
536
546
  json=payload,
537
547
  timeout=self.timeout
538
548
  )
539
- response.raise_for_status()
540
- resp_json = response.json()
541
- if not resp_json.get("success") or "data" not in resp_json or "job_id" not in resp_json["data"]:
542
- raise exceptions.FailedToGenerateResponseError(f"SpeechMa API error: {resp_json}")
543
- job_id = resp_json["data"]["job_id"]
544
549
 
545
- # Poll for job completion
546
- status_url = f"https://speechma.com/com.api/tts-api.php/status/{job_id}"
547
- for _ in range(30): # up to ~30 seconds
548
- status_resp = self.session.get(
549
- status_url,
550
- headers=self.headers,
551
- timeout=self.timeout
552
- )
553
- status_resp.raise_for_status()
554
- status_json = status_resp.json()
555
- if status_json.get("success") and status_json.get("data", {}).get("status") == "completed":
556
- break
557
- time.sleep(1)
558
- else:
559
- raise exceptions.FailedToGenerateResponseError("TTS job did not complete in time.")
550
+ if response.status_code != 200:
551
+ if verbose:
552
+ self.logger.error(f"API error: Status {response.status_code}")
553
+ raise exceptions.FailedToGenerateResponseError(f"API returned status {response.status_code}: {response.text[:500]}")
560
554
 
561
- # Download the audio file (API provides a URL in the status response)
562
- data = status_json["data"]
563
- audio_url = f"https://speechma.com/com.api/tts-api.php/audio/{job_id}"
564
- audio_resp = self.session.get(audio_url, timeout=self.timeout)
565
- audio_resp.raise_for_status()
566
- with open(filename, 'wb') as f:
567
- f.write(audio_resp.content)
568
- return filename.as_posix()
555
+ # Check if response is audio data (content-type should be audio/mpeg)
556
+ content_type = response.headers.get('content-type', '').lower()
557
+ if verbose:
558
+ self.logger.debug(f"Response content type: {content_type}")
559
+ self.logger.debug(f"Response size: {len(response.content)} bytes")
560
+
561
+ if 'audio' in content_type or response.content.startswith(b'\xff\xfb') or response.content.startswith(b'ID3') or b'LAME' in response.content[:100]:
562
+ # This is audio data, save it directly
563
+ with open(filename, 'wb') as f:
564
+ f.write(response.content)
565
+ if verbose:
566
+ self.logger.debug(f"Audio saved to: {filename}")
567
+ return filename.as_posix()
568
+ else:
569
+ # Unexpected response format
570
+ if verbose:
571
+ self.logger.error(f"Unexpected response format: {content_type}")
572
+ raise exceptions.FailedToGenerateResponseError(f"Unexpected response format. Content-Type: {content_type}, Content: {response.text[:200]}")
569
573
 
570
574
  except requests.exceptions.RequestException as e:
575
+ if verbose:
576
+ self.logger.error(f"Request failed: {e}")
571
577
  raise exceptions.FailedToGenerateResponseError(
572
578
  f"Failed to perform the operation: {e}"
573
579
  )
@@ -0,0 +1,366 @@
1
+ from curl_cffi.requests import Session
2
+ from curl_cffi import CurlError
3
+ from typing import Any, Dict, Optional, Generator, Union
4
+
5
+ from webscout.AIutel import Optimizers
6
+ from webscout.AIutel import Conversation
7
+ from webscout.AIutel import AwesomePrompts, sanitize_stream
8
+ from webscout.AIbase import Provider
9
+ from webscout import exceptions
10
+ from webscout.litagent import LitAgent
11
+
12
+ class TogetherAI(Provider):
13
+ """
14
+ A class to interact with the TogetherAI API.
15
+ """
16
+
17
+ AVAILABLE_MODELS = [
18
+ "Gryphe/MythoMax-L2-13b",
19
+ "Gryphe/MythoMax-L2-13b-Lite",
20
+ "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
21
+ "Qwen/QwQ-32B",
22
+ "Qwen/Qwen2-72B-Instruct",
23
+ "Qwen/Qwen2-VL-72B-Instruct",
24
+ "Qwen/Qwen2.5-72B-Instruct-Turbo",
25
+ "Qwen/Qwen2.5-7B-Instruct-Turbo",
26
+ "Qwen/Qwen2.5-Coder-32B-Instruct",
27
+ "Qwen/Qwen2.5-VL-72B-Instruct",
28
+ "Qwen/Qwen3-235B-A22B-fp8",
29
+ "Qwen/Qwen3-235B-A22B-fp8-tput",
30
+ "Rrrr/meta-llama/Llama-3-70b-chat-hf-6f9ad551",
31
+ "Rrrr/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo-03dc18e1",
32
+ "Rrrr/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo-6c92f39d",
33
+ "arcee-ai/arcee-blitz",
34
+ "arcee-ai/caller",
35
+ "arcee-ai/coder-large",
36
+ "arcee-ai/maestro-reasoning",
37
+ "arcee-ai/virtuoso-large",
38
+ "arcee-ai/virtuoso-medium-v2",
39
+ "arcee_ai/arcee-spotlight",
40
+ "blackbox/meta-llama-3-1-8b",
41
+ "deepseek-ai/DeepSeek-R1",
42
+ "deepseek-ai/DeepSeek-R1-Distill-Llama-70B",
43
+ "deepseek-ai/DeepSeek-R1-Distill-Llama-70B-free",
44
+ "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B",
45
+ "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B",
46
+ "deepseek-ai/DeepSeek-V3",
47
+ "deepseek-ai/DeepSeek-V3-p-dp",
48
+ "google/gemma-2-27b-it",
49
+ "google/gemma-2b-it",
50
+ "lgai/exaone-3-5-32b-instruct",
51
+ "lgai/exaone-deep-32b",
52
+ "marin-community/marin-8b-instruct",
53
+ "meta-llama/Llama-3-70b-chat-hf",
54
+ "meta-llama/Llama-3-8b-chat-hf",
55
+ "meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo",
56
+ "meta-llama/Llama-3.2-3B-Instruct-Turbo",
57
+ "meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo",
58
+ "meta-llama/Llama-3.3-70B-Instruct-Turbo",
59
+ "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free",
60
+ "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
61
+ "meta-llama/Llama-4-Scout-17B-16E-Instruct",
62
+ "meta-llama/Llama-Vision-Free",
63
+ "meta-llama/Meta-Llama-3-70B-Instruct-Turbo",
64
+ "meta-llama/Meta-Llama-3-8B-Instruct-Lite",
65
+ "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo",
66
+ "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
67
+ "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
68
+ "mistralai/Mistral-7B-Instruct-v0.1",
69
+ "mistralai/Mistral-7B-Instruct-v0.2",
70
+ "mistralai/Mistral-7B-Instruct-v0.3",
71
+ "mistralai/Mistral-Small-24B-Instruct-2501",
72
+ "mistralai/Mixtral-8x7B-Instruct-v0.1",
73
+ "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF",
74
+ "perplexity-ai/r1-1776",
75
+ "roberizk@gmail.com/meta-llama/Llama-3-70b-chat-hf-26ee936b",
76
+ "roberizk@gmail.com/meta-llama/Meta-Llama-3-70B-Instruct-6feb41f7",
77
+ "roberizk@gmail.com/meta-llama/Meta-Llama-3-8B-Instruct-8ced8839",
78
+ "scb10x/scb10x-llama3-1-typhoon2-70b-instruct",
79
+ "scb10x/scb10x-llama3-1-typhoon2-8b-instruct",
80
+ "togethercomputer/MoA-1",
81
+ "togethercomputer/MoA-1-Turbo",
82
+ "togethercomputer/Refuel-Llm-V2",
83
+ "togethercomputer/Refuel-Llm-V2-Small",
84
+ ]
85
+
86
+ @staticmethod
87
+ def _togetherai_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
88
+ """Extracts content from TogetherAI stream JSON objects."""
89
+ if isinstance(chunk, dict):
90
+ return chunk.get("choices", [{}])[0].get("delta", {}).get("content")
91
+ return None
92
+
93
+ def __init__(
94
+ self,
95
+ is_conversation: bool = True,
96
+ max_tokens: int = 2049,
97
+ timeout: int = 30,
98
+ intro: str = None,
99
+ filepath: str = None,
100
+ update_file: bool = True,
101
+ proxies: dict = {},
102
+ history_offset: int = 10250,
103
+ act: str = None,
104
+ model: str = "meta-llama/Llama-3.1-8B-Instruct-Turbo",
105
+ system_prompt: str = "You are a helpful assistant.",
106
+ browser: str = "chrome"
107
+ ):
108
+ """Initializes the TogetherAI API client."""
109
+ if model not in self.AVAILABLE_MODELS:
110
+ raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
111
+
112
+ self.api_endpoint = "https://api.together.xyz/v1/chat/completions"
113
+ self.activation_endpoint = "https://www.codegeneration.ai/activate-v2"
114
+
115
+ # Initialize LitAgent
116
+ self.agent = LitAgent()
117
+ self.fingerprint = self.agent.generate_fingerprint(browser)
118
+
119
+ # Use the fingerprint for headers
120
+ self.headers = {
121
+ "Accept": self.fingerprint["accept"],
122
+ "Accept-Language": self.fingerprint["accept_language"],
123
+ "Content-Type": "application/json",
124
+ "Cache-Control": "no-cache",
125
+ "Origin": "https://www.codegeneration.ai",
126
+ "Pragma": "no-cache",
127
+ "Referer": "https://www.codegeneration.ai/",
128
+ "Sec-Fetch-Dest": "empty",
129
+ "Sec-Fetch-Mode": "cors",
130
+ "Sec-Fetch-Site": "same-site",
131
+ "User-Agent": self.fingerprint["user_agent"],
132
+ }
133
+
134
+ # Initialize curl_cffi Session
135
+ self.session = Session()
136
+ self.session.headers.update(self.headers)
137
+ self.session.proxies = proxies
138
+ self.system_prompt = system_prompt
139
+ self.is_conversation = is_conversation
140
+ self.max_tokens_to_sample = max_tokens
141
+ self.timeout = timeout
142
+ self.last_response = {}
143
+ self.model = model
144
+ self._api_key_cache = None
145
+
146
+ self.__available_optimizers = (
147
+ method
148
+ for method in dir(Optimizers)
149
+ if callable(getattr(Optimizers, method)) and not method.startswith("__")
150
+ )
151
+ Conversation.intro = (
152
+ AwesomePrompts().get_act(
153
+ act, raise_not_found=True, default=None, case_insensitive=True
154
+ )
155
+ if act
156
+ else intro or Conversation.intro
157
+ )
158
+
159
+ self.conversation = Conversation(
160
+ is_conversation, self.max_tokens_to_sample, filepath, update_file
161
+ )
162
+ self.conversation.history_offset = history_offset
163
+
164
+ def refresh_identity(self, browser: str = None):
165
+ """
166
+ Refreshes the browser identity fingerprint.
167
+
168
+ Args:
169
+ browser: Specific browser to use for the new fingerprint
170
+ """
171
+ browser = browser or self.fingerprint.get("browser_type", "chrome")
172
+ self.fingerprint = self.agent.generate_fingerprint(browser)
173
+
174
+ # Update headers with new fingerprint
175
+ self.headers.update({
176
+ "Accept": self.fingerprint["accept"],
177
+ "Accept-Language": self.fingerprint["accept_language"],
178
+ "User-Agent": self.fingerprint["user_agent"],
179
+ })
180
+
181
+ # Update session headers
182
+ self.session.headers.update(self.headers)
183
+
184
+ return self.fingerprint
185
+
186
+ def get_activation_key(self) -> str:
187
+ """Get API key from activation endpoint"""
188
+ if self._api_key_cache:
189
+ return self._api_key_cache
190
+
191
+ try:
192
+ response = self.session.get(
193
+ self.activation_endpoint,
194
+ headers={"Accept": "application/json"},
195
+ timeout=30
196
+ )
197
+ response.raise_for_status()
198
+ activation_data = response.json()
199
+ self._api_key_cache = activation_data["openAIParams"]["apiKey"]
200
+ return self._api_key_cache
201
+ except Exception as e:
202
+ raise exceptions.FailedToGenerateResponseError(f"Failed to get activation key: {e}")
203
+
204
+ def ask(
205
+ self,
206
+ prompt: str,
207
+ stream: bool = False,
208
+ raw: bool = False,
209
+ optimizer: str = None,
210
+ conversationally: bool = False,
211
+ ) -> Union[Dict[str, Any], Generator]:
212
+ """
213
+ Sends a prompt to the TogetherAI API and returns the response.
214
+ """
215
+ conversation_prompt = self.conversation.gen_complete_prompt(prompt)
216
+ if optimizer:
217
+ if optimizer in self.__available_optimizers:
218
+ conversation_prompt = getattr(Optimizers, optimizer)(
219
+ conversation_prompt if conversationally else prompt
220
+ )
221
+ else:
222
+ raise Exception(f"Optimizer is not one of {self.__available_optimizers}")
223
+
224
+ # Get API key if not already set
225
+ if not self.headers.get("Authorization"):
226
+ api_key = self.get_activation_key()
227
+ self.headers["Authorization"] = f"Bearer {api_key}"
228
+ self.session.headers.update(self.headers)
229
+
230
+ # Payload construction
231
+ payload = {
232
+ "model": self.model,
233
+ "messages": [
234
+ {"role": "system", "content": self.system_prompt},
235
+ {"role": "user", "content": conversation_prompt},
236
+ ],
237
+ "stream": stream
238
+ }
239
+
240
+ def for_stream():
241
+ streaming_text = ""
242
+ try:
243
+ response = self.session.post(
244
+ self.api_endpoint,
245
+ json=payload,
246
+ stream=True,
247
+ timeout=self.timeout,
248
+ impersonate="chrome110"
249
+ )
250
+ response.raise_for_status()
251
+
252
+ # Use sanitize_stream
253
+ processed_stream = sanitize_stream(
254
+ data=response.iter_content(chunk_size=None),
255
+ intro_value="data:",
256
+ to_json=True,
257
+ skip_markers=["[DONE]"],
258
+ content_extractor=self._togetherai_extractor,
259
+ yield_raw_on_error=False
260
+ )
261
+
262
+ for content_chunk in processed_stream:
263
+ if content_chunk and isinstance(content_chunk, str):
264
+ streaming_text += content_chunk
265
+ resp = dict(text=content_chunk)
266
+ yield resp if not raw else content_chunk
267
+
268
+ except CurlError as e:
269
+ raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {str(e)}") from e
270
+ except Exception as e:
271
+ raise exceptions.FailedToGenerateResponseError(f"Request failed ({type(e).__name__}): {str(e)}") from e
272
+ finally:
273
+ if streaming_text:
274
+ self.last_response = {"text": streaming_text}
275
+ self.conversation.update_chat_history(prompt, streaming_text)
276
+
277
+ def for_non_stream():
278
+ try:
279
+ response = self.session.post(
280
+ self.api_endpoint,
281
+ json=payload,
282
+ timeout=self.timeout,
283
+ impersonate="chrome110"
284
+ )
285
+ response.raise_for_status()
286
+
287
+ response_text = response.text
288
+
289
+ # Use sanitize_stream to parse the non-streaming JSON response
290
+ processed_stream = sanitize_stream(
291
+ data=response_text,
292
+ to_json=True,
293
+ intro_value=None,
294
+ content_extractor=lambda chunk: chunk.get("choices", [{}])[0].get("message", {}).get("content") if isinstance(chunk, dict) else None,
295
+ yield_raw_on_error=False
296
+ )
297
+ content = next(processed_stream, None)
298
+ content = content if isinstance(content, str) else ""
299
+
300
+ self.last_response = {"text": content}
301
+ self.conversation.update_chat_history(prompt, content)
302
+ return self.last_response if not raw else content
303
+
304
+ except CurlError as e:
305
+ raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {e}") from e
306
+ except Exception as e:
307
+ err_text = getattr(e, 'response', None) and getattr(e.response, 'text', '')
308
+ raise exceptions.FailedToGenerateResponseError(f"Request failed ({type(e).__name__}): {e} - {err_text}") from e
309
+
310
+ return for_stream() if stream else for_non_stream()
311
+
312
+ def chat(
313
+ self,
314
+ prompt: str,
315
+ stream: bool = False,
316
+ optimizer: str = None,
317
+ conversationally: bool = False,
318
+ ) -> Union[str, Generator[str, None, None]]:
319
+ """Generate response `str`"""
320
+ def for_stream_chat():
321
+ gen = self.ask(
322
+ prompt, stream=True, raw=False,
323
+ optimizer=optimizer, conversationally=conversationally
324
+ )
325
+ for response_dict in gen:
326
+ yield self.get_message(response_dict)
327
+
328
+ def for_non_stream_chat():
329
+ response_data = self.ask(
330
+ prompt, stream=False, raw=False,
331
+ optimizer=optimizer, conversationally=conversationally
332
+ )
333
+ return self.get_message(response_data)
334
+
335
+ return for_stream_chat() if stream else for_non_stream_chat()
336
+
337
+ def get_message(self, response: dict) -> str:
338
+ """Retrieves message only from response"""
339
+ assert isinstance(response, dict), "Response should be of dict data-type only"
340
+ return response["text"]
341
+
342
+
343
+ if __name__ == "__main__":
344
+ print("-" * 80)
345
+ print(f"{'Model':<50} {'Status':<10} {'Response'}")
346
+ print("-" * 80)
347
+
348
+ for model in TogetherAI.AVAILABLE_MODELS:
349
+ try:
350
+ test_ai = TogetherAI(model=model, timeout=60)
351
+ response = test_ai.chat("Say 'Hello' in one word", stream=True)
352
+ response_text = ""
353
+ for chunk in response:
354
+ response_text += chunk
355
+
356
+ if response_text and len(response_text.strip()) > 0:
357
+ status = "✓"
358
+ # Clean and truncate response
359
+ clean_text = response_text.strip().encode('utf-8', errors='ignore').decode('utf-8')
360
+ display_text = clean_text[:50] + "..." if len(clean_text) > 50 else clean_text
361
+ else:
362
+ status = "✗"
363
+ display_text = "Empty or invalid response"
364
+ print(f"\r{model:<50} {status:<10} {display_text}")
365
+ except Exception as e:
366
+ print(f"\r{model:<50} {'✗':<10} {str(e)}")