| 
									
										
										
										
											2023-11-19 13:41:43 +08:00
										 |  |  | import hashlib | 
					
						
							| 
									
										
										
										
											2024-01-03 08:22:48 +08:00
										 |  |  | import re | 
					
						
							| 
									
										
										
										
											2025-07-07 22:16:19 +08:00
										 |  |  | import threading | 
					
						
							| 
									
										
										
										
											2024-06-20 19:38:59 +08:00
										 |  |  | import time | 
					
						
							| 
									
										
										
										
											2024-08-28 06:10:27 +08:00
										 |  |  | import uuid | 
					
						
							| 
									
										
										
										
											2025-02-25 22:36:25 +08:00
										 |  |  | import logging | 
					
						
							| 
									
										
										
										
											2024-08-28 06:10:27 +08:00
										 |  |  | from datetime import timedelta | 
					
						
							|  |  |  | from pathlib import Path | 
					
						
							|  |  |  | from typing import Callable, Optional | 
					
						
							| 
									
										
										
										
											2025-02-28 15:13:30 +08:00
										 |  |  | import json | 
					
						
							| 
									
										
										
										
											2024-06-09 18:01:25 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-07 10:09:57 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-05 15:05:14 +08:00
										 |  |  | import collections.abc | 
					
						
							| 
									
										
										
										
											2025-02-25 22:36:25 +08:00
										 |  |  | from open_webui.env import SRC_LOG_LEVELS | 
					
						
							| 
									
										
										
										
											2025-02-05 15:05:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-25 22:36:25 +08:00
										 |  |  | log = logging.getLogger(__name__) | 
					
						
							|  |  |  | log.setLevel(SRC_LOG_LEVELS["MAIN"]) | 
					
						
							| 
									
										
										
										
											2025-02-05 15:05:14 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def deep_update(d, u): | 
					
						
							|  |  |  |     for k, v in u.items(): | 
					
						
							|  |  |  |         if isinstance(v, collections.abc.Mapping): | 
					
						
							|  |  |  |             d[k] = deep_update(d.get(k, {}), v) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             d[k] = v | 
					
						
							|  |  |  |     return d | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-14 16:26:46 +08:00
										 |  |  | def get_message_list(messages_map, message_id): | 
					
						
							| 
									
										
										
										
											2024-12-19 17:00:32 +08:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Reconstructs a list of messages in order up to the specified message_id. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param message_id: ID of the message to reconstruct the chain | 
					
						
							|  |  |  |     :param messages: Message history dict containing all messages | 
					
						
							|  |  |  |     :return: List of ordered messages starting from the root to the given message | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-27 05:55:48 +08:00
										 |  |  |     # Handle case where messages is None | 
					
						
							| 
									
										
										
										
											2025-09-14 16:26:46 +08:00
										 |  |  |     if not messages_map: | 
					
						
							| 
									
										
										
										
											2025-05-27 05:55:48 +08:00
										 |  |  |         return []  # Return empty list instead of None to prevent iteration errors | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-19 17:00:32 +08:00
										 |  |  |     # Find the message by its id | 
					
						
							| 
									
										
										
										
											2025-09-14 16:26:46 +08:00
										 |  |  |     current_message = messages_map.get(message_id) | 
					
						
							| 
									
										
										
										
											2024-12-19 17:00:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if not current_message: | 
					
						
							| 
									
										
										
										
											2025-05-27 05:35:09 +08:00
										 |  |  |         return []  # Return empty list instead of None to prevent iteration errors | 
					
						
							| 
									
										
										
										
											2024-12-19 17:00:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Reconstruct the chain by following the parentId links | 
					
						
							|  |  |  |     message_list = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     while current_message: | 
					
						
							|  |  |  |         message_list.insert( | 
					
						
							|  |  |  |             0, current_message | 
					
						
							|  |  |  |         )  # Insert the message at the beginning of the list | 
					
						
							| 
									
										
										
										
											2025-05-27 05:35:09 +08:00
										 |  |  |         parent_id = current_message.get("parentId")  # Use .get() for safety | 
					
						
							| 
									
										
										
										
											2025-09-14 16:26:46 +08:00
										 |  |  |         current_message = messages_map.get(parent_id) if parent_id else None | 
					
						
							| 
									
										
										
										
											2024-12-19 17:00:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return message_list | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-07 10:09:57 +08:00
										 |  |  | def get_messages_content(messages: list[dict]) -> str: | 
					
						
							|  |  |  |     return "\n".join( | 
					
						
							|  |  |  |         [ | 
					
						
							|  |  |  |             f"{message['role'].upper()}: {get_content_from_message(message)}" | 
					
						
							|  |  |  |             for message in messages | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2024-08-03 21:24:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-09 18:01:25 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 20:46:31 +08:00
										 |  |  | def get_last_user_message_item(messages: list[dict]) -> Optional[dict]: | 
					
						
							| 
									
										
										
										
											2024-06-09 18:01:25 +08:00
										 |  |  |     for message in reversed(messages): | 
					
						
							|  |  |  |         if message["role"] == "user": | 
					
						
							| 
									
										
										
										
											2024-07-02 15:37:21 +08:00
										 |  |  |             return message | 
					
						
							|  |  |  |     return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-01 04:58:40 +08:00
										 |  |  | def get_content_from_message(message: dict) -> Optional[str]: | 
					
						
							| 
									
										
										
										
											2025-05-28 05:34:53 +08:00
										 |  |  |     if isinstance(message.get("content"), list): | 
					
						
							| 
									
										
										
										
											2024-08-01 04:58:40 +08:00
										 |  |  |         for item in message["content"]: | 
					
						
							|  |  |  |             if item["type"] == "text": | 
					
						
							|  |  |  |                 return item["text"] | 
					
						
							|  |  |  |     else: | 
					
						
							| 
									
										
										
										
											2025-05-28 05:34:53 +08:00
										 |  |  |         return message.get("content") | 
					
						
							| 
									
										
										
										
											2024-06-09 18:01:25 +08:00
										 |  |  |     return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 20:46:31 +08:00
										 |  |  | def get_last_user_message(messages: list[dict]) -> Optional[str]: | 
					
						
							| 
									
										
										
										
											2024-08-01 04:58:40 +08:00
										 |  |  |     message = get_last_user_message_item(messages) | 
					
						
							|  |  |  |     if message is None: | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  |     return get_content_from_message(message) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-25 14:45:21 +08:00
										 |  |  | def get_last_assistant_message_item(messages: list[dict]) -> Optional[dict]: | 
					
						
							|  |  |  |     for message in reversed(messages): | 
					
						
							|  |  |  |         if message["role"] == "assistant": | 
					
						
							|  |  |  |             return message | 
					
						
							|  |  |  |     return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 20:46:31 +08:00
										 |  |  | def get_last_assistant_message(messages: list[dict]) -> Optional[str]: | 
					
						
							| 
									
										
										
										
											2024-06-09 18:01:25 +08:00
										 |  |  |     for message in reversed(messages): | 
					
						
							|  |  |  |         if message["role"] == "assistant": | 
					
						
							| 
									
										
										
										
											2024-08-01 04:58:40 +08:00
										 |  |  |             return get_content_from_message(message) | 
					
						
							| 
									
										
										
										
											2024-06-09 18:01:25 +08:00
										 |  |  |     return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 20:46:31 +08:00
										 |  |  | def get_system_message(messages: list[dict]) -> Optional[dict]: | 
					
						
							| 
									
										
										
										
											2024-06-18 04:28:29 +08:00
										 |  |  |     for message in messages: | 
					
						
							|  |  |  |         if message["role"] == "system": | 
					
						
							|  |  |  |             return message | 
					
						
							|  |  |  |     return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 20:46:31 +08:00
										 |  |  | def remove_system_message(messages: list[dict]) -> list[dict]: | 
					
						
							| 
									
										
										
										
											2024-06-18 04:28:29 +08:00
										 |  |  |     return [message for message in messages if message["role"] != "system"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-14 20:58:37 +08:00
										 |  |  | def pop_system_message(messages: list[dict]) -> tuple[Optional[dict], list[dict]]: | 
					
						
							| 
									
										
										
										
											2024-06-18 04:28:29 +08:00
										 |  |  |     return get_system_message(messages), remove_system_message(messages) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-24 22:38:14 +08:00
										 |  |  | def update_message_content(message: dict, content: str, append: bool = True) -> dict: | 
					
						
							|  |  |  |     if isinstance(message["content"], list): | 
					
						
							|  |  |  |         for item in message["content"]: | 
					
						
							|  |  |  |             if item["type"] == "text": | 
					
						
							|  |  |  |                 if append: | 
					
						
							|  |  |  |                     item["text"] = f"{item['text']}\n{content}" | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     item["text"] = f"{content}\n{item['text']}" | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         if append: | 
					
						
							|  |  |  |             message["content"] = f"{message['content']}\n{content}" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             message["content"] = f"{content}\n{message['content']}" | 
					
						
							|  |  |  |     return message | 
					
						
							| 
									
										
										
										
											2024-07-26 19:22:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-23 07:26:14 +08:00
										 |  |  | def add_or_update_system_message( | 
					
						
							|  |  |  |     content: str, messages: list[dict], append: bool = False | 
					
						
							|  |  |  | ): | 
					
						
							| 
									
										
										
										
											2024-06-09 18:01:25 +08:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Adds a new system message at the beginning of the messages list | 
					
						
							|  |  |  |     or updates the existing system message at the beginning. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :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[0].get("role") == "system": | 
					
						
							| 
									
										
										
										
											2025-09-24 22:38:14 +08:00
										 |  |  |         messages[0] = update_message_content(messages[0], content, append) | 
					
						
							| 
									
										
										
										
											2024-06-09 18:01:25 +08:00
										 |  |  |     else: | 
					
						
							|  |  |  |         # Insert at the beginning | 
					
						
							|  |  |  |         messages.insert(0, {"role": "system", "content": content}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return messages | 
					
						
							| 
									
										
										
										
											2023-11-19 13:41:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-20 19:38:59 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-24 22:38:14 +08:00
										 |  |  | def add_or_update_user_message(content: str, messages: list[dict], append: bool = True): | 
					
						
							| 
									
										
										
										
											2025-02-03 17:14:38 +08:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Adds a new user message at the end of the messages list | 
					
						
							|  |  |  |     or updates the existing user 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") == "user": | 
					
						
							| 
									
										
										
										
											2025-09-24 22:38:14 +08:00
										 |  |  |         messages[-1] = update_message_content(messages[-1], content, append) | 
					
						
							| 
									
										
										
										
											2025-02-03 17:14:38 +08:00
										 |  |  |     else: | 
					
						
							|  |  |  |         # Insert at the end | 
					
						
							|  |  |  |         messages.append({"role": "user", "content": content}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return messages | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-24 22:38:14 +08:00
										 |  |  | def prepend_to_first_user_message_content( | 
					
						
							|  |  |  |     content: str, messages: list[dict] | 
					
						
							|  |  |  | ) -> list[dict]: | 
					
						
							|  |  |  |     for message in messages: | 
					
						
							|  |  |  |         if message["role"] == "user": | 
					
						
							|  |  |  |             message = update_message_content(message, content, append=False) | 
					
						
							|  |  |  |             break | 
					
						
							|  |  |  |     return messages | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-03 07:11:50 +08:00
										 |  |  | 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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-01 05:01:22 +08:00
										 |  |  | def openai_chat_message_template(model: str): | 
					
						
							| 
									
										
										
										
											2024-06-20 19:38:59 +08:00
										 |  |  |     return { | 
					
						
							|  |  |  |         "id": f"{model}-{str(uuid.uuid4())}", | 
					
						
							|  |  |  |         "created": int(time.time()), | 
					
						
							|  |  |  |         "model": model, | 
					
						
							| 
									
										
										
										
											2024-07-31 22:26:26 +08:00
										 |  |  |         "choices": [{"index": 0, "logprobs": None, "finish_reason": None}], | 
					
						
							| 
									
										
										
										
											2024-06-20 19:38:59 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 22:26:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-21 07:07:57 +08:00
										 |  |  | def openai_chat_chunk_message_template( | 
					
						
							| 
									
										
										
										
											2025-02-05 13:42:49 +08:00
										 |  |  |     model: str, | 
					
						
							|  |  |  |     content: Optional[str] = None, | 
					
						
							| 
									
										
										
										
											2025-06-10 17:10:31 +08:00
										 |  |  |     reasoning_content: Optional[str] = None, | 
					
						
							| 
									
										
										
										
											2025-02-05 13:42:49 +08:00
										 |  |  |     tool_calls: Optional[list[dict]] = None, | 
					
						
							|  |  |  |     usage: Optional[dict] = None, | 
					
						
							| 
									
										
										
										
											2024-09-21 07:07:57 +08:00
										 |  |  | ) -> dict: | 
					
						
							| 
									
										
										
										
											2024-08-01 05:01:22 +08:00
										 |  |  |     template = openai_chat_message_template(model) | 
					
						
							| 
									
										
										
										
											2024-07-31 22:26:26 +08:00
										 |  |  |     template["object"] = "chat.completion.chunk" | 
					
						
							| 
									
										
										
										
											2025-02-05 13:42:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-05 15:05:14 +08:00
										 |  |  |     template["choices"][0]["index"] = 0 | 
					
						
							| 
									
										
										
										
											2025-02-05 13:42:49 +08:00
										 |  |  |     template["choices"][0]["delta"] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if content: | 
					
						
							|  |  |  |         template["choices"][0]["delta"]["content"] = content | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-10 17:10:31 +08:00
										 |  |  |     if reasoning_content: | 
					
						
							| 
									
										
										
										
											2025-06-10 17:16:44 +08:00
										 |  |  |         template["choices"][0]["delta"]["reasoning_content"] = reasoning_content | 
					
						
							| 
									
										
										
										
											2025-06-10 17:10:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-05 13:42:49 +08:00
										 |  |  |     if tool_calls: | 
					
						
							|  |  |  |         template["choices"][0]["delta"]["tool_calls"] = tool_calls | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-09 04:31:28 +08:00
										 |  |  |     if not content and not reasoning_content and not tool_calls: | 
					
						
							| 
									
										
										
										
											2024-09-21 07:07:57 +08:00
										 |  |  |         template["choices"][0]["finish_reason"] = "stop" | 
					
						
							| 
									
										
										
										
											2024-12-13 15:31:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if usage: | 
					
						
							|  |  |  |         template["usage"] = usage | 
					
						
							| 
									
										
										
										
											2024-07-31 22:26:26 +08:00
										 |  |  |     return template | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-21 07:07:57 +08:00
										 |  |  | def openai_chat_completion_message_template( | 
					
						
							| 
									
										
										
										
											2025-02-13 16:13:33 +08:00
										 |  |  |     model: str, | 
					
						
							| 
									
										
										
										
											2025-02-12 16:11:26 +08:00
										 |  |  |     message: Optional[str] = None, | 
					
						
							| 
									
										
										
										
											2025-06-10 17:10:31 +08:00
										 |  |  |     reasoning_content: Optional[str] = None, | 
					
						
							| 
									
										
										
										
											2025-02-12 16:11:26 +08:00
										 |  |  |     tool_calls: Optional[list[dict]] = None, | 
					
						
							| 
									
										
										
										
											2025-02-13 16:13:33 +08:00
										 |  |  |     usage: Optional[dict] = None, | 
					
						
							| 
									
										
										
										
											2024-09-21 07:07:57 +08:00
										 |  |  | ) -> dict: | 
					
						
							| 
									
										
										
										
											2024-08-01 05:01:22 +08:00
										 |  |  |     template = openai_chat_message_template(model) | 
					
						
							| 
									
										
										
										
											2024-07-31 22:26:26 +08:00
										 |  |  |     template["object"] = "chat.completion" | 
					
						
							| 
									
										
										
										
											2024-10-07 05:52:12 +08:00
										 |  |  |     if message is not None: | 
					
						
							| 
									
										
										
										
											2025-02-14 07:17:41 +08:00
										 |  |  |         template["choices"][0]["message"] = { | 
					
						
							|  |  |  |             "role": "assistant", | 
					
						
							| 
									
										
										
										
											2025-06-10 17:10:31 +08:00
										 |  |  |             "content": message, | 
					
						
							|  |  |  |             **({"reasoning_content": reasoning_content} if reasoning_content else {}), | 
					
						
							| 
									
										
										
										
											2025-02-14 07:17:41 +08:00
										 |  |  |             **({"tool_calls": tool_calls} if tool_calls else {}), | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-12 16:11:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 22:26:26 +08:00
										 |  |  |     template["choices"][0]["finish_reason"] = "stop" | 
					
						
							| 
									
										
										
										
											2024-12-13 15:31:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if usage: | 
					
						
							|  |  |  |         template["usage"] = usage | 
					
						
							| 
									
										
										
										
											2024-08-04 19:10:02 +08:00
										 |  |  |     return template | 
					
						
							| 
									
										
										
										
											2024-07-31 22:26:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-19 13:41:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | def get_gravatar_url(email): | 
					
						
							|  |  |  |     # Trim leading and trailing whitespace from | 
					
						
							|  |  |  |     # an email address and force all characters | 
					
						
							|  |  |  |     # to lower case | 
					
						
							|  |  |  |     address = str(email).strip().lower() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Create a SHA256 hash of the final string | 
					
						
							|  |  |  |     hash_object = hashlib.sha256(address.encode()) | 
					
						
							|  |  |  |     hash_hex = hash_object.hexdigest() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Grab the actual image URL | 
					
						
							| 
									
										
										
										
											2023-11-19 16:46:27 +08:00
										 |  |  |     return f"https://www.gravatar.com/avatar/{hash_hex}?d=mp" | 
					
						
							| 
									
										
										
										
											2023-12-24 07:38:52 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-08 12:52:24 +08:00
										 |  |  | def calculate_sha256(file_path, chunk_size): | 
					
						
							| 
									
										
										
										
											2025-02-10 14:20:47 +08:00
										 |  |  |     # Compute SHA-256 hash of a file efficiently in chunks | 
					
						
							| 
									
										
										
										
											2023-12-24 07:38:52 +08:00
										 |  |  |     sha256 = hashlib.sha256() | 
					
						
							| 
									
										
										
										
											2025-02-08 12:52:24 +08:00
										 |  |  |     with open(file_path, "rb") as f: | 
					
						
							|  |  |  |         while chunk := f.read(chunk_size): | 
					
						
							|  |  |  |             sha256.update(chunk) | 
					
						
							| 
									
										
										
										
											2023-12-24 07:38:52 +08:00
										 |  |  |     return sha256.hexdigest() | 
					
						
							| 
									
										
										
										
											2024-01-03 08:22:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-27 14:17:28 +08:00
										 |  |  | def calculate_sha256_string(string): | 
					
						
							|  |  |  |     # Create a new SHA-256 hash object | 
					
						
							|  |  |  |     sha256_hash = hashlib.sha256() | 
					
						
							|  |  |  |     # Update the hash object with the bytes of the input string | 
					
						
							|  |  |  |     sha256_hash.update(string.encode("utf-8")) | 
					
						
							|  |  |  |     # Get the hexadecimal representation of the hash | 
					
						
							|  |  |  |     hashed_string = sha256_hash.hexdigest() | 
					
						
							|  |  |  |     return hashed_string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-03 08:22:48 +08:00
										 |  |  | def validate_email_format(email: str) -> bool: | 
					
						
							| 
									
										
										
										
											2024-05-08 23:40:18 +08:00
										 |  |  |     if email.endswith("@localhost"): | 
					
						
							|  |  |  |         return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return bool(re.match(r"[^@]+@[^@]+\.[^@]+", email)) | 
					
						
							| 
									
										
										
										
											2024-02-18 13:06:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def sanitize_filename(file_name): | 
					
						
							|  |  |  |     # Convert to lowercase | 
					
						
							|  |  |  |     lower_case_file_name = file_name.lower() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Remove special characters using regular expression | 
					
						
							|  |  |  |     sanitized_file_name = re.sub(r"[^\w\s]", "", lower_case_file_name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Replace spaces with dashes | 
					
						
							|  |  |  |     final_file_name = re.sub(r"\s+", "-", sanitized_file_name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return final_file_name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def extract_folders_after_data_docs(path): | 
					
						
							|  |  |  |     # Convert the path to a Path object if it's not already | 
					
						
							|  |  |  |     path = Path(path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Extract parts of the path | 
					
						
							|  |  |  |     parts = path.parts | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Find the index of '/data/docs' in the path | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         index_data_docs = parts.index("data") + 1 | 
					
						
							|  |  |  |         index_docs = parts.index("docs", index_data_docs) + 1 | 
					
						
							|  |  |  |     except ValueError: | 
					
						
							|  |  |  |         return [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Exclude the filename and accumulate folder names | 
					
						
							|  |  |  |     tags = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     folders = parts[index_docs:-1] | 
					
						
							| 
									
										
										
										
											2024-08-01 04:58:40 +08:00
										 |  |  |     for idx, _ in enumerate(folders): | 
					
						
							| 
									
										
										
										
											2024-02-18 13:06:08 +08:00
										 |  |  |         tags.append("/".join(folders[: idx + 1])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return tags | 
					
						
							| 
									
										
										
										
											2024-02-20 12:44:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_duration(duration: str) -> Optional[timedelta]: | 
					
						
							|  |  |  |     if duration == "-1" or duration == "0": | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Regular expression to find number and unit pairs | 
					
						
							|  |  |  |     pattern = r"(-?\d+(\.\d+)?)(ms|s|m|h|d|w)" | 
					
						
							|  |  |  |     matches = re.findall(pattern, duration) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if not matches: | 
					
						
							|  |  |  |         raise ValueError("Invalid duration string") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     total_duration = timedelta() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for number, _, unit in matches: | 
					
						
							|  |  |  |         number = float(number) | 
					
						
							|  |  |  |         if unit == "ms": | 
					
						
							|  |  |  |             total_duration += timedelta(milliseconds=number) | 
					
						
							|  |  |  |         elif unit == "s": | 
					
						
							|  |  |  |             total_duration += timedelta(seconds=number) | 
					
						
							|  |  |  |         elif unit == "m": | 
					
						
							|  |  |  |             total_duration += timedelta(minutes=number) | 
					
						
							|  |  |  |         elif unit == "h": | 
					
						
							|  |  |  |             total_duration += timedelta(hours=number) | 
					
						
							|  |  |  |         elif unit == "d": | 
					
						
							|  |  |  |             total_duration += timedelta(days=number) | 
					
						
							|  |  |  |         elif unit == "w": | 
					
						
							|  |  |  |             total_duration += timedelta(weeks=number) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return total_duration | 
					
						
							| 
									
										
										
										
											2024-05-25 10:26:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_ollama_modelfile(model_text): | 
					
						
							|  |  |  |     parameters_meta = { | 
					
						
							|  |  |  |         "mirostat": int, | 
					
						
							|  |  |  |         "mirostat_eta": float, | 
					
						
							|  |  |  |         "mirostat_tau": float, | 
					
						
							|  |  |  |         "num_ctx": int, | 
					
						
							|  |  |  |         "repeat_last_n": int, | 
					
						
							|  |  |  |         "repeat_penalty": float, | 
					
						
							|  |  |  |         "temperature": float, | 
					
						
							|  |  |  |         "seed": int, | 
					
						
							|  |  |  |         "tfs_z": float, | 
					
						
							|  |  |  |         "num_predict": int, | 
					
						
							|  |  |  |         "top_k": int, | 
					
						
							|  |  |  |         "top_p": float, | 
					
						
							| 
									
										
										
										
											2024-06-04 03:48:17 +08:00
										 |  |  |         "num_keep": int, | 
					
						
							|  |  |  |         "presence_penalty": float, | 
					
						
							|  |  |  |         "frequency_penalty": float, | 
					
						
							|  |  |  |         "num_batch": int, | 
					
						
							|  |  |  |         "num_gpu": int, | 
					
						
							|  |  |  |         "use_mmap": bool, | 
					
						
							|  |  |  |         "use_mlock": bool, | 
					
						
							|  |  |  |         "num_thread": int, | 
					
						
							| 
									
										
										
										
											2024-05-25 10:26:27 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     data = {"base_model_id": None, "params": {}} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Parse base model | 
					
						
							|  |  |  |     base_model_match = re.search( | 
					
						
							|  |  |  |         r"^FROM\s+(\w+)", model_text, re.MULTILINE | re.IGNORECASE | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     if base_model_match: | 
					
						
							|  |  |  |         data["base_model_id"] = base_model_match.group(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Parse template | 
					
						
							|  |  |  |     template_match = re.search( | 
					
						
							|  |  |  |         r'TEMPLATE\s+"""(.+?)"""', model_text, re.DOTALL | re.IGNORECASE | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     if template_match: | 
					
						
							|  |  |  |         data["params"] = {"template": template_match.group(1).strip()} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Parse stops | 
					
						
							|  |  |  |     stops = re.findall(r'PARAMETER stop "(.*?)"', model_text, re.IGNORECASE) | 
					
						
							|  |  |  |     if stops: | 
					
						
							|  |  |  |         data["params"]["stop"] = stops | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Parse other parameters from the provided list | 
					
						
							|  |  |  |     for param, param_type in parameters_meta.items(): | 
					
						
							|  |  |  |         param_match = re.search(rf"PARAMETER {param} (.+)", model_text, re.IGNORECASE) | 
					
						
							|  |  |  |         if param_match: | 
					
						
							|  |  |  |             value = param_match.group(1) | 
					
						
							| 
									
										
										
										
											2024-06-04 03:48:17 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             try: | 
					
						
							| 
									
										
										
										
											2024-08-01 04:58:40 +08:00
										 |  |  |                 if param_type is int: | 
					
						
							| 
									
										
										
										
											2024-06-04 03:48:17 +08:00
										 |  |  |                     value = int(value) | 
					
						
							| 
									
										
										
										
											2024-08-01 04:58:40 +08:00
										 |  |  |                 elif param_type is float: | 
					
						
							| 
									
										
										
										
											2024-06-04 03:48:17 +08:00
										 |  |  |                     value = float(value) | 
					
						
							| 
									
										
										
										
											2024-08-01 04:58:40 +08:00
										 |  |  |                 elif param_type is bool: | 
					
						
							| 
									
										
										
										
											2024-06-04 03:48:17 +08:00
										 |  |  |                     value = value.lower() == "true" | 
					
						
							|  |  |  |             except Exception as e: | 
					
						
							| 
									
										
										
										
											2025-02-25 22:36:25 +08:00
										 |  |  |                 log.exception(f"Failed to parse parameter {param}: {e}") | 
					
						
							| 
									
										
										
										
											2024-06-04 03:48:17 +08:00
										 |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-25 10:26:27 +08:00
										 |  |  |             data["params"][param] = value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Parse adapter | 
					
						
							|  |  |  |     adapter_match = re.search(r"ADAPTER (.+)", model_text, re.IGNORECASE) | 
					
						
							|  |  |  |     if adapter_match: | 
					
						
							|  |  |  |         data["params"]["adapter"] = adapter_match.group(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Parse system description | 
					
						
							|  |  |  |     system_desc_match = re.search( | 
					
						
							|  |  |  |         r'SYSTEM\s+"""(.+?)"""', model_text, re.DOTALL | re.IGNORECASE | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2024-06-09 04:45:33 +08:00
										 |  |  |     system_desc_match_single = re.search( | 
					
						
							|  |  |  |         r"SYSTEM\s+([^\n]+)", model_text, re.IGNORECASE | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-25 10:26:27 +08:00
										 |  |  |     if system_desc_match: | 
					
						
							|  |  |  |         data["params"]["system"] = system_desc_match.group(1).strip() | 
					
						
							| 
									
										
										
										
											2024-06-09 04:45:33 +08:00
										 |  |  |     elif system_desc_match_single: | 
					
						
							|  |  |  |         data["params"]["system"] = system_desc_match_single.group(1).strip() | 
					
						
							| 
									
										
										
										
											2024-05-25 10:26:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Parse messages | 
					
						
							|  |  |  |     messages = [] | 
					
						
							|  |  |  |     message_matches = re.findall(r"MESSAGE (\w+) (.+)", model_text, re.IGNORECASE) | 
					
						
							|  |  |  |     for role, content in message_matches: | 
					
						
							|  |  |  |         messages.append({"role": role, "content": content}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if messages: | 
					
						
							|  |  |  |         data["params"]["messages"] = messages | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return data | 
					
						
							| 
									
										
										
										
											2025-02-28 15:13:30 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-01 23:28:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-28 15:13:30 +08:00
										 |  |  | def convert_logit_bias_input_to_json(user_input): | 
					
						
							| 
									
										
										
										
											2025-03-01 23:28:00 +08:00
										 |  |  |     logit_bias_pairs = user_input.split(",") | 
					
						
							| 
									
										
										
										
											2025-02-28 15:13:30 +08:00
										 |  |  |     logit_bias_json = {} | 
					
						
							|  |  |  |     for pair in logit_bias_pairs: | 
					
						
							| 
									
										
										
										
											2025-03-01 23:28:00 +08:00
										 |  |  |         token, bias = pair.split(":") | 
					
						
							| 
									
										
										
										
											2025-02-28 15:13:30 +08:00
										 |  |  |         token = str(token.strip()) | 
					
						
							|  |  |  |         bias = int(bias.strip()) | 
					
						
							|  |  |  |         bias = 100 if bias > 100 else -100 if bias < -100 else bias | 
					
						
							|  |  |  |         logit_bias_json[token] = bias | 
					
						
							| 
									
										
										
										
											2025-03-01 23:28:00 +08:00
										 |  |  |     return json.dumps(logit_bias_json) | 
					
						
							| 
									
										
										
										
											2025-07-07 22:16:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def freeze(value): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Freeze a value to make it hashable. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if isinstance(value, dict): | 
					
						
							|  |  |  |         return frozenset((k, freeze(v)) for k, v in value.items()) | 
					
						
							|  |  |  |     elif isinstance(value, list): | 
					
						
							|  |  |  |         return tuple(freeze(v) for v in value) | 
					
						
							|  |  |  |     return value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-17 08:06:16 +08:00
										 |  |  | def throttle(interval: float = 10.0): | 
					
						
							| 
									
										
										
										
											2025-07-07 22:16:19 +08:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Decorator to prevent a function from being called more than once within a specified duration. | 
					
						
							|  |  |  |     If the function is called again within the duration, it returns None. To avoid returning | 
					
						
							|  |  |  |     different types, the return type of the function should be Optional[T]. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param interval: Duration in seconds to wait before allowing the function to be called again. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2025-07-19 19:20:53 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-07 22:16:19 +08:00
										 |  |  |     def decorator(func): | 
					
						
							|  |  |  |         last_calls = {} | 
					
						
							|  |  |  |         lock = threading.Lock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def wrapper(*args, **kwargs): | 
					
						
							| 
									
										
										
										
											2025-08-17 08:06:16 +08:00
										 |  |  |             if interval is None: | 
					
						
							|  |  |  |                 return func(*args, **kwargs) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-07 22:16:19 +08:00
										 |  |  |             key = (args, freeze(kwargs)) | 
					
						
							|  |  |  |             now = time.time() | 
					
						
							|  |  |  |             if now - last_calls.get(key, 0) < interval: | 
					
						
							|  |  |  |                 return None | 
					
						
							|  |  |  |             with lock: | 
					
						
							|  |  |  |                 if now - last_calls.get(key, 0) < interval: | 
					
						
							|  |  |  |                     return None | 
					
						
							|  |  |  |                 last_calls[key] = now | 
					
						
							|  |  |  |             return func(*args, **kwargs) | 
					
						
							| 
									
										
										
										
											2025-07-19 19:20:53 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-07 22:16:19 +08:00
										 |  |  |         return wrapper | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return decorator |