summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHeiner Lohaus <hlohaus@users.noreply.github.com>2024-11-17 11:06:37 +0100
committerHeiner Lohaus <hlohaus@users.noreply.github.com>2024-11-17 11:06:37 +0100
commitea1448001df9c66f05a52618eb3e40a88df5efe8 (patch)
tree3f113ea8beb7c43920871019512aeb8de9d1b4f7
parentFix api streaming, fix AsyncClient (#2357) (diff)
downloadgpt4free-ea1448001df9c66f05a52618eb3e40a88df5efe8.tar
gpt4free-ea1448001df9c66f05a52618eb3e40a88df5efe8.tar.gz
gpt4free-ea1448001df9c66f05a52618eb3e40a88df5efe8.tar.bz2
gpt4free-ea1448001df9c66f05a52618eb3e40a88df5efe8.tar.lz
gpt4free-ea1448001df9c66f05a52618eb3e40a88df5efe8.tar.xz
gpt4free-ea1448001df9c66f05a52618eb3e40a88df5efe8.tar.zst
gpt4free-ea1448001df9c66f05a52618eb3e40a88df5efe8.zip
Diffstat (limited to '')
-rw-r--r--.github/workflows/publish-workflow.yaml12
-rw-r--r--docker-compose-slim.yml25
-rw-r--r--docker/Dockerfile1
-rw-r--r--docker/Dockerfile-slim68
-rwxr-xr-xdocker/supervisor-api.conf12
-rwxr-xr-xdocker/supervisor-gui.conf2
-rwxr-xr-xdocker/supervisor.conf15
-rw-r--r--docs/docker.md24
-rw-r--r--g4f/Provider/Cloudflare.py15
-rw-r--r--g4f/Provider/HuggingChat.py34
-rw-r--r--g4f/Provider/needs_auth/Gemini.py80
-rw-r--r--g4f/Provider/needs_auth/GeminiPro.py4
-rw-r--r--g4f/Provider/needs_auth/HuggingFace.py29
-rw-r--r--g4f/Provider/needs_auth/MetaAI.py3
-rw-r--r--g4f/Provider/needs_auth/MetaAIAccount.py2
-rw-r--r--g4f/Provider/needs_auth/__init__.py1
-rw-r--r--g4f/gui/server/api.py32
-rw-r--r--g4f/image.py2
-rw-r--r--requirements-slim.txt16
-rw-r--r--requirements.txt4
-rw-r--r--setup.py4
21 files changed, 240 insertions, 145 deletions
diff --git a/.github/workflows/publish-workflow.yaml b/.github/workflows/publish-workflow.yaml
index bfc0b735..2f3624d1 100644
--- a/.github/workflows/publish-workflow.yaml
+++ b/.github/workflows/publish-workflow.yaml
@@ -48,3 +48,15 @@ jobs:
labels: ${{ steps.metadata.outputs.labels }}
build-args: |
G4F_VERSION=${{ github.ref_name }}
+ - name: Build and push slim image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: docker/Dockerfile-slim
+ push: true
+ tags: |
+ hlohaus789/g4f=slim
+ hlohaus789/g4f=${{ github.ref_name }}-slim
+ labels: ${{ steps.metadata.outputs.labels }}
+ build-args: |
+ G4F_VERSION=${{ github.ref_name }} \ No newline at end of file
diff --git a/docker-compose-slim.yml b/docker-compose-slim.yml
new file mode 100644
index 00000000..ec0ee0fc
--- /dev/null
+++ b/docker-compose-slim.yml
@@ -0,0 +1,25 @@
+version: '3'
+
+services:
+ g4f-gui:
+ container_name: g4f-gui
+ image: hlohaus789/g4f:slim
+ build:
+ context: .
+ dockerfile: docker/Dockerfile-slim
+ command: python -m g4f.cli gui -debug
+ volumes:
+ - .:/app
+ ports:
+ - '8080:8080'
+ g4f-api:
+ container_name: g4f-api
+ image: hlohaus789/g4f:slim
+ build:
+ context: .
+ dockerfile: docker/Dockerfile-slim
+ command: python -m g4f.cli api
+ volumes:
+ - .:/app
+ ports:
+ - '1337:1337' \ No newline at end of file
diff --git a/docker/Dockerfile b/docker/Dockerfile
index eb03390c..625312e2 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -40,6 +40,7 @@ RUN apt-get -qqy update \
# Update entrypoint
COPY docker/supervisor.conf /etc/supervisor/conf.d/selenium.conf
+COPY docker/supervisor-api.conf /etc/supervisor/conf.d/api.conf
COPY docker/supervisor-gui.conf /etc/supervisor/conf.d/gui.conf
# If no gui
diff --git a/docker/Dockerfile-slim b/docker/Dockerfile-slim
new file mode 100644
index 00000000..6c0afc64
--- /dev/null
+++ b/docker/Dockerfile-slim
@@ -0,0 +1,68 @@
+FROM python:bookworm
+
+ARG G4F_VERSION
+ARG G4F_USER=g4f
+ARG G4F_USER_ID=1000
+ARG PYDANTIC_VERSION=1.8.1
+
+ENV G4F_VERSION $G4F_VERSION
+ENV G4F_USER $G4F_USER
+ENV G4F_USER_ID $G4F_USER_ID
+ENV G4F_DIR /app
+
+RUN apt-get update && apt-get upgrade -y \
+ && apt-get install -y git \
+ && apt-get install --quiet --yes --no-install-recommends \
+ build-essential \
+# Add user and user group
+ && groupadd -g $G4F_USER_ID $G4F_USER \
+ && useradd -rm -G sudo -u $G4F_USER_ID -g $G4F_USER_ID $G4F_USER \
+ && mkdir -p /var/log/supervisor \
+ && chown "${G4F_USER_ID}:${G4F_USER_ID}" /var/log/supervisor \
+ && echo "${G4F_USER}:${G4F_USER}" | chpasswd
+
+USER $G4F_USER_ID
+WORKDIR $G4F_DIR
+
+ENV HOME /home/$G4F_USER
+ENV PATH "${HOME}/.local/bin:${HOME}/.cargo/bin:${PATH}"
+
+# Create app dir and copy the project's requirements file into it
+RUN mkdir -p $G4F_DIR
+COPY requirements-slim.txt $G4F_DIR
+
+# Install rust toolchain
+RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
+
+# Upgrade pip for the latest features and install the project's Python dependencies.
+RUN python -m pip install --upgrade pip \
+ && pip install --no-cache-dir \
+ Cython==0.29.22 \
+ setuptools \
+ # Install PyDantic
+ && pip install \
+ -vvv \
+ --no-cache-dir \
+ --no-binary pydantic \
+ --global-option=build_ext \
+ --global-option=-j8 \
+ pydantic==${PYDANTIC_VERSION} \
+ && pip install --no-cache-dir -r requirements-slim.txt \
+ # Remove build packages
+ && pip uninstall --yes \
+ Cython \
+ setuptools
+
+USER root
+
+# Clean up build deps
+RUN rustup self uninstall -y \
+ && apt-get purge --auto-remove --yes \
+ build-essential \
+ && apt-get clean \
+ && rm --recursive --force /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+USER $G4F_USER_ID
+
+# Copy the entire package into the container.
+ADD --chown=$G4F_USER:$G4F_USER g4f $G4F_DIR/g4f \ No newline at end of file
diff --git a/docker/supervisor-api.conf b/docker/supervisor-api.conf
new file mode 100755
index 00000000..74572634
--- /dev/null
+++ b/docker/supervisor-api.conf
@@ -0,0 +1,12 @@
+[program:g4f-api]
+priority=15
+command=python -m g4f.cli api
+directory=/app
+stopasgroup=true
+autostart=true
+autorestart=true
+
+;Logs (all Hub activity redirected to stdout so it can be seen through "docker logs"
+redirect_stderr=true
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0 \ No newline at end of file
diff --git a/docker/supervisor-gui.conf b/docker/supervisor-gui.conf
index 44273c67..0c77ffc5 100755
--- a/docker/supervisor-gui.conf
+++ b/docker/supervisor-gui.conf
@@ -1,6 +1,6 @@
[program:g4f-gui]
priority=15
-command=python -m g4f.cli gui
+command=python -m g4f.cli gui -debug
directory=/app
stopasgroup=true
autostart=true
diff --git a/docker/supervisor.conf b/docker/supervisor.conf
index f0f01fd1..7fd4331a 100755
--- a/docker/supervisor.conf
+++ b/docker/supervisor.conf
@@ -47,17 +47,4 @@ stderr_logfile_maxbytes=50MB
stdout_logfile_backups=5
stderr_logfile_backups=5
stdout_capture_maxbytes=50MB
-stderr_capture_maxbytes=50MB
-
-[program:g4f-api]
-priority=15
-command=python -m g4f.cli api
-directory=/app
-stopasgroup=true
-autostart=true
-autorestart=true
-
-;Logs (all Hub activity redirected to stdout so it can be seen through "docker logs"
-redirect_stderr=true
-stdout_logfile=/dev/stdout
-stdout_logfile_maxbytes=0 \ No newline at end of file
+stderr_capture_maxbytes=50MB \ No newline at end of file
diff --git a/docs/docker.md b/docs/docker.md
index 8017715c..ce7fd466 100644
--- a/docs/docker.md
+++ b/docs/docker.md
@@ -28,12 +28,22 @@
```
2. **Build and Run with Docker Compose**
+
+ Pull the latest image and run a container with Google Chrome support:
+ ```bash
+ docker pull hlohaus789/g4f
+ docker-compose up -d
+ ```
+ Or run the small docker images without Google Chrome:
```bash
- docker-compose up --build
+ docker-compose -f docker-compose-slim.yml up -d
```
-3. **Access the API**
- The server will be accessible at `http://localhost:1337`
+3. **Access the API or the GUI**
+
+ The api server will be accessible at `http://localhost:1337`
+
+ And the gui at this url: `http://localhost:8080`
### Non-Docker Method
If you encounter issues with Docker, you can run the project directly using Python:
@@ -54,8 +64,12 @@ If you encounter issues with Docker, you can run the project directly using Pyth
python -m g4f.api.run
```
-4. **Access the API**
- The server will be accessible at `http://localhost:1337`
+4. **Access the API or the GUI**
+
+ The api server will be accessible at `http://localhost:1337`
+
+ And the gui at this url: `http://localhost:8080`
+
## Testing the API
**You can test the API using curl or by creating a simple Python script:**
diff --git a/g4f/Provider/Cloudflare.py b/g4f/Provider/Cloudflare.py
index 825c5027..7d477d57 100644
--- a/g4f/Provider/Cloudflare.py
+++ b/g4f/Provider/Cloudflare.py
@@ -7,6 +7,7 @@ import uuid
from ..typing import AsyncResult, Messages, Cookies
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin, get_running_loop
from ..requests import Session, StreamSession, get_args_from_nodriver, raise_for_status, merge_cookies
+from ..errors import ResponseStatusError
class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
label = "Cloudflare AI"
@@ -42,10 +43,14 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
cls._args = asyncio.run(args)
with Session(**cls._args) as session:
response = session.get(cls.models_url)
- raise_for_status(response)
+ cls._args["cookies"] = merge_cookies(cls._args["cookies"] , response)
+ try:
+ raise_for_status(response)
+ except ResponseStatusError as e:
+ cls._args = None
+ raise e
json_data = response.json()
cls.models = [model.get("name") for model in json_data.get("models")]
- cls._args["cookies"] = merge_cookies(cls._args["cookies"] , response)
return cls.models
@classmethod
@@ -74,8 +79,12 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
cls.api_endpoint,
json=data,
) as response:
- await raise_for_status(response)
cls._args["cookies"] = merge_cookies(cls._args["cookies"] , response)
+ try:
+ await raise_for_status(response)
+ except ResponseStatusError as e:
+ cls._args = None
+ raise e
async for line in response.iter_lines():
if line.startswith(b'data: '):
if line == b'data: [DONE]':
diff --git a/g4f/Provider/HuggingChat.py b/g4f/Provider/HuggingChat.py
index 509a7f16..2481aa31 100644
--- a/g4f/Provider/HuggingChat.py
+++ b/g4f/Provider/HuggingChat.py
@@ -4,12 +4,13 @@ import json
import requests
try:
- from curl_cffi import requests as cf_reqs
+ from curl_cffi import Session
has_curl_cffi = True
except ImportError:
has_curl_cffi = False
from ..typing import CreateResult, Messages
from ..errors import MissingRequirementsError
+from ..requests.raise_for_status import raise_for_status
from .base_provider import ProviderModelMixin, AbstractProvider
from .helper import format_prompt
@@ -18,7 +19,7 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
working = True
supports_stream = True
default_model = "meta-llama/Meta-Llama-3.1-70B-Instruct"
-
+
models = [
'meta-llama/Meta-Llama-3.1-70B-Instruct',
'CohereForAI/c4ai-command-r-plus-08-2024',
@@ -30,7 +31,7 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
'mistralai/Mistral-Nemo-Instruct-2407',
'microsoft/Phi-3.5-mini-instruct',
]
-
+
model_aliases = {
"llama-3.1-70b": "meta-llama/Meta-Llama-3.1-70B-Instruct",
"command-r-plus": "CohereForAI/c4ai-command-r-plus-08-2024",
@@ -44,15 +45,6 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
}
@classmethod
- def get_model(cls, model: str) -> str:
- if model in cls.models:
- return model
- elif model in cls.model_aliases:
- return cls.model_aliases[model]
- else:
- return cls.default_model
-
- @classmethod
def create_completion(
cls,
model: str,
@@ -65,7 +57,7 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
model = cls.get_model(model)
if model in cls.models:
- session = cf_reqs.Session()
+ session = Session()
session.headers = {
'accept': '*/*',
'accept-language': 'en',
@@ -82,20 +74,18 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
}
-
json_data = {
'model': model,
}
-
response = session.post('https://huggingface.co/chat/conversation', json=json_data)
- if response.status_code != 200:
- raise RuntimeError(f"Request failed with status code: {response.status_code}, response: {response.text}")
+ raise_for_status(response)
conversationId = response.json().get('conversationId')
# Get the data response and parse it properly
response = session.get(f'https://huggingface.co/chat/conversation/{conversationId}/__data.json?x-sveltekit-invalidated=11')
-
+ raise_for_status(response)
+
# Split the response content by newlines and parse each line as JSON
try:
json_data = None
@@ -156,6 +146,7 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
headers=headers,
files=files,
)
+ raise_for_status(response)
full_response = ""
for line in response.iter_lines():
@@ -182,9 +173,4 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
full_response = full_response.replace('<|im_end|', '').replace('\u0000', '').strip()
if not stream:
- yield full_response
-
- @classmethod
- def supports_model(cls, model: str) -> bool:
- """Check if the model is supported by the provider."""
- return model in cls.models or model in cls.model_aliases
+ yield full_response \ No newline at end of file
diff --git a/g4f/Provider/needs_auth/Gemini.py b/g4f/Provider/needs_auth/Gemini.py
index dad54c84..781aa410 100644
--- a/g4f/Provider/needs_auth/Gemini.py
+++ b/g4f/Provider/needs_auth/Gemini.py
@@ -6,24 +6,20 @@ import random
import re
from aiohttp import ClientSession, BaseConnector
-
-from ..helper import get_connector
-
try:
- from selenium.webdriver.common.by import By
- from selenium.webdriver.support.ui import WebDriverWait
- from selenium.webdriver.support import expected_conditions as EC
+ import nodriver
+ has_nodriver = True
except ImportError:
- pass
+ has_nodriver = False
from ... import debug
from ...typing import Messages, Cookies, ImageType, AsyncResult, AsyncIterator
from ..base_provider import AsyncGeneratorProvider, BaseConversation
from ..helper import format_prompt, get_cookies
from ...requests.raise_for_status import raise_for_status
-from ...errors import MissingAuthError, MissingRequirementsError
+from ...requests.aiohttp import get_connector
+from ...errors import MissingAuthError
from ...image import ImageResponse, to_bytes
-from ...webdriver import get_browser, get_driver_cookies
REQUEST_HEADERS = {
"authority": "gemini.google.com",
@@ -64,9 +60,9 @@ class Gemini(AsyncGeneratorProvider):
@classmethod
async def nodriver_login(cls, proxy: str = None) -> AsyncIterator[str]:
- try:
- import nodriver as uc
- except ImportError:
+ if not has_nodriver:
+ if debug.logging:
+ print("Skip nodriver login in Gemini provider")
return
try:
from platformdirs import user_config_dir
@@ -75,7 +71,7 @@ class Gemini(AsyncGeneratorProvider):
user_data_dir = None
if debug.logging:
print(f"Open nodriver with user_dir: {user_data_dir}")
- browser = await uc.start(
+ browser = await nodriver.start(
user_data_dir=user_data_dir,
browser_args=None if proxy is None else [f"--proxy-server={proxy}"],
)
@@ -92,30 +88,6 @@ class Gemini(AsyncGeneratorProvider):
cls._cookies = cookies
@classmethod
- async def webdriver_login(cls, proxy: str) -> AsyncIterator[str]:
- driver = None
- try:
- driver = get_browser(proxy=proxy)
- try:
- driver.get(f"{cls.url}/app")
- WebDriverWait(driver, 5).until(
- EC.visibility_of_element_located((By.CSS_SELECTOR, "div.ql-editor.textarea"))
- )
- except:
- login_url = os.environ.get("G4F_LOGIN_URL")
- if login_url:
- yield f"Please login: [Google Gemini]({login_url})\n\n"
- WebDriverWait(driver, 240).until(
- EC.visibility_of_element_located((By.CSS_SELECTOR, "div.ql-editor.textarea"))
- )
- cls._cookies = get_driver_cookies(driver)
- except MissingRequirementsError:
- pass
- finally:
- if driver:
- driver.close()
-
- @classmethod
async def create_async_generator(
cls,
model: str,
@@ -143,9 +115,6 @@ class Gemini(AsyncGeneratorProvider):
if not cls._snlm0e:
async for chunk in cls.nodriver_login(proxy):
yield chunk
- if cls._cookies is None:
- async for chunk in cls.webdriver_login(proxy):
- yield chunk
if not cls._snlm0e:
if cls._cookies is None or "__Secure-1PSID" not in cls._cookies:
raise MissingAuthError('Missing "__Secure-1PSID" cookie')
@@ -211,20 +180,23 @@ class Gemini(AsyncGeneratorProvider):
yield content[last_content_len:]
last_content_len = len(content)
if image_prompt:
- images = [image[0][3][3] for image in response_part[4][0][12][7][0]]
- if response_format == "b64_json":
- yield ImageResponse(images, image_prompt, {"cookies": cls._cookies})
- else:
- resolved_images = []
- preview = []
- for image in images:
- async with client.get(image, allow_redirects=False) as fetch:
- image = fetch.headers["location"]
- async with client.get(image, allow_redirects=False) as fetch:
- image = fetch.headers["location"]
- resolved_images.append(image)
- preview.append(image.replace('=s512', '=s200'))
- yield ImageResponse(resolved_images, image_prompt, {"orginal_links": images, "preview": preview})
+ try:
+ images = [image[0][3][3] for image in response_part[4][0][12][7][0]]
+ if response_format == "b64_json":
+ yield ImageResponse(images, image_prompt, {"cookies": cls._cookies})
+ else:
+ resolved_images = []
+ preview = []
+ for image in images:
+ async with client.get(image, allow_redirects=False) as fetch:
+ image = fetch.headers["location"]
+ async with client.get(image, allow_redirects=False) as fetch:
+ image = fetch.headers["location"]
+ resolved_images.append(image)
+ preview.append(image.replace('=s512', '=s200'))
+ yield ImageResponse(resolved_images, image_prompt, {"orginal_links": images, "preview": preview})
+ except TypeError:
+ pass
def build_request(
prompt: str,
diff --git a/g4f/Provider/needs_auth/GeminiPro.py b/g4f/Provider/needs_auth/GeminiPro.py
index 7e52a194..a7f1e0aa 100644
--- a/g4f/Provider/needs_auth/GeminiPro.py
+++ b/g4f/Provider/needs_auth/GeminiPro.py
@@ -16,9 +16,9 @@ class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin):
working = True
supports_message_history = True
needs_auth = True
- default_model = "gemini-1.5-pro-latest"
+ default_model = "gemini-1.5-pro"
default_vision_model = default_model
- models = [default_model, "gemini-pro", "gemini-pro-vision", "gemini-1.5-flash"]
+ models = [default_model, "gemini-pro", "gemini-1.5-flash", "gemini-1.5-flash-8b"]
@classmethod
async def create_async_generator(
diff --git a/g4f/Provider/needs_auth/HuggingFace.py b/g4f/Provider/needs_auth/HuggingFace.py
index ecc75d1c..35270e60 100644
--- a/g4f/Provider/needs_auth/HuggingFace.py
+++ b/g4f/Provider/needs_auth/HuggingFace.py
@@ -1,13 +1,11 @@
from __future__ import annotations
import json
-from aiohttp import ClientSession, BaseConnector
from ...typing import AsyncResult, Messages
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
-from ..helper import get_connector
-from ...errors import RateLimitError, ModelNotFoundError
-from ...requests.raise_for_status import raise_for_status
+from ...errors import ModelNotFoundError
+from ...requests import StreamSession, raise_for_status
from ..HuggingChat import HuggingChat
@@ -21,22 +19,12 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
model_aliases = HuggingChat.model_aliases
@classmethod
- def get_model(cls, model: str) -> str:
- if model in cls.models:
- return model
- elif model in cls.model_aliases:
- return cls.model_aliases[model]
- else:
- return cls.default_model
-
- @classmethod
async def create_async_generator(
cls,
model: str,
messages: Messages,
stream: bool = True,
proxy: str = None,
- connector: BaseConnector = None,
api_base: str = "https://api-inference.huggingface.co",
api_key: str = None,
max_new_tokens: int = 1024,
@@ -62,7 +50,6 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
}
if api_key is not None:
headers["Authorization"] = f"Bearer {api_key}"
-
params = {
"return_full_text": False,
"max_new_tokens": max_new_tokens,
@@ -70,10 +57,9 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
**kwargs
}
payload = {"inputs": format_prompt(messages), "parameters": params, "stream": stream}
-
- async with ClientSession(
+ async with StreamSession(
headers=headers,
- connector=get_connector(connector, proxy)
+ proxy=proxy
) as session:
async with session.post(f"{api_base.rstrip('/')}/models/{model}", json=payload) as response:
if response.status == 404:
@@ -81,7 +67,7 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
await raise_for_status(response)
if stream:
first = True
- async for line in response.content:
+ async for line in response.iter_lines():
if line.startswith(b"data:"):
data = json.loads(line[5:])
if not data["token"]["special"]:
@@ -89,7 +75,8 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
if first:
first = False
chunk = chunk.lstrip()
- yield chunk
+ if chunk:
+ yield chunk
else:
yield (await response.json())[0]["generated_text"].strip()
@@ -101,4 +88,4 @@ def format_prompt(messages: Messages) -> str:
for idx, message in enumerate(messages)
if message["role"] == "assistant"
])
- return f"{history}<s>[INST] {question} [/INST]"
+ return f"{history}<s>[INST] {question} [/INST]" \ No newline at end of file
diff --git a/g4f/Provider/needs_auth/MetaAI.py b/g4f/Provider/needs_auth/MetaAI.py
index 4b730abd..568de701 100644
--- a/g4f/Provider/needs_auth/MetaAI.py
+++ b/g4f/Provider/needs_auth/MetaAI.py
@@ -79,7 +79,6 @@ class MetaAI(AsyncGeneratorProvider, ProviderModelMixin):
self.access_token = None
if self.access_token is None and cookies is None:
await self.update_access_token()
-
if self.access_token is None:
url = "https://www.meta.ai/api/graphql/"
payload = {"lsd": self.lsd, 'fb_dtsg': self.dtsg}
@@ -128,6 +127,8 @@ class MetaAI(AsyncGeneratorProvider, ProviderModelMixin):
json_line = json.loads(line)
except json.JSONDecodeError:
continue
+ if json_line.get("errors"):
+ raise RuntimeError("\n".join([error.get("message") for error in json_line.get("errors")]))
bot_response_message = json_line.get("data", {}).get("node", {}).get("bot_response_message", {})
streaming_state = bot_response_message.get("streaming_state")
fetch_id = bot_response_message.get("fetch_id") or fetch_id
diff --git a/g4f/Provider/needs_auth/MetaAIAccount.py b/g4f/Provider/needs_auth/MetaAIAccount.py
index 2d54f3e0..0a586006 100644
--- a/g4f/Provider/needs_auth/MetaAIAccount.py
+++ b/g4f/Provider/needs_auth/MetaAIAccount.py
@@ -2,7 +2,7 @@ from __future__ import annotations
from ...typing import AsyncResult, Messages, Cookies
from ..helper import format_prompt, get_cookies
-from ..MetaAI import MetaAI
+from .MetaAI import MetaAI
class MetaAIAccount(MetaAI):
needs_auth = True
diff --git a/g4f/Provider/needs_auth/__init__.py b/g4f/Provider/needs_auth/__init__.py
index 26c50c0a..ace53876 100644
--- a/g4f/Provider/needs_auth/__init__.py
+++ b/g4f/Provider/needs_auth/__init__.py
@@ -11,6 +11,7 @@ from .GeminiPro import GeminiPro
from .Groq import Groq
from .HuggingFace import HuggingFace
from .MetaAI import MetaAI
+from .MetaAIAccount import MetaAIAccount
from .OpenaiAPI import OpenaiAPI
from .OpenaiChat import OpenaiChat
from .PerplexityApi import PerplexityApi
diff --git a/g4f/gui/server/api.py b/g4f/gui/server/api.py
index dafcb5d4..f03d2048 100644
--- a/g4f/gui/server/api.py
+++ b/g4f/gui/server/api.py
@@ -6,14 +6,14 @@ import uuid
import asyncio
import time
from aiohttp import ClientSession
-from typing import Iterator, Optional, AsyncIterator, Union
+from typing import Iterator, Optional
from flask import send_from_directory
from g4f import version, models
from g4f import get_last_provider, ChatCompletion
from g4f.errors import VersionNotFoundError
from g4f.typing import Cookies
-from g4f.image import ImagePreview, ImageResponse, is_accepted_format
+from g4f.image import ImagePreview, ImageResponse, is_accepted_format, extract_data_uri
from g4f.requests.aiohttp import get_connector
from g4f.Provider import ProviderType, __providers__, __map__
from g4f.providers.base_provider import ProviderModelMixin, FinishReason
@@ -31,7 +31,6 @@ def ensure_images_dir():
conversations: dict[dict[str, BaseConversation]] = {}
-
class Api:
@staticmethod
def get_models() -> list[str]:
@@ -176,18 +175,22 @@ class Api:
connector=get_connector(None, os.environ.get("G4F_PROXY")),
cookies=cookies
) as session:
- async def copy_image(image):
- async with session.get(image) as response:
- target = os.path.join(images_dir, f"{int(time.time())}_{str(uuid.uuid4())}")
+ async def copy_image(image: str) -> str:
+ target = os.path.join(images_dir, f"{int(time.time())}_{str(uuid.uuid4())}")
+ if image.startswith("data:"):
with open(target, "wb") as f:
- async for chunk in response.content.iter_any():
- f.write(chunk)
- with open(target, "rb") as f:
- extension = is_accepted_format(f.read(12)).split("/")[-1]
- extension = "jpg" if extension == "jpeg" else extension
- new_target = f"{target}.{extension}"
- os.rename(target, new_target)
- return f"/images/{os.path.basename(new_target)}"
+ f.write(extract_data_uri(image))
+ else:
+ async with session.get(image) as response:
+ with open(target, "wb") as f:
+ async for chunk in response.content.iter_any():
+ f.write(chunk)
+ with open(target, "rb") as f:
+ extension = is_accepted_format(f.read(12)).split("/")[-1]
+ extension = "jpg" if extension == "jpeg" else extension
+ new_target = f"{target}.{extension}"
+ os.rename(target, new_target)
+ return f"/images/{os.path.basename(new_target)}"
return await asyncio.gather(*[copy_image(image) for image in images])
@@ -197,7 +200,6 @@ class Api:
response_type: content
}
-
def get_error_message(exception: Exception) -> str:
message = f"{type(exception).__name__}: {exception}"
provider = get_last_provider()
diff --git a/g4f/image.py b/g4f/image.py
index 556ec43d..8a3d7a74 100644
--- a/g4f/image.py
+++ b/g4f/image.py
@@ -133,7 +133,7 @@ def extract_data_uri(data_uri: str) -> bytes:
Returns:
bytes: The extracted binary data.
"""
- data = data_uri.split(",")[1]
+ data = data_uri.split(",")[-1]
data = base64.b64decode(data)
return data
diff --git a/requirements-slim.txt b/requirements-slim.txt
new file mode 100644
index 00000000..b9cbceba
--- /dev/null
+++ b/requirements-slim.txt
@@ -0,0 +1,16 @@
+requests
+pycryptodome
+curl_cffi>=0.6.2
+aiohttp
+certifi
+duckduckgo-search>=5.0
+nest_asyncio
+werkzeug
+pillow
+fastapi
+uvicorn
+flask
+brotli
+beautifulsoup4
+aiohttp_socks
+cryptography \ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 1a014bac..83130838 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,7 +4,6 @@ curl_cffi>=0.6.2
aiohttp
certifi
browser_cookie3
-PyExecJS
duckduckgo-search>=5.0
nest_asyncio
werkzeug
@@ -19,5 +18,4 @@ aiohttp_socks
pywebview
plyer
cryptography
-nodriver
-cloudscraper
+nodriver \ No newline at end of file
diff --git a/setup.py b/setup.py
index b35f9754..0cafb642 100644
--- a/setup.py
+++ b/setup.py
@@ -62,6 +62,10 @@ EXTRA_REQUIRE = {
"duckduckgo-search>=5.0",
"browser_cookie3"
],
+ "search": [
+ "beautifulsoup4", "pillow",
+ "duckduckgo-search>=5.0",
+ ],
"local": [
"gpt4all"
]