diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index f89738b0db..c008a8d15f 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1128,6 +1128,12 @@ WEB_SEARCH_CONFIRMATION_CONTENT = os.getenv( 'Your query will be sent to the configured web search provider.', ) +#################################### +# Tool Call Confirmation +#################################### + +ENABLE_TOOL_CALL_CONFIRMATION = os.getenv('ENABLE_TOOL_CALL_CONFIRMATION', 'False').lower() == 'true' + WEB_SEARCH_ENGINE = os.getenv('WEB_SEARCH_ENGINE', '') BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL = ( @@ -2861,6 +2867,7 @@ DEFAULT_CONFIG = { 'web.search.enable': ENABLE_WEB_SEARCH, 'web.search.confirmation.enable': ENABLE_WEB_SEARCH_CONFIRMATION, 'web.search.confirmation.content': WEB_SEARCH_CONFIRMATION_CONTENT, + 'tool.call.confirmation.enable': ENABLE_TOOL_CALL_CONFIRMATION, 'web.search.engine': WEB_SEARCH_ENGINE, 'web.search.bypass_embedding_and_retrieval': BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL, 'web.search.bypass_web_loader': BYPASS_WEB_SEARCH_WEB_LOADER, diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 82eed2f4d6..959fff8ee8 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1868,6 +1868,7 @@ async def get_app_config(request: Request): 'web.search.enable', 'web.search.confirmation.enable', 'web.search.confirmation.content', + 'tool.call.confirmation.enable', 'code_execution.enable', 'code_interpreter.enable', 'image_generation.enable', @@ -1936,6 +1937,7 @@ async def get_app_config(request: Request): 'enable_web_search': config.get('web.search.enable'), 'enable_web_search_confirmation': config.get('web.search.confirmation.enable'), 'web_search_confirmation_content': config.get('web.search.confirmation.content'), + 'enable_tool_call_confirmation': config.get('tool.call.confirmation.enable'), 'enable_code_execution': config.get('code_execution.enable'), 'enable_code_interpreter': config.get('code_interpreter.enable'), 'enable_image_generation': config.get('image_generation.enable'), diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index b507aa66c5..c2d0af7fea 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -1082,6 +1082,11 @@ async def terminal_event_handler( ) +class _ToolCallDenied(Exception): + """Sentinel exception raised when the user denies a tool call confirmation.""" + pass + + async def chat_completion_tools_handler( request: Request, body: dict, extra_params: dict, user: UserModel, models, tools ) -> tuple[dict, dict]: @@ -1189,6 +1194,26 @@ async def chat_completion_tools_handler( allowed_params = spec.get('parameters', {}).get('properties', {}).keys() tool_function_params = {k: v for k, v in tool_function_params.items() if k in allowed_params} + # Tool call confirmation: ask user before executing + if await Config.get('tool.call.confirmation.enable') and event_caller: + params_display = json.dumps(tool_function_params, indent=2, ensure_ascii=False) + confirmation = await event_caller( + { + 'type': 'confirmation', + 'data': { + 'title': f'Allow tool call: {tool_function_name}?', + 'message': f'The model wants to call **{tool_function_name}** with:\n```json\n{params_display}\n```', + }, + } + ) + if not confirmation: + tool_result = json.dumps({ + 'status': 'denied', + 'message': f'Tool call to {tool_function_name} was denied by user.', + }) + # Skip tool execution; jump to result processing + raise _ToolCallDenied() + if tool.get('direct', False): tool_result = await event_caller( { @@ -1206,6 +1231,8 @@ async def chat_completion_tools_handler( tool_function = tool['callable'] tool_result = await tool_function(**tool_function_params) + except _ToolCallDenied: + pass # tool_result already set with denial message except Exception as e: tool_result = str(e) diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 07df7a3736..f41e3a3bd2 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -294,6 +294,7 @@ type Config = { enable_web_search?: boolean; enable_web_search_confirmation?: boolean; web_search_confirmation_content?: string; + enable_tool_call_confirmation?: boolean; enable_google_drive_integration: boolean; enable_onedrive_integration: boolean; enable_image_generation: boolean;