mirror of
https://github.com/xtekky/gpt4free.git
synced 2025-10-07 17:21:16 +08:00
Arm2 (#2414)
* Fix arm v7 build / improve api * Update stubs.py * Fix unit tests
This commit is contained in:
@@ -47,7 +47,7 @@ RUN python -m pip install --upgrade pip \
|
|||||||
--global-option=build_ext \
|
--global-option=build_ext \
|
||||||
--global-option=-j8 \
|
--global-option=-j8 \
|
||||||
pydantic==${PYDANTIC_VERSION} \
|
pydantic==${PYDANTIC_VERSION} \
|
||||||
&& cat requirements.txt | xargs -n 1 pip install --no-cache-dir \
|
&& cat requirements-slim.txt | xargs -n 1 pip install --no-cache-dir || true \
|
||||||
# Remove build packages
|
# Remove build packages
|
||||||
&& pip uninstall --yes \
|
&& pip uninstall --yes \
|
||||||
Cython \
|
Cython \
|
||||||
|
@@ -46,7 +46,6 @@ class PollinationsAI(OpenaiAPI):
|
|||||||
seed: str = None,
|
seed: str = None,
|
||||||
**kwargs
|
**kwargs
|
||||||
) -> AsyncResult:
|
) -> AsyncResult:
|
||||||
if model:
|
|
||||||
model = cls.get_model(model)
|
model = cls.get_model(model)
|
||||||
if model in cls.image_models:
|
if model in cls.image_models:
|
||||||
if prompt is None:
|
if prompt is None:
|
||||||
|
@@ -313,6 +313,7 @@ class Conversation(BaseConversation):
|
|||||||
self.conversation_id = conversation_id
|
self.conversation_id = conversation_id
|
||||||
self.response_id = response_id
|
self.response_id = response_id
|
||||||
self.choice_id = choice_id
|
self.choice_id = choice_id
|
||||||
|
|
||||||
async def iter_filter_base64(response_iter: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
|
async def iter_filter_base64(response_iter: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
|
||||||
search_for = b'[["wrb.fr","XqA3Ic","[\\"'
|
search_for = b'[["wrb.fr","XqA3Ic","[\\"'
|
||||||
end_with = b'\\'
|
end_with = b'\\'
|
||||||
|
@@ -8,21 +8,29 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
from fastapi import FastAPI, Response, Request, UploadFile
|
from fastapi import FastAPI, Response, Request, UploadFile, Depends
|
||||||
|
from fastapi.middleware.wsgi import WSGIMiddleware
|
||||||
from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
|
from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
|
||||||
from fastapi.exceptions import RequestValidationError
|
from fastapi.exceptions import RequestValidationError
|
||||||
from fastapi.security import APIKeyHeader
|
from fastapi.security import APIKeyHeader
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
|
from starlette.status import (
|
||||||
|
HTTP_200_OK,
|
||||||
|
HTTP_422_UNPROCESSABLE_ENTITY,
|
||||||
|
HTTP_404_NOT_FOUND,
|
||||||
|
HTTP_401_UNAUTHORIZED,
|
||||||
|
HTTP_403_FORBIDDEN
|
||||||
|
)
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from starlette.responses import FileResponse
|
from starlette.responses import FileResponse
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field
|
||||||
from typing import Union, Optional, List
|
from typing import Union, Optional, List, Annotated
|
||||||
|
|
||||||
import g4f
|
import g4f
|
||||||
import g4f.debug
|
import g4f.debug
|
||||||
from g4f.client import AsyncClient, ChatCompletion, convert_to_provider
|
from g4f.client import AsyncClient, ChatCompletion, ImagesResponse, convert_to_provider
|
||||||
from g4f.providers.response import BaseConversation
|
from g4f.providers.response import BaseConversation
|
||||||
from g4f.client.helper import filter_none
|
from g4f.client.helper import filter_none
|
||||||
from g4f.image import is_accepted_format, images_dir
|
from g4f.image import is_accepted_format, images_dir
|
||||||
@@ -30,6 +38,7 @@ from g4f.typing import Messages
|
|||||||
from g4f.errors import ProviderNotFoundError
|
from g4f.errors import ProviderNotFoundError
|
||||||
from g4f.cookies import read_cookie_files, get_cookies_dir
|
from g4f.cookies import read_cookie_files, get_cookies_dir
|
||||||
from g4f.Provider import ProviderType, ProviderUtils, __providers__
|
from g4f.Provider import ProviderType, ProviderUtils, __providers__
|
||||||
|
from g4f.gui import get_gui_app
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -50,6 +59,10 @@ def create_app(g4f_api_key: str = None):
|
|||||||
api.register_authorization()
|
api.register_authorization()
|
||||||
api.register_validation_exception_handler()
|
api.register_validation_exception_handler()
|
||||||
|
|
||||||
|
if AppConfig.gui:
|
||||||
|
gui_app = WSGIMiddleware(get_gui_app())
|
||||||
|
app.mount("/", gui_app)
|
||||||
|
|
||||||
# Read cookie files if not ignored
|
# Read cookie files if not ignored
|
||||||
if not AppConfig.ignore_cookie_files:
|
if not AppConfig.ignore_cookie_files:
|
||||||
read_cookie_files()
|
read_cookie_files()
|
||||||
@@ -61,17 +74,17 @@ def create_app_debug(g4f_api_key: str = None):
|
|||||||
return create_app(g4f_api_key)
|
return create_app(g4f_api_key)
|
||||||
|
|
||||||
class ChatCompletionsConfig(BaseModel):
|
class ChatCompletionsConfig(BaseModel):
|
||||||
messages: Messages
|
messages: Messages = Field(examples=[[{"role": "system", "content": ""}, {"role": "user", "content": ""}]])
|
||||||
model: str
|
model: str = Field(default="")
|
||||||
provider: Optional[str] = None
|
provider: Optional[str] = Field(examples=[None])
|
||||||
stream: bool = False
|
stream: bool = False
|
||||||
temperature: Optional[float] = None
|
temperature: Optional[float] = Field(examples=[None])
|
||||||
max_tokens: Optional[int] = None
|
max_tokens: Optional[int] = Field(examples=[None])
|
||||||
stop: Union[list[str], str, None] = None
|
stop: Union[list[str], str, None] = Field(examples=[None])
|
||||||
api_key: Optional[str] = None
|
api_key: Optional[str] = Field(examples=[None])
|
||||||
web_search: Optional[bool] = None
|
web_search: Optional[bool] = Field(examples=[None])
|
||||||
proxy: Optional[str] = None
|
proxy: Optional[str] = Field(examples=[None])
|
||||||
conversation_id: str = None
|
conversation_id: Optional[str] = Field(examples=[None])
|
||||||
|
|
||||||
class ImageGenerationConfig(BaseModel):
|
class ImageGenerationConfig(BaseModel):
|
||||||
prompt: str
|
prompt: str
|
||||||
@@ -101,6 +114,9 @@ class ModelResponseModel(BaseModel):
|
|||||||
created: int
|
created: int
|
||||||
owned_by: Optional[str]
|
owned_by: Optional[str]
|
||||||
|
|
||||||
|
class ErrorResponseModel(BaseModel):
|
||||||
|
error: str
|
||||||
|
|
||||||
class AppConfig:
|
class AppConfig:
|
||||||
ignored_providers: Optional[list[str]] = None
|
ignored_providers: Optional[list[str]] = None
|
||||||
g4f_api_key: Optional[str] = None
|
g4f_api_key: Optional[str] = None
|
||||||
@@ -109,6 +125,7 @@ class AppConfig:
|
|||||||
provider: str = None
|
provider: str = None
|
||||||
image_provider: str = None
|
image_provider: str = None
|
||||||
proxy: str = None
|
proxy: str = None
|
||||||
|
gui: bool = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_config(cls, **data):
|
def set_config(cls, **data):
|
||||||
@@ -129,6 +146,8 @@ class Api:
|
|||||||
self.get_g4f_api_key = APIKeyHeader(name="g4f-api-key")
|
self.get_g4f_api_key = APIKeyHeader(name="g4f-api-key")
|
||||||
self.conversations: dict[str, dict[str, BaseConversation]] = {}
|
self.conversations: dict[str, dict[str, BaseConversation]] = {}
|
||||||
|
|
||||||
|
security = HTTPBearer(auto_error=False)
|
||||||
|
|
||||||
def register_authorization(self):
|
def register_authorization(self):
|
||||||
@self.app.middleware("http")
|
@self.app.middleware("http")
|
||||||
async def authorization(request: Request, call_next):
|
async def authorization(request: Request, call_next):
|
||||||
@@ -192,7 +211,7 @@ class Api:
|
|||||||
} for model_id, model in model_list.items()]
|
} for model_id, model in model_list.items()]
|
||||||
|
|
||||||
@self.app.get("/v1/models/{model_name}")
|
@self.app.get("/v1/models/{model_name}")
|
||||||
async def model_info(model_name: str):
|
async def model_info(model_name: str) -> ModelResponseModel:
|
||||||
if model_name in g4f.models.ModelUtils.convert:
|
if model_name in g4f.models.ModelUtils.convert:
|
||||||
model_info = g4f.models.ModelUtils.convert[model_name]
|
model_info = g4f.models.ModelUtils.convert[model_name]
|
||||||
return JSONResponse({
|
return JSONResponse({
|
||||||
@@ -201,20 +220,20 @@ class Api:
|
|||||||
'created': 0,
|
'created': 0,
|
||||||
'owned_by': model_info.base_provider
|
'owned_by': model_info.base_provider
|
||||||
})
|
})
|
||||||
return JSONResponse({"error": "The model does not exist."}, 404)
|
return JSONResponse({"error": "The model does not exist."}, HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
@self.app.post("/v1/chat/completions")
|
@self.app.post("/v1/chat/completions", response_model=ChatCompletion)
|
||||||
async def chat_completions(config: ChatCompletionsConfig, request: Request = None, provider: str = None):
|
async def chat_completions(
|
||||||
|
config: ChatCompletionsConfig,
|
||||||
|
credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None,
|
||||||
|
provider: str = None
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
config.provider = provider if config.provider is None else config.provider
|
config.provider = provider if config.provider is None else config.provider
|
||||||
if config.provider is None:
|
if config.provider is None:
|
||||||
config.provider = AppConfig.provider
|
config.provider = AppConfig.provider
|
||||||
if config.api_key is None and request is not None:
|
if credentials is not None:
|
||||||
auth_header = request.headers.get("Authorization")
|
config.api_key = credentials.credentials
|
||||||
if auth_header is not None:
|
|
||||||
api_key = auth_header.split(None, 1)[-1]
|
|
||||||
if api_key and api_key != "Bearer":
|
|
||||||
config.api_key = api_key
|
|
||||||
|
|
||||||
conversation = return_conversation = None
|
conversation = return_conversation = None
|
||||||
if config.conversation_id is not None and config.provider is not None:
|
if config.conversation_id is not None and config.provider is not None:
|
||||||
@@ -242,8 +261,7 @@ class Api:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not config.stream:
|
if not config.stream:
|
||||||
response: ChatCompletion = await response
|
return await response
|
||||||
return JSONResponse(response.to_json())
|
|
||||||
|
|
||||||
async def streaming():
|
async def streaming():
|
||||||
try:
|
try:
|
||||||
@@ -254,7 +272,7 @@ class Api:
|
|||||||
self.conversations[config.conversation_id] = {}
|
self.conversations[config.conversation_id] = {}
|
||||||
self.conversations[config.conversation_id][config.provider] = chunk
|
self.conversations[config.conversation_id][config.provider] = chunk
|
||||||
else:
|
else:
|
||||||
yield f"data: {json.dumps(chunk.to_json())}\n\n"
|
yield f"data: {chunk.json()}\n\n"
|
||||||
except GeneratorExit:
|
except GeneratorExit:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -268,15 +286,15 @@ class Api:
|
|||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
return Response(content=format_exception(e, config), status_code=500, media_type="application/json")
|
return Response(content=format_exception(e, config), status_code=500, media_type="application/json")
|
||||||
|
|
||||||
@self.app.post("/v1/images/generate")
|
@self.app.post("/v1/images/generate", response_model=ImagesResponse)
|
||||||
@self.app.post("/v1/images/generations")
|
@self.app.post("/v1/images/generations", response_model=ImagesResponse)
|
||||||
async def generate_image(config: ImageGenerationConfig, request: Request):
|
async def generate_image(
|
||||||
if config.api_key is None:
|
request: Request,
|
||||||
auth_header = request.headers.get("Authorization")
|
config: ImageGenerationConfig,
|
||||||
if auth_header is not None:
|
credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None
|
||||||
api_key = auth_header.split(None, 1)[-1]
|
):
|
||||||
if api_key and api_key != "Bearer":
|
if credentials is not None:
|
||||||
config.api_key = api_key
|
config.api_key = credentials.credentials
|
||||||
try:
|
try:
|
||||||
response = await self.client.images.generate(
|
response = await self.client.images.generate(
|
||||||
prompt=config.prompt,
|
prompt=config.prompt,
|
||||||
@@ -291,7 +309,7 @@ class Api:
|
|||||||
for image in response.data:
|
for image in response.data:
|
||||||
if hasattr(image, "url") and image.url.startswith("/"):
|
if hasattr(image, "url") and image.url.startswith("/"):
|
||||||
image.url = f"{request.base_url}{image.url.lstrip('/')}"
|
image.url = f"{request.base_url}{image.url.lstrip('/')}"
|
||||||
return JSONResponse(response.to_json())
|
return response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
return Response(content=format_exception(e, config, True), status_code=500, media_type="application/json")
|
return Response(content=format_exception(e, config, True), status_code=500, media_type="application/json")
|
||||||
@@ -342,22 +360,29 @@ class Api:
|
|||||||
file.file.close()
|
file.file.close()
|
||||||
return response_data
|
return response_data
|
||||||
|
|
||||||
@self.app.get("/v1/synthesize/{provider}")
|
@self.app.get("/v1/synthesize/{provider}", responses={
|
||||||
|
HTTP_200_OK: {"content": {"audio/*": {}}},
|
||||||
|
HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
|
||||||
|
HTTP_422_UNPROCESSABLE_ENTITY: {"model": ErrorResponseModel},
|
||||||
|
})
|
||||||
async def synthesize(request: Request, provider: str):
|
async def synthesize(request: Request, provider: str):
|
||||||
try:
|
try:
|
||||||
provider_handler = convert_to_provider(provider)
|
provider_handler = convert_to_provider(provider)
|
||||||
except ProviderNotFoundError:
|
except ProviderNotFoundError:
|
||||||
return Response("Provider not found", 404)
|
return JSONResponse({"error": "Provider not found"}, HTTP_404_NOT_FOUND)
|
||||||
if not hasattr(provider_handler, "synthesize"):
|
if not hasattr(provider_handler, "synthesize"):
|
||||||
return Response("Provider doesn't support synthesize", 500)
|
return JSONResponse({"error": "Provider doesn't support synthesize"}, HTTP_404_NOT_FOUND)
|
||||||
if len(request.query_params) == 0:
|
if len(request.query_params) == 0:
|
||||||
return Response("Missing query params", 500)
|
return JSONResponse({"error": "Missing query params"}, HTTP_422_UNPROCESSABLE_ENTITY)
|
||||||
response_data = provider_handler.synthesize({**request.query_params})
|
response_data = provider_handler.synthesize({**request.query_params})
|
||||||
content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
|
content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
|
||||||
return StreamingResponse(response_data, media_type=content_type)
|
return StreamingResponse(response_data, media_type=content_type)
|
||||||
|
|
||||||
@self.app.get("/images/{filename}")
|
@self.app.get("/images/{filename}", response_class=FileResponse, responses={
|
||||||
async def get_image(filename) -> FileResponse:
|
HTTP_200_OK: {"content": {"image/*": {}}},
|
||||||
|
HTTP_404_NOT_FOUND: {}
|
||||||
|
})
|
||||||
|
async def get_image(filename):
|
||||||
target = os.path.join(images_dir, filename)
|
target = os.path.join(images_dir, filename)
|
||||||
|
|
||||||
if not os.path.isfile(target):
|
if not os.path.isfile(target):
|
||||||
|
@@ -12,6 +12,7 @@ def main():
|
|||||||
api_parser = subparsers.add_parser("api")
|
api_parser = subparsers.add_parser("api")
|
||||||
api_parser.add_argument("--bind", default="0.0.0.0:1337", help="The bind string.")
|
api_parser.add_argument("--bind", default="0.0.0.0:1337", help="The bind string.")
|
||||||
api_parser.add_argument("--debug", action="store_true", help="Enable verbose logging.")
|
api_parser.add_argument("--debug", action="store_true", help="Enable verbose logging.")
|
||||||
|
api_parser.add_argument("--gui", "-g", default=False, action="store_true", help="Add gui to the api.")
|
||||||
api_parser.add_argument("--model", default=None, help="Default model for chat completion. (incompatible with --reload and --workers)")
|
api_parser.add_argument("--model", default=None, help="Default model for chat completion. (incompatible with --reload and --workers)")
|
||||||
api_parser.add_argument("--provider", choices=[provider.__name__ for provider in Provider.__providers__ if provider.working],
|
api_parser.add_argument("--provider", choices=[provider.__name__ for provider in Provider.__providers__ if provider.working],
|
||||||
default=None, help="Default provider for chat completion. (incompatible with --reload and --workers)")
|
default=None, help="Default provider for chat completion. (incompatible with --reload and --workers)")
|
||||||
@@ -48,7 +49,8 @@ def run_api_args(args):
|
|||||||
provider=args.provider,
|
provider=args.provider,
|
||||||
image_provider=args.image_provider,
|
image_provider=args.image_provider,
|
||||||
proxy=args.proxy,
|
proxy=args.proxy,
|
||||||
model=args.model
|
model=args.model,
|
||||||
|
gui=args.gui,
|
||||||
)
|
)
|
||||||
g4f.cookies.browsers = [g4f.cookies[browser] for browser in args.cookie_browsers]
|
g4f.cookies.browsers = [g4f.cookies[browser] for browser in args.cookie_browsers]
|
||||||
run_api(
|
run_api(
|
||||||
|
@@ -73,7 +73,7 @@ def iter_response(
|
|||||||
finish_reason = "stop"
|
finish_reason = "stop"
|
||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
yield ChatCompletionChunk(chunk, None, completion_id, int(time.time()))
|
yield ChatCompletionChunk.model_construct(chunk, None, completion_id, int(time.time()))
|
||||||
|
|
||||||
if finish_reason is not None:
|
if finish_reason is not None:
|
||||||
break
|
break
|
||||||
@@ -83,12 +83,12 @@ def iter_response(
|
|||||||
finish_reason = "stop" if finish_reason is None else finish_reason
|
finish_reason = "stop" if finish_reason is None else finish_reason
|
||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
yield ChatCompletionChunk(None, finish_reason, completion_id, int(time.time()))
|
yield ChatCompletionChunk.model_construct(None, finish_reason, completion_id, int(time.time()))
|
||||||
else:
|
else:
|
||||||
if response_format is not None and "type" in response_format:
|
if response_format is not None and "type" in response_format:
|
||||||
if response_format["type"] == "json_object":
|
if response_format["type"] == "json_object":
|
||||||
content = filter_json(content)
|
content = filter_json(content)
|
||||||
yield ChatCompletion(content, finish_reason, completion_id, int(time.time()))
|
yield ChatCompletion.model_construct(content, finish_reason, completion_id, int(time.time()))
|
||||||
|
|
||||||
# Synchronous iter_append_model_and_provider function
|
# Synchronous iter_append_model_and_provider function
|
||||||
def iter_append_model_and_provider(response: ChatCompletionResponseType) -> ChatCompletionResponseType:
|
def iter_append_model_and_provider(response: ChatCompletionResponseType) -> ChatCompletionResponseType:
|
||||||
@@ -137,7 +137,7 @@ async def async_iter_response(
|
|||||||
finish_reason = "stop"
|
finish_reason = "stop"
|
||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
yield ChatCompletionChunk(chunk, None, completion_id, int(time.time()))
|
yield ChatCompletionChunk.model_construct(chunk, None, completion_id, int(time.time()))
|
||||||
|
|
||||||
if finish_reason is not None:
|
if finish_reason is not None:
|
||||||
break
|
break
|
||||||
@@ -145,12 +145,12 @@ async def async_iter_response(
|
|||||||
finish_reason = "stop" if finish_reason is None else finish_reason
|
finish_reason = "stop" if finish_reason is None else finish_reason
|
||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
yield ChatCompletionChunk(None, finish_reason, completion_id, int(time.time()))
|
yield ChatCompletionChunk.model_construct(None, finish_reason, completion_id, int(time.time()))
|
||||||
else:
|
else:
|
||||||
if response_format is not None and "type" in response_format:
|
if response_format is not None and "type" in response_format:
|
||||||
if response_format["type"] == "json_object":
|
if response_format["type"] == "json_object":
|
||||||
content = filter_json(content)
|
content = filter_json(content)
|
||||||
yield ChatCompletion(content, finish_reason, completion_id, int(time.time()))
|
yield ChatCompletion.model_construct(content, finish_reason, completion_id, int(time.time()))
|
||||||
finally:
|
finally:
|
||||||
if hasattr(response, 'aclose'):
|
if hasattr(response, 'aclose'):
|
||||||
await safe_aclose(response)
|
await safe_aclose(response)
|
||||||
@@ -394,13 +394,13 @@ class Images:
|
|||||||
if response_format == "b64_json":
|
if response_format == "b64_json":
|
||||||
with open(os.path.join(images_dir, os.path.basename(image_file)), "rb") as file:
|
with open(os.path.join(images_dir, os.path.basename(image_file)), "rb") as file:
|
||||||
image_data = base64.b64encode(file.read()).decode()
|
image_data = base64.b64encode(file.read()).decode()
|
||||||
return Image(url=image_file, b64_json=image_data, revised_prompt=response.alt)
|
return Image.model_construct(url=image_file, b64_json=image_data, revised_prompt=response.alt)
|
||||||
return Image(url=image_file, revised_prompt=response.alt)
|
return Image.model_construct(url=image_file, revised_prompt=response.alt)
|
||||||
images = await asyncio.gather(*[process_image_item(image) for image in images])
|
images = await asyncio.gather(*[process_image_item(image) for image in images])
|
||||||
else:
|
else:
|
||||||
images = [Image(url=image, revised_prompt=response.alt) for image in response.get_list()]
|
images = [Image.model_construct(url=image, revised_prompt=response.alt) for image in response.get_list()]
|
||||||
last_provider = get_last_provider(True)
|
last_provider = get_last_provider(True)
|
||||||
return ImagesResponse(
|
return ImagesResponse.model_construct(
|
||||||
images,
|
images,
|
||||||
model=last_provider.get("model") if model is None else model,
|
model=last_provider.get("model") if model is None else model,
|
||||||
provider=last_provider.get("name") if provider is None else provider
|
provider=last_provider.get("name") if provider is None else provider
|
||||||
|
@@ -1,130 +1,150 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Union
|
from typing import Optional, List, Dict
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
class Model():
|
from .helper import filter_none
|
||||||
...
|
|
||||||
|
|
||||||
class ChatCompletion(Model):
|
try:
|
||||||
def __init__(
|
from pydantic import BaseModel, Field
|
||||||
self,
|
except ImportError:
|
||||||
|
class BaseModel():
|
||||||
|
@classmethod
|
||||||
|
def model_construct(cls, **data):
|
||||||
|
new = cls()
|
||||||
|
for key, value in data.items():
|
||||||
|
setattr(new, key, value)
|
||||||
|
return new
|
||||||
|
class Field():
|
||||||
|
def __init__(self, **config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ChatCompletionChunk(BaseModel):
|
||||||
|
id: str
|
||||||
|
object: str
|
||||||
|
created: int
|
||||||
|
model: str
|
||||||
|
provider: Optional[str]
|
||||||
|
choices: List[ChatCompletionDeltaChoice]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def model_construct(
|
||||||
|
cls,
|
||||||
content: str,
|
content: str,
|
||||||
finish_reason: str,
|
finish_reason: str,
|
||||||
completion_id: str = None,
|
completion_id: str = None,
|
||||||
created: int = None
|
created: int = None
|
||||||
):
|
):
|
||||||
self.id: str = f"chatcmpl-{completion_id}" if completion_id else None
|
return super().model_construct(
|
||||||
self.object: str = "chat.completion"
|
id=f"chatcmpl-{completion_id}" if completion_id else None,
|
||||||
self.created: int = created
|
object="chat.completion.cunk",
|
||||||
self.model: str = None
|
created=created,
|
||||||
self.provider: str = None
|
model=None,
|
||||||
self.choices = [ChatCompletionChoice(ChatCompletionMessage(content), finish_reason)]
|
provider=None,
|
||||||
self.usage: dict[str, int] = {
|
choices=[ChatCompletionDeltaChoice.model_construct(
|
||||||
|
ChatCompletionDelta.model_construct(content),
|
||||||
|
finish_reason
|
||||||
|
)]
|
||||||
|
)
|
||||||
|
|
||||||
|
class ChatCompletionMessage(BaseModel):
|
||||||
|
role: str
|
||||||
|
content: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def model_construct(cls, content: str):
|
||||||
|
return super().model_construct(role="assistant", content=content)
|
||||||
|
|
||||||
|
class ChatCompletionChoice(BaseModel):
|
||||||
|
index: int
|
||||||
|
message: ChatCompletionMessage
|
||||||
|
finish_reason: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def model_construct(cls, message: ChatCompletionMessage, finish_reason: str):
|
||||||
|
return super().model_construct(index=0, message=message, finish_reason=finish_reason)
|
||||||
|
|
||||||
|
class ChatCompletion(BaseModel):
|
||||||
|
id: str
|
||||||
|
object: str
|
||||||
|
created: int
|
||||||
|
model: str
|
||||||
|
provider: Optional[str]
|
||||||
|
choices: List[ChatCompletionChoice]
|
||||||
|
usage: Dict[str, int] = Field(examples=[{
|
||||||
|
"prompt_tokens": 0, #prompt_tokens,
|
||||||
|
"completion_tokens": 0, #completion_tokens,
|
||||||
|
"total_tokens": 0, #prompt_tokens + completion_tokens,
|
||||||
|
}])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def model_construct(
|
||||||
|
cls,
|
||||||
|
content: str,
|
||||||
|
finish_reason: str,
|
||||||
|
completion_id: str = None,
|
||||||
|
created: int = None
|
||||||
|
):
|
||||||
|
return super().model_construct(
|
||||||
|
id=f"chatcmpl-{completion_id}" if completion_id else None,
|
||||||
|
object="chat.completion",
|
||||||
|
created=created,
|
||||||
|
model=None,
|
||||||
|
provider=None,
|
||||||
|
choices=[ChatCompletionChoice.model_construct(
|
||||||
|
ChatCompletionMessage.model_construct(content),
|
||||||
|
finish_reason
|
||||||
|
)],
|
||||||
|
usage={
|
||||||
"prompt_tokens": 0, #prompt_tokens,
|
"prompt_tokens": 0, #prompt_tokens,
|
||||||
"completion_tokens": 0, #completion_tokens,
|
"completion_tokens": 0, #completion_tokens,
|
||||||
"total_tokens": 0, #prompt_tokens + completion_tokens,
|
"total_tokens": 0, #prompt_tokens + completion_tokens,
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def to_json(self):
|
class ChatCompletionDelta(BaseModel):
|
||||||
return {
|
role: str
|
||||||
**self.__dict__,
|
content: str
|
||||||
"choices": [choice.to_json() for choice in self.choices]
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChatCompletionChunk(Model):
|
@classmethod
|
||||||
def __init__(
|
def model_construct(cls, content: Optional[str]):
|
||||||
self,
|
return super().model_construct(role="assistant", content=content)
|
||||||
content: str,
|
|
||||||
finish_reason: str,
|
|
||||||
completion_id: str = None,
|
|
||||||
created: int = None
|
|
||||||
):
|
|
||||||
self.id: str = f"chatcmpl-{completion_id}" if completion_id else None
|
|
||||||
self.object: str = "chat.completion.chunk"
|
|
||||||
self.created: int = created
|
|
||||||
self.model: str = None
|
|
||||||
self.provider: str = None
|
|
||||||
self.choices = [ChatCompletionDeltaChoice(ChatCompletionDelta(content), finish_reason)]
|
|
||||||
|
|
||||||
def to_json(self):
|
class ChatCompletionDeltaChoice(BaseModel):
|
||||||
return {
|
index: int
|
||||||
**self.__dict__,
|
delta: ChatCompletionDelta
|
||||||
"choices": [choice.to_json() for choice in self.choices]
|
finish_reason: Optional[str]
|
||||||
}
|
|
||||||
|
|
||||||
class ChatCompletionMessage(Model):
|
@classmethod
|
||||||
def __init__(self, content: Union[str, None]):
|
def model_construct(cls, delta: ChatCompletionDelta, finish_reason: Optional[str]):
|
||||||
self.role = "assistant"
|
return super().model_construct(index=0, delta=delta, finish_reason=finish_reason)
|
||||||
self.content = content
|
|
||||||
|
|
||||||
def to_json(self):
|
class Image(BaseModel):
|
||||||
return self.__dict__
|
url: Optional[str]
|
||||||
|
b64_json: Optional[str]
|
||||||
|
revised_prompt: Optional[str]
|
||||||
|
|
||||||
class ChatCompletionChoice(Model):
|
@classmethod
|
||||||
def __init__(self, message: ChatCompletionMessage, finish_reason: str):
|
def model_construct(cls, url: str = None, b64_json: str = None, revised_prompt: str = None):
|
||||||
self.index = 0
|
return super().model_construct(**filter_none(
|
||||||
self.message = message
|
url=url,
|
||||||
self.finish_reason = finish_reason
|
b64_json=b64_json,
|
||||||
|
revised_prompt=revised_prompt
|
||||||
|
))
|
||||||
|
|
||||||
def to_json(self):
|
class ImagesResponse(BaseModel):
|
||||||
return {
|
|
||||||
**self.__dict__,
|
|
||||||
"message": self.message.to_json()
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChatCompletionDelta(Model):
|
|
||||||
content: Union[str, None] = None
|
|
||||||
|
|
||||||
def __init__(self, content: Union[str, None]):
|
|
||||||
if content is not None:
|
|
||||||
self.content = content
|
|
||||||
self.role = "assistant"
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
return self.__dict__
|
|
||||||
|
|
||||||
class ChatCompletionDeltaChoice(Model):
|
|
||||||
def __init__(self, delta: ChatCompletionDelta, finish_reason: Union[str, None]):
|
|
||||||
self.index = 0
|
|
||||||
self.delta = delta
|
|
||||||
self.finish_reason = finish_reason
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
return {
|
|
||||||
**self.__dict__,
|
|
||||||
"delta": self.delta.to_json()
|
|
||||||
}
|
|
||||||
|
|
||||||
class Image(Model):
|
|
||||||
def __init__(self, url: str = None, b64_json: str = None, revised_prompt: str = None) -> None:
|
|
||||||
if url is not None:
|
|
||||||
self.url = url
|
|
||||||
if b64_json is not None:
|
|
||||||
self.b64_json = b64_json
|
|
||||||
if revised_prompt is not None:
|
|
||||||
self.revised_prompt = revised_prompt
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
return self.__dict__
|
|
||||||
|
|
||||||
class ImagesResponse(Model):
|
|
||||||
data: list[Image]
|
data: list[Image]
|
||||||
model: str
|
model: str
|
||||||
provider: str
|
provider: str
|
||||||
created: int
|
created: int
|
||||||
|
|
||||||
def __init__(self, data: list[Image], created: int = None, model: str = None, provider: str = None) -> None:
|
@classmethod
|
||||||
self.data = data
|
def model_construct(cls, data: list[Image], created: int = None, model: str = None, provider: str = None):
|
||||||
if created is None:
|
if created is None:
|
||||||
created = int(time())
|
created = int(time())
|
||||||
self.model = model
|
return super().model_construct(
|
||||||
if provider is not None:
|
data=data,
|
||||||
self.provider = provider
|
model=model,
|
||||||
self.created = created
|
provider=provider,
|
||||||
|
created=created
|
||||||
def to_json(self):
|
)
|
||||||
return {
|
|
||||||
**self.__dict__,
|
|
||||||
"data": [image.to_json() for image in self.data]
|
|
||||||
}
|
|
||||||
|
@@ -8,16 +8,7 @@ try:
|
|||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
import_error = e
|
import_error = e
|
||||||
|
|
||||||
def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> None:
|
def get_gui_app():
|
||||||
if import_error is not None:
|
|
||||||
raise MissingRequirementsError(f'Install "gui" requirements | pip install -U g4f[gui]\n{import_error}')
|
|
||||||
|
|
||||||
config = {
|
|
||||||
'host' : host,
|
|
||||||
'port' : port,
|
|
||||||
'debug': debug
|
|
||||||
}
|
|
||||||
|
|
||||||
site = Website(app)
|
site = Website(app)
|
||||||
for route in site.routes:
|
for route in site.routes:
|
||||||
app.add_url_rule(
|
app.add_url_rule(
|
||||||
@@ -33,6 +24,19 @@ def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> Non
|
|||||||
view_func = backend_api.routes[route]['function'],
|
view_func = backend_api.routes[route]['function'],
|
||||||
methods = backend_api.routes[route]['methods'],
|
methods = backend_api.routes[route]['methods'],
|
||||||
)
|
)
|
||||||
|
return app
|
||||||
|
|
||||||
|
def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> None:
|
||||||
|
if import_error is not None:
|
||||||
|
raise MissingRequirementsError(f'Install "gui" requirements | pip install -U g4f[gui]\n{import_error}')
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'host' : host,
|
||||||
|
'port' : port,
|
||||||
|
'debug': debug
|
||||||
|
}
|
||||||
|
|
||||||
|
get_gui_app()
|
||||||
|
|
||||||
print(f"Running on port {config['port']}")
|
print(f"Running on port {config['port']}")
|
||||||
app.run(**config)
|
app.run(**config)
|
||||||
|
@@ -22,11 +22,11 @@ conversations: dict[dict[str, BaseConversation]] = {}
|
|||||||
|
|
||||||
class Api:
|
class Api:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_models() -> list[str]:
|
def get_models():
|
||||||
return models._all_models
|
return models._all_models
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_provider_models(provider: str, api_key: str = None) -> list[dict]:
|
def get_provider_models(provider: str, api_key: str = None):
|
||||||
if provider in __map__:
|
if provider in __map__:
|
||||||
provider: ProviderType = __map__[provider]
|
provider: ProviderType = __map__[provider]
|
||||||
if issubclass(provider, ProviderModelMixin):
|
if issubclass(provider, ProviderModelMixin):
|
||||||
@@ -46,39 +46,7 @@ class Api:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_image_models() -> list[dict]:
|
def get_providers() -> dict[str, str]:
|
||||||
image_models = []
|
|
||||||
index = []
|
|
||||||
for provider in __providers__:
|
|
||||||
if hasattr(provider, "image_models"):
|
|
||||||
if hasattr(provider, "get_models"):
|
|
||||||
provider.get_models()
|
|
||||||
parent = provider
|
|
||||||
if hasattr(provider, "parent"):
|
|
||||||
parent = __map__[provider.parent]
|
|
||||||
if parent.__name__ not in index:
|
|
||||||
for model in provider.image_models:
|
|
||||||
image_models.append({
|
|
||||||
"provider": parent.__name__,
|
|
||||||
"url": parent.url,
|
|
||||||
"label": parent.label if hasattr(parent, "label") else None,
|
|
||||||
"image_model": model,
|
|
||||||
"vision_model": getattr(parent, "default_vision_model", None)
|
|
||||||
})
|
|
||||||
index.append(parent.__name__)
|
|
||||||
elif hasattr(provider, "default_vision_model") and provider.__name__ not in index:
|
|
||||||
image_models.append({
|
|
||||||
"provider": provider.__name__,
|
|
||||||
"url": provider.url,
|
|
||||||
"label": provider.label if hasattr(provider, "label") else None,
|
|
||||||
"image_model": None,
|
|
||||||
"vision_model": provider.default_vision_model
|
|
||||||
})
|
|
||||||
index.append(provider.__name__)
|
|
||||||
return image_models
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_providers() -> list[str]:
|
|
||||||
return {
|
return {
|
||||||
provider.__name__: (provider.label if hasattr(provider, "label") else provider.__name__)
|
provider.__name__: (provider.label if hasattr(provider, "label") else provider.__name__)
|
||||||
+ (" (Image Generation)" if getattr(provider, "image_models", None) else "")
|
+ (" (Image Generation)" if getattr(provider, "image_models", None) else "")
|
||||||
@@ -90,7 +58,7 @@ class Api:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_version():
|
def get_version() -> dict:
|
||||||
try:
|
try:
|
||||||
current_version = version.utils.current_version
|
current_version = version.utils.current_version
|
||||||
except VersionNotFoundError:
|
except VersionNotFoundError:
|
||||||
|
@@ -3,7 +3,7 @@ import flask
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
from flask import request, Flask
|
from flask import Flask, request, jsonify
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
@@ -42,17 +42,26 @@ class Backend_Api(Api):
|
|||||||
app (Flask): Flask application instance to attach routes to.
|
app (Flask): Flask application instance to attach routes to.
|
||||||
"""
|
"""
|
||||||
self.app: Flask = app
|
self.app: Flask = app
|
||||||
|
|
||||||
|
def jsonify_models(**kwargs):
|
||||||
|
response = self.get_models(**kwargs)
|
||||||
|
if isinstance(response, list):
|
||||||
|
return jsonify(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def jsonify_provider_models(**kwargs):
|
||||||
|
response = self.get_provider_models(**kwargs)
|
||||||
|
if isinstance(response, list):
|
||||||
|
return jsonify(response)
|
||||||
|
return response
|
||||||
|
|
||||||
self.routes = {
|
self.routes = {
|
||||||
'/backend-api/v2/models': {
|
'/backend-api/v2/models': {
|
||||||
'function': self.get_models,
|
'function': jsonify_models,
|
||||||
'methods': ['GET']
|
'methods': ['GET']
|
||||||
},
|
},
|
||||||
'/backend-api/v2/models/<provider>': {
|
'/backend-api/v2/models/<provider>': {
|
||||||
'function': self.get_provider_models,
|
'function': jsonify_provider_models,
|
||||||
'methods': ['GET']
|
|
||||||
},
|
|
||||||
'/backend-api/v2/image_models': {
|
|
||||||
'function': self.get_image_models,
|
|
||||||
'methods': ['GET']
|
'methods': ['GET']
|
||||||
},
|
},
|
||||||
'/backend-api/v2/providers': {
|
'/backend-api/v2/providers': {
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from flask import render_template, redirect
|
from flask import render_template, redirect
|
||||||
|
|
||||||
|
def redirect_home():
|
||||||
|
return redirect('/chat')
|
||||||
|
|
||||||
class Website:
|
class Website:
|
||||||
def __init__(self, app) -> None:
|
def __init__(self, app) -> None:
|
||||||
self.app = app
|
self.app = app
|
||||||
def redirect_home():
|
|
||||||
return redirect('/chat')
|
|
||||||
self.routes = {
|
self.routes = {
|
||||||
'/': {
|
'/': {
|
||||||
'function': redirect_home,
|
'function': redirect_home,
|
||||||
@@ -35,7 +36,7 @@ class Website:
|
|||||||
|
|
||||||
def _chat(self, conversation_id):
|
def _chat(self, conversation_id):
|
||||||
if '-' not in conversation_id:
|
if '-' not in conversation_id:
|
||||||
return redirect('/chat')
|
return redirect_home()
|
||||||
return render_template('index.html', chat_id=conversation_id)
|
return render_template('index.html', chat_id=conversation_id)
|
||||||
|
|
||||||
def _index(self):
|
def _index(self):
|
||||||
|
@@ -11,7 +11,7 @@ class CloudflareError(ResponseStatusError):
|
|||||||
...
|
...
|
||||||
|
|
||||||
def is_cloudflare(text: str) -> bool:
|
def is_cloudflare(text: str) -> bool:
|
||||||
if "Generated by cloudfront" in text:
|
if "Generated by cloudfront" in text or '<p id="cf-spinner-please-wait">' in text:
|
||||||
return True
|
return True
|
||||||
elif "<title>Attention Required! | Cloudflare</title>" in text or 'id="cf-cloudflare-status"' in text:
|
elif "<title>Attention Required! | Cloudflare</title>" in text or 'id="cf-cloudflare-status"' in text:
|
||||||
return True
|
return True
|
||||||
|
Reference in New Issue
Block a user