__init__.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. from __future__ import annotations
  2. import logging
  3. import json
  4. import uvicorn
  5. import secrets
  6. from fastapi import FastAPI, Response, Request
  7. from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
  8. from fastapi.exceptions import RequestValidationError
  9. from fastapi.security import APIKeyHeader
  10. from starlette.exceptions import HTTPException
  11. from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
  12. from fastapi.encoders import jsonable_encoder
  13. from pydantic import BaseModel
  14. from typing import Union, Optional
  15. import g4f
  16. import g4f.debug
  17. from g4f.client import AsyncClient
  18. from g4f.typing import Messages
  19. from g4f.cookies import read_cookie_files
  20. def create_app():
  21. app = FastAPI()
  22. api = Api(app)
  23. api.register_routes()
  24. api.register_authorization()
  25. api.register_validation_exception_handler()
  26. if not AppConfig.ignore_cookie_files:
  27. read_cookie_files()
  28. return app
  29. def create_app_debug():
  30. g4f.debug.logging = True
  31. return create_app()
  32. class ChatCompletionsForm(BaseModel):
  33. messages: Messages
  34. model: str
  35. provider: Optional[str] = None
  36. stream: bool = False
  37. temperature: Optional[float] = None
  38. max_tokens: Optional[int] = None
  39. stop: Union[list[str], str, None] = None
  40. api_key: Optional[str] = None
  41. web_search: Optional[bool] = None
  42. proxy: Optional[str] = None
  43. class ImagesGenerateForm(BaseModel):
  44. model: Optional[str] = None
  45. provider: Optional[str] = None
  46. prompt: str
  47. response_format: Optional[str] = None
  48. api_key: Optional[str] = None
  49. proxy: Optional[str] = None
  50. class AppConfig():
  51. ignored_providers: Optional[list[str]] = None
  52. g4f_api_key: Optional[str] = None
  53. ignore_cookie_files: bool = False
  54. defaults: dict = {}
  55. @classmethod
  56. def set_config(cls, **data):
  57. for key, value in data.items():
  58. setattr(cls, key, value)
  59. class Api:
  60. def __init__(self, app: FastAPI) -> None:
  61. self.app = app
  62. self.client = AsyncClient()
  63. self.get_g4f_api_key = APIKeyHeader(name="g4f-api-key")
  64. def register_authorization(self):
  65. @self.app.middleware("http")
  66. async def authorization(request: Request, call_next):
  67. if AppConfig.g4f_api_key and request.url.path in ["/v1/chat/completions", "/v1/completions"]:
  68. try:
  69. user_g4f_api_key = await self.get_g4f_api_key(request)
  70. except HTTPException as e:
  71. if e.status_code == 403:
  72. return JSONResponse(
  73. status_code=HTTP_401_UNAUTHORIZED,
  74. content=jsonable_encoder({"detail": "G4F API key required"}),
  75. )
  76. if not secrets.compare_digest(AppConfig.g4f_api_key, user_g4f_api_key):
  77. return JSONResponse(
  78. status_code=HTTP_403_FORBIDDEN,
  79. content=jsonable_encoder({"detail": "Invalid G4F API key"}),
  80. )
  81. return await call_next(request)
  82. def register_validation_exception_handler(self):
  83. @self.app.exception_handler(RequestValidationError)
  84. async def validation_exception_handler(request: Request, exc: RequestValidationError):
  85. details = exc.errors()
  86. modified_details = [{
  87. "loc": error["loc"],
  88. "message": error["msg"],
  89. "type": error["type"],
  90. } for error in details]
  91. return JSONResponse(
  92. status_code=HTTP_422_UNPROCESSABLE_ENTITY,
  93. content=jsonable_encoder({"detail": modified_details}),
  94. )
  95. def register_routes(self):
  96. @self.app.get("/")
  97. async def read_root():
  98. return RedirectResponse("/v1", 302)
  99. @self.app.get("/v1")
  100. async def read_root_v1():
  101. return HTMLResponse('g4f API: Go to '
  102. '<a href="/v1/chat/completions">chat/completions</a> '
  103. 'or <a href="/v1/models">models</a>.')
  104. @self.app.get("/v1/models")
  105. async def models():
  106. model_list = {
  107. model: g4f.models.ModelUtils.convert[model]
  108. for model in g4f.Model.__all__()
  109. }
  110. model_list = [{
  111. 'id': model_id,
  112. 'object': 'model',
  113. 'created': 0,
  114. 'owned_by': model.base_provider
  115. } for model_id, model in model_list.items()]
  116. return JSONResponse({
  117. "object": "list",
  118. "data": model_list,
  119. })
  120. @self.app.get("/v1/models/{model_name}")
  121. async def model_info(model_name: str):
  122. try:
  123. model_info = g4f.models.ModelUtils.convert[model_name]
  124. return JSONResponse({
  125. 'id': model_name,
  126. 'object': 'model',
  127. 'created': 0,
  128. 'owned_by': model_info.base_provider
  129. })
  130. except:
  131. return JSONResponse({"error": "The model does not exist."})
  132. @self.app.post("/v1/chat/completions")
  133. async def chat_completions(config: ChatCompletionsForm, request: Request = None, provider: str = None):
  134. try:
  135. config.provider = provider if config.provider is None else config.provider
  136. if config.api_key is None and request is not None:
  137. auth_header = request.headers.get("Authorization")
  138. if auth_header is not None:
  139. auth_header = auth_header.split(None, 1)[-1]
  140. if auth_header and auth_header != "Bearer":
  141. config.api_key = auth_header
  142. response = self.client.chat.completions.create(
  143. **{
  144. **AppConfig.defaults,
  145. **config.dict(exclude_none=True),
  146. },
  147. ignored=AppConfig.ignored_providers
  148. )
  149. if not config.stream:
  150. return JSONResponse((await response).to_json())
  151. async def streaming():
  152. try:
  153. async for chunk in response:
  154. yield f"data: {json.dumps(chunk.to_json())}\n\n"
  155. except GeneratorExit:
  156. pass
  157. except Exception as e:
  158. logging.exception(e)
  159. yield f'data: {format_exception(e, config)}\n\n'
  160. yield "data: [DONE]\n\n"
  161. return StreamingResponse(streaming(), media_type="text/event-stream")
  162. except Exception as e:
  163. logging.exception(e)
  164. return Response(content=format_exception(e, config), status_code=500, media_type="application/json")
  165. @self.app.post("/v1/completions")
  166. async def completions():
  167. return Response(content=json.dumps({'info': 'Not working yet.'}, indent=4), media_type="application/json")
  168. @self.app.post("/v1/images/generations")
  169. async def images_generate(config: ImagesGenerateForm, request: Request = None, provider: str = None):
  170. try:
  171. config.provider = provider if config.provider is None else config.provider
  172. if config.api_key is None and request is not None:
  173. auth_header = request.headers.get("Authorization")
  174. if auth_header is not None:
  175. auth_header = auth_header.split(None, 1)[-1]
  176. if auth_header and auth_header != "Bearer":
  177. config.api_key = auth_header
  178. response = self.client.images.generate(
  179. **config.dict(exclude_none=True),
  180. )
  181. return JSONResponse((await response).to_json())
  182. except Exception as e:
  183. logging.exception(e)
  184. return Response(content=format_exception(e, config), status_code=500, media_type="application/json")
  185. def format_exception(e: Exception, config: ChatCompletionsForm) -> str:
  186. last_provider = g4f.get_last_provider(True)
  187. return json.dumps({
  188. "error": {"message": f"{e.__class__.__name__}: {e}"},
  189. "model": last_provider.get("model") if last_provider else config.model,
  190. "provider": last_provider.get("name") if last_provider else config.provider
  191. })
  192. def run_api(
  193. host: str = '0.0.0.0',
  194. port: int = 1337,
  195. bind: str = None,
  196. debug: bool = False,
  197. workers: int = None,
  198. use_colors: bool = None
  199. ) -> None:
  200. print(f'Starting server... [g4f v-{g4f.version.utils.current_version}]' + (" (debug)" if debug else ""))
  201. if use_colors is None:
  202. use_colors = debug
  203. if bind is not None:
  204. host, port = bind.split(":")
  205. uvicorn.run(
  206. f"g4f.api:create_app{'_debug' if debug else ''}",
  207. host=host, port=int(port),
  208. workers=workers,
  209. use_colors=use_colors,
  210. factory=True,
  211. reload=debug
  212. )