simple_prompt_transform.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. import enum
  2. import json
  3. import os
  4. from collections.abc import Mapping, Sequence
  5. from typing import TYPE_CHECKING, Any, Optional, cast
  6. from core.app.app_config.entities import PromptTemplateEntity
  7. from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity
  8. from core.file import file_manager
  9. from core.memory.token_buffer_memory import TokenBufferMemory
  10. from core.model_runtime.entities.message_entities import (
  11. ImagePromptMessageContent,
  12. PromptMessage,
  13. PromptMessageContentUnionTypes,
  14. SystemPromptMessage,
  15. TextPromptMessageContent,
  16. UserPromptMessage,
  17. )
  18. from core.prompt.entities.advanced_prompt_entities import MemoryConfig
  19. from core.prompt.prompt_transform import PromptTransform
  20. from core.prompt.utils.prompt_template_parser import PromptTemplateParser
  21. from models.model import AppMode
  22. if TYPE_CHECKING:
  23. from core.file.models import File
  24. class ModelMode(enum.StrEnum):
  25. COMPLETION = "completion"
  26. CHAT = "chat"
  27. prompt_file_contents: dict[str, Any] = {}
  28. class SimplePromptTransform(PromptTransform):
  29. """
  30. Simple Prompt Transform for Chatbot App Basic Mode.
  31. """
  32. def get_prompt(
  33. self,
  34. app_mode: AppMode,
  35. prompt_template_entity: PromptTemplateEntity,
  36. inputs: Mapping[str, str],
  37. query: str,
  38. files: Sequence["File"],
  39. context: Optional[str],
  40. memory: Optional[TokenBufferMemory],
  41. model_config: ModelConfigWithCredentialsEntity,
  42. image_detail_config: Optional[ImagePromptMessageContent.DETAIL] = None,
  43. ) -> tuple[list[PromptMessage], Optional[list[str]]]:
  44. inputs = {key: str(value) for key, value in inputs.items()}
  45. model_mode = ModelMode(model_config.mode)
  46. if model_mode == ModelMode.CHAT:
  47. prompt_messages, stops = self._get_chat_model_prompt_messages(
  48. app_mode=app_mode,
  49. pre_prompt=prompt_template_entity.simple_prompt_template or "",
  50. inputs=inputs,
  51. query=query,
  52. files=files,
  53. context=context,
  54. memory=memory,
  55. model_config=model_config,
  56. image_detail_config=image_detail_config,
  57. )
  58. else:
  59. prompt_messages, stops = self._get_completion_model_prompt_messages(
  60. app_mode=app_mode,
  61. pre_prompt=prompt_template_entity.simple_prompt_template or "",
  62. inputs=inputs,
  63. query=query,
  64. files=files,
  65. context=context,
  66. memory=memory,
  67. model_config=model_config,
  68. image_detail_config=image_detail_config,
  69. )
  70. return prompt_messages, stops
  71. def _get_prompt_str_and_rules(
  72. self,
  73. app_mode: AppMode,
  74. model_config: ModelConfigWithCredentialsEntity,
  75. pre_prompt: str,
  76. inputs: dict,
  77. query: Optional[str] = None,
  78. context: Optional[str] = None,
  79. histories: Optional[str] = None,
  80. ) -> tuple[str, dict]:
  81. # get prompt template
  82. prompt_template_config = self.get_prompt_template(
  83. app_mode=app_mode,
  84. provider=model_config.provider,
  85. model=model_config.model,
  86. pre_prompt=pre_prompt,
  87. has_context=context is not None,
  88. query_in_prompt=query is not None,
  89. with_memory_prompt=histories is not None,
  90. )
  91. variables = {k: inputs[k] for k in prompt_template_config["custom_variable_keys"] if k in inputs}
  92. for v in prompt_template_config["special_variable_keys"]:
  93. # support #context#, #query# and #histories#
  94. if v == "#context#":
  95. variables["#context#"] = context or ""
  96. elif v == "#query#":
  97. variables["#query#"] = query or ""
  98. elif v == "#histories#":
  99. variables["#histories#"] = histories or ""
  100. prompt_template = prompt_template_config["prompt_template"]
  101. prompt = prompt_template.format(variables)
  102. return prompt, prompt_template_config["prompt_rules"]
  103. def get_prompt_template(
  104. self,
  105. app_mode: AppMode,
  106. provider: str,
  107. model: str,
  108. pre_prompt: str,
  109. has_context: bool,
  110. query_in_prompt: bool,
  111. with_memory_prompt: bool = False,
  112. ) -> dict:
  113. prompt_rules = self._get_prompt_rule(app_mode=app_mode, provider=provider, model=model)
  114. custom_variable_keys = []
  115. special_variable_keys = []
  116. prompt = ""
  117. for order in prompt_rules["system_prompt_orders"]:
  118. if order == "context_prompt" and has_context:
  119. prompt += prompt_rules["context_prompt"]
  120. special_variable_keys.append("#context#")
  121. elif order == "pre_prompt" and pre_prompt:
  122. prompt += pre_prompt + "\n"
  123. pre_prompt_template = PromptTemplateParser(template=pre_prompt)
  124. custom_variable_keys = pre_prompt_template.variable_keys
  125. elif order == "histories_prompt" and with_memory_prompt:
  126. prompt += prompt_rules["histories_prompt"]
  127. special_variable_keys.append("#histories#")
  128. if query_in_prompt:
  129. prompt += prompt_rules.get("query_prompt", "{{#query#}}")
  130. special_variable_keys.append("#query#")
  131. return {
  132. "prompt_template": PromptTemplateParser(template=prompt),
  133. "custom_variable_keys": custom_variable_keys,
  134. "special_variable_keys": special_variable_keys,
  135. "prompt_rules": prompt_rules,
  136. }
  137. def _get_chat_model_prompt_messages(
  138. self,
  139. app_mode: AppMode,
  140. pre_prompt: str,
  141. inputs: dict,
  142. query: str,
  143. context: Optional[str],
  144. files: Sequence["File"],
  145. memory: Optional[TokenBufferMemory],
  146. model_config: ModelConfigWithCredentialsEntity,
  147. image_detail_config: Optional[ImagePromptMessageContent.DETAIL] = None,
  148. ) -> tuple[list[PromptMessage], Optional[list[str]]]:
  149. prompt_messages: list[PromptMessage] = []
  150. # get prompt
  151. prompt, _ = self._get_prompt_str_and_rules(
  152. app_mode=app_mode,
  153. model_config=model_config,
  154. pre_prompt=pre_prompt,
  155. inputs=inputs,
  156. query=None,
  157. context=context,
  158. )
  159. if prompt and query:
  160. prompt_messages.append(SystemPromptMessage(content=prompt))
  161. if memory:
  162. prompt_messages = self._append_chat_histories(
  163. memory=memory,
  164. memory_config=MemoryConfig(
  165. window=MemoryConfig.WindowConfig(
  166. enabled=False,
  167. )
  168. ),
  169. prompt_messages=prompt_messages,
  170. model_config=model_config,
  171. )
  172. if query:
  173. prompt_messages.append(self._get_last_user_message(query, files, image_detail_config))
  174. else:
  175. prompt_messages.append(self._get_last_user_message(prompt, files, image_detail_config))
  176. return prompt_messages, None
  177. def _get_completion_model_prompt_messages(
  178. self,
  179. app_mode: AppMode,
  180. pre_prompt: str,
  181. inputs: dict,
  182. query: str,
  183. context: Optional[str],
  184. files: Sequence["File"],
  185. memory: Optional[TokenBufferMemory],
  186. model_config: ModelConfigWithCredentialsEntity,
  187. image_detail_config: Optional[ImagePromptMessageContent.DETAIL] = None,
  188. ) -> tuple[list[PromptMessage], Optional[list[str]]]:
  189. # get prompt
  190. prompt, prompt_rules = self._get_prompt_str_and_rules(
  191. app_mode=app_mode,
  192. model_config=model_config,
  193. pre_prompt=pre_prompt,
  194. inputs=inputs,
  195. query=query,
  196. context=context,
  197. )
  198. if memory:
  199. tmp_human_message = UserPromptMessage(content=prompt)
  200. rest_tokens = self._calculate_rest_token([tmp_human_message], model_config)
  201. histories = self._get_history_messages_from_memory(
  202. memory=memory,
  203. memory_config=MemoryConfig(
  204. window=MemoryConfig.WindowConfig(
  205. enabled=False,
  206. )
  207. ),
  208. max_token_limit=rest_tokens,
  209. human_prefix=prompt_rules.get("human_prefix", "Human"),
  210. ai_prefix=prompt_rules.get("assistant_prefix", "Assistant"),
  211. )
  212. # get prompt
  213. prompt, prompt_rules = self._get_prompt_str_and_rules(
  214. app_mode=app_mode,
  215. model_config=model_config,
  216. pre_prompt=pre_prompt,
  217. inputs=inputs,
  218. query=query,
  219. context=context,
  220. histories=histories,
  221. )
  222. stops = prompt_rules.get("stops")
  223. if stops is not None and len(stops) == 0:
  224. stops = None
  225. return [self._get_last_user_message(prompt, files, image_detail_config)], stops
  226. def _get_last_user_message(
  227. self,
  228. prompt: str,
  229. files: Sequence["File"],
  230. image_detail_config: Optional[ImagePromptMessageContent.DETAIL] = None,
  231. ) -> UserPromptMessage:
  232. if files:
  233. prompt_message_contents: list[PromptMessageContentUnionTypes] = []
  234. for file in files:
  235. prompt_message_contents.append(
  236. file_manager.to_prompt_message_content(file, image_detail_config=image_detail_config)
  237. )
  238. prompt_message_contents.append(TextPromptMessageContent(data=prompt))
  239. prompt_message = UserPromptMessage(content=prompt_message_contents)
  240. else:
  241. prompt_message = UserPromptMessage(content=prompt)
  242. return prompt_message
  243. def _get_prompt_rule(self, app_mode: AppMode, provider: str, model: str) -> dict:
  244. """
  245. Get simple prompt rule.
  246. :param app_mode: app mode
  247. :param provider: model provider
  248. :param model: model name
  249. :return:
  250. """
  251. prompt_file_name = self._prompt_file_name(app_mode=app_mode, provider=provider, model=model)
  252. # Check if the prompt file is already loaded
  253. if prompt_file_name in prompt_file_contents:
  254. return cast(dict, prompt_file_contents[prompt_file_name])
  255. # Get the absolute path of the subdirectory
  256. prompt_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "prompt_templates")
  257. json_file_path = os.path.join(prompt_path, f"{prompt_file_name}.json")
  258. # Open the JSON file and read its content
  259. with open(json_file_path, encoding="utf-8") as json_file:
  260. content = json.load(json_file)
  261. # Store the content of the prompt file
  262. prompt_file_contents[prompt_file_name] = content
  263. return cast(dict, content)
  264. def _prompt_file_name(self, app_mode: AppMode, provider: str, model: str) -> str:
  265. # baichuan
  266. is_baichuan = False
  267. if provider == "baichuan":
  268. is_baichuan = True
  269. else:
  270. baichuan_supported_providers = ["huggingface_hub", "openllm", "xinference"]
  271. if provider in baichuan_supported_providers and "baichuan" in model.lower():
  272. is_baichuan = True
  273. if is_baichuan:
  274. if app_mode == AppMode.COMPLETION:
  275. return "baichuan_completion"
  276. else:
  277. return "baichuan_chat"
  278. # common
  279. if app_mode == AppMode.COMPLETION:
  280. return "common_completion"
  281. else:
  282. return "common_chat"