返回提交历史
Modified
g4f/Provider/Perplexity.py
+144
-126
Modified
g4f/Provider/PerplexityLabs.py
+1
-1
XFEstudio/gpt4free
Update Perplexity provider to improve model selection and response handling
877d5cce
代码差异
2 个文件
+145
-127
@@ -1,39 +1,51 @@
1
1
from __future__ import annotations
2
2
3
import json
3
import random
4
4
import uuid
5
5
6
from ..typing import AsyncResult, Messages
7
from ..requests import StreamSession, raise_for_status
8
from ..errors import ResponseError
9
from ..providers.response import FinishReason, Sources
6
from ..typing import AsyncResult, Messages, Cookies
7
from ..requests import StreamSession, raise_for_status, sse_stream
8
from ..cookies import get_cookies
9
from ..providers.response import ProviderInfo
10
10
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
11
from .. import debug
11
12
12
13
class Perplexity(AsyncGeneratorProvider, ProviderModelMixin):
13
14
label = "Perplexity"
14
15
url = "https://www.perplexity.ai"
16
cookie_domain = ".perplexity.ai"
15
17
working = True
16
supports_stream = True
17
supports_system_message = True
18
supports_message_history = True
18
active_by_default = True
19
19
20
default_model = "turbo"
20
default_model = "auto"
21
21
models = [
22
22
default_model,
23
"sonar",
24
"sonar-pro",
23
"turbo",
24
"pplx_pro",
25
"gpt-5",
25
26
]
27
model_aliases = {
28
"gpt-5": "gpt5",
29
}
30
31
_user_id = None
26
32
27
33
@classmethod
28
34
async def create_async_generator(
29
35
cls,
30
36
model: str,
31
37
messages: Messages,
38
cookies: Cookies = None,
32
39
proxy: str = None,
33
40
**kwargs
34
41
) -> AsyncResult:
35
model = cls.get_model(model)
36
42
if not model:
43
model = cls.default_model
44
if cookies is None:
45
cookies = get_cookies(cls.cookie_domain, False)
46
else:
47
cls._user_id = None
48
37
49
# Generate UUIDs for request tracking
38
50
frontend_uuid = str(uuid.uuid4())
39
51
frontend_context_uuid = str(uuid.uuid4())
@@ -58,121 +70,127 @@ class Perplexity(AsyncGeneratorProvider, ProviderModelMixin):
58
70
if message["role"] == "user":
59
71
query = message["content"]
60
72
break
61
62
# Prepare the request payload
63
data = {
64
"params": {
65
"attachments": [],
66
"language": "en-US",
67
"timezone": "America/New_York",
68
"search_focus": "internet",
69
"sources": ["web"],
70
"search_recency_filter": None,
71
"frontend_uuid": frontend_uuid,
72
"mode": "concise",
73
"model_preference": model,
74
"is_related_query": False,
75
"is_sponsored": False,
76
"visitor_id": visitor_id,
77
"frontend_context_uuid": frontend_context_uuid,
78
"prompt_source": "user",
79
"query_source": "home",
80
"is_incognito": False,
81
"time_from_first_type": 0,
82
"local_search_enabled": False,
83
"use_schematized_api": True,
84
"send_back_text_in_streaming_api": False,
85
"supported_block_use_cases": [
86
"answer_modes",
87
"media_items",
88
"knowledge_cards",
89
"inline_entity_cards",
90
"place_widgets",
91
"finance_widgets",
92
"sports_widgets",
93
"shopping_widgets",
94
"jobs_widgets",
95
"search_result_widgets",
96
"clarification_responses",
97
"inline_images",
98
"inline_assets",
99
"inline_finance_widgets",
100
"placeholder_cards",
101
"diff_blocks",
102
"inline_knowledge_cards",
103
"entity_group_v2",
104
"refinement_filters",
105
"canvas_mode"
106
],
107
"client_coordinates": None,
108
"mentions": [],
109
"dsl_query": query,
110
"skip_search_enabled": False,
111
"is_nav_suggestions_disabled": False,
112
"always_search_override": False,
113
"override_no_search": False,
114
"comet_max_assistant_enabled": False,
115
"version": "2.18"
116
},
117
"query_str": query
118
}
119
120
async with StreamSession(headers=headers, proxy=proxy, impersonate="chrome") as session:
73
74
async with StreamSession(headers=headers, cookies=cookies, proxy=proxy, impersonate="chrome") as session:
75
if cls._user_id is None:
76
async with session.get(f"{cls.url}/api/auth/session") as response:
77
await raise_for_status(response)
78
user = await response.json()
79
cls._user_id = user.get("user", {}).get("id")
80
debug.log(f"Perplexity user id: {cls._user_id}")
81
if model == "auto":
82
model = "pplx_pro" if cls._user_id else "turbo"
83
yield ProviderInfo(**cls.get_dict(), model=model)
84
if model in cls.model_aliases:
85
model = cls.model_aliases[model]
86
if cls._user_id is None:
87
data = {
88
"params": {
89
"attachments": [],
90
"language": "en-US",
91
"timezone": "America/New_York",
92
"search_focus": "internet",
93
"sources": ["web"],
94
"search_recency_filter": None,
95
"frontend_uuid": frontend_uuid,
96
"mode": "concise",
97
"model_preference": model,
98
"is_related_query": False,
99
"is_sponsored": False,
100
"visitor_id": visitor_id,
101
"frontend_context_uuid": frontend_context_uuid,
102
"prompt_source": "user",
103
"query_source": "home",
104
"is_incognito": False,
105
"time_from_first_type": 0,
106
"local_search_enabled": False,
107
"use_schematized_api": True,
108
"send_back_text_in_streaming_api": False,
109
"supported_block_use_cases": [
110
"answer_modes",
111
"media_items",
112
"knowledge_cards",
113
"inline_entity_cards",
114
"place_widgets",
115
"finance_widgets",
116
"sports_widgets",
117
"shopping_widgets",
118
"jobs_widgets",
119
"search_result_widgets",
120
"clarification_responses",
121
"inline_images",
122
"inline_assets",
123
"inline_finance_widgets",
124
"placeholder_cards",
125
"diff_blocks",
126
"inline_knowledge_cards",
127
"entity_group_v2",
128
"refinement_filters",
129
"canvas_mode"
130
],
131
"client_coordinates": None,
132
"mentions": [],
133
"dsl_query": query,
134
"skip_search_enabled": False,
135
"is_nav_suggestions_disabled": False,
136
"always_search_override": False,
137
"override_no_search": False,
138
"comet_max_assistant_enabled": False,
139
"version": "2.18"
140
},
141
"query_str": query
142
}
143
else:
144
data = {
145
"params": {
146
"last_backend_uuid": None,
147
"read_write_token": "457a2d3d-c53f-4065-8554-7645a36fc220",
148
"attachments": [],
149
"language": "en-US",
150
"timezone": "America/New_York",
151
"search_focus": "internet",
152
"sources": ["web"],
153
"frontend_uuid": frontend_uuid,
154
"mode": "copilot",
155
"model_preference": "gpt5",
156
"is_related_query": False,
157
"is_sponsored": False,
158
"visitor_id": visitor_id,
159
"user_nextauth_id": cls._user_id,
160
"prompt_source": "user",
161
"query_source":"followup",
162
"is_incognito": False,
163
"time_from_first_type": random.randint(0, 1000),
164
"local_search_enabled": False,
165
"use_schematized_api": True,
166
"send_back_text_in_streaming_api": False,
167
"supported_block_use_cases": ["answer_modes", "media_items", "knowledge_cards", "inline_entity_cards", "place_widgets", "finance_widgets", "sports_widgets", "shopping_widgets", "jobs_widgets", "search_result_widgets", "clarification_responses", "inline_images", "inline_assets", "inline_finance_widgets", "placeholder_cards", "diff_blocks", "inline_knowledge_cards", "entity_group_v2", "refinement_filters", "canvas_mode"],
168
"client_coordinates": None,
169
"mentions": [],
170
"skip_search_enabled": True,
171
"is_nav_suggestions_disabled": False,
172
"followup_source": "link",
173
"version": "2.18"
174
},
175
"query_str": query
176
}
121
177
async with session.post(
122
178
f"{cls.url}/rest/sse/perplexity_ask",
123
179
json=data,
124
180
) as response:
125
181
await raise_for_status(response)
126
127
# Track last message position for incremental updates
128
last_message = 0
129
130
# Parse SSE stream
131
async for line in response.content:
132
if line:
133
line = line.decode('utf-8').strip()
134
if line.startswith("data: "):
135
data_str = line[6:]
136
if data_str == "[DONE]":
137
break
138
try:
139
json_data = json.loads(data_str)
140
141
# Handle error responses
142
if json_data.get("status") == "failed":
143
error_message = json_data.get("text", "Unknown API error")
144
raise ResponseError(f"API Error: {error_message}")
145
146
# Handle normal responses with 'output' field (like PerplexityLabs)
147
if "output" in json_data:
148
# Yield incremental output
149
output = json_data["output"]
150
if len(output) > last_message:
151
yield output[last_message:]
152
last_message = len(output)
153
154
# Check if response is final
155
if json_data.get("final", False):
156
# Yield citations if available
157
if json_data.get("citations"):
158
yield Sources(json_data["citations"])
159
yield FinishReason("stop")
160
break
161
162
# Handle alternative response format with 'text' field
163
elif "text" in json_data:
164
text = json_data["text"]
165
if text:
166
yield text
167
168
# Check if response is final
169
if json_data.get("final", False):
170
yield FinishReason("stop")
171
break
172
173
except json.JSONDecodeError:
174
# Skip malformed JSON
175
continue
176
except ResponseError:
177
# Re-raise ResponseError directly
178
raise
182
full_response = ""
183
last_response = ""
184
async for json_data in sse_stream(response):
185
for block in json_data.get("blocks", []):
186
for patch in block.get("diff_block", {}).get("patches", []):
187
value = patch.get("value", "")
188
value = value.get("answer", "") if isinstance(value, dict) else value
189
if value:
190
if value.startswith(full_response):
191
value = value[len(full_response):]
192
if value.startswith(last_response):
193
value = value[len(last_response):]
194
last_response = value
195
full_response += value
196
yield value
@@ -15,7 +15,7 @@ WS_URL = "wss://www.perplexity.ai/socket.io/"
15
15
class PerplexityLabs(AsyncGeneratorProvider, ProviderModelMixin):
16
16
label = "Perplexity Labs"
17
17
url = "https://labs.perplexity.ai"
18
working = True
18
working = False
19
19
active_by_default = True
20
20
21
21
default_model = "r1-1776"