From 9def1aa71f5c0340967297a94b7742c8d7c7fd8d Mon Sep 17 00:00:00 2001 From: kqlio67 <166700875+kqlio67@users.noreply.github.com> Date: Fri, 24 Jan 2025 02:47:57 +0000 Subject: Update model configurations, provider implementations, and documentation (#2577) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update model configurations, provider implementations, and documentation - Updated model names and aliases for Qwen QVQ 72B and Qwen 2 72B (@TheFirstNoob) - Revised HuggingSpace class configuration, added default_image_model - Added llama-3.2-70b alias for Llama 3.2 70B model in AutonomousAI - Removed BlackboxCreateAgent class - Added gpt-4o alias for Copilot model - Moved api_key to Mhystical class attribute - Added models property with default_model value for Free2GPT - Simplified Jmuz class implementation - Improved image generation and model handling in DeepInfra - Standardized default models and removed aliases in Gemini - Replaced model aliases with direct model list in GlhfChat (@TheFirstNoob) - Removed trailing slash from image generation URL in PollinationsAI (https://github.com/xtekky/gpt4free/issues/2571) - Updated llama and qwen model configurations - Enhanced provider documentation and model details * Removed from (g4f/models.py) 'Yqcloud' provider from Default due to error 'ResponseStatusError: Response 429: 文字过长,请删减后重试。' * Update docs/providers-and-models.md * refactor(g4f/Provider/DDG.py): Add error handling and rate limiting to DDG provider - Add custom exception classes for rate limits, timeouts, and conversation limits - Implement rate limiting with sleep between requests (0.75s minimum delay) - Add model validation method to check supported models - Add proper error handling for API responses with custom exceptions - Improve session cookie handling for conversation persistence - Clean up User-Agent string and remove redundant code - Add proper error propagation through async generator Breaking changes: - New custom exceptions may require updates to error handling code - Rate limiting affects request timing and throughput - Model validation is now stricter Related: - Adds error handling similar to standard API clients - Improves reliability and robustness of chat interactions * Update g4f/models.py g4f/Provider/PollinationsAI.py * Update g4f/models.py * Restored provider which was not working and was disabled (g4f/Provider/DeepInfraChat.py) * Fixing a bug with Streaming Completions * Update g4f/Provider/PollinationsAI.py * Update g4f/Provider/Blackbox.py g4f/Provider/DDG.py * Added another model for generating images 'ImageGeneration2' to the 'Blackbox' provider * Update docs/providers-and-models.md * Update g4f/models.py g4f/Provider/Blackbox.py * Added a new OIVSCode provider from the Text Models and Vision (Image Upload) model * Update docs/providers-and-models.md * docs: add Conversation Memory class with context handling requested by @TheFirstNoob * Simplified README.md documentation added new docs/configuration.md documentation * Update add README.md docs/configuration.md * Update README.md * Update docs/providers-and-models.md g4f/models.py g4f/Provider/PollinationsAI.py * Added new model deepseek-r1 to Blackbox provider. @TheFirstNoob * Fixed bugs and updated docs/providers-and-models.md etc/unittest/client.py g4f/models.py g4f/Provider/. --------- Co-authored-by: kqlio67 <> Co-authored-by: H Lohaus --- g4f/Provider/DDG.py | 176 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 126 insertions(+), 50 deletions(-) (limited to 'g4f/Provider/DDG.py') diff --git a/g4f/Provider/DDG.py b/g4f/Provider/DDG.py index fbe0ad4b..254901f8 100644 --- a/g4f/Provider/DDG.py +++ b/g4f/Provider/DDG.py @@ -1,19 +1,33 @@ from __future__ import annotations +import time from aiohttp import ClientSession, ClientTimeout import json import asyncio import random -from ..typing import AsyncResult, Messages +from ..typing import AsyncResult, Messages, Cookies from ..requests.raise_for_status import raise_for_status from .base_provider import AsyncGeneratorProvider, ProviderModelMixin from .helper import format_prompt from ..providers.response import FinishReason, JsonConversation +class DuckDuckGoSearchException(Exception): + """Base exception class for duckduckgo_search.""" + +class RatelimitException(DuckDuckGoSearchException): + """Raised for rate limit exceeded errors during API requests.""" + +class TimeoutException(DuckDuckGoSearchException): + """Raised for timeout errors during API requests.""" + +class ConversationLimitException(DuckDuckGoSearchException): + """Raised for conversation limit during API requests to AI endpoint.""" + class Conversation(JsonConversation): vqd: str = None message_history: Messages = [] + cookies: dict = {} def __init__(self, model: str): self.model = model @@ -39,20 +53,40 @@ class DDG(AsyncGeneratorProvider, ProviderModelMixin): "mixtral-8x7b": "mistralai/Mixtral-8x7B-Instruct-v0.1", } + last_request_time = 0 + + @classmethod + def validate_model(cls, model: str) -> str: + """Validates and returns the correct model name""" + if model in cls.model_aliases: + model = cls.model_aliases[model] + if model not in cls.models: + raise ValueError(f"Model {model} not supported. Available models: {cls.models}") + return model + + @classmethod + async def sleep(cls): + """Implements rate limiting between requests""" + now = time.time() + if cls.last_request_time > 0: + delay = max(0.0, 0.75 - (now - cls.last_request_time)) + if delay > 0: + await asyncio.sleep(delay) + cls.last_request_time = now + @classmethod async def fetch_vqd(cls, session: ClientSession, max_retries: int = 3) -> str: - """ - Fetches the required VQD token for the chat session with retries. - """ + """Fetches the required VQD token for the chat session with retries.""" headers = { "accept": "text/event-stream", - "content-type": "application/json", + "content-type": "application/json", "x-vqd-accept": "1", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36" + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } for attempt in range(max_retries): try: + await cls.sleep() async with session.get(cls.status_url, headers=headers) as response: if response.status == 200: vqd = response.headers.get("x-vqd-4", "") @@ -81,50 +115,92 @@ class DDG(AsyncGeneratorProvider, ProviderModelMixin): messages: Messages, proxy: str = None, timeout: int = 30, + cookies: Cookies = None, conversation: Conversation = None, return_conversation: bool = False, **kwargs - ) -> AsyncResult: - model = cls.get_model(model) - async with ClientSession(timeout=ClientTimeout(total=timeout)) as session: - # Fetch VQD token - if conversation is None: - conversation = Conversation(model) - conversation.vqd = await cls.fetch_vqd(session) - conversation.message_history = [{"role": "user", "content": format_prompt(messages)}] - else: - conversation.message_history.append(messages[-1]) - headers = { - "accept": "text/event-stream", - "content-type": "application/json", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", - "x-vqd-4": conversation.vqd, - } - data = { - "model": model, - "messages": conversation.message_history, - } - async with session.post(cls.api_endpoint, json=data, headers=headers, proxy=proxy) as response: - await raise_for_status(response) - reason = None - full_message = "" - async for line in response.content: - line = line.decode("utf-8").strip() - if line.startswith("data:"): - try: - message = json.loads(line[5:].strip()) - if "message" in message: - if message["message"]: - yield message["message"] - full_message += message["message"] - reason = "length" - else: - reason = "stop" - except json.JSONDecodeError: - continue - if return_conversation: - conversation.message_history.append({"role": "assistant", "content": full_message}) - conversation.vqd = response.headers.get("x-vqd-4", conversation.vqd) - yield conversation - if reason is not None: - yield FinishReason(reason) + ) -> AsyncResult: + model = cls.validate_model(model) + + if cookies is None and conversation is not None: + cookies = conversation.cookies + + try: + async with ClientSession(timeout=ClientTimeout(total=timeout), cookies=cookies) as session: + if conversation is None: + conversation = Conversation(model) + conversation.vqd = await cls.fetch_vqd(session) + conversation.message_history = [{"role": "user", "content": format_prompt(messages)}] + else: + conversation.message_history.append(messages[-1]) + + headers = { + "accept": "text/event-stream", + "content-type": "application/json", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "x-vqd-4": conversation.vqd, + } + + data = { + "model": model, + "messages": conversation.message_history, + } + + await cls.sleep() + try: + async with session.post(cls.api_endpoint, json=data, headers=headers, proxy=proxy) as response: + await raise_for_status(response) + reason = None + full_message = "" + + async for line in response.content: + line = line.decode("utf-8").strip() + if line.startswith("data:"): + try: + message = json.loads(line[5:].strip()) + + if "action" in message and message["action"] == "error": + error_type = message.get("type", "") + if message.get("status") == 429: + if error_type == "ERR_CONVERSATION_LIMIT": + raise ConversationLimitException(error_type) + raise RatelimitException(error_type) + raise DuckDuckGoSearchException(error_type) + + if "message" in message: + if message["message"]: + yield message["message"] + full_message += message["message"] + reason = "length" + else: + reason = "stop" + except json.JSONDecodeError: + continue + + if return_conversation: + conversation.message_history.append({"role": "assistant", "content": full_message}) + conversation.vqd = response.headers.get("x-vqd-4", conversation.vqd) + conversation.cookies = { + n: c.value + for n, c in session.cookie_jar.filter_cookies(cls.url).items() + } + + if reason is not None: + yield FinishReason(reason) + + if return_conversation: + yield conversation + + except asyncio.TimeoutError as e: + raise TimeoutException(f"Request timed out: {str(e)}") + except Exception as e: + if "time" in str(e).lower(): + raise TimeoutException(f"Request timed out: {str(e)}") + raise DuckDuckGoSearchException(f"Request failed: {str(e)}") + + except Exception as e: + if isinstance(e, (RatelimitException, TimeoutException, ConversationLimitException)): + raise + if "time" in str(e).lower(): + raise TimeoutException(f"Request timed out: {str(e)}") + raise DuckDuckGoSearchException(f"Request failed: {str(e)}") -- cgit v1.2.3