1. Add shot paragraph mode for System Prompter, which output continuous paragraph using natural language.
This commit is contained in:
jakechai
2026-02-20 21:53:13 +08:00
parent fe6032d7fc
commit c6146b9177
7 changed files with 162 additions and 74 deletions
+12 -18
View File
@@ -391,27 +391,18 @@ If you like what I share, please support me with [PayPal](https://paypal.me/jake
- 2026-01-31 - v2.5.3 1. Add SAM3D Mesh Sequence From Video node to get mesh sequence from video using SAM3D. Need to install [ComfyUI-SAM3DBody](https://github.com/PozzettiAndrea/ComfyUI-SAM3DBody).
- 2026-02-01 - v2.5.4 1. Update SAM3D Mesh Sequence From Video node: Align the mesh sequence to the video instead of aligning along the creation center.
- 2026-02-02 - v2.5.5 1. Update SAM3D Mesh Sequence From Video node: set z offset to 0 for mesh sequence.
- 2026-02-04 - v2.5.6 1. Add Wan 2.2 SVI pro long video generation workflow.
- 2026-02-05 - v2.5.7 1. Add JK_module_Motion-Get_Mesh_Sequence workflow [here](Workflow/ComfyUI/Module/JK_module_Motion-Get_Mesh_Sequence.json).
- 2026-02-12 - v2.5.9 1. Add Trellis 2 img2mesh workflow but the result is not as good as the official space.
2. Update auto prompt workflows using new QWen3 Prompt Enhancer node.
3. Add LTXV2 video generation workflow, supports t2v | ff2v | lf2v | flf2v | frames2v | v2v, supports ControlNet | upscale.
4. Add more camera motion to Random Prompter.
5. Change SeamlessTile to "Modify in place" for all SD15 | SDXL workflows | Group Nodes | Subgraphs.
- 2026-02-14 - v2.6.0 1. Update the JK_module_Imgen_Seamless Texture workflow and remove deprecated nodes.
2. Update the JK_workflow_imgen_RandomPrompter workflow and using the latest Portrait Master nodes.
</details>
- 2026-02-04 - v2.5.6
1. Add Wan 2.2 SVI pro long video generation workflow.
- 2026-02-05 - v2.5.7
1. Add JK_module_Motion-Get_Mesh_Sequence workflow [here](Workflow/ComfyUI/Module/JK_module_Motion-Get_Mesh_Sequence.json).
- 2026-02-12 - v2.5.9
1. Add Trellis 2 img2mesh workflow but the result is not as good as the official space.
2. Update auto prompt workflows using new QWen3 Prompt Enhancer node.
3. Add LTXV2 video generation workflow, supports t2v | ff2v | lf2v | flf2v | frames2v | v2v, supports ControlNet | upscale.
4. Add more camera motion to Random Prompter.
5. Change SeamlessTile to "Modify in place" for all SD15 | SDXL workflows | Group Nodes | Subgraphs.
- 2026-02-14 - v2.6.0
1. Update the JK_module_Imgen_Seamless Texture workflow and remove deprecated nodes.
2. Update the JK_workflow_imgen_RandomPrompter workflow and using the latest Portrait Master nodes.
- 2026-02-15 - v2.6.1
1. Update System Prompter node, supports shot scripts for video generation.
2. Update workflows including System Prompter node.
@@ -429,6 +420,9 @@ If you like what I share, please support me with [PayPal](https://paypal.me/jake
- 2026-02-19 - v2.6.4
1. Add LTXV2 long video gen workflow.
- 2023-02-20 - v2.6.5
1. Add shot paragraph mode for System Prompter, which output continuous paragraph using natural language.
## Installation
1. `git clone https://github.com/jakechai/ComfyUI-JakeUpgrade` into the `custom_nodes` folder
- e.g. `custom_nodes\ComfyUI-JakeUpgrade`
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+99 -35
View File
@@ -49,7 +49,7 @@ class SysPromptBuilder:
"""Convert language to preset key"""
return "cn" if language == "Chinese" else "en"
def _replace_variables(self, text: str, output_language: str, shot_count: int, json_detail_format: bool) -> str:
def _replace_variables(self, mode, text: str, system_language: str, output_language: str, shot_count: int) -> str:
"""Replace variables in text with actual values"""
if not text:
return text
@@ -64,13 +64,30 @@ class SysPromptBuilder:
# Replace <shot_count>
text = text.replace("<shot_count>", str(shot_count))
# Replace <json_detail_format>
json_detail_format_str = self.preset_data.get("json_detail", {}).get("for_image", {}).get("format", "{}")
json_detail_format_global_str = self.preset_data.get("json_detail", {}).get("for_video", {}).get("format_global", "{}")
json_detail_format_shot_str = self.preset_data.get("json_detail", {}).get("for_video", {}).get("format_shot", "{}")
text = text.replace("<json_detail_format>", json_detail_format_str)
text = text.replace("<json_detail_format_global>", json_detail_format_global_str)
text = text.replace("<json_detail_format_shot>", json_detail_format_shot_str)
# Replace <shot_detail>
shot_detail_str = self.preset_data.get("json_detail", {}).get("for_image", {}).get("format", {})
shot_detail_global_str = self.preset_data.get("json_detail", {}).get("for_video", {}).get("format_global", {})
shot_detail_shot_str = self.preset_data.get("json_detail", {}).get("for_video", {}).get("format_shot", {})
if mode=="shot script":
if system_language == "en":
paragraph_global_str = self.preset_data.get("paragraph", {}).get("script", {}).get("global", {}).get("en", {})
paragraph_shot_str = self.preset_data.get("paragraph", {}).get("script", {}).get("shot", {}).get("en", {})
else:
paragraph_global_str = self.preset_data.get("paragraph", {}).get("script", {}).get("global", {}).get("cn", {})
paragraph_shot_str = self.preset_data.get("paragraph", {}).get("script", {}).get("shot", {}).get("cn", {})
elif mode=="shot paragraph":
if system_language == "en":
paragraph_global_str = self.preset_data.get("paragraph", {}).get("paragraph", {}).get("global", {}).get("en", {})
paragraph_shot_str = self.preset_data.get("paragraph", {}).get("paragraph", {}).get("shot", {}).get("en", {})
else:
paragraph_global_str = self.preset_data.get("paragraph", {}).get("paragraph", {}).get("global", {}).get("cn", {})
paragraph_shot_str = self.preset_data.get("paragraph", {}).get("paragraph", {}).get("shot", {}).get("cn", {})
text = text.replace("<shot_detail>", shot_detail_str)
text = text.replace("<shot_detail_global>", shot_detail_global_str)
text = text.replace("<shot_detail_shot>", shot_detail_shot_str)
text = text.replace("<paragraph_global>", paragraph_global_str if mode!="single image" else "")
text = text.replace("<paragraph_shot>", paragraph_shot_str if mode!="single image" else "")
return text
@@ -95,13 +112,13 @@ class SysPromptBuilder:
except Exception as e:
return ""
def _build_common_parts(self, model_type: str, mode: str, shot_continuity: bool, shot_script_for: bool,
detail: str, json_detail_format: bool, input_as_1st_shot: bool) -> list:
def _build_common_parts(self, model_type: str, mode: str, shot_continuity: bool, shot_for: bool,
detail: str, shot_detail: bool, input_as_1st_shot: bool) -> list:
"""Build common template parts for both LLM and VLM"""
json_key = "json_detail" if json_detail_format else "no_json_detail"
json_key = "json_detail" if shot_detail else "no_json_detail"
if mode == "single image":
if json_detail_format:
if shot_detail:
return [
[model_type, "single", "part_01"],
["detail_preset", detail],
@@ -116,53 +133,100 @@ class SysPromptBuilder:
[model_type, "single", "part_02"],
["add_json_detail", "single", json_key]
]
else: # shot script
if json_detail_format:
elif mode == "shot script":
if shot_detail:
return [
[model_type, "script", "part_01"],
["detail_preset", detail],
(["first_shot_01"] if input_as_1st_shot else []),
(["no_story_rule"] if shot_script_for else []),
(["no_story_rule"] if shot_for else []),
[model_type, "script", "part_02"],
["split_shots"],
[model_type, "script", "part_03"],
(["first_shot_02"] if input_as_1st_shot else []),
["detail_preset", detail],
(["no_story_rule"] if shot_script_for else []),
(["no_story_rule"] if shot_for else []),
(["shot_continuity"] if shot_continuity else []),
[model_type, "script", "part_04"],
(["first_shot_03"] if input_as_1st_shot else []),
["add_json_detail", "script", json_key],
["json_detail", "common"],
(["json_detail", "for_image", "description"] if (not shot_script_for) or (not shot_continuity and shot_script_for) else []),
(["json_detail", "for_video", "description_shot"] if shot_continuity and shot_script_for else []),
(["json_detail", "for_image", "description"] if (not shot_for) or (not shot_continuity and shot_for) else []),
# 以上与paragraph一致↓
(["json_detail", "for_video", "description_shot"] if shot_continuity and shot_for else []),
[model_type, "script", "part_05"],
["combine_shots"],
(["json_detail", "for_video", "description_global"] if shot_continuity and shot_script_for else []),
(["json_detail", "for_video", "description_global_prefix"] if shot_continuity and shot_for else []),
(["json_detail", "for_video", "description_global"] if shot_continuity and shot_for else []),
]
else:
return [
[model_type, "script", "part_01"],
["detail_preset", detail],
(["no_story_rule"] if shot_script_for else []),
(["no_story_rule"] if shot_for else []),
(["first_shot_01"] if input_as_1st_shot else []),
[model_type, "script", "part_02"],
["split_shots"],
[model_type, "script", "part_03"],
(["first_shot_02"] if input_as_1st_shot else []),
["detail_preset", detail],
(["no_story_rule"] if shot_script_for else []),
(["no_story_rule"] if shot_for else []),
(["shot_continuity"] if shot_continuity else []),
[model_type, "script", "part_04"],
(["first_shot_03"] if input_as_1st_shot else []),
["add_json_detail", "script", json_key],
(["video_common_description_no_json_detail", "shot"] if shot_continuity and shot_script_for else []),
# 以上与paragraph一致↓
(["video_common_description_no_json_detail", "shot"] if shot_continuity and shot_for else []),
[model_type, "script", "part_05"],
["combine_shots"],
(["video_common_description_no_json_detail", "global"] if shot_continuity and shot_script_for else []),
["combine_shots"],
(["video_common_description_no_json_detail", "global_prefix"] if shot_continuity and shot_for else []),
(["video_common_description_no_json_detail", "global"] if shot_continuity and shot_for else []),
]
else: # shot paragraph
if shot_detail:
return [
[model_type, "script", "part_01"],
["detail_preset", detail],
(["first_shot_01"] if input_as_1st_shot else []),
(["no_story_rule"] if shot_for else []),
[model_type, "script", "part_02"],
["split_shots"],
[model_type, "script", "part_03"],
(["first_shot_02"] if input_as_1st_shot else []),
["detail_preset", detail],
(["no_story_rule"] if shot_for else []),
(["shot_continuity"] if shot_continuity else []),
[model_type, "script", "part_04"],
(["first_shot_03"] if input_as_1st_shot else []),
["add_json_detail", "script", json_key],
["json_detail", "common"],
(["json_detail", "for_image", "description"] if (not shot_for) or (not shot_continuity and shot_for) else []),
# 以上与script一致↑
(["json_detail", "for_video", "description_global"] if shot_continuity and shot_for else []),
(["json_detail", "for_video", "description_shot"] if shot_continuity and shot_for else []),
]
else:
return [
[model_type, "script", "part_01"],
["detail_preset", detail],
(["no_story_rule"] if shot_for else []),
(["first_shot_01"] if input_as_1st_shot else []),
[model_type, "script", "part_02"],
["split_shots"],
[model_type, "script", "part_03"],
(["first_shot_02"] if input_as_1st_shot else []),
["detail_preset", detail],
(["no_story_rule"] if shot_for else []),
(["shot_continuity"] if shot_continuity else []),
[model_type, "script", "part_04"],
(["first_shot_03"] if input_as_1st_shot else []),
["add_json_detail", "script", json_key],
# 以上与script一致↑
(["video_common_description_no_json_detail", "global"] if shot_continuity and shot_for else []),
(["video_common_description_no_json_detail", "shot"] if shot_continuity and shot_for else []),
]
def build_prompt(self, model: str, mode: str, shot_continuity: bool, shot_script_for: bool, detail: str, json_detail_format: bool,
def build_prompt(self, model: str, mode: str, shot_continuity: bool, shot_for: bool, detail: str, shot_detail: bool,
shot_count: int, input_as_1st_shot: bool, system_language: str, output_language: str) -> str:
"""Build prompt based on input parameters"""
@@ -170,7 +234,7 @@ class SysPromptBuilder:
model_type = model.lower()
# Build template parts based on model and mode
template_parts = self._build_common_parts(model_type, mode, shot_continuity, shot_script_for, detail, json_detail_format, input_as_1st_shot)
template_parts = self._build_common_parts(model_type, mode, shot_continuity, shot_for, detail, shot_detail, input_as_1st_shot)
# Add final part for LLM
if model == "Text":
@@ -186,7 +250,7 @@ class SysPromptBuilder:
# Combine all parts and replace variables
full_prompt = "".join(prompt_parts)
full_prompt = self._replace_variables(full_prompt, output_language, shot_count, json_detail_format)
full_prompt = self._replace_variables(mode, full_prompt, system_language, output_language, shot_count)
return full_prompt
@@ -201,15 +265,15 @@ class SystemPrompter_JK:
"default": "Text",
"tooltip": "Select model type. Text: generate prompt from user text; Image: generate prompt from ref image."
}),
"mode": (["single image", "shot script"], {
"mode": (["single image", "shot script", "shot paragraph"], {
"default": "single image",
"tooltip": "Select mode: single image or shot script."
"tooltip": "Select mode: single image, shot script or one shot paragraph."
}),
"shot_continuity": ("BOOLEAN", {
"default": True,
"tooltip": "Shot script continuity setting."
}),
"shot_script_for": ("BOOLEAN", {
"shot_for": ("BOOLEAN", {
"default": False,
"label_off": "image",
"label_on": "video",
@@ -230,7 +294,7 @@ class SystemPrompter_JK:
"default": "detailed",
"tooltip": "Select detail level for prompt generation."
}),
"json_detail_format": ("BOOLEAN", {
"shot_detail": ("BOOLEAN", {
"default": False,
"tooltip": "Only available for QWen3-VL for now. Whether to output in JSON format with detailed breakdown."
}),
@@ -256,7 +320,7 @@ class SystemPrompter_JK:
def __init__(self):
self.builder = SysPromptBuilder()
def build_prompt(self, model: str, mode: str, shot_continuity: bool, shot_script_for: bool, detail: str, json_detail_format: bool,
def build_prompt(self, model: str, mode: str, shot_continuity: bool, shot_for: bool, detail: str, shot_detail: bool,
shot_count: int, input_as_1st_shot: bool, system_language: str, output_language: str) -> Tuple[str]:
"""Build and return the prompt string"""
@@ -264,9 +328,9 @@ class SystemPrompter_JK:
model=model,
mode=mode,
shot_continuity=shot_continuity,
shot_script_for=shot_script_for,
shot_for=shot_for,
detail=detail,
json_detail_format=json_detail_format,
shot_detail=shot_detail,
shot_count=shot_count,
input_as_1st_shot=input_as_1st_shot,
system_language=system_language,
@@ -346,7 +410,7 @@ class ShotScriptCombiner_JK:
result = []
# 检查是否是特殊JSON格式(包含subject, background等字段)
is_special_format = ShotScriptUtils.is_special_json_detail_format(data)
is_special_format = ShotScriptUtils.is_special_shot_detail(data)
# 如果是特殊格式且没有shot键,直接作为单个shot处理
if is_special_format and not ShotScriptUtils.has_shot_keys(data, max_shots):
+1 -1
View File
@@ -1132,7 +1132,7 @@ class ShotScriptUtils:
return [str(key) for key in data.keys()]
@staticmethod
def is_special_json_detail_format(data: dict) -> bool:
def is_special_shot_detail(data: dict) -> bool:
"""检查是否是特殊JSON格式(包含subject, background等字段)"""
special_fields = [
'subject', 'background', 'atmosphere', 'lighting',
+47 -17
View File
@@ -87,24 +87,28 @@
},
"json_detail": {
"common": {
"en": "The detailed descriptions include the subject, background(scene, season, time, weather), atmosphere(mood and color tone), action, expression, lighting, camera type (shot size and view angle), camera movement, and style. ",
"cn": "细节描述包含主体、背景(场景、季节、时间、天气)、氛围(气氛及色调)、动作、表情、灯光、相机类型(景别及视角)、相机运动、风格。"
"en": "The detailed descriptions include the subject, background(scene, season, time, weather, ambient sound<if any>), atmosphere(mood and color tone), action (and voice (if any)), expression, lighting, camera type (shot size and view angle), camera movement, and style. ",
"cn": "细节描述包含主体、背景(场景、季节、时间、天气、声音(如果有))、氛围(气氛及色调)、动作(及话音(如果有))、表情、灯光、相机类型(景别及视角)、相机运动、风格。"
},
"for_image": {
"description": {
"en": "Place the detailed descriptions into the corresponding areas of a JSON file and output them in <specified_language> language, <json_detail_format>; 'motion' refers to the action of the subject, and 'movement' refers to the movement of the camera; \n",
"cn": "将细节描述放入JSON格式中对应区域并以<特定语种>输出,<json_detail_format>;'motion'指主体动作,'movement'指相机运动;\n"
"en": "Place the detailed descriptions into the corresponding areas of a JSON file and output them in <specified_language> language, <shot_detail>; 'motion' refers to the action of the subject, and 'movement' refers to the movement of the camera; \n",
"cn": "将细节描述放入JSON格式中对应区域并以<特定语种>输出,<shot_detail>;'motion'指主体动作,'movement'指相机运动;\n"
},
"format": "{'subject': '', 'background': '', 'atmosphere': '', 'motion': '', 'expression': '', 'lighting': '', 'shot type': '', 'movement': '', 'style': ''}"
},
"for_video": {
"description_global_prefix": {
"en": "Add a 'Global' item at the beginning of the JSON format, ",
"cn": "在JSON格式最前面增加'Global'项,"
},
"description_global": {
"en": "Add a 'Global' item at the beginning of the JSON format, place the subject, background(scene, season, time, weather), atmosphere(mood and color tone), action, expression, lighting, camera type (shot size and view angle), camera movement, and style detailed descriptions of all shot scripts in the corresponding area and output them in <specified_language>, <json_detail_format_global>; \n",
"cn": "在JSON格式最前面增加'Global'项,将主体、背景(场景、季节、时间、天气)、氛围(气氛及色调)、灯光、风格细节描述放入对应区域并以<特定语种>输出,<json_detail_format_global>;\n"
"en": "get the subject, background(scene, season, time, weather, ambient sound<if any>), atmosphere(mood and color tone), lighting, and style descriptions of all shot scripts, <paragraph_global> and place them in the corresponding area of a JSON file and output them in <specified_language>, <shot_detail_global>; \n",
"cn": "将所有分镜头提示词中的主体、背景(场景、季节、时间、天气、声音(如果有))、氛围(气氛及色调)、灯光、风格等内容<paragraph_global>放入JSON格式中对应区域并以<特定语种>输出,<shot_detail_global>;\n"
},
"description_shot": {
"en": "Place the action, expression, lighting, camera type (shot size and view angle), camera movement detailed descriptions into the corresponding areas of a JSON file and output them in <specified_language> language, <json_detail_format_shot>; 'motion' refers to the action of the subject, and 'movement' refers to the movement of the camera; \n",
"cn": "将各分镜头的动作、表情、相机类型(景别及视角)、相机运动细节描述放入JSON格式中对应区域并以<特定语种>输出,<json_detail_format_shot>;'motion'指主体动作,'movement'指相机运动;\n"
"en": "Get the action (and voice (if any)), expression, lighting, camera type (shot size and view angle), camera movement descriptions, <paragraph_shot> and place them into the corresponding areas of a JSON file and output them in <specified_language> language, <shot_detail_shot>; 'motion' refers to the action of the subject, and 'movement' refers to the movement of the camera; The value cannot be in list format; \n",
"cn": "将各分镜头提示词中的动作(及话音(如果有))、表情、相机类型(景别及视角)、相机运动等内容<paragraph_shot>放入JSON格式中对应区域并以<特定语种>输出,<shot_detail_shot>;'motion'指主体动作,'movement'指相机运动;值不能为列表格式;\n"
},
"format_global": "{'subject': '', 'background': '', 'atmosphere': '', 'lighting': '', 'style': ''}",
"format_shot": "{'motion': '', 'expression': '', 'shot type': '', 'movement': ''}"
@@ -123,11 +127,11 @@
},
"script": {
"json_detail": {
"en": "Split the detailed descriptions for each all shot scripts, ",
"cn": "对所有分镜头脚本进行细节描述的拆分,"
"en": "Split the detailed descriptions for each all shot prompts, ",
"cn": "对所有分镜头提示词进行细节描述的拆分,"
},
"no_json_detail": {
"en": "Each shot script is the generated <specified_language> language shot script; \n",
"en": "Each shot script is the generated <specified_language> language shot prompt; \n",
"cn": "每个镜头的分镜脚本即生成的<特定语种>分镜提示词;\n"
}
}
@@ -145,18 +149,44 @@
"cn": "将所有分镜头脚本放在按JSON格式代表对应镜头编号的英文名称简写中(以'shot'_为前缀)。\n"
},
"video_common_description_no_json_detail": {
"global_prefix": {
"en": "Add a 'Global' item at the beginning of the JSON format, ",
"cn": "在JSON格式最前面增加'Global'项,"
},
"global": {
"en": "Add a 'Global' item at the beginning of the JSON format, redescribe the subject (such as characters), background (such as scenes), lighting and visual style in one sentence and output in <specified_language>. \n",
"cn": "在JSON格式最前面增加'Global'项,将所有分镜头脚本中的主体(如人物)、背景(如场景)、灯光及画面风格等内容,重新描述成一句话并按<特定语种>输出。\n"
"en": "get the subject, background(scene, season, time, weather, ambient sound<if any>), atmosphere(mood and color tone), lighting, and style of all shot prompts, <paragraph_global> and output in <specified_language>. \n",
"cn": "将所有分镜头脚本中的主体、背景(场景、季节、时间、天气、声音(如果有))、氛围(气氛及色调)、灯光、风格等内容<paragraph_global>并按<特定语种>输出。\n"
},
"shot": {
"en": "Redescribes motions, expressions, camera type (shot size and angle), and camera movement and output them in <specified_language> for Each shot script; \n",
"cn": "将每个分镜脚本中的动作、表情、相机类型(景别及视角)、相机运动等内容,重新描述并以<特定语种>输出;\n"
"en": "get the action (and voice (if any)), expression, lighting, camera type (shot size and view angle), camera movement of all shot prompts, <paragraph_shot> and output them in <specified_language> for Each shot script; \n",
"cn": "将每个分镜脚本中的动作(及话音(如果有))、表情、相机类型(景别及视角)、相机运动等内容<paragraph_shot>并以<特定语种>输出;\n"
}
},
"no_story_rule": {
"en": " Do not describe subject's internal thoughts or motivations; ",
"cn": "不要描述主体的内心想法或动机;"
"en": " Do not describe subject's internal thoughts or motivations; Do not use hyperbole, metaphor, personification, or other rhetorical devices; ",
"cn": "不要描述主体的内心想法或动机;不要使用夸张、比喻等修辞手法;"
},
"paragraph": {
"script": {
"global": {
"en": ", write it into a continuous paragraph using natural language, ",
"cn": ",使用自然语言编写成一个连续段落,"
},
"shot": {
"en": ", write it into a continuous paragraph using natural language, ",
"cn": ",使用自然语言编写成一个连续段落,"
}
},
"paragraph": {
"global": {
"en": ", write it into a continuous paragraph using natural language, ",
"cn": ",使用自然语言合并编写成一个连续段落,"
},
"shot": {
"en": ", using time conjunctions (such as 'first', 'then', 'finally'), combine them using natural language according to the sequence of shots, and write them into consecutive paragraphs, the use of shot numbers or timestamps is strictly prohibited, ",
"cn": ",使用时间连接词(例如“首先”、“然后”、“最后”),按分镜头顺序使用自然语言合并,并编写成连续段落,严禁使用镜头编号或时间戳,"
}
}
},
"first_shot_01": {
"en": " And use this prompt as the first shot script;",
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-jakeupgrade"
description = "ComfyUI workflow customization by Jake."
version = "2.6.4"
version = "2.6.5"
license = {file = "LICENSE"}
dependencies = ["opencv-python", "piexif", "torch", "torchvision", "numpy", "pillow", "simpleeval", "PyYAML", "toml"]