misc.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. import hashlib
  2. import re
  3. import time
  4. import uuid
  5. from datetime import timedelta
  6. from pathlib import Path
  7. from typing import Callable, Optional
  8. def get_message_list(messages, message_id):
  9. """
  10. Reconstructs a list of messages in order up to the specified message_id.
  11. :param message_id: ID of the message to reconstruct the chain
  12. :param messages: Message history dict containing all messages
  13. :return: List of ordered messages starting from the root to the given message
  14. """
  15. # Find the message by its id
  16. current_message = messages.get(message_id)
  17. if not current_message:
  18. return f"Message ID {message_id} not found in the history."
  19. # Reconstruct the chain by following the parentId links
  20. message_list = []
  21. while current_message:
  22. message_list.insert(
  23. 0, current_message
  24. ) # Insert the message at the beginning of the list
  25. parent_id = current_message["parentId"]
  26. current_message = messages.get(parent_id) if parent_id else None
  27. return message_list
  28. def get_messages_content(messages: list[dict]) -> str:
  29. return "\n".join(
  30. [
  31. f"{message['role'].upper()}: {get_content_from_message(message)}"
  32. for message in messages
  33. ]
  34. )
  35. def get_last_user_message_item(messages: list[dict]) -> Optional[dict]:
  36. for message in reversed(messages):
  37. if message["role"] == "user":
  38. return message
  39. return None
  40. def get_content_from_message(message: dict) -> Optional[str]:
  41. if isinstance(message["content"], list):
  42. for item in message["content"]:
  43. if item["type"] == "text":
  44. return item["text"]
  45. else:
  46. return message["content"]
  47. return None
  48. def get_last_user_message(messages: list[dict]) -> Optional[str]:
  49. message = get_last_user_message_item(messages)
  50. if message is None:
  51. return None
  52. return get_content_from_message(message)
  53. def get_last_assistant_message_item(messages: list[dict]) -> Optional[dict]:
  54. for message in reversed(messages):
  55. if message["role"] == "assistant":
  56. return message
  57. return None
  58. def get_last_assistant_message(messages: list[dict]) -> Optional[str]:
  59. for message in reversed(messages):
  60. if message["role"] == "assistant":
  61. return get_content_from_message(message)
  62. return None
  63. def get_system_message(messages: list[dict]) -> Optional[dict]:
  64. for message in messages:
  65. if message["role"] == "system":
  66. return message
  67. return None
  68. def remove_system_message(messages: list[dict]) -> list[dict]:
  69. return [message for message in messages if message["role"] != "system"]
  70. def pop_system_message(messages: list[dict]) -> tuple[Optional[dict], list[dict]]:
  71. return get_system_message(messages), remove_system_message(messages)
  72. def prepend_to_first_user_message_content(
  73. content: str, messages: list[dict]
  74. ) -> list[dict]:
  75. for message in messages:
  76. if message["role"] == "user":
  77. if isinstance(message["content"], list):
  78. for item in message["content"]:
  79. if item["type"] == "text":
  80. item["text"] = f"{content}\n{item['text']}"
  81. else:
  82. message["content"] = f"{content}\n{message['content']}"
  83. break
  84. return messages
  85. def add_or_update_system_message(content: str, messages: list[dict]):
  86. """
  87. Adds a new system message at the beginning of the messages list
  88. or updates the existing system message at the beginning.
  89. :param msg: The message to be added or appended.
  90. :param messages: The list of message dictionaries.
  91. :return: The updated list of message dictionaries.
  92. """
  93. if messages and messages[0].get("role") == "system":
  94. messages[0]["content"] = f"{content}\n{messages[0]['content']}"
  95. else:
  96. # Insert at the beginning
  97. messages.insert(0, {"role": "system", "content": content})
  98. return messages
  99. def openai_chat_message_template(model: str):
  100. return {
  101. "id": f"{model}-{str(uuid.uuid4())}",
  102. "created": int(time.time()),
  103. "model": model,
  104. "choices": [{"index": 0, "logprobs": None, "finish_reason": None}],
  105. }
  106. def openai_chat_chunk_message_template(
  107. model: str, message: Optional[str] = None, usage: Optional[dict] = None
  108. ) -> dict:
  109. template = openai_chat_message_template(model)
  110. template["object"] = "chat.completion.chunk"
  111. if message:
  112. template["choices"][0]["delta"] = {"content": message}
  113. else:
  114. template["choices"][0]["finish_reason"] = "stop"
  115. if usage:
  116. template["usage"] = usage
  117. return template
  118. def openai_chat_completion_message_template(
  119. model: str, message: Optional[str] = None, usage: Optional[dict] = None
  120. ) -> dict:
  121. template = openai_chat_message_template(model)
  122. template["object"] = "chat.completion"
  123. if message is not None:
  124. template["choices"][0]["message"] = {"content": message, "role": "assistant"}
  125. template["choices"][0]["finish_reason"] = "stop"
  126. if usage:
  127. template["usage"] = usage
  128. return template
  129. def get_gravatar_url(email):
  130. # Trim leading and trailing whitespace from
  131. # an email address and force all characters
  132. # to lower case
  133. address = str(email).strip().lower()
  134. # Create a SHA256 hash of the final string
  135. hash_object = hashlib.sha256(address.encode())
  136. hash_hex = hash_object.hexdigest()
  137. # Grab the actual image URL
  138. return f"https://www.gravatar.com/avatar/{hash_hex}?d=mp"
  139. def calculate_sha256(file):
  140. sha256 = hashlib.sha256()
  141. # Read the file in chunks to efficiently handle large files
  142. for chunk in iter(lambda: file.read(8192), b""):
  143. sha256.update(chunk)
  144. return sha256.hexdigest()
  145. def calculate_sha256_string(string):
  146. # Create a new SHA-256 hash object
  147. sha256_hash = hashlib.sha256()
  148. # Update the hash object with the bytes of the input string
  149. sha256_hash.update(string.encode("utf-8"))
  150. # Get the hexadecimal representation of the hash
  151. hashed_string = sha256_hash.hexdigest()
  152. return hashed_string
  153. def validate_email_format(email: str) -> bool:
  154. if email.endswith("@localhost"):
  155. return True
  156. return bool(re.match(r"[^@]+@[^@]+\.[^@]+", email))
  157. def sanitize_filename(file_name):
  158. # Convert to lowercase
  159. lower_case_file_name = file_name.lower()
  160. # Remove special characters using regular expression
  161. sanitized_file_name = re.sub(r"[^\w\s]", "", lower_case_file_name)
  162. # Replace spaces with dashes
  163. final_file_name = re.sub(r"\s+", "-", sanitized_file_name)
  164. return final_file_name
  165. def extract_folders_after_data_docs(path):
  166. # Convert the path to a Path object if it's not already
  167. path = Path(path)
  168. # Extract parts of the path
  169. parts = path.parts
  170. # Find the index of '/data/docs' in the path
  171. try:
  172. index_data_docs = parts.index("data") + 1
  173. index_docs = parts.index("docs", index_data_docs) + 1
  174. except ValueError:
  175. return []
  176. # Exclude the filename and accumulate folder names
  177. tags = []
  178. folders = parts[index_docs:-1]
  179. for idx, _ in enumerate(folders):
  180. tags.append("/".join(folders[: idx + 1]))
  181. return tags
  182. def parse_duration(duration: str) -> Optional[timedelta]:
  183. if duration == "-1" or duration == "0":
  184. return None
  185. # Regular expression to find number and unit pairs
  186. pattern = r"(-?\d+(\.\d+)?)(ms|s|m|h|d|w)"
  187. matches = re.findall(pattern, duration)
  188. if not matches:
  189. raise ValueError("Invalid duration string")
  190. total_duration = timedelta()
  191. for number, _, unit in matches:
  192. number = float(number)
  193. if unit == "ms":
  194. total_duration += timedelta(milliseconds=number)
  195. elif unit == "s":
  196. total_duration += timedelta(seconds=number)
  197. elif unit == "m":
  198. total_duration += timedelta(minutes=number)
  199. elif unit == "h":
  200. total_duration += timedelta(hours=number)
  201. elif unit == "d":
  202. total_duration += timedelta(days=number)
  203. elif unit == "w":
  204. total_duration += timedelta(weeks=number)
  205. return total_duration
  206. def parse_ollama_modelfile(model_text):
  207. parameters_meta = {
  208. "mirostat": int,
  209. "mirostat_eta": float,
  210. "mirostat_tau": float,
  211. "num_ctx": int,
  212. "repeat_last_n": int,
  213. "repeat_penalty": float,
  214. "temperature": float,
  215. "seed": int,
  216. "tfs_z": float,
  217. "num_predict": int,
  218. "top_k": int,
  219. "top_p": float,
  220. "num_keep": int,
  221. "typical_p": float,
  222. "presence_penalty": float,
  223. "frequency_penalty": float,
  224. "penalize_newline": bool,
  225. "numa": bool,
  226. "num_batch": int,
  227. "num_gpu": int,
  228. "main_gpu": int,
  229. "low_vram": bool,
  230. "f16_kv": bool,
  231. "vocab_only": bool,
  232. "use_mmap": bool,
  233. "use_mlock": bool,
  234. "num_thread": int,
  235. }
  236. data = {"base_model_id": None, "params": {}}
  237. # Parse base model
  238. base_model_match = re.search(
  239. r"^FROM\s+(\w+)", model_text, re.MULTILINE | re.IGNORECASE
  240. )
  241. if base_model_match:
  242. data["base_model_id"] = base_model_match.group(1)
  243. # Parse template
  244. template_match = re.search(
  245. r'TEMPLATE\s+"""(.+?)"""', model_text, re.DOTALL | re.IGNORECASE
  246. )
  247. if template_match:
  248. data["params"] = {"template": template_match.group(1).strip()}
  249. # Parse stops
  250. stops = re.findall(r'PARAMETER stop "(.*?)"', model_text, re.IGNORECASE)
  251. if stops:
  252. data["params"]["stop"] = stops
  253. # Parse other parameters from the provided list
  254. for param, param_type in parameters_meta.items():
  255. param_match = re.search(rf"PARAMETER {param} (.+)", model_text, re.IGNORECASE)
  256. if param_match:
  257. value = param_match.group(1)
  258. try:
  259. if param_type is int:
  260. value = int(value)
  261. elif param_type is float:
  262. value = float(value)
  263. elif param_type is bool:
  264. value = value.lower() == "true"
  265. except Exception as e:
  266. print(e)
  267. continue
  268. data["params"][param] = value
  269. # Parse adapter
  270. adapter_match = re.search(r"ADAPTER (.+)", model_text, re.IGNORECASE)
  271. if adapter_match:
  272. data["params"]["adapter"] = adapter_match.group(1)
  273. # Parse system description
  274. system_desc_match = re.search(
  275. r'SYSTEM\s+"""(.+?)"""', model_text, re.DOTALL | re.IGNORECASE
  276. )
  277. system_desc_match_single = re.search(
  278. r"SYSTEM\s+([^\n]+)", model_text, re.IGNORECASE
  279. )
  280. if system_desc_match:
  281. data["params"]["system"] = system_desc_match.group(1).strip()
  282. elif system_desc_match_single:
  283. data["params"]["system"] = system_desc_match_single.group(1).strip()
  284. # Parse messages
  285. messages = []
  286. message_matches = re.findall(r"MESSAGE (\w+) (.+)", model_text, re.IGNORECASE)
  287. for role, content in message_matches:
  288. messages.append({"role": role, "content": content})
  289. if messages:
  290. data["params"]["messages"] = messages
  291. return data