from __future__ import annotations import asyncio import aiohttp import random import string import json from urllib.parse import urlencode from aiohttp import ClientSession from ..typing import AsyncResult, Messages from .base_provider import AsyncGeneratorProvider, ProviderModelMixin from .helper import format_prompt class RubiksAI(AsyncGeneratorProvider, ProviderModelMixin): label = "Rubiks AI" url = "https://rubiks.ai" api_endpoint = "https://rubiks.ai/search/api.php" working = True supports_stream = True supports_system_message = True supports_message_history = True default_model = 'llama-3.1-70b-versatile' models = [default_model, 'gpt-4o-mini'] model_aliases = { "llama-3.1-70b": "llama-3.1-70b-versatile", } @classmethod def get_model(cls, model: str) -> str: if model in cls.models: return model elif model in cls.model_aliases: return cls.model_aliases[model] else: return cls.default_model @staticmethod def generate_mid() -> str: """ Generates a 'mid' string following the pattern: 6 characters - 4 characters - 4 characters - 4 characters - 12 characters Example: 0r7v7b-quw4-kdy3-rvdu-ekief6xbuuq4 """ parts = [ ''.join(random.choices(string.ascii_lowercase + string.digits, k=6)), ''.join(random.choices(string.ascii_lowercase + string.digits, k=4)), ''.join(random.choices(string.ascii_lowercase + string.digits, k=4)), ''.join(random.choices(string.ascii_lowercase + string.digits, k=4)), ''.join(random.choices(string.ascii_lowercase + string.digits, k=12)) ] return '-'.join(parts) @staticmethod def create_referer(q: str, mid: str, model: str = '') -> str: """ Creates a Referer URL with dynamic q and mid values, using urlencode for safe parameter encoding. """ params = {'q': q, 'model': model, 'mid': mid} encoded_params = urlencode(params) return f'https://rubiks.ai/search/?{encoded_params}' @classmethod async def create_async_generator( cls, model: str, messages: Messages, proxy: str = None, websearch: bool = False, **kwargs ) -> AsyncResult: """ Creates an asynchronous generator that sends requests to the Rubiks AI API and yields the response. Parameters: - model (str): The model to use in the request. - messages (Messages): The messages to send as a prompt. - proxy (str, optional): Proxy URL, if needed. - websearch (bool, optional): Indicates whether to include search sources in the response. Defaults to False. """ model = cls.get_model(model) prompt = format_prompt(messages) q_value = prompt mid_value = cls.generate_mid() referer = cls.create_referer(q=q_value, mid=mid_value, model=model) url = cls.api_endpoint params = { 'q': q_value, 'model': model, 'id': '', 'mid': mid_value } headers = { 'Accept': 'text/event-stream', 'Accept-Language': 'en-US,en;q=0.9', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Pragma': 'no-cache', 'Referer': referer, 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36', 'sec-ch-ua': '"Chromium";v="129", "Not=A?Brand";v="8"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Linux"' } try: timeout = aiohttp.ClientTimeout(total=None) async with ClientSession(timeout=timeout) as session: async with session.get(url, headers=headers, params=params, proxy=proxy) as response: if response.status != 200: yield f"Request ended with status code {response.status}" return assistant_text = '' sources = [] async for line in response.content: decoded_line = line.decode('utf-8').strip() if not decoded_line.startswith('data: '): continue data = decoded_line[6:] if data in ('[DONE]', '{"done": ""}'): break try: json_data = json.loads(data) except json.JSONDecodeError: continue if 'url' in json_data and 'title' in json_data: if websearch: sources.append({'title': json_data['title'], 'url': json_data['url']}) elif 'choices' in json_data: for choice in json_data['choices']: delta = choice.get('delta', {}) content = delta.get('content', '') role = delta.get('role', '') if role == 'assistant': continue assistant_text += content if websearch and sources: sources_text = '\n'.join([f"{i+1}. [{s['title']}]: {s['url']}" for i, s in enumerate(sources)]) assistant_text += f"\n\n**Source:**\n{sources_text}" yield assistant_text except asyncio.CancelledError: yield "The request was cancelled." except aiohttp.ClientError as e: yield f"An error occurred during the request: {e}" except Exception as e: yield f"An unexpected error occurred: {e}"