diff --git a/g4f/Provider/Perplexity.py b/g4f/Provider/Perplexity.py index 467c74f9..725dfb43 100644 --- a/g4f/Provider/Perplexity.py +++ b/g4f/Provider/Perplexity.py @@ -6,7 +6,7 @@ import uuid from ..typing import AsyncResult, Messages, Cookies from ..requests import StreamSession, raise_for_status, sse_stream from ..cookies import get_cookies -from ..providers.response import ProviderInfo, JsonConversation, JsonRequest, JsonResponse, Reasoning +from ..providers.response import ProviderInfo, JsonConversation, JsonRequest, JsonResponse, Reasoning, Sources, SuggestedFollowups, ImageResponse, PreviewResponse, YouTubeResponse from .base_provider import AsyncGeneratorProvider, ProviderModelMixin from .. import debug @@ -254,6 +254,19 @@ class Perplexity(AsyncGeneratorProvider, ProviderModelMixin): async for json_data in sse_stream(response): yield JsonResponse.from_dict(json_data) for block in json_data.get("blocks", []): + if block.get("intended_usage") == "sources_answer_mode": + yield Sources(block.get("sources_mode_block", {}).get("web_results", [])) + continue + if block.get("intended_usage") == "media_items": + yield PreviewResponse([ + ImageResponse(item.get("url"), item.get("name"), { + "height": item.get("image_height"), + "width": item.get("image_width"), + **item + }) if item.get("medium") == "image" else YouTubeResponse(item.get("url").split("=").pop()) + for item in block.get("media_block", {}).get("media_items", []) + ]) + continue for patch in block.get("diff_block", {}).get("patches", []): if patch.get("path") == "/progress": continue @@ -278,3 +291,8 @@ class Perplexity(AsyncGeneratorProvider, ProviderModelMixin): if value: full_response += value yield value + if "related_query_items" in json_data: + followups = [] + for item in json_data["related_query_items"]: + followups.append(item.get("text", "")) + yield SuggestedFollowups(followups) diff --git a/g4f/api/__init__.py b/g4f/api/__init__.py index 00c32f3e..7380a5c6 100644 --- a/g4f/api/__init__.py +++ b/g4f/api/__init__.py @@ -25,6 +25,7 @@ from starlette.status import ( HTTP_404_NOT_FOUND, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, + HTTP_429_TOO_MANY_REQUESTS, HTTP_500_INTERNAL_SERVER_ERROR, ) from starlette.staticfiles import NotModifiedResponse @@ -442,7 +443,7 @@ class Api: current_most_wanted = next(iter(most_wanted.values()), 0) is_most_wanted = False if x_forwarded_for in most_wanted: - if failure_counts.get(x_forwarded_for, 0) > 0: + if failure_counts.get(x_forwarded_for, 0) > 1: failure_counts[x_forwarded_for] -= 1 most_wanted[x_forwarded_for] += 1 elif most_wanted[x_forwarded_for] >= current_most_wanted: @@ -457,7 +458,7 @@ class Api: sorted_most_wanted = dict(sorted(most_wanted.items(), key=lambda item: item[1], reverse=True)) debug.log(f"Most wanted IPs: {sorted_most_wanted}") if is_most_wanted: - raise RateLimitError("You are most wanted! Please wait before making another request.") + return ErrorResponse.from_message("You are most wanted! Please wait before making another request.", status_code=HTTP_429_TOO_MANY_REQUESTS) if provider is not None and provider not in Provider.__map__: if provider in model_map: config.model = provider diff --git a/g4f/providers/response.py b/g4f/providers/response.py index ae48d3e0..b5acda95 100644 --- a/g4f/providers/response.py +++ b/g4f/providers/response.py @@ -303,7 +303,7 @@ class Sources(ResponseType): if not self.list: return "" return "\n\n\n\n" + ("\n>\n".join([ - f"> [{idx}] {format_link(link['url'], link.get('title', None))}" + f"> [{idx}] {format_link(link['url'], link.get('title', link.get('name', None)))}" for idx, link in enumerate(self.list) ])) @@ -413,8 +413,8 @@ class ImageResponse(MediaResponse): """Return images as markdown.""" if self.get("width") and self.get("height"): return "\n".join([ - f'' - + f'{html.escape(' + f'' + + f'{html.escape(self.alt)}' for url in self.get_list() ]) return format_images_markdown(self.urls, self.alt, self.get("preview")) @@ -442,7 +442,7 @@ class PreviewResponse(HiddenResponse): def to_string(self) -> str: """Return data as a string.""" - return self.data + return "".join([str(item) for item in self.data]) if isinstance(self.data, list) else str(self.data) class Parameters(ResponseType, JsonMixin): def __str__(self) -> str: