XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Enhance Yupp provider with model tags management and improve response handling

b2efb16b
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

5 个文件 +83 -41
Modified g4f/Provider/Yupp.py +76 -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:
Modified g4f/cookies.py +1 -0
@@ -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":
Modified g4f/gui/server/api.py +1 -0
@@ -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]
Modified g4f/providers/base_provider.py +1 -0
@@ -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]:
Modified g4f/providers/response.py +4 -0
@@ -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