RubiksAI.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. from __future__ import annotations
  2. import random
  3. import string
  4. import json
  5. from urllib.parse import urlencode
  6. from aiohttp import ClientSession
  7. from ..typing import AsyncResult, Messages
  8. from .base_provider import AsyncGeneratorProvider, ProviderModelMixin, Sources
  9. from ..requests.raise_for_status import raise_for_status
  10. class RubiksAI(AsyncGeneratorProvider, ProviderModelMixin):
  11. label = "Rubiks AI"
  12. url = "https://rubiks.ai"
  13. api_endpoint = "https://rubiks.ai/search/api/"
  14. working = True
  15. supports_stream = True
  16. supports_system_message = True
  17. supports_message_history = True
  18. default_model = 'gpt-4o-mini'
  19. models = [default_model, 'gpt-4o', 'o1-mini', 'claude-3.5-sonnet', 'grok-beta', 'gemini-1.5-pro', 'nova-pro', "llama-3.1-70b-versatile"]
  20. model_aliases = {
  21. "llama-3.1-70b": "llama-3.1-70b-versatile",
  22. }
  23. @staticmethod
  24. def generate_mid() -> str:
  25. """
  26. Generates a 'mid' string following the pattern:
  27. 6 characters - 4 characters - 4 characters - 4 characters - 12 characters
  28. Example: 0r7v7b-quw4-kdy3-rvdu-ekief6xbuuq4
  29. """
  30. parts = [
  31. ''.join(random.choices(string.ascii_lowercase + string.digits, k=6)),
  32. ''.join(random.choices(string.ascii_lowercase + string.digits, k=4)),
  33. ''.join(random.choices(string.ascii_lowercase + string.digits, k=4)),
  34. ''.join(random.choices(string.ascii_lowercase + string.digits, k=4)),
  35. ''.join(random.choices(string.ascii_lowercase + string.digits, k=12))
  36. ]
  37. return '-'.join(parts)
  38. @staticmethod
  39. def create_referer(q: str, mid: str, model: str = '') -> str:
  40. """
  41. Creates a Referer URL with dynamic q and mid values, using urlencode for safe parameter encoding.
  42. """
  43. params = {'q': q, 'model': model, 'mid': mid}
  44. encoded_params = urlencode(params)
  45. return f'https://rubiks.ai/search/?{encoded_params}'
  46. @classmethod
  47. async def create_async_generator(
  48. cls,
  49. model: str,
  50. messages: Messages,
  51. proxy: str = None,
  52. web_search: bool = False,
  53. temperature: float = 0.6,
  54. **kwargs
  55. ) -> AsyncResult:
  56. """
  57. Creates an asynchronous generator that sends requests to the Rubiks AI API and yields the response.
  58. Parameters:
  59. - model (str): The model to use in the request.
  60. - messages (Messages): The messages to send as a prompt.
  61. - proxy (str, optional): Proxy URL, if needed.
  62. - web_search (bool, optional): Indicates whether to include search sources in the response. Defaults to False.
  63. """
  64. model = cls.get_model(model)
  65. mid_value = cls.generate_mid()
  66. referer = cls.create_referer(q=messages[-1]["content"], mid=mid_value, model=model)
  67. data = {
  68. "messages": messages,
  69. "model": model,
  70. "search": web_search,
  71. "stream": True,
  72. "temperature": temperature
  73. }
  74. headers = {
  75. 'Accept': 'text/event-stream',
  76. 'Accept-Language': 'en-US,en;q=0.9',
  77. 'Cache-Control': 'no-cache',
  78. 'Connection': 'keep-alive',
  79. 'Pragma': 'no-cache',
  80. 'Referer': referer,
  81. 'Sec-Fetch-Dest': 'empty',
  82. 'Sec-Fetch-Mode': 'cors',
  83. 'Sec-Fetch-Site': 'same-origin',
  84. 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
  85. 'sec-ch-ua': '"Chromium";v="129", "Not=A?Brand";v="8"',
  86. 'sec-ch-ua-mobile': '?0',
  87. 'sec-ch-ua-platform': '"Linux"'
  88. }
  89. async with ClientSession() as session:
  90. async with session.post(cls.api_endpoint, headers=headers, json=data, proxy=proxy) as response:
  91. await raise_for_status(response)
  92. sources = []
  93. async for line in response.content:
  94. decoded_line = line.decode('utf-8').strip()
  95. if not decoded_line.startswith('data: '):
  96. continue
  97. data = decoded_line[6:]
  98. if data in ('[DONE]', '{"done": ""}'):
  99. break
  100. try:
  101. json_data = json.loads(data)
  102. except json.JSONDecodeError:
  103. continue
  104. if 'url' in json_data and 'title' in json_data:
  105. if web_search:
  106. sources.append(json_data)
  107. elif 'choices' in json_data:
  108. for choice in json_data['choices']:
  109. delta = choice.get('delta', {})
  110. content = delta.get('content', '')
  111. if content:
  112. yield content
  113. if web_search and sources:
  114. yield Sources(sources)