g4f 6.9.8__py3-none-any.whl → 6.9.10__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.
- g4f/Provider/PollinationsAI.py +6 -4
- g4f/Provider/Yupp.py +217 -75
- g4f/Provider/__init__.py +0 -2
- g4f/Provider/local/Ollama.py +3 -2
- g4f/Provider/needs_auth/Azure.py +1 -1
- g4f/Provider/needs_auth/Claude.py +1 -1
- g4f/Provider/needs_auth/GeminiPro.py +1 -1
- g4f/Provider/needs_auth/Groq.py +1 -0
- g4f/Provider/needs_auth/Nvidia.py +1 -0
- g4f/Provider/needs_auth/OpenRouter.py +1 -0
- g4f/client/__init__.py +1 -1
- g4f/gui/server/backend_api.py +1 -1
- g4f/mcp/server.py +112 -0
- g4f/mcp/tools.py +1 -2
- g4f/models.py +1 -4
- {g4f-6.9.8.dist-info → g4f-6.9.10.dist-info}/METADATA +3 -1
- {g4f-6.9.8.dist-info → g4f-6.9.10.dist-info}/RECORD +21 -23
- {g4f-6.9.8.dist-info → g4f-6.9.10.dist-info}/WHEEL +1 -1
- g4f/Provider/Startnest.py +0 -215
- g4f/Provider/StringableInference.py +0 -31
- {g4f-6.9.8.dist-info → g4f-6.9.10.dist-info}/entry_points.txt +0 -0
- {g4f-6.9.8.dist-info → g4f-6.9.10.dist-info}/licenses/LICENSE +0 -0
- {g4f-6.9.8.dist-info → g4f-6.9.10.dist-info}/top_level.txt +0 -0
g4f/Provider/PollinationsAI.py
CHANGED
|
@@ -44,9 +44,9 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
44
44
|
gen_text_api_endpoint = "https://gen.pollinations.ai/v1/chat/completions"
|
|
45
45
|
image_models_endpoint = "https://gen.pollinations.ai/image/models"
|
|
46
46
|
text_models_endpoint = "https://gen.pollinations.ai/text/models"
|
|
47
|
-
balance_endpoint = "https://
|
|
48
|
-
worker_api_endpoint = "https://
|
|
49
|
-
worker_models_endpoint = "https://
|
|
47
|
+
balance_endpoint = "https://g4f.space/api/pollinations/account/balance"
|
|
48
|
+
worker_api_endpoint = "https://g4f.space/api/pollinations/chat/completions"
|
|
49
|
+
worker_models_endpoint = "https://g4f.space/api/pollinations/models"
|
|
50
50
|
|
|
51
51
|
# Models configuration
|
|
52
52
|
default_model = "openai"
|
|
@@ -263,6 +263,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
263
263
|
has_audio = True
|
|
264
264
|
break
|
|
265
265
|
model = "openai-audio" if has_audio else cls.default_model
|
|
266
|
+
if not api_key:
|
|
267
|
+
api_key = AuthManager.load_api_key(cls)
|
|
266
268
|
if cls.get_models(api_key=api_key, timeout=kwargs.get("timeout", 15)):
|
|
267
269
|
if model in cls.model_aliases:
|
|
268
270
|
model = cls.model_aliases[model]
|
|
@@ -379,7 +381,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
379
381
|
return f"{url}&seed={seed}" if seed else url
|
|
380
382
|
|
|
381
383
|
headers = None
|
|
382
|
-
if api_key and api_key.startswith("g4f_") or api_key.startswith("gfs_"):
|
|
384
|
+
if api_key and (api_key.startswith("g4f_") or api_key.startswith("gfs_")):
|
|
383
385
|
headers = {"authorization": f"Bearer {api_key}"}
|
|
384
386
|
async with ClientSession(
|
|
385
387
|
headers=DEFAULT_HEADERS,
|
g4f/Provider/Yupp.py
CHANGED
|
@@ -6,12 +6,15 @@ import re
|
|
|
6
6
|
import time
|
|
7
7
|
import uuid
|
|
8
8
|
from concurrent.futures import ThreadPoolExecutor
|
|
9
|
+
from typing import Optional, Dict, Any, List
|
|
9
10
|
|
|
10
11
|
try:
|
|
11
12
|
import cloudscraper
|
|
12
13
|
from cloudscraper import CloudScraper
|
|
14
|
+
has_cloudscraper = True
|
|
13
15
|
except ImportError:
|
|
14
16
|
from typing import Type as CloudScraper
|
|
17
|
+
has_cloudscraper = False
|
|
15
18
|
|
|
16
19
|
from .helper import get_last_user_message
|
|
17
20
|
from .yupp.models import YuppModelManager
|
|
@@ -20,17 +23,27 @@ from ..debug import log
|
|
|
20
23
|
from ..errors import RateLimitError, ProviderException, MissingAuthError, MissingRequirementsError
|
|
21
24
|
from ..image import is_accepted_format, to_bytes
|
|
22
25
|
from ..providers.base_provider import AsyncGeneratorProvider, ProviderModelMixin
|
|
23
|
-
from ..providers.response import
|
|
24
|
-
|
|
26
|
+
from ..providers.response import (
|
|
27
|
+
Reasoning,
|
|
28
|
+
PlainTextResponse,
|
|
29
|
+
PreviewResponse,
|
|
30
|
+
JsonConversation,
|
|
31
|
+
ImageResponse,
|
|
32
|
+
ProviderInfo,
|
|
33
|
+
FinishReason,
|
|
34
|
+
JsonResponse,
|
|
35
|
+
VariantResponse,
|
|
36
|
+
)
|
|
25
37
|
from ..tools.auth import AuthManager
|
|
26
38
|
from ..tools.media import merge_media
|
|
27
|
-
from ..typing import AsyncResult, Messages
|
|
39
|
+
from ..typing import AsyncResult, Messages
|
|
28
40
|
|
|
29
41
|
YUPP_ACCOUNTS: List[Dict[str, Any]] = []
|
|
30
42
|
account_rotation_lock = asyncio.Lock()
|
|
31
43
|
ImagesCache: Dict[str, dict] = {}
|
|
32
44
|
_accounts_loaded = False
|
|
33
|
-
_executor = ThreadPoolExecutor(max_workers=
|
|
45
|
+
_executor = ThreadPoolExecutor(max_workers=32)
|
|
46
|
+
MAX_CACHE_SIZE = 1000
|
|
34
47
|
|
|
35
48
|
|
|
36
49
|
def create_scraper():
|
|
@@ -87,10 +100,10 @@ async def get_best_yupp_account() -> Optional[Dict[str, Any]]:
|
|
|
87
100
|
acc
|
|
88
101
|
for acc in YUPP_ACCOUNTS
|
|
89
102
|
if acc["is_valid"]
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
and (
|
|
104
|
+
acc["error_count"] < max_error_count
|
|
105
|
+
or now - acc["last_used"] > error_cooldown
|
|
106
|
+
)
|
|
94
107
|
]
|
|
95
108
|
|
|
96
109
|
if not valid_accounts:
|
|
@@ -98,8 +111,8 @@ async def get_best_yupp_account() -> Optional[Dict[str, Any]]:
|
|
|
98
111
|
|
|
99
112
|
for acc in valid_accounts:
|
|
100
113
|
if (
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
acc["error_count"] >= max_error_count
|
|
115
|
+
and now - acc["last_used"] > error_cooldown
|
|
103
116
|
):
|
|
104
117
|
acc["error_count"] = 0
|
|
105
118
|
|
|
@@ -109,11 +122,11 @@ async def get_best_yupp_account() -> Optional[Dict[str, Any]]:
|
|
|
109
122
|
return account
|
|
110
123
|
|
|
111
124
|
|
|
112
|
-
def sync_claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any],
|
|
125
|
+
def sync_claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], eval_id: str):
|
|
113
126
|
try:
|
|
114
|
-
log_debug(f"Claiming reward {
|
|
127
|
+
log_debug(f"Claiming reward {eval_id}...")
|
|
115
128
|
url = "https://yupp.ai/api/trpc/reward.claim?batch=1"
|
|
116
|
-
payload = {"0": {"json": {"
|
|
129
|
+
payload = {"0": {"json": {"evalId": eval_id}}}
|
|
117
130
|
scraper.cookies.set("__Secure-yupp.session-token", account['token'])
|
|
118
131
|
response = scraper.post(url, json=payload)
|
|
119
132
|
response.raise_for_status()
|
|
@@ -122,13 +135,104 @@ def sync_claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], rewar
|
|
|
122
135
|
log_debug(f"Reward claimed successfully. New balance: {balance}")
|
|
123
136
|
return balance
|
|
124
137
|
except Exception as e:
|
|
125
|
-
log_debug(f"Failed to claim reward {
|
|
138
|
+
log_debug(f"Failed to claim reward {eval_id}. Error: {e}")
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], eval_id: str):
|
|
143
|
+
loop = asyncio.get_event_loop()
|
|
144
|
+
return await loop.run_in_executor(_executor, sync_claim_yupp_reward, scraper, account, eval_id)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def sync_record_model_feedback(
|
|
148
|
+
scraper: CloudScraper,
|
|
149
|
+
account: Dict[str, Any],
|
|
150
|
+
turn_id: str,
|
|
151
|
+
left_message_id: str,
|
|
152
|
+
right_message_id: str
|
|
153
|
+
) -> Optional[str]:
|
|
154
|
+
try:
|
|
155
|
+
log_debug(f"Recording model feedback for turn {turn_id}...")
|
|
156
|
+
url = "https://yupp.ai/api/trpc/evals.recordModelFeedback?batch=1"
|
|
157
|
+
payload = {
|
|
158
|
+
"0": {
|
|
159
|
+
"json": {
|
|
160
|
+
"turnId": turn_id,
|
|
161
|
+
"evalType": "SELECTION",
|
|
162
|
+
"messageEvals": [
|
|
163
|
+
{
|
|
164
|
+
"messageId": right_message_id,
|
|
165
|
+
"rating": "GOOD",
|
|
166
|
+
"reasons": ["Fast"]
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"messageId": left_message_id,
|
|
170
|
+
"rating": "BAD",
|
|
171
|
+
"reasons": []
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
"comment": "",
|
|
175
|
+
"requireReveal": False
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
scraper.cookies.set("__Secure-yupp.session-token", account['token'])
|
|
180
|
+
response = scraper.post(url, json=payload)
|
|
181
|
+
response.raise_for_status()
|
|
182
|
+
data = response.json()
|
|
183
|
+
|
|
184
|
+
for result in data:
|
|
185
|
+
json_data = result.get("result", {}).get("data", {}).get("json", {})
|
|
186
|
+
eval_id = json_data.get("evalId")
|
|
187
|
+
final_reward = json_data.get("finalRewardAmount")
|
|
188
|
+
log_debug(f"Feedback recorded - evalId: {eval_id}, reward: {final_reward}")
|
|
189
|
+
|
|
190
|
+
if final_reward:
|
|
191
|
+
return eval_id
|
|
192
|
+
return None
|
|
193
|
+
except Exception as e:
|
|
194
|
+
log_debug(f"Failed to record model feedback. Error: {e}")
|
|
126
195
|
return None
|
|
127
196
|
|
|
128
197
|
|
|
129
|
-
async def
|
|
198
|
+
async def record_model_feedback(
|
|
199
|
+
scraper: CloudScraper,
|
|
200
|
+
account: Dict[str, Any],
|
|
201
|
+
turn_id: str,
|
|
202
|
+
left_message_id: str,
|
|
203
|
+
right_message_id: str
|
|
204
|
+
) -> Optional[str]:
|
|
205
|
+
loop = asyncio.get_event_loop()
|
|
206
|
+
return await loop.run_in_executor(
|
|
207
|
+
_executor, sync_record_model_feedback, scraper, account, turn_id, left_message_id, right_message_id
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def sync_delete_chat(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
|
|
212
|
+
try:
|
|
213
|
+
log_debug(f"Deleting chat {chat_id}...")
|
|
214
|
+
url = "https://yupp.ai/api/trpc/chat.deleteChat?batch=1"
|
|
215
|
+
payload = {"0": {"json": {"chatId": chat_id}}}
|
|
216
|
+
scraper.cookies.set("__Secure-yupp.session-token", account['token'])
|
|
217
|
+
response = scraper.post(url, json=payload)
|
|
218
|
+
response.raise_for_status()
|
|
219
|
+
data = response.json()
|
|
220
|
+
if (
|
|
221
|
+
isinstance(data, list) and len(data) > 0
|
|
222
|
+
and data[0].get("result", {}).get("data", {}).get("json") is None
|
|
223
|
+
):
|
|
224
|
+
log_debug(f"Chat {chat_id} deleted successfully")
|
|
225
|
+
return True
|
|
226
|
+
log_debug(f"Unexpected response while deleting chat: {data}")
|
|
227
|
+
return False
|
|
228
|
+
except Exception as e:
|
|
229
|
+
log_debug(f"Failed to delete chat {chat_id}: {e}")
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def delete_chat(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
|
|
130
234
|
loop = asyncio.get_event_loop()
|
|
131
|
-
return await loop.run_in_executor(_executor,
|
|
235
|
+
return await loop.run_in_executor(_executor, sync_delete_chat, scraper, account, chat_id)
|
|
132
236
|
|
|
133
237
|
|
|
134
238
|
def sync_make_chat_private(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
|
|
@@ -148,8 +252,8 @@ def sync_make_chat_private(scraper: CloudScraper, account: Dict[str, Any], chat_
|
|
|
148
252
|
response.raise_for_status()
|
|
149
253
|
data = response.json()
|
|
150
254
|
if (
|
|
151
|
-
|
|
152
|
-
|
|
255
|
+
isinstance(data, list) and len(data) > 0
|
|
256
|
+
and "json" in data[0].get("result", {}).get("data", {})
|
|
153
257
|
):
|
|
154
258
|
log_debug(f"Chat {chat_id} is now PRIVATE")
|
|
155
259
|
return True
|
|
@@ -205,10 +309,18 @@ def format_messages_for_yupp(messages: Messages) -> str:
|
|
|
205
309
|
return result
|
|
206
310
|
|
|
207
311
|
|
|
312
|
+
def evict_cache_if_needed():
|
|
313
|
+
global ImagesCache
|
|
314
|
+
if len(ImagesCache) > MAX_CACHE_SIZE:
|
|
315
|
+
keys_to_remove = list(ImagesCache.keys())[:len(ImagesCache) - MAX_CACHE_SIZE + 100]
|
|
316
|
+
for key in keys_to_remove:
|
|
317
|
+
del ImagesCache[key]
|
|
318
|
+
|
|
319
|
+
|
|
208
320
|
class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
209
321
|
url = "https://yupp.ai"
|
|
210
322
|
login_url = "https://discord.gg/qXA4Wf4Fsm"
|
|
211
|
-
working =
|
|
323
|
+
working = has_cloudscraper
|
|
212
324
|
active_by_default = True
|
|
213
325
|
supports_stream = True
|
|
214
326
|
image_cache = True
|
|
@@ -228,8 +340,10 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
228
340
|
cls.models_tags = {model.get("name"): manager.processor.generate_tags(model) for model in models}
|
|
229
341
|
cls.models = [model.get("name") for model in models]
|
|
230
342
|
cls.image_models = [model.get("name") for model in models if model.get("isImageGeneration")]
|
|
231
|
-
cls.vision_models = [
|
|
232
|
-
|
|
343
|
+
cls.vision_models = [
|
|
344
|
+
model.get("name") for model in models
|
|
345
|
+
if "image/*" in model.get("supportedAttachmentMimeTypes", [])
|
|
346
|
+
]
|
|
233
347
|
return cls.models
|
|
234
348
|
|
|
235
349
|
@classmethod
|
|
@@ -271,8 +385,15 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
271
385
|
|
|
272
386
|
attachment_resp = scraper.post(
|
|
273
387
|
"https://yupp.ai/api/trpc/chat.createAttachmentForUploadedFile?batch=1",
|
|
274
|
-
json={
|
|
275
|
-
|
|
388
|
+
json={
|
|
389
|
+
"0": {
|
|
390
|
+
"json": {
|
|
391
|
+
"fileName": name,
|
|
392
|
+
"contentType": is_accepted_format(data),
|
|
393
|
+
"fileId": upload_info["fileId"]
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
},
|
|
276
397
|
cookies={"__Secure-yupp.session-token": account["token"]}
|
|
277
398
|
)
|
|
278
399
|
attachment_resp.raise_for_status()
|
|
@@ -283,6 +404,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
283
404
|
"attachmentId": attachment["attachment_id"],
|
|
284
405
|
"chatMessageId": ""
|
|
285
406
|
}
|
|
407
|
+
evict_cache_if_needed()
|
|
286
408
|
ImagesCache[image_hash] = file_info
|
|
287
409
|
files.append(file_info)
|
|
288
410
|
return files
|
|
@@ -305,8 +427,8 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
305
427
|
}
|
|
306
428
|
)
|
|
307
429
|
resp.raise_for_status()
|
|
308
|
-
data = resp.json()
|
|
309
|
-
return data
|
|
430
|
+
data = resp.json()[0]["result"]["data"]["json"]
|
|
431
|
+
return data.get("signed_url", data.get("signedURL"))
|
|
310
432
|
|
|
311
433
|
@classmethod
|
|
312
434
|
async def get_signed_image(cls, scraper: CloudScraper, image_id: str) -> str:
|
|
@@ -314,23 +436,26 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
314
436
|
return await loop.run_in_executor(_executor, cls.sync_get_signed_image, scraper, image_id)
|
|
315
437
|
|
|
316
438
|
@classmethod
|
|
317
|
-
def sync_stream_request(cls, scraper: CloudScraper, url: str, payload: list, headers: dict,
|
|
318
|
-
timeout: int):
|
|
439
|
+
def sync_stream_request(cls, scraper: CloudScraper, url: str, payload: list, headers: dict, timeout: int):
|
|
319
440
|
response = scraper.post(url, json=payload, headers=headers, stream=True, timeout=timeout)
|
|
320
441
|
response.raise_for_status()
|
|
321
442
|
return response
|
|
322
443
|
|
|
323
444
|
@classmethod
|
|
324
445
|
async def create_async_generator(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
446
|
+
cls,
|
|
447
|
+
model: str,
|
|
448
|
+
messages: Messages,
|
|
449
|
+
proxy: str = None,
|
|
450
|
+
api_key: str = None,
|
|
451
|
+
**kwargs,
|
|
330
452
|
) -> AsyncResult:
|
|
331
|
-
if
|
|
332
|
-
raise MissingRequirementsError(
|
|
333
|
-
|
|
453
|
+
if not has_cloudscraper:
|
|
454
|
+
raise MissingRequirementsError(
|
|
455
|
+
"cloudscraper library is required for Yupp provider | install it via 'pip install cloudscraper'"
|
|
456
|
+
)
|
|
457
|
+
if not api_key:
|
|
458
|
+
api_key = AuthManager.load_api_key(cls)
|
|
334
459
|
if not api_key:
|
|
335
460
|
api_key = get_cookies("yupp.ai", False).get("__Secure-yupp.session-token")
|
|
336
461
|
if api_key:
|
|
@@ -351,7 +476,8 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
351
476
|
prompt = get_last_user_message(messages, prompt)
|
|
352
477
|
|
|
353
478
|
log_debug(
|
|
354
|
-
f"Use url_uuid: {url_uuid}, Formatted prompt length: {len(prompt)}, Is new conversation: {is_new_conversation}"
|
|
479
|
+
f"Use url_uuid: {url_uuid}, Formatted prompt length: {len(prompt)}, Is new conversation: {is_new_conversation}"
|
|
480
|
+
)
|
|
355
481
|
|
|
356
482
|
max_attempts = len(YUPP_ACCOUNTS)
|
|
357
483
|
for attempt in range(max_attempts):
|
|
@@ -437,10 +563,13 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
437
563
|
timeout
|
|
438
564
|
)
|
|
439
565
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
566
|
+
try:
|
|
567
|
+
async for chunk in cls._process_stream_response(response, account, scraper, prompt, model):
|
|
568
|
+
yield chunk
|
|
569
|
+
finally:
|
|
570
|
+
response.close()
|
|
571
|
+
if not kwargs.get("conversation"):
|
|
572
|
+
asyncio.create_task(delete_chat(scraper, account, url_uuid))
|
|
444
573
|
return
|
|
445
574
|
|
|
446
575
|
except RateLimitError:
|
|
@@ -462,7 +591,8 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
462
591
|
log_debug(f"Unexpected error with account ...{account['token'][-4:]}: {str(e)}")
|
|
463
592
|
error_str = str(e).lower()
|
|
464
593
|
if "500" in error_str or "internal server error" in error_str:
|
|
465
|
-
|
|
594
|
+
async with account_rotation_lock:
|
|
595
|
+
account["error_count"] += 1
|
|
466
596
|
continue
|
|
467
597
|
async with account_rotation_lock:
|
|
468
598
|
account["error_count"] += 1
|
|
@@ -472,12 +602,12 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
472
602
|
|
|
473
603
|
@classmethod
|
|
474
604
|
async def _process_stream_response(
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
605
|
+
cls,
|
|
606
|
+
response,
|
|
607
|
+
account: Dict[str, Any],
|
|
608
|
+
scraper: CloudScraper,
|
|
609
|
+
prompt: str,
|
|
610
|
+
model_id: str
|
|
481
611
|
) -> AsyncResult:
|
|
482
612
|
line_pattern = re.compile(b"^([0-9a-fA-F]+):(.*)")
|
|
483
613
|
target_stream_id = None
|
|
@@ -562,6 +692,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
562
692
|
is_started: bool = False
|
|
563
693
|
variant_image: Optional[ImageResponse] = None
|
|
564
694
|
reward_id = "a"
|
|
695
|
+
reward_kw = {}
|
|
565
696
|
routing_id = "e"
|
|
566
697
|
turn_id = None
|
|
567
698
|
persisted_turn_id = None
|
|
@@ -642,7 +773,8 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
642
773
|
right_stream = data.get("rightStream", {})
|
|
643
774
|
if data.get("quickResponse", {}) != "$undefined":
|
|
644
775
|
quick_response_id = extract_ref_id(
|
|
645
|
-
data.get("quickResponse", {}).get("stream", {}).get("next")
|
|
776
|
+
data.get("quickResponse", {}).get("stream", {}).get("next")
|
|
777
|
+
)
|
|
646
778
|
|
|
647
779
|
if data.get("turnId", {}) != "$undefined":
|
|
648
780
|
turn_id = extract_ref_id(data.get("turnId", {}).get("next"))
|
|
@@ -693,20 +825,20 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
693
825
|
if ref_name in image_blocks:
|
|
694
826
|
img_block_text = image_blocks[ref_name]
|
|
695
827
|
async for chunk in process_content_chunk(
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
828
|
+
img_block_text,
|
|
829
|
+
ref_name,
|
|
830
|
+
line_count,
|
|
831
|
+
for_target=True
|
|
700
832
|
):
|
|
701
833
|
stream["target"].append(chunk)
|
|
702
834
|
is_started = True
|
|
703
835
|
yield chunk
|
|
704
836
|
else:
|
|
705
837
|
async for chunk in process_content_chunk(
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
838
|
+
content,
|
|
839
|
+
chunk_id,
|
|
840
|
+
line_count,
|
|
841
|
+
for_target=True
|
|
710
842
|
):
|
|
711
843
|
stream["target"].append(chunk)
|
|
712
844
|
is_started = True
|
|
@@ -719,10 +851,10 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
719
851
|
content = data.get("curr", "")
|
|
720
852
|
if content:
|
|
721
853
|
async for chunk in process_content_chunk(
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
854
|
+
content,
|
|
855
|
+
chunk_id,
|
|
856
|
+
line_count,
|
|
857
|
+
for_target=False
|
|
726
858
|
):
|
|
727
859
|
stream["variant"].append(chunk)
|
|
728
860
|
if isinstance(chunk, ImageResponse):
|
|
@@ -738,32 +870,35 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
738
870
|
content = data.get("curr", "")
|
|
739
871
|
if content:
|
|
740
872
|
async for chunk in process_content_chunk(
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
873
|
+
content,
|
|
874
|
+
chunk_id,
|
|
875
|
+
line_count,
|
|
876
|
+
for_target=False
|
|
745
877
|
):
|
|
746
878
|
stream["quick"].append(chunk)
|
|
747
879
|
quick_content += content
|
|
748
880
|
yield PreviewResponse(content)
|
|
749
881
|
|
|
750
|
-
elif chunk_id
|
|
882
|
+
elif chunk_id == turn_id:
|
|
883
|
+
reward_kw["turn_id"] = data.get("curr", "")
|
|
884
|
+
|
|
885
|
+
elif chunk_id == persisted_turn_id:
|
|
751
886
|
pass
|
|
752
887
|
|
|
753
888
|
elif chunk_id == right_message_id:
|
|
754
|
-
|
|
889
|
+
reward_kw["right_message_id"] = data.get("curr", "")
|
|
755
890
|
|
|
756
891
|
elif chunk_id == left_message_id:
|
|
757
|
-
|
|
892
|
+
reward_kw["left_message_id"] = data.get("curr", "")
|
|
758
893
|
|
|
759
894
|
elif isinstance(data, dict) and "curr" in data:
|
|
760
895
|
content = data.get("curr", "")
|
|
761
896
|
if content:
|
|
762
897
|
async for chunk in process_content_chunk(
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
898
|
+
content,
|
|
899
|
+
chunk_id,
|
|
900
|
+
line_count,
|
|
901
|
+
for_target=False
|
|
767
902
|
):
|
|
768
903
|
stream["extra"].append(chunk)
|
|
769
904
|
if isinstance(chunk, str) and "<streaming stopped unexpectedly" in chunk:
|
|
@@ -779,7 +914,14 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
|
|
779
914
|
log_debug(f"Finished processing {line_count} lines")
|
|
780
915
|
|
|
781
916
|
finally:
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
917
|
+
log_debug(f"Get Reward: {reward_kw}")
|
|
918
|
+
if reward_kw.get("turn_id") and reward_kw.get("left_message_id") and reward_kw.get("right_message_id"):
|
|
919
|
+
eval_id = await record_model_feedback(
|
|
920
|
+
scraper,
|
|
921
|
+
account,
|
|
922
|
+
reward_kw["turn_id"],
|
|
923
|
+
reward_kw["left_message_id"],
|
|
924
|
+
reward_kw["right_message_id"]
|
|
925
|
+
)
|
|
926
|
+
if eval_id:
|
|
927
|
+
await claim_yupp_reward(scraper, account, eval_id)
|
g4f/Provider/__init__.py
CHANGED
|
@@ -49,9 +49,7 @@ from .OperaAria import OperaAria
|
|
|
49
49
|
from .Perplexity import Perplexity
|
|
50
50
|
from .PollinationsAI import PollinationsAI
|
|
51
51
|
from .PollinationsImage import PollinationsImage
|
|
52
|
-
from .Startnest import Startnest
|
|
53
52
|
from .Qwen import Qwen
|
|
54
|
-
from .StringableInference import StringableInference
|
|
55
53
|
from .TeachAnything import TeachAnything
|
|
56
54
|
from .WeWordle import WeWordle
|
|
57
55
|
from .Yqcloud import Yqcloud
|
g4f/Provider/local/Ollama.py
CHANGED
|
@@ -13,6 +13,7 @@ from ...typing import AsyncResult, Messages
|
|
|
13
13
|
class Ollama(OpenaiTemplate):
|
|
14
14
|
label = "Ollama 🦙"
|
|
15
15
|
url = "https://ollama.com"
|
|
16
|
+
base_url = "https://g4f.space/api/ollama"
|
|
16
17
|
login_url = "https://ollama.com/settings/keys"
|
|
17
18
|
needs_auth = False
|
|
18
19
|
working = True
|
|
@@ -34,7 +35,7 @@ class Ollama(OpenaiTemplate):
|
|
|
34
35
|
cls.live += 1
|
|
35
36
|
cls.models = [model["name"] for model in models]
|
|
36
37
|
if base_url is None:
|
|
37
|
-
host = os.getenv("OLLAMA_HOST", "
|
|
38
|
+
host = os.getenv("OLLAMA_HOST", "localhost")
|
|
38
39
|
port = os.getenv("OLLAMA_PORT", "11434")
|
|
39
40
|
url = f"http://{host}:{port}/api/tags"
|
|
40
41
|
else:
|
|
@@ -66,7 +67,7 @@ class Ollama(OpenaiTemplate):
|
|
|
66
67
|
base_url: str = f"http://{host}:{port}/v1"
|
|
67
68
|
if model in cls.local_models:
|
|
68
69
|
async with StreamSession(headers={"Authorization": f"Bearer {api_key}"}, proxy=proxy) as session:
|
|
69
|
-
async with session.post(f"{base_url}/api/chat", json={
|
|
70
|
+
async with session.post(f"{base_url.replace('/v1', '')}/api/chat", json={
|
|
70
71
|
"model": model,
|
|
71
72
|
"messages": messages,
|
|
72
73
|
}) as response:
|
g4f/Provider/needs_auth/Azure.py
CHANGED
|
@@ -14,7 +14,7 @@ from ..helper import format_media_prompt
|
|
|
14
14
|
class Azure(OpenaiTemplate):
|
|
15
15
|
label = "Azure ☁️"
|
|
16
16
|
url = "https://ai.azure.com"
|
|
17
|
-
base_url = "https://g4f.
|
|
17
|
+
base_url = "https://g4f.space/api/azure"
|
|
18
18
|
working = True
|
|
19
19
|
active_by_default = False
|
|
20
20
|
login_url = "https://discord.gg/qXA4Wf4Fsm"
|
|
@@ -9,7 +9,7 @@ from ..template import OpenaiTemplate
|
|
|
9
9
|
class Claude(OpenaiTemplate):
|
|
10
10
|
label = "Claude 💥"
|
|
11
11
|
url = "https://claude.ai"
|
|
12
|
-
base_url = "https://g4f.
|
|
12
|
+
base_url = "https://g4f.space/api/claude"
|
|
13
13
|
working = True
|
|
14
14
|
active_by_default = True
|
|
15
15
|
login_url = "https://discord.gg/qXA4Wf4Fsm"
|
|
@@ -7,7 +7,7 @@ class GeminiPro(OpenaiTemplate):
|
|
|
7
7
|
url = "https://ai.google.dev"
|
|
8
8
|
login_url = "https://aistudio.google.com/u/0/apikey"
|
|
9
9
|
base_url = "https://generativelanguage.googleapis.com/v1beta/openai"
|
|
10
|
-
backup_url = "https://g4f.
|
|
10
|
+
backup_url = "https://g4f.space/api/gemini-v1beta"
|
|
11
11
|
active_by_default = True
|
|
12
12
|
working = True
|
|
13
13
|
default_model = "models/gemini-2.5-flash"
|
g4f/Provider/needs_auth/Groq.py
CHANGED
|
@@ -7,6 +7,7 @@ class Groq(OpenaiTemplate):
|
|
|
7
7
|
url = "https://console.groq.com/playground"
|
|
8
8
|
login_url = "https://console.groq.com/keys"
|
|
9
9
|
base_url = "https://api.groq.com/openai/v1"
|
|
10
|
+
backup_url = "https://g4f.space/api/groq"
|
|
10
11
|
working = True
|
|
11
12
|
active_by_default = True
|
|
12
13
|
default_model = DEFAULT_MODEL
|
|
@@ -6,6 +6,7 @@ from ...config import DEFAULT_MODEL
|
|
|
6
6
|
class Nvidia(OpenaiTemplate):
|
|
7
7
|
label = "Nvidia"
|
|
8
8
|
base_url = "https://integrate.api.nvidia.com/v1"
|
|
9
|
+
backup_url = "https://g4f.space/api/nvidia"
|
|
9
10
|
login_url = "https://google.com"
|
|
10
11
|
url = "https://build.nvidia.com"
|
|
11
12
|
working = True
|
g4f/client/__init__.py
CHANGED
|
@@ -821,7 +821,7 @@ class ClientFactory:
|
|
|
821
821
|
elif provider.startswith("custom:"):
|
|
822
822
|
if provider.startswith("custom:"):
|
|
823
823
|
serverId = provider[7:]
|
|
824
|
-
base_url = f"https://
|
|
824
|
+
base_url = f"https://g4f.space/custom/{serverId}"
|
|
825
825
|
if not base_url:
|
|
826
826
|
raise ValueError("base_url is required for custom providers")
|
|
827
827
|
provider = create_custom_provider(base_url, api_key, name=name, **kwargs)
|
g4f/gui/server/backend_api.py
CHANGED
|
@@ -331,7 +331,7 @@ class Backend_Api(Api):
|
|
|
331
331
|
for chunk in response:
|
|
332
332
|
if isinstance(chunk, FinishReason):
|
|
333
333
|
yield f"[{chunk.reason}]" if chunk.reason != "stop" else ""
|
|
334
|
-
elif not isinstance(chunk, Exception):
|
|
334
|
+
elif not isinstance(chunk, (Exception, JsonResponse)):
|
|
335
335
|
chunk = str(chunk)
|
|
336
336
|
if chunk:
|
|
337
337
|
yield chunk
|
g4f/mcp/server.py
CHANGED
|
@@ -10,13 +10,21 @@ The server exposes tools for:
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
13
15
|
import sys
|
|
14
16
|
import json
|
|
15
17
|
import asyncio
|
|
18
|
+
import hashlib
|
|
19
|
+
from email.utils import formatdate
|
|
16
20
|
from typing import Any, Dict, List, Optional, Union
|
|
17
21
|
from dataclasses import dataclass
|
|
22
|
+
from urllib.parse import unquote_plus
|
|
18
23
|
|
|
19
24
|
from ..debug import enable_logging
|
|
25
|
+
from ..cookies import read_cookie_files
|
|
26
|
+
from ..image import EXTENSIONS_MAP
|
|
27
|
+
from ..image.copy_images import get_media_dir, copy_media, get_source_url
|
|
20
28
|
|
|
21
29
|
from .tools import MarkItDownTool, TextToAudioTool, WebSearchTool, WebScrapeTool, ImageGenerationTool
|
|
22
30
|
from .tools import WebSearchTool, WebScrapeTool, ImageGenerationTool
|
|
@@ -214,6 +222,7 @@ class MCPServer:
|
|
|
214
222
|
sys.exit(1)
|
|
215
223
|
|
|
216
224
|
enable_logging()
|
|
225
|
+
read_cookie_files()
|
|
217
226
|
|
|
218
227
|
async def handle_mcp_request(request: web.Request) -> web.Response:
|
|
219
228
|
nonlocal origin
|
|
@@ -273,17 +282,120 @@ class MCPServer:
|
|
|
273
282
|
"server": self.server_info
|
|
274
283
|
})
|
|
275
284
|
|
|
285
|
+
async def handle_media(request: web.Request) -> web.Response:
|
|
286
|
+
"""Serve media files from generated_media directory"""
|
|
287
|
+
filename = request.match_info.get('filename', '')
|
|
288
|
+
if not filename:
|
|
289
|
+
return web.Response(status=404, text="File not found")
|
|
290
|
+
|
|
291
|
+
def get_timestamp(s):
|
|
292
|
+
m = re.match("^[0-9]+", s)
|
|
293
|
+
return int(m.group(0)) if m else 0
|
|
294
|
+
|
|
295
|
+
target = os.path.join(get_media_dir(), os.path.basename(filename))
|
|
296
|
+
|
|
297
|
+
# Try URL-decoded filename if not found
|
|
298
|
+
if not os.path.isfile(target):
|
|
299
|
+
other_name = os.path.join(get_media_dir(), os.path.basename(unquote_plus(filename)))
|
|
300
|
+
if os.path.isfile(other_name):
|
|
301
|
+
target = other_name
|
|
302
|
+
|
|
303
|
+
# Get file extension and mime type
|
|
304
|
+
ext = os.path.splitext(filename)[1][1:].lower()
|
|
305
|
+
mime_type = EXTENSIONS_MAP.get(ext, "application/octet-stream")
|
|
306
|
+
|
|
307
|
+
# Try to fetch from backend if file doesn't exist
|
|
308
|
+
if not os.path.isfile(target) and mime_type != "application/octet-stream":
|
|
309
|
+
source_url = get_source_url(str(request.query_string))
|
|
310
|
+
ssl = None
|
|
311
|
+
if source_url is not None:
|
|
312
|
+
try:
|
|
313
|
+
await copy_media([source_url], target=target, ssl=ssl)
|
|
314
|
+
sys.stderr.write(f"File copied from {source_url}\n")
|
|
315
|
+
except Exception as e:
|
|
316
|
+
sys.stderr.write(f"Download failed: {source_url} - {e}\n")
|
|
317
|
+
raise web.HTTPFound(location=source_url)
|
|
318
|
+
|
|
319
|
+
if not os.path.isfile(target):
|
|
320
|
+
return web.Response(status=404, text="File not found")
|
|
321
|
+
|
|
322
|
+
# Build response headers
|
|
323
|
+
stat_result = os.stat(target)
|
|
324
|
+
headers = {
|
|
325
|
+
"cache-control": "public, max-age=31536000",
|
|
326
|
+
"last-modified": formatdate(get_timestamp(filename), usegmt=True),
|
|
327
|
+
"etag": f'"{hashlib.md5(filename.encode()).hexdigest()}"',
|
|
328
|
+
"content-length": str(stat_result.st_size),
|
|
329
|
+
"content-type": mime_type,
|
|
330
|
+
"access-control-allow-origin": "*",
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# Check for conditional request
|
|
334
|
+
if_none_match = request.headers.get("if-none-match")
|
|
335
|
+
if if_none_match:
|
|
336
|
+
etag = headers["etag"]
|
|
337
|
+
if etag in [tag.strip(" W/") for tag in if_none_match.split(",")]:
|
|
338
|
+
return web.Response(status=304, headers=headers)
|
|
339
|
+
|
|
340
|
+
# Serve the file
|
|
341
|
+
return web.FileResponse(target, headers=headers)
|
|
342
|
+
|
|
343
|
+
async def handle_synthesize(request: web.Request) -> web.Response:
|
|
344
|
+
"""Handle synthesize requests for text-to-speech"""
|
|
345
|
+
provider_name = request.match_info.get('provider', '')
|
|
346
|
+
if not provider_name:
|
|
347
|
+
return web.Response(status=400, text="Provider not specified")
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
from ..Provider import ProviderUtils
|
|
351
|
+
provider_handler = ProviderUtils.convert.get(provider_name)
|
|
352
|
+
if provider_handler is None:
|
|
353
|
+
return web.Response(status=404, text=f"Provider not found: {provider_name}")
|
|
354
|
+
except Exception as e:
|
|
355
|
+
return web.Response(status=404, text=f"Provider not found: {provider_name}")
|
|
356
|
+
|
|
357
|
+
if not hasattr(provider_handler, "synthesize"):
|
|
358
|
+
return web.Response(status=500, text=f"Provider doesn't support synthesize: {provider_name}")
|
|
359
|
+
|
|
360
|
+
# Get query parameters
|
|
361
|
+
params = dict(request.query)
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
# Call the synthesize method
|
|
365
|
+
response_data = provider_handler.synthesize(params)
|
|
366
|
+
|
|
367
|
+
# Handle async generator
|
|
368
|
+
async def generate():
|
|
369
|
+
async for chunk in response_data:
|
|
370
|
+
yield chunk
|
|
371
|
+
|
|
372
|
+
content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
|
|
373
|
+
return web.Response(
|
|
374
|
+
body=b"".join([chunk async for chunk in generate()]),
|
|
375
|
+
content_type=content_type,
|
|
376
|
+
headers={
|
|
377
|
+
"cache-control": "max-age=604800",
|
|
378
|
+
"access-control-allow-origin": "*",
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
except Exception as e:
|
|
382
|
+
sys.stderr.write(f"Synthesize error: {e}\n")
|
|
383
|
+
return web.Response(status=500, text=f"Synthesize error: {str(e)}")
|
|
384
|
+
|
|
276
385
|
# Create aiohttp application
|
|
277
386
|
app = web.Application()
|
|
278
387
|
app.router.add_options('/mcp', lambda request: web.Response(headers={"access-control-allow-origin": "*", "access-control-allow-methods": "POST, OPTIONS", "access-control-allow-headers": "Content-Type"}))
|
|
279
388
|
app.router.add_post('/mcp', handle_mcp_request)
|
|
280
389
|
app.router.add_get('/health', handle_health)
|
|
390
|
+
app.router.add_get('/media/{filename:.*}', handle_media)
|
|
391
|
+
app.router.add_get('/backend-api/v2/synthesize/{provider}', handle_synthesize)
|
|
281
392
|
|
|
282
393
|
# Start server
|
|
283
394
|
sys.stderr.write(f"Starting {self.server_info['name']} v{self.server_info['version']} (HTTP mode)\n")
|
|
284
395
|
sys.stderr.write(f"Listening on http://{host}:{port}\n")
|
|
285
396
|
sys.stderr.write(f"MCP endpoint: http://{host}:{port}/mcp\n")
|
|
286
397
|
sys.stderr.write(f"Health check: http://{host}:{port}/health\n")
|
|
398
|
+
sys.stderr.write(f"Media files: http://{host}:{port}/media/{{filename}}\n")
|
|
287
399
|
sys.stderr.flush()
|
|
288
400
|
|
|
289
401
|
runner = web.AppRunner(app)
|
g4f/mcp/tools.py
CHANGED
g4f/models.py
CHANGED
|
@@ -19,7 +19,6 @@ from .Provider import (
|
|
|
19
19
|
OIVSCodeSer0501,
|
|
20
20
|
OperaAria,
|
|
21
21
|
Perplexity,
|
|
22
|
-
Startnest,
|
|
23
22
|
OpenAIFM,
|
|
24
23
|
PollinationsAI,
|
|
25
24
|
PollinationsImage,
|
|
@@ -157,7 +156,6 @@ default = Model(
|
|
|
157
156
|
Copilot,
|
|
158
157
|
DeepInfra,
|
|
159
158
|
OperaAria,
|
|
160
|
-
Startnest,
|
|
161
159
|
GLM,
|
|
162
160
|
PollinationsAI,
|
|
163
161
|
Qwen,
|
|
@@ -179,7 +177,6 @@ default_vision = VisionModel(
|
|
|
179
177
|
OIVSCodeSer2,
|
|
180
178
|
PollinationsAI,
|
|
181
179
|
OperaAria,
|
|
182
|
-
Startnest,
|
|
183
180
|
Together,
|
|
184
181
|
HuggingSpace,
|
|
185
182
|
GeminiPro,
|
|
@@ -207,7 +204,7 @@ gpt_4o = VisionModel(
|
|
|
207
204
|
gpt_4o_mini = Model(
|
|
208
205
|
name = 'gpt-4o-mini',
|
|
209
206
|
base_provider = 'OpenAI',
|
|
210
|
-
best_provider = IterListProvider([Chatai, OIVSCodeSer2,
|
|
207
|
+
best_provider = IterListProvider([Chatai, OIVSCodeSer2, OpenaiChat])
|
|
211
208
|
)
|
|
212
209
|
|
|
213
210
|
# gpt_4o_mini_audio = AudioModel(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: g4f
|
|
3
|
-
Version: 6.9.
|
|
3
|
+
Version: 6.9.10
|
|
4
4
|
Summary: The official gpt4free repository | various collection of powerful language models
|
|
5
5
|
Home-page: https://github.com/xtekky/gpt4free
|
|
6
6
|
Author: Tekky
|
|
@@ -42,6 +42,7 @@ Requires-Dist: setuptools; extra == "all"
|
|
|
42
42
|
Requires-Dist: markitdown[all]; extra == "all"
|
|
43
43
|
Requires-Dist: python-dotenv; extra == "all"
|
|
44
44
|
Requires-Dist: aiofile; extra == "all"
|
|
45
|
+
Requires-Dist: cloudscraper; extra == "all"
|
|
45
46
|
Provides-Extra: slim
|
|
46
47
|
Requires-Dist: curl_cffi>=0.6.2; extra == "slim"
|
|
47
48
|
Requires-Dist: certifi; extra == "slim"
|
|
@@ -61,6 +62,7 @@ Requires-Dist: pypdf2; extra == "slim"
|
|
|
61
62
|
Requires-Dist: python-docx; extra == "slim"
|
|
62
63
|
Requires-Dist: python-dotenv; extra == "slim"
|
|
63
64
|
Requires-Dist: aiofile; extra == "slim"
|
|
65
|
+
Requires-Dist: cloudscraper; extra == "slim"
|
|
64
66
|
Provides-Extra: image
|
|
65
67
|
Requires-Dist: pillow; extra == "image"
|
|
66
68
|
Requires-Dist: cairosvg; extra == "image"
|
|
@@ -5,7 +5,7 @@ g4f/cookies.py,sha256=gx68OX5he2bYvsvvIQEESMUfwnVI18DOMJt9KvQrM8g,7915
|
|
|
5
5
|
g4f/debug.py,sha256=Dh0cmlxUydMDfjJBiPWrO9dAuvv2sTElb3R036ax-34,1021
|
|
6
6
|
g4f/errors.py,sha256=8Mp77UMW9_c8Flr1Z0Cvdrv0ixOF9CE0j1QX-cc5Zfc,2368
|
|
7
7
|
g4f/files.py,sha256=JzlWe3V6Xc7FN4eag7Utc4P9dHN1s1sMKFvYSaM0NQg,907
|
|
8
|
-
g4f/models.py,sha256=
|
|
8
|
+
g4f/models.py,sha256=QZV4UAis0QYaWGVO4Nv6n_3wMrLQvWHJ9V11Lo1EmBQ,24927
|
|
9
9
|
g4f/typing.py,sha256=fBBb74HkxtLa358vjcd1U5yr3ThlKzP2DfE8wdx01lA,2388
|
|
10
10
|
g4f/version.py,sha256=Lt6aOvuLBfDOHUSAtmncRMuNoDtisb1m8a447o7DkoM,4382
|
|
11
11
|
g4f/Provider/ApiAirforce.py,sha256=gYaq0MjLDVtzfXpnkhGWdKAZqnfC2UK1dji5u_EvhsQ,1148
|
|
@@ -23,16 +23,14 @@ g4f/Provider/Mintlify.py,sha256=3Bvy1oh6scg4xs9pw5RY4mmi8XEvQDelnNp664TefbU,7937
|
|
|
23
23
|
g4f/Provider/OIVSCodeSer.py,sha256=clebON7Ssd9syewh3BWT59XOeB-WORXF6FPOwbzfRmo,1366
|
|
24
24
|
g4f/Provider/OperaAria.py,sha256=sLnTOKzbW9BxTxNmHXJ-YDnhPU6Dj6MBdDfqDh-Zz-c,14662
|
|
25
25
|
g4f/Provider/Perplexity.py,sha256=GUebbVin9hCg4FDei9RX2N6WboFcDDPm061WnnpPRaI,13236
|
|
26
|
-
g4f/Provider/PollinationsAI.py,sha256=
|
|
26
|
+
g4f/Provider/PollinationsAI.py,sha256=8LMR8wl3Lh-pzhUHJW71rObSHgBeo7p4LL5GD6IWe3Y,21814
|
|
27
27
|
g4f/Provider/PollinationsImage.py,sha256=wdGY9kbPGlqAkyeJcjXgWOG3HLVPU4QK-JENwg3gmwk,2297
|
|
28
28
|
g4f/Provider/Qwen.py,sha256=XPJHRlobijqjdDGVqA-aVyRx2SeM28zSWf-NmzxJtgE,23876
|
|
29
|
-
g4f/Provider/Startnest.py,sha256=OocXEAK3pKG-tC_D_chGE7GD22dZr67J1m7JBhLxniM,9526
|
|
30
|
-
g4f/Provider/StringableInference.py,sha256=ZohMZrVAn6G82zrYpLTvELkzfds4nxu09lx7wizFnbk,999
|
|
31
29
|
g4f/Provider/TeachAnything.py,sha256=ST87YdOdxtIc5JMaKzGdVy9J9wlhdhYIchRtXBljIBU,2843
|
|
32
30
|
g4f/Provider/WeWordle.py,sha256=ocKEfMYBKWPzv2WoDntn-WC-DkrSg5b_ipL9VYhTgsw,7007
|
|
33
31
|
g4f/Provider/Yqcloud.py,sha256=xzX-G-Lv0pyOHTypSJdavmbm3GPxnL-wRoT3VmjzUIM,3213
|
|
34
|
-
g4f/Provider/Yupp.py,sha256=
|
|
35
|
-
g4f/Provider/__init__.py,sha256=
|
|
32
|
+
g4f/Provider/Yupp.py,sha256=E3ft5HshW-IfGdE2KtdpNOmbBkBmBeyl-c8xM-BTpEo,36906
|
|
33
|
+
g4f/Provider/__init__.py,sha256=2azA2zjXZjkH3yDJSoWKeB3SCGB7SIa_0_DN44Lf39g,2684
|
|
36
34
|
g4f/Provider/base_provider.py,sha256=lAd80-2hO1kzs9n9TUKv6fR4zHjSdZBqVErpm_gna9o,199
|
|
37
35
|
g4f/Provider/helper.py,sha256=_4fO_b2U8BnpuF6kijUM4SaFbX2Pcz6mksxK9mVXhP4,111
|
|
38
36
|
g4f/Provider/audio/EdgeTTS.py,sha256=VXcLCZDdf8DL0jIEO3tvduPK4NguUMEDJg9U5MkNvJk,2822
|
|
@@ -55,16 +53,16 @@ g4f/Provider/hf_space/StabilityAI_SD35Large.py,sha256=-VT4qa_K6MshG2TX6y7k01NMGH
|
|
|
55
53
|
g4f/Provider/hf_space/__init__.py,sha256=licdlcuTs0yPwADmjBUA5uoN4ao-FnCUmlKiMWoHJ2g,3602
|
|
56
54
|
g4f/Provider/hf_space/raise_for_status.py,sha256=xoVwrZSwJUvqQkSfeUAMUst8SyobpSKPux1iYu4SNH0,935
|
|
57
55
|
g4f/Provider/local/Local.py,sha256=b6vSZcr5CfEXD5GMqNtwCfXNJtKyWKPg3jqBWJEZEwQ,1251
|
|
58
|
-
g4f/Provider/local/Ollama.py,sha256=
|
|
56
|
+
g4f/Provider/local/Ollama.py,sha256=MtB36eUcn6iyJ9XZWfEHIrkCGeZ0Z6Hysm7AgHQUwE0,3835
|
|
59
57
|
g4f/Provider/local/__init__.py,sha256=cEFsdfkq0bgVAIM__Sxc6WfIoGa3_Ymol26kQb6x14k,73
|
|
60
58
|
g4f/Provider/needs_auth/AIBadgr.py,sha256=Oc05Cs-x40sF7tcrAyUqyoZ-TbtmV4wnMIm5aEjQ0xw,420
|
|
61
59
|
g4f/Provider/needs_auth/Anthropic.py,sha256=-8hRSpSGaC3-AzhkuIsf1K1w8ZVKKb_T6Pwy3N8JPak,10200
|
|
62
|
-
g4f/Provider/needs_auth/Azure.py,sha256=
|
|
60
|
+
g4f/Provider/needs_auth/Azure.py,sha256=q8Un1ouuG57-9WDyHnRdMfhTjfYM8ky0_8SIYOIkPck,6109
|
|
63
61
|
g4f/Provider/needs_auth/BingCreateImages.py,sha256=_GrPx7KuttKrQ4IKX0fFwDPVsiCW9xTLycULNClZ6KQ,2066
|
|
64
62
|
g4f/Provider/needs_auth/BlackboxPro.py,sha256=BA-eiaVGczyB7BHQ47jehq6dTkEslithw1nN2whceZo,270382
|
|
65
63
|
g4f/Provider/needs_auth/CablyAI.py,sha256=113DsXPXAq3DO2QPHPJ0UJwMpXxomb7BM07RrcV6YQk,1253
|
|
66
64
|
g4f/Provider/needs_auth/Cerebras.py,sha256=WcPgAVW9QRqoDJ9-KgWYDl08buEVRl1pWiwy7giAGlE,1753
|
|
67
|
-
g4f/Provider/needs_auth/Claude.py,sha256=
|
|
65
|
+
g4f/Provider/needs_auth/Claude.py,sha256=Xk6kXNhygZ62DbTuDNDo2K8qD_8CZMqc_IwCQBuaoR0,1400
|
|
68
66
|
g4f/Provider/needs_auth/Cohere.py,sha256=NhHzZrYD2uLxFtEsBKR1FR6UywT3hhie5jIh2Ppvbrg,5851
|
|
69
67
|
g4f/Provider/needs_auth/CopilotAccount.py,sha256=VCjfUzlPZxU-SduLy3V-YFrUN_KOLZoT2b-OZmZD4rw,394
|
|
70
68
|
g4f/Provider/needs_auth/Custom.py,sha256=bD6ao9IkIG_oW7bYAHCSEZrMEnDX9u2X96q9LkCMbWc,347
|
|
@@ -73,19 +71,19 @@ g4f/Provider/needs_auth/DeepSeekAPI.py,sha256=UaN4W5b_1nXsyB5MN81NzHkQLz1V6DOzte
|
|
|
73
71
|
g4f/Provider/needs_auth/FenayAI.py,sha256=2NkDnhdEVDMD1Xk13rp78nRt7oGwbgsX0jlonWt-Ym8,519
|
|
74
72
|
g4f/Provider/needs_auth/Gemini.py,sha256=p0idmlvIbjg3kMEGsOv5N9b-j5b-blNT2yxO2xnAmiQ,22853
|
|
75
73
|
g4f/Provider/needs_auth/GeminiCLI.py,sha256=BNQv_Hqzu3E0GtVRbZDZdcq--ZHpJDdLRF5ssG4yX9o,23554
|
|
76
|
-
g4f/Provider/needs_auth/GeminiPro.py,sha256=
|
|
74
|
+
g4f/Provider/needs_auth/GeminiPro.py,sha256=Q2G8tuZEZQOUSE-m3PoIs5NMyXuOO55784kXk8rFdsM,755
|
|
77
75
|
g4f/Provider/needs_auth/GigaChat.py,sha256=2IqQlav_-g-4ZUMJ0tO8KGpvchg9_0ap6rzGP_5dLRI,6325
|
|
78
76
|
g4f/Provider/needs_auth/GithubCopilot.py,sha256=AUoapqX6lwgC5WPIpKdKdhXcuXo4Stq7I0W5bVHMEbI,6093
|
|
79
77
|
g4f/Provider/needs_auth/GithubCopilotAPI.py,sha256=O-8Bq6eWrVZTYFS5QufMDJ9SiSFsd0Pz91yejb6spOI,353
|
|
80
78
|
g4f/Provider/needs_auth/GlhfChat.py,sha256=qSPKXnQ7igjF6_kiBnFhwq-YAqGmpZg_yu4OMRliSP4,1189
|
|
81
79
|
g4f/Provider/needs_auth/Grok.py,sha256=3uwt0vjsSNHLZKS2DcUHTztiNqPIemwW82E2x0AQRTw,12884
|
|
82
|
-
g4f/Provider/needs_auth/Groq.py,sha256=
|
|
80
|
+
g4f/Provider/needs_auth/Groq.py,sha256=zErhEO8-oo4dOZY1u2uz0Xcxb3N76epEBhbuiOjYbx0,594
|
|
83
81
|
g4f/Provider/needs_auth/LMArena.py,sha256=dKwHEgdvEY9ctjNZzi-COdV8RHqV_z7qEoKiLGGhxus,121399
|
|
84
82
|
g4f/Provider/needs_auth/MetaAI.py,sha256=Bz9pvJUVH7RtCAP1Huvay-EgO057fL362mhx3GtVAqM,10653
|
|
85
83
|
g4f/Provider/needs_auth/MetaAIAccount.py,sha256=D4LnhAt2MuKx1a4uSgX2lUbQrzAkeIYm8JCnZieZiak,672
|
|
86
84
|
g4f/Provider/needs_auth/MicrosoftDesigner.py,sha256=4sJdjBPgiW9TEh4CeplCTNPXv6ZtZtFh0SYAiVfnrqk,7178
|
|
87
|
-
g4f/Provider/needs_auth/Nvidia.py,sha256=
|
|
88
|
-
g4f/Provider/needs_auth/OpenRouter.py,sha256=
|
|
85
|
+
g4f/Provider/needs_auth/Nvidia.py,sha256=RcVVSpTZM9VCMhLZ4z3g6j9yxHPQETeWQJb24SPfaII,439
|
|
86
|
+
g4f/Provider/needs_auth/OpenRouter.py,sha256=KKWKKrzw2BGscd05m3BVw21TOHWRMkFETzOPQ1o5YNA,900
|
|
89
87
|
g4f/Provider/needs_auth/OpenaiAPI.py,sha256=KpJ6qvUlFsFqpQbcwikNYLVsFx6K9h393Pu-yf_uS3g,362
|
|
90
88
|
g4f/Provider/needs_auth/OpenaiAccount.py,sha256=vSe6Je-DoNbdGGEE-gNaW0Sa81rL8FPMaNyYr1U4PcI,209
|
|
91
89
|
g4f/Provider/needs_auth/OpenaiChat.py,sha256=dVSm-HAzlvb3KZqZNf0XYFxvIBTfXajKQfe8-JuO-vM,67285
|
|
@@ -148,7 +146,7 @@ g4f/api/stubs.py,sha256=9xoJDAmRyUtL_jlqhVI8Q8G-gF9zTse46iNBGEYvD9s,4252
|
|
|
148
146
|
g4f/cli/__init__.py,sha256=GUr_JFjLQDmDHmEyafHPvjaPYCQ5oeJclSazoEfRcxA,9167
|
|
149
147
|
g4f/cli/__main__.py,sha256=SYf3GG2ZCYygnpZ25muxdmT40Dfbx-J0ionEAnfBxuI,94
|
|
150
148
|
g4f/cli/client.py,sha256=azUTVzdOtfwj8Z6twhODzBQsONiwBBGq90h61Wu_PyY,11859
|
|
151
|
-
g4f/client/__init__.py,sha256=
|
|
149
|
+
g4f/client/__init__.py,sha256=Cx6-yGyo4rkeIJq6Q5ECA0GINkvdbXhIkkr0cpUQk-4,35501
|
|
152
150
|
g4f/client/helper.py,sha256=SwdS7I7CkWBG05INo04eMuukLRPUTkX4awgMHGPLpPI,1852
|
|
153
151
|
g4f/client/models.py,sha256=H8wu1UMFu8oCw7jy6x5B46GjPJPUa35h42Pq87Dp6lI,2318
|
|
154
152
|
g4f/client/service.py,sha256=lElAhe0y0ZzllkKjX2CBzy_kqgkwYw8P9fZWHivgWqw,5330
|
|
@@ -161,7 +159,7 @@ g4f/gui/webview.py,sha256=OcXnxG41h4RFSAQqq7pUBUv2UL3rbLlU3_RsOydabgs,1338
|
|
|
161
159
|
g4f/gui/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
162
160
|
g4f/gui/server/api.py,sha256=a0Yt2r-JpL6roGEZUCGKO20v1x8dY2jQ6uqCVWzd4tA,16641
|
|
163
161
|
g4f/gui/server/app.py,sha256=HwJeICAyMkrSQdrUiW2DhZGwe_l40Qb21dF_HiTgMv0,92
|
|
164
|
-
g4f/gui/server/backend_api.py,sha256=
|
|
162
|
+
g4f/gui/server/backend_api.py,sha256=cfgwrKaEA-efmKKt3Lm8uVfngSzHDCKgpC4ZeRFHthk,29976
|
|
165
163
|
g4f/gui/server/config.py,sha256=RCpWrnnfJ5Lmq63r6_rz2cxicCadLUgCxoRFZUgUs3s,15354
|
|
166
164
|
g4f/gui/server/crypto.py,sha256=6irNKqjlKfwnk4MY3TOhFlBWM2mZ6zjZaDmnanub_rc,2786
|
|
167
165
|
g4f/gui/server/internet.py,sha256=NSItPuwLwIegcUmfjw5SBOtBQroNQp7osd2pCNFGx6g,109
|
|
@@ -185,8 +183,8 @@ g4f/locals/models.py,sha256=9uzKnQwLBjtjGU6G3f13jAY5Y9aI2PD4hTi4_bFMqa4,1674
|
|
|
185
183
|
g4f/locals/provider.py,sha256=2Wk2PKzB005kYjHEABF_5a4j3qgxxIzgKMK28ewi3sg,2858
|
|
186
184
|
g4f/mcp/__init__.py,sha256=9S6ReeCAJIiWTpkLRvrk1KwfEsT1GKO7_chQoue0J5Q,573
|
|
187
185
|
g4f/mcp/__main__.py,sha256=zmnb03P5of2g8IZymmrRrYpMo6kKgQWD-0QemIcguOc,184
|
|
188
|
-
g4f/mcp/server.py,sha256=
|
|
189
|
-
g4f/mcp/tools.py,sha256=
|
|
186
|
+
g4f/mcp/server.py,sha256=zZq4moxcp0sI2w3t1kt3JAC529yfbClchgWK4Y0V7Rw,16900
|
|
187
|
+
g4f/mcp/tools.py,sha256=G09YfVemVsPbkMLIr5iiE0jHvIUj2Of8Rc02dUaHzzU,15309
|
|
190
188
|
g4f/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
191
189
|
g4f/providers/any_model_map.py,sha256=Go7AaJUyvI3NFA5AwjzT7sL8Suk7B0OhtqpzHb512g0,230475
|
|
192
190
|
g4f/providers/any_provider.py,sha256=EnK9VvBGEUGEe66QsDXEaxL7tvrnNea1ZKukbnMX2uc,19250
|
|
@@ -210,9 +208,9 @@ g4f/tools/files.py,sha256=bUTbeNp8ujTZQiW3GLTtFcgl3-1AnH3cDyIKHfh6Mjc,23397
|
|
|
210
208
|
g4f/tools/media.py,sha256=AE9hGVRxQBVZzQ_Ylzeoo2TJUGXSBXO5RbLwj1I2ZTE,4701
|
|
211
209
|
g4f/tools/run_tools.py,sha256=Yb0osWdDt4wUnkvwln4R6qcLapdddC3n2giZMPXWkzM,16662
|
|
212
210
|
g4f/tools/web_search.py,sha256=vAZJD-qy15llsgAbkXzoEltqdFB6TlKLbqDJ1DS-6vs,2086
|
|
213
|
-
g4f-6.9.
|
|
214
|
-
g4f-6.9.
|
|
215
|
-
g4f-6.9.
|
|
216
|
-
g4f-6.9.
|
|
217
|
-
g4f-6.9.
|
|
218
|
-
g4f-6.9.
|
|
211
|
+
g4f-6.9.10.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
|
212
|
+
g4f-6.9.10.dist-info/METADATA,sha256=sblmkzCLywyAXtzAFD9ORBJ4QAb4sGt6jylU28Vpa6I,23345
|
|
213
|
+
g4f-6.9.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
214
|
+
g4f-6.9.10.dist-info/entry_points.txt,sha256=J7Usl6dNjXJlvuzGAUEm6cuDXWpVdGq7SfK-tPoiZSI,67
|
|
215
|
+
g4f-6.9.10.dist-info/top_level.txt,sha256=bMRlTupWYCcLWy80AnnKZkhpBsXsF8gI3BaMhSZSgRo,4
|
|
216
|
+
g4f-6.9.10.dist-info/RECORD,,
|
g4f/Provider/Startnest.py
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from aiohttp import ClientSession
|
|
4
|
-
import json
|
|
5
|
-
import time
|
|
6
|
-
import hashlib
|
|
7
|
-
|
|
8
|
-
from ..typing import AsyncResult, Messages, MediaListType
|
|
9
|
-
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
|
|
10
|
-
from .helper import format_prompt
|
|
11
|
-
from ..tools.media import merge_media
|
|
12
|
-
from ..image import to_data_uri
|
|
13
|
-
from ..providers.response import FinishReason
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Startnest(AsyncGeneratorProvider, ProviderModelMixin):
|
|
17
|
-
label = "Startnest"
|
|
18
|
-
url = "https://play.google.com/store/apps/details?id=starnest.aitype.aikeyboard.chatbot.chatgpt"
|
|
19
|
-
api_endpoint = "https://api.startnest.uk/api/completions/stream"
|
|
20
|
-
|
|
21
|
-
working = False
|
|
22
|
-
needs_auth = False
|
|
23
|
-
supports_stream = True
|
|
24
|
-
supports_system_message = True
|
|
25
|
-
supports_message_history = True
|
|
26
|
-
|
|
27
|
-
default_model = 'gpt-4o-mini'
|
|
28
|
-
models = [default_model]
|
|
29
|
-
vision_models = models
|
|
30
|
-
|
|
31
|
-
@classmethod
|
|
32
|
-
def generate_signature(cls, timestamp: int) -> str:
|
|
33
|
-
"""
|
|
34
|
-
Generate signature for authorization header
|
|
35
|
-
You may need to adjust this based on the actual signature algorithm
|
|
36
|
-
"""
|
|
37
|
-
# This is a placeholder - the actual signature generation might involve:
|
|
38
|
-
# - A secret key
|
|
39
|
-
# - Specific string formatting
|
|
40
|
-
# - Different hash input
|
|
41
|
-
|
|
42
|
-
# Example implementation (adjust as needed):
|
|
43
|
-
kid = "36ccfe00-78fc-4cab-9c5b-5460b0c78513"
|
|
44
|
-
algorithm = "sha256"
|
|
45
|
-
validity = 90
|
|
46
|
-
user_id = ""
|
|
47
|
-
|
|
48
|
-
# The actual signature generation logic needs to be determined
|
|
49
|
-
# This is just a placeholder that creates a hash from timestamp
|
|
50
|
-
signature_input = f"{kid}{timestamp}{validity}".encode()
|
|
51
|
-
signature_value = hashlib.sha256(signature_input).hexdigest()
|
|
52
|
-
|
|
53
|
-
return f"Signature kid={kid}&algorithm={algorithm}×tamp={timestamp}&validity={validity}&userId={user_id}&value={signature_value}"
|
|
54
|
-
|
|
55
|
-
@classmethod
|
|
56
|
-
async def create_async_generator(
|
|
57
|
-
cls,
|
|
58
|
-
model: str,
|
|
59
|
-
messages: Messages,
|
|
60
|
-
proxy: str = None,
|
|
61
|
-
media: MediaListType = None,
|
|
62
|
-
stream: bool = True,
|
|
63
|
-
max_tokens: int = None,
|
|
64
|
-
**kwargs
|
|
65
|
-
) -> AsyncResult:
|
|
66
|
-
model = cls.get_model(model)
|
|
67
|
-
|
|
68
|
-
# Generate current timestamp
|
|
69
|
-
timestamp = int(time.time())
|
|
70
|
-
|
|
71
|
-
headers = {
|
|
72
|
-
"Accept-Encoding": "gzip",
|
|
73
|
-
"app_name": "AIKEYBOARD",
|
|
74
|
-
"Authorization": cls.generate_signature(timestamp),
|
|
75
|
-
"Connection": "Keep-Alive",
|
|
76
|
-
"Content-Type": "application/json; charset=UTF-8",
|
|
77
|
-
"Host": "api.startnest.uk",
|
|
78
|
-
"User-Agent": "okhttp/4.9.0",
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async with ClientSession() as session:
|
|
82
|
-
# Merge media with messages
|
|
83
|
-
media = list(merge_media(media, messages))
|
|
84
|
-
|
|
85
|
-
# Convert messages to the required format
|
|
86
|
-
formatted_messages = []
|
|
87
|
-
for i, msg in enumerate(messages):
|
|
88
|
-
if isinstance(msg, dict):
|
|
89
|
-
role = msg.get("role", "user")
|
|
90
|
-
content = msg.get("content", "")
|
|
91
|
-
|
|
92
|
-
# Create content array
|
|
93
|
-
content_array = []
|
|
94
|
-
|
|
95
|
-
# Add images if this is the last user message and media exists
|
|
96
|
-
if media and role == "user" and i == len(messages) - 1:
|
|
97
|
-
for image, _ in media:
|
|
98
|
-
image_data_uri = to_data_uri(image)
|
|
99
|
-
content_array.append({
|
|
100
|
-
"image_url": {
|
|
101
|
-
"url": image_data_uri
|
|
102
|
-
},
|
|
103
|
-
"type": "image_url"
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
# Add text content
|
|
107
|
-
if content:
|
|
108
|
-
content_array.append({
|
|
109
|
-
"text": content,
|
|
110
|
-
"type": "text"
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
formatted_messages.append({
|
|
114
|
-
"role": role,
|
|
115
|
-
"content": content_array
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
# If only one message and no media, use format_prompt as requested
|
|
119
|
-
if len(messages) == 1 and not media:
|
|
120
|
-
prompt_text = format_prompt(messages)
|
|
121
|
-
formatted_messages = [{
|
|
122
|
-
"role": "user",
|
|
123
|
-
"content": [{"text": prompt_text, "type": "text"}]
|
|
124
|
-
}]
|
|
125
|
-
|
|
126
|
-
data = {
|
|
127
|
-
"isVip": True,
|
|
128
|
-
"max_tokens": max_tokens,
|
|
129
|
-
"messages": formatted_messages,
|
|
130
|
-
"stream": stream
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
# Add advanceToolType if media is present
|
|
134
|
-
if media:
|
|
135
|
-
data["advanceToolType"] = "upload_and_ask"
|
|
136
|
-
|
|
137
|
-
async with session.post(cls.api_endpoint, json=data, headers=headers, proxy=proxy) as response:
|
|
138
|
-
response.raise_for_status()
|
|
139
|
-
|
|
140
|
-
if stream:
|
|
141
|
-
# Handle streaming response (SSE format)
|
|
142
|
-
async for line in response.content:
|
|
143
|
-
if line:
|
|
144
|
-
line = line.decode('utf-8').strip()
|
|
145
|
-
if line.startswith("data: "):
|
|
146
|
-
data_str = line[6:]
|
|
147
|
-
if data_str == "[DONE]":
|
|
148
|
-
break
|
|
149
|
-
try:
|
|
150
|
-
json_data = json.loads(data_str)
|
|
151
|
-
if "choices" in json_data and len(json_data["choices"]) > 0:
|
|
152
|
-
choice = json_data["choices"][0]
|
|
153
|
-
|
|
154
|
-
# Handle content
|
|
155
|
-
delta = choice.get("delta", {})
|
|
156
|
-
content = delta.get("content", "")
|
|
157
|
-
if content:
|
|
158
|
-
yield content
|
|
159
|
-
|
|
160
|
-
# Handle finish_reason
|
|
161
|
-
if "finish_reason" in choice and choice["finish_reason"] is not None:
|
|
162
|
-
yield FinishReason(choice["finish_reason"])
|
|
163
|
-
break
|
|
164
|
-
|
|
165
|
-
except json.JSONDecodeError:
|
|
166
|
-
continue
|
|
167
|
-
else:
|
|
168
|
-
# Handle non-streaming response (regular JSON)
|
|
169
|
-
response_text = await response.text()
|
|
170
|
-
try:
|
|
171
|
-
json_data = json.loads(response_text)
|
|
172
|
-
if "choices" in json_data and len(json_data["choices"]) > 0:
|
|
173
|
-
choice = json_data["choices"][0]
|
|
174
|
-
if "message" in choice and "content" in choice["message"]:
|
|
175
|
-
content = choice["message"]["content"]
|
|
176
|
-
if content:
|
|
177
|
-
yield content.strip()
|
|
178
|
-
|
|
179
|
-
# Handle finish_reason for non-streaming
|
|
180
|
-
if "finish_reason" in choice and choice["finish_reason"] is not None:
|
|
181
|
-
yield FinishReason(choice["finish_reason"])
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
except json.JSONDecodeError:
|
|
185
|
-
# If it's still SSE format even when stream=False, handle it
|
|
186
|
-
lines = response_text.strip().split('\n')
|
|
187
|
-
full_content = []
|
|
188
|
-
finish_reason_value = None
|
|
189
|
-
|
|
190
|
-
for line in lines:
|
|
191
|
-
if line.startswith("data: "):
|
|
192
|
-
data_str = line[6:]
|
|
193
|
-
if data_str == "[DONE]":
|
|
194
|
-
break
|
|
195
|
-
try:
|
|
196
|
-
json_data = json.loads(data_str)
|
|
197
|
-
if "choices" in json_data and len(json_data["choices"]) > 0:
|
|
198
|
-
choice = json_data["choices"][0]
|
|
199
|
-
delta = choice.get("delta", {})
|
|
200
|
-
content = delta.get("content", "")
|
|
201
|
-
if content:
|
|
202
|
-
full_content.append(content)
|
|
203
|
-
|
|
204
|
-
# Store finish_reason
|
|
205
|
-
if "finish_reason" in choice and choice["finish_reason"] is not None:
|
|
206
|
-
finish_reason_value = choice["finish_reason"]
|
|
207
|
-
|
|
208
|
-
except json.JSONDecodeError:
|
|
209
|
-
continue
|
|
210
|
-
|
|
211
|
-
if full_content:
|
|
212
|
-
yield ''.join(full_content)
|
|
213
|
-
|
|
214
|
-
if finish_reason_value:
|
|
215
|
-
yield FinishReason(finish_reason_value)
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import secrets
|
|
4
|
-
import string
|
|
5
|
-
|
|
6
|
-
from .template import OpenaiTemplate
|
|
7
|
-
|
|
8
|
-
class StringableInference(OpenaiTemplate):
|
|
9
|
-
label = "Stringable Inference"
|
|
10
|
-
url = "https://stringable-inference.onrender.com"
|
|
11
|
-
base_url = "https://stringableinf.com/api"
|
|
12
|
-
api_endpoint = "https://stringableinf.com/api/v1/chat/completions"
|
|
13
|
-
|
|
14
|
-
working = False
|
|
15
|
-
active_by_default = True
|
|
16
|
-
default_model = "deepseek-v3.2"
|
|
17
|
-
default_vision_model = "gpt-oss-120b"
|
|
18
|
-
|
|
19
|
-
@classmethod
|
|
20
|
-
def get_headers(cls, stream: bool, api_key: str = None, headers: dict = None) -> dict:
|
|
21
|
-
return {
|
|
22
|
-
"Accept": "text/event-stream" if stream else "application/json",
|
|
23
|
-
"Content-Type": "application/json",
|
|
24
|
-
"HTTP-Referer": "https://g4f.dev/",
|
|
25
|
-
"X-Title": "G4F Python",
|
|
26
|
-
**(
|
|
27
|
-
{"Authorization": f"Bearer {api_key}"}
|
|
28
|
-
if api_key else {}
|
|
29
|
-
),
|
|
30
|
-
**({} if headers is None else headers)
|
|
31
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|