USER
Your task is to review pull requests. Instructions:
- Provide the response in following JSON format: {"reviews": [{"line": <line_number>, "body": "<review comment>"}]}
- Do not give positive comments or compliments.
- Provide comments and suggestions ONLY if there is something to improve, otherwise "reviews" should be an empty array.
- Write the comment in GitHub Markdown format.
- Use the given description only for the overall context and only comment the code.
- IMPORTANT: NEVER suggest adding comments to the code.
Review the following code diff in the file "g4f/api/__init__.py" and take the pull request title and description into account when writing the response.
Pull request title: [pull] main from xtekky:main
Pull request description:
---
See Commits and Changes for more details.
-----
Created by [<img src="https://prod.download/pull-18h-svg" valign="bottom"/> **pull[bot]**](https://github.com/wei/pull)
_Can you help keep this open source service alive? **[💖 Please sponsor : )](https://prod.download/pull-pr-sponsor)**_
---
Each line is prefixed by its number. Code to review:
```
14: from fastapi.encoders import jsonable_encoder
15: from fastapi.middleware.cors import CORSMiddleware
16: from pydantic import BaseModel
-from typing import Union, Optional
17:+from typing import Union, Optional, Iterator
18:
19: import g4f
20: import g4f.debug
-from g4f.client import Client
21:+from g4f.client import Client, ChatCompletion, ChatCompletionChunk, ImagesResponse
22: from g4f.typing import Messages
23: from g4f.cookies import read_cookie_files
24:
-def create_app():
25:+def create_app(g4f_api_key: str = None):
26: app = FastAPI()
- api = Api(app)
27:+
28:+ # Add CORS middleware
29: app.add_middleware(
30: CORSMiddleware,
31: allow_origin_regex=".*",
33: allow_methods=["*"],
34: allow_headers=["*"],
35: )
36:+
37:+ api = Api(app, g4f_api_key=g4f_api_key)
38: api.register_routes()
39: api.register_authorization()
40: api.register_validation_exception_handler()
41:+
42:+ # Read cookie files if not ignored
43: if not AppConfig.ignore_cookie_files:
44: read_cookie_files()
- return app
45:
-def create_app_debug():
- g4f.debug.logging = True
- return create_app()
46:+ return app
47:
-class ChatCompletionsForm(BaseModel):
48:+class ChatCompletionsConfig(BaseModel):
49: messages: Messages
50: model: str
51: provider: Optional[str] = None
57: web_search: Optional[bool] = None
58: proxy: Optional[str] = None
59:
-class ImagesGenerateForm(BaseModel):
- model: Optional[str] = None
- provider: Optional[str] = None
60:+class ImageGenerationConfig(BaseModel):
61: prompt: str
- response_format: Optional[str] = None
- api_key: Optional[str] = None
- proxy: Optional[str] = None
62:+ model: Optional[str] = None
63:+ response_format: str = "url"
64:
-class AppConfig():
65:+class AppConfig:
66: ignored_providers: Optional[list[str]] = None
67: g4f_api_key: Optional[str] = None
68: ignore_cookie_files: bool = False
73: for key, value in data.items():
74: setattr(cls, key, value)
75:
76:+list_ignored_providers: list[str] = None
77:+
78:+def set_list_ignored_providers(ignored: list[str]):
79:+ global list_ignored_providers
80:+ list_ignored_providers = ignored
81:+
82: class Api:
- def __init__(self, app: FastAPI) -> None:
83:+ def __init__(self, app: FastAPI, g4f_api_key=None) -> None:
84: self.app = app
85: self.client = Client()
86:+ self.g4f_api_key = g4f_api_key
87: self.get_g4f_api_key = APIKeyHeader(name="g4f-api-key")
88:
89: def register_authorization(self):
90: @self.app.middleware("http")
91: async def authorization(request: Request, call_next):
- if AppConfig.g4f_api_key and request.url.path in ["/v1/chat/completions", "/v1/completions"]:
92:+ if self.g4f_api_key and request.url.path in ["/v1/chat/completions", "/v1/completions", "/v1/images/generate"]:
93: try:
94: user_g4f_api_key = await self.get_g4f_api_key(request)
95: except HTTPException as e:
98: status_code=HTTP_401_UNAUTHORIZED,
99: content=jsonable_encoder({"detail": "G4F API key required"}),
100: )
- if not secrets.compare_digest(AppConfig.g4f_api_key, user_g4f_api_key):
101:+ if not secrets.compare_digest(self.g4f_api_key, user_g4f_api_key):
102: return JSONResponse(
103: status_code=HTTP_403_FORBIDDEN,
104: content=jsonable_encoder({"detail": "Invalid G4F API key"}),
105: )
- return await call_next(request)
106:+
107:+ response = await call_next(request)
108:+ return response
109:
110: def register_validation_exception_handler(self):
111: @self.app.exception_handler(RequestValidationError)
112: async def validation_exception_handler(request: Request, exc: RequestValidationError):
113: details = exc.errors()
- modified_details = [{
- "loc": error["loc"],
- "message": error["msg"],
- "type": error["type"],
- } for error in details]
114:+ modified_details = []
115:+ for error in details:
116:+ modified_details.append({
117:+ "loc": error["loc"],
118:+ "message": error["msg"],
119:+ "type": error["type"],
120:+ })
121: return JSONResponse(
122: status_code=HTTP_422_UNPROCESSABLE_ENTITY,
123: content=jsonable_encoder({"detail": modified_details}),
131: @self.app.get("/v1")
132: async def read_root_v1():
133: return HTMLResponse('g4f API: Go to '
- '<a href="/v1/chat/completions">chat/completions</a> '
- 'or <a href="/v1/models">models</a>.')
134:+ '<a href="/v1/chat/completions">chat/completions</a>, '
135:+ '<a href="/v1/models">models</a>, or '
136:+ '<a href="/v1/images/generate">images/generate</a>.')
137:
138: @self.app.get("/v1/models")
139: async def models():
- model_list = {
- model: g4f.models.ModelUtils.convert[model]
140:+ model_list = dict(
141:+ (model, g4f.models.ModelUtils.convert[model])
142: for model in g4f.Model.__all__()
- }
143:+ )
144: model_list = [{
145: 'id': model_id,
146: 'object': 'model',
147: 'created': 0,
148: 'owned_by': model.base_provider
149: } for model_id, model in model_list.items()]
- return JSONResponse({
- "object": "list",
- "data": model_list,
- })
150:+ return JSONResponse(model_list)
151:
152: @self.app.get("/v1/models/{model_name}")
153: async def model_info(model_name: str):
163: return JSONResponse({"error": "The model does not exist."})
164:
165: @self.app.post("/v1/chat/completions")
- async def chat_completions(config: ChatCompletionsForm, request: Request = None, provider: str = None):
166:+ async def chat_completions(config: ChatCompletionsConfig, request: Request = None, provider: str = None):
167: try:
168: config.provider = provider if config.provider is None else config.provider
169: if config.api_key is None and request is not None:
172: auth_header = auth_header.split(None, 1)[-1]
173: if auth_header and auth_header != "Bearer":
174: config.api_key = auth_header
- # Use the asynchronous create method and await it
- response = await self.client.chat.completions.async_create(
175:+
176:+ # Create the completion response
177:+ response = self.client.chat.completions.create(
178: **{
179: **AppConfig.defaults,
180: **config.dict(exclude_none=True),
181: },
182: ignored=AppConfig.ignored_providers
183: )
- if not config.stream:
184:+
185:+ # Check if the response is synchronous or asynchronous
186:+ if isinstance(response, ChatCompletion):
187:+ # Synchronous response
188: return JSONResponse(response.to_json())
189:
190:+ if not config.stream:
191:+ # If the response is an iterator but not streaming, collect the result
192:+ response_list = list(response) if isinstance(response, Iterator) else [response]
193:+ return JSONResponse(response_list[0].to_json())
194:+
195:+ # Streaming response
196: async def streaming():
197: try:
198: async for chunk in response:
203: logging.exception(e)
204: yield f'data: {format_exception(e, config)}\n\n'
205: yield "data: [DONE]\n\n"
206:+
207: return StreamingResponse(streaming(), media_type="text/event-stream")
208:
209: except Exception as e:
210: logging.exception(e)
211: return Response(content=format_exception(e, config), status_code=500, media_type="application/json")
212:
- @self.app.post("/v1/completions")
- async def completions():
- return Response(content=json.dumps({'info': 'Not working yet.'}, indent=4), media_type="application/json")
-
- @self.app.post("/v1/images/generations")
- async def images_generate(config: ImagesGenerateForm, request: Request = None, provider: str = None):
213:+ @self.app.post("/v1/images/generate")
214:+ async def generate_image(config: ImageGenerationConfig):
215: try:
- config.provider = provider if config.provider is None else config.provider
- if config.api_key is None and request is not None:
- auth_header = request.headers.get("Authorization")
- if auth_header is not None:
- auth_header = auth_header.split(None, 1)[-1]
- if auth_header and auth_header != "Bearer":
- config.api_key = auth_header
- # Use the asynchronous generate method and await it
- response = await self.client.images.async_generate(
- **config.dict(exclude_none=True),
216:+ response: ImagesResponse = await self.client.images.async_generate(
217:+ prompt=config.prompt,
218:+ model=config.model,
219:+ response_format=config.response_format
220: )
- return JSONResponse(response.to_json())
221:+ # Convert Image objects to dictionaries
222:+ response_data = [image.to_dict() for image in response.data]
223:+ return JSONResponse({"data": response_data})
224: except Exception as e:
225: logging.exception(e)
226: return Response(content=format_exception(e, config), status_code=500, media_type="application/json")
227:
-def format_exception(e: Exception, config: ChatCompletionsForm) -> str:
228:+ @self.app.post("/v1/completions")
229:+ async def completions():
230:+ return Response(content=json.dumps({'info': 'Not working yet.'}, indent=4), media_type="application/json")
231:+
232:+def format_exception(e: Exception, config: Union[ChatCompletionsConfig, ImageGenerationConfig]) -> str:
233: last_provider = g4f.get_last_provider(True)
234: return json.dumps({
235: "error": {"message": f"{e.__class__.__name__}: {e}"},
- "model": last_provider.get("model") if last_provider else config.model,
- "provider": last_provider.get("name") if last_provider else config.provider
236:+ "model": last_provider.get("model") if last_provider else getattr(config, 'model', None),
237:+ "provider": last_provider.get("name") if last_provider else getattr(config, 'provider', None)
238: })
239:
240: def run_api(
243: bind: str = None,
244: debug: bool = False,
245: workers: int = None,
- use_colors: bool = None
246:+ use_colors: bool = None,
247:+ g4f_api_key: str = None
248: ) -> None:
249: print(f'Starting server... [g4f v-{g4f.version.utils.current_version}]' + (" (debug)" if debug else ""))
250: if use_colors is None:
251: use_colors = debug
252: if bind is not None:
253: host, port = bind.split(":")
254:+ if debug:
255:+ g4f.debug.logging = True
256: uvicorn.run(
- f"g4f.api:create_app{'_debug' if debug else ''}",
- host=host, port=int(port),
- workers=workers,
- use_colors=use_colors,
- factory=True,
257:+ "g4f.api:create_app",
258:+ host=host,
259:+ port=int(port),
260:+ workers=workers,
261:+ use_colors=use_colors,
262:+ factory=True,
263: reload=debug
264: )
```