Coverage for open_webui/events.py: 70%
529 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
1from __future__ import annotations
3import asyncio
4import inspect
5import logging
6import time
7import uuid
8from types import SimpleNamespace
9from typing import Any
11from open_webui.env import ENABLE_PLUGINS, VERSION
12from open_webui.models.config import Config
13from pydantic import BaseModel, ConfigDict, Field, model_validator
14from open_webui.retrieval.web.utils import validate_url
15from open_webui.utils.webhook import post_webhook
17log = logging.getLogger(__name__)
19MAX_STRING_LENGTH = 1000
20EVENT_WEBHOOKS_CONFIG_KEY = 'events.webhooks'
21LEGACY_WEBHOOK_CONFIG_KEY = 'webhook_url'
22DEFAULT_WEBHOOK_ID = 'default'
25class EventDefinition(BaseModel):
26 model_config = ConfigDict(frozen=True)
28 name: str
29 description: str | None = None
30 message: str | None = None
32 @model_validator(mode='after')
33 def defaults(self) -> 'EventDefinition':
34 title = self.name.replace('.', ' ').replace('_', ' ').title()
35 if self.description is None: 35 ↛ 36line 35 didn't jump to line 36 because the condition on line 35 was never true
36 object.__setattr__(self, 'description', f'{title}.')
37 if self.message is None: 37 ↛ 38line 37 didn't jump to line 38 because the condition on line 37 was never true
38 object.__setattr__(self, 'message', title)
39 return self
42class EventDefinitions(BaseModel):
43 model_config = ConfigDict(frozen=True)
45 SYSTEM_STARTUP_STARTED: EventDefinition = EventDefinition(
46 name='system.startup.started', description='Application startup began.', message='Startup started'
47 )
48 SYSTEM_STARTUP_COMPLETED: EventDefinition = EventDefinition(
49 name='system.startup.completed', description='Application startup completed.', message='Startup completed'
50 )
51 SYSTEM_SHUTDOWN_STARTED: EventDefinition = EventDefinition(
52 name='system.shutdown.started', description='Application shutdown began.', message='Shutdown started'
53 )
54 SYSTEM_SHUTDOWN_COMPLETED: EventDefinition = EventDefinition(
55 name='system.shutdown.completed', description='Application shutdown completed.', message='Shutdown completed'
56 )
57 CONFIG_IMPORTED: EventDefinition = EventDefinition(
58 name='config.imported', description='Configuration was imported.', message='Config imported'
59 )
60 CONFIG_UPDATED: EventDefinition = EventDefinition(
61 name='config.updated', description='Configuration was updated.', message='Config updated'
62 )
63 CONFIG_WEBHOOK_UPDATED: EventDefinition = EventDefinition(
64 name='config.webhook.updated',
65 description='Event webhook configuration was updated.',
66 message='Webhook configuration updated',
67 )
68 CONFIG_CONNECTIONS_UPDATED: EventDefinition = EventDefinition(
69 name='config.connections.updated',
70 description='Connection configuration was updated.',
71 message='Config Connections updated',
72 )
73 CONFIG_TOOL_SERVERS_UPDATED: EventDefinition = EventDefinition(
74 name='config.tool_servers.updated',
75 description='Tool server configuration was updated.',
76 message='Config Tool Servers updated',
77 )
78 CONFIG_TERMINAL_SERVERS_UPDATED: EventDefinition = EventDefinition(
79 name='config.terminal_servers.updated',
80 description='Terminal server configuration was updated.',
81 message='Config Terminal Servers updated',
82 )
83 CONFIG_CODE_EXECUTION_UPDATED: EventDefinition = EventDefinition(
84 name='config.code_execution.updated',
85 description='Code execution configuration was updated.',
86 message='Config Code Execution updated',
87 )
88 CONFIG_MODELS_UPDATED: EventDefinition = EventDefinition(
89 name='config.models.updated', description='Model configuration was updated.', message='Config Models updated'
90 )
91 CONFIG_BANNERS_UPDATED: EventDefinition = EventDefinition(
92 name='config.banners.updated', description='Banner configuration was updated.', message='Config Banners updated'
93 )
94 CONFIG_SUGGESTIONS_UPDATED: EventDefinition = EventDefinition(
95 name='config.suggestions.updated',
96 description='Suggestion configuration was updated.',
97 message='Config Suggestions updated',
98 )
99 AUTH_SIGNUP: EventDefinition = EventDefinition(
100 name='auth.signup', description='A user account was created through signup.', message='User signed up'
101 )
102 AUTH_LOGIN: EventDefinition = EventDefinition(
103 name='auth.login', description='A user successfully logged in.', message='User logged in'
104 )
105 AUTH_LOGOUT: EventDefinition = EventDefinition(
106 name='auth.logout', description='A user logged out.', message='User logged out'
107 )
108 AUTH_PASSWORD_CHANGED: EventDefinition = EventDefinition(
109 name='auth.password_changed', description='A user password was changed.', message='Password changed'
110 )
111 AUTH_API_KEY_CREATED: EventDefinition = EventDefinition(
112 name='auth.api_key.created', description='A user API key was created.', message='API key created'
113 )
114 AUTH_API_KEY_DELETED: EventDefinition = EventDefinition(
115 name='auth.api_key.deleted', description='A user API key was deleted.', message='API key deleted'
116 )
117 AUTH_OAUTH_SESSION_DELETED: EventDefinition = EventDefinition(
118 name='auth.oauth_session.deleted', description='An OAuth session was deleted.', message='OAuth session deleted'
119 )
120 USER_CREATED: EventDefinition = EventDefinition(
121 name='user.created', description='A user account was created.', message='User created'
122 )
123 USER_UPDATED: EventDefinition = EventDefinition(
124 name='user.updated', description='A user account was updated.', message='User updated'
125 )
126 USER_DELETED: EventDefinition = EventDefinition(
127 name='user.deleted', description='A user account was deleted.', message='User deleted'
128 )
129 USER_ROLE_UPDATED: EventDefinition = EventDefinition(
130 name='user.role_updated', description='A user role was updated.', message='User role updated'
131 )
132 USER_STATUS_UPDATED: EventDefinition = EventDefinition(
133 name='user.status_updated', description='A user status was updated.', message='User status updated'
134 )
135 USER_SETTINGS_UPDATED: EventDefinition = EventDefinition(
136 name='user.settings_updated', description='A user settings object was updated.', message='User settings updated'
137 )
138 USER_PROFILE_UPDATED: EventDefinition = EventDefinition(
139 name='user.profile_updated', description='A user profile was updated.', message='User profile updated'
140 )
141 USER_PERMISSIONS_UPDATED: EventDefinition = EventDefinition(
142 name='user.permissions_updated',
143 description='A user permissions object was updated.',
144 message='User permissions updated',
145 )
146 GROUP_CREATED: EventDefinition = EventDefinition(
147 name='group.created', description='A group was created.', message='Group created'
148 )
149 GROUP_UPDATED: EventDefinition = EventDefinition(
150 name='group.updated', description='A group was updated.', message='Group updated'
151 )
152 GROUP_DELETED: EventDefinition = EventDefinition(
153 name='group.deleted', description='A group was deleted.', message='Group deleted'
154 )
155 GROUP_MEMBER_ADDED: EventDefinition = EventDefinition(
156 name='group.member_added', description='A user was added to a group.', message='Group member added'
157 )
158 GROUP_MEMBER_REMOVED: EventDefinition = EventDefinition(
159 name='group.member_removed', description='A user was removed from a group.', message='Group member removed'
160 )
161 CHAT_CREATED: EventDefinition = EventDefinition(
162 name='chat.created', description='A chat was created.', message='Chat created'
163 )
164 CHAT_FINISHED: EventDefinition = EventDefinition(
165 name='chat.finished', description='A chat response finished.', message='Chat finished'
166 )
167 CHAT_FAILED: EventDefinition = EventDefinition(
168 name='chat.failed', description='A chat response failed.', message='Chat failed'
169 )
170 CHAT_IMPORTED: EventDefinition = EventDefinition(
171 name='chat.imported', description='A chat was imported.', message='Chat imported'
172 )
173 CHAT_UPDATED: EventDefinition = EventDefinition(
174 name='chat.updated', description='A chat was updated.', message='Chat updated'
175 )
176 CHAT_DELETED: EventDefinition = EventDefinition(
177 name='chat.deleted', description='A chat was deleted.', message='Chat deleted'
178 )
179 CHAT_DELETED_ALL: EventDefinition = EventDefinition(
180 name='chat.deleted_all', description='All chats for a scope were deleted.', message='Chat deleted all'
181 )
182 CHAT_COMPACTED: EventDefinition = EventDefinition(
183 name='chat.compacted', description='A chat was compacted.', message='Chat compacted'
184 )
185 CHAT_PINNED: EventDefinition = EventDefinition(
186 name='chat.pinned', description='A chat was pinned.', message='Chat pinned'
187 )
188 CHAT_UNPINNED: EventDefinition = EventDefinition(
189 name='chat.unpinned', description='A chat was unpinned.', message='Chat unpinned'
190 )
191 CHAT_CLONED: EventDefinition = EventDefinition(
192 name='chat.cloned', description='A chat was cloned.', message='Chat cloned'
193 )
194 CHAT_ARCHIVED: EventDefinition = EventDefinition(
195 name='chat.archived', description='A chat was archived.', message='Chat archived'
196 )
197 CHAT_UNARCHIVED: EventDefinition = EventDefinition(
198 name='chat.unarchived', description='A chat was unarchived.', message='Chat unarchived'
199 )
200 CHAT_SHARED: EventDefinition = EventDefinition(
201 name='chat.shared', description='A chat was shared.', message='Chat shared'
202 )
203 CHAT_UNSHARED: EventDefinition = EventDefinition(
204 name='chat.unshared', description='A chat was unshared.', message='Chat unshared'
205 )
206 CHAT_FOLDER_UPDATED: EventDefinition = EventDefinition(
207 name='chat.folder_updated', description='A chat folder assignment was updated.', message='Chat folder updated'
208 )
209 CHAT_TAG_ADDED: EventDefinition = EventDefinition(
210 name='chat.tag_added', description='A tag was added to a chat.', message='Chat tag added'
211 )
212 CHAT_TAG_REMOVED: EventDefinition = EventDefinition(
213 name='chat.tag_removed', description='A tag was removed from a chat.', message='Chat tag removed'
214 )
215 MESSAGE_CREATED: EventDefinition = EventDefinition(
216 name='message.created', description='A message was created.', message='Message created'
217 )
218 MESSAGE_UPDATED: EventDefinition = EventDefinition(
219 name='message.updated', description='A message was updated.', message='Message updated'
220 )
221 MESSAGE_DELETED: EventDefinition = EventDefinition(
222 name='message.deleted', description='A message was deleted.', message='Message deleted'
223 )
224 MESSAGE_EVENT_RECEIVED: EventDefinition = EventDefinition(
225 name='message.event_received',
226 description='A message-level event was received.',
227 message='Message event received',
228 )
229 MESSAGE_REACTION_ADDED: EventDefinition = EventDefinition(
230 name='message.reaction_added',
231 description='A reaction was added to a message.',
232 message='Message reaction added',
233 )
234 MESSAGE_REACTION_REMOVED: EventDefinition = EventDefinition(
235 name='message.reaction_removed',
236 description='A reaction was removed from a message.',
237 message='Message reaction removed',
238 )
239 MESSAGE_PINNED: EventDefinition = EventDefinition(
240 name='message.pinned', description='A message was pinned.', message='Message pinned'
241 )
242 MESSAGE_UNPINNED: EventDefinition = EventDefinition(
243 name='message.unpinned', description='A message was unpinned.', message='Message unpinned'
244 )
245 CHANNEL_CREATED: EventDefinition = EventDefinition(
246 name='channel.created', description='A channel was created.', message='Channel created'
247 )
248 CHANNEL_UPDATED: EventDefinition = EventDefinition(
249 name='channel.updated', description='A channel was updated.', message='Channel updated'
250 )
251 CHANNEL_DELETED: EventDefinition = EventDefinition(
252 name='channel.deleted', description='A channel was deleted.', message='Channel deleted'
253 )
254 CHANNEL_MEMBER_ADDED: EventDefinition = EventDefinition(
255 name='channel.member_added', description='A member was added to a channel.', message='Channel member added'
256 )
257 CHANNEL_MEMBER_REMOVED: EventDefinition = EventDefinition(
258 name='channel.member_removed',
259 description='A member was removed from a channel.',
260 message='Channel member removed',
261 )
262 CHANNEL_MEMBER_ACTIVE_UPDATED: EventDefinition = EventDefinition(
263 name='channel.member_active_updated',
264 description='A channel member active state was updated.',
265 message='Channel member active updated',
266 )
267 CHANNEL_MESSAGE: EventDefinition = EventDefinition(
268 name='channel.message',
269 description='A channel message was posted.',
270 message='Channel message',
271 )
272 CHANNEL_WEBHOOK_CREATED: EventDefinition = EventDefinition(
273 name='channel.webhook.created',
274 description='A channel incoming webhook was created.',
275 message='Channel Webhook created',
276 )
277 CHANNEL_WEBHOOK_UPDATED: EventDefinition = EventDefinition(
278 name='channel.webhook.updated',
279 description='A channel incoming webhook was updated.',
280 message='Channel Webhook updated',
281 )
282 CHANNEL_WEBHOOK_DELETED: EventDefinition = EventDefinition(
283 name='channel.webhook.deleted',
284 description='A channel incoming webhook was deleted.',
285 message='Channel Webhook deleted',
286 )
287 FILE_UPLOADED: EventDefinition = EventDefinition(
288 name='file.uploaded', description='A file was uploaded.', message='File uploaded'
289 )
290 FILE_CONTENT_UPDATED: EventDefinition = EventDefinition(
291 name='file.content_updated', description='File content was updated.', message='File content updated'
292 )
293 FILE_RENAMED: EventDefinition = EventDefinition(
294 name='file.renamed', description='A file was renamed.', message='File renamed'
295 )
296 FILE_DELETED: EventDefinition = EventDefinition(
297 name='file.deleted', description='A file was deleted.', message='File deleted'
298 )
299 FILE_DELETED_ALL: EventDefinition = EventDefinition(
300 name='file.deleted_all', description='All files for a scope were deleted.', message='File deleted all'
301 )
302 FOLDER_CREATED: EventDefinition = EventDefinition(
303 name='folder.created', description='A folder was created.', message='Folder created'
304 )
305 FOLDER_UPDATED: EventDefinition = EventDefinition(
306 name='folder.updated', description='A folder was updated.', message='Folder updated'
307 )
308 FOLDER_PARENT_UPDATED: EventDefinition = EventDefinition(
309 name='folder.parent_updated', description='A folder parent was updated.', message='Folder parent updated'
310 )
311 FOLDER_ACCESS_UPDATED: EventDefinition = EventDefinition(
312 name='folder.access_updated', description='Folder access was updated.', message='Folder access updated'
313 )
314 FOLDER_DELETED: EventDefinition = EventDefinition(
315 name='folder.deleted', description='A folder was deleted.', message='Folder deleted'
316 )
317 NOTE_CREATED: EventDefinition = EventDefinition(
318 name='note.created', description='A note was created.', message='Note created'
319 )
320 NOTE_UPDATED: EventDefinition = EventDefinition(
321 name='note.updated', description='A note was updated.', message='Note updated'
322 )
323 NOTE_ACCESS_UPDATED: EventDefinition = EventDefinition(
324 name='note.access_updated', description='Note access was updated.', message='Note access updated'
325 )
326 NOTE_PINNED: EventDefinition = EventDefinition(
327 name='note.pinned', description='A note was pinned.', message='Note pinned'
328 )
329 NOTE_UNPINNED: EventDefinition = EventDefinition(
330 name='note.unpinned', description='A note was unpinned.', message='Note unpinned'
331 )
332 NOTE_DELETED: EventDefinition = EventDefinition(
333 name='note.deleted', description='A note was deleted.', message='Note deleted'
334 )
335 MEMORY_CREATED: EventDefinition = EventDefinition(
336 name='memory.created', description='A memory was created.', message='Memory created'
337 )
338 MEMORY_UPDATED: EventDefinition = EventDefinition(
339 name='memory.updated', description='A memory was updated.', message='Memory updated'
340 )
341 MEMORY_DELETED: EventDefinition = EventDefinition(
342 name='memory.deleted', description='A memory was deleted.', message='Memory deleted'
343 )
344 MEMORY_RESET: EventDefinition = EventDefinition(
345 name='memory.reset', description='A memory was reset.', message='Memory reset'
346 )
347 KNOWLEDGE_CREATED: EventDefinition = EventDefinition(
348 name='knowledge.created', description='A knowledge was created.', message='Knowledge created'
349 )
350 KNOWLEDGE_UPDATED: EventDefinition = EventDefinition(
351 name='knowledge.updated', description='A knowledge was updated.', message='Knowledge updated'
352 )
353 KNOWLEDGE_DELETED: EventDefinition = EventDefinition(
354 name='knowledge.deleted', description='A knowledge was deleted.', message='Knowledge deleted'
355 )
356 KNOWLEDGE_RESET: EventDefinition = EventDefinition(
357 name='knowledge.reset', description='A knowledge was reset.', message='Knowledge reset'
358 )
359 KNOWLEDGE_REINDEXED: EventDefinition = EventDefinition(
360 name='knowledge.reindexed', description='A knowledge was reindexed.', message='Knowledge reindexed'
361 )
362 KNOWLEDGE_ACCESS_UPDATED: EventDefinition = EventDefinition(
363 name='knowledge.access_updated', description='Knowledge access was updated.', message='Knowledge access updated'
364 )
365 KNOWLEDGE_FILE_ADDED: EventDefinition = EventDefinition(
366 name='knowledge.file.added', description='A file was added to a knowledge base.', message='Knowledge File added'
367 )
368 KNOWLEDGE_FILE_UPDATED: EventDefinition = EventDefinition(
369 name='knowledge.file.updated', description='A knowledge file was updated.', message='Knowledge File updated'
370 )
371 KNOWLEDGE_FILE_REMOVED: EventDefinition = EventDefinition(
372 name='knowledge.file.removed',
373 description='A file was removed from a knowledge base.',
374 message='Knowledge File removed',
375 )
376 KNOWLEDGE_FILE_MOVED: EventDefinition = EventDefinition(
377 name='knowledge.file.moved', description='A knowledge file was moved.', message='Knowledge File moved'
378 )
379 KNOWLEDGE_DIRECTORY_CREATED: EventDefinition = EventDefinition(
380 name='knowledge.directory.created',
381 description='A knowledge directory was created.',
382 message='Knowledge Directory created',
383 )
384 KNOWLEDGE_DIRECTORY_UPDATED: EventDefinition = EventDefinition(
385 name='knowledge.directory.updated',
386 description='A knowledge directory was updated.',
387 message='Knowledge Directory updated',
388 )
389 KNOWLEDGE_DIRECTORY_DELETED: EventDefinition = EventDefinition(
390 name='knowledge.directory.deleted',
391 description='A knowledge directory was deleted.',
392 message='Knowledge Directory deleted',
393 )
394 KNOWLEDGE_EXTERNAL_CONNECTION_CREATED: EventDefinition = EventDefinition(
395 name='knowledge.external_connection.created',
396 description='A knowledge external connection was created.',
397 message='Knowledge External Connection created',
398 )
399 KNOWLEDGE_EXTERNAL_CONNECTION_UPDATED: EventDefinition = EventDefinition(
400 name='knowledge.external_connection.updated',
401 description='A knowledge external connection was updated.',
402 message='Knowledge External Connection updated',
403 )
404 KNOWLEDGE_EXTERNAL_CONNECTION_DELETED: EventDefinition = EventDefinition(
405 name='knowledge.external_connection.deleted',
406 description='A knowledge external connection was deleted.',
407 message='Knowledge External Connection deleted',
408 )
409 RETRIEVAL_CONTENT_PROCESSED: EventDefinition = EventDefinition(
410 name='retrieval.content.processed',
411 description='Retrieval content was processed.',
412 message='Retrieval Content processed',
413 )
414 RETRIEVAL_CONTENT_PROCESS_FAILED: EventDefinition = EventDefinition(
415 name='retrieval.content.process_failed',
416 description='Retrieval content processing failed.',
417 message='Retrieval Content process failed',
418 )
419 RETRIEVAL_COLLECTION_DELETED: EventDefinition = EventDefinition(
420 name='retrieval.collection.deleted',
421 description='A retrieval collection was deleted.',
422 message='Retrieval Collection deleted',
423 )
424 RETRIEVAL_VECTOR_DB_RESET: EventDefinition = EventDefinition(
425 name='retrieval.vector_db.reset',
426 description='The retrieval vector database was reset.',
427 message='Retrieval Vector Db reset',
428 )
429 RETRIEVAL_UPLOADS_RESET: EventDefinition = EventDefinition(
430 name='retrieval.uploads.reset', description='Retrieval uploads were reset.', message='Retrieval Uploads reset'
431 )
432 MODEL_CREATED: EventDefinition = EventDefinition(
433 name='model.created', description='A model was created.', message='Model created'
434 )
435 MODEL_IMPORTED: EventDefinition = EventDefinition(
436 name='model.imported', description='A model was imported.', message='Model imported'
437 )
438 MODEL_SYNCED: EventDefinition = EventDefinition(
439 name='model.synced', description='A model was synced.', message='Model synced'
440 )
441 MODEL_UPDATED: EventDefinition = EventDefinition(
442 name='model.updated', description='A model was updated.', message='Model updated'
443 )
444 MODEL_DELETED: EventDefinition = EventDefinition(
445 name='model.deleted', description='A model was deleted.', message='Model deleted'
446 )
447 MODEL_ENABLED: EventDefinition = EventDefinition(
448 name='model.enabled', description='A model was enabled.', message='Model enabled'
449 )
450 MODEL_DISABLED: EventDefinition = EventDefinition(
451 name='model.disabled', description='A model was disabled.', message='Model disabled'
452 )
453 MODEL_ACCESS_UPDATED: EventDefinition = EventDefinition(
454 name='model.access_updated', description='Model access was updated.', message='Model access updated'
455 )
456 MODEL_PROVIDER_CONFIG_UPDATED: EventDefinition = EventDefinition(
457 name='model.provider_config.updated',
458 description='Model provider configuration was updated.',
459 message='Model Provider Config updated',
460 )
461 MODEL_PROVIDER_REQUEST_FAILED: EventDefinition = EventDefinition(
462 name='model.provider_request.failed',
463 description='A model provider request failed.',
464 message='Model provider request failed',
465 )
466 MODEL_PROVIDER_MODEL_CREATED: EventDefinition = EventDefinition(
467 name='model.provider_model.created',
468 description='A provider model was created.',
469 message='Provider model created',
470 )
471 MODEL_PROVIDER_MODEL_DELETED: EventDefinition = EventDefinition(
472 name='model.provider_model.deleted',
473 description='A provider model was deleted.',
474 message='Provider model deleted',
475 )
476 FUNCTION_CREATED: EventDefinition = EventDefinition(
477 name='function.created', description='A function was created.', message='Function created'
478 )
479 FUNCTION_UPDATED: EventDefinition = EventDefinition(
480 name='function.updated', description='A function was updated.', message='Function updated'
481 )
482 FUNCTION_DELETED: EventDefinition = EventDefinition(
483 name='function.deleted', description='A function was deleted.', message='Function deleted'
484 )
485 FUNCTION_ENABLED: EventDefinition = EventDefinition(
486 name='function.enabled', description='A function was enabled.', message='Function enabled'
487 )
488 FUNCTION_DISABLED: EventDefinition = EventDefinition(
489 name='function.disabled', description='A function was disabled.', message='Function disabled'
490 )
491 FUNCTION_ENABLE_STARTED: EventDefinition = EventDefinition(
492 name='function.enable_started',
493 description='A function is about to be enabled.',
494 message='Function enable started',
495 )
496 FUNCTION_DISABLE_STARTED: EventDefinition = EventDefinition(
497 name='function.disable_started',
498 description='A function is about to be disabled.',
499 message='Function disable started',
500 )
501 FUNCTION_VALVES_UPDATED: EventDefinition = EventDefinition(
502 name='function.valves_updated', description='Function valves were updated.', message='Function valves updated'
503 )
504 TOOL_CREATED: EventDefinition = EventDefinition(
505 name='tool.created', description='A tool was created.', message='Tool created'
506 )
507 TOOL_UPDATED: EventDefinition = EventDefinition(
508 name='tool.updated', description='A tool was updated.', message='Tool updated'
509 )
510 TOOL_DELETED: EventDefinition = EventDefinition(
511 name='tool.deleted', description='A tool was deleted.', message='Tool deleted'
512 )
513 TOOL_ACCESS_UPDATED: EventDefinition = EventDefinition(
514 name='tool.access_updated', description='Tool access was updated.', message='Tool access updated'
515 )
516 TOOL_VALVES_UPDATED: EventDefinition = EventDefinition(
517 name='tool.valves_updated', description='Tool valves were updated.', message='Tool valves updated'
518 )
519 SKILL_CREATED: EventDefinition = EventDefinition(
520 name='skill.created', description='A skill was created.', message='Skill created'
521 )
522 SKILL_UPDATED: EventDefinition = EventDefinition(
523 name='skill.updated', description='A skill was updated.', message='Skill updated'
524 )
525 SKILL_DELETED: EventDefinition = EventDefinition(
526 name='skill.deleted', description='A skill was deleted.', message='Skill deleted'
527 )
528 SKILL_ENABLED: EventDefinition = EventDefinition(
529 name='skill.enabled', description='A skill was enabled.', message='Skill enabled'
530 )
531 SKILL_DISABLED: EventDefinition = EventDefinition(
532 name='skill.disabled', description='A skill was disabled.', message='Skill disabled'
533 )
534 PROMPT_CREATED: EventDefinition = EventDefinition(
535 name='prompt.created', description='A prompt was created.', message='Prompt created'
536 )
537 PROMPT_UPDATED: EventDefinition = EventDefinition(
538 name='prompt.updated', description='A prompt was updated.', message='Prompt updated'
539 )
540 PROMPT_DELETED: EventDefinition = EventDefinition(
541 name='prompt.deleted', description='A prompt was deleted.', message='Prompt deleted'
542 )
543 PROMPT_ENABLED: EventDefinition = EventDefinition(
544 name='prompt.enabled', description='A prompt was enabled.', message='Prompt enabled'
545 )
546 PROMPT_DISABLED: EventDefinition = EventDefinition(
547 name='prompt.disabled', description='A prompt was disabled.', message='Prompt disabled'
548 )
549 PROMPT_VERSION_UPDATED: EventDefinition = EventDefinition(
550 name='prompt.version_updated', description='A prompt version was updated.', message='Prompt version updated'
551 )
552 PROMPT_ACCESS_UPDATED: EventDefinition = EventDefinition(
553 name='prompt.access_updated', description='Prompt access was updated.', message='Prompt access updated'
554 )
555 PIPELINE_UPLOADED: EventDefinition = EventDefinition(
556 name='pipeline.uploaded', description='A pipeline was uploaded.', message='Pipeline uploaded'
557 )
558 PIPELINE_ADDED: EventDefinition = EventDefinition(
559 name='pipeline.added', description='A pipeline was added.', message='Pipeline added'
560 )
561 PIPELINE_DELETED: EventDefinition = EventDefinition(
562 name='pipeline.deleted', description='A pipeline was deleted.', message='Pipeline deleted'
563 )
564 PIPELINE_VALVES_UPDATED: EventDefinition = EventDefinition(
565 name='pipeline.valves_updated', description='Pipeline valves were updated.', message='Pipeline valves updated'
566 )
567 CALENDAR_CREATED: EventDefinition = EventDefinition(
568 name='calendar.created', description='A calendar was created.', message='Calendar created'
569 )
570 CALENDAR_UPDATED: EventDefinition = EventDefinition(
571 name='calendar.updated', description='A calendar was updated.', message='Calendar updated'
572 )
573 CALENDAR_DELETED: EventDefinition = EventDefinition(
574 name='calendar.deleted', description='A calendar was deleted.', message='Calendar deleted'
575 )
576 CALENDAR_DEFAULT_UPDATED: EventDefinition = EventDefinition(
577 name='calendar.default_updated',
578 description='The default calendar was updated.',
579 message='Calendar default updated',
580 )
581 CALENDAR_EVENT_CREATED: EventDefinition = EventDefinition(
582 name='calendar.event.created', description='A calendar event was created.', message='Calendar Event created'
583 )
584 CALENDAR_EVENT_UPDATED: EventDefinition = EventDefinition(
585 name='calendar.event.updated', description='A calendar event was updated.', message='Calendar Event updated'
586 )
587 CALENDAR_EVENT_DELETED: EventDefinition = EventDefinition(
588 name='calendar.event.deleted', description='A calendar event was deleted.', message='Calendar Event deleted'
589 )
590 CALENDAR_EVENT_RSVP_UPDATED: EventDefinition = EventDefinition(
591 name='calendar.event.rsvp_updated',
592 description='A calendar event RSVP was updated.',
593 message='Calendar Event rsvp updated',
594 )
595 CALENDAR_ALERT: EventDefinition = EventDefinition(
596 name='calendar.alert',
597 description='A calendar event alert was triggered.',
598 message='Calendar alert',
599 )
600 AUTOMATION_CREATED: EventDefinition = EventDefinition(
601 name='automation.created', description='An automation was created.', message='Automation created'
602 )
603 AUTOMATION_UPDATED: EventDefinition = EventDefinition(
604 name='automation.updated', description='An automation was updated.', message='Automation updated'
605 )
606 AUTOMATION_ENABLED: EventDefinition = EventDefinition(
607 name='automation.enabled', description='An automation was enabled.', message='Automation enabled'
608 )
609 AUTOMATION_DISABLED: EventDefinition = EventDefinition(
610 name='automation.disabled', description='An automation was disabled.', message='Automation disabled'
611 )
612 AUTOMATION_DELETED: EventDefinition = EventDefinition(
613 name='automation.deleted', description='An automation was deleted.', message='Automation deleted'
614 )
615 AUTOMATION_RUN_STARTED: EventDefinition = EventDefinition(
616 name='automation.run_started', description='An automation run started.', message='Automation run started'
617 )
618 AUTOMATION_RUN_COMPLETED: EventDefinition = EventDefinition(
619 name='automation.run_completed', description='An automation run completed.', message='Automation run completed'
620 )
621 AUTOMATION_RUN_FAILED: EventDefinition = EventDefinition(
622 name='automation.run_failed', description='An automation run failed.', message='Automation run failed'
623 )
624 FEEDBACK_CREATED: EventDefinition = EventDefinition(
625 name='feedback.created', description='A feedback was created.', message='Feedback created'
626 )
627 FEEDBACK_UPDATED: EventDefinition = EventDefinition(
628 name='feedback.updated', description='A feedback was updated.', message='Feedback updated'
629 )
630 FEEDBACK_DELETED: EventDefinition = EventDefinition(
631 name='feedback.deleted', description='A feedback was deleted.', message='Feedback deleted'
632 )
633 FEEDBACK_DELETED_ALL: EventDefinition = EventDefinition(
634 name='feedback.deleted_all', description='All feedback for a scope was deleted.', message='Feedback deleted all'
635 )
636 IMAGE_GENERATED: EventDefinition = EventDefinition(
637 name='image.generated', description='An image was generated.', message='Image generated'
638 )
639 IMAGE_EDITED: EventDefinition = EventDefinition(
640 name='image.edited', description='An image was edited.', message='Image edited'
641 )
642 AUDIO_SPEECH_REQUESTED: EventDefinition = EventDefinition(
643 name='audio.speech_requested', description='Speech generation was requested.', message='Speech requested'
644 )
645 AUDIO_TRANSCRIPTION_REQUESTED: EventDefinition = EventDefinition(
646 name='audio.transcription_requested',
647 description='Audio transcription was requested.',
648 message='Transcription requested',
649 )
650 TERMINAL_SESSION_OPENED: EventDefinition = EventDefinition(
651 name='terminal.session.opened', description='A terminal session was opened.', message='Terminal Session opened'
652 )
653 TERMINAL_SESSION_CLOSED: EventDefinition = EventDefinition(
654 name='terminal.session.closed', description='A terminal session was closed.', message='Terminal Session closed'
655 )
656 NOTIFICATION_TEST: EventDefinition = EventDefinition(
657 name='notification.test', description='A notification target test was sent.', message='Notification test'
658 )
659 NOTIFICATION_MANUAL: EventDefinition = EventDefinition(
660 name='notification.manual', description='A manual notification was sent.', message='Notification sent'
661 )
664EVENTS = EventDefinitions()
665EVENT_DEFINITIONS = tuple(getattr(EVENTS, field_name) for field_name in EventDefinitions.model_fields)
666EVENT_DEFINITIONS_BY_NAME = {definition.name: definition for definition in EVENT_DEFINITIONS}
667EVENT_CATALOG = tuple(definition.name for definition in EVENT_DEFINITIONS)
668EVENT_CATALOG_SET = set(EVENT_CATALOG)
669NOTIFICATION_EVENTS = (
670 EVENTS.CHAT_FINISHED.name,
671 EVENTS.CHAT_FAILED.name,
672 EVENTS.CHANNEL_MESSAGE.name,
673 EVENTS.CALENDAR_ALERT.name,
674 EVENTS.RETRIEVAL_CONTENT_PROCESS_FAILED.name,
675)
678def get_event_catalog() -> list[dict[str, str]]:
679 return [
680 {
681 'event': definition.name,
682 'description': definition.description,
683 'message': definition.message,
684 }
685 for definition in EVENT_DEFINITIONS
686 ]
689SENSITIVE_KEYS = {
690 'password',
691 'hashed_password',
692 'token',
693 'access_token',
694 'refresh_token',
695 'id_token',
696 'api_key',
697 'secret',
698 'key',
699 'authorization',
700 'cookie',
701 'webhook_token',
702}
704SAFE_ACTOR_FIELDS = ('id', 'name', 'email', 'role', 'created_at', 'updated_at')
707def normalize_event_webhook(webhook: dict[str, Any], *, create: bool = False) -> dict[str, Any]:
708 now = int(time.time())
709 webhook_id = str(webhook.get('id') or uuid.uuid4())
710 url = str(webhook.get('url') or '').strip()
712 events = [str(event).strip() for event in (webhook.get('events') or ['*']) if str(event).strip()]
713 events = events or ['*']
714 for event_filter in events:
715 if event_filter == '*':
716 continue
717 if event_filter.endswith('.*'): 717 ↛ 718line 717 didn't jump to line 718 because the condition on line 717 was never true
718 prefix = event_filter[:-2]
719 if prefix and any(event.startswith(f'{prefix}.') for event in EVENT_CATALOG):
720 continue
721 raise ValueError(f'Invalid event pattern: {event_filter}')
722 if event_filter not in EVENT_CATALOG_SET: 722 ↛ 714line 722 didn't jump to line 714 because the condition on line 722 was always true
723 raise ValueError(f'Invalid event: {event_filter}')
725 targets = normalize_event_targets(webhook.get('targets'))
727 return {
728 'id': webhook_id,
729 'name': str(webhook.get('name') or ('Default webhook' if webhook_id == DEFAULT_WEBHOOK_ID else 'Webhook')),
730 'url': url,
731 'enabled': bool(webhook.get('enabled', True)),
732 'events': events,
733 'targets': targets,
734 'created_at': int(webhook.get('created_at') or now),
735 'updated_at': now if create or webhook.get('updated_at') is None else int(webhook.get('updated_at') or now),
736 }
739def normalize_event_targets(targets: Any) -> list[dict[str, str]] | None:
740 if targets is None:
741 return None
742 if not isinstance(targets, list): 742 ↛ 743line 742 didn't jump to line 743 because the condition on line 742 was never true
743 raise ValueError('Invalid targets')
745 normalized = []
746 seen = set()
747 for target in targets:
748 if not isinstance(target, dict): 748 ↛ 749line 748 didn't jump to line 749 because the condition on line 748 was never true
749 raise ValueError('Invalid target')
751 target_type = str(target.get('type') or '').strip()
752 target_id = str(target.get('id') or '').strip()
753 if target_type not in {'user', 'group'} or not target_id: 753 ↛ 756line 753 didn't jump to line 756 because the condition on line 753 was always true
754 raise ValueError('Invalid target')
756 key = (target_type, target_id)
757 if key in seen:
758 continue
760 normalized.append({'type': target_type, 'id': target_id})
761 seen.add(key)
763 return normalized
766def event_filter_matches(webhook: dict[str, Any], event_name: str) -> bool:
767 if not webhook.get('enabled', True):
768 return False
770 for event_filter in webhook.get('events') or ['*']:
771 if event_filter == '*':
772 return True
773 if event_filter.endswith('.*') and event_name.startswith(f'{event_filter[:-2]}.'):
774 return True
775 if event_name == event_filter:
776 return True
777 return False
780def event_user_ids(event: 'Event') -> set[str]:
781 user_ids = set()
782 actor = event.actor or {}
783 subject = event.subject or {}
784 data = event.data or {}
786 if actor.get('id'):
787 user_ids.add(str(actor['id']))
789 if subject.get('type') == 'user' and subject.get('id'):
790 user_ids.add(str(subject['id']))
792 if data.get('user_id'):
793 user_ids.add(str(data['user_id']))
795 for user_id in data.get('user_ids') or []:
796 if user_id:
797 user_ids.add(str(user_id))
799 return user_ids
802async def event_target_matches(
803 targets: list[dict[str, str]] | None,
804 user_ids: set[str],
805 user_group_ids: dict[str, set[str]] | None = None,
806) -> bool:
807 if targets is None:
808 return True
809 if not targets:
810 return not user_ids
811 if not user_ids:
812 return False
814 target_user_ids = {target['id'] for target in targets if target.get('type') == 'user'}
815 if target_user_ids.intersection(user_ids):
816 return True
818 target_group_ids = {target['id'] for target in targets if target.get('type') == 'group'}
819 if not target_group_ids:
820 return False
822 if user_group_ids is None:
823 from open_webui.models.groups import Groups
825 groups_by_user = await Groups.get_groups_by_member_ids(list(user_ids))
826 user_group_ids = {user_id: {group.id for group in groups} for user_id, groups in groups_by_user.items()}
828 return any(group_ids.intersection(target_group_ids) for group_ids in user_group_ids.values())
831async def event_webhook_matches(webhook: dict[str, Any], event: 'Event') -> bool:
832 if not event_filter_matches(webhook, event.event):
833 return False
835 return await event_target_matches(webhook.get('targets'), event_user_ids(event))
838async def get_event_webhooks() -> list[dict[str, Any]]:
839 webhooks = await Config.get(EVENT_WEBHOOKS_CONFIG_KEY, []) or []
840 if not isinstance(webhooks, list): 840 ↛ 841line 840 didn't jump to line 841 because the condition on line 840 was never true
841 return []
843 normalized = []
844 for webhook in webhooks:
845 if not isinstance(webhook, dict): 845 ↛ 846line 845 didn't jump to line 846 because the condition on line 845 was never true
846 continue
847 try:
848 normalized.append(normalize_event_webhook(webhook))
849 except ValueError:
850 log.exception('Invalid event webhook config skipped')
851 return normalized
854async def migrate_legacy_webhook_config() -> list[dict[str, Any]]:
855 webhooks = await get_event_webhooks()
856 if any(webhook.get('id') == DEFAULT_WEBHOOK_ID for webhook in webhooks): 856 ↛ 857line 856 didn't jump to line 857 because the condition on line 856 was never true
857 return webhooks
859 now = int(time.time())
860 legacy_url = await Config.get(LEGACY_WEBHOOK_CONFIG_KEY) or ''
861 if not legacy_url: 861 ↛ 864line 861 didn't jump to line 864 because the condition on line 861 was always true
862 return webhooks
864 webhooks = [
865 {
866 'id': DEFAULT_WEBHOOK_ID,
867 'name': 'Default webhook',
868 'url': legacy_url,
869 'enabled': True,
870 'events': ['*'],
871 'targets': None,
872 'created_at': now,
873 'updated_at': now,
874 },
875 *webhooks,
876 ]
877 await Config.upsert({EVENT_WEBHOOKS_CONFIG_KEY: webhooks})
878 return webhooks
881async def upsert_event_webhook(webhook: dict[str, Any]) -> dict[str, Any]:
882 webhooks = await get_event_webhooks()
883 url = str(webhook.get('url') or '').strip()
884 if url:
885 validate_url(url)
887 normalized = normalize_event_webhook(webhook, create=True)
888 replaced = False
889 next_webhooks = []
891 for existing in webhooks:
892 if existing.get('id') == normalized['id']: 892 ↛ 893line 892 didn't jump to line 893 because the condition on line 892 was never true
893 next_webhooks.append(
894 {
895 **existing,
896 **normalized,
897 'created_at': existing.get('created_at') or normalized['created_at'],
898 }
899 )
900 replaced = True
901 else:
902 next_webhooks.append(existing)
904 if not replaced: 904 ↛ 907line 904 didn't jump to line 907 because the condition on line 904 was always true
905 next_webhooks.append(normalized)
907 await Config.upsert({EVENT_WEBHOOKS_CONFIG_KEY: next_webhooks})
908 return next(webhook for webhook in next_webhooks if webhook.get('id') == normalized['id'])
911async def delete_event_webhook(webhook_id: str) -> bool:
912 webhooks = await get_event_webhooks()
913 next_webhooks = [webhook for webhook in webhooks if webhook.get('id') != webhook_id]
914 if len(next_webhooks) == len(webhooks): 914 ↛ 917line 914 didn't jump to line 917 because the condition on line 914 was always true
915 return False
917 values = {EVENT_WEBHOOKS_CONFIG_KEY: next_webhooks}
918 if webhook_id == DEFAULT_WEBHOOK_ID:
919 values[LEGACY_WEBHOOK_CONFIG_KEY] = ''
921 await Config.upsert(values)
922 return True
925class Event(BaseModel):
926 model_config = ConfigDict(populate_by_name=True)
928 schema_: str = Field(alias='schema')
929 id: str
930 event: str
931 resource: str
932 operation: str
933 created_at: int
934 instance_id: str | None
935 version: str
936 source: str
937 actor: dict[str, Any] | None = None
938 subject: dict[str, Any] | None = None
939 data: dict[str, Any] = Field(default_factory=dict)
940 message: str | None = None
942 def model_dump(self, *args, **kwargs) -> dict[str, Any]:
943 kwargs.setdefault('by_alias', True)
944 return super().model_dump(*args, **kwargs)
947def _sensitive(key: Any) -> bool:
948 normalized = str(key).lower().replace('-', '_')
949 return (
950 normalized in SENSITIVE_KEYS
951 or normalized.endswith('_token')
952 or normalized.endswith('_secret')
953 or normalized.endswith('_api_key')
954 or normalized.endswith('_key')
955 )
958def _sanitize(value: Any) -> Any:
959 if hasattr(value, 'model_dump'):
960 value = value.model_dump()
962 if isinstance(value, dict):
963 return {key: _sanitize(item) for key, item in value.items() if not _sensitive(key)}
965 if isinstance(value, (list, tuple, set)):
966 return [_sanitize(item) for item in value]
968 if isinstance(value, str) and len(value) > MAX_STRING_LENGTH: 968 ↛ 969line 968 didn't jump to line 969 because the condition on line 968 was never true
969 return f'{value[:MAX_STRING_LENGTH]}...'
971 return value
974def _actor(actor: Any | None) -> dict[str, Any] | None:
975 actor = _sanitize(actor)
976 if not actor:
977 return None
979 get = actor.get if isinstance(actor, dict) else lambda key: getattr(actor, key, None)
980 data = {field: get(field) for field in SAFE_ACTOR_FIELDS if get(field) is not None}
981 if not data: 981 ↛ 982line 981 didn't jump to line 982 because the condition on line 981 was never true
982 return None
984 data['type'] = get('type') or 'user'
985 return data
988def event_name(event: EventDefinition | str) -> str:
989 name = event.name if isinstance(event, EventDefinition) else str(event)
990 if name not in EVENT_CATALOG_SET: 990 ↛ 991line 990 didn't jump to line 991 because the condition on line 990 was never true
991 raise ValueError(f'Unknown event: {name}')
992 return name
995def build_event(
996 request_or_app: Any,
997 event: EventDefinition | str,
998 *,
999 actor: Any | None = None,
1000 subject_id: Any | None = None,
1001 subject_type: str | None = None,
1002 source: str = 'api',
1003 data: dict | None = None,
1004 message: str | None = None,
1005) -> Event:
1006 event_name_value = event_name(event)
1007 app = getattr(request_or_app, 'app', request_or_app)
1008 parts = event_name_value.split('.')
1009 resource = '.'.join(parts[:-1])
1010 instance_id = getattr(getattr(app, 'state', None), 'instance_id', None)
1011 subject = (
1012 {'type': subject_type or resource, 'id': subject_id}
1013 if subject_id is not None or subject_type is not None
1014 else None
1015 )
1017 return Event(
1018 schema=VERSION,
1019 id=str(uuid.uuid4()),
1020 event=event_name_value,
1021 resource=resource,
1022 operation=parts[-1],
1023 created_at=int(time.time()),
1024 instance_id=instance_id,
1025 version=VERSION,
1026 source=source,
1027 actor=_actor(actor),
1028 subject=_sanitize(subject) if subject else None,
1029 data=_sanitize(data or {}),
1030 message=message,
1031 )
1034async def dispatch_webhook_event(app: Any, event: Event) -> None:
1035 # LICENSE covers this Open WebUI webhook identifier.
1036 # Do not alter, remove, obscure, or replace it except as LICENSE permits:
1037 # https://docs.openwebui.com/license.
1038 name = getattr(getattr(app, 'state', None), 'WEBUI_NAME', 'Open WebUI')
1039 subject = event.subject or {}
1040 subject_id = subject.get('id')
1041 definition = EVENT_DEFINITIONS_BY_NAME.get(event.event)
1042 message = event.message or (definition.message if definition else event.event)
1043 if subject_id:
1044 message = f'{message} ({subject_id})'
1046 for webhook in await get_event_webhooks():
1047 if not webhook.get('url') or not await event_webhook_matches(webhook, event): 1047 ↛ 1050line 1047 didn't jump to line 1050 because the condition on line 1047 was always true
1048 continue
1050 try:
1051 await post_webhook(
1052 name,
1053 webhook['url'],
1054 message,
1055 event.model_dump(),
1056 description=definition.description if definition else None,
1057 )
1058 except Exception:
1059 log.exception('Event webhook failed for %s', webhook.get('id'))
1062def schedule_webhook_dispatch(app: Any, event: Event) -> None:
1063 try:
1064 asyncio.create_task(dispatch_webhook_event(app, event))
1065 except RuntimeError:
1066 log.exception('Event webhook delivery could not be scheduled for %s', event.event)
1069class WebhookEventSink:
1070 async def handle_event(self, app: Any, event: Event, request: Any | None = None) -> None:
1071 schedule_webhook_dispatch(app, event)
1074def schedule_notification_dispatch(app: Any, event: Event) -> None:
1075 try:
1076 from open_webui.utils.notifications import dispatch_notification_event
1078 asyncio.create_task(dispatch_notification_event(app, event))
1079 except RuntimeError:
1080 log.exception('Notification delivery could not be scheduled for %s', event.event)
1083class NotificationEventSink:
1084 async def handle_event(self, app: Any, event: Event, request: Any | None = None) -> None:
1085 if event.event in NOTIFICATION_EVENTS:
1086 schedule_notification_dispatch(app, event)
1089class SocketSessionEventSink:
1090 async def handle_event(self, app: Any, event: Event, request: Any | None = None) -> None:
1091 if event.event not in {EVENTS.USER_DELETED.name, EVENTS.USER_ROLE_UPDATED.name}:
1092 return
1094 subject = event.subject or {}
1095 if subject.get('type') != 'user' or not subject.get('id'): 1095 ↛ 1096line 1095 didn't jump to line 1096 because the condition on line 1095 was never true
1096 return
1098 from open_webui.socket.main import disconnect_user_sessions
1100 await disconnect_user_sessions(str(subject['id']))
1103async def dispatch_event_functions(
1104 app: Any, event: Event, request: Any | None = None, extra_function_ids: list[str] | None = None
1105) -> None:
1106 if not ENABLE_PLUGINS: 1106 ↛ 1107line 1106 didn't jump to line 1107 because the condition on line 1106 was never true
1107 return
1109 from open_webui.models.functions import Functions
1110 from open_webui.utils.plugin import get_function_module_from_cache
1112 context = request or SimpleNamespace(app=app)
1113 event_payload = event.model_dump()
1115 try:
1116 event_functions = await Functions.get_functions_by_type('event', active_only=True)
1117 if extra_function_ids: 1117 ↛ 1118line 1117 didn't jump to line 1118 because the condition on line 1117 was never true
1118 extra_functions = await Functions.get_functions_by_ids(extra_function_ids)
1119 existing_ids = {function.id for function in event_functions}
1120 event_functions.extend(
1121 function for function in extra_functions if function.type == 'event' and function.id not in existing_ids
1122 )
1123 except Exception:
1124 log.exception('Event functions could not be loaded for %s', event.event)
1125 return
1127 for function in event_functions: 1127 ↛ 1128line 1127 didn't jump to line 1128 because the loop on line 1127 never started
1128 try:
1129 function_module, _, _ = await get_function_module_from_cache(context, function.id, function=function)
1130 handler = getattr(function_module, 'event', None)
1131 if not handler:
1132 continue
1134 if hasattr(function_module, 'valves') and hasattr(function_module, 'Valves'):
1135 valves = await Functions.get_function_valves_by_id(function.id)
1136 function_module.valves = function_module.Valves(**(valves if valves else {}))
1138 sig = inspect.signature(handler)
1139 accepts_kwargs = any(param.kind == inspect.Parameter.VAR_KEYWORD for param in sig.parameters.values())
1140 extra_params = {
1141 'event': event_payload,
1142 '__id__': function.id,
1143 '__event__': event,
1144 '__event_id__': event.id,
1145 '__event_name__': event.event,
1146 '__app__': app,
1147 '__request__': request,
1148 }
1149 params = {key: value for key, value in extra_params.items() if accepts_kwargs or key in sig.parameters}
1151 if inspect.iscoroutinefunction(handler):
1152 await handler(**params)
1153 else:
1154 handler(**params)
1155 except Exception:
1156 log.exception('Event function failed for %s', function.id)
1159def schedule_event_function_dispatch(app: Any, event: Event, request: Any | None = None) -> None:
1160 try:
1161 asyncio.create_task(dispatch_event_functions(app, event, request))
1162 except RuntimeError:
1163 log.exception('Event functions could not be scheduled for %s', event.event)
1166class EventFunctionSink:
1167 async def handle_event(self, app: Any, event: Event, request: Any | None = None) -> None:
1168 schedule_event_function_dispatch(app, event, request)
1171EVENT_SINKS = [SocketSessionEventSink(), EventFunctionSink(), WebhookEventSink(), NotificationEventSink()]
1174async def publish_event(
1175 request_or_app: Any,
1176 event: EventDefinition | str,
1177 *,
1178 actor: Any | None = None,
1179 subject_id: Any | None = None,
1180 subject_type: str | None = None,
1181 source: str = 'api',
1182 data: dict | None = None,
1183 message: str | None = None,
1184) -> None:
1185 app = getattr(request_or_app, 'app', request_or_app)
1186 request = request_or_app if hasattr(request_or_app, 'app') else None
1187 event_payload = build_event(
1188 request_or_app,
1189 event,
1190 actor=actor,
1191 subject_id=subject_id,
1192 subject_type=subject_type,
1193 source=source,
1194 data=data,
1195 message=message,
1196 )
1198 for sink in EVENT_SINKS:
1199 try:
1200 await sink.handle_event(app, event_payload, request=request)
1201 except Exception:
1202 log.exception('Event sink failed for %s', event_payload.event)
1205async def publish_model_provider_request_failed(
1206 request_or_app: Any,
1207 *,
1208 actor: Any | None,
1209 provider: str,
1210 base_url: str,
1211 status: int,
1212 requested_model: str | None = None,
1213 api_key: str | None = None,
1214 upstream_error: Any = None,
1215) -> None:
1216 error = upstream_error.get('error') if isinstance(upstream_error, dict) else upstream_error
1217 error_code = None
1218 if isinstance(error, dict):
1219 error_code = error.get('code') or error.get('type') or error.get('error_code')
1220 error = error.get('message') or error.get('detail') or error
1222 error_text = str(error or '')
1223 marker = f'{error_code or ""} {error_text}'.lower()
1224 error_type = (
1225 'model_not_found'
1226 if status == 404
1227 and any(value in marker for value in ('model_not_found', 'model not found', 'does not exist', 'no such model'))
1228 else 'authentication_failed'
1229 if status in (401, 403)
1230 else 'rate_limited'
1231 if status == 429
1232 else 'server_failed'
1233 if status >= 500
1234 else 'upstream_error'
1235 )
1237 # Server-log only; the upstream error body is otherwise invisible to admins
1238 # (event sinks require an event function or webhook to be configured).
1239 log.log(
1240 logging.ERROR if status >= 500 else logging.WARNING,
1241 'Upstream %s request failed: HTTP %d (%s) url=%s model=%s code=%s message=%s',
1242 provider,
1243 status,
1244 error_type,
1245 base_url,
1246 requested_model or '-',
1247 error_code or '-',
1248 error_text[:MAX_STRING_LENGTH] or '-',
1249 )
1251 data = {
1252 'error_type': error_type,
1253 'status': status,
1254 'provider': provider,
1255 'base_url': base_url,
1256 }
1257 if requested_model: 1257 ↛ 1259line 1257 didn't jump to line 1259 because the condition on line 1257 was always true
1258 data['requested_model'] = requested_model
1259 if api_key:
1260 data['api_key_suffix'] = f'...{api_key[-4:]}'
1261 if error_code: 1261 ↛ 1262line 1261 didn't jump to line 1262 because the condition on line 1261 was never true
1262 data['upstream_error_code'] = error_code
1263 if error: 1263 ↛ anywhereline 1263 didn't jump anywhere: it always raised an exception.
1264 data['upstream_message'] = error
1266 await publish_event(
1267 request_or_app,
1268 EVENTS.MODEL_PROVIDER_REQUEST_FAILED,
1269 actor=actor,
1270 subject_id=requested_model,
1271 subject_type='model',
1272 data=data,
1273 )