open-webui/backend/open_webui/apps/webui/main.py

493 lines
15 KiB
Python
Raw Normal View History

2024-08-27 22:10:27 +00:00
import inspect
import json
import logging
2024-10-22 10:16:48 +00:00
import time
2024-08-27 22:10:27 +00:00
from typing import AsyncGenerator, Generator, Iterator
from open_webui.apps.socket.main import get_event_call, get_event_emitter
from open_webui.apps.webui.models.functions import Functions
from open_webui.apps.webui.models.models import Models
from open_webui.apps.webui.routers import (
2024-01-08 07:43:32 +00:00
auths,
chats,
2024-10-17 04:05:03 +00:00
folders,
2024-08-27 22:10:27 +00:00
configs,
2024-11-15 02:35:14 +00:00
groups,
2024-08-27 22:10:27 +00:00
files,
functions,
memories,
2024-05-24 07:26:00 +00:00
models,
2024-10-02 05:45:04 +00:00
knowledge,
2024-01-08 07:43:32 +00:00
prompts,
2024-10-22 10:16:48 +00:00
evaluations,
2024-08-27 22:10:27 +00:00
tools,
users,
2024-01-08 07:43:32 +00:00
utils,
)
from open_webui.apps.webui.utils import load_function_module_by_id
from open_webui.config import (
ADMIN_EMAIL,
2024-08-27 22:10:27 +00:00
CORS_ALLOW_ORIGIN,
2024-02-14 09:17:43 +00:00
DEFAULT_MODELS,
DEFAULT_PROMPT_SUGGESTIONS,
DEFAULT_USER_ROLE,
2024-08-27 22:10:27 +00:00
ENABLE_COMMUNITY_SHARING,
ENABLE_LOGIN_FORM,
2024-08-27 22:10:27 +00:00
ENABLE_MESSAGE_RATING,
ENABLE_SIGNUP,
2024-10-22 10:16:48 +00:00
ENABLE_EVALUATION_ARENA_MODELS,
EVALUATION_ARENA_MODELS,
DEFAULT_ARENA_MODEL,
2024-08-27 22:10:27 +00:00
JWT_EXPIRES_IN,
ENABLE_OAUTH_ROLE_MANAGEMENT,
2024-10-03 18:55:32 +00:00
OAUTH_ROLES_CLAIM,
2024-08-27 22:10:27 +00:00
OAUTH_EMAIL_CLAIM,
OAUTH_PICTURE_CLAIM,
OAUTH_USERNAME_CLAIM,
2024-10-11 12:08:11 +00:00
OAUTH_ALLOWED_ROLES,
OAUTH_ADMIN_ROLES,
2024-08-27 22:10:27 +00:00
SHOW_ADMIN_DETAILS,
2024-02-14 09:17:43 +00:00
USER_PERMISSIONS,
2024-03-21 01:35:02 +00:00
WEBHOOK_URL,
2024-08-27 22:10:27 +00:00
WEBUI_AUTH,
WEBUI_BANNERS,
ENABLE_LDAP,
LDAP_SERVER_LABEL,
LDAP_SERVER_HOST,
LDAP_SERVER_PORT,
LDAP_ATTRIBUTE_FOR_USERNAME,
LDAP_SEARCH_FILTERS,
LDAP_SEARCH_BASE,
LDAP_APP_DN,
LDAP_APP_PASSWORD,
LDAP_USE_TLS,
LDAP_CA_CERT_FILE,
LDAP_CIPHERS,
2024-06-11 03:39:55 +00:00
AppConfig,
2024-02-14 09:17:43 +00:00
)
from open_webui.env import (
2024-11-10 02:01:23 +00:00
ENV,
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
WEBUI_AUTH_TRUSTED_NAME_HEADER,
)
2024-08-27 22:10:27 +00:00
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
2024-06-24 18:17:18 +00:00
from pydantic import BaseModel
from open_webui.utils.misc import (
2024-08-27 22:10:27 +00:00
openai_chat_chunk_message_template,
openai_chat_completion_message_template,
)
2024-09-07 02:09:57 +00:00
from open_webui.utils.payload import (
apply_model_params_to_body_openai,
apply_model_system_prompt_to_body,
)
from open_webui.utils.tools import get_tools
2024-06-24 18:17:18 +00:00
2024-11-15 02:35:14 +00:00
app = FastAPI(
docs_url="/docs" if ENV == "dev" else None,
openapi_url="/openapi.json" if ENV == "dev" else None,
redoc_url=None,
)
2023-11-19 00:47:12 +00:00
2024-08-22 12:34:35 +00:00
log = logging.getLogger(__name__)
app.state.config = AppConfig()
2024-02-20 04:44:00 +00:00
app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP
app.state.config.ENABLE_LOGIN_FORM = ENABLE_LOGIN_FORM
app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN
2024-06-11 05:38:48 +00:00
app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER
app.state.config.SHOW_ADMIN_DETAILS = SHOW_ADMIN_DETAILS
app.state.config.ADMIN_EMAIL = ADMIN_EMAIL
app.state.config.DEFAULT_MODELS = DEFAULT_MODELS
app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
app.state.config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
2024-11-17 05:07:56 +00:00
app.state.config.USER_PERMISSIONS = USER_PERMISSIONS
app.state.config.WEBHOOK_URL = WEBHOOK_URL
app.state.config.BANNERS = WEBUI_BANNERS
2024-05-25 01:26:36 +00:00
2024-05-26 16:10:25 +00:00
app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING
2024-08-19 13:16:49 +00:00
app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING
2024-05-25 01:26:36 +00:00
2024-10-22 10:16:48 +00:00
app.state.config.ENABLE_EVALUATION_ARENA_MODELS = ENABLE_EVALUATION_ARENA_MODELS
app.state.config.EVALUATION_ARENA_MODELS = EVALUATION_ARENA_MODELS
app.state.config.OAUTH_USERNAME_CLAIM = OAUTH_USERNAME_CLAIM
app.state.config.OAUTH_PICTURE_CLAIM = OAUTH_PICTURE_CLAIM
app.state.config.OAUTH_EMAIL_CLAIM = OAUTH_EMAIL_CLAIM
app.state.config.ENABLE_OAUTH_ROLE_MANAGEMENT = ENABLE_OAUTH_ROLE_MANAGEMENT
2024-10-03 18:55:32 +00:00
app.state.config.OAUTH_ROLES_CLAIM = OAUTH_ROLES_CLAIM
2024-10-11 12:08:11 +00:00
app.state.config.OAUTH_ALLOWED_ROLES = OAUTH_ALLOWED_ROLES
app.state.config.OAUTH_ADMIN_ROLES = OAUTH_ADMIN_ROLES
2024-10-03 18:55:32 +00:00
app.state.config.ENABLE_LDAP = ENABLE_LDAP
app.state.config.LDAP_SERVER_LABEL = LDAP_SERVER_LABEL
app.state.config.LDAP_SERVER_HOST = LDAP_SERVER_HOST
app.state.config.LDAP_SERVER_PORT = LDAP_SERVER_PORT
app.state.config.LDAP_ATTRIBUTE_FOR_USERNAME = LDAP_ATTRIBUTE_FOR_USERNAME
app.state.config.LDAP_APP_DN = LDAP_APP_DN
app.state.config.LDAP_APP_PASSWORD = LDAP_APP_PASSWORD
app.state.config.LDAP_SEARCH_BASE = LDAP_SEARCH_BASE
app.state.config.LDAP_SEARCH_FILTERS = LDAP_SEARCH_FILTERS
app.state.config.LDAP_USE_TLS = LDAP_USE_TLS
app.state.config.LDAP_CA_CERT_FILE = LDAP_CA_CERT_FILE
app.state.config.LDAP_CIPHERS = LDAP_CIPHERS
2024-06-11 05:38:48 +00:00
app.state.TOOLS = {}
2024-06-20 07:37:02 +00:00
app.state.FUNCTIONS = {}
2024-05-19 15:00:07 +00:00
2023-11-19 00:47:12 +00:00
app.add_middleware(
CORSMiddleware,
2024-08-18 21:17:26 +00:00
allow_origins=CORS_ALLOW_ORIGIN,
2023-11-19 00:47:12 +00:00
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
2024-06-20 07:49:11 +00:00
app.include_router(configs.router, prefix="/configs", tags=["configs"])
2024-10-22 10:16:48 +00:00
2023-11-19 00:47:12 +00:00
app.include_router(auths.router, prefix="/auths", tags=["auths"])
app.include_router(users.router, prefix="/users", tags=["users"])
2024-10-22 10:16:48 +00:00
app.include_router(chats.router, prefix="/chats", tags=["chats"])
2024-05-19 15:00:07 +00:00
2024-05-24 07:26:00 +00:00
app.include_router(models.router, prefix="/models", tags=["models"])
2024-10-02 05:45:04 +00:00
app.include_router(knowledge.router, prefix="/knowledge", tags=["knowledge"])
app.include_router(prompts.router, prefix="/prompts", tags=["prompts"])
2024-06-20 07:49:11 +00:00
app.include_router(tools.router, prefix="/tools", tags=["tools"])
2024-05-19 15:00:07 +00:00
2024-10-02 05:45:04 +00:00
app.include_router(memories.router, prefix="/memories", tags=["memories"])
2024-10-22 10:16:48 +00:00
app.include_router(folders.router, prefix="/folders", tags=["folders"])
2024-11-15 02:35:14 +00:00
app.include_router(groups.router, prefix="/groups", tags=["groups"])
2024-10-22 10:16:48 +00:00
app.include_router(files.router, prefix="/files", tags=["files"])
2024-11-15 02:35:14 +00:00
app.include_router(functions.router, prefix="/functions", tags=["functions"])
app.include_router(evaluations.router, prefix="/evaluations", tags=["evaluations"])
2024-10-22 10:16:48 +00:00
2023-12-27 06:10:22 +00:00
app.include_router(utils.router, prefix="/utils", tags=["utils"])
2023-11-19 00:47:12 +00:00
@app.get("/")
async def get_status():
2024-01-03 00:48:10 +00:00
return {
"status": True,
"auth": WEBUI_AUTH,
"default_models": app.state.config.DEFAULT_MODELS,
"default_prompt_suggestions": app.state.config.DEFAULT_PROMPT_SUGGESTIONS,
2024-01-03 00:48:10 +00:00
}
2024-06-20 11:38:59 +00:00
2024-10-21 11:14:49 +00:00
async def get_all_models():
2024-10-22 10:16:48 +00:00
models = []
2024-10-21 11:14:49 +00:00
pipe_models = await get_pipe_models()
2024-10-22 10:16:48 +00:00
models = models + pipe_models
if app.state.config.ENABLE_EVALUATION_ARENA_MODELS:
arena_models = []
if len(app.state.config.EVALUATION_ARENA_MODELS) > 0:
arena_models = [
{
"id": model["id"],
"name": model["name"],
"info": {
"meta": model["meta"],
},
"object": "model",
"created": int(time.time()),
"owned_by": "arena",
"arena": True,
}
for model in app.state.config.EVALUATION_ARENA_MODELS
]
else:
# Add default arena model
arena_models = [
{
"id": DEFAULT_ARENA_MODEL["id"],
"name": DEFAULT_ARENA_MODEL["name"],
"info": {
"meta": DEFAULT_ARENA_MODEL["meta"],
},
"object": "model",
"created": int(time.time()),
"owned_by": "arena",
"arena": True,
}
]
models = models + arena_models
return models
2024-10-21 11:14:49 +00:00
2024-07-31 12:35:02 +00:00
def get_function_module(pipe_id: str):
# Check if function is already loaded
if pipe_id not in app.state.FUNCTIONS:
function_module, _, _ = load_function_module_by_id(pipe_id)
app.state.FUNCTIONS[pipe_id] = function_module
else:
function_module = app.state.FUNCTIONS[pipe_id]
if hasattr(function_module, "valves") and hasattr(function_module, "Valves"):
valves = Functions.get_function_valves_by_id(pipe_id)
function_module.valves = function_module.Valves(**(valves if valves else {}))
return function_module
async def get_pipe_models():
pipes = Functions.get_functions_by_type("pipe", active_only=True)
2024-06-20 11:38:59 +00:00
pipe_models = []
for pipe in pipes:
2024-07-31 12:35:02 +00:00
function_module = get_function_module(pipe.id)
2024-06-24 17:37:57 +00:00
2024-06-20 11:38:59 +00:00
# Check if function is a manifold
2024-07-31 21:05:37 +00:00
if hasattr(function_module, "pipes"):
2024-09-05 16:55:31 +00:00
sub_pipes = []
2024-07-31 12:35:02 +00:00
# Check if pipes is a function or a list
2024-09-05 16:55:31 +00:00
try:
if callable(function_module.pipes):
sub_pipes = function_module.pipes()
else:
sub_pipes = function_module.pipes
except Exception as e:
log.exception(e)
sub_pipes = []
print(sub_pipes)
for p in sub_pipes:
sub_pipe_id = f'{pipe.id}.{p["id"]}'
sub_pipe_name = p["name"]
2024-07-31 12:35:02 +00:00
if hasattr(function_module, "name"):
2024-09-05 16:55:31 +00:00
sub_pipe_name = f"{function_module.name}{sub_pipe_name}"
2024-07-31 12:35:02 +00:00
pipe_flag = {"type": pipe.type}
pipe_models.append(
{
2024-09-05 16:55:31 +00:00
"id": sub_pipe_id,
"name": sub_pipe_name,
2024-07-31 12:35:02 +00:00
"object": "model",
"created": pipe.created_at,
"owned_by": "openai",
"pipe": pipe_flag,
}
)
2024-06-20 11:38:59 +00:00
else:
2024-07-11 23:24:59 +00:00
pipe_flag = {"type": "pipe"}
2024-06-20 11:38:59 +00:00
pipe_models.append(
{
"id": pipe.id,
"name": pipe.name,
"object": "model",
"created": pipe.created_at,
"owned_by": "openai",
2024-07-11 23:24:59 +00:00
"pipe": pipe_flag,
2024-06-20 11:38:59 +00:00
}
)
return pipe_models
2024-06-24 18:17:18 +00:00
2024-07-31 12:35:02 +00:00
async def execute_pipe(pipe, params):
if inspect.iscoroutinefunction(pipe):
return await pipe(**params)
else:
return pipe(**params)
2024-07-11 22:20:56 +00:00
2024-07-11 20:43:44 +00:00
2024-07-31 14:26:26 +00:00
async def get_message_content(res: str | Generator | AsyncGenerator) -> str:
2024-07-31 12:35:02 +00:00
if isinstance(res, str):
return res
if isinstance(res, Generator):
return "".join(map(str, res))
if isinstance(res, AsyncGenerator):
return "".join([str(stream) async for stream in res])
2024-07-04 20:41:18 +00:00
2024-07-31 12:35:02 +00:00
def process_line(form_data: dict, line):
if isinstance(line, BaseModel):
line = line.model_dump_json()
line = f"data: {line}"
if isinstance(line, dict):
line = f"data: {json.dumps(line)}"
2024-07-04 20:41:18 +00:00
2024-07-31 12:35:02 +00:00
try:
line = line.decode("utf-8")
except Exception:
pass
2024-07-04 20:41:18 +00:00
2024-07-31 12:35:02 +00:00
if line.startswith("data:"):
return f"{line}\n\n"
2024-07-04 20:41:18 +00:00
else:
2024-07-31 21:00:00 +00:00
line = openai_chat_chunk_message_template(form_data["model"], line)
2024-07-31 12:35:02 +00:00
return f"data: {json.dumps(line)}\n\n"
def get_pipe_id(form_data: dict) -> str:
pipe_id = form_data["model"]
if "." in pipe_id:
pipe_id, _ = pipe_id.split(".", 1)
print(pipe_id)
return pipe_id
2024-08-22 12:34:35 +00:00
def get_function_params(function_module, form_data, user, extra_params=None):
if extra_params is None:
extra_params = {}
2024-07-31 12:35:02 +00:00
pipe_id = get_pipe_id(form_data)
2024-08-22 13:23:32 +00:00
2024-07-31 12:35:02 +00:00
# Get the signature of the function
2024-08-01 23:45:50 +00:00
sig = inspect.signature(function_module.pipe)
2024-08-22 13:23:32 +00:00
params = {"body": form_data} | {
k: v for k, v in extra_params.items() if k in sig.parameters
}
2024-08-22 12:34:35 +00:00
2024-08-22 13:20:19 +00:00
if "__user__" in params and hasattr(function_module, "UserValves"):
2024-08-22 12:34:35 +00:00
user_valves = Functions.get_user_valves_by_id_and_user_id(pipe_id, user.id)
2024-08-22 13:20:19 +00:00
try:
params["__user__"]["valves"] = function_module.UserValves(**user_valves)
except Exception as e:
log.exception(e)
params["__user__"]["valves"] = function_module.UserValves()
2024-08-22 13:03:39 +00:00
2024-07-31 12:35:02 +00:00
return params
2024-06-24 18:17:18 +00:00
2024-11-16 12:41:07 +00:00
async def generate_function_chat_completion(form_data, user, models: dict = {}):
2024-07-31 12:51:25 +00:00
model_id = form_data.get("model")
model_info = Models.get_model_by_id(model_id)
2024-08-22 13:09:06 +00:00
2024-08-20 14:41:49 +00:00
metadata = form_data.pop("metadata", {})
2024-08-22 13:09:06 +00:00
2024-08-20 14:41:49 +00:00
files = metadata.get("files", [])
2024-08-21 20:42:25 +00:00
tool_ids = metadata.get("tool_ids", [])
# Check if tool_ids is None
if tool_ids is None:
tool_ids = []
2024-07-31 12:51:25 +00:00
2024-08-01 23:45:50 +00:00
__event_emitter__ = None
__event_call__ = None
__task__ = None
2024-10-06 21:56:49 +00:00
__task_body__ = None
2024-08-01 23:45:50 +00:00
if metadata:
if all(k in metadata for k in ("session_id", "chat_id", "message_id")):
__event_emitter__ = get_event_emitter(metadata)
__event_call__ = get_event_call(metadata)
__task__ = metadata.get("task", None)
2024-10-06 21:56:49 +00:00
__task_body__ = metadata.get("task_body", None)
2024-08-01 23:45:50 +00:00
2024-08-19 10:08:27 +00:00
extra_params = {
"__event_emitter__": __event_emitter__,
"__event_call__": __event_call__,
"__task__": __task__,
2024-10-06 21:56:49 +00:00
"__task_body__": __task_body__,
"__files__": files,
2024-08-22 12:34:35 +00:00
"__user__": {
"id": user.id,
"email": user.email,
"name": user.name,
"role": user.role,
},
"__metadata__": metadata,
2024-08-22 13:24:48 +00:00
}
2024-08-23 11:58:43 +00:00
extra_params["__tools__"] = get_tools(
app,
tool_ids,
user,
{
**extra_params,
2024-11-16 12:41:07 +00:00
"__model__": models.get(form_data["model"], None),
2024-08-23 11:58:43 +00:00
"__messages__": form_data["messages"],
"__files__": files,
},
2024-08-22 13:24:48 +00:00
)
2024-07-31 12:51:25 +00:00
if model_info:
if model_info.base_model_id:
form_data["model"] = model_info.base_model_id
params = model_info.params.model_dump()
2024-08-06 10:31:45 +00:00
form_data = apply_model_params_to_body_openai(params, form_data)
2024-08-01 23:45:50 +00:00
form_data = apply_model_system_prompt_to_body(params, form_data, user)
2024-07-11 22:20:56 +00:00
2024-07-31 21:05:37 +00:00
pipe_id = get_pipe_id(form_data)
function_module = get_function_module(pipe_id)
2024-06-24 19:56:41 +00:00
2024-07-31 21:05:37 +00:00
pipe = function_module.pipe
2024-08-19 10:08:27 +00:00
params = get_function_params(function_module, form_data, user, extra_params)
2024-06-24 19:56:41 +00:00
if form_data.get("stream", False):
2024-06-24 18:17:18 +00:00
2024-07-31 21:05:37 +00:00
async def stream_content():
2024-06-24 18:17:18 +00:00
try:
2024-07-31 12:35:02 +00:00
res = await execute_pipe(pipe, params)
2024-06-24 19:56:41 +00:00
2024-07-31 21:05:37 +00:00
# Directly return if the response is a StreamingResponse
if isinstance(res, StreamingResponse):
async for data in res.body_iterator:
yield data
return
if isinstance(res, dict):
yield f"data: {json.dumps(res)}\n\n"
return
2024-06-24 18:17:18 +00:00
except Exception as e:
print(f"Error: {e}")
2024-07-31 21:05:37 +00:00
yield f"data: {json.dumps({'error': {'detail':str(e)}})}\n\n"
return
2024-06-24 18:17:18 +00:00
2024-07-31 21:05:37 +00:00
if isinstance(res, str):
message = openai_chat_chunk_message_template(form_data["model"], res)
yield f"data: {json.dumps(message)}\n\n"
if isinstance(res, Iterator):
for line in res:
yield process_line(form_data, line)
if isinstance(res, AsyncGenerator):
async for line in res:
yield process_line(form_data, line)
if isinstance(res, str) or isinstance(res, Generator):
finish_message = openai_chat_chunk_message_template(
form_data["model"], ""
)
finish_message["choices"][0]["finish_reason"] = "stop"
yield f"data: {json.dumps(finish_message)}\n\n"
yield "data: [DONE]"
return StreamingResponse(stream_content(), media_type="text/event-stream")
else:
try:
res = await execute_pipe(pipe, params)
except Exception as e:
print(f"Error: {e}")
return {"error": {"detail": str(e)}}
2024-07-31 12:35:02 +00:00
2024-07-31 21:05:37 +00:00
if isinstance(res, StreamingResponse) or isinstance(res, dict):
return res
if isinstance(res, BaseModel):
return res.model_dump()
2024-06-24 18:17:18 +00:00
2024-07-31 21:05:37 +00:00
message = await get_message_content(res)
return openai_chat_completion_message_template(form_data["model"], message)