Coverage for open_webui/utils/actions.py: 24%
73 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 05:07 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 05:07 +0000
1import inspect
2import logging
3import sys
4from typing import Any
6from fastapi import Request
7from open_webui.env import ENABLE_PLUGINS, GLOBAL_LOG_LEVEL
8from open_webui.models.functions import Functions
9from open_webui.models.users import UserModel
10from open_webui.socket.main import get_event_call, get_event_emitter
11from open_webui.utils.middleware import process_tool_result
12from open_webui.utils.models import check_model_access, get_all_models
13from open_webui.utils.plugin import get_function_module_from_cache
15logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
16log = logging.getLogger(__name__)
19async def chat_action(request: Request, action_id: str, form_data: dict, user: Any):
20 if not ENABLE_PLUGINS: 20 ↛ 21line 20 didn't jump to line 21 because the condition on line 20 was never true
21 raise Exception('Plugins are disabled by ENABLE_PLUGINS=false')
23 if '.' in action_id:
24 action_id, sub_action_id = action_id.split('.')
25 else:
26 sub_action_id = None
28 action = await Functions.get_function_by_id(action_id)
29 if not action: 29 ↛ 32line 29 didn't jump to line 32 because the condition on line 29 was always true
30 raise Exception(f'Action not found: {action_id}')
32 if not request.app.state.MODELS:
33 await get_all_models(request, user=user)
35 if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
36 models = {
37 request.state.model['id']: request.state.model,
38 }
39 else:
40 models = request.app.state.MODELS
42 data = form_data
43 model_id = data['model']
45 if model_id not in models:
46 raise Exception('Model not found')
47 model = models[model_id]
49 # Availability gate — keep this route consistent with the actions a model
50 # actually surfaces to the client. Executing admin-authored Function code is
51 # intended; this only stops a disabled, unassigned, or access-restricted
52 # action from being reached by calling the route with a raw action_id.
53 if action.type != 'action' or not action.is_active:
54 raise Exception(f'Action not available: {action_id}')
56 # Direct connections carry a client-supplied model the caller already owns,
57 # so scope the model-bound checks to server-resolved models.
58 if not getattr(request.state, 'direct', False) and user.role != 'admin':
59 await check_model_access(user, model)
60 # model['actions'] entries are '<function_id>' or '<function_id>.<sub_id>';
61 # the function id is always the prefix.
62 surfaced_action_ids = {item.get('id', '').split('.', 1)[0] for item in model.get('actions', [])}
63 if action_id not in surfaced_action_ids:
64 raise Exception(f'Action not available: {action_id}')
66 __event_emitter__ = await get_event_emitter(
67 {
68 'chat_id': data['chat_id'],
69 'message_id': data['id'],
70 'session_id': data['session_id'],
71 'user_id': user.id,
72 }
73 )
74 __event_call__ = await get_event_call(
75 {
76 'chat_id': data['chat_id'],
77 'message_id': data['id'],
78 'session_id': data['session_id'],
79 'user_id': user.id,
80 }
81 )
83 function_module, _, _ = await get_function_module_from_cache(request, action_id)
85 if hasattr(function_module, 'valves') and hasattr(function_module, 'Valves'):
86 valves = await Functions.get_function_valves_by_id(action_id)
87 function_module.valves = function_module.Valves(**(valves if valves else {}))
89 if hasattr(function_module, 'action'):
90 try:
91 action = function_module.action
93 # Get the signature of the function
94 sig = inspect.signature(action)
95 params = {'body': data}
97 # Extra parameters to be passed to the function
98 extra_params = {
99 '__model__': model,
100 '__id__': sub_action_id if sub_action_id is not None else action_id,
101 '__event_emitter__': __event_emitter__,
102 '__event_call__': __event_call__,
103 '__request__': request,
104 }
106 # Add extra params in contained in function signature
107 for key, value in extra_params.items():
108 if key in sig.parameters:
109 params[key] = value
111 if '__user__' in sig.parameters:
112 __user__ = user.model_dump() if isinstance(user, UserModel) else {}
114 try:
115 if hasattr(function_module, 'UserValves'):
116 __user__['valves'] = function_module.UserValves(
117 **await Functions.get_user_valves_by_id_and_user_id(action_id, user.id)
118 )
119 except Exception as e:
120 log.exception(f'Failed to get user values: {e}')
122 params = {**params, '__user__': __user__}
124 if inspect.iscoroutinefunction(action):
125 data = await action(**params)
126 else:
127 data = action(**params)
129 # Process action result for Rich UI embeds (HTMLResponse, tuple with headers)
130 processed_result, _, action_embeds = await process_tool_result(
131 request,
132 action_id,
133 data,
134 'action',
135 )
137 if action_embeds:
138 await __event_emitter__(
139 {
140 'type': 'embeds',
141 'data': {
142 'embeds': action_embeds,
143 },
144 }
145 )
146 # Replace data with the processed status dict so we don't
147 # try to serialize the raw HTMLResponse / tuple back to the client
148 data = processed_result
150 except Exception as e:
151 raise Exception(f'Error: {e}')
153 return data