返回提交历史
Modified
.github/workflows/publish-workflow.yaml
+12
-0
Added
docker-compose-slim.yml
+25
-0
Modified
docker/Dockerfile
+1
-0
Added
docker/Dockerfile-slim
+68
-0
Added
docker/supervisor-api.conf
+12
-0
Modified
docker/supervisor-gui.conf
+1
-1
Modified
docker/supervisor.conf
+1
-14
Modified
docs/docker.md
+19
-5
Modified
g4f/Provider/Cloudflare.py
+12
-3
Modified
g4f/Provider/HuggingChat.py
+10
-24
Modified
g4f/Provider/needs_auth/Gemini.py
+26
-54
Modified
g4f/Provider/needs_auth/GeminiPro.py
+2
-2
Modified
g4f/Provider/needs_auth/HuggingFace.py
+8
-21
Modified
g4f/Provider/needs_auth/MetaAI.py
+2
-1
Modified
g4f/Provider/needs_auth/MetaAIAccount.py
+1
-1
Modified
g4f/Provider/needs_auth/__init__.py
+1
-0
Modified
g4f/gui/server/api.py
+17
-15
Modified
g4f/image.py
+1
-1
Added
requirements-slim.txt
+16
-0
Modified
requirements.txt
+1
-3
Modified
setup.py
+4
-0
XFEstudio/gpt4free
Add nodriver to Gemini provider, Add slim docker image with google-chrome usage, Add the new docker images to publish worklow, Update requirements.txt and pip requirements
ea144800
代码差异
21 个文件
+240
-145
@@ -48,3 +48,15 @@ jobs:
48
48
labels: ${{ steps.metadata.outputs.labels }}
49
49
build-args: |
50
50
G4F_VERSION=${{ github.ref_name }}
51
- name: Build and push slim image
52
uses: docker/build-push-action@v5
53
with:
54
context: .
55
file: docker/Dockerfile-slim
56
push: true
57
tags: |
58
hlohaus789/g4f=slim
59
hlohaus789/g4f=${{ github.ref_name }}-slim
60
labels: ${{ steps.metadata.outputs.labels }}
61
build-args: |
62
G4F_VERSION=${{ github.ref_name }}
@@ -0,0 +1,25 @@
1
version: '3'
2
3
services:
4
g4f-gui:
5
container_name: g4f-gui
6
image: hlohaus789/g4f:slim
7
build:
8
context: .
9
dockerfile: docker/Dockerfile-slim
10
command: python -m g4f.cli gui -debug
11
volumes:
12
- .:/app
13
ports:
14
- '8080:8080'
15
g4f-api:
16
container_name: g4f-api
17
image: hlohaus789/g4f:slim
18
build:
19
context: .
20
dockerfile: docker/Dockerfile-slim
21
command: python -m g4f.cli api
22
volumes:
23
- .:/app
24
ports:
25
- '1337:1337'
@@ -40,6 +40,7 @@ RUN apt-get -qqy update \
40
40
41
41
# Update entrypoint
42
42
COPY docker/supervisor.conf /etc/supervisor/conf.d/selenium.conf
43
COPY docker/supervisor-api.conf /etc/supervisor/conf.d/api.conf
43
44
COPY docker/supervisor-gui.conf /etc/supervisor/conf.d/gui.conf
44
45
45
46
# If no gui
@@ -0,0 +1,68 @@
1
FROM python:bookworm
2
3
ARG G4F_VERSION
4
ARG G4F_USER=g4f
5
ARG G4F_USER_ID=1000
6
ARG PYDANTIC_VERSION=1.8.1
7
8
ENV G4F_VERSION $G4F_VERSION
9
ENV G4F_USER $G4F_USER
10
ENV G4F_USER_ID $G4F_USER_ID
11
ENV G4F_DIR /app
12
13
RUN apt-get update && apt-get upgrade -y \
14
&& apt-get install -y git \
15
&& apt-get install --quiet --yes --no-install-recommends \
16
build-essential \
17
# Add user and user group
18
&& groupadd -g $G4F_USER_ID $G4F_USER \
19
&& useradd -rm -G sudo -u $G4F_USER_ID -g $G4F_USER_ID $G4F_USER \
20
&& mkdir -p /var/log/supervisor \
21
&& chown "${G4F_USER_ID}:${G4F_USER_ID}" /var/log/supervisor \
22
&& echo "${G4F_USER}:${G4F_USER}" | chpasswd
23
24
USER $G4F_USER_ID
25
WORKDIR $G4F_DIR
26
27
ENV HOME /home/$G4F_USER
28
ENV PATH "${HOME}/.local/bin:${HOME}/.cargo/bin:${PATH}"
29
30
# Create app dir and copy the project's requirements file into it
31
RUN mkdir -p $G4F_DIR
32
COPY requirements-slim.txt $G4F_DIR
33
34
# Install rust toolchain
35
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
36
37
# Upgrade pip for the latest features and install the project's Python dependencies.
38
RUN python -m pip install --upgrade pip \
39
&& pip install --no-cache-dir \
40
Cython==0.29.22 \
41
setuptools \
42
# Install PyDantic
43
&& pip install \
44
-vvv \
45
--no-cache-dir \
46
--no-binary pydantic \
47
--global-option=build_ext \
48
--global-option=-j8 \
49
pydantic==${PYDANTIC_VERSION} \
50
&& pip install --no-cache-dir -r requirements-slim.txt \
51
# Remove build packages
52
&& pip uninstall --yes \
53
Cython \
54
setuptools
55
56
USER root
57
58
# Clean up build deps
59
RUN rustup self uninstall -y \
60
&& apt-get purge --auto-remove --yes \
61
build-essential \
62
&& apt-get clean \
63
&& rm --recursive --force /var/lib/apt/lists/* /tmp/* /var/tmp/*
64
65
USER $G4F_USER_ID
66
67
# Copy the entire package into the container.
68
ADD --chown=$G4F_USER:$G4F_USER g4f $G4F_DIR/g4f
@@ -0,0 +1,12 @@
1
[program:g4f-api]
2
priority=15
3
command=python -m g4f.cli api
4
directory=/app
5
stopasgroup=true
6
autostart=true
7
autorestart=true
8
9
;Logs (all Hub activity redirected to stdout so it can be seen through "docker logs"
10
redirect_stderr=true
11
stdout_logfile=/dev/stdout
12
stdout_logfile_maxbytes=0
@@ -1,6 +1,6 @@
1
1
[program:g4f-gui]
2
2
priority=15
3
command=python -m g4f.cli gui
3
command=python -m g4f.cli gui -debug
4
4
directory=/app
5
5
stopasgroup=true
6
6
autostart=true
@@ -47,17 +47,4 @@ stderr_logfile_maxbytes=50MB
47
47
stdout_logfile_backups=5
48
48
stderr_logfile_backups=5
49
49
stdout_capture_maxbytes=50MB
50
stderr_capture_maxbytes=50MB
51
52
[program:g4f-api]
53
priority=15
54
command=python -m g4f.cli api
55
directory=/app
56
stopasgroup=true
57
autostart=true
58
autorestart=true
59
60
;Logs (all Hub activity redirected to stdout so it can be seen through "docker logs"
61
redirect_stderr=true
62
stdout_logfile=/dev/stdout
63
stdout_logfile_maxbytes=0
50
stderr_capture_maxbytes=50MB
@@ -28,12 +28,22 @@
28
28
```
29
29
30
30
2. **Build and Run with Docker Compose**
31
32
Pull the latest image and run a container with Google Chrome support:
33
```bash
34
docker pull hlohaus789/g4f
35
docker-compose up -d
36
```
37
Or run the small docker images without Google Chrome:
31
38
```bash
32
docker-compose up --build
39
docker-compose -f docker-compose-slim.yml up -d
33
40
```
34
41
35
3. **Access the API**
36
The server will be accessible at `http://localhost:1337`
42
3. **Access the API or the GUI**
43
44
The api server will be accessible at `http://localhost:1337`
45
46
And the gui at this url: `http://localhost:8080`
37
47
38
48
### Non-Docker Method
39
49
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
54
64
python -m g4f.api.run
55
65
```
56
66
57
4. **Access the API**
58
The server will be accessible at `http://localhost:1337`
67
4. **Access the API or the GUI**
68
69
The api server will be accessible at `http://localhost:1337`
70
71
And the gui at this url: `http://localhost:8080`
72
59
73
60
74
## Testing the API
61
75
**You can test the API using curl or by creating a simple Python script:**
@@ -7,6 +7,7 @@ import uuid
7
7
from ..typing import AsyncResult, Messages, Cookies
8
8
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin, get_running_loop
9
9
from ..requests import Session, StreamSession, get_args_from_nodriver, raise_for_status, merge_cookies
10
from ..errors import ResponseStatusError
10
11
11
12
class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
12
13
label = "Cloudflare AI"
@@ -42,10 +43,14 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
42
43
cls._args = asyncio.run(args)
43
44
with Session(**cls._args) as session:
44
45
response = session.get(cls.models_url)
45
raise_for_status(response)
46
cls._args["cookies"] = merge_cookies(cls._args["cookies"] , response)
47
try:
48
raise_for_status(response)
49
except ResponseStatusError as e:
50
cls._args = None
51
raise e
46
52
json_data = response.json()
47
53
cls.models = [model.get("name") for model in json_data.get("models")]
48
cls._args["cookies"] = merge_cookies(cls._args["cookies"] , response)
49
54
return cls.models
50
55
51
56
@classmethod
@@ -74,8 +79,12 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
74
79
cls.api_endpoint,
75
80
json=data,
76
81
) as response:
77
await raise_for_status(response)
78
82
cls._args["cookies"] = merge_cookies(cls._args["cookies"] , response)
83
try:
84
await raise_for_status(response)
85
except ResponseStatusError as e:
86
cls._args = None
87
raise e
79
88
async for line in response.iter_lines():
80
89
if line.startswith(b'data: '):
81
90
if line == b'data: [DONE]':
@@ -4,12 +4,13 @@ import json
4
4
import requests
5
5
6
6
try:
7
from curl_cffi import requests as cf_reqs
7
from curl_cffi import Session
8
8
has_curl_cffi = True
9
9
except ImportError:
10
10
has_curl_cffi = False
11
11
from ..typing import CreateResult, Messages
12
12
from ..errors import MissingRequirementsError
13
from ..requests.raise_for_status import raise_for_status
13
14
from .base_provider import ProviderModelMixin, AbstractProvider
14
15
from .helper import format_prompt
15
16
@@ -18,7 +19,7 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
18
19
working = True
19
20
supports_stream = True
20
21
default_model = "meta-llama/Meta-Llama-3.1-70B-Instruct"
21
22
22
23
models = [
23
24
'meta-llama/Meta-Llama-3.1-70B-Instruct',
24
25
'CohereForAI/c4ai-command-r-plus-08-2024',
@@ -30,7 +31,7 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
30
31
'mistralai/Mistral-Nemo-Instruct-2407',
31
32
'microsoft/Phi-3.5-mini-instruct',
32
33
]
33
34
34
35
model_aliases = {
35
36
"llama-3.1-70b": "meta-llama/Meta-Llama-3.1-70B-Instruct",
36
37
"command-r-plus": "CohereForAI/c4ai-command-r-plus-08-2024",
@@ -43,15 +44,6 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
43
44
"phi-3.5-mini": "microsoft/Phi-3.5-mini-instruct",
44
45
}
45
46
46
@classmethod
47
def get_model(cls, model: str) -> str:
48
if model in cls.models:
49
return model
50
elif model in cls.model_aliases:
51
return cls.model_aliases[model]
52
else:
53
return cls.default_model
54
55
47
@classmethod
56
48
def create_completion(
57
49
cls,
@@ -65,7 +57,7 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
65
57
model = cls.get_model(model)
66
58
67
59
if model in cls.models:
68
session = cf_reqs.Session()
60
session = Session()
69
61
session.headers = {
70
62
'accept': '*/*',
71
63
'accept-language': 'en',
@@ -82,20 +74,18 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
82
74
'sec-fetch-site': 'same-origin',
83
75
'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',
84
76
}
85
86
77
json_data = {
87
78
'model': model,
88
79
}
89
90
80
response = session.post('https://huggingface.co/chat/conversation', json=json_data)
91
if response.status_code != 200:
92
raise RuntimeError(f"Request failed with status code: {response.status_code}, response: {response.text}")
81
raise_for_status(response)
93
82
94
83
conversationId = response.json().get('conversationId')
95
84
96
85
# Get the data response and parse it properly
97
86
response = session.get(f'https://huggingface.co/chat/conversation/{conversationId}/__data.json?x-sveltekit-invalidated=11')
98
87
raise_for_status(response)
88
99
89
# Split the response content by newlines and parse each line as JSON
100
90
try:
101
91
json_data = None
@@ -156,6 +146,7 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
156
146
headers=headers,
157
147
files=files,
158
148
)
149
raise_for_status(response)
159
150
160
151
full_response = ""
161
152
for line in response.iter_lines():
@@ -182,9 +173,4 @@ class HuggingChat(AbstractProvider, ProviderModelMixin):
182
173
full_response = full_response.replace('<|im_end|', '').replace('\u0000', '').strip()
183
174
184
175
if not stream:
185
yield full_response
186
187
@classmethod
188
def supports_model(cls, model: str) -> bool:
189
"""Check if the model is supported by the provider."""
190
return model in cls.models or model in cls.model_aliases
176
yield full_response
@@ -6,24 +6,20 @@ import random
6
6
import re
7
7
8
8
from aiohttp import ClientSession, BaseConnector
9
10
from ..helper import get_connector
11
12
9
try:
13
from selenium.webdriver.common.by import By
14
from selenium.webdriver.support.ui import WebDriverWait
15
from selenium.webdriver.support import expected_conditions as EC
10
import nodriver
11
has_nodriver = True
16
12
except ImportError:
17
pass
13
has_nodriver = False
18
14
19
15
from ... import debug
20
16
from ...typing import Messages, Cookies, ImageType, AsyncResult, AsyncIterator
21
17
from ..base_provider import AsyncGeneratorProvider, BaseConversation
22
18
from ..helper import format_prompt, get_cookies
23
19
from ...requests.raise_for_status import raise_for_status
24
from ...errors import MissingAuthError, MissingRequirementsError
20
from ...requests.aiohttp import get_connector
21
from ...errors import MissingAuthError
25
22
from ...image import ImageResponse, to_bytes
26
from ...webdriver import get_browser, get_driver_cookies
27
23
28
24
REQUEST_HEADERS = {
29
25
"authority": "gemini.google.com",
@@ -64,9 +60,9 @@ class Gemini(AsyncGeneratorProvider):
64
60
65
61
@classmethod
66
62
async def nodriver_login(cls, proxy: str = None) -> AsyncIterator[str]:
67
try:
68
import nodriver as uc
69
except ImportError:
63
if not has_nodriver:
64
if debug.logging:
65
print("Skip nodriver login in Gemini provider")
70
66
return
71
67
try:
72
68
from platformdirs import user_config_dir
@@ -75,7 +71,7 @@ class Gemini(AsyncGeneratorProvider):
75
71
user_data_dir = None
76
72
if debug.logging:
77
73
print(f"Open nodriver with user_dir: {user_data_dir}")
78
browser = await uc.start(
74
browser = await nodriver.start(
79
75
user_data_dir=user_data_dir,
80
76
browser_args=None if proxy is None else [f"--proxy-server={proxy}"],
81
77
)
@@ -91,30 +87,6 @@ class Gemini(AsyncGeneratorProvider):
91
87
await page.close()
92
88
cls._cookies = cookies
93
89
94
@classmethod
95
async def webdriver_login(cls, proxy: str) -> AsyncIterator[str]:
96
driver = None
97
try:
98
driver = get_browser(proxy=proxy)
99
try:
100
driver.get(f"{cls.url}/app")
101
WebDriverWait(driver, 5).until(
102
EC.visibility_of_element_located((By.CSS_SELECTOR, "div.ql-editor.textarea"))
103
)
104
except:
105
login_url = os.environ.get("G4F_LOGIN_URL")
106
if login_url:
107
yield f"Please login: [Google Gemini]({login_url})\n\n"
108
WebDriverWait(driver, 240).until(
109
EC.visibility_of_element_located((By.CSS_SELECTOR, "div.ql-editor.textarea"))
110
)
111
cls._cookies = get_driver_cookies(driver)
112
except MissingRequirementsError:
113
pass
114
finally:
115
if driver:
116
driver.close()
117
118
90
@classmethod
119
91
async def create_async_generator(
120
92
cls,
@@ -143,9 +115,6 @@ class Gemini(AsyncGeneratorProvider):
143
115
if not cls._snlm0e:
144
116
async for chunk in cls.nodriver_login(proxy):
145
117
yield chunk
146
if cls._cookies is None:
147
async for chunk in cls.webdriver_login(proxy):
148
yield chunk
149
118
if not cls._snlm0e:
150
119
if cls._cookies is None or "__Secure-1PSID" not in cls._cookies:
151
120
raise MissingAuthError('Missing "__Secure-1PSID" cookie')
@@ -211,20 +180,23 @@ class Gemini(AsyncGeneratorProvider):
211
180
yield content[last_content_len:]
212
181
last_content_len = len(content)
213
182
if image_prompt:
214
images = [image[0][3][3] for image in response_part[4][0][12][7][0]]
215
if response_format == "b64_json":
216
yield ImageResponse(images, image_prompt, {"cookies": cls._cookies})
217
else:
218
resolved_images = []
219
preview = []
220
for image in images:
221
async with client.get(image, allow_redirects=False) as fetch:
222
image = fetch.headers["location"]
223
async with client.get(image, allow_redirects=False) as fetch:
224
image = fetch.headers["location"]
225
resolved_images.append(image)
226
preview.append(image.replace('=s512', '=s200'))
227
yield ImageResponse(resolved_images, image_prompt, {"orginal_links": images, "preview": preview})
183
try:
184
images = [image[0][3][3] for image in response_part[4][0][12][7][0]]
185
if response_format == "b64_json":
186
yield ImageResponse(images, image_prompt, {"cookies": cls._cookies})
187
else:
188
resolved_images = []
189
preview = []
190
for image in images:
191
async with client.get(image, allow_redirects=False) as fetch:
192
image = fetch.headers["location"]
193
async with client.get(image, allow_redirects=False) as fetch:
194
image = fetch.headers["location"]
195
resolved_images.append(image)
196
preview.append(image.replace('=s512', '=s200'))
197
yield ImageResponse(resolved_images, image_prompt, {"orginal_links": images, "preview": preview})
198
except TypeError:
199
pass
228
200
229
201
def build_request(
230
202
prompt: str,
@@ -16,9 +16,9 @@ class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin):
16
16
working = True
17
17
supports_message_history = True
18
18
needs_auth = True
19
default_model = "gemini-1.5-pro-latest"
19
default_model = "gemini-1.5-pro"
20
20
default_vision_model = default_model
21
models = [default_model, "gemini-pro", "gemini-pro-vision", "gemini-1.5-flash"]
21
models = [default_model, "gemini-pro", "gemini-1.5-flash", "gemini-1.5-flash-8b"]
22
22
23
23
@classmethod
24
24
async def create_async_generator(