XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/gpt4free

Add make_chat_private function and enhance message formatting for Yupp provider @GamerReady

fe0d8e81
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

4 个文件 +214 -135
Added g4f.exe +1 -0
@@ -0,0 +1 @@
1 Subproject commit 5595da04099433411b1b81465b945adda35cc47e
Modified g4f/Provider/Copilot.py +1 -1
@@ -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:
Modified g4f/Provider/Yupp.py +190 -134
@@ -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}")
Modified g4f_cli.py +22 -0
@@ -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__":