Skip to content

Commit 315930f

Browse files
authored
feat: (m-decompose) Module Prompt V3 (#770)
* clean: use default model * add: decomp prompt v3 * upd: enhance general instruction module prompt * upd: enhance subtask constraint assign module prompt * upd: setup the default model * clean: deleted dump results * upd: clean example * upd: setup default model * clean: pre-commit * clean: tmp file * sync: revert to the main * sync: revert to the main * sync: revert to the main * clean: pre-commit format * fix: pre-commit format * mod: pre-commit modifcation * mod: pre-commit clean * mod: pre-commit clean * mod: pre-commit clean * add: enable the v3 * clean: version verify * upd: unify the constraint tag parsing with more item variants * upd: update decompose tests up-to-date * clean: pre-commit * clean: pre-commit module * clean: pre-commit test
1 parent 88a4d75 commit 315930f

11 files changed

Lines changed: 400 additions & 507 deletions

File tree

cli/decompose/decompose.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class DecompVersion(StrEnum):
4242
latest = "latest"
4343
v1 = "v1"
4444
v2 = "v2"
45-
# v3 = "v3"
45+
v3 = "v3"
4646

4747

4848
this_file_dir = Path(__file__).resolve().parent
@@ -226,6 +226,15 @@ def run(
226226
case_sensitive=False,
227227
),
228228
] = LogMode.demo,
229+
enable_script_run: Annotated[
230+
bool,
231+
typer.Option(
232+
help=(
233+
"When true, generated scripts expose argparse runtime options "
234+
"for backend, model, endpoint, and API key overrides."
235+
)
236+
),
237+
] = False,
229238
) -> None:
230239
"""Runs the ``m decompose`` CLI workflow and writes generated outputs.
231240
@@ -253,6 +262,8 @@ def run(
253262
prompts and programs. Each name must be a valid non-keyword Python
254263
identifier.
255264
log_mode: Logging verbosity for CLI and pipeline execution.
265+
enable_script_run: Whether generated scripts should expose argparse
266+
runtime options. Defaults to ``False``.
256267
257268
Raises:
258269
AssertionError: If ``out_name`` is invalid, ``out_dir`` does not name an
@@ -277,6 +288,7 @@ def run(
277288
logger.info("model_id : %s", model_id)
278289
logger.info("version : %s", version.value)
279290
logger.info("log_mode : %s", log_mode.value)
291+
logger.info("script options : %s", enable_script_run)
280292
logger.info("input_vars : %s", input_var or "[]")
281293

282294
environment = Environment(
@@ -393,6 +405,11 @@ def run(
393405
subtasks=decomp_data["subtasks"],
394406
user_inputs=input_var,
395407
identified_constraints=decomp_data["identified_constraints"],
408+
model_id=model_id,
409+
backend=backend.value,
410+
backend_endpoint=backend_endpoint,
411+
backend_api_key=backend_api_key,
412+
enable_script_run=enable_script_run,
396413
)
397414
+ "\n"
398415
)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{% if user_inputs -%}
2+
import os
3+
{% endif -%}
4+
import textwrap
5+
6+
import mellea
7+
8+
{%- set ns = namespace(need_req=false) -%}
9+
{%- for item in subtasks -%}
10+
{%- for c in item.constraints or [] -%}
11+
{%- if c.val_fn -%}
12+
{%- set ns.need_req = true -%}
13+
{%- endif -%}
14+
{%- endfor -%}
15+
{%- endfor %}
16+
17+
{%- if ns.need_req %}
18+
from mellea.stdlib.requirements import req
19+
{%- for c in identified_constraints %}
20+
{%- if c.val_fn and c.val_fn_name %}
21+
from validations.{{ c.val_fn_name }} import validate_input as {{ c.val_fn_name }}
22+
{%- endif %}
23+
{%- endfor %}
24+
{%- endif %}
25+
26+
m = mellea.start_session(model_id="mistral-small3.2:latest")
27+
{%- if user_inputs %}
28+
29+
30+
# User Input Variables
31+
try:
32+
{%- for var in user_inputs %}
33+
{{ var | lower }} = os.environ["{{ var | upper }}"]
34+
{%- endfor %}
35+
except KeyError as e:
36+
raise SystemExit(f"ERROR: One or more required environment variables are not set: {e}")
37+
{%- endif %}
38+
{%- for item in subtasks %}
39+
40+
41+
{{ item.tag | lower }}_gnrl = textwrap.dedent(
42+
R"""
43+
{{ item.general_instructions | trim | indent(width=4, first=False) }}
44+
""".strip()
45+
)
46+
{{ item.tag | lower }} = m.instruct(
47+
{%- if not (item.input_vars_required or []) %}
48+
{{ item.subtask[3:] | trim | tojson }},
49+
{%- else %}
50+
textwrap.dedent(
51+
R"""
52+
{{ item.subtask[3:] | trim }}
53+
54+
Here are the input variables and their content:
55+
{%- for var in item.input_vars_required or [] %}
56+
57+
- {{ var | upper }} = {{ "{{" }}{{ var | upper }}{{ "}}" }}
58+
{%- endfor %}
59+
""".strip()
60+
),
61+
{%- endif %}
62+
{%- if item.constraints %}
63+
requirements=[
64+
{%- for c in item.constraints %}
65+
{%- if c.val_fn and c.val_fn_name %}
66+
req(
67+
{{ c.constraint | tojson}},
68+
validation_fn={{ c.val_fn_name }},
69+
),
70+
{%- else %}
71+
{{ c.constraint | tojson}},
72+
{%- endif %}
73+
{%- endfor %}
74+
],
75+
{%- else %}
76+
requirements=None,
77+
{%- endif %}
78+
{%- if item.input_vars_required %}
79+
user_variables={
80+
{%- for var in item.input_vars_required or [] %}
81+
{{ var | upper | tojson }}: {{ var | lower }},
82+
{%- endfor %}
83+
},
84+
{%- endif %}
85+
grounding_context={
86+
"GENERAL_INSTRUCTIONS": {{ item.tag | lower }}_gnrl,
87+
{%- for var in item.depends_on or [] %}
88+
{{ var | upper | tojson }}: {{ var | lower }}.value,
89+
{%- endfor %}
90+
},
91+
)
92+
assert {{ item.tag | lower }}.value is not None, 'ERROR: task "{{ item.tag | lower }}" execution failed'
93+
{%- if loop.last %}
94+
95+
96+
final_answer = {{ item.tag | lower }}.value
97+
98+
print(final_answer)
99+
{%- endif -%}
100+
{%- endfor -%}

cli/decompose/prompt_modules/general_instructions/_general_instructions.py

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,41 @@
1313
T = TypeVar("T")
1414

1515
RE_GENERAL_INSTRUCTIONS = re.compile(
16-
r"<general_instructions>(.+?)</general_instructions>",
16+
r"<general_instructions>(.*?)</general_instructions>",
1717
flags=re.IGNORECASE | re.DOTALL,
1818
)
1919

20+
RE_GENERAL_INSTRUCTIONS_OPEN = re.compile(
21+
r"<general_instructions>(.*)", flags=re.IGNORECASE | re.DOTALL
22+
)
23+
24+
RE_FINAL_SENTENCE = re.compile(
25+
r"\n*All tags are closed and my assignment is finished\.\s*$", flags=re.IGNORECASE
26+
)
27+
2028

2129
@final
2230
class _GeneralInstructions(PromptModule):
2331
@staticmethod
2432
def _default_parser(generated_str: str) -> str:
2533
general_instructions_match = re.search(RE_GENERAL_INSTRUCTIONS, generated_str)
2634

27-
general_instructions_str: str | None = (
28-
general_instructions_match.group(1).strip()
29-
if general_instructions_match
30-
else None
31-
)
32-
33-
if general_instructions_str is None:
34-
raise TagExtractionError(
35-
'LLM failed to generate correct tags for extraction: "<general_instructions>"'
35+
if general_instructions_match:
36+
general_instructions_str = general_instructions_match.group(1).strip()
37+
else:
38+
# fallback: opening tag only (in case the closing tag is missing)
39+
general_instructions_match = re.search(
40+
RE_GENERAL_INSTRUCTIONS_OPEN, generated_str
3641
)
42+
if not general_instructions_match:
43+
raise TagExtractionError(
44+
'LLM failed to generate correct tags for extraction: "<general_instructions>"'
45+
)
46+
general_instructions_str = general_instructions_match.group(1).strip()
47+
48+
general_instructions_str = re.sub(
49+
RE_FINAL_SENTENCE, "", general_instructions_str
50+
).strip()
3751

3852
return general_instructions_str
3953

@@ -50,20 +64,19 @@ def generate(
5064

5165
system_prompt = get_system_prompt()
5266
user_prompt = get_user_prompt(task_prompt=input_str)
53-
5467
action = Message("user", user_prompt)
5568

69+
model_options = {
70+
ModelOption.SYSTEM_PROMPT: system_prompt,
71+
ModelOption.TEMPERATURE: 0,
72+
ModelOption.MAX_NEW_TOKENS: max_new_tokens,
73+
}
74+
5675
try:
57-
gen_result = mellea_session.act(
58-
action=action,
59-
model_options={
60-
ModelOption.SYSTEM_PROMPT: system_prompt,
61-
ModelOption.TEMPERATURE: 0,
62-
ModelOption.MAX_NEW_TOKENS: max_new_tokens,
63-
},
64-
).value
76+
response = mellea_session.act(action=action, model_options=model_options)
77+
gen_result = response.value
6578
except Exception as e:
66-
raise BackendGenerationError(f"LLM generation failed: {e}")
79+
raise BackendGenerationError(f"LLM generation failed: {e}") from e
6780

6881
if gen_result is None:
6982
raise BackendGenerationError(

cli/decompose/prompt_modules/general_instructions/_prompt/system_template.jinja2

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Do not write anything between </general_instructions> and the final sentence exc
1313
Here are some complete examples to guide you on how to complete your assignment:
1414

1515
{% for item in icl_examples -%}
16-
<example>
16+
<example_{{ loop.index }}>
1717
<task_prompt>
1818
{{ item["task_prompt"] }}
1919
</task_prompt>
@@ -22,7 +22,7 @@ Here are some complete examples to guide you on how to complete your assignment:
2222
</general_instructions>
2323

2424
All tags are closed and my assignment is finished.
25-
</example>
25+
</example_{{ loop.index }}>
2626

2727
{% endfor -%}
2828
That concludes the complete examples of your assignment.

0 commit comments

Comments
 (0)