返回提交历史
Modified
g4f/Provider/Yupp.py
+76
-41
Modified
g4f/cookies.py
+1
-0
Modified
g4f/gui/server/api.py
+1
-0
Modified
g4f/providers/base_provider.py
+1
-0
Modified
g4f/providers/response.py
+4
-0
XFEstudio/gpt4free
Enhance Yupp provider with model tags management and improve response handling
b2efb16b
代码差异
5 个文件
+83
-41
@@ -3,14 +3,17 @@ import time
3
3
import uuid
4
4
import re
5
5
import os
6
from typing import Optional, Dict, Any, Generator, List
6
from typing import Iterable, Optional, Dict, Any, Generator, List
7
7
import threading
8
import requests
9
8
10
from ..providers.base_provider import AbstractProvider, ProviderModelMixin
9
from ..providers.response import Reasoning, PlainTextResponse, PreviewResponse
11
from ..providers.response import Reasoning, PlainTextResponse, PreviewResponse, JsonConversation, ImageResponse, VariantResponse
10
12
from ..errors import RateLimitError, ProviderException
11
13
from ..cookies import get_cookies
12
14
from ..tools.auth import AuthManager
13
15
from .yupp.models import YuppModelManager
16
from .helper import get_last_message
14
17
from ..debug import log
15
18
16
19
# Global variables to manage Yupp accounts (should be set by your main application)
@@ -94,6 +97,9 @@ def get_best_yupp_account() -> Optional[Dict[str, Any]]:
94
97
95
98
def format_messages_for_yupp(messages: List[Dict[str, str]]) -> str:
96
99
"""Format multi-turn conversation for Yupp single-turn format"""
100
if len(messages) == 1:
101
return messages[0].get("content", "").strip()
102
97
103
formatted = []
98
104
99
105
# Handle system messages
@@ -171,6 +177,7 @@ class Yupp(AbstractProvider, ProviderModelMixin):
171
177
manager = YuppModelManager(api_key=api_key)
172
178
models = manager.client.fetch_models()
173
179
if models:
180
cls.models_tags = {model.get("name"): manager.processor.generate_tags(model) for model in models}
174
181
cls.models = [model.get("name") for model in models]
175
182
return cls.models
176
183
@@ -181,8 +188,8 @@ class Yupp(AbstractProvider, ProviderModelMixin):
181
188
messages: List[Dict[str, str]] = None,
182
189
stream: bool = False,
183
190
api_key: Optional[str] = None,
184
temperature: float = 0.7,
185
max_tokens: int = 1000,
191
prompt: Optional[str] = None,
192
conversation: JsonConversation = None,
186
193
**kwargs,
187
194
) -> Generator[str, Any, None]:
188
195
if not api_key:
@@ -204,8 +211,16 @@ class Yupp(AbstractProvider, ProviderModelMixin):
204
211
raise ProviderException("No Yupp accounts configured. Set YUPP_API_KEY environment variable.")
205
212
206
213
# Format messages
207
question = format_messages_for_yupp(messages)
208
log_debug(f"Formatted question length: {len(question)}")
214
if conversation is None or True:
215
if prompt is None:
216
prompt = format_messages_for_yupp(messages)
217
url_uuid = str(uuid.uuid4())
218
yield JsonConversation(url_uuid=url_uuid)
219
else:
220
if prompt is None:
221
prompt = get_last_message(messages)
222
url_uuid = conversation.url_uuid
223
log_debug(f"Use url uuid: {url_uuid}, Formatted prompt length: {len(prompt)}")
209
224
210
225
# Try all accounts with rotation
211
226
max_attempts = len(YUPP_ACCOUNTS)
@@ -216,8 +231,7 @@ class Yupp(AbstractProvider, ProviderModelMixin):
216
231
217
232
try:
218
233
yield from cls._make_yupp_request(
219
account, question, model, model, stream,
220
temperature, max_tokens, **kwargs
234
account, prompt, model, url_uuid, **kwargs
221
235
)
222
236
return # Success, exit the loop
223
237
@@ -247,25 +261,24 @@ class Yupp(AbstractProvider, ProviderModelMixin):
247
261
cls,
248
262
account: Dict[str, Any],
249
263
question: str,
250
model_name: str,
251
264
model_id: str,
252
stream: bool,
253
temperature: float,
254
max_tokens: int,
265
url_uuid: Optional[str] = None,
266
next_action: str = "7f2a2308b5fc462a2c26df714cb2cccd02a9c10fbb",
255
267
**kwargs
256
268
) -> Generator[str, Any, None]:
257
269
"""Make actual request to Yupp.ai"""
258
270
259
271
# Build request
260
url_uuid = str(uuid.uuid4())
272
if url_uuid is None:
273
url_uuid = str(uuid.uuid4())
261
274
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
262
275
263
276
headers = {
264
277
"accept": "text/x-component",
265
"accept-language": "de,en-US;q=0.9,en;q=0.8,zh-CN;q=0.7,zh;q=0.6",
278
"accept-language": "en-US",
266
279
"cache-control": "no-cache",
267
280
"content-type": "text/plain;charset=UTF-8",
268
"next-action": "7f2a2308b5fc462a2c26df714cb2cccd02a9c10fbb",
281
"next-action": next_action,
269
282
"pragma": "no-cache",
270
283
"priority": "u=1, i",
271
284
"sec-ch-ua": "\"Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Google Chrome\";v=\"140\"",
@@ -277,7 +290,7 @@ class Yupp(AbstractProvider, ProviderModelMixin):
277
290
"cookie": f"__Secure-yupp.session-token={account['token']}",
278
291
}
279
292
280
log_debug(f"Sending request to Yupp.ai with account ...{account['token'][-4:]}")
293
log_debug(f"Sending request to Yupp.ai with account: {account['token'][:10]}...")
281
294
282
295
payload = [
283
296
url_uuid,
@@ -287,7 +300,7 @@ class Yupp(AbstractProvider, ProviderModelMixin):
287
300
"$undefined",
288
301
[],
289
302
"$undefined",
290
[{"modelName": model_name, "promptModifierId": "$undefined"}] if model_name else "none",
303
[{"modelName": model_id, "promptModifierId": "$undefined"}] if model_id else "none",
291
304
"text",
292
305
False,
293
306
"$undefined",
@@ -305,14 +318,16 @@ class Yupp(AbstractProvider, ProviderModelMixin):
305
318
response.raise_for_status()
306
319
307
320
yield from cls._process_stream_response(
308
response.iter_lines(), account
321
response.iter_lines(), account, session, question
309
322
)
310
323
311
324
@classmethod
312
325
def _process_stream_response(
313
326
cls,
314
response_lines,
315
account: Dict[str, Any]
327
response_lines: Iterable[bytes],
328
account: Dict[str, Any],
329
session: requests.Session,
330
prompt: Optional[str] = None,
316
331
) -> Generator[str, Any, None]:
317
332
"""Process Yupp stream response and convert to OpenAI format"""
318
333
@@ -324,7 +339,6 @@ class Yupp(AbstractProvider, ProviderModelMixin):
324
339
thinking_content = ""
325
340
normal_content = ""
326
341
select_stream = [None, None]
327
processed_content = set()
328
342
329
343
def extract_ref_id(ref):
330
344
"""Extract ID from reference string, e.g., from '$@123' extract '123'"""
@@ -335,27 +349,23 @@ class Yupp(AbstractProvider, ProviderModelMixin):
335
349
if not content or content in [None, "", "$undefined"]:
336
350
return False
337
351
338
if content.startswith("\\n\\<streaming stopped") or content.startswith("\n\\<streaming stopped"):
339
return False
340
341
if re.match(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", content.strip()):
342
return False
343
344
if len(content.strip()) == 0:
345
return False
346
347
if content.strip() in ["$undefined", "undefined", "null", "NULL"]:
348
return False
349
350
352
return True
351
353
352
354
def process_content_chunk(content: str, chunk_id: str, line_count: int):
353
355
"""Process single content chunk"""
354
nonlocal is_thinking, thinking_content, normal_content
356
nonlocal is_thinking, thinking_content, normal_content, session
355
357
356
358
if not is_valid_content(content):
357
359
return
358
360
361
if '<yapp class="image-gen">' in content:
362
content = content.split('<yapp class="image-gen">').pop().split('</yapp>')[0]
363
url = f"https://yupp.ai/api/trpc/chat.getSignedImage"
364
response = session.get(url, params={"batch": "1", "input": json.dumps({"0": {"json": {"imageId": json.loads(content).get("image_id")}}})})
365
response.raise_for_status()
366
yield ImageResponse(response.json()[0]["result"]["data"]["json"]["signed_url"], prompt)
367
return
368
359
369
# log_debug(f"Processing chunk #{line_count} with content: '{content[:50]}...'")
360
370
361
371
if is_thinking:
@@ -368,6 +378,10 @@ class Yupp(AbstractProvider, ProviderModelMixin):
368
378
# log_debug("Starting to process Yupp stream response...")
369
379
line_count = 0
370
380
quick_response_id = None
381
variant_stream_id = None
382
found_image: Optional[ImageResponse] = None
383
variant_image: Optional[ImageResponse] = None
384
variant_text = ""
371
385
372
386
for line in response_lines:
373
387
@@ -407,10 +421,11 @@ class Yupp(AbstractProvider, ProviderModelMixin):
407
421
if isinstance(data, dict):
408
422
for i, selection in enumerate(data.get("modelSelections", [])):
409
423
if selection.get("selectionSource") == "USER_SELECTED":
410
if i < len(select_stream) and isinstance(select_stream[i], dict):
411
target_stream_id = extract_ref_id(select_stream[i].get("next"))
412
log_debug(f"Found target stream ID: {target_stream_id}")
413
break
424
target_stream_id = extract_ref_id(select_stream[i].get("next"))
425
log_debug(f"Found target stream ID: {target_stream_id}")
426
else:
427
variant_stream_id = extract_ref_id(select_stream[i].get("next"))
428
log_debug(f"Found variant stream ID: {variant_stream_id}")
414
429
415
430
# Process target stream content
416
431
elif target_stream_id and chunk_id == target_stream_id:
@@ -419,10 +434,27 @@ class Yupp(AbstractProvider, ProviderModelMixin):
419
434
target_stream_id = extract_ref_id(data.get("next"))
420
435
content = data.get("curr", "")
421
436
if content:
422
yield from process_content_chunk(content, chunk_id, line_count)
437
for chunk in process_content_chunk(content, chunk_id, line_count):
438
if isinstance(chunk, ImageResponse):
439
found_image = chunk
440
yield chunk
441
442
elif variant_stream_id and chunk_id == variant_stream_id:
443
yield PlainTextResponse("[Variant] " + line.decode(errors="ignore"))
444
if isinstance(data, dict):
445
variant_stream_id = extract_ref_id(data.get("next"))
446
content = data.get("curr", "")
447
if content:
448
for chunk in process_content_chunk(content, chunk_id, line_count):
449
if isinstance(chunk, ImageResponse):
450
variant_image = chunk
451
yield PreviewResponse(str(variant_image))
452
elif found_image is None:
453
variant_text += str(chunk)
454
yield PreviewResponse(variant_text)
423
455
424
456
elif quick_response_id and chunk_id == quick_response_id:
425
yield PlainTextResponse(line.decode(errors="ignore"))
457
yield PlainTextResponse("[Quick] " + line.decode(errors="ignore"))
426
458
if isinstance(data, dict):
427
459
content = data.get("curr", "")
428
460
if content:
@@ -432,8 +464,11 @@ class Yupp(AbstractProvider, ProviderModelMixin):
432
464
elif isinstance(data, dict) and "curr" in data:
433
465
content = data.get("curr", "")
434
466
if content:
435
pass #yield from process_content_chunk(content, chunk_id)
467
yield PlainTextResponse("[Extra] " + line.decode(errors="ignore"))
436
468
469
if variant_image is not None:
470
yield variant_image
471
437
472
log_debug(f"Finished processing {line_count} lines")
438
473
439
474
except:
@@ -64,6 +64,7 @@ DOMAINS = (
64
64
"chatgpt.com",
65
65
".cerebras.ai",
66
66
"github.com",
67
"yupp.ai",
67
68
)
68
69
69
70
if has_browser_cookie3 and os.environ.get("DBUS_SESSION_BUS_ADDRESS") == "/dev/null":
@@ -59,6 +59,7 @@ class Api:
59
59
"video": model in provider.video_models,
60
60
"image": model in provider.image_models,
61
61
"count": False if provider.models_count is None else provider.models_count.get(model),
62
"tags": [] if provider.models_tags is None else provider.models_tags.get(model, []),
62
63
}
63
64
if provider in Provider.__map__:
64
65
provider = Provider.__map__[provider]
@@ -373,6 +373,7 @@ class ProviderModelMixin:
373
373
audio_models: dict = {}
374
374
last_model: str = None
375
375
models_loaded: bool = False
376
models_tags: dict[str, list[str]] = None
376
377
377
378
@classmethod
378
379
def get_models(cls, api_key: str = None, **kwargs) -> list[str]:
@@ -238,6 +238,10 @@ class PlainTextResponse(HiddenResponse):
238
238
def __init__(self, text: str) -> None:
239
239
self.text = text
240
240
241
class VariantResponse(HiddenResponse):
242
def __init__(self, text: str) -> None:
243
self.text = text
244
241
245
class ContinueResponse(HiddenResponse):
242
246
def __init__(self, text: str) -> None:
243
247
self.text = text