webscout 8.2.8__py3-none-any.whl → 8.2.9__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.
- webscout/AIauto.py +32 -14
- webscout/AIbase.py +96 -37
- webscout/AIutel.py +491 -87
- webscout/Bard.py +441 -323
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/YTToolkit/ytapi/video.py +232 -232
- webscout/Litlogger/README.md +10 -0
- webscout/Litlogger/__init__.py +7 -59
- webscout/Litlogger/formats.py +4 -0
- webscout/Litlogger/handlers.py +103 -0
- webscout/Litlogger/levels.py +13 -0
- webscout/Litlogger/logger.py +92 -0
- webscout/Provider/AISEARCH/Perplexity.py +332 -358
- webscout/Provider/AISEARCH/felo_search.py +9 -35
- webscout/Provider/AISEARCH/genspark_search.py +30 -56
- webscout/Provider/AISEARCH/hika_search.py +4 -16
- webscout/Provider/AISEARCH/iask_search.py +410 -436
- webscout/Provider/AISEARCH/monica_search.py +4 -30
- webscout/Provider/AISEARCH/scira_search.py +6 -32
- webscout/Provider/AISEARCH/webpilotai_search.py +38 -64
- webscout/Provider/Blackboxai.py +153 -35
- webscout/Provider/Deepinfra.py +339 -339
- webscout/Provider/ExaChat.py +358 -358
- webscout/Provider/Gemini.py +169 -169
- webscout/Provider/GithubChat.py +1 -2
- webscout/Provider/Glider.py +3 -3
- webscout/Provider/HeckAI.py +171 -81
- webscout/Provider/OPENAI/BLACKBOXAI.py +766 -735
- webscout/Provider/OPENAI/Cloudflare.py +7 -7
- webscout/Provider/OPENAI/FreeGemini.py +6 -5
- webscout/Provider/OPENAI/NEMOTRON.py +8 -20
- webscout/Provider/OPENAI/Qwen3.py +283 -0
- webscout/Provider/OPENAI/README.md +952 -1253
- webscout/Provider/OPENAI/TwoAI.py +357 -0
- webscout/Provider/OPENAI/__init__.py +5 -1
- webscout/Provider/OPENAI/ai4chat.py +40 -40
- webscout/Provider/OPENAI/api.py +808 -649
- webscout/Provider/OPENAI/c4ai.py +3 -3
- webscout/Provider/OPENAI/chatgpt.py +555 -555
- webscout/Provider/OPENAI/chatgptclone.py +493 -487
- webscout/Provider/OPENAI/chatsandbox.py +4 -3
- webscout/Provider/OPENAI/copilot.py +242 -0
- webscout/Provider/OPENAI/deepinfra.py +5 -2
- webscout/Provider/OPENAI/e2b.py +63 -5
- webscout/Provider/OPENAI/exaai.py +416 -410
- webscout/Provider/OPENAI/exachat.py +444 -443
- webscout/Provider/OPENAI/freeaichat.py +2 -2
- webscout/Provider/OPENAI/glider.py +5 -2
- webscout/Provider/OPENAI/groq.py +5 -2
- webscout/Provider/OPENAI/heckai.py +308 -307
- webscout/Provider/OPENAI/mcpcore.py +8 -2
- webscout/Provider/OPENAI/multichat.py +4 -4
- webscout/Provider/OPENAI/netwrck.py +6 -5
- webscout/Provider/OPENAI/oivscode.py +287 -0
- webscout/Provider/OPENAI/opkfc.py +496 -496
- webscout/Provider/OPENAI/pydantic_imports.py +172 -0
- webscout/Provider/OPENAI/scirachat.py +15 -9
- webscout/Provider/OPENAI/sonus.py +304 -303
- webscout/Provider/OPENAI/standardinput.py +433 -433
- webscout/Provider/OPENAI/textpollinations.py +4 -4
- webscout/Provider/OPENAI/toolbaz.py +413 -413
- webscout/Provider/OPENAI/typefully.py +3 -3
- webscout/Provider/OPENAI/typegpt.py +11 -5
- webscout/Provider/OPENAI/uncovrAI.py +463 -462
- webscout/Provider/OPENAI/utils.py +90 -79
- webscout/Provider/OPENAI/venice.py +431 -425
- webscout/Provider/OPENAI/wisecat.py +387 -381
- webscout/Provider/OPENAI/writecream.py +3 -3
- webscout/Provider/OPENAI/x0gpt.py +365 -378
- webscout/Provider/OPENAI/yep.py +39 -13
- webscout/Provider/TTI/README.md +55 -101
- webscout/Provider/TTI/__init__.py +4 -9
- webscout/Provider/TTI/aiarta.py +365 -0
- webscout/Provider/TTI/artbit.py +0 -0
- webscout/Provider/TTI/base.py +64 -0
- webscout/Provider/TTI/fastflux.py +200 -0
- webscout/Provider/TTI/magicstudio.py +201 -0
- webscout/Provider/TTI/piclumen.py +203 -0
- webscout/Provider/TTI/pixelmuse.py +225 -0
- webscout/Provider/TTI/pollinations.py +221 -0
- webscout/Provider/TTI/utils.py +11 -0
- webscout/Provider/TTS/__init__.py +2 -1
- webscout/Provider/TTS/base.py +159 -159
- webscout/Provider/TTS/openai_fm.py +129 -0
- webscout/Provider/TextPollinationsAI.py +308 -308
- webscout/Provider/TwoAI.py +239 -44
- webscout/Provider/UNFINISHED/Youchat.py +330 -330
- webscout/Provider/UNFINISHED/puterjs.py +635 -0
- webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
- webscout/Provider/Writecream.py +246 -246
- webscout/Provider/__init__.py +2 -0
- webscout/Provider/ai4chat.py +33 -8
- webscout/Provider/koala.py +169 -169
- webscout/Provider/oivscode.py +309 -0
- webscout/Provider/samurai.py +3 -2
- webscout/Provider/typegpt.py +3 -3
- webscout/Provider/uncovr.py +368 -368
- webscout/client.py +70 -0
- webscout/litprinter/__init__.py +58 -58
- webscout/optimizers.py +419 -419
- webscout/scout/README.md +3 -1
- webscout/scout/core/crawler.py +134 -64
- webscout/scout/core/scout.py +148 -109
- webscout/scout/element.py +106 -88
- webscout/swiftcli/Readme.md +323 -323
- webscout/swiftcli/plugins/manager.py +9 -2
- webscout/version.py +1 -1
- webscout/zeroart/__init__.py +134 -134
- webscout/zeroart/effects.py +100 -100
- webscout/zeroart/fonts.py +1238 -1238
- {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/METADATA +159 -35
- {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/RECORD +116 -161
- {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/WHEEL +1 -1
- {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/entry_points.txt +1 -0
- webscout/Litlogger/Readme.md +0 -175
- webscout/Litlogger/core/__init__.py +0 -6
- webscout/Litlogger/core/level.py +0 -23
- webscout/Litlogger/core/logger.py +0 -165
- webscout/Litlogger/handlers/__init__.py +0 -12
- webscout/Litlogger/handlers/console.py +0 -33
- webscout/Litlogger/handlers/file.py +0 -143
- webscout/Litlogger/handlers/network.py +0 -173
- webscout/Litlogger/styles/__init__.py +0 -7
- webscout/Litlogger/styles/colors.py +0 -249
- webscout/Litlogger/styles/formats.py +0 -458
- webscout/Litlogger/styles/text.py +0 -87
- webscout/Litlogger/utils/__init__.py +0 -6
- webscout/Litlogger/utils/detectors.py +0 -153
- webscout/Litlogger/utils/formatters.py +0 -200
- webscout/Provider/TTI/AiForce/README.md +0 -159
- webscout/Provider/TTI/AiForce/__init__.py +0 -22
- webscout/Provider/TTI/AiForce/async_aiforce.py +0 -224
- webscout/Provider/TTI/AiForce/sync_aiforce.py +0 -245
- webscout/Provider/TTI/FreeAIPlayground/README.md +0 -99
- webscout/Provider/TTI/FreeAIPlayground/__init__.py +0 -9
- webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +0 -181
- webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +0 -180
- webscout/Provider/TTI/ImgSys/README.md +0 -174
- webscout/Provider/TTI/ImgSys/__init__.py +0 -23
- webscout/Provider/TTI/ImgSys/async_imgsys.py +0 -202
- webscout/Provider/TTI/ImgSys/sync_imgsys.py +0 -195
- webscout/Provider/TTI/MagicStudio/README.md +0 -101
- webscout/Provider/TTI/MagicStudio/__init__.py +0 -2
- webscout/Provider/TTI/MagicStudio/async_magicstudio.py +0 -111
- webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +0 -109
- webscout/Provider/TTI/Nexra/README.md +0 -155
- webscout/Provider/TTI/Nexra/__init__.py +0 -22
- webscout/Provider/TTI/Nexra/async_nexra.py +0 -286
- webscout/Provider/TTI/Nexra/sync_nexra.py +0 -258
- webscout/Provider/TTI/PollinationsAI/README.md +0 -146
- webscout/Provider/TTI/PollinationsAI/__init__.py +0 -23
- webscout/Provider/TTI/PollinationsAI/async_pollinations.py +0 -311
- webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +0 -265
- webscout/Provider/TTI/aiarta/README.md +0 -134
- webscout/Provider/TTI/aiarta/__init__.py +0 -2
- webscout/Provider/TTI/aiarta/async_aiarta.py +0 -482
- webscout/Provider/TTI/aiarta/sync_aiarta.py +0 -440
- webscout/Provider/TTI/artbit/README.md +0 -100
- webscout/Provider/TTI/artbit/__init__.py +0 -22
- webscout/Provider/TTI/artbit/async_artbit.py +0 -155
- webscout/Provider/TTI/artbit/sync_artbit.py +0 -148
- webscout/Provider/TTI/fastflux/README.md +0 -129
- webscout/Provider/TTI/fastflux/__init__.py +0 -22
- webscout/Provider/TTI/fastflux/async_fastflux.py +0 -261
- webscout/Provider/TTI/fastflux/sync_fastflux.py +0 -252
- webscout/Provider/TTI/huggingface/README.md +0 -114
- webscout/Provider/TTI/huggingface/__init__.py +0 -22
- webscout/Provider/TTI/huggingface/async_huggingface.py +0 -199
- webscout/Provider/TTI/huggingface/sync_huggingface.py +0 -195
- webscout/Provider/TTI/piclumen/README.md +0 -161
- webscout/Provider/TTI/piclumen/__init__.py +0 -23
- webscout/Provider/TTI/piclumen/async_piclumen.py +0 -268
- webscout/Provider/TTI/piclumen/sync_piclumen.py +0 -233
- webscout/Provider/TTI/pixelmuse/README.md +0 -79
- webscout/Provider/TTI/pixelmuse/__init__.py +0 -4
- webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +0 -249
- webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +0 -182
- webscout/Provider/TTI/talkai/README.md +0 -139
- webscout/Provider/TTI/talkai/__init__.py +0 -4
- webscout/Provider/TTI/talkai/async_talkai.py +0 -229
- webscout/Provider/TTI/talkai/sync_talkai.py +0 -207
- webscout/Provider/UNFINISHED/oivscode.py +0 -351
- {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/top_level.txt +0 -0
webscout/Provider/TwoAI.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from curl_cffi.requests import Session
|
|
2
2
|
from curl_cffi import CurlError
|
|
3
3
|
import json
|
|
4
|
+
import base64
|
|
5
|
+
import time
|
|
4
6
|
from typing import Any, Dict, Optional, Generator, Union
|
|
5
7
|
import re # Import re for parsing SSE
|
|
8
|
+
import urllib.parse
|
|
6
9
|
|
|
7
10
|
from webscout.AIutel import Optimizers
|
|
8
11
|
from webscout.AIutel import Conversation
|
|
@@ -10,22 +13,153 @@ from webscout.AIutel import AwesomePrompts, sanitize_stream # Import sanitize_st
|
|
|
10
13
|
from webscout.AIbase import Provider
|
|
11
14
|
from webscout import exceptions
|
|
12
15
|
from webscout.litagent import LitAgent
|
|
16
|
+
from webscout.Extra.tempmail import get_random_email
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
class TwoAI(Provider):
|
|
16
20
|
"""
|
|
17
21
|
A class to interact with the Two AI API (v2) with LitAgent user-agent.
|
|
22
|
+
SUTRA is a family of large multi-lingual language models (LMLMs) developed by TWO AI.
|
|
23
|
+
SUTRA's dual-transformer extends the power of both MoE and Dense AI language model architectures,
|
|
24
|
+
delivering cost-efficient multilingual capabilities for over 50+ languages.
|
|
25
|
+
|
|
26
|
+
API keys can be generated using the generate_api_key() method, which uses a temporary email
|
|
27
|
+
to register for the Two AI service and extract the API key from the confirmation email.
|
|
18
28
|
"""
|
|
19
29
|
|
|
20
30
|
AVAILABLE_MODELS = [
|
|
21
|
-
"sutra-v2",
|
|
22
|
-
"sutra-r0"
|
|
23
|
-
|
|
31
|
+
"sutra-v2", # Multilingual AI model for instruction execution and conversational intelligence
|
|
32
|
+
"sutra-r0", # Advanced reasoning model for complex problem-solving and deep contextual understanding
|
|
24
33
|
]
|
|
25
34
|
|
|
35
|
+
@staticmethod
|
|
36
|
+
def generate_api_key() -> str:
|
|
37
|
+
"""
|
|
38
|
+
Generate a new Two AI API key using a temporary email.
|
|
39
|
+
|
|
40
|
+
This method:
|
|
41
|
+
1. Creates a temporary email using webscout's tempmail module
|
|
42
|
+
2. Registers for Two AI using the Loops.so newsletter form
|
|
43
|
+
3. Waits for and extracts the API key from the confirmation email
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
str: The generated API key
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
Exception: If the API key cannot be generated
|
|
50
|
+
"""
|
|
51
|
+
# Get a temporary email
|
|
52
|
+
email, provider = get_random_email("tempmailio")
|
|
53
|
+
|
|
54
|
+
# Register for Two AI using the Loops.so newsletter form
|
|
55
|
+
loops_url = "https://app.loops.so/api/newsletter-form/cm7i4o92h057auy1o74cxbhxo"
|
|
56
|
+
|
|
57
|
+
# Create a session with appropriate headers
|
|
58
|
+
session = Session()
|
|
59
|
+
session.headers.update({
|
|
60
|
+
'User-Agent': LitAgent().random(),
|
|
61
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
62
|
+
'Origin': 'https://www.two.ai',
|
|
63
|
+
'Referer': 'https://app.loops.so/',
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
# Prepare form data
|
|
67
|
+
form_data = {
|
|
68
|
+
'email': email,
|
|
69
|
+
'userGroup': 'Via Framer',
|
|
70
|
+
'mailingLists': 'cm8ay9cic00x70kjv0bd34k66'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Send the registration request
|
|
74
|
+
encoded_data = urllib.parse.urlencode(form_data)
|
|
75
|
+
response = session.post(loops_url, data=encoded_data, impersonate="chrome120")
|
|
76
|
+
|
|
77
|
+
if response.status_code != 200:
|
|
78
|
+
raise Exception(f"Failed to register for Two AI: {response.status_code} - {response.text}")
|
|
79
|
+
|
|
80
|
+
# Wait for the confirmation email and extract the API key
|
|
81
|
+
max_attempts = 5
|
|
82
|
+
attempt = 0
|
|
83
|
+
api_key = None
|
|
84
|
+
wait_time = 2
|
|
85
|
+
|
|
86
|
+
while attempt < max_attempts and not api_key:
|
|
87
|
+
messages = provider.get_messages()
|
|
88
|
+
|
|
89
|
+
for message in messages:
|
|
90
|
+
# Check if this is likely the confirmation email based on subject and sender
|
|
91
|
+
subject = message.get('subject', '')
|
|
92
|
+
sender = ''
|
|
93
|
+
|
|
94
|
+
# Try to get the sender from different possible fields
|
|
95
|
+
if 'from' in message:
|
|
96
|
+
if isinstance(message['from'], dict):
|
|
97
|
+
sender = message['from'].get('address', '')
|
|
98
|
+
else:
|
|
99
|
+
sender = str(message['from'])
|
|
100
|
+
elif 'sender' in message:
|
|
101
|
+
if isinstance(message['sender'], dict):
|
|
102
|
+
sender = message['sender'].get('address', '')
|
|
103
|
+
else:
|
|
104
|
+
sender = str(message['sender'])
|
|
105
|
+
|
|
106
|
+
# Look for keywords in the subject that indicate this is the confirmation email
|
|
107
|
+
subject_match = any(keyword in subject.lower() for keyword in
|
|
108
|
+
['welcome', 'confirm', 'verify', 'api', 'key', 'sutra', 'two.ai', 'loops'])
|
|
109
|
+
|
|
110
|
+
# Look for keywords in the sender that indicate this is from Two AI or Loops
|
|
111
|
+
sender_match = any(keyword in sender.lower() for keyword in
|
|
112
|
+
['two.ai', 'sutra', 'loops.so', 'loops', 'no-reply', 'noreply'])
|
|
113
|
+
|
|
114
|
+
is_confirmation = subject_match or sender_match
|
|
115
|
+
|
|
116
|
+
if is_confirmation:
|
|
117
|
+
pass
|
|
118
|
+
# Try to get the message content from various possible fields
|
|
119
|
+
content = None
|
|
120
|
+
|
|
121
|
+
# Check for body field (seen in the debug output)
|
|
122
|
+
if 'body' in message:
|
|
123
|
+
content = message['body']
|
|
124
|
+
# Check for content.text field
|
|
125
|
+
elif 'content' in message and 'text' in message['content']:
|
|
126
|
+
content = message['content']['text']
|
|
127
|
+
# Check for html field
|
|
128
|
+
elif 'html' in message:
|
|
129
|
+
content = message['html']
|
|
130
|
+
# Check for text field
|
|
131
|
+
elif 'text' in message:
|
|
132
|
+
content = message['text']
|
|
133
|
+
|
|
134
|
+
if not content:
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
# Look for the API key pattern in the email content
|
|
138
|
+
# First, try to find the API key directly
|
|
139
|
+
api_key_match = re.search(r'sutra_[A-Za-z0-9]{60,70}', content)
|
|
140
|
+
|
|
141
|
+
# If not found, try looking for the key with the label
|
|
142
|
+
if not api_key_match:
|
|
143
|
+
key_section_match = re.search(r'🔑 SUTRA API Key\s*([^\s]+)', content)
|
|
144
|
+
if key_section_match:
|
|
145
|
+
api_key_match = re.search(r'(sutra_[A-Za-z0-9]+)', key_section_match.group(1))
|
|
146
|
+
|
|
147
|
+
# If still not found, try a more general pattern
|
|
148
|
+
if not api_key_match:
|
|
149
|
+
api_key_match = re.search(r'sutra_\S+', content)
|
|
150
|
+
|
|
151
|
+
if api_key_match:
|
|
152
|
+
api_key = api_key_match.group(0)
|
|
153
|
+
break
|
|
154
|
+
if not api_key:
|
|
155
|
+
attempt += 1
|
|
156
|
+
time.sleep(wait_time)
|
|
157
|
+
if not api_key:
|
|
158
|
+
raise Exception("Failed to get API key from confirmation email")
|
|
159
|
+
return api_key
|
|
160
|
+
|
|
26
161
|
def __init__(
|
|
27
162
|
self,
|
|
28
|
-
api_key: str = None,
|
|
29
163
|
is_conversation: bool = True,
|
|
30
164
|
max_tokens: int = 1024,
|
|
31
165
|
timeout: int = 30,
|
|
@@ -35,19 +169,39 @@ class TwoAI(Provider):
|
|
|
35
169
|
proxies: dict = {},
|
|
36
170
|
history_offset: int = 10250,
|
|
37
171
|
act: str = None,
|
|
38
|
-
model: str = "sutra-v2", #
|
|
172
|
+
model: str = "sutra-v2", # Default model
|
|
39
173
|
temperature: float = 0.6,
|
|
40
174
|
system_message: str = "You are a helpful assistant."
|
|
41
175
|
):
|
|
42
|
-
"""
|
|
176
|
+
"""
|
|
177
|
+
Initializes the TwoAI API client.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
is_conversation: Whether to maintain conversation history.
|
|
181
|
+
max_tokens: Maximum number of tokens to generate.
|
|
182
|
+
timeout: Request timeout in seconds.
|
|
183
|
+
intro: Introduction text for the conversation.
|
|
184
|
+
filepath: Path to save conversation history.
|
|
185
|
+
update_file: Whether to update the conversation history file.
|
|
186
|
+
proxies: Proxy configuration for requests.
|
|
187
|
+
history_offset: Maximum history length in characters.
|
|
188
|
+
act: Persona for the conversation.
|
|
189
|
+
model: Model to use. Must be one of AVAILABLE_MODELS.
|
|
190
|
+
temperature: Temperature for generation (0.0 to 1.0).
|
|
191
|
+
system_message: System message to use for the conversation.
|
|
192
|
+
"""
|
|
43
193
|
if model not in self.AVAILABLE_MODELS:
|
|
44
194
|
raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
|
|
45
|
-
|
|
195
|
+
|
|
196
|
+
# Always auto-generate API key
|
|
197
|
+
api_key = self.generate_api_key()
|
|
198
|
+
|
|
199
|
+
self.url = "https://api.two.ai/v2/chat/completions" # API endpoint
|
|
46
200
|
self.headers = {
|
|
47
201
|
'User-Agent': LitAgent().random(),
|
|
48
|
-
'Accept': '
|
|
202
|
+
'Accept': 'text/event-stream', # For streaming responses
|
|
49
203
|
'Content-Type': 'application/json',
|
|
50
|
-
'
|
|
204
|
+
'Authorization': f'Bearer {api_key}', # Using Bearer token authentication
|
|
51
205
|
'Origin': 'https://chat.two.ai',
|
|
52
206
|
'Referer': 'https://api.two.app/'
|
|
53
207
|
}
|
|
@@ -64,6 +218,7 @@ class TwoAI(Provider):
|
|
|
64
218
|
self.model = model
|
|
65
219
|
self.temperature = temperature
|
|
66
220
|
self.system_message = system_message
|
|
221
|
+
self.api_key = api_key
|
|
67
222
|
|
|
68
223
|
self.__available_optimizers = (
|
|
69
224
|
method
|
|
@@ -96,6 +251,19 @@ class TwoAI(Provider):
|
|
|
96
251
|
content = delta.get("content")
|
|
97
252
|
return content if isinstance(content, str) else None
|
|
98
253
|
|
|
254
|
+
def encode_image(self, image_path: str) -> str:
|
|
255
|
+
"""
|
|
256
|
+
Encode an image file to base64 string.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
image_path: Path to the image file
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Base64 encoded string of the image
|
|
263
|
+
"""
|
|
264
|
+
with open(image_path, "rb") as image_file:
|
|
265
|
+
return base64.b64encode(image_file.read()).decode('utf-8')
|
|
266
|
+
|
|
99
267
|
def ask(
|
|
100
268
|
self,
|
|
101
269
|
prompt: str,
|
|
@@ -104,6 +272,7 @@ class TwoAI(Provider):
|
|
|
104
272
|
optimizer: str = None,
|
|
105
273
|
conversationally: bool = False,
|
|
106
274
|
online_search: bool = True,
|
|
275
|
+
image_path: str = None,
|
|
107
276
|
) -> Union[Dict[str, Any], Generator]:
|
|
108
277
|
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
|
109
278
|
if optimizer:
|
|
@@ -112,14 +281,36 @@ class TwoAI(Provider):
|
|
|
112
281
|
else:
|
|
113
282
|
raise Exception(f"Optimizer is not one of {self.__available_optimizers}")
|
|
114
283
|
|
|
284
|
+
# Prepare messages with image if provided
|
|
285
|
+
if image_path:
|
|
286
|
+
# Create a message with image content
|
|
287
|
+
image_content = {
|
|
288
|
+
"type": "image_url",
|
|
289
|
+
"image_url": {
|
|
290
|
+
"url": f"data:image/jpeg;base64,{self.encode_image(image_path)}"
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
user_message = {
|
|
294
|
+
"role": "user",
|
|
295
|
+
"content": [
|
|
296
|
+
{"type": "text", "text": conversation_prompt},
|
|
297
|
+
image_content
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
else:
|
|
301
|
+
# Text-only message
|
|
302
|
+
user_message = {"role": "user", "content": conversation_prompt}
|
|
303
|
+
|
|
304
|
+
# Prepare the payload
|
|
115
305
|
payload = {
|
|
116
306
|
"messages": [
|
|
117
307
|
*([{"role": "system", "content": self.system_message}] if self.system_message else []),
|
|
118
|
-
|
|
308
|
+
user_message
|
|
119
309
|
],
|
|
120
310
|
"model": self.model,
|
|
121
311
|
"temperature": self.temperature,
|
|
122
312
|
"max_tokens": self.max_tokens_to_sample,
|
|
313
|
+
"stream": stream,
|
|
123
314
|
"extra_body": {
|
|
124
315
|
"online_search": online_search,
|
|
125
316
|
}
|
|
@@ -208,6 +399,7 @@ class TwoAI(Provider):
|
|
|
208
399
|
optimizer: str = None,
|
|
209
400
|
conversationally: bool = False,
|
|
210
401
|
online_search: bool = True,
|
|
402
|
+
image_path: str = None,
|
|
211
403
|
) -> str:
|
|
212
404
|
effective_stream = stream if stream is not None else True
|
|
213
405
|
|
|
@@ -220,6 +412,7 @@ class TwoAI(Provider):
|
|
|
220
412
|
optimizer=optimizer,
|
|
221
413
|
conversationally=conversationally,
|
|
222
414
|
online_search=online_search,
|
|
415
|
+
image_path=image_path,
|
|
223
416
|
)
|
|
224
417
|
for response_dict in gen:
|
|
225
418
|
yield self.get_message(response_dict) # get_message expects dict
|
|
@@ -233,6 +426,7 @@ class TwoAI(Provider):
|
|
|
233
426
|
optimizer=optimizer,
|
|
234
427
|
conversationally=conversationally,
|
|
235
428
|
online_search=online_search,
|
|
429
|
+
image_path=image_path,
|
|
236
430
|
)
|
|
237
431
|
return self.get_message(response_dict) # get_message expects dict
|
|
238
432
|
|
|
@@ -244,37 +438,38 @@ class TwoAI(Provider):
|
|
|
244
438
|
|
|
245
439
|
|
|
246
440
|
if __name__ == "__main__":
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
441
|
+
print("-" * 80)
|
|
442
|
+
print(f"{'Model':<50} {'Status':<10} {'Response'}")
|
|
443
|
+
print("-" * 80)
|
|
444
|
+
|
|
445
|
+
for model in TwoAI.AVAILABLE_MODELS:
|
|
446
|
+
try:
|
|
447
|
+
test_ai = TwoAI(model=model, timeout=60)
|
|
448
|
+
# Test stream first
|
|
449
|
+
response_stream = test_ai.chat("Say 'Hello' in one word", stream=True)
|
|
450
|
+
response_text = ""
|
|
451
|
+
print(f"\r{model:<50} {'Streaming...':<10}", end="", flush=True)
|
|
452
|
+
for chunk in response_stream:
|
|
453
|
+
response_text += chunk
|
|
454
|
+
# Optional: print chunks as they arrive for visual feedback
|
|
455
|
+
# print(chunk, end="", flush=True)
|
|
456
|
+
|
|
457
|
+
if response_text and len(response_text.strip()) > 0:
|
|
458
|
+
status = "✓"
|
|
459
|
+
# Clean and truncate response
|
|
460
|
+
clean_text = response_text.strip() # Already decoded in get_message
|
|
461
|
+
display_text = clean_text[:50] + "..." if len(clean_text) > 50 else clean_text
|
|
462
|
+
else:
|
|
463
|
+
status = "✗ (Stream)"
|
|
464
|
+
display_text = "Empty or invalid stream response"
|
|
465
|
+
print(f"\r{model:<50} {status:<10} {display_text}")
|
|
466
|
+
|
|
467
|
+
# Optional: Add non-stream test if needed, but stream test covers basic functionality
|
|
468
|
+
# print(f"\r{model:<50} {'Non-Stream...':<10}", end="", flush=True)
|
|
469
|
+
# response_non_stream = test_ai.chat("Say 'Hi' again", stream=False)
|
|
470
|
+
# if not response_non_stream or len(response_non_stream.strip()) == 0:
|
|
471
|
+
# print(f"\r{model:<50} {'✗ (Non-Stream)':<10} Empty non-stream response")
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
except Exception as e:
|
|
475
|
+
print(f"\r{model:<50} {'✗':<10} {str(e)}")
|