wilson989 commited on
Commit
0a81be9
·
verified ·
1 Parent(s): 3e65ea9

Upload 3 files

Browse files
Files changed (3) hide show
  1. __init__.py +526 -0
  2. run.py +4 -0
  3. stubs.py +78 -0
__init__.py ADDED
@@ -0,0 +1,526 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import json
5
+ import uvicorn
6
+ import secrets
7
+ import os
8
+ import shutil
9
+ from email.utils import formatdate
10
+ import os.path
11
+ from fastapi import FastAPI, Response, Request, UploadFile, Depends
12
+ from fastapi.middleware.wsgi import WSGIMiddleware
13
+ from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
14
+ from fastapi.exceptions import RequestValidationError
15
+ from fastapi.security import APIKeyHeader
16
+ from starlette.exceptions import HTTPException
17
+ from starlette.status import (
18
+ HTTP_200_OK,
19
+ HTTP_422_UNPROCESSABLE_ENTITY,
20
+ HTTP_404_NOT_FOUND,
21
+ HTTP_401_UNAUTHORIZED,
22
+ HTTP_403_FORBIDDEN,
23
+ HTTP_500_INTERNAL_SERVER_ERROR,
24
+ )
25
+ from starlette.staticfiles import NotModifiedResponse
26
+ from fastapi.encoders import jsonable_encoder
27
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials, HTTPBasic
28
+ from fastapi.middleware.cors import CORSMiddleware
29
+ from starlette.responses import FileResponse
30
+ from starlette._compat import md5_hexdigest
31
+ from types import SimpleNamespace
32
+ from typing import Union, Optional, List
33
+
34
+ import g4f
35
+ import g4f.debug
36
+ from g4f.client import AsyncClient, ChatCompletion, ImagesResponse, convert_to_provider
37
+ from g4f.providers.response import BaseConversation, JsonConversation
38
+ from g4f.client.helper import filter_none
39
+ from g4f.image import is_data_uri_an_image, images_dir
40
+ from g4f.errors import ProviderNotFoundError, ModelNotFoundError, MissingAuthError, NoValidHarFileError
41
+ from g4f.cookies import read_cookie_files, get_cookies_dir
42
+ from g4f.Provider import ProviderType, ProviderUtils, __providers__
43
+ from g4f.gui import get_gui_app
44
+ from .stubs import (
45
+ ChatCompletionsConfig, ImageGenerationConfig,
46
+ ProviderResponseModel, ModelResponseModel,
47
+ ErrorResponseModel, ProviderResponseDetailModel,
48
+ FileResponseModel, Annotated
49
+ )
50
+
51
+ logger = logging.getLogger(__name__)
52
+
53
+ DEFAULT_PORT = 1337
54
+
55
+ def create_app():
56
+ app = FastAPI()
57
+
58
+ # Add CORS middleware
59
+ app.add_middleware(
60
+ CORSMiddleware,
61
+ allow_origin_regex=".*",
62
+ allow_credentials=True,
63
+ allow_methods=["*"],
64
+ allow_headers=["*"],
65
+ )
66
+
67
+ api = Api(app)
68
+
69
+ api.register_routes()
70
+ api.register_authorization()
71
+ api.register_validation_exception_handler()
72
+
73
+ if AppConfig.gui:
74
+ gui_app = WSGIMiddleware(get_gui_app())
75
+ app.mount("/", gui_app)
76
+
77
+ # Read cookie files if not ignored
78
+ if not AppConfig.ignore_cookie_files:
79
+ read_cookie_files()
80
+
81
+ if AppConfig.ignored_providers:
82
+ for provider in AppConfig.ignored_providers:
83
+ if provider in ProviderUtils.convert:
84
+ ProviderUtils.convert[provider].working = False
85
+
86
+ return app
87
+
88
+ def create_app_debug():
89
+ g4f.debug.logging = True
90
+ return create_app()
91
+
92
+ def create_app_with_gui_and_debug():
93
+ g4f.debug.logging = True
94
+ AppConfig.gui = True
95
+ return create_app()
96
+
97
+ class ErrorResponse(Response):
98
+ media_type = "application/json"
99
+
100
+ @classmethod
101
+ def from_exception(cls, exception: Exception,
102
+ config: Union[ChatCompletionsConfig, ImageGenerationConfig] = None,
103
+ status_code: int = HTTP_500_INTERNAL_SERVER_ERROR):
104
+ return cls(format_exception(exception, config), status_code)
105
+
106
+ @classmethod
107
+ def from_message(cls, message: str, status_code: int = HTTP_500_INTERNAL_SERVER_ERROR, headers: dict = None):
108
+ return cls(format_exception(message), status_code, headers=headers)
109
+
110
+ def render(self, content) -> bytes:
111
+ return str(content).encode(errors="ignore")
112
+
113
+ class AppConfig:
114
+ ignored_providers: Optional[list[str]] = None
115
+ g4f_api_key: Optional[str] = None
116
+ ignore_cookie_files: bool = False
117
+ model: str = None
118
+ provider: str = None
119
+ image_provider: str = None
120
+ proxy: str = None
121
+ gui: bool = False
122
+
123
+ @classmethod
124
+ def set_config(cls, **data):
125
+ for key, value in data.items():
126
+ setattr(cls, key, value)
127
+
128
+ class Api:
129
+ def __init__(self, app: FastAPI) -> None:
130
+ self.app = app
131
+ self.client = AsyncClient()
132
+ self.get_g4f_api_key = APIKeyHeader(name="g4f-api-key")
133
+ self.conversations: dict[str, dict[str, BaseConversation]] = {}
134
+
135
+ security = HTTPBearer(auto_error=False)
136
+ basic_security = HTTPBasic()
137
+
138
+ async def get_username(self, request: Request) -> str:
139
+ credentials = await self.basic_security(request)
140
+ current_password_bytes = credentials.password.encode()
141
+ is_correct_password = secrets.compare_digest(
142
+ current_password_bytes, AppConfig.g4f_api_key.encode()
143
+ )
144
+ if not is_correct_password:
145
+ raise HTTPException(
146
+ status_code=HTTP_401_UNAUTHORIZED,
147
+ detail="Incorrect username or password",
148
+ headers={"WWW-Authenticate": "Basic"},
149
+ )
150
+ return credentials.username
151
+
152
+ def register_authorization(self):
153
+ if AppConfig.g4f_api_key:
154
+ print(f"Register authentication key: {''.join(['*' for _ in range(len(AppConfig.g4f_api_key))])}")
155
+ @self.app.middleware("http")
156
+ async def authorization(request: Request, call_next):
157
+ if AppConfig.g4f_api_key is not None:
158
+ try:
159
+ user_g4f_api_key = await self.get_g4f_api_key(request)
160
+ except HTTPException:
161
+ user_g4f_api_key = None
162
+ path = request.url.path
163
+ if path.startswith("/v1"):
164
+ if user_g4f_api_key is None:
165
+ return ErrorResponse.from_message("G4F API key required", HTTP_401_UNAUTHORIZED)
166
+ if not secrets.compare_digest(AppConfig.g4f_api_key, user_g4f_api_key):
167
+ return ErrorResponse.from_message("Invalid G4F API key", HTTP_403_FORBIDDEN)
168
+ else:
169
+ if user_g4f_api_key is not None and path.startswith("/images/"):
170
+ if not secrets.compare_digest(AppConfig.g4f_api_key, user_g4f_api_key):
171
+ return ErrorResponse.from_message("Invalid G4F API key", HTTP_403_FORBIDDEN)
172
+ elif path.startswith("/backend-api/") or path.startswith("/images/") or path.startswith("/chat/") and path != "/chat/":
173
+ try:
174
+ username = await self.get_username(request)
175
+ except HTTPException as e:
176
+ return ErrorResponse.from_message(e.detail, e.status_code, e.headers)
177
+ response = await call_next(request)
178
+ response.headers["X-Username"] = username
179
+ return response
180
+ return await call_next(request)
181
+
182
+ def register_validation_exception_handler(self):
183
+ @self.app.exception_handler(RequestValidationError)
184
+ async def validation_exception_handler(request: Request, exc: RequestValidationError):
185
+ details = exc.errors()
186
+ modified_details = []
187
+ for error in details:
188
+ modified_details.append({
189
+ "loc": error["loc"],
190
+ "message": error["msg"],
191
+ "type": error["type"],
192
+ })
193
+ return JSONResponse(
194
+ status_code=HTTP_422_UNPROCESSABLE_ENTITY,
195
+ content=jsonable_encoder({"detail": modified_details}),
196
+ )
197
+
198
+ def register_routes(self):
199
+ @self.app.get("/")
200
+ async def read_root():
201
+ if AppConfig.gui:
202
+ return RedirectResponse("/chat/", 302)
203
+ return RedirectResponse("/v1", 302)
204
+
205
+ @self.app.get("/v1")
206
+ async def read_root_v1():
207
+ return HTMLResponse('g4f API: Go to '
208
+ '<a href="/v1/models">models</a>, '
209
+ '<a href="/v1/chat/completions">chat/completions</a>, or '
210
+ '<a href="/v1/images/generate">images/generate</a> <br><br>'
211
+ 'Open Swagger UI at: '
212
+ '<a href="/docs">/docs</a>')
213
+
214
+ @self.app.get("/v1/models", responses={
215
+ HTTP_200_OK: {"model": List[ModelResponseModel]},
216
+ })
217
+ async def models():
218
+ return [{
219
+ 'id': model_id,
220
+ 'object': 'model',
221
+ 'created': 0,
222
+ 'owned_by': model.base_provider
223
+ } for model_id, model in g4f.models.ModelUtils.convert.items()]
224
+
225
+ @self.app.get("/v1/models/{model_name}", responses={
226
+ HTTP_200_OK: {"model": ModelResponseModel},
227
+ HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
228
+ })
229
+ async def model_info(model_name: str) -> ModelResponseModel:
230
+ if model_name in g4f.models.ModelUtils.convert:
231
+ model_info = g4f.models.ModelUtils.convert[model_name]
232
+ return JSONResponse({
233
+ 'id': model_name,
234
+ 'object': 'model',
235
+ 'created': 0,
236
+ 'owned_by': model_info.base_provider
237
+ })
238
+ return ErrorResponse.from_message("The model does not exist.", HTTP_404_NOT_FOUND)
239
+
240
+ @self.app.post("/v1/chat/completions", responses={
241
+ HTTP_200_OK: {"model": ChatCompletion},
242
+ HTTP_401_UNAUTHORIZED: {"model": ErrorResponseModel},
243
+ HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
244
+ HTTP_422_UNPROCESSABLE_ENTITY: {"model": ErrorResponseModel},
245
+ HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponseModel},
246
+ })
247
+ async def chat_completions(
248
+ config: ChatCompletionsConfig,
249
+ credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None,
250
+ provider: str = None
251
+ ):
252
+ try:
253
+ config.provider = provider if config.provider is None else config.provider
254
+ if config.provider is None:
255
+ config.provider = AppConfig.provider
256
+ if credentials is not None:
257
+ config.api_key = credentials.credentials
258
+
259
+ conversation = return_conversation = None
260
+ if conversation is not None:
261
+ conversation = JsonConversation(**conversation)
262
+ return_conversation = True
263
+ elif config.conversation_id is not None and config.provider is not None:
264
+ return_conversation = True
265
+ if config.conversation_id in self.conversations:
266
+ if config.provider in self.conversations[config.conversation_id]:
267
+ conversation = self.conversations[config.conversation_id][config.provider]
268
+
269
+ if config.image is not None:
270
+ try:
271
+ is_data_uri_an_image(config.image)
272
+ except ValueError as e:
273
+ return ErrorResponse.from_message(f"The image you send must be a data URI. Example: data:image/jpeg;base64,...", status_code=HTTP_422_UNPROCESSABLE_ENTITY)
274
+ if config.images is not None:
275
+ for image in config.images:
276
+ try:
277
+ is_data_uri_an_image(image[0])
278
+ except ValueError as e:
279
+ example = json.dumps({"images": [["data:image/jpeg;base64,...", "filename"]]})
280
+ return ErrorResponse.from_message(f'The image you send must be a data URI. Example: {example}', status_code=HTTP_422_UNPROCESSABLE_ENTITY)
281
+
282
+ # Create the completion response
283
+ response = self.client.chat.completions.create(
284
+ **filter_none(
285
+ **{
286
+ "model": AppConfig.model,
287
+ "provider": AppConfig.provider,
288
+ "proxy": AppConfig.proxy,
289
+ **config.dict(exclude_none=True),
290
+ **{
291
+ "conversation_id": None,
292
+ "return_conversation": return_conversation,
293
+ "conversation": conversation
294
+ }
295
+ },
296
+ ignored=AppConfig.ignored_providers
297
+ ),
298
+ )
299
+
300
+ if not config.stream:
301
+ return await response
302
+
303
+ async def streaming():
304
+ try:
305
+ async for chunk in response:
306
+ if isinstance(chunk, BaseConversation):
307
+ if config.conversation_id is not None and config.provider is not None:
308
+ if config.conversation_id not in self.conversations:
309
+ self.conversations[config.conversation_id] = {}
310
+ self.conversations[config.conversation_id][config.provider] = chunk
311
+ else:
312
+ yield f"data: {chunk.json()}\n\n"
313
+ except GeneratorExit:
314
+ pass
315
+ except Exception as e:
316
+ logger.exception(e)
317
+ yield f'data: {format_exception(e, config)}\n\n'
318
+ yield "data: [DONE]\n\n"
319
+
320
+ return StreamingResponse(streaming(), media_type="text/event-stream")
321
+
322
+ except (ModelNotFoundError, ProviderNotFoundError) as e:
323
+ logger.exception(e)
324
+ return ErrorResponse.from_exception(e, config, HTTP_404_NOT_FOUND)
325
+ except (MissingAuthError, NoValidHarFileError) as e:
326
+ logger.exception(e)
327
+ return ErrorResponse.from_exception(e, config, HTTP_401_UNAUTHORIZED)
328
+ except Exception as e:
329
+ logger.exception(e)
330
+ return ErrorResponse.from_exception(e, config, HTTP_500_INTERNAL_SERVER_ERROR)
331
+
332
+ responses = {
333
+ HTTP_200_OK: {"model": ImagesResponse},
334
+ HTTP_401_UNAUTHORIZED: {"model": ErrorResponseModel},
335
+ HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
336
+ HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponseModel},
337
+ }
338
+ @self.app.post("/v1/images/generate", responses=responses)
339
+ @self.app.post("/v1/images/generations", responses=responses)
340
+ async def generate_image(
341
+ request: Request,
342
+ config: ImageGenerationConfig,
343
+ credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None
344
+ ):
345
+ if credentials is not None:
346
+ config.api_key = credentials.credentials
347
+ try:
348
+ response = await self.client.images.generate(
349
+ prompt=config.prompt,
350
+ model=config.model,
351
+ provider=AppConfig.image_provider if config.provider is None else config.provider,
352
+ **filter_none(
353
+ response_format=config.response_format,
354
+ api_key=config.api_key,
355
+ proxy=config.proxy
356
+ )
357
+ )
358
+ for image in response.data:
359
+ if hasattr(image, "url") and image.url.startswith("/"):
360
+ image.url = f"{request.base_url}{image.url.lstrip('/')}"
361
+ return response
362
+ except (ModelNotFoundError, ProviderNotFoundError) as e:
363
+ logger.exception(e)
364
+ return ErrorResponse.from_exception(e, config, HTTP_404_NOT_FOUND)
365
+ except MissingAuthError as e:
366
+ logger.exception(e)
367
+ return ErrorResponse.from_exception(e, config, HTTP_401_UNAUTHORIZED)
368
+ except Exception as e:
369
+ logger.exception(e)
370
+ return ErrorResponse.from_exception(e, config, HTTP_500_INTERNAL_SERVER_ERROR)
371
+
372
+ @self.app.get("/v1/providers", responses={
373
+ HTTP_200_OK: {"model": List[ProviderResponseModel]},
374
+ })
375
+ async def providers():
376
+ return [{
377
+ 'id': provider.__name__,
378
+ 'object': 'provider',
379
+ 'created': 0,
380
+ 'url': provider.url,
381
+ 'label': getattr(provider, "label", None),
382
+ } for provider in __providers__ if provider.working]
383
+
384
+ @self.app.get("/v1/providers/{provider}", responses={
385
+ HTTP_200_OK: {"model": ProviderResponseDetailModel},
386
+ HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
387
+ })
388
+ async def providers_info(provider: str):
389
+ if provider not in ProviderUtils.convert:
390
+ return ErrorResponse.from_message("The provider does not exist.", 404)
391
+ provider: ProviderType = ProviderUtils.convert[provider]
392
+ def safe_get_models(provider: ProviderType) -> list[str]:
393
+ try:
394
+ return provider.get_models() if hasattr(provider, "get_models") else []
395
+ except:
396
+ return []
397
+ return {
398
+ 'id': provider.__name__,
399
+ 'object': 'provider',
400
+ 'created': 0,
401
+ 'url': provider.url,
402
+ 'label': getattr(provider, "label", None),
403
+ 'models': safe_get_models(provider),
404
+ 'image_models': getattr(provider, "image_models", []) or [],
405
+ 'vision_models': [model for model in [getattr(provider, "default_vision_model", None)] if model],
406
+ 'params': [*provider.get_parameters()] if hasattr(provider, "get_parameters") else []
407
+ }
408
+
409
+ @self.app.post("/v1/upload_cookies", responses={
410
+ HTTP_200_OK: {"model": List[FileResponseModel]},
411
+ })
412
+ def upload_cookies(files: List[UploadFile]):
413
+ response_data = []
414
+ if not AppConfig.ignore_cookie_files:
415
+ for file in files:
416
+ try:
417
+ if file and file.filename.endswith(".json") or file.filename.endswith(".har"):
418
+ filename = os.path.basename(file.filename)
419
+ with open(os.path.join(get_cookies_dir(), filename), 'wb') as f:
420
+ shutil.copyfileobj(file.file, f)
421
+ response_data.append({"filename": filename})
422
+ finally:
423
+ file.file.close()
424
+ read_cookie_files()
425
+ return response_data
426
+
427
+ @self.app.get("/v1/synthesize/{provider}", responses={
428
+ HTTP_200_OK: {"content": {"audio/*": {}}},
429
+ HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
430
+ HTTP_422_UNPROCESSABLE_ENTITY: {"model": ErrorResponseModel},
431
+ })
432
+ async def synthesize(request: Request, provider: str):
433
+ try:
434
+ provider_handler = convert_to_provider(provider)
435
+ except ProviderNotFoundError as e:
436
+ return ErrorResponse.from_exception(e, status_code=HTTP_404_NOT_FOUND)
437
+ if not hasattr(provider_handler, "synthesize"):
438
+ return ErrorResponse.from_message("Provider doesn't support synthesize", HTTP_404_NOT_FOUND)
439
+ if len(request.query_params) == 0:
440
+ return ErrorResponse.from_message("Missing query params", HTTP_422_UNPROCESSABLE_ENTITY)
441
+ response_data = provider_handler.synthesize({**request.query_params})
442
+ content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
443
+ return StreamingResponse(response_data, media_type=content_type)
444
+
445
+ @self.app.get("/images/{filename}", response_class=FileResponse, responses={
446
+ HTTP_200_OK: {"content": {"image/*": {}}},
447
+ HTTP_404_NOT_FOUND: {}
448
+ })
449
+ async def get_image(filename, request: Request):
450
+ target = os.path.join(images_dir, filename)
451
+ ext = os.path.splitext(filename)[1]
452
+ stat_result = SimpleNamespace()
453
+ stat_result.st_size = 0
454
+ if os.path.isfile(target):
455
+ stat_result.st_size = os.stat(target).st_size
456
+ stat_result.st_mtime = int(f"{filename.split('_')[0]}")
457
+ response = FileResponse(
458
+ target,
459
+ media_type=f"image/{ext.replace('jpg', 'jepg')}",
460
+ headers={
461
+ "content-length": str(stat_result.st_size),
462
+ "last-modified": formatdate(stat_result.st_mtime, usegmt=True),
463
+ "etag": f'"{md5_hexdigest(filename.encode(), usedforsecurity=False)}"'
464
+ },
465
+ )
466
+ try:
467
+ if_none_match = request.headers["if-none-match"]
468
+ etag = response.headers["etag"]
469
+ if etag in [tag.strip(" W/") for tag in if_none_match.split(",")]:
470
+ return NotModifiedResponse(response.headers)
471
+ except KeyError:
472
+ pass
473
+ if not os.path.isfile(target):
474
+ return Response(status_code=HTTP_404_NOT_FOUND)
475
+ return response
476
+
477
+ def format_exception(e: Union[Exception, str], config: Union[ChatCompletionsConfig, ImageGenerationConfig] = None, image: bool = False) -> str:
478
+ last_provider = {} if not image else g4f.get_last_provider(True)
479
+ provider = (AppConfig.image_provider if image else AppConfig.provider)
480
+ model = AppConfig.model
481
+ if config is not None:
482
+ if config.provider is not None:
483
+ provider = config.provider
484
+ if config.model is not None:
485
+ model = config.model
486
+ if isinstance(e, str):
487
+ message = e
488
+ else:
489
+ message = f"{e.__class__.__name__}: {e}"
490
+ return json.dumps({
491
+ "error": {"message": message},
492
+ **filter_none(
493
+ model=last_provider.get("model") if model is None else model,
494
+ provider=last_provider.get("name") if provider is None else provider
495
+ )
496
+ })
497
+
498
+ def run_api(
499
+ host: str = '0.0.0.0',
500
+ port: int = None,
501
+ bind: str = None,
502
+ debug: bool = False,
503
+ workers: int = None,
504
+ use_colors: bool = None,
505
+ reload: bool = False
506
+ ) -> None:
507
+ print(f'Starting server... [g4f v-{g4f.version.utils.current_version}]' + (" (debug)" if debug else ""))
508
+ if use_colors is None:
509
+ use_colors = debug
510
+ if bind is not None:
511
+ host, port = bind.split(":")
512
+ if port is None:
513
+ port = DEFAULT_PORT
514
+ if AppConfig.gui and debug:
515
+ method = "create_app_with_gui_and_debug"
516
+ else:
517
+ method = "create_app_debug" if debug else "create_app"
518
+ uvicorn.run(
519
+ f"g4f.api:{method}",
520
+ host=host,
521
+ port=int(port),
522
+ workers=workers,
523
+ use_colors=use_colors,
524
+ factory=True,
525
+ reload=reload
526
+ )
run.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import g4f.api
2
+
3
+ if __name__ == "__main__":
4
+ g4f.api.run_api(debug=True)
stubs.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, Field
4
+ from typing import Union, Optional
5
+ try:
6
+ from typing import Annotated
7
+ except ImportError:
8
+ class Annotated:
9
+ pass
10
+ from g4f.typing import Messages
11
+
12
+ class ChatCompletionsConfig(BaseModel):
13
+ messages: Messages = Field(examples=[[{"role": "system", "content": ""}, {"role": "user", "content": ""}]])
14
+ model: str = Field(default="")
15
+ provider: Optional[str] = None
16
+ stream: bool = False
17
+ image: Optional[str] = None
18
+ image_name: Optional[str] = None
19
+ images: Optional[list[tuple[str, str]]] = None
20
+ temperature: Optional[float] = None
21
+ max_tokens: Optional[int] = None
22
+ stop: Union[list[str], str, None] = None
23
+ api_key: Optional[str] = None
24
+ web_search: Optional[bool] = None
25
+ proxy: Optional[str] = None
26
+ conversation_id: Optional[str] = None
27
+ conversation: Optional[dict] = None
28
+ history_disabled: Optional[bool] = None
29
+ auto_continue: Optional[bool] = None
30
+ timeout: Optional[int] = None
31
+ tool_calls: list = Field(default=[], examples=[[
32
+ {
33
+ "function": {
34
+ "arguments": {"query":"search query", "max_results":5, "max_words": 2500, "backend": "api", "add_text": True, "timeout": 5},
35
+ "name": "search_tool"
36
+ },
37
+ "type": "function"
38
+ }
39
+ ]])
40
+ tools: list = None
41
+
42
+ class ImageGenerationConfig(BaseModel):
43
+ prompt: str
44
+ model: Optional[str] = None
45
+ provider: Optional[str] = None
46
+ response_format: Optional[str] = None
47
+ api_key: Optional[str] = None
48
+ proxy: Optional[str] = None
49
+
50
+ class ProviderResponseModel(BaseModel):
51
+ id: str
52
+ object: str = "provider"
53
+ created: int
54
+ url: Optional[str]
55
+ label: Optional[str]
56
+
57
+ class ProviderResponseDetailModel(ProviderResponseModel):
58
+ models: list[str]
59
+ image_models: list[str]
60
+ vision_models: list[str]
61
+ params: list[str]
62
+
63
+ class ModelResponseModel(BaseModel):
64
+ id: str
65
+ object: str = "model"
66
+ created: int
67
+ owned_by: Optional[str]
68
+
69
+ class ErrorResponseModel(BaseModel):
70
+ error: ErrorResponseMessageModel
71
+ model: Optional[str] = None
72
+ provider: Optional[str] = None
73
+
74
+ class ErrorResponseMessageModel(BaseModel):
75
+ message: str
76
+
77
+ class FileResponseModel(BaseModel):
78
+ filename: str