diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 80e1e7ab2..beeac535e 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1283,7 +1283,28 @@ TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = PersistentConfig( ) -DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = """Available Tools: {{TOOLS}}\nReturn an empty string if no tools match the query. If a function tool matches, construct and return a JSON object in the format {\"name\": \"functionName\", \"parameters\": {\"requiredFunctionParamKey\": \"requiredFunctionParamValue\"}} using the appropriate tool and its parameters. Only return the object and limit the response to the JSON object without additional text.""" +DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = """Available Tools: {{TOOLS}} + +Your task is to choose and return the correct tool(s) from the list of available tools based on the query. Follow these guidelines: + +- Return only the JSON object, without any additional text or explanation. + +- If no tools match the query, return an empty array: + { + "tool_calls": [] + } + +- If one or more tools match the query, construct a JSON response containing a "tool_calls" array with objects that include: + - "name": The tool's name. + - "parameters": A dictionary of required parameters and their corresponding values. + +The format for the JSON response is strictly: +{ + "tool_calls": [ + {"name": "toolName1", "parameters": {"key1": "value1"}}, + {"name": "toolName2", "parameters": {"key2": "value2"}} + ] +}""" DEFAULT_EMOJI_GENERATION_PROMPT_TEMPLATE = """Your task is to reflect the speaker's likely facial expression through a fitting emoji. Interpret emotions from the message and reflect their facial expression using fitting, diverse emojis (e.g., 😊, 😢, 😡, 😱). @@ -1325,6 +1346,7 @@ CHROMA_HTTP_SSL = os.environ.get("CHROMA_HTTP_SSL", "false").lower() == "true" MILVUS_URI = os.environ.get("MILVUS_URI", f"{DATA_DIR}/vector_db/milvus.db") MILVUS_DB = os.environ.get("MILVUS_DB", "default") +MILVUS_TOKEN = os.environ.get("MILVUS_TOKEN", None) # Qdrant QDRANT_URI = os.environ.get("QDRANT_URI", None) diff --git a/backend/open_webui/retrieval/vector/dbs/milvus.py b/backend/open_webui/retrieval/vector/dbs/milvus.py index bdfa16eb6..43c3f3d1a 100644 --- a/backend/open_webui/retrieval/vector/dbs/milvus.py +++ b/backend/open_webui/retrieval/vector/dbs/milvus.py @@ -8,13 +8,17 @@ from open_webui.retrieval.vector.main import VectorItem, SearchResult, GetResult from open_webui.config import ( MILVUS_URI, MILVUS_DB, + MILVUS_TOKEN, ) class MilvusClient: def __init__(self): self.collection_prefix = "open_webui" - self.client = Client(uri=MILVUS_URI, database=MILVUS_DB) + if MILVUS_TOKEN is None: + self.client = Client(uri=MILVUS_URI, database=MILVUS_DB) + else: + self.client = Client(uri=MILVUS_URI, database=MILVUS_DB, token=MILVUS_TOKEN) def _result_to_get_result(self, result) -> GetResult: ids = [] diff --git a/backend/open_webui/routers/tasks.py b/backend/open_webui/routers/tasks.py index 299ec5326..f56a0232d 100644 --- a/backend/open_webui/routers/tasks.py +++ b/backend/open_webui/routers/tasks.py @@ -90,6 +90,10 @@ async def update_task_config( form_data.TITLE_GENERATION_PROMPT_TEMPLATE ) + request.app.state.config.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = ( + form_data.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE + ) + request.app.state.config.ENABLE_AUTOCOMPLETE_GENERATION = ( form_data.ENABLE_AUTOCOMPLETE_GENERATION ) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 2a68d8d0a..be301ec10 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -270,60 +270,70 @@ async def chat_completion_tools_handler( result = json.loads(content) - tool_function_name = result.get("name", None) - if tool_function_name not in tools: - return body, {} + async def tool_call_handler(tool_call): + log.debug(f"{tool_call=}") - tool_function_params = result.get("parameters", {}) + tool_function_name = tool_call.get("name", None) + if tool_function_name not in tools: + return body, {} - try: - required_params = ( - tools[tool_function_name] - .get("spec", {}) - .get("parameters", {}) - .get("required", []) - ) - tool_function = tools[tool_function_name]["callable"] - tool_function_params = { - k: v - for k, v in tool_function_params.items() - if k in required_params - } - tool_output = await tool_function(**tool_function_params) + tool_function_params = tool_call.get("parameters", {}) - except Exception as e: - tool_output = str(e) - - if isinstance(tool_output, str): - if tools[tool_function_name]["citation"]: - sources.append( - { - "source": { - "name": f"TOOL:{tools[tool_function_name]['toolkit_id']}/{tool_function_name}" - }, - "document": [tool_output], - "metadata": [ - { - "source": f"TOOL:{tools[tool_function_name]['toolkit_id']}/{tool_function_name}" - } - ], - } - ) - else: - sources.append( - { - "source": {}, - "document": [tool_output], - "metadata": [ - { - "source": f"TOOL:{tools[tool_function_name]['toolkit_id']}/{tool_function_name}" - } - ], - } + try: + required_params = ( + tools[tool_function_name] + .get("spec", {}) + .get("parameters", {}) + .get("required", []) ) + tool_function = tools[tool_function_name]["callable"] + tool_function_params = { + k: v + for k, v in tool_function_params.items() + if k in required_params + } + tool_output = await tool_function(**tool_function_params) - if tools[tool_function_name]["file_handler"]: - skip_files = True + except Exception as e: + tool_output = str(e) + + if isinstance(tool_output, str): + if tools[tool_function_name]["citation"]: + sources.append( + { + "source": { + "name": f"TOOL:{tools[tool_function_name]['toolkit_id']}/{tool_function_name}" + }, + "document": [tool_output], + "metadata": [ + { + "source": f"TOOL:{tools[tool_function_name]['toolkit_id']}/{tool_function_name}" + } + ], + } + ) + else: + sources.append( + { + "source": {}, + "document": [tool_output], + "metadata": [ + { + "source": f"TOOL:{tools[tool_function_name]['toolkit_id']}/{tool_function_name}" + } + ], + } + ) + + if tools[tool_function_name]["file_handler"]: + skip_files = True + + # check if "tool_calls" in result + if result.get("tool_calls"): + for tool_call in result.get("tool_calls"): + await tool_call_handler(tool_call) + else: + await tool_call_handler(result) except Exception as e: log.exception(f"Error: {e}") diff --git a/backend/open_webui/utils/misc.py b/backend/open_webui/utils/misc.py index 8792b1cfc..71e1cbbfb 100644 --- a/backend/open_webui/utils/misc.py +++ b/backend/open_webui/utils/misc.py @@ -131,6 +131,25 @@ def add_or_update_system_message(content: str, messages: list[dict]): return messages +def append_or_update_assistant_message(content: str, messages: list[dict]): + """ + Adds a new assistant message at the end of the messages list + or updates the existing assistant message at the end. + + :param msg: The message to be added or appended. + :param messages: The list of message dictionaries. + :return: The updated list of message dictionaries. + """ + + if messages and messages[-1].get("role") == "assistant": + messages[-1]["content"] = f"{messages[-1]['content']}\n{content}" + else: + # Insert at the end + messages.append({"role": "assistant", "content": content}) + + return messages + + def openai_chat_message_template(model: str): return { "id": f"{model}-{str(uuid.uuid4())}", diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index f60e2ff60..7c0c53c2d 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -82,7 +82,8 @@ class OAuthManager: oauth_allowed_roles = auth_manager_config.OAUTH_ALLOWED_ROLES oauth_admin_roles = auth_manager_config.OAUTH_ADMIN_ROLES oauth_roles = None - role = "pending" # Default/fallback role if no matching roles are found + # Default/fallback role if no matching roles are found + role = auth_manager_config.DEFAULT_USER_ROLE # Next block extracts the roles from the user data, accepting nested claims of any depth if oauth_claim and oauth_allowed_roles and oauth_admin_roles: @@ -273,7 +274,7 @@ class OAuthManager: log.error( f"Error downloading profile image '{picture_url}': {e}" ) - picture_url = "" + picture_url = "/user.png" if not picture_url: picture_url = "/user.png" diff --git a/backend/requirements.txt b/backend/requirements.txt index b08c17677..473231710 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -11,7 +11,7 @@ python-jose==3.3.0 passlib[bcrypt]==1.7.4 requests==2.32.3 -aiohttp==3.11.8 +aiohttp==3.11.11 async-timeout aiocache aiofiles @@ -57,7 +57,7 @@ einops==0.8.0 ftfy==6.2.3 pypdf==4.3.1 fpdf2==2.8.2 -pymdown-extensions==10.11.2 +pymdown-extensions==10.14.2 docx2txt==0.8 python-pptx==1.0.0 unstructured==0.16.17 @@ -71,16 +71,16 @@ xlrd==2.0.1 validators==0.34.0 psutil sentencepiece -soundfile==0.12.1 +soundfile==0.13.1 -opencv-python-headless==4.10.0.84 +opencv-python-headless==4.11.0.86 rapidocr-onnxruntime==1.3.24 rank-bm25==0.2.2 faster-whisper==1.0.3 PyJWT[crypto]==2.10.1 -authlib==1.3.2 +authlib==1.4.1 black==24.8.0 langfuse==2.44.0 @@ -89,7 +89,7 @@ pytube==15.0.0 extract_msg pydub -duckduckgo-search~=7.2.1 +duckduckgo-search~=7.3.0 ## Google Drive google-api-python-client diff --git a/pyproject.toml b/pyproject.toml index 6e7f607b3..12ec9f3fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "passlib[bcrypt]==1.7.4", "requests==2.32.3", - "aiohttp==3.11.8", + "aiohttp==3.11.11", "async-timeout", "aiocache", "aiofiles", @@ -63,7 +63,7 @@ dependencies = [ "ftfy==6.2.3", "pypdf==4.3.1", "fpdf2==2.8.2", - "pymdown-extensions==10.11.2", + "pymdown-extensions==10.14.2", "docx2txt==0.8", "python-pptx==1.0.0", "unstructured==0.16.17", @@ -77,16 +77,16 @@ dependencies = [ "validators==0.34.0", "psutil", "sentencepiece", - "soundfile==0.12.1", + "soundfile==0.13.1", - "opencv-python-headless==4.10.0.84", + "opencv-python-headless==4.11.0.86", "rapidocr-onnxruntime==1.3.24", "rank-bm25==0.2.2", "faster-whisper==1.0.3", "PyJWT[crypto]==2.10.1", - "authlib==1.3.2", + "authlib==1.4.1", "black==24.8.0", "langfuse==2.44.0", @@ -95,7 +95,7 @@ dependencies = [ "extract_msg", "pydub", - "duckduckgo-search~=7.2.1", + "duckduckgo-search~=7.3.0", "google-api-python-client", "google-auth-httplib2", diff --git a/src/lib/components/admin/Settings/Interface.svelte b/src/lib/components/admin/Settings/Interface.svelte index 055acbf80..332d02c5a 100644 --- a/src/lib/components/admin/Settings/Interface.svelte +++ b/src/lib/components/admin/Settings/Interface.svelte @@ -31,7 +31,8 @@ ENABLE_TAGS_GENERATION: true, ENABLE_SEARCH_QUERY_GENERATION: true, ENABLE_RETRIEVAL_QUERY_GENERATION: true, - QUERY_GENERATION_PROMPT_TEMPLATE: '' + QUERY_GENERATION_PROMPT_TEMPLATE: '', + TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: '' }; let promptSuggestions = []; @@ -251,6 +252,20 @@ +