mirror of
https://github.com/xtekky/gpt4free.git
synced 2025-10-07 09:11:38 +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=-j8 \
|
||||
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
|
||||
&& pip uninstall --yes \
|
||||
Cython \
|
||||
|
@@ -46,7 +46,6 @@ class PollinationsAI(OpenaiAPI):
|
||||
seed: str = None,
|
||||
**kwargs
|
||||
) -> AsyncResult:
|
||||
if model:
|
||||
model = cls.get_model(model)
|
||||
if model in cls.image_models:
|
||||
if prompt is None:
|
||||
|
@@ -313,6 +313,7 @@ class Conversation(BaseConversation):
|
||||
self.conversation_id = conversation_id
|
||||
self.response_id = response_id
|
||||
self.choice_id = choice_id
|
||||
|
||||
async def iter_filter_base64(response_iter: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
|
||||
search_for = b'[["wrb.fr","XqA3Ic","[\\"'
|
||||
end_with = b'\\'
|
||||
|
@@ -8,21 +8,29 @@ import os
|
||||
import shutil
|
||||
|
||||
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.exceptions import RequestValidationError
|
||||
from fastapi.security import APIKeyHeader
|
||||
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.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from starlette.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
from typing import Union, Optional, List
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Union, Optional, List, Annotated
|
||||
|
||||
import g4f
|
||||
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.client.helper import filter_none
|
||||
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.cookies import read_cookie_files, get_cookies_dir
|
||||
from g4f.Provider import ProviderType, ProviderUtils, __providers__
|
||||
from g4f.gui import get_gui_app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -50,6 +59,10 @@ def create_app(g4f_api_key: str = None):
|
||||
api.register_authorization()
|
||||
api.register_validation_exception_handler()
|
||||
|
||||
if AppConfig.gui:
|
||||
gui_app = WSGIMiddleware(get_gui_app())
|
||||
app.mount("/", gui_app)
|
||||
|
||||
# Read cookie files if not ignored
|
||||
if not AppConfig.ignore_cookie_files:
|
||||
read_cookie_files()
|
||||
@@ -61,17 +74,17 @@ def create_app_debug(g4f_api_key: str = None):
|
||||
return create_app(g4f_api_key)
|
||||
|
||||
class ChatCompletionsConfig(BaseModel):
|
||||
messages: Messages
|
||||
model: str
|
||||
provider: Optional[str] = None
|
||||
messages: Messages = Field(examples=[[{"role": "system", "content": ""}, {"role": "user", "content": ""}]])
|
||||
model: str = Field(default="")
|
||||
provider: Optional[str] = Field(examples=[None])
|
||||
stream: bool = False
|
||||
temperature: Optional[float] = None
|
||||
max_tokens: Optional[int] = None
|
||||
stop: Union[list[str], str, None] = None
|
||||
api_key: Optional[str] = None
|
||||
web_search: Optional[bool] = None
|
||||
proxy: Optional[str] = None
|
||||
conversation_id: str = None
|
||||
temperature: Optional[float] = Field(examples=[None])
|
||||
max_tokens: Optional[int] = Field(examples=[None])
|
||||
stop: Union[list[str], str, None] = Field(examples=[None])
|
||||
api_key: Optional[str] = Field(examples=[None])
|
||||
web_search: Optional[bool] = Field(examples=[None])
|
||||
proxy: Optional[str] = Field(examples=[None])
|
||||
conversation_id: Optional[str] = Field(examples=[None])
|
||||
|
||||
class ImageGenerationConfig(BaseModel):
|
||||
prompt: str
|
||||
@@ -101,6 +114,9 @@ class ModelResponseModel(BaseModel):
|
||||
created: int
|
||||
owned_by: Optional[str]
|
||||
|
||||
class ErrorResponseModel(BaseModel):
|
||||
error: str
|
||||
|
||||
class AppConfig:
|
||||
ignored_providers: Optional[list[str]] = None
|
||||
g4f_api_key: Optional[str] = None
|
||||
@@ -109,6 +125,7 @@ class AppConfig:
|
||||
provider: str = None
|
||||
image_provider: str = None
|
||||
proxy: str = None
|
||||
gui: bool = False
|
||||
|
||||
@classmethod
|
||||
def set_config(cls, **data):
|
||||
@@ -129,6 +146,8 @@ class Api:
|
||||
self.get_g4f_api_key = APIKeyHeader(name="g4f-api-key")
|
||||
self.conversations: dict[str, dict[str, BaseConversation]] = {}
|
||||
|
||||
security = HTTPBearer(auto_error=False)
|
||||
|
||||
def register_authorization(self):
|
||||
@self.app.middleware("http")
|
||||
async def authorization(request: Request, call_next):
|
||||
@@ -192,7 +211,7 @@ class Api:
|
||||
} for model_id, model in model_list.items()]
|
||||
|
||||
@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:
|
||||
model_info = g4f.models.ModelUtils.convert[model_name]
|
||||
return JSONResponse({
|
||||
@@ -201,20 +220,20 @@ class Api:
|
||||
'created': 0,
|
||||
'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")
|
||||
async def chat_completions(config: ChatCompletionsConfig, request: Request = None, provider: str = None):
|
||||
@self.app.post("/v1/chat/completions", response_model=ChatCompletion)
|
||||
async def chat_completions(
|
||||
config: ChatCompletionsConfig,
|
||||
credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None,
|
||||
provider: str = None
|
||||
):
|
||||
try:
|
||||
config.provider = provider if config.provider is None else config.provider
|
||||
if config.provider is None:
|
||||
config.provider = AppConfig.provider
|
||||
if config.api_key is None and request is not None:
|
||||
auth_header = request.headers.get("Authorization")
|
||||
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
|
||||
if credentials is not None:
|
||||
config.api_key = credentials.credentials
|
||||
|
||||
conversation = return_conversation = None
|
||||
if config.conversation_id is not None and config.provider is not None:
|
||||
@@ -242,8 +261,7 @@ class Api:
|
||||
)
|
||||
|
||||
if not config.stream:
|
||||
response: ChatCompletion = await response
|
||||
return JSONResponse(response.to_json())
|
||||
return await response
|
||||
|
||||
async def streaming():
|
||||
try:
|
||||
@@ -254,7 +272,7 @@ class Api:
|
||||
self.conversations[config.conversation_id] = {}
|
||||
self.conversations[config.conversation_id][config.provider] = chunk
|
||||
else:
|
||||
yield f"data: {json.dumps(chunk.to_json())}\n\n"
|
||||
yield f"data: {chunk.json()}\n\n"
|
||||
except GeneratorExit:
|
||||
pass
|
||||
except Exception as e:
|
||||
@@ -268,15 +286,15 @@ class Api:
|
||||
logger.exception(e)
|
||||
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/generations")
|
||||
async def generate_image(config: ImageGenerationConfig, request: Request):
|
||||
if config.api_key is None:
|
||||
auth_header = request.headers.get("Authorization")
|
||||
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
|
||||
@self.app.post("/v1/images/generate", response_model=ImagesResponse)
|
||||
@self.app.post("/v1/images/generations", response_model=ImagesResponse)
|
||||
async def generate_image(
|
||||
request: Request,
|
||||
config: ImageGenerationConfig,
|
||||
credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None
|
||||
):
|
||||
if credentials is not None:
|
||||
config.api_key = credentials.credentials
|
||||
try:
|
||||
response = await self.client.images.generate(
|
||||
prompt=config.prompt,
|
||||
@@ -291,7 +309,7 @@ class Api:
|
||||
for image in response.data:
|
||||
if hasattr(image, "url") and image.url.startswith("/"):
|
||||
image.url = f"{request.base_url}{image.url.lstrip('/')}"
|
||||
return JSONResponse(response.to_json())
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return Response(content=format_exception(e, config, True), status_code=500, media_type="application/json")
|
||||
@@ -342,22 +360,29 @@ class Api:
|
||||
file.file.close()
|
||||
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):
|
||||
try:
|
||||
provider_handler = convert_to_provider(provider)
|
||||
except ProviderNotFoundError:
|
||||
return Response("Provider not found", 404)
|
||||
return JSONResponse({"error": "Provider not found"}, HTTP_404_NOT_FOUND)
|
||||
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:
|
||||
return Response("Missing query params", 500)
|
||||
return JSONResponse({"error": "Missing query params"}, HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
response_data = provider_handler.synthesize({**request.query_params})
|
||||
content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
|
||||
return StreamingResponse(response_data, media_type=content_type)
|
||||
|
||||
@self.app.get("/images/{filename}")
|
||||
async def get_image(filename) -> FileResponse:
|
||||
@self.app.get("/images/{filename}", response_class=FileResponse, responses={
|
||||
HTTP_200_OK: {"content": {"image/*": {}}},
|
||||
HTTP_404_NOT_FOUND: {}
|
||||
})
|
||||
async def get_image(filename):
|
||||
target = os.path.join(images_dir, filename)
|
||||
|
||||
if not os.path.isfile(target):
|
||||
|
@@ -12,6 +12,7 @@ def main():
|
||||
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("--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("--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)")
|
||||
@@ -48,7 +49,8 @@ def run_api_args(args):
|
||||
provider=args.provider,
|
||||
image_provider=args.image_provider,
|
||||
proxy=args.proxy,
|
||||
model=args.model
|
||||
model=args.model,
|
||||
gui=args.gui,
|
||||
)
|
||||
g4f.cookies.browsers = [g4f.cookies[browser] for browser in args.cookie_browsers]
|
||||
run_api(
|
||||
|
@@ -73,7 +73,7 @@ def iter_response(
|
||||
finish_reason = "stop"
|
||||
|
||||
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:
|
||||
break
|
||||
@@ -83,12 +83,12 @@ def iter_response(
|
||||
finish_reason = "stop" if finish_reason is None else finish_reason
|
||||
|
||||
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:
|
||||
if response_format is not None and "type" in response_format:
|
||||
if response_format["type"] == "json_object":
|
||||
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
|
||||
def iter_append_model_and_provider(response: ChatCompletionResponseType) -> ChatCompletionResponseType:
|
||||
@@ -137,7 +137,7 @@ async def async_iter_response(
|
||||
finish_reason = "stop"
|
||||
|
||||
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:
|
||||
break
|
||||
@@ -145,12 +145,12 @@ async def async_iter_response(
|
||||
finish_reason = "stop" if finish_reason is None else finish_reason
|
||||
|
||||
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:
|
||||
if response_format is not None and "type" in response_format:
|
||||
if response_format["type"] == "json_object":
|
||||
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:
|
||||
if hasattr(response, 'aclose'):
|
||||
await safe_aclose(response)
|
||||
@@ -394,13 +394,13 @@ class Images:
|
||||
if response_format == "b64_json":
|
||||
with open(os.path.join(images_dir, os.path.basename(image_file)), "rb") as file:
|
||||
image_data = base64.b64encode(file.read()).decode()
|
||||
return Image(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, b64_json=image_data, 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])
|
||||
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)
|
||||
return ImagesResponse(
|
||||
return ImagesResponse.model_construct(
|
||||
images,
|
||||
model=last_provider.get("model") if model is None else model,
|
||||
provider=last_provider.get("name") if provider is None else provider
|
||||
|
@@ -1,130 +1,150 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Union
|
||||
from typing import Optional, List, Dict
|
||||
from time import time
|
||||
|
||||
class Model():
|
||||
...
|
||||
from .helper import filter_none
|
||||
|
||||
class ChatCompletion(Model):
|
||||
def __init__(
|
||||
self,
|
||||
try:
|
||||
from pydantic import BaseModel, Field
|
||||
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,
|
||||
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"
|
||||
self.created: int = created
|
||||
self.model: str = None
|
||||
self.provider: str = None
|
||||
self.choices = [ChatCompletionChoice(ChatCompletionMessage(content), finish_reason)]
|
||||
self.usage: dict[str, int] = {
|
||||
return super().model_construct(
|
||||
id=f"chatcmpl-{completion_id}" if completion_id else None,
|
||||
object="chat.completion.cunk",
|
||||
created=created,
|
||||
model=None,
|
||||
provider=None,
|
||||
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,
|
||||
"completion_tokens": 0, #completion_tokens,
|
||||
"total_tokens": 0, #prompt_tokens + completion_tokens,
|
||||
}
|
||||
)
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
**self.__dict__,
|
||||
"choices": [choice.to_json() for choice in self.choices]
|
||||
}
|
||||
class ChatCompletionDelta(BaseModel):
|
||||
role: str
|
||||
content: str
|
||||
|
||||
class ChatCompletionChunk(Model):
|
||||
def __init__(
|
||||
self,
|
||||
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)]
|
||||
@classmethod
|
||||
def model_construct(cls, content: Optional[str]):
|
||||
return super().model_construct(role="assistant", content=content)
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
**self.__dict__,
|
||||
"choices": [choice.to_json() for choice in self.choices]
|
||||
}
|
||||
class ChatCompletionDeltaChoice(BaseModel):
|
||||
index: int
|
||||
delta: ChatCompletionDelta
|
||||
finish_reason: Optional[str]
|
||||
|
||||
class ChatCompletionMessage(Model):
|
||||
def __init__(self, content: Union[str, None]):
|
||||
self.role = "assistant"
|
||||
self.content = content
|
||||
@classmethod
|
||||
def model_construct(cls, delta: ChatCompletionDelta, finish_reason: Optional[str]):
|
||||
return super().model_construct(index=0, delta=delta, finish_reason=finish_reason)
|
||||
|
||||
def to_json(self):
|
||||
return self.__dict__
|
||||
class Image(BaseModel):
|
||||
url: Optional[str]
|
||||
b64_json: Optional[str]
|
||||
revised_prompt: Optional[str]
|
||||
|
||||
class ChatCompletionChoice(Model):
|
||||
def __init__(self, message: ChatCompletionMessage, finish_reason: str):
|
||||
self.index = 0
|
||||
self.message = message
|
||||
self.finish_reason = finish_reason
|
||||
@classmethod
|
||||
def model_construct(cls, url: str = None, b64_json: str = None, revised_prompt: str = None):
|
||||
return super().model_construct(**filter_none(
|
||||
url=url,
|
||||
b64_json=b64_json,
|
||||
revised_prompt=revised_prompt
|
||||
))
|
||||
|
||||
def to_json(self):
|
||||
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):
|
||||
class ImagesResponse(BaseModel):
|
||||
data: list[Image]
|
||||
model: str
|
||||
provider: str
|
||||
created: int
|
||||
|
||||
def __init__(self, data: list[Image], created: int = None, model: str = None, provider: str = None) -> None:
|
||||
self.data = data
|
||||
@classmethod
|
||||
def model_construct(cls, data: list[Image], created: int = None, model: str = None, provider: str = None):
|
||||
if created is None:
|
||||
created = int(time())
|
||||
self.model = model
|
||||
if provider is not None:
|
||||
self.provider = provider
|
||||
self.created = created
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
**self.__dict__,
|
||||
"data": [image.to_json() for image in self.data]
|
||||
}
|
||||
return super().model_construct(
|
||||
data=data,
|
||||
model=model,
|
||||
provider=provider,
|
||||
created=created
|
||||
)
|
||||
|
@@ -8,6 +8,24 @@ try:
|
||||
except ImportError as e:
|
||||
import_error = e
|
||||
|
||||
def get_gui_app():
|
||||
site = Website(app)
|
||||
for route in site.routes:
|
||||
app.add_url_rule(
|
||||
route,
|
||||
view_func=site.routes[route]['function'],
|
||||
methods=site.routes[route]['methods'],
|
||||
)
|
||||
|
||||
backend_api = Backend_Api(app)
|
||||
for route in backend_api.routes:
|
||||
app.add_url_rule(
|
||||
route,
|
||||
view_func = backend_api.routes[route]['function'],
|
||||
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}')
|
||||
@@ -18,21 +36,7 @@ def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> Non
|
||||
'debug': debug
|
||||
}
|
||||
|
||||
site = Website(app)
|
||||
for route in site.routes:
|
||||
app.add_url_rule(
|
||||
route,
|
||||
view_func = site.routes[route]['function'],
|
||||
methods = site.routes[route]['methods'],
|
||||
)
|
||||
|
||||
backend_api = Backend_Api(app)
|
||||
for route in backend_api.routes:
|
||||
app.add_url_rule(
|
||||
route,
|
||||
view_func = backend_api.routes[route]['function'],
|
||||
methods = backend_api.routes[route]['methods'],
|
||||
)
|
||||
get_gui_app()
|
||||
|
||||
print(f"Running on port {config['port']}")
|
||||
app.run(**config)
|
||||
|
@@ -22,11 +22,11 @@ conversations: dict[dict[str, BaseConversation]] = {}
|
||||
|
||||
class Api:
|
||||
@staticmethod
|
||||
def get_models() -> list[str]:
|
||||
def get_models():
|
||||
return models._all_models
|
||||
|
||||
@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__:
|
||||
provider: ProviderType = __map__[provider]
|
||||
if issubclass(provider, ProviderModelMixin):
|
||||
@@ -46,39 +46,7 @@ class Api:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_image_models() -> list[dict]:
|
||||
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]:
|
||||
def get_providers() -> dict[str, str]:
|
||||
return {
|
||||
provider.__name__: (provider.label if hasattr(provider, "label") else provider.__name__)
|
||||
+ (" (Image Generation)" if getattr(provider, "image_models", None) else "")
|
||||
@@ -90,7 +58,7 @@ class Api:
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_version():
|
||||
def get_version() -> dict:
|
||||
try:
|
||||
current_version = version.utils.current_version
|
||||
except VersionNotFoundError:
|
||||
|
@@ -3,7 +3,7 @@ import flask
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
from flask import request, Flask
|
||||
from flask import Flask, request, jsonify
|
||||
from typing import Generator
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
@@ -42,17 +42,26 @@ class Backend_Api(Api):
|
||||
app (Flask): Flask application instance to attach routes to.
|
||||
"""
|
||||
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 = {
|
||||
'/backend-api/v2/models': {
|
||||
'function': self.get_models,
|
||||
'function': jsonify_models,
|
||||
'methods': ['GET']
|
||||
},
|
||||
'/backend-api/v2/models/<provider>': {
|
||||
'function': self.get_provider_models,
|
||||
'methods': ['GET']
|
||||
},
|
||||
'/backend-api/v2/image_models': {
|
||||
'function': self.get_image_models,
|
||||
'function': jsonify_provider_models,
|
||||
'methods': ['GET']
|
||||
},
|
||||
'/backend-api/v2/providers': {
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import uuid
|
||||
from flask import render_template, redirect
|
||||
|
||||
def redirect_home():
|
||||
return redirect('/chat')
|
||||
|
||||
class Website:
|
||||
def __init__(self, app) -> None:
|
||||
self.app = app
|
||||
def redirect_home():
|
||||
return redirect('/chat')
|
||||
self.routes = {
|
||||
'/': {
|
||||
'function': redirect_home,
|
||||
@@ -35,7 +36,7 @@ class Website:
|
||||
|
||||
def _chat(self, conversation_id):
|
||||
if '-' not in conversation_id:
|
||||
return redirect('/chat')
|
||||
return redirect_home()
|
||||
return render_template('index.html', chat_id=conversation_id)
|
||||
|
||||
def _index(self):
|
||||
|
@@ -11,7 +11,7 @@ class CloudflareError(ResponseStatusError):
|
||||
...
|
||||
|
||||
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
|
||||
elif "<title>Attention Required! | Cloudflare</title>" in text or 'id="cf-cloudflare-status"' in text:
|
||||
return True
|
||||
|
Reference in New Issue
Block a user