mirror of
https://github.com/xtekky/gpt4free.git
synced 2024-12-23 11:02:40 +03:00
Merge pull request #2362 from hlohaus/data-uri
Add nodriver to Gemini provider,
This commit is contained in:
commit
275574d71e
12
.github/workflows/publish-workflow.yaml
vendored
12
.github/workflows/publish-workflow.yaml
vendored
@ -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 }}
|
25
docker-compose-slim.yml
Normal file
25
docker-compose-slim.yml
Normal file
@ -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'
|
@ -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
|
||||
|
68
docker/Dockerfile-slim
Normal file
68
docker/Dockerfile-slim
Normal file
@ -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
|
12
docker/supervisor-api.conf
Executable file
12
docker/supervisor-api.conf
Executable file
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
stderr_capture_maxbytes=50MB
|
@ -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-compose up --build
|
||||
docker pull hlohaus789/g4f
|
||||
docker-compose up -d
|
||||
```
|
||||
Or run the small docker images without Google Chrome:
|
||||
```bash
|
||||
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:**
|
||||
|
@ -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]':
|
||||
|
@ -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",
|
||||
@ -43,15 +44,6 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
|
||||
"phi-3.5-mini": "microsoft/Phi-3.5-mini-instruct",
|
||||
}
|
||||
|
||||
@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,
|
||||
@ -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
|
@ -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}"],
|
||||
)
|
||||
@ -91,30 +87,6 @@ class Gemini(AsyncGeneratorProvider):
|
||||
await page.close()
|
||||
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,
|
||||
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
||||
@ -20,15 +18,6 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
|
||||
models = HuggingChat.models
|
||||
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,
|
||||
@ -36,7 +25,6 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
|
||||
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]"
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
16
requirements-slim.txt
Normal file
16
requirements-slim.txt
Normal file
@ -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
|
@ -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
|
Loading…
Reference in New Issue
Block a user