HuggingChat.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. from __future__ import annotations
  2. import json
  3. try:
  4. from curl_cffi.requests import Session, CurlMime
  5. has_curl_cffi = True
  6. except ImportError:
  7. has_curl_cffi = False
  8. from ...typing import CreateResult, Messages, Cookies
  9. from ...errors import MissingRequirementsError
  10. from ...requests.raise_for_status import raise_for_status
  11. from ...cookies import get_cookies
  12. from ...image import ImageResponse
  13. from ..base_provider import ProviderModelMixin, AbstractProvider, BaseConversation
  14. from ..helper import format_prompt
  15. from ... import debug
  16. class Conversation(BaseConversation):
  17. def __init__(self, conversation_id: str, message_id: str):
  18. self.conversation_id = conversation_id
  19. self.message_id = message_id
  20. class HuggingChat(AbstractProvider, ProviderModelMixin):
  21. url = "https://huggingface.co/chat"
  22. working = True
  23. supports_stream = True
  24. needs_auth = True
  25. default_model = "Qwen/Qwen2.5-72B-Instruct"
  26. default_image_model = "black-forest-labs/FLUX.1-dev"
  27. image_models = [
  28. "black-forest-labs/FLUX.1-dev"
  29. ]
  30. models = [
  31. default_model,
  32. 'meta-llama/Llama-3.3-70B-Instruct',
  33. 'CohereForAI/c4ai-command-r-plus-08-2024',
  34. 'Qwen/QwQ-32B-Preview',
  35. 'nvidia/Llama-3.1-Nemotron-70B-Instruct-HF',
  36. 'Qwen/Qwen2.5-Coder-32B-Instruct',
  37. 'meta-llama/Llama-3.2-11B-Vision-Instruct',
  38. 'NousResearch/Hermes-3-Llama-3.1-8B',
  39. 'mistralai/Mistral-Nemo-Instruct-2407',
  40. 'microsoft/Phi-3.5-mini-instruct',
  41. *image_models
  42. ]
  43. model_aliases = {
  44. ### Chat ###
  45. "qwen-2.5-72b": "Qwen/Qwen2.5-72B-Instruct",
  46. "llama-3.3-70b": "meta-llama/Llama-3.3-70B-Instruct",
  47. "command-r-plus": "CohereForAI/c4ai-command-r-plus-08-2024",
  48. "qwq-32b": "Qwen/QwQ-32B-Preview",
  49. "nemotron-70b": "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF",
  50. "qwen-2.5-coder-32b": "Qwen/Qwen2.5-Coder-32B-Instruct",
  51. "llama-3.2-11b": "meta-llama/Llama-3.2-11B-Vision-Instruct",
  52. "hermes-3": "NousResearch/Hermes-3-Llama-3.1-8B",
  53. "mistral-nemo": "mistralai/Mistral-Nemo-Instruct-2407",
  54. "phi-3.5-mini": "microsoft/Phi-3.5-mini-instruct",
  55. ### Image ###
  56. "flux-dev": "black-forest-labs/FLUX.1-dev",
  57. }
  58. @classmethod
  59. def create_completion(
  60. cls,
  61. model: str,
  62. messages: Messages,
  63. stream: bool,
  64. return_conversation: bool = False,
  65. conversation: Conversation = None,
  66. web_search: bool = False,
  67. cookies: Cookies = None,
  68. **kwargs
  69. ) -> CreateResult:
  70. if not has_curl_cffi:
  71. raise MissingRequirementsError('Install "curl_cffi" package | pip install -U curl_cffi')
  72. model = cls.get_model(model)
  73. if cookies is None:
  74. cookies = get_cookies("huggingface.co")
  75. session = Session(cookies=cookies)
  76. session.headers = {
  77. 'accept': '*/*',
  78. 'accept-language': 'en',
  79. 'cache-control': 'no-cache',
  80. 'origin': 'https://huggingface.co',
  81. 'pragma': 'no-cache',
  82. 'priority': 'u=1, i',
  83. 'referer': 'https://huggingface.co/chat/',
  84. 'sec-ch-ua': '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',
  85. 'sec-ch-ua-mobile': '?0',
  86. 'sec-ch-ua-platform': '"macOS"',
  87. 'sec-fetch-dest': 'empty',
  88. 'sec-fetch-mode': 'cors',
  89. 'sec-fetch-site': 'same-origin',
  90. 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
  91. }
  92. if conversation is None:
  93. conversationId = cls.create_conversation(session, model)
  94. messageId = cls.fetch_message_id(session, conversationId)
  95. conversation = Conversation(conversationId, messageId)
  96. if return_conversation:
  97. yield conversation
  98. inputs = format_prompt(messages)
  99. else:
  100. conversation.message_id = cls.fetch_message_id(session, conversation.conversation_id)
  101. inputs = messages[-1]["content"]
  102. debug.log(f"Use conversation: {conversation.conversation_id} Use message: {conversation.message_id}")
  103. settings = {
  104. "inputs": inputs,
  105. "id": conversation.message_id,
  106. "is_retry": False,
  107. "is_continue": False,
  108. "web_search": web_search,
  109. "tools": ["000000000000000000000001"] if model in cls.image_models else [],
  110. }
  111. headers = {
  112. 'accept': '*/*',
  113. 'accept-language': 'en',
  114. 'cache-control': 'no-cache',
  115. 'origin': 'https://huggingface.co',
  116. 'pragma': 'no-cache',
  117. 'priority': 'u=1, i',
  118. 'referer': f'https://huggingface.co/chat/conversation/{conversation.conversation_id}',
  119. 'sec-ch-ua': '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',
  120. 'sec-ch-ua-mobile': '?0',
  121. 'sec-ch-ua-platform': '"macOS"',
  122. 'sec-fetch-dest': 'empty',
  123. 'sec-fetch-mode': 'cors',
  124. 'sec-fetch-site': 'same-origin',
  125. 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
  126. }
  127. data = CurlMime()
  128. data.addpart('data', data=json.dumps(settings, separators=(',', ':')))
  129. response = session.post(
  130. f'https://huggingface.co/chat/conversation/{conversation.conversation_id}',
  131. cookies=session.cookies,
  132. headers=headers,
  133. multipart=data,
  134. stream=True
  135. )
  136. raise_for_status(response)
  137. full_response = ""
  138. for line in response.iter_lines():
  139. if not line:
  140. continue
  141. try:
  142. line = json.loads(line)
  143. except json.JSONDecodeError as e:
  144. print(f"Failed to decode JSON: {line}, error: {e}")
  145. continue
  146. if "type" not in line:
  147. raise RuntimeError(f"Response: {line}")
  148. elif line["type"] == "stream":
  149. token = line["token"].replace('\u0000', '')
  150. full_response += token
  151. if stream:
  152. yield token
  153. elif line["type"] == "finalAnswer":
  154. break
  155. elif line["type"] == "file":
  156. url = f"https://huggingface.co/chat/conversation/{conversation.conversation_id}/output/{line['sha']}"
  157. yield ImageResponse(url, alt=messages[-1]["content"], options={"cookies": cookies})
  158. full_response = full_response.replace('<|im_end|', '').replace('\u0000', '').strip()
  159. if not stream:
  160. yield full_response
  161. @classmethod
  162. def create_conversation(cls, session: Session, model: str):
  163. if model in cls.image_models:
  164. model = cls.default_model
  165. json_data = {
  166. 'model': model,
  167. }
  168. response = session.post('https://huggingface.co/chat/conversation', json=json_data)
  169. raise_for_status(response)
  170. return response.json().get('conversationId')
  171. @classmethod
  172. def fetch_message_id(cls, session: Session, conversation_id: str):
  173. # Get the data response and parse it properly
  174. response = session.get(f'https://huggingface.co/chat/conversation/{conversation_id}/__data.json?x-sveltekit-invalidated=11')
  175. raise_for_status(response)
  176. # Split the response content by newlines and parse each line as JSON
  177. try:
  178. json_data = None
  179. for line in response.text.split('\n'):
  180. if line.strip():
  181. try:
  182. parsed = json.loads(line)
  183. if isinstance(parsed, dict) and "nodes" in parsed:
  184. json_data = parsed
  185. break
  186. except json.JSONDecodeError:
  187. continue
  188. if not json_data:
  189. raise RuntimeError("Failed to parse response data")
  190. data = json_data["nodes"][1]["data"]
  191. keys = data[data[0]["messages"]]
  192. message_keys = data[keys[-1]]
  193. return data[message_keys["id"]]
  194. except (KeyError, IndexError, TypeError) as e:
  195. raise RuntimeError(f"Failed to extract message ID: {str(e)}")