PerplexityLabs.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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. default_model = "r1-1776"
  16. models = [
  17. default_model,
  18. "sonar-pro",
  19. "sonar",
  20. "sonar-reasoning",
  21. "sonar-reasoning-pro",
  22. ]
  23. @classmethod
  24. async def create_async_generator(
  25. cls,
  26. model: str,
  27. messages: Messages,
  28. proxy: str = None,
  29. **kwargs
  30. ) -> AsyncResult:
  31. headers = {
  32. "Origin": cls.url,
  33. "Referer": f"{cls.url}/",
  34. }
  35. async with StreamSession(headers=headers, proxy=proxy, impersonate="chrome") as session:
  36. t = format(random.getrandbits(32), "08x")
  37. async with session.get(
  38. f"{API_URL}?EIO=4&transport=polling&t={t}"
  39. ) as response:
  40. await raise_for_status(response)
  41. text = await response.text()
  42. assert text.startswith("0")
  43. sid = json.loads(text[1:])["sid"]
  44. post_data = '40{"jwt":"anonymous-ask-user"}'
  45. async with session.post(
  46. f"{API_URL}?EIO=4&transport=polling&t={t}&sid={sid}",
  47. data=post_data
  48. ) as response:
  49. await raise_for_status(response)
  50. assert await response.text() == "OK"
  51. async with session.get(
  52. f"{API_URL}?EIO=4&transport=polling&t={t}&sid={sid}",
  53. data=post_data
  54. ) as response:
  55. await raise_for_status(response)
  56. assert (await response.text()).startswith("40")
  57. async with session.ws_connect(f"{WS_URL}?EIO=4&transport=websocket&sid={sid}", autoping=False) as ws:
  58. await ws.send_str("2probe")
  59. assert(await ws.receive_str() == "3probe")
  60. await ws.send_str("5")
  61. assert(await ws.receive_str() == "6")
  62. message_data = {
  63. "version": "2.18",
  64. "source": "default",
  65. "model": model,
  66. "messages": [message for message in messages if isinstance(message["content"], str)],
  67. }
  68. await ws.send_str("42" + json.dumps(["perplexity_labs", message_data]))
  69. last_message = 0
  70. while True:
  71. message = await ws.receive_str()
  72. if message == "2":
  73. if last_message == 0:
  74. raise RuntimeError("Unknown error")
  75. await ws.send_str("3")
  76. continue
  77. try:
  78. if not message.startswith("42"):
  79. continue
  80. parsed_data = json.loads(message[2:])
  81. message_type = parsed_data[0]
  82. data = parsed_data[1]
  83. # Handle error responses
  84. if message_type.endswith("_query_progress") and data.get("status") == "failed":
  85. error_message = data.get("text", "Unknown API error")
  86. raise ResponseError(f"API Error: {error_message}")
  87. # Handle normal responses
  88. if "output" in data:
  89. if last_message == 0 and model == cls.default_model:
  90. yield "<think>"
  91. yield data["output"][last_message:]
  92. last_message = len(data["output"])
  93. if data["final"]:
  94. if data["citations"]:
  95. yield Sources(data["citations"])
  96. yield FinishReason("stop")
  97. break
  98. except ResponseError as e:
  99. # Re-raise ResponseError directly
  100. raise e
  101. except Exception as e:
  102. raise ResponseError(f"Error processing message: {message}") from e