websearch / perplexity /labs.py
hermesinho's picture
v3: MCP server with auto-account, updated models
7099c5c
Raw
History Blame Contribute Delete
4.5 kB
import json
import random
import socket
import ssl
import time
from threading import Thread
from curl_cffi import requests
from websocket import WebSocketApp
from .config import DEFAULT_HEADERS, ENDPOINT_SOCKET_IO, LABS_MODELS
from .exceptions import InvalidModelError, NetworkError
from .logger import get_logger
logger = get_logger("labs")
class LabsClient:
def __init__(self):
self.session = requests.Session(headers=DEFAULT_HEADERS.copy(), impersonate="chrome")
self.timestamp = format(random.getrandbits(32), "08x")
poll_url = f"{ENDPOINT_SOCKET_IO}?EIO=4&transport=polling&t={self.timestamp}"
self.sid = json.loads(self.session.get(poll_url).text[1:])["sid"]
self.last_answer = None
self.history = []
auth_url = (
f"{ENDPOINT_SOCKET_IO}?EIO=4&transport=polling"
f"&t={self.timestamp}&sid={self.sid}"
)
assert self.session.post(auth_url, data='40{"jwt":"anonymous-ask-user"}').text == "OK"
context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_3
self.sock = context.wrap_socket(
socket.create_connection(("www.perplexity.ai", 443)),
server_hostname="www.perplexity.ai",
)
websocket_url = (
"wss://www.perplexity.ai/socket.io/?EIO=4&transport=websocket"
f"&sid={self.sid}"
)
self.ws = WebSocketApp(
url=websocket_url,
header={"User-Agent": self.session.headers["User-Agent"]},
cookie="; ".join(
[f"{key}={value}" for key, value in self.session.cookies.get_dict().items()]
),
on_open=lambda ws: (ws.send("2probe"), ws.send("5")),
on_message=self._on_message,
on_error=lambda ws, error: logger.error(f"WebSocket Error: {error}"),
socket=self.sock,
)
Thread(target=self.ws.run_forever, daemon=True).start()
while not (self.ws.sock and self.ws.sock.connected):
time.sleep(0.01)
def _on_message(self, ws, message):
try:
if message == "2":
ws.send("3")
if message.startswith("42"):
response = json.loads(message[2:])[1]
if "final" in response:
self.last_answer = response
except json.JSONDecodeError as e:
logger.error(f"JSON decode error in labs message: {e}")
except Exception as e:
logger.error(f"Error in labs message handler: {e}")
def ask(self, query, model="r1-1776", stream=False):
if model not in LABS_MODELS:
raise InvalidModelError(
f"Invalid labs model '{model}'. Must be one of: {', '.join(LABS_MODELS)}"
)
self.last_answer = None
self.history.append({"role": "user", "content": query})
self.ws.send(
"42" + json.dumps(
[
"perplexity_labs",
{
"messages": self.history,
"model": model,
"source": "default",
"version": "2.18",
},
]
)
)
def stream_response():
answer = None
while True:
if self.last_answer != answer:
answer = self.last_answer
yield answer
if self.last_answer and self.last_answer.get("final"):
answer = self.last_answer
self.last_answer = None
self.history.append(
{
"role": "assistant",
"content": answer["output"],
"priority": 0,
}
)
return
time.sleep(0.01)
if stream:
return stream_response()
while True:
if self.last_answer and self.last_answer.get("final"):
answer = self.last_answer
self.last_answer = None
self.history.append(
{
"role": "assistant",
"content": answer["output"],
"priority": 0,
}
)
return answer
time.sleep(0.01)