Skip to content

Commit 2fb0163

Browse files
authored
Merge pull request xtekky#2417 from hlohaus/arm3
Improve error handling in api, Update openapi.json workflow
2 parents 1982a78 + c57321e commit 2fb0163

File tree

3 files changed

+25
-52
lines changed

3 files changed

+25
-52
lines changed

.github/workflows/publish-workflow.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ jobs:
1616
python-version: "3.8"
1717
cache: 'pip'
1818
- 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
2022
- name: Generate openapi.json
2123
run: |
2224
python -m etc.tool.openapi

docker/Dockerfile-slim

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
FROM python:bookworm
1+
FROM python:slim-bookworm
22

33
ARG G4F_VERSION
44
ARG G4F_USER=g4f
55
ARG G4F_USER_ID=1000
6-
ARG PYDANTIC_VERSION=1.8.1
76

87
ENV G4F_VERSION $G4F_VERSION
98
ENV G4F_USER $G4F_USER
@@ -12,60 +11,29 @@ ENV G4F_DIR /app
1211

1312
RUN apt-get update && apt-get upgrade -y \
1413
&& apt-get install -y git \
15-
&& apt-get install --quiet --yes --no-install-recommends \
16-
build-essential \
1714
# Add user and user group
1815
&& groupadd -g $G4F_USER_ID $G4F_USER \
1916
&& useradd -rm -G sudo -u $G4F_USER_ID -g $G4F_USER_ID $G4F_USER \
2017
&& mkdir -p /var/log/supervisor \
2118
&& chown "${G4F_USER_ID}:${G4F_USER_ID}" /var/log/supervisor \
2219
&& 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/*
2423

2524
USER $G4F_USER_ID
2625
WORKDIR $G4F_DIR
2726

2827
ENV HOME /home/$G4F_USER
29-
ENV PATH "${HOME}/.local/bin:${HOME}/.cargo/bin:${PATH}"
28+
ENV PATH "${HOME}/.local/bin:${PATH}"
3029

3130
# Create app dir and copy the project's requirements file into it
3231
RUN mkdir -p $G4F_DIR
3332
COPY requirements-min.txt $G4F_DIR
3433
COPY requirements-slim.txt $G4F_DIR
3534

36-
# Install rust toolchain
37-
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
38-
3935
# 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
6937

7038
# Copy the entire package into the container.
7139
ADD --chown=$G4F_USER:$G4F_USER g4f $G4F_DIR/g4f

g4f/api/__init__.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ def from_exception(cls, exception: Exception,
147147
def from_message(cls, message: str, status_code: int = HTTP_500_INTERNAL_SERVER_ERROR):
148148
return cls(format_exception(message), status_code)
149149

150+
def render(self, content) -> bytes:
151+
return str(content).encode(errors="ignore")
152+
150153
class AppConfig:
151154
ignored_providers: Optional[list[str]] = None
152155
g4f_api_key: Optional[str] = None
@@ -186,9 +189,9 @@ async def authorization(request: Request, call_next):
186189
user_g4f_api_key = await self.get_g4f_api_key(request)
187190
except HTTPException as e:
188191
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)
190193
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)
192195
return await call_next(request)
193196

194197
def register_validation_exception_handler(self):
@@ -249,7 +252,7 @@ async def model_info(model_name: str) -> ModelResponseModel:
249252
'created': 0,
250253
'owned_by': model_info.base_provider
251254
})
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)
253256

254257
@self.app.post("/v1/chat/completions", responses={
255258
HTTP_200_OK: {"model": ChatCompletion},
@@ -318,13 +321,13 @@ async def streaming():
318321

319322
except (ModelNotFoundError, ProviderNotFoundError) as e:
320323
logger.exception(e)
321-
return ErrorResponse(e, HTTP_404_NOT_FOUND)
324+
return ErrorResponse.from_exception(e, config, HTTP_404_NOT_FOUND)
322325
except MissingAuthError as e:
323326
logger.exception(e)
324-
return ErrorResponse(e, HTTP_401_UNAUTHORIZED)
327+
return ErrorResponse.from_exception(e, config, HTTP_401_UNAUTHORIZED)
325328
except Exception as e:
326329
logger.exception(e)
327-
return ErrorResponse(e, HTTP_500_INTERNAL_SERVER_ERROR)
330+
return ErrorResponse.from_exception(e, config, HTTP_500_INTERNAL_SERVER_ERROR)
328331

329332
responses = {
330333
HTTP_200_OK: {"model": ImagesResponse},
@@ -359,13 +362,13 @@ async def generate_image(
359362
return response
360363
except (ModelNotFoundError, ProviderNotFoundError) as e:
361364
logger.exception(e)
362-
return ErrorResponse(e, HTTP_404_NOT_FOUND)
365+
return ErrorResponse.from_exception(e, config, HTTP_404_NOT_FOUND)
363366
except MissingAuthError as e:
364367
logger.exception(e)
365-
return ErrorResponse(e, HTTP_401_UNAUTHORIZED)
368+
return ErrorResponse.from_exception(e, config, HTTP_401_UNAUTHORIZED)
366369
except Exception as e:
367370
logger.exception(e)
368-
return ErrorResponse(e, HTTP_500_INTERNAL_SERVER_ERROR)
371+
return ErrorResponse.from_exception(e, config, HTTP_500_INTERNAL_SERVER_ERROR)
369372

370373
@self.app.get("/v1/providers", responses={
371374
HTTP_200_OK: {"model": List[ProviderResponseModel]},
@@ -428,12 +431,12 @@ def upload_cookies(files: List[UploadFile]):
428431
async def synthesize(request: Request, provider: str):
429432
try:
430433
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)
433436
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)
435438
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)
437440
response_data = provider_handler.synthesize({**request.query_params})
438441
content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
439442
return StreamingResponse(response_data, media_type=content_type)

0 commit comments

Comments
 (0)