返回提交历史
Added
g4f.exe
+1
-0
Modified
g4f/Provider/Copilot.py
+1
-1
Modified
g4f/Provider/Yupp.py
+190
-134
Modified
g4f_cli.py
+22
-0
XFEstudio/gpt4free
Add make_chat_private function and enhance message formatting for Yupp provider @GamerReady
fe0d8e81
代码差异
4 个文件
+214
-135
@@ -0,0 +1 @@
1
Subproject commit 5595da04099433411b1b81465b945adda35cc47e
@@ -99,7 +99,7 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
99
99
@classmethod
100
100
async def on_auth_async(cls, cookies: dict = None, proxy: str = None, **kwargs) -> AsyncIterator:
101
101
if cookies is None:
102
cookies = get_cookies(cls.cookie_domain, False, cache_result=False)
102
cookies = get_fake_cookie() or get_cookies(cls.cookie_domain, False, cache_result=False)
103
103
access_token = None
104
104
useridentitytype = None
105
105
if cls.needs_auth or cls.anon_cookie_name not in cookies:
@@ -119,6 +119,47 @@ def claim_yupp_reward(account: Dict[str, Any], reward_id: str):
119
119
log_debug(f"Failed to claim reward {reward_id}. Error: {e}")
120
120
return None
121
121
122
def make_chat_private(account: Dict[str, Any], chat_id: str) -> bool:
123
"""
124
Set a Yupp chat's sharing status to PRIVATE.
125
Returns True if successful, False otherwise.
126
"""
127
try:
128
log_debug(f"Setting chat {chat_id} to PRIVATE...")
129
url = "https://yupp.ai/api/trpc/chat.updateSharingSettings?batch=1"
130
payload = {
131
"0": {
132
"json": {
133
"chatId": chat_id,
134
"status": "PRIVATE"
135
}
136
}
137
}
138
headers = {
139
"Content-Type": "application/json",
140
"Cookie": f"__Secure-yupp.session-token={account['token']}",
141
}
142
143
session = create_requests_session()
144
response = session.post(url, json=payload, headers=headers)
145
response.raise_for_status()
146
147
data = response.json()
148
# Expected: [{"result":{"data":{"json":{}}}}]
149
if (
150
isinstance(data, list) and len(data) > 0
151
and "json" in data[0].get("result", {}).get("data", {})
152
):
153
log_debug(f"Chat {chat_id} is now PRIVATE ✅")
154
return True
155
156
log_debug(f"Unexpected response while setting chat private: {data}")
157
return False
158
159
except Exception as e:
160
log_debug(f"Failed to make chat {chat_id} private: {e}")
161
return False
162
122
163
def log_debug(message: str):
123
164
"""Debug logging (can be replaced with your logging system)"""
124
165
if os.getenv("DEBUG_MODE", "false").lower() == "true":
@@ -126,6 +167,43 @@ def log_debug(message: str):
126
167
else:
127
168
log(f"[Yupp] {message}")
128
169
170
def format_messages_for_yupp(messages: List[Dict[str, str]]) -> str:
171
"""Format multi-turn conversation for Yupp single-turn format"""
172
if not messages:
173
return ""
174
175
if len(messages) == 1 and isinstance(messages[0].get("content"), str):
176
return messages[0].get("content", "").strip()
177
178
formatted = []
179
180
# Handle system messages
181
system_messages = [msg for msg in messages if msg.get("role") in ["developer", "system"]]
182
if system_messages:
183
for sys_msg in system_messages:
184
content = sys_msg.get("content", "")
185
formatted.append(content)
186
187
# Handle user and assistant messages
188
user_assistant_msgs = [msg for msg in messages if msg.get("role") in ["user", "assistant"]]
189
for msg in user_assistant_msgs:
190
role = "Human" if msg.get("role") == "user" else "Assistant"
191
content = msg.get("content", "")
192
for part in content if isinstance(content, list) else [{"text": content}]:
193
if part.get("text", "").strip():
194
formatted.append(f"\n\n{role}: {part.get('text', '')}")
195
196
# Ensure it ends with Assistant: for the model to continue
197
if not formatted or not formatted[-1].strip().startswith("Assistant:"):
198
formatted.append("\n\nAssistant:")
199
200
result = "".join(formatted)
201
# Remove leading \n\n if present
202
if result.startswith("\n\n"):
203
result = result[2:]
204
205
return result
206
129
207
class Yupp(AbstractProvider, ProviderModelMixin):
130
208
"""
131
209
Yupp.ai Provider for g4f
@@ -178,27 +256,125 @@ class Yupp(AbstractProvider, ProviderModelMixin):
178
256
179
257
if messages is None:
180
258
messages = []
181
182
# Format messages
183
prompt = get_last_user_message(messages, prompt)
259
260
# Format messages - use the new format_messages_for_yupp function
184
261
url_uuid = None
185
262
if conversation is not None:
186
263
url_uuid = conversation.url_uuid
187
log_debug(f"Use url_uuid: {url_uuid}, Formatted prompt length: {len(prompt)}")
188
264
265
# Determine the prompt based on conversation context
266
is_new_conversation = url_uuid is None
267
if prompt is None:
268
if is_new_conversation:
269
# New conversation - format all messages
270
prompt = format_messages_for_yupp(messages)
271
else:
272
# Continuing conversation - use only the last user message
273
prompt = get_last_user_message(messages, prompt)
274
275
log_debug(f"Use url_uuid: {url_uuid}, Formatted prompt length: {len(prompt)}, Is new conversation: {is_new_conversation}")
276
189
277
# Try all accounts with rotation
190
278
max_attempts = len(YUPP_ACCOUNTS)
191
279
for attempt in range(max_attempts):
192
280
account = get_best_yupp_account()
193
281
if not account:
194
282
raise ProviderException("No valid Yupp accounts available")
195
283
196
284
try:
197
yield from cls._make_yupp_request(
198
account, messages, prompt, model, url_uuid, **kwargs
199
)
200
return # Success, exit the loop
201
285
# Prepare the request
286
session = create_requests_session()
287
turn_id = str(uuid.uuid4())
288
files = []
289
290
# Handle media attachments if any
291
media = kwargs.get("media", None)
292
if media:
293
for file, name in list(merge_media(media, messages)):
294
data = to_bytes(file)
295
presigned_resp = session.post(
296
"https://yupp.ai/api/trpc/chat.createPresignedURLForUpload?batch=1",
297
json={"0": {"json": {"fileName": name, "fileSize": len(data), "contentType": is_accepted_format(data)}}},
298
headers={"Content-Type": "application/json", "Cookie": f"__Secure-yupp.session-token={account['token']}"}
299
)
300
presigned_resp.raise_for_status()
301
upload_info = presigned_resp.json()[0]["result"]["data"]["json"]
302
upload_url = upload_info["signedUrl"]
303
session.put(upload_url, data=data, headers={"Content-Type": is_accepted_format(data), "Content-Length": str(len(data))})
304
attachment_resp = session.post(
305
"https://yupp.ai/api/trpc/chat.createAttachmentForUploadedFile?batch=1",
306
json={"0": {"json": {"fileName": name, "contentType": is_accepted_format(data), "fileId": upload_info["fileId"]}}},
307
cookies={"__Secure-yupp.session-token": account["token"]}
308
)
309
attachment_resp.raise_for_status()
310
attachment = attachment_resp.json()[0]["result"]["data"]["json"]
311
files.append({
312
"fileName": attachment["file_name"],
313
"contentType": attachment["content_type"],
314
"attachmentId": attachment["attachment_id"],
315
"chatMessageId": ""
316
})
317
318
# Build payload and URL - FIXED: Use consistent url_uuid handling
319
if is_new_conversation:
320
url_uuid = str(uuid.uuid4())
321
payload = [
322
url_uuid,
323
turn_id,
324
prompt,
325
"$undefined",
326
"$undefined",
327
files,
328
"$undefined",
329
[{"modelName": model, "promptModifierId": "$undefined"}] if model else "none",
330
"text",
331
True,
332
"$undefined",
333
]
334
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
335
# Yield the conversation info first
336
yield JsonConversation(url_uuid=url_uuid)
337
next_action = kwargs.get("next_action", "7f2a2308b5fc462a2c26df714cb2cccd02a9c10fbb")
338
else:
339
# Continuing existing conversation
340
payload = [
341
url_uuid,
342
turn_id,
343
prompt,
344
False,
345
[],
346
[{"modelName": model, "promptModifierId": "$undefined"}] if model else [],
347
"text",
348
files
349
]
350
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
351
next_action = kwargs.get("next_action", "7f1e9eec4ab22c8bfc73a50c026db603cd8380f87d")
352
353
headers = {
354
"accept": "text/x-component",
355
"content-type": "text/plain;charset=UTF-8",
356
"next-action": next_action,
357
"cookie": f"__Secure-yupp.session-token={account['token']}",
358
}
359
360
log_debug(f"Sending request to: {url}")
361
log_debug(f"Payload structure: {type(payload)}, length: {len(str(payload))}")
362
363
# Send request
364
response = session.post(url, data=json.dumps(payload), headers=headers, stream=True, timeout=60)
365
response.raise_for_status()
366
367
# Attempt to make chat private
368
try:
369
make_chat_private(account, url_uuid)
370
except Exception as e:
371
log_debug(f"Failed to set chat private for {url_uuid}: {e}")
372
373
# Yield streaming responses
374
yield from cls._process_stream_response(response.iter_lines(), account, session, prompt, model)
375
376
return # Exit after successful completion
377
202
378
except RateLimitError:
203
379
log_debug(f"Account ...{account['token'][-4:]} hit rate limit, rotating")
204
380
with account_rotation_lock:
@@ -217,128 +393,8 @@ class Yupp(AbstractProvider, ProviderModelMixin):
217
393
with account_rotation_lock:
218
394
account["error_count"] += 1
219
395
raise ProviderException(f"Yupp request failed: {str(e)}") from e
220
221
raise ProviderException("All Yupp accounts failed after rotation attempts")
222
223
@classmethod
224
def _make_yupp_request(
225
cls,
226
account: Dict[str, Any],
227
messages: List[Dict[str, str]],
228
prompt: str,
229
model_id: str,
230
url_uuid: Optional[str] = None,
231
media: List[str] = None,
232
next_action: str = "7f2a2308b5fc462a2c26df714cb2cccd02a9c10fbb",
233
**kwargs
234
) -> Generator[str, Any, None]:
235
"""Make actual request to Yupp.ai"""
236
237
session = create_requests_session()
238
239
files = []
240
for file, name in list(merge_media(media, messages)):
241
data = to_bytes(file)
242
url = "https://yupp.ai/api/trpc/chat.createPresignedURLForUpload?batch=1"
243
payload = {
244
"0": {
245
"json": {
246
"fileName": name,
247
"fileSize": len(data),
248
"contentType": is_accepted_format(data)
249
}
250
}
251
}
252
response = session.post(url, json=payload, headers={
253
"Content-Type": "application/json",
254
"Cookie": f"__Secure-yupp.session-token={account['token']}",
255
})
256
response.raise_for_status()
257
upload_info = response.json()[0]["result"]["data"]["json"]
258
upload_url = upload_info["signedUrl"]
259
upload_resp = session.put(upload_url, data=data, headers={
260
"Content-Type": is_accepted_format(data),
261
"Cookie": f"__Secure-yupp.session-token={account['token']}",
262
"Content-Length": str(len(data)),
263
})
264
upload_resp.raise_for_status()
265
url = "https://yupp.ai/api/trpc/chat.createAttachmentForUploadedFile?batch=1"
266
response = session.post(url, json={
267
"0": {
268
"json": {
269
"fileName": name,
270
"contentType": is_accepted_format(data),
271
"fileId": upload_info["fileId"],
272
}
273
}
274
}, cookies={
275
"__Secure-yupp.session-token": account["token"]
276
})
277
response.raise_for_status()
278
attachment = response.json()[0]["result"]["data"]["json"]
279
files.append({
280
"fileName": attachment["file_name"],
281
"contentType": attachment["content_type"],
282
"attachmentId": attachment["attachment_id"],
283
"chatMessageId": ""
284
})
285
286
# Build request
287
log_debug(f"Sending request to Yupp.ai with account: {account['token'][:10]}...")
288
289
turn_id = str(uuid.uuid4())
290
if url_uuid is None:
291
url_uuid = str(uuid.uuid4())
292
payload = [
293
url_uuid,
294
turn_id,
295
prompt,
296
"$undefined",
297
"$undefined",
298
files,
299
"$undefined",
300
[{"modelName": model_id, "promptModifierId": "$undefined"}] if model_id else "none",
301
"text",
302
False,
303
"$undefined",
304
]
305
yield JsonConversation(url_uuid=url_uuid)
306
else:
307
payload = [
308
url_uuid,
309
turn_id,
310
prompt,
311
False,
312
[],
313
[{"modelName": model_id, "promptModifierId": "$undefined"}] if model_id else [],
314
"text",
315
files
316
]
317
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
318
next_action = "7f1e9eec4ab22c8bfc73a50c026db603cd8380f87d"
319
320
headers = {
321
"accept": "text/x-component",
322
"content-type": "text/plain;charset=UTF-8",
323
"next-action": next_action,
324
"cookie": f"__Secure-yupp.session-token={account['token']}",
325
}
326
396
327
log_debug(f"Request uuid: {url_uuid}, Model: {model_id}, Prompt length: {len(prompt)}, Files: {len(files)}")
328
329
# Send request
330
response = session.post(
331
url,
332
data=json.dumps(payload),
333
headers=headers,
334
stream=True,
335
timeout=60
336
)
337
response.raise_for_status()
338
339
yield from cls._process_stream_response(
340
response.iter_lines(), account, session, prompt, model_id
341
)
397
raise ProviderException("All Yupp accounts failed after rotation attempts")
342
398
343
399
@classmethod
344
400
def _process_stream_response(
@@ -528,7 +584,7 @@ def init_yupp_provider():
528
584
# Example usage and testing
529
585
if __name__ == "__main__":
530
586
# Set up environment for testing
531
# os.environ["DEBUG_MODE"] = "true"
587
os.environ["DEBUG_MODE"] = "true"
532
588
533
589
# Initialize provider
534
590
provider = init_yupp_provider()
@@ -544,4 +600,4 @@ if __name__ == "__main__":
544
600
if isinstance(chunk, str) and chunk.strip():
545
601
print(chunk, end="")
546
602
except Exception as e:
547
print(f"\nStream test failed: {e}")
603
print(f"\nStream test failed: {e}")
@@ -4,6 +4,28 @@ Entry point for g4f CLI executable builds
4
4
This file is used as the main entry point for building executables with Nuitka
5
5
"""
6
6
7
import g4f.debug
8
g4f.debug.enable_logging()
9
10
from g4f.client import Client
11
from g4f.errors import ModelNotFoundError
12
13
import g4f.Provider
14
15
try:
16
client = Client(provider=g4f.Provider.PollinationsAI)
17
response = client.chat.completions.create(
18
model="openai",
19
messages=[{"role": "user", "content": "Hello!"}],
20
stream=True,
21
raw=True
22
)
23
for r in response:
24
print(r)
25
except ModelNotFoundError as e:
26
print(f"Successfully")
27
exit(0)
28
7
29
import g4f.cli
8
30
9
31
if __name__ == "__main__":