XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 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
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

21 个文件 +240 -145
Modified .github/workflows/publish-workflow.yaml +12 -0
@@ -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 }}
Added docker-compose-slim.yml +25 -0
@@ -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'
Modified docker/Dockerfile +1 -0
@@ -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
Added docker/Dockerfile-slim +68 -0
@@ -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
Added docker/supervisor-api.conf +12 -0
@@ -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
Modified docker/supervisor-gui.conf +1 -1
@@ -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
Modified docker/supervisor.conf +1 -14
@@ -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
Modified docs/docker.md +19 -5
@@ -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:**
Modified g4f/Provider/Cloudflare.py +12 -3
@@ -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]':
Modified g4f/Provider/HuggingChat.py +10 -24
@@ -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
Modified g4f/Provider/needs_auth/Gemini.py +26 -54
@@ -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,
Modified g4f/Provider/needs_auth/GeminiPro.py +2 -2
@@ -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(
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