backend.py 7.2 KB

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