448 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			448 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
| import time
 | |
| import logging
 | |
| import sys
 | |
| 
 | |
| from aiocache import cached
 | |
| from typing import Any, Optional
 | |
| import random
 | |
| import json
 | |
| import inspect
 | |
| import uuid
 | |
| import asyncio
 | |
| 
 | |
| from fastapi import Request, status
 | |
| from starlette.responses import Response, StreamingResponse, JSONResponse
 | |
| 
 | |
| 
 | |
| from open_webui.models.users import UserModel
 | |
| 
 | |
| from open_webui.socket.main import (
 | |
|     sio,
 | |
|     get_event_call,
 | |
|     get_event_emitter,
 | |
| )
 | |
| from open_webui.functions import generate_function_chat_completion
 | |
| 
 | |
| from open_webui.routers.openai import (
 | |
|     generate_chat_completion as generate_openai_chat_completion,
 | |
| )
 | |
| 
 | |
| from open_webui.routers.ollama import (
 | |
|     generate_chat_completion as generate_ollama_chat_completion,
 | |
| )
 | |
| 
 | |
| from open_webui.routers.pipelines import (
 | |
|     process_pipeline_inlet_filter,
 | |
|     process_pipeline_outlet_filter,
 | |
| )
 | |
| 
 | |
| from open_webui.models.functions import Functions
 | |
| from open_webui.models.models import Models
 | |
| 
 | |
| 
 | |
| from open_webui.utils.plugin import load_function_module_by_id
 | |
| from open_webui.utils.models import get_all_models, check_model_access
 | |
| from open_webui.utils.payload import convert_payload_openai_to_ollama
 | |
| from open_webui.utils.response import (
 | |
|     convert_response_ollama_to_openai,
 | |
|     convert_streaming_response_ollama_to_openai,
 | |
| )
 | |
| from open_webui.utils.filter import (
 | |
|     get_sorted_filter_ids,
 | |
|     process_filter_functions,
 | |
| )
 | |
| 
 | |
| from open_webui.env import SRC_LOG_LEVELS, GLOBAL_LOG_LEVEL, BYPASS_MODEL_ACCESS_CONTROL
 | |
| 
 | |
| 
 | |
| logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
 | |
| log = logging.getLogger(__name__)
 | |
| log.setLevel(SRC_LOG_LEVELS["MAIN"])
 | |
| 
 | |
| 
 | |
| async def generate_direct_chat_completion(
 | |
|     request: Request,
 | |
|     form_data: dict,
 | |
|     user: Any,
 | |
|     models: dict,
 | |
| ):
 | |
|     log.info("generate_direct_chat_completion")
 | |
| 
 | |
|     metadata = form_data.pop("metadata", {})
 | |
| 
 | |
|     user_id = metadata.get("user_id")
 | |
|     session_id = metadata.get("session_id")
 | |
|     request_id = str(uuid.uuid4())  # Generate a unique request ID
 | |
| 
 | |
|     event_caller = get_event_call(metadata)
 | |
| 
 | |
|     channel = f"{user_id}:{session_id}:{request_id}"
 | |
| 
 | |
|     if form_data.get("stream"):
 | |
|         q = asyncio.Queue()
 | |
| 
 | |
|         async def message_listener(sid, data):
 | |
|             """
 | |
|             Handle received socket messages and push them into the queue.
 | |
|             """
 | |
|             await q.put(data)
 | |
| 
 | |
|         # Register the listener
 | |
|         sio.on(channel, message_listener)
 | |
| 
 | |
|         # Start processing chat completion in background
 | |
|         res = await event_caller(
 | |
|             {
 | |
|                 "type": "request:chat:completion",
 | |
|                 "data": {
 | |
|                     "form_data": form_data,
 | |
|                     "model": models[form_data["model"]],
 | |
|                     "channel": channel,
 | |
|                     "session_id": session_id,
 | |
|                 },
 | |
|             }
 | |
|         )
 | |
| 
 | |
|         log.info(f"res: {res}")
 | |
| 
 | |
|         if res.get("status", False):
 | |
|             # Define a generator to stream responses
 | |
|             async def event_generator():
 | |
|                 nonlocal q
 | |
|                 try:
 | |
|                     while True:
 | |
|                         data = await q.get()  # Wait for new messages
 | |
|                         if isinstance(data, dict):
 | |
|                             if "done" in data and data["done"]:
 | |
|                                 break  # Stop streaming when 'done' is received
 | |
| 
 | |
|                             yield f"data: {json.dumps(data)}\n\n"
 | |
|                         elif isinstance(data, str):
 | |
|                             yield data
 | |
|                 except Exception as e:
 | |
|                     log.debug(f"Error in event generator: {e}")
 | |
|                     pass
 | |
| 
 | |
|             # Define a background task to run the event generator
 | |
|             async def background():
 | |
|                 try:
 | |
|                     del sio.handlers["/"][channel]
 | |
|                 except Exception as e:
 | |
|                     pass
 | |
| 
 | |
|             # Return the streaming response
 | |
|             return StreamingResponse(
 | |
|                 event_generator(), media_type="text/event-stream", background=background
 | |
|             )
 | |
|         else:
 | |
|             raise Exception(str(res))
 | |
|     else:
 | |
|         res = await event_caller(
 | |
|             {
 | |
|                 "type": "request:chat:completion",
 | |
|                 "data": {
 | |
|                     "form_data": form_data,
 | |
|                     "model": models[form_data["model"]],
 | |
|                     "channel": channel,
 | |
|                     "session_id": session_id,
 | |
|                 },
 | |
|             }
 | |
|         )
 | |
| 
 | |
|         if "error" in res and res["error"]:
 | |
|             raise Exception(res["error"])
 | |
| 
 | |
|         return res
 | |
| 
 | |
| 
 | |
| async def generate_chat_completion(
 | |
|     request: Request,
 | |
|     form_data: dict,
 | |
|     user: Any,
 | |
|     bypass_filter: bool = False,
 | |
| ):
 | |
|     log.debug(f"generate_chat_completion: {form_data}")
 | |
|     if BYPASS_MODEL_ACCESS_CONTROL:
 | |
|         bypass_filter = True
 | |
| 
 | |
|     if hasattr(request.state, "metadata"):
 | |
|         if "metadata" not in form_data:
 | |
|             form_data["metadata"] = request.state.metadata
 | |
|         else:
 | |
|             form_data["metadata"] = {
 | |
|                 **form_data["metadata"],
 | |
|                 **request.state.metadata,
 | |
|             }
 | |
| 
 | |
|     if getattr(request.state, "direct", False) and hasattr(request.state, "model"):
 | |
|         models = {
 | |
|             request.state.model["id"]: request.state.model,
 | |
|         }
 | |
|         log.debug(f"direct connection to model: {models}")
 | |
|     else:
 | |
|         models = request.app.state.MODELS
 | |
| 
 | |
|     model_id = form_data["model"]
 | |
|     if model_id not in models:
 | |
|         raise Exception("Model not found")
 | |
| 
 | |
|     model = models[model_id]
 | |
| 
 | |
|     if getattr(request.state, "direct", False):
 | |
|         return await generate_direct_chat_completion(
 | |
|             request, form_data, user=user, models=models
 | |
|         )
 | |
|     else:
 | |
|         # Check if user has access to the model
 | |
|         if not bypass_filter and user.role == "user":
 | |
|             try:
 | |
|                 check_model_access(user, model)
 | |
|             except Exception as e:
 | |
|                 raise e
 | |
| 
 | |
|         if model.get("owned_by") == "arena":
 | |
|             model_ids = model.get("info", {}).get("meta", {}).get("model_ids")
 | |
|             filter_mode = model.get("info", {}).get("meta", {}).get("filter_mode")
 | |
|             if model_ids and filter_mode == "exclude":
 | |
|                 model_ids = [
 | |
|                     model["id"]
 | |
|                     for model in list(request.app.state.MODELS.values())
 | |
|                     if model.get("owned_by") != "arena" and model["id"] not in model_ids
 | |
|                 ]
 | |
| 
 | |
|             selected_model_id = None
 | |
|             if isinstance(model_ids, list) and model_ids:
 | |
|                 selected_model_id = random.choice(model_ids)
 | |
|             else:
 | |
|                 model_ids = [
 | |
|                     model["id"]
 | |
|                     for model in list(request.app.state.MODELS.values())
 | |
|                     if model.get("owned_by") != "arena"
 | |
|                 ]
 | |
|                 selected_model_id = random.choice(model_ids)
 | |
| 
 | |
|             form_data["model"] = selected_model_id
 | |
| 
 | |
|             if form_data.get("stream") == True:
 | |
| 
 | |
|                 async def stream_wrapper(stream):
 | |
|                     yield f"data: {json.dumps({'selected_model_id': selected_model_id})}\n\n"
 | |
|                     async for chunk in stream:
 | |
|                         yield chunk
 | |
| 
 | |
|                 response = await generate_chat_completion(
 | |
|                     request, form_data, user, bypass_filter=True
 | |
|                 )
 | |
|                 return StreamingResponse(
 | |
|                     stream_wrapper(response.body_iterator),
 | |
|                     media_type="text/event-stream",
 | |
|                     background=response.background,
 | |
|                 )
 | |
|             else:
 | |
|                 return {
 | |
|                     **(
 | |
|                         await generate_chat_completion(
 | |
|                             request, form_data, user, bypass_filter=True
 | |
|                         )
 | |
|                     ),
 | |
|                     "selected_model_id": selected_model_id,
 | |
|                 }
 | |
| 
 | |
|         if model.get("pipe"):
 | |
|             # Below does not require bypass_filter because this is the only route the uses this function and it is already bypassing the filter
 | |
|             return await generate_function_chat_completion(
 | |
|                 request, form_data, user=user, models=models
 | |
|             )
 | |
|         if model.get("owned_by") == "ollama":
 | |
|             # Using /ollama/api/chat endpoint
 | |
|             form_data = convert_payload_openai_to_ollama(form_data)
 | |
|             response = await generate_ollama_chat_completion(
 | |
|                 request=request,
 | |
|                 form_data=form_data,
 | |
|                 user=user,
 | |
|                 bypass_filter=bypass_filter,
 | |
|             )
 | |
|             if form_data.get("stream"):
 | |
|                 response.headers["content-type"] = "text/event-stream"
 | |
|                 return StreamingResponse(
 | |
|                     convert_streaming_response_ollama_to_openai(response),
 | |
|                     headers=dict(response.headers),
 | |
|                     background=response.background,
 | |
|                 )
 | |
|             else:
 | |
|                 return convert_response_ollama_to_openai(response)
 | |
|         else:
 | |
|             return await generate_openai_chat_completion(
 | |
|                 request=request,
 | |
|                 form_data=form_data,
 | |
|                 user=user,
 | |
|                 bypass_filter=bypass_filter,
 | |
|             )
 | |
| 
 | |
| 
 | |
| chat_completion = generate_chat_completion
 | |
| 
 | |
| 
 | |
| async def chat_completed(request: Request, form_data: dict, user: Any):
 | |
|     if not request.app.state.MODELS:
 | |
|         await get_all_models(request, user=user)
 | |
| 
 | |
|     if getattr(request.state, "direct", False) and hasattr(request.state, "model"):
 | |
|         models = {
 | |
|             request.state.model["id"]: request.state.model,
 | |
|         }
 | |
|     else:
 | |
|         models = request.app.state.MODELS
 | |
| 
 | |
|     data = form_data
 | |
|     model_id = data["model"]
 | |
|     if model_id not in models:
 | |
|         raise Exception("Model not found")
 | |
| 
 | |
|     model = models[model_id]
 | |
| 
 | |
|     try:
 | |
|         data = await process_pipeline_outlet_filter(request, data, user, models)
 | |
|     except Exception as e:
 | |
|         return Exception(f"Error: {e}")
 | |
| 
 | |
|     metadata = {
 | |
|         "chat_id": data["chat_id"],
 | |
|         "message_id": data["id"],
 | |
|         "session_id": data["session_id"],
 | |
|         "user_id": user.id,
 | |
|     }
 | |
| 
 | |
|     extra_params = {
 | |
|         "__event_emitter__": get_event_emitter(metadata),
 | |
|         "__event_call__": get_event_call(metadata),
 | |
|         "__user__": {
 | |
|             "id": user.id,
 | |
|             "email": user.email,
 | |
|             "name": user.name,
 | |
|             "role": user.role,
 | |
|         },
 | |
|         "__metadata__": metadata,
 | |
|         "__request__": request,
 | |
|         "__model__": model,
 | |
|     }
 | |
| 
 | |
|     try:
 | |
|         result, _ = await process_filter_functions(
 | |
|             request=request,
 | |
|             filter_ids=get_sorted_filter_ids(model),
 | |
|             filter_type="outlet",
 | |
|             form_data=data,
 | |
|             extra_params=extra_params,
 | |
|         )
 | |
|         return result
 | |
|     except Exception as e:
 | |
|         return Exception(f"Error: {e}")
 | |
| 
 | |
| 
 | |
| async def chat_action(request: Request, action_id: str, form_data: dict, user: Any):
 | |
|     if "." in action_id:
 | |
|         action_id, sub_action_id = action_id.split(".")
 | |
|     else:
 | |
|         sub_action_id = None
 | |
| 
 | |
|     action = Functions.get_function_by_id(action_id)
 | |
|     if not action:
 | |
|         raise Exception(f"Action not found: {action_id}")
 | |
| 
 | |
|     if not request.app.state.MODELS:
 | |
|         await get_all_models(request, user=user)
 | |
| 
 | |
|     if getattr(request.state, "direct", False) and hasattr(request.state, "model"):
 | |
|         models = {
 | |
|             request.state.model["id"]: request.state.model,
 | |
|         }
 | |
|     else:
 | |
|         models = request.app.state.MODELS
 | |
| 
 | |
|     data = form_data
 | |
|     model_id = data["model"]
 | |
| 
 | |
|     if model_id not in models:
 | |
|         raise Exception("Model not found")
 | |
|     model = models[model_id]
 | |
| 
 | |
|     __event_emitter__ = get_event_emitter(
 | |
|         {
 | |
|             "chat_id": data["chat_id"],
 | |
|             "message_id": data["id"],
 | |
|             "session_id": data["session_id"],
 | |
|             "user_id": user.id,
 | |
|         }
 | |
|     )
 | |
|     __event_call__ = get_event_call(
 | |
|         {
 | |
|             "chat_id": data["chat_id"],
 | |
|             "message_id": data["id"],
 | |
|             "session_id": data["session_id"],
 | |
|             "user_id": user.id,
 | |
|         }
 | |
|     )
 | |
| 
 | |
|     if action_id in request.app.state.FUNCTIONS:
 | |
|         function_module = request.app.state.FUNCTIONS[action_id]
 | |
|     else:
 | |
|         function_module, _, _ = load_function_module_by_id(action_id)
 | |
|         request.app.state.FUNCTIONS[action_id] = function_module
 | |
| 
 | |
|     if hasattr(function_module, "valves") and hasattr(function_module, "Valves"):
 | |
|         valves = Functions.get_function_valves_by_id(action_id)
 | |
|         function_module.valves = function_module.Valves(**(valves if valves else {}))
 | |
| 
 | |
|     if hasattr(function_module, "action"):
 | |
|         try:
 | |
|             action = function_module.action
 | |
| 
 | |
|             # Get the signature of the function
 | |
|             sig = inspect.signature(action)
 | |
|             params = {"body": data}
 | |
| 
 | |
|             # Extra parameters to be passed to the function
 | |
|             extra_params = {
 | |
|                 "__model__": model,
 | |
|                 "__id__": sub_action_id if sub_action_id is not None else action_id,
 | |
|                 "__event_emitter__": __event_emitter__,
 | |
|                 "__event_call__": __event_call__,
 | |
|                 "__request__": request,
 | |
|             }
 | |
| 
 | |
|             # Add extra params in contained in function signature
 | |
|             for key, value in extra_params.items():
 | |
|                 if key in sig.parameters:
 | |
|                     params[key] = value
 | |
| 
 | |
|             if "__user__" in sig.parameters:
 | |
|                 __user__ = {
 | |
|                     "id": user.id,
 | |
|                     "email": user.email,
 | |
|                     "name": user.name,
 | |
|                     "role": user.role,
 | |
|                 }
 | |
| 
 | |
|                 try:
 | |
|                     if hasattr(function_module, "UserValves"):
 | |
|                         __user__["valves"] = function_module.UserValves(
 | |
|                             **Functions.get_user_valves_by_id_and_user_id(
 | |
|                                 action_id, user.id
 | |
|                             )
 | |
|                         )
 | |
|                 except Exception as e:
 | |
|                     log.exception(f"Failed to get user values: {e}")
 | |
| 
 | |
|                 params = {**params, "__user__": __user__}
 | |
| 
 | |
|             if inspect.iscoroutinefunction(action):
 | |
|                 data = await action(**params)
 | |
|             else:
 | |
|                 data = action(**params)
 | |
| 
 | |
|         except Exception as e:
 | |
|             return Exception(f"Error: {e}")
 | |
| 
 | |
|     return data
 |