backend.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import logging
  2. import json
  3. from flask import request, Flask
  4. from typing import Generator
  5. from g4f import version, models
  6. from g4f import get_last_provider, ChatCompletion
  7. from g4f.image import is_allowed_extension, to_image
  8. from g4f.errors import VersionNotFoundError
  9. from g4f.Provider import __providers__
  10. from g4f.Provider.bing.create_images import patch_provider
  11. from .internet import get_search_message
  12. class Backend_Api:
  13. """
  14. Handles various endpoints in a Flask application for backend operations.
  15. This class provides methods to interact with models, providers, and to handle
  16. various functionalities like conversations, error handling, and version management.
  17. Attributes:
  18. app (Flask): A Flask application instance.
  19. routes (dict): A dictionary mapping API endpoints to their respective handlers.
  20. """
  21. def __init__(self, app: Flask) -> None:
  22. """
  23. Initialize the backend API with the given Flask application.
  24. Args:
  25. app (Flask): Flask application instance to attach routes to.
  26. """
  27. self.app: Flask = app
  28. self.routes = {
  29. '/backend-api/v2/models': {
  30. 'function': self.get_models,
  31. 'methods': ['GET']
  32. },
  33. '/backend-api/v2/providers': {
  34. 'function': self.get_providers,
  35. 'methods': ['GET']
  36. },
  37. '/backend-api/v2/version': {
  38. 'function': self.get_version,
  39. 'methods': ['GET']
  40. },
  41. '/backend-api/v2/conversation': {
  42. 'function': self.handle_conversation,
  43. 'methods': ['POST']
  44. },
  45. '/backend-api/v2/gen.set.summarize:title': {
  46. 'function': self.generate_title,
  47. 'methods': ['POST']
  48. },
  49. '/backend-api/v2/error': {
  50. 'function': self.handle_error,
  51. 'methods': ['POST']
  52. }
  53. }
  54. def handle_error(self):
  55. """
  56. Initialize the backend API with the given Flask application.
  57. Args:
  58. app (Flask): Flask application instance to attach routes to.
  59. """
  60. print(request.json)
  61. return 'ok', 200
  62. def get_models(self):
  63. """
  64. Return a list of all models.
  65. Fetches and returns a list of all available models in the system.
  66. Returns:
  67. List[str]: A list of model names.
  68. """
  69. return models._all_models
  70. def get_providers(self):
  71. """
  72. Return a list of all working providers.
  73. """
  74. return [provider.__name__ for provider in __providers__ if provider.working]
  75. def get_version(self):
  76. """
  77. Returns the current and latest version of the application.
  78. Returns:
  79. dict: A dictionary containing the current and latest version.
  80. """
  81. try:
  82. current_version = version.utils.current_version
  83. except VersionNotFoundError:
  84. current_version = None
  85. return {
  86. "version": current_version,
  87. "latest_version": version.get_latest_version(),
  88. }
  89. def generate_title(self):
  90. """
  91. Generates and returns a title based on the request data.
  92. Returns:
  93. dict: A dictionary with the generated title.
  94. """
  95. return {'title': ''}
  96. def handle_conversation(self):
  97. """
  98. Handles conversation requests and streams responses back.
  99. Returns:
  100. Response: A Flask response object for streaming.
  101. """
  102. kwargs = self._prepare_conversation_kwargs()
  103. return self.app.response_class(
  104. self._create_response_stream(kwargs),
  105. mimetype='text/event-stream'
  106. )
  107. def _prepare_conversation_kwargs(self):
  108. """
  109. Prepares arguments for chat completion based on the request data.
  110. Reads the request and prepares the necessary arguments for handling
  111. a chat completion request.
  112. Returns:
  113. dict: Arguments prepared for chat completion.
  114. """
  115. kwargs = {}
  116. if "image" in request.files:
  117. file = request.files['image']
  118. if file.filename != '' and is_allowed_extension(file.filename):
  119. kwargs['image'] = to_image(file.stream, file.filename.endswith('.svg'))
  120. kwargs['image_name'] = file.filename
  121. if "json" in request.form:
  122. json_data = json.loads(request.form['json'])
  123. else:
  124. json_data = request.json
  125. provider = json_data.get('provider', '').replace('g4f.Provider.', '')
  126. provider = provider if provider and provider != "Auto" else None
  127. if "image" in kwargs and not provider:
  128. provider = "Bing"
  129. if provider == 'OpenaiChat':
  130. kwargs['auto_continue'] = True
  131. messages = json_data['messages']
  132. if json_data.get('web_search'):
  133. if provider == "Bing":
  134. kwargs['web_search'] = True
  135. else:
  136. messages[-1]["content"] = get_search_message(messages[-1]["content"])
  137. model = json_data.get('model')
  138. model = model if model else models.default
  139. patch = patch_provider if json_data.get('patch_provider') else None
  140. return {
  141. "model": model,
  142. "provider": provider,
  143. "messages": messages,
  144. "stream": True,
  145. "ignore_stream": True,
  146. "patch_provider": patch,
  147. **kwargs
  148. }
  149. def _create_response_stream(self, kwargs) -> Generator[str, None, None]:
  150. """
  151. Creates and returns a streaming response for the conversation.
  152. Args:
  153. kwargs (dict): Arguments for creating the chat completion.
  154. Yields:
  155. str: JSON formatted response chunks for the stream.
  156. Raises:
  157. Exception: If an error occurs during the streaming process.
  158. """
  159. try:
  160. first = True
  161. for chunk in ChatCompletion.create(**kwargs):
  162. if first:
  163. first = False
  164. yield self._format_json('provider', get_last_provider(True))
  165. if isinstance(chunk, Exception):
  166. logging.exception(chunk)
  167. yield self._format_json('message', get_error_message(chunk))
  168. else:
  169. yield self._format_json('content', str(chunk))
  170. except Exception as e:
  171. logging.exception(e)
  172. yield self._format_json('error', get_error_message(e))
  173. def _format_json(self, response_type: str, content) -> str:
  174. """
  175. Formats and returns a JSON response.
  176. Args:
  177. response_type (str): The type of the response.
  178. content: The content to be included in the response.
  179. Returns:
  180. str: A JSON formatted string.
  181. """
  182. return json.dumps({
  183. 'type': response_type,
  184. response_type: content
  185. }) + "\n"
  186. def get_error_message(exception: Exception) -> str:
  187. """
  188. Generates a formatted error message from an exception.
  189. Args:
  190. exception (Exception): The exception to format.
  191. Returns:
  192. str: A formatted error message string.
  193. """
  194. return f"{get_last_provider().__name__}: {type(exception).__name__}: {exception}"