返回提交历史
Modified
.github/workflows/publish-workflow.yaml
+3
-1
Modified
docker/Dockerfile-slim
+6
-38
Modified
g4f/api/__init__.py
+16
-13
XFEstudio/gpt4free
Improve error handling in api, Update openapi.json workflow
c57321e2
代码差异
3 个文件
+25
-52
@@ -16,7 +16,9 @@ jobs:
16
16
python-version: "3.8"
17
17
cache: 'pip'
18
18
- name: Install requirements
19
run: pip install fastapi uvicorn python-multipart
19
run: |
20
pip install fastapi uvicorn python-multipart
21
pip install -r requirements-min.txt
20
22
- name: Generate openapi.json
21
23
run: |
22
24
python -m etc.tool.openapi
@@ -1,9 +1,8 @@
1
FROM python:bookworm
1
FROM python:slim-bookworm
2
2
3
3
ARG G4F_VERSION
4
4
ARG G4F_USER=g4f
5
5
ARG G4F_USER_ID=1000
6
ARG PYDANTIC_VERSION=1.8.1
7
6
8
7
ENV G4F_VERSION $G4F_VERSION
9
8
ENV G4F_USER $G4F_USER
@@ -12,60 +11,29 @@ ENV G4F_DIR /app
12
11
13
12
RUN apt-get update && apt-get upgrade -y \
14
13
&& apt-get install -y git \
15
&& apt-get install --quiet --yes --no-install-recommends \
16
build-essential \
17
14
# Add user and user group
18
15
&& groupadd -g $G4F_USER_ID $G4F_USER \
19
16
&& useradd -rm -G sudo -u $G4F_USER_ID -g $G4F_USER_ID $G4F_USER \
20
17
&& mkdir -p /var/log/supervisor \
21
18
&& chown "${G4F_USER_ID}:${G4F_USER_ID}" /var/log/supervisor \
22
19
&& echo "${G4F_USER}:${G4F_USER}" | chpasswd \
23
&& python -m pip install --upgrade pip
20
&& python -m pip install --upgrade pip \
21
&& apt-get clean \
22
&& rm --recursive --force /var/lib/apt/lists/* /tmp/* /var/tmp/*
24
23
25
24
USER $G4F_USER_ID
26
25
WORKDIR $G4F_DIR
27
26
28
27
ENV HOME /home/$G4F_USER
29
ENV PATH "${HOME}/.local/bin:${HOME}/.cargo/bin:${PATH}"
28
ENV PATH "${HOME}/.local/bin:${PATH}"
30
29
31
30
# Create app dir and copy the project's requirements file into it
32
31
RUN mkdir -p $G4F_DIR
33
32
COPY requirements-min.txt $G4F_DIR
34
33
COPY requirements-slim.txt $G4F_DIR
35
34
36
# Install rust toolchain
37
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
38
39
35
# Upgrade pip for the latest features and install the project's Python dependencies.
40
RUN pip install --no-cache-dir -r requirements-min.txt \
41
&& pip install --no-cache-dir --no-binary setuptools \
42
Cython==0.29.22 \
43
setuptools \
44
# Install PyDantic
45
&& pip install \
46
-vvv \
47
--no-cache-dir \
48
--no-binary :all: \
49
--global-option=build_ext \
50
--global-option=-j8 \
51
pydantic==${PYDANTIC_VERSION} \
52
&& cat requirements-slim.txt | xargs -n 1 pip install --no-cache-dir || true \
53
# Remove build packages
54
&& pip uninstall --yes \
55
Cython \
56
setuptools
57
58
USER root
59
60
# Clean up build deps
61
RUN rm --recursive --force "${HOME}/.rustup" \
62
&& rustup self uninstall -y \
63
&& apt-get purge --auto-remove --yes \
64
build-essential \
65
&& apt-get clean \
66
&& rm --recursive --force /var/lib/apt/lists/* /tmp/* /var/tmp/*
67
68
USER $G4F_USER_ID
36
RUN cat requirements-slim.txt | xargs -n 1 pip install --no-cache-dir || true
69
37
70
38
# Copy the entire package into the container.
71
39
ADD --chown=$G4F_USER:$G4F_USER g4f $G4F_DIR/g4f
@@ -147,6 +147,9 @@ class ErrorResponse(Response):
147
147
def from_message(cls, message: str, status_code: int = HTTP_500_INTERNAL_SERVER_ERROR):
148
148
return cls(format_exception(message), status_code)
149
149
150
def render(self, content) -> bytes:
151
return str(content).encode(errors="ignore")
152
150
153
class AppConfig:
151
154
ignored_providers: Optional[list[str]] = None
152
155
g4f_api_key: Optional[str] = None
@@ -186,9 +189,9 @@ class Api:
186
189
user_g4f_api_key = await self.get_g4f_api_key(request)
187
190
except HTTPException as e:
188
191
if e.status_code == 403:
189
return ErrorResponse("G4F API key required", HTTP_401_UNAUTHORIZED)
192
return ErrorResponse.from_message("G4F API key required", HTTP_401_UNAUTHORIZED)
190
193
if not secrets.compare_digest(self.g4f_api_key, user_g4f_api_key):
191
return ErrorResponse("Invalid G4F API key", HTTP_403_FORBIDDEN)
194
return ErrorResponse.from_message("Invalid G4F API key", HTTP_403_FORBIDDEN)
192
195
return await call_next(request)
193
196
194
197
def register_validation_exception_handler(self):
@@ -249,7 +252,7 @@ class Api:
249
252
'created': 0,
250
253
'owned_by': model_info.base_provider
251
254
})
252
return ErrorResponse("The model does not exist.", HTTP_404_NOT_FOUND)
255
return ErrorResponse.from_message("The model does not exist.", HTTP_404_NOT_FOUND)
253
256
254
257
@self.app.post("/v1/chat/completions", responses={
255
258
HTTP_200_OK: {"model": ChatCompletion},
@@ -318,13 +321,13 @@ class Api:
318
321
319
322
except (ModelNotFoundError, ProviderNotFoundError) as e:
320
323
logger.exception(e)
321
return ErrorResponse(e, HTTP_404_NOT_FOUND)
324
return ErrorResponse.from_exception(e, config, HTTP_404_NOT_FOUND)
322
325
except MissingAuthError as e:
323
326
logger.exception(e)
324
return ErrorResponse(e, HTTP_401_UNAUTHORIZED)
327
return ErrorResponse.from_exception(e, config, HTTP_401_UNAUTHORIZED)
325
328
except Exception as e:
326
329
logger.exception(e)
327
return ErrorResponse(e, HTTP_500_INTERNAL_SERVER_ERROR)
330
return ErrorResponse.from_exception(e, config, HTTP_500_INTERNAL_SERVER_ERROR)
328
331
329
332
responses = {
330
333
HTTP_200_OK: {"model": ImagesResponse},
@@ -359,13 +362,13 @@ class Api:
359
362
return response
360
363
except (ModelNotFoundError, ProviderNotFoundError) as e:
361
364
logger.exception(e)
362
return ErrorResponse(e, HTTP_404_NOT_FOUND)
365
return ErrorResponse.from_exception(e, config, HTTP_404_NOT_FOUND)
363
366
except MissingAuthError as e:
364
367
logger.exception(e)
365
return ErrorResponse(e, HTTP_401_UNAUTHORIZED)
368
return ErrorResponse.from_exception(e, config, HTTP_401_UNAUTHORIZED)
366
369
except Exception as e:
367
370
logger.exception(e)
368
return ErrorResponse(e, HTTP_500_INTERNAL_SERVER_ERROR)
371
return ErrorResponse.from_exception(e, config, HTTP_500_INTERNAL_SERVER_ERROR)
369
372
370
373
@self.app.get("/v1/providers", responses={
371
374
HTTP_200_OK: {"model": List[ProviderResponseModel]},
@@ -428,12 +431,12 @@ class Api:
428
431
async def synthesize(request: Request, provider: str):
429
432
try:
430
433
provider_handler = convert_to_provider(provider)
431
except ProviderNotFoundError:
432
return ErrorResponse("Provider not found", HTTP_404_NOT_FOUND)
434
except ProviderNotFoundError as e:
435
return ErrorResponse.from_exception(e, status_code=HTTP_404_NOT_FOUND)
433
436
if not hasattr(provider_handler, "synthesize"):
434
return ErrorResponse("Provider doesn't support synthesize", HTTP_404_NOT_FOUND)
437
return ErrorResponse.from_message("Provider doesn't support synthesize", HTTP_404_NOT_FOUND)
435
438
if len(request.query_params) == 0:
436
return ErrorResponse("Missing query params", HTTP_422_UNPROCESSABLE_ENTITY)
439
return ErrorResponse.from_message("Missing query params", HTTP_422_UNPROCESSABLE_ENTITY)
437
440
response_data = provider_handler.synthesize({**request.query_params})
438
441
content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
439
442
return StreamingResponse(response_data, media_type=content_type)