PerplexityLabs.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. from __future__ import annotations
  2. import random
  3. import json
  4. from ..typing import AsyncResult, Messages
  5. from ..requests import StreamSession, raise_for_status
  6. from ..errors import ResponseError
  7. from ..providers.response import FinishReason, Sources
  8. from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
  9. API_URL = "https://www.perplexity.ai/socket.io/"
  10. WS_URL = "wss://www.perplexity.ai/socket.io/"
  11. class PerplexityLabs(AsyncGeneratorProvider, ProviderModelMixin):
  12. label = "Perplexity Labs"
  13. url = "https://labs.perplexity.ai"
  14. working = True
  15. active_by_default = True
  16. default_model = "r1-1776"
  17. models = [
  18. default_model,
  19. "sonar-pro",
  20. "sonar",
  21. "sonar-reasoning",
  22. "sonar-reasoning-pro",
  23. ]
  24. @classmethod
  25. async def create_async_generator(
  26. cls,
  27. model: str,
  28. messages: Messages,
  29. proxy: str = None,
  30. **kwargs
  31. ) -> AsyncResult:
  32. headers = {
  33. "Origin": cls.url,
  34. "Referer": f"{cls.url}/",
  35. }
  36. async with StreamSession(headers=headers, proxy=proxy, impersonate="chrome") as session:
  37. t = format(random.getrandbits(32), "08x")
  38. async with session.get(
  39. f"{API_URL}?EIO=4&transport=polling&t={t}"
  40. ) as response:
  41. await raise_for_status(response)
  42. text = await response.text()
  43. assert text.startswith("0")
  44. sid = json.loads(text[1:])["sid"]
  45. post_data = '40{"jwt":"anonymous-ask-user"}'
  46. async with session.post(
  47. f"{API_URL}?EIO=4&transport=polling&t={t}&sid={sid}",
  48. data=post_data
  49. ) as response:
  50. await raise_for_status(response)
  51. assert await response.text() == "OK"
  52. async with session.get(
  53. f"{API_URL}?EIO=4&transport=polling&t={t}&sid={sid}",
  54. data=post_data
  55. ) as response:
  56. await raise_for_status(response)
  57. assert (await response.text()).startswith("40")
  58. async with session.ws_connect(f"{WS_URL}?EIO=4&transport=websocket&sid={sid}", autoping=False) as ws:
  59. await ws.send_str("2probe")
  60. assert(await ws.receive_str() == "3probe")
  61. await ws.send_str("5")
  62. assert(await ws.receive_str() == "6")
  63. format_messages = []
  64. last_is_assistant = False
  65. for message in messages:
  66. if message["role"] == "assistant":
  67. if last_is_assistant:
  68. continue
  69. last_is_assistant = True
  70. else:
  71. last_is_assistant = False
  72. if isinstance(message["content"], str):
  73. format_messages.append({
  74. "role": message["role"],
  75. "content": message["content"]
  76. })
  77. message_data = {
  78. "version": "2.18",
  79. "source": "default",
  80. "model": model,
  81. "messages": format_messages
  82. }
  83. await ws.send_str("42" + json.dumps(["perplexity_labs", message_data]))
  84. last_message = 0
  85. while True:
  86. message = await ws.receive_str()
  87. if message == "2":
  88. if last_message == 0:
  89. raise RuntimeError("Unknown error")
  90. await ws.send_str("3")
  91. continue
  92. try:
  93. if not message.startswith("42"):
  94. continue
  95. parsed_data = json.loads(message[2:])
  96. message_type = parsed_data[0]
  97. data = parsed_data[1]
  98. # Handle error responses
  99. if message_type.endswith("_query_progress") and data.get("status") == "failed":
  100. error_message = data.get("text", "Unknown API error")
  101. raise ResponseError(f"API Error: {error_message}\n")
  102. # Handle normal responses
  103. if "output" in data:
  104. if last_message == 0 and model == cls.default_model:
  105. yield "<think>"
  106. yield data["output"][last_message:]
  107. last_message = len(data["output"])
  108. if data["final"]:
  109. if data["citations"]:
  110. yield Sources(data["citations"])
  111. yield FinishReason("stop")
  112. break
  113. except ResponseError as e:
  114. # Re-raise ResponseError directly
  115. raise e
  116. except Exception as e:
  117. raise ResponseError(f"Error processing message: {message}") from e