diff --git a/cli/decompose/decompose.py b/cli/decompose/decompose.py
index 72bc5d7ea..9d2572a28 100644
--- a/cli/decompose/decompose.py
+++ b/cli/decompose/decompose.py
@@ -42,7 +42,7 @@ class DecompVersion(StrEnum):
latest = "latest"
v1 = "v1"
v2 = "v2"
- # v3 = "v3"
+ v3 = "v3"
this_file_dir = Path(__file__).resolve().parent
@@ -226,6 +226,15 @@ def run(
case_sensitive=False,
),
] = LogMode.demo,
+ enable_script_run: Annotated[
+ bool,
+ typer.Option(
+ help=(
+ "When true, generated scripts expose argparse runtime options "
+ "for backend, model, endpoint, and API key overrides."
+ )
+ ),
+ ] = False,
) -> None:
"""Runs the ``m decompose`` CLI workflow and writes generated outputs.
@@ -253,6 +262,8 @@ def run(
prompts and programs. Each name must be a valid non-keyword Python
identifier.
log_mode: Logging verbosity for CLI and pipeline execution.
+ enable_script_run: Whether generated scripts should expose argparse
+ runtime options. Defaults to ``False``.
Raises:
AssertionError: If ``out_name`` is invalid, ``out_dir`` does not name an
@@ -277,6 +288,7 @@ def run(
logger.info("model_id : %s", model_id)
logger.info("version : %s", version.value)
logger.info("log_mode : %s", log_mode.value)
+ logger.info("script options : %s", enable_script_run)
logger.info("input_vars : %s", input_var or "[]")
environment = Environment(
@@ -393,6 +405,11 @@ def run(
subtasks=decomp_data["subtasks"],
user_inputs=input_var,
identified_constraints=decomp_data["identified_constraints"],
+ model_id=model_id,
+ backend=backend.value,
+ backend_endpoint=backend_endpoint,
+ backend_api_key=backend_api_key,
+ enable_script_run=enable_script_run,
)
+ "\n"
)
diff --git a/cli/decompose/m_decomp_result_v3.py.jinja2 b/cli/decompose/m_decomp_result_v3.py.jinja2
new file mode 100644
index 000000000..5e791c29d
--- /dev/null
+++ b/cli/decompose/m_decomp_result_v3.py.jinja2
@@ -0,0 +1,100 @@
+{% if user_inputs -%}
+import os
+{% endif -%}
+import textwrap
+
+import mellea
+
+{%- set ns = namespace(need_req=false) -%}
+{%- for item in subtasks -%}
+ {%- for c in item.constraints or [] -%}
+ {%- if c.val_fn -%}
+ {%- set ns.need_req = true -%}
+ {%- endif -%}
+ {%- endfor -%}
+{%- endfor %}
+
+{%- if ns.need_req %}
+from mellea.stdlib.requirements import req
+{%- for c in identified_constraints %}
+{%- if c.val_fn and c.val_fn_name %}
+from validations.{{ c.val_fn_name }} import validate_input as {{ c.val_fn_name }}
+{%- endif %}
+{%- endfor %}
+{%- endif %}
+
+m = mellea.start_session(model_id="mistral-small3.2:latest")
+{%- if user_inputs %}
+
+
+# User Input Variables
+try:
+ {%- for var in user_inputs %}
+ {{ var | lower }} = os.environ["{{ var | upper }}"]
+ {%- endfor %}
+except KeyError as e:
+ raise SystemExit(f"ERROR: One or more required environment variables are not set: {e}")
+{%- endif %}
+{%- for item in subtasks %}
+
+
+{{ item.tag | lower }}_gnrl = textwrap.dedent(
+ R"""
+ {{ item.general_instructions | trim | indent(width=4, first=False) }}
+ """.strip()
+)
+{{ item.tag | lower }} = m.instruct(
+ {%- if not (item.input_vars_required or []) %}
+ {{ item.subtask[3:] | trim | tojson }},
+ {%- else %}
+ textwrap.dedent(
+ R"""
+ {{ item.subtask[3:] | trim }}
+
+ Here are the input variables and their content:
+ {%- for var in item.input_vars_required or [] %}
+
+ - {{ var | upper }} = {{ "{{" }}{{ var | upper }}{{ "}}" }}
+ {%- endfor %}
+ """.strip()
+ ),
+ {%- endif %}
+ {%- if item.constraints %}
+ requirements=[
+ {%- for c in item.constraints %}
+ {%- if c.val_fn and c.val_fn_name %}
+ req(
+ {{ c.constraint | tojson}},
+ validation_fn={{ c.val_fn_name }},
+ ),
+ {%- else %}
+ {{ c.constraint | tojson}},
+ {%- endif %}
+ {%- endfor %}
+ ],
+ {%- else %}
+ requirements=None,
+ {%- endif %}
+ {%- if item.input_vars_required %}
+ user_variables={
+ {%- for var in item.input_vars_required or [] %}
+ {{ var | upper | tojson }}: {{ var | lower }},
+ {%- endfor %}
+ },
+ {%- endif %}
+ grounding_context={
+ "GENERAL_INSTRUCTIONS": {{ item.tag | lower }}_gnrl,
+ {%- for var in item.depends_on or [] %}
+ {{ var | upper | tojson }}: {{ var | lower }}.value,
+ {%- endfor %}
+ },
+)
+assert {{ item.tag | lower }}.value is not None, 'ERROR: task "{{ item.tag | lower }}" execution failed'
+{%- if loop.last %}
+
+
+final_answer = {{ item.tag | lower }}.value
+
+print(final_answer)
+{%- endif -%}
+{%- endfor -%}
diff --git a/cli/decompose/prompt_modules/general_instructions/_general_instructions.py b/cli/decompose/prompt_modules/general_instructions/_general_instructions.py
index 26b51c43a..39ec82ade 100644
--- a/cli/decompose/prompt_modules/general_instructions/_general_instructions.py
+++ b/cli/decompose/prompt_modules/general_instructions/_general_instructions.py
@@ -13,10 +13,18 @@
T = TypeVar("T")
RE_GENERAL_INSTRUCTIONS = re.compile(
- r"(.+?)",
+ r"(.*?)",
flags=re.IGNORECASE | re.DOTALL,
)
+RE_GENERAL_INSTRUCTIONS_OPEN = re.compile(
+ r"(.*)", flags=re.IGNORECASE | re.DOTALL
+)
+
+RE_FINAL_SENTENCE = re.compile(
+ r"\n*All tags are closed and my assignment is finished\.\s*$", flags=re.IGNORECASE
+)
+
@final
class _GeneralInstructions(PromptModule):
@@ -24,16 +32,22 @@ class _GeneralInstructions(PromptModule):
def _default_parser(generated_str: str) -> str:
general_instructions_match = re.search(RE_GENERAL_INSTRUCTIONS, generated_str)
- general_instructions_str: str | None = (
- general_instructions_match.group(1).strip()
- if general_instructions_match
- else None
- )
-
- if general_instructions_str is None:
- raise TagExtractionError(
- 'LLM failed to generate correct tags for extraction: ""'
+ if general_instructions_match:
+ general_instructions_str = general_instructions_match.group(1).strip()
+ else:
+ # fallback: opening tag only (in case the closing tag is missing)
+ general_instructions_match = re.search(
+ RE_GENERAL_INSTRUCTIONS_OPEN, generated_str
)
+ if not general_instructions_match:
+ raise TagExtractionError(
+ 'LLM failed to generate correct tags for extraction: ""'
+ )
+ general_instructions_str = general_instructions_match.group(1).strip()
+
+ general_instructions_str = re.sub(
+ RE_FINAL_SENTENCE, "", general_instructions_str
+ ).strip()
return general_instructions_str
@@ -50,20 +64,19 @@ def generate(
system_prompt = get_system_prompt()
user_prompt = get_user_prompt(task_prompt=input_str)
-
action = Message("user", user_prompt)
+ model_options = {
+ ModelOption.SYSTEM_PROMPT: system_prompt,
+ ModelOption.TEMPERATURE: 0,
+ ModelOption.MAX_NEW_TOKENS: max_new_tokens,
+ }
+
try:
- gen_result = mellea_session.act(
- action=action,
- model_options={
- ModelOption.SYSTEM_PROMPT: system_prompt,
- ModelOption.TEMPERATURE: 0,
- ModelOption.MAX_NEW_TOKENS: max_new_tokens,
- },
- ).value
+ response = mellea_session.act(action=action, model_options=model_options)
+ gen_result = response.value
except Exception as e:
- raise BackendGenerationError(f"LLM generation failed: {e}")
+ raise BackendGenerationError(f"LLM generation failed: {e}") from e
if gen_result is None:
raise BackendGenerationError(
diff --git a/cli/decompose/prompt_modules/general_instructions/_prompt/system_template.jinja2 b/cli/decompose/prompt_modules/general_instructions/_prompt/system_template.jinja2
index 58047c155..d806cf6bc 100644
--- a/cli/decompose/prompt_modules/general_instructions/_prompt/system_template.jinja2
+++ b/cli/decompose/prompt_modules/general_instructions/_prompt/system_template.jinja2
@@ -13,7 +13,7 @@ Do not write anything between and the final sentence exc
Here are some complete examples to guide you on how to complete your assignment:
{% for item in icl_examples -%}
-
+
{{ item["task_prompt"] }}
@@ -22,7 +22,7 @@ Here are some complete examples to guide you on how to complete your assignment:
All tags are closed and my assignment is finished.
-
+
{% endfor -%}
That concludes the complete examples of your assignment.
diff --git a/cli/decompose/prompt_modules/subtask_constraint_assign/_subtask_constraint_assign.py b/cli/decompose/prompt_modules/subtask_constraint_assign/_subtask_constraint_assign.py
index 45f56df0a..50a152648 100644
--- a/cli/decompose/prompt_modules/subtask_constraint_assign/_subtask_constraint_assign.py
+++ b/cli/decompose/prompt_modules/subtask_constraint_assign/_subtask_constraint_assign.py
@@ -4,6 +4,7 @@
from mellea import MelleaSession
from mellea.backends import ModelOption
+from mellea.core import FancyLogger
from mellea.stdlib.components import Message
from .._prompt_modules import PromptModule, PromptModuleString
@@ -11,6 +12,8 @@
from ._prompt import get_system_prompt, get_user_prompt
from ._types import SubtaskPromptConstraintsItem
+FancyLogger.get_logger().setLevel("DEBUG")
+
T = TypeVar("T")
RE_GEN_DATA_FORMAT = re.compile(
@@ -19,10 +22,20 @@
)
RE_ASSIGNED_CONS = re.compile(
- r"(.+?)",
+ r"(.*?)",
flags=re.IGNORECASE | re.DOTALL,
)
+# Regex to match common list formats produced by LLMs:
+# - bullet lists: -, *, •, en dash, em dash
+# - numbered lists: 1. 1)
+RE_LIST_ITEM = re.compile(r"^\s*(?:[-*•]|[–—]|(?:\d+)[.)])\s*(.+?)\s*$")
+
+# Regex to remove trailing "closing sentence" often generated by LLMs
+RE_TRAILING_CLOSURE = re.compile(
+ r"\n*All tags are closed and my assignment is finished\.\s*$", flags=re.IGNORECASE
+)
+
class SubtaskConstraintAssignArgs(TypedDict):
subtasks_tags_and_prompts: Sequence[tuple[str, str, str]]
@@ -31,6 +44,52 @@ class SubtaskConstraintAssignArgs(TypedDict):
@final
class _SubtaskConstraintAssign(PromptModule):
+ @staticmethod
+ def _is_na_value(text: str) -> bool:
+ """
+ Check if the entire content represents a "no constraints" signal.
+
+ This avoids false positives such as:
+ "- If missing, return N/A"
+
+ Only returns True if the WHOLE text is equivalent to N/A.
+ """
+ normalized = re.sub(r"\s+", "", text).upper()
+ return normalized in {"N/A", "NA"}
+
+ @staticmethod
+ def _extract_constraints(text: str) -> list[str]:
+ """
+ Extract constraints from list-style text.
+
+ Supports:
+ - "- item"
+ - "* item"
+ - "• item"
+ - "1. item"
+ - "1) item"
+ - "en dash item"
+ - "em dash item"
+
+ Returns:
+ Deduplicated list while preserving order.
+ """
+ constraints: list[str] = []
+
+ for line in text.splitlines():
+ stripped = line.strip()
+ if not stripped:
+ continue
+
+ match = RE_LIST_ITEM.match(line)
+ if match:
+ value = match.group(1).strip()
+ if value:
+ constraints.append(value)
+
+ # Remove duplicates while preserving order
+ return list(dict.fromkeys(constraints))
+
@staticmethod
def _default_parser(generated_str: str) -> list[SubtaskPromptConstraintsItem]:
r"""Default parser of the `subtask_constraint_assign` module.
@@ -39,49 +98,66 @@ def _default_parser(generated_str: str) -> list[SubtaskPromptConstraintsItem]:
on the size and capabilities of the LLM used. The results are also not guaranteed, so
take a look at this module's Exceptions and plan for unreliable results._
+ This parser processes the serialized output produced by the
+ `subtask_constraint_assign.generate()` method and converts it into structured
+ constraint assignments for each subtask.
+
+ The parser is designed to be robust against minor formatting variations in LLM
+ responses. In particular, it:
+ - extracts content from `` tags when present;
+ - falls back to the raw generated text when the expected tags are missing;
+ - supports multiple list formats commonly produced by LLMs, such as `-`, `*`,
+ `•`, `1.`, and `1)`;
+ - treats the entire content as "no constraints" only when the full content is
+ equivalent to `N/A` or `NA`;
+ - falls back to using the full text as a single constraint when non-empty content
+ is present but no list-style items can be parsed.
+
Args:
- generated_str (`str`): The LLM's answer to be parsed
- (this `str` contains the result of the LLM calls
- for each subtask, separated by a character combination
- to enable parsing).
+ generated_str (`str`): The LLM output to be parsed.
+ This string contains the generation results for each subtask,
+ separated by internal delimiters to enable structured parsing.
Returns:
list[SubtaskPromptConstraintsItem]: A `list` of
- `NamedTuple` (`SubtaskPromptConstraintsItem`) where each
- `tuple` contains the "subtask" (`str`), its "tag" (`str`), its
- generated "prompt_template" (`str`), and
- its assigned "constraints" (`list[str]`).
+ `SubtaskPromptConstraintsItem` objects where each item contains:
+ - `subtask` (`str`): the subtask title or description;
+ - `tag` (`str`): the variable/tag associated with the subtask;
+ - `prompt_template` (`str`): the generated prompt template for the subtask;
+ - `constraints` (`list[str]`): the constraints assigned to that subtask.
- Note that the result "constraints" list can be empty.
+ Note that the `constraints` list can be empty.
- For example
+ For example:
```
- [ SubtaskPromptConstraintsItem(
- subtask=,
- tag=,
- prompt_template=
- constraints=
- ),
- ...
+ [
+ SubtaskPromptConstraintsItem(
+ subtask=,
+ tag=,
+ prompt_template=,
+ constraints=,
+ ),
+ ...
]
```
- You can use dot notation to access the values. For example
+ You can use dot notation to access the values. For example:
```
- result: PromptModuleString = # Result of the subtask_constraint_assign.generate() method
+ result: PromptModuleString = ... # Result of subtask_constraint_assign.generate()
parsed_result: list[SubtaskPromptConstraintsItem] = result.parse()
- subtask_0: str = result[0].subtask
- tag_0: str = result[0].tag
- prompt_template_0: str = result[0].prompt_template
- constraints_0: list[str] = result[0].constraints
+ subtask_0: str = parsed_result[0].subtask
+ tag_0: str = parsed_result[0].tag
+ prompt_template_0: str = parsed_result[0].prompt_template
+ constraints_0: list[str] = parsed_result[0].constraints
```
Raises:
- TagExtractionError: An error occurred trying to extract content from the
- generated output. The LLM probably failed to open and close
- the \ tags for one of the subtasks.
+ This parser does not intentionally raise `TagExtractionError` when the
+ expected tags are missing. Instead, it attempts to recover by falling back
+ to raw generated text. Parsing may still fail indirectly if the serialized
+ generation format is malformed beyond recovery.
"""
gen_data = re.findall(RE_GEN_DATA_FORMAT, generated_str)
@@ -90,32 +166,46 @@ def _default_parser(generated_str: str) -> list[SubtaskPromptConstraintsItem]:
for data in gen_data:
data = cast(tuple[str, str, str, str], data)
+ # Try extracting content inside tags
subtask_constraint_assign_match = re.search(RE_ASSIGNED_CONS, data[3])
- subtask_constraint_assign_str: str | None = (
- subtask_constraint_assign_match.group(1).strip()
- if subtask_constraint_assign_match
- else None
- )
-
- if subtask_constraint_assign_str is None:
- raise TagExtractionError(
- 'LLM failed to generate correct tags for extraction: ""'
+ if subtask_constraint_assign_match:
+ subtask_constraint_assign_str = subtask_constraint_assign_match.group(
+ 1
+ ).strip()
+ else:
+ # Fallback to raw text if tags are missing
+ FancyLogger.get_logger().warning(
+ "Expected tags missing from LLM response; falling back to raw response text. "
+ "Downstream stages may receive unstructured content."
)
+ subtask_constraint_assign_str = data[3].strip()
+
+ # Remove common trailing LLM artifacts
+ subtask_constraint_assign_str = re.sub(
+ RE_TRAILING_CLOSURE, "", subtask_constraint_assign_str
+ ).strip()
+
+ # Handle "no constraints" case
+ if _SubtaskConstraintAssign._is_na_value(subtask_constraint_assign_str):
+ subtask_constraint_assign: list[str] = []
- subtask_constraint_assign_str_upper = subtask_constraint_assign_str.upper()
- if (
- "N/A" in subtask_constraint_assign_str_upper
- or "N / A" in subtask_constraint_assign_str_upper
- or "N/ A" in subtask_constraint_assign_str_upper
- or "N /A" in subtask_constraint_assign_str_upper
- ):
- subtask_constraint_assign = []
else:
- subtask_constraint_assign = [
- line.strip()[2:] if line.strip()[:2] == "- " else line.strip()
- for line in subtask_constraint_assign_str.splitlines()
- ]
+ # Try extracting list-style constraints
+ subtask_constraint_assign = (
+ _SubtaskConstraintAssign._extract_constraints(
+ subtask_constraint_assign_str
+ )
+ )
+
+ # -------- Secondary fallback --------
+ # If content exists but no list items were parsed,
+ # treat the whole text as a single constraint.
+ if subtask_constraint_assign_str and not subtask_constraint_assign:
+ FancyLogger.get_logger().warning(
+ "No list-style constraints detected; falling back to full text as a single constraint."
+ )
+ subtask_constraint_assign = [subtask_constraint_assign_str]
result.append(
SubtaskPromptConstraintsItem(
@@ -143,55 +233,69 @@ def generate( # type: ignore[override]
# About the mypy ignore statement above: https://github.com/python/mypy/issues/3737
**kwargs: Unpack[SubtaskConstraintAssignArgs],
) -> PromptModuleString[T]:
- """Receives a list of subtasks (with their tags and template prompts) and a list of
- constraints written in natural language.
+ """Receives a list of subtasks (with their tags and template prompts) and a list
+ of constraints written in natural language.
- Selects and assign, to each subtask, the constraints that the LLM judges
- to be appropriate (amongst the provided constraint list) to each subtask.
+ Selects and assigns, to each subtask, the constraints that the LLM judges
+ to be appropriate (among the provided constraint list) for that subtask.
_**Disclaimer**: This is a LLM-prompting module, so the results will vary depending
on the size and capabilities of the LLM used. The results are also not guaranteed, so
take a look at this module's Exceptions and plan for unreliable results._
Args:
- mellea_session (`MelleaSession`): A mellea session with a backend.
- input_str (`None`, optional): This module doesn't use the "input_str" argument.
- max_new_tokens (`int`, optional): Maximum tokens to generate.
- Try increasing the value if you are getting `TagExtractionError`.
- Defaults to `8192`.
- parser (`Callable[[str], Any]`, optional): A string parsing function.
+ mellea_session (`MelleaSession`): A mellea session configured with a backend.
+ input_str (`None`, optional): This module does not use the `input_str`
+ argument. It is kept for interface compatibility.
+ max_new_tokens (`int`, optional): Maximum number of tokens to generate
+ for each LLM call. Try increasing this value if the model is truncating
+ its answers. Defaults to `4096`.
+ parser (`Callable[[str], Any]`, optional): A parsing function used by the
+ returned `PromptModuleString.parse()` method.
Defaults to `_SubtaskConstraintAssign._default_parser`.
- subtasks_tags_and_prompts (`Sequence[tuple[str, str, str]]`): A list of subtasks,
- their respective tags and prompts.
+ subtasks_tags_and_prompts (`Sequence[tuple[str, str, str]]`): A sequence of
+ subtasks and their respective tags and prompt templates.
+
+ This was designed to receive the parsed result of the
+ `subtask_prompt_generator` module, but this is not required, as long as
+ the provided data follows the expected format.
- This was designed to receive the parsed result of the `subtask_prompt_generator`
- module, but it's not required, you are able to provide arguments in the correct format.
+ Each item must be a `tuple[str, str, str]` where:
+ - the first element is the subtask title or description in natural language;
+ - the second element is a descriptive tag/variable name for that subtask;
+ - the third element is the prompt template to be executed by an LLM.
- The list must be composed of `tuple[str, str, str]` objects where the first position is
- the subtask title/description in natural language, the second position is a tag/variable
- with a descriptive name related to its subtask, and the third position is the template
- prompt for an LLM to execute the subtask. e.g.
+ Example:
```
subtasks_tags_and_prompts = [
- ("1. Read the document and write a summary", "DOCUMENT_SUMMARY", ""),
- ("2. Write the 3 most important phrases as bullets", "IMPORTANT_PHRASES", "")
+ (
+ "1. Read the document and write a summary",
+ "DOCUMENT_SUMMARY",
+ "",
+ ),
+ (
+ "2. Write the 3 most important phrases as bullets",
+ "IMPORTANT_PHRASES",
+ "",
+ ),
]
```
constraint_list (`Sequence[str]`): A list of constraints written in natural language.
- This was designed to take in a list of constraints identified from the prompt
- that originated the subtasks provided, so they can be correctly
- distributed and assigned to the subtasks.
+ This was designed to receive constraints extracted from the original user
+ request so they can be distributed across the generated subtasks.
Returns:
- PromptModuleString: A `PromptModuleString` class containing the generated output.
+ PromptModuleString: A `PromptModuleString` containing the serialized LLM output.
- The `PromptModuleString` class behaves like a `str`, but with an additional `parse()` method
- to execute the parsing function passed in the `parser` argument of
- this method (the `parser` argument defaults to `_SubtaskConstraintAssign._default_parser`).
+ The `PromptModuleString` behaves like a `str`, but also provides a `parse()`
+ method that applies the parsing function passed through the `parser`
+ argument. By default, this is
+ `_SubtaskConstraintAssign._default_parser`.
Raises:
- BackendGenerationError: Some error occurred during the LLM generation call.
+ BackendGenerationError: Raised when an LLM generation call fails, or when the
+ backend returns `None` as the generated value.
"""
system_prompt = get_system_prompt()
diff --git a/docs/examples/m_decompose/decompose_using_cli.sh b/docs/examples/m_decompose/decompose_using_cli.sh
index 9c36bdf19..56eb07102 100755
--- a/docs/examples/m_decompose/decompose_using_cli.sh
+++ b/docs/examples/m_decompose/decompose_using_cli.sh
@@ -4,4 +4,3 @@
MODEL_ID=mistral-small3.2:latest # granite4:latest
m decompose run --model-id $MODEL_ID --out-dir ./ --input-file example.txt
-
diff --git a/docs/examples/m_decompose/example.txt b/docs/examples/m_decompose/example.txt
index f6c2f4af5..f536187b2 100644
--- a/docs/examples/m_decompose/example.txt
+++ b/docs/examples/m_decompose/example.txt
@@ -1,3 +1,3 @@
-I will visit Grand Canyon National Park for 3 days in early May. Please create a travel itinerary that includes major scenic viewpoints and short hiking trails. The daily walking distance should stay under 6 miles, and each day should include at least one sunset or sunrise viewpoint.
+I will visit Grand Canyon National Park for 3 days in early May. Please create a travel itinerary that includes major scenic viewpoints and short hiking trails. The daily walking distance should stay under 6 miles, and at least one day should include at least one sunset or sunrise viewpoint.
I am preparing my U.S. federal tax return for 2025. My income sources include W-2 salary, house rental, and investment dividends. Please help estimate my federal tax liability and suitable which types of program of TurboTax for possible deductions. Assume I can take the standard/itemized deduction, and the calculation should assume I live in Virginia.
We are building an AI assistant for meeting productivity that can summarize meetings and generate follow-up tasks. Please propose 5 core product features that would provide the most value to users. The features should be feasible and simple enough for a MVP, and each feature should be under 60 words.
\ No newline at end of file
diff --git a/docs/examples/m_decompose/python/python_decompose_final_output.txt b/docs/examples/m_decompose/python/python_decompose_final_output.txt
deleted file mode 100644
index 76ac65b58..000000000
--- a/docs/examples/m_decompose/python/python_decompose_final_output.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-[38;20m=== 12:28:43-INFO ======
-Starting Mellea session: backend=ollama, model=granite4:micro, context=SimpleContext[0m
-[38;20m=== 12:28:45-INFO ======
-SUCCESS[0m
-[38;20m=== 12:28:47-INFO ======
-FAILED. Valid: 0/1[0m
-[38;20m=== 12:28:48-INFO ======
-FAILED. Valid: 0/1[0m
-[38;20m=== 12:28:48-INFO ======
-Invoking select_from_failure after 2 failed attempts.[0m
-[38;20m=== 12:28:55-INFO ======
-SUCCESS[0m
-[38;20m=== 12:28:59-INFO ======
-SUCCESS[0m
-[38;20m=== 12:29:10-INFO ======
-FAILED. Valid: 3/4[0m
-[38;20m=== 12:29:19-INFO ======
-FAILED. Valid: 3/4[0m
-[38;20m=== 12:29:19-INFO ======
-Invoking select_from_failure after 2 failed attempts.[0m
-**Title:** Kickstart Your Day: Unleash Morning Exercise's Power!
-
-**Introduction:**
-Are you tired of feeling sluggish first thing in the morning? Imagine starting your day with a burst of energy and positivity, setting the tone for everything else to come. Morning exercise isn't just about getting fit; it's a powerful way to enhance mental clarity, boost mood, and set a productive tone for the entire day. This blog post, "Kickstart Your Day: Unleash Morning Exercise's Power!" will explore how incorporating morning workouts into your routine can transform not only your physical health but also significantly impact your overall well-being and productivity.
-
-**Benefits:**
-
-#### 1. **Enhanced Mental Clarity and Cognitive Function**
-Engaging in morning exercise significantly boosts mental clarity, allowing individuals to approach their day with a focused mind. This heightened sense of awareness can lead to improved decision-making skills throughout the day, enhancing productivity and efficiency at work or school. Research from the *International Journal of Behavioral Nutrition and Physical Activity* indicates that regular morning exercise correlates with lower levels of cortisol, the stress hormone, leading to reduced anxiety and depression symptoms.
-
-#### 2. **Improved Mood and Reduced Stress Levels**
-Morning exercise is known for its ability to elevate mood significantly, reducing symptoms of anxiety and depression. By boosting serotonin levels—often referred to as the "feel-good" hormone—morning workouts can set a positive tone for the entire day, reducing stress and enhancing overall emotional well-being.
-
-#### 3. **Boosted Energy Levels Throughout the Day**
-Morning exercise is a powerful way to increase energy levels for several hours post-workout, allowing for sustained productivity throughout the day. This surge in energy can lead to improved performance in daily activities, whether it's meeting deadlines at work or spending quality time with family and friends.
-
-**Conclusion:**
-
-Incorporating morning exercise into your daily routine can significantly enhance various aspects of health and well-being, from mental clarity to improved mood and sustained energy levels. As highlighted in the blog post "Kickstart Your Day: Unleash Morning Exercise's Power!", these benefits not only transform physical fitness but also have a profound impact on overall productivity and happiness.
-
-The key themes we've explored—enhanced cognitive function, improved emotional health, and boosted vitality—underscore why morning exercise is not merely an activity but a transformative habit. By aligning your physical routine with your body's natural rhythms in the morning, you're essentially equipping yourself to tackle the day head-on, more focused, motivated, and ready to embrace whatever challenges come your way.
-
-Now, imagine waking up each day feeling energized, clear-headed, and eager to make a difference. This is not just wishful thinking—it's achievable with consistent morning exercise. So why wait? Let this be the start of your journey towards a more vibrant, productive, and fulfilling life. Embrace the power of mornings, embrace the power within you, and watch as every day transforms into an opportunity to excel.
-
-Take that first step today. Whether it's a brisk walk, a yoga session, or a high-intensity workout, the key is starting and making it a part of your daily routine. Your future self will thank you for choosing to wake up not just earlier but also more energized and ready to seize the day. Let morning exercise be the catalyst that propels you towards a healthier, happier, and more productive lifestyle.
diff --git a/docs/examples/m_decompose/python/python_decompose_result.json b/docs/examples/m_decompose/python/python_decompose_result.json
deleted file mode 100644
index 8a2903782..000000000
--- a/docs/examples/m_decompose/python/python_decompose_result.json
+++ /dev/null
@@ -1,121 +0,0 @@
-{
- "original_task_prompt": "Write a short blog post about the benefits of morning exercise.\nInclude a catchy title, an introduction paragraph, three main benefits\nwith explanations, and a conclusion that encourages readers to start\ntheir morning exercise routine.",
- "subtask_list": [
- "1. Create a catchy title for the blog post about the benefits of morning exercise. -",
- "2. Write an introduction paragraph that sets the stage for the blog post. -",
- "3. Identify and explain three main benefits of morning exercise with detailed explanations. -",
- "4. Write a conclusion that encourages readers to start their morning exercise routine. -",
- "5. Compile the title, introduction, three main benefits, and conclusion into a single cohesive blog post. -"
- ],
- "identified_constraints": [
- {
- "constraint": "Include a catchy title",
- "validation_strategy": "llm"
- },
- {
- "constraint": "Include an introduction paragraph",
- "validation_strategy": "llm"
- },
- {
- "constraint": "Include three main benefits with explanations",
- "validation_strategy": "llm"
- },
- {
- "constraint": "Include a conclusion that encourages readers to start their morning exercise routine",
- "validation_strategy": "llm"
- }
- ],
- "subtasks": [
- {
- "subtask": "1. Create a catchy title for the blog post about the benefits of morning exercise. -",
- "tag": "BLOG_TITLE",
- "constraints": [
- {
- "constraint": "Include a catchy title",
- "validation_strategy": "llm"
- }
- ],
- "prompt_template": "Your task is to create a catchy title for a blog post about the benefits of morning exercise. Follow these steps to accomplish your task:\n\n1. **Understand the Topic**: The blog post will focus on the benefits of morning exercise. The title should be engaging and clearly convey the main topic of the post.\n\n2. **Identify Key Elements**: Consider the key elements that make morning exercise beneficial. These could include improved mood, increased energy, better focus, and enhanced metabolism.\n\n3. **Use Power Words**: Incorporate power words that evoke curiosity, excitement, or a sense of urgency. Examples include \"Boost,\" \"Transform,\" \"Unlock,\" \"Energize,\" and \"Revitalize.\"\n\n4. **Keep It Concise**: The title should be short and to the point, ideally between 5 to 10 words. It should be easy to read and remember.\n\n5. **Make It Action-Oriented**: Use verbs that encourage action, such as \"Start,\" \"Jumpstart,\" \"Kickstart,\" or \"Ignite.\"\n\n6. **Consider SEO**: Think about common search terms related to morning exercise. Including relevant keywords can help improve the post's visibility.\n\n7. **Examples for Inspiration**:\n - \"Jumpstart Your Day: The Power of Morning Exercise\"\n - \"Energize Your Mornings: Unlock the Benefits of Morning Exercise\"\n - \"Transform Your Day with Morning Exercise\"\n - \"Boost Your Energy: The Magic of Morning Workouts\"\n - \"Revitalize Your Mornings: The Benefits of Morning Exercise\"\n\n8. **Create the Title**: Based on the above guidelines, create a catchy and engaging title for the blog post. Ensure it captures the essence of the topic and entices readers to click and read more.\n\nYour final answer should be only the title text.",
- "input_vars_required": [],
- "depends_on": []
- },
- {
- "subtask": "2. Write an introduction paragraph that sets the stage for the blog post. -",
- "tag": "INTRODUCTION",
- "constraints": [
- {
- "constraint": "Include an introduction paragraph",
- "validation_strategy": "llm"
- }
- ],
- "prompt_template": "Your task is to write an engaging introduction paragraph for a blog post about the benefits of morning exercise. The introduction should set the stage for the blog post, capturing the reader's attention and providing a brief overview of what will be discussed.\n\nTo accomplish this, follow these steps:\n\n1. **Understand the Context**:\n - The blog post is about the benefits of morning exercise.\n - The title of the blog post is: {{BLOG_TITLE}}\n\n2. **Craft the Introduction**:\n - Start with a hook that grabs the reader's attention. This could be a question, a surprising fact, or a relatable scenario.\n - Briefly introduce the topic of morning exercise and why it is important.\n - Provide a smooth transition to the main benefits that will be discussed in the blog post.\n\n3. **Ensure Engagement**:\n - Use a conversational and engaging tone to connect with the readers.\n - Keep the introduction concise and to the point, ideally between 3 to 5 sentences.\n\nHere is an example structure to guide your writing:\n- **Sentence 1**: Hook to grab the reader's attention.\n- **Sentence 2**: Introduce the topic of morning exercise.\n- **Sentence 3**: Briefly mention the benefits that will be discussed.\n- **Sentence 4**: Transition to the main content of the blog post.\n\nEnsure that the introduction flows naturally and sets the stage for the rest of the blog post. You should write only the introduction paragraph, do not include the guidance structure.",
- "input_vars_required": [],
- "depends_on": [
- "BLOG_TITLE"
- ]
- },
- {
- "subtask": "3. Identify and explain three main benefits of morning exercise with detailed explanations. -",
- "tag": "BENEFITS",
- "constraints": [
- {
- "constraint": "Include three main benefits with explanations",
- "validation_strategy": "llm"
- }
- ],
- "prompt_template": "Your task is to identify and explain three main benefits of morning exercise with detailed explanations. Follow these steps to accomplish your task:\n\nFirst, review the title and introduction created in the previous steps to understand the context and tone of the blog post:\n\n{{BLOG_TITLE}}\n\n\n{{INTRODUCTION}}\n\n\nNext, research and identify three main benefits of morning exercise. These benefits should be supported by evidence or expert opinions to ensure credibility.\n\nFor each benefit, provide a detailed explanation that includes:\n- The specific benefit of morning exercise\n- How this benefit positively impacts health, well-being, or daily life\n- Any relevant studies, expert opinions, or personal anecdotes that support the benefit\n\nEnsure that the explanations are clear, concise, and engaging to keep the reader interested.\n\nFinally, present the three main benefits with their detailed explanations in a structured format that can be easily integrated into the blog post.",
- "input_vars_required": [],
- "depends_on": [
- "BLOG_TITLE",
- "INTRODUCTION"
- ]
- },
- {
- "subtask": "4. Write a conclusion that encourages readers to start their morning exercise routine. -",
- "tag": "CONCLUSION",
- "constraints": [
- {
- "constraint": "Include a conclusion that encourages readers to start their morning exercise routine",
- "validation_strategy": "llm"
- }
- ],
- "prompt_template": "Your task is to write a compelling conclusion for a blog post about the benefits of morning exercise. The conclusion should encourage readers to start their morning exercise routine. Follow these steps to accomplish your task:\n\nFirst, review the title and introduction of the blog post to understand the context and tone:\n\n{{BLOG_TITLE}}\n\n\n{{INTRODUCTION}}\n\n\nNext, consider the three main benefits of morning exercise that have been previously identified and explained:\n\n{{BENEFITS}}\n\n\nUse the information from the title, introduction, and benefits to craft a conclusion that:\n1. Summarizes the key points discussed in the blog post.\n2. Reinforces the importance of morning exercise.\n3. Encourages readers to take action and start their morning exercise routine.\n4. Maintains a positive and motivating tone.\n\nEnsure the conclusion is concise, engaging, and leaves readers feeling inspired to make a change in their daily routine.\n\nFinally, write the conclusion paragraph that encourages readers to start their morning exercise routine.",
- "input_vars_required": [],
- "depends_on": [
- "BLOG_TITLE",
- "INTRODUCTION",
- "BENEFITS"
- ]
- },
- {
- "subtask": "5. Compile the title, introduction, three main benefits, and conclusion into a single cohesive blog post. -",
- "tag": "FINAL_BLOG_POST",
- "constraints": [
- {
- "constraint": "Include a catchy title",
- "validation_strategy": "llm"
- },
- {
- "constraint": "Include an introduction paragraph",
- "validation_strategy": "llm"
- },
- {
- "constraint": "Include three main benefits with explanations",
- "validation_strategy": "llm"
- },
- {
- "constraint": "Include a conclusion that encourages readers to start their morning exercise routine",
- "validation_strategy": "llm"
- }
- ],
- "prompt_template": "Your task is to compile the title, introduction, three main benefits, and conclusion into a single cohesive blog post about the benefits of morning exercise.\n\nTo accomplish this, follow these steps:\n\n1. **Review the Components**:\n Carefully review the title, introduction, three main benefits, and conclusion that have been generated in the previous steps. These components are provided below:\n\n \n {{BLOG_TITLE}}\n \n\n \n {{INTRODUCTION}}\n \n\n \n {{BENEFITS}}\n \n\n \n {{CONCLUSION}}\n \n\n2. **Structure the Blog Post**:\n Organize the components into a well-structured blog post. The structure should include:\n - The catchy title at the beginning.\n - The introduction paragraph that sets the stage for the blog post.\n - The three main benefits with detailed explanations.\n - The conclusion that encourages readers to start their morning exercise routine.\n\n3. **Ensure Cohesion**:\n Make sure the blog post flows smoothly from one section to the next. The transitions between the introduction, benefits, and conclusion should be natural and logical.\n\n4. **Check for Consistency**:\n Verify that the tone and style are consistent throughout the blog post. Ensure that the language used in the title, introduction, benefits, and conclusion aligns with the overall theme of the blog post.\n\n5. **Final Review**:\n Read through the entire blog post to ensure it is cohesive, well-organized, and free of any grammatical or spelling errors. Make any necessary adjustments to improve clarity and readability.\n\n6. **Output the Blog Post**:\n Provide the final compiled blog post as your answer. Ensure that the output includes only the blog post text without any additional information or instructions.\n\nBy following these steps, you will create a single cohesive blog post that effectively communicates the benefits of morning exercise.",
- "input_vars_required": [],
- "depends_on": [
- "BLOG_TITLE",
- "INTRODUCTION",
- "BENEFITS",
- "CONCLUSION"
- ]
- }
- ]
-}
\ No newline at end of file
diff --git a/docs/examples/m_decompose/python/python_decompose_result.py b/docs/examples/m_decompose/python/python_decompose_result.py
deleted file mode 100644
index e4f77c9e3..000000000
--- a/docs/examples/m_decompose/python/python_decompose_result.py
+++ /dev/null
@@ -1,222 +0,0 @@
-# pytest: skip_always
-import textwrap
-
-import mellea
-
-# Note: This is an example of an intermediary result from using decompose in python_decompose_example.py, not an example of how to use decompose.
-
-
-m = mellea.start_session()
-
-
-# 1. Create a catchy title for the blog post about the benefits of morning exercise. - - BLOG_TITLE
-blog_title = m.instruct(
- textwrap.dedent(
- R"""
- Your task is to create a catchy title for a blog post about the benefits of morning exercise. Follow these steps to accomplish your task:
-
- 1. **Understand the Topic**: The blog post will focus on the benefits of morning exercise. The title should be engaging and clearly convey the main topic of the post.
-
- 2. **Identify Key Elements**: Consider the key elements that make morning exercise beneficial. These could include improved mood, increased energy, better focus, and enhanced metabolism.
-
- 3. **Use Power Words**: Incorporate power words that evoke curiosity, excitement, or a sense of urgency. Examples include "Boost," "Transform," "Unlock," "Energize," and "Revitalize."
-
- 4. **Keep It Concise**: The title should be short and to the point, ideally between 5 to 10 words. It should be easy to read and remember.
-
- 5. **Make It Action-Oriented**: Use verbs that encourage action, such as "Start," "Jumpstart," "Kickstart," or "Ignite."
-
- 6. **Consider SEO**: Think about common search terms related to morning exercise. Including relevant keywords can help improve the post's visibility.
-
- 7. **Examples for Inspiration**:
- - "Jumpstart Your Day: The Power of Morning Exercise"
- - "Energize Your Mornings: Unlock the Benefits of Morning Exercise"
- - "Transform Your Day with Morning Exercise"
- - "Boost Your Energy: The Magic of Morning Workouts"
- - "Revitalize Your Mornings: The Benefits of Morning Exercise"
-
- 8. **Create the Title**: Based on the above guidelines, create a catchy and engaging title for the blog post. Ensure it captures the essence of the topic and entices readers to click and read more.
-
- Your final answer should be only the title text.
- """.strip()
- ),
- requirements=["Include a catchy title"],
-)
-assert blog_title.value is not None, 'ERROR: task "blog_title" execution failed'
-
-# 2. Write an introduction paragraph that sets the stage for the blog post. - - INTRODUCTION
-introduction = m.instruct(
- textwrap.dedent(
- R"""
- Your task is to write an engaging introduction paragraph for a blog post about the benefits of morning exercise. The introduction should set the stage for the blog post, capturing the reader's attention and providing a brief overview of what will be discussed.
-
- To accomplish this, follow these steps:
-
- 1. **Understand the Context**:
- - The blog post is about the benefits of morning exercise.
- - The title of the blog post is: {{BLOG_TITLE}}
-
- 2. **Craft the Introduction**:
- - Start with a hook that grabs the reader's attention. This could be a question, a surprising fact, or a relatable scenario.
- - Briefly introduce the topic of morning exercise and why it is important.
- - Provide a smooth transition to the main benefits that will be discussed in the blog post.
-
- 3. **Ensure Engagement**:
- - Use a conversational and engaging tone to connect with the readers.
- - Keep the introduction concise and to the point, ideally between 3 to 5 sentences.
-
- Here is an example structure to guide your writing:
- - **Sentence 1**: Hook to grab the reader's attention.
- - **Sentence 2**: Introduce the topic of morning exercise.
- - **Sentence 3**: Briefly mention the benefits that will be discussed.
- - **Sentence 4**: Transition to the main content of the blog post.
-
- Ensure that the introduction flows naturally and sets the stage for the rest of the blog post. You should write only the introduction paragraph, do not include the guidance structure.
- """.strip()
- ),
- requirements=["Include an introduction paragraph"],
- user_variables={"BLOG_TITLE": blog_title.value},
-)
-assert introduction.value is not None, 'ERROR: task "introduction" execution failed'
-
-# 3. Identify and explain three main benefits of morning exercise with detailed explanations. - - BENEFITS
-benefits = m.instruct(
- textwrap.dedent(
- R"""
- Your task is to identify and explain three main benefits of morning exercise with detailed explanations. Follow these steps to accomplish your task:
-
- First, review the title and introduction created in the previous steps to understand the context and tone of the blog post:
-
- {{BLOG_TITLE}}
-
-
- {{INTRODUCTION}}
-
-
- Next, research and identify three main benefits of morning exercise. These benefits should be supported by evidence or expert opinions to ensure credibility.
-
- For each benefit, provide a detailed explanation that includes:
- - The specific benefit of morning exercise
- - How this benefit positively impacts health, well-being, or daily life
- - Any relevant studies, expert opinions, or personal anecdotes that support the benefit
-
- Ensure that the explanations are clear, concise, and engaging to keep the reader interested.
-
- Finally, present the three main benefits with their detailed explanations in a structured format that can be easily integrated into the blog post.
- """.strip()
- ),
- requirements=["Include three main benefits with explanations"],
- user_variables={"BLOG_TITLE": blog_title.value, "INTRODUCTION": introduction.value},
-)
-assert benefits.value is not None, 'ERROR: task "benefits" execution failed'
-
-# 4. Write a conclusion that encourages readers to start their morning exercise routine. - - CONCLUSION
-conclusion = m.instruct(
- textwrap.dedent(
- R"""
- Your task is to write a compelling conclusion for a blog post about the benefits of morning exercise. The conclusion should encourage readers to start their morning exercise routine. Follow these steps to accomplish your task:
-
- First, review the title and introduction of the blog post to understand the context and tone:
-
- {{BLOG_TITLE}}
-
-
- {{INTRODUCTION}}
-
-
- Next, consider the three main benefits of morning exercise that have been previously identified and explained:
-
- {{BENEFITS}}
-
-
- Use the information from the title, introduction, and benefits to craft a conclusion that:
- 1. Summarizes the key points discussed in the blog post.
- 2. Reinforces the importance of morning exercise.
- 3. Encourages readers to take action and start their morning exercise routine.
- 4. Maintains a positive and motivating tone.
-
- Ensure the conclusion is concise, engaging, and leaves readers feeling inspired to make a change in their daily routine.
-
- Finally, write the conclusion paragraph that encourages readers to start their morning exercise routine.
- """.strip()
- ),
- requirements=[
- "Include a conclusion that encourages readers to start their morning exercise routine"
- ],
- user_variables={
- "BLOG_TITLE": blog_title.value,
- "INTRODUCTION": introduction.value,
- "BENEFITS": benefits.value,
- },
-)
-assert conclusion.value is not None, 'ERROR: task "conclusion" execution failed'
-
-# 5. Compile the title, introduction, three main benefits, and conclusion into a single cohesive blog post. - - FINAL_BLOG_POST
-final_blog_post = m.instruct(
- textwrap.dedent(
- R"""
- Your task is to compile the title, introduction, three main benefits, and conclusion into a single cohesive blog post about the benefits of morning exercise.
-
- To accomplish this, follow these steps:
-
- 1. **Review the Components**:
- Carefully review the title, introduction, three main benefits, and conclusion that have been generated in the previous steps. These components are provided below:
-
-
- {{BLOG_TITLE}}
-
-
-
- {{INTRODUCTION}}
-
-
-
- {{BENEFITS}}
-
-
-
- {{CONCLUSION}}
-
-
- 2. **Structure the Blog Post**:
- Organize the components into a well-structured blog post. The structure should include:
- - The catchy title at the beginning.
- - The introduction paragraph that sets the stage for the blog post.
- - The three main benefits with detailed explanations.
- - The conclusion that encourages readers to start their morning exercise routine.
-
- 3. **Ensure Cohesion**:
- Make sure the blog post flows smoothly from one section to the next. The transitions between the introduction, benefits, and conclusion should be natural and logical.
-
- 4. **Check for Consistency**:
- Verify that the tone and style are consistent throughout the blog post. Ensure that the language used in the title, introduction, benefits, and conclusion aligns with the overall theme of the blog post.
-
- 5. **Final Review**:
- Read through the entire blog post to ensure it is cohesive, well-organized, and free of any grammatical or spelling errors. Make any necessary adjustments to improve clarity and readability.
-
- 6. **Output the Blog Post**:
- Provide the final compiled blog post as your answer. Ensure that the output includes only the blog post text without any additional information or instructions.
-
- By following these steps, you will create a single cohesive blog post that effectively communicates the benefits of morning exercise.
- """.strip()
- ),
- requirements=[
- "Include a catchy title",
- "Include an introduction paragraph",
- "Include three main benefits with explanations",
- "Include a conclusion that encourages readers to start their morning exercise routine",
- ],
- user_variables={
- "BLOG_TITLE": blog_title.value,
- "INTRODUCTION": introduction.value,
- "BENEFITS": benefits.value,
- "CONCLUSION": conclusion.value,
- },
-)
-assert final_blog_post.value is not None, (
- 'ERROR: task "final_blog_post" execution failed'
-)
-
-
-final_answer = final_blog_post.value
-
-print(final_answer)
diff --git a/test/decompose/test_decompose.py b/test/decompose/test_decompose.py
index d23cbcf62..35303a78b 100644
--- a/test/decompose/test_decompose.py
+++ b/test/decompose/test_decompose.py
@@ -10,9 +10,9 @@
import pytest
-from cli.decompose.decompose import DecompVersion, run
+from cli.decompose.decompose import DecompVersion, reorder_subtasks, run
from cli.decompose.logging import LogMode
-from cli.decompose.pipeline import DecompBackend, DecompPipelineResult
+from cli.decompose.pipeline import ConstraintResult, DecompBackend, DecompPipelineResult
class DummyTemplate:
@@ -32,18 +32,39 @@ def get_template(self, template_name: str) -> DummyTemplate:
return DummyTemplate()
-def make_decomp_result() -> DecompPipelineResult:
- """Create a minimal valid decomposition result for CLI tests."""
+def make_decomp_result(with_code_validation: bool = False) -> DecompPipelineResult:
+ """Create a valid decomposition result for CLI tests."""
+ identified_constraints: list[ConstraintResult] = []
+ subtask_constraints: list[ConstraintResult] = []
+
+ if with_code_validation:
+ identified_constraints = [
+ {
+ "constraint": "Return valid JSON.",
+ "val_strategy": "code",
+ "val_fn": "def validate_input(text: str) -> bool:\n return text.startswith('{')",
+ "val_fn_name": "val_fn_1",
+ }
+ ]
+ subtask_constraints = [
+ {
+ "constraint": "Return valid JSON.",
+ "val_strategy": "code",
+ "val_fn": "def validate_input(text: str) -> bool:\n return text.startswith('{')",
+ "val_fn_name": "val_fn_1",
+ }
+ ]
+
return {
"original_task_prompt": "Test task prompt",
"subtask_list": ["Task A"],
- "identified_constraints": [],
+ "identified_constraints": identified_constraints,
"subtasks": [
{
- "subtask": "Task A",
+ "subtask": "1. Task A",
"tag": "TASK_A",
- "general_instructions": "",
- "constraints": [],
+ "general_instructions": "Keep the answer concise.",
+ "constraints": subtask_constraints,
"prompt_template": "Do A",
"input_vars_required": [],
"depends_on": [],
@@ -192,18 +213,17 @@ def test_latest_version_resolves_to_last_declared_version(
"""``latest`` resolves to the last declared enum version."""
input_file = write_input_file(tmp_path, "Test")
requested_templates: list[str] = []
+ environment = Mock()
- class TrackingEnvironment:
- """Tracking Jinja environment for template resolution assertions."""
+ def get_template(template_name: str) -> DummyTemplate:
+ requested_templates.append(template_name)
+ return DummyTemplate()
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- pass
+ environment.get_template.side_effect = get_template
- def get_template(self, template_name: str) -> DummyTemplate:
- requested_templates.append(template_name)
- return DummyTemplate()
-
- monkeypatch.setattr("cli.decompose.decompose.Environment", TrackingEnvironment)
+ monkeypatch.setattr(
+ "cli.decompose.decompose.Environment", lambda *args, **kwargs: environment
+ )
monkeypatch.setattr(
"cli.decompose.decompose.FileSystemLoader", lambda *args, **kwargs: None
)
@@ -219,7 +239,7 @@ def get_template(self, template_name: str) -> DummyTemplate:
version=DecompVersion.latest,
)
- assert requested_templates == ["m_decomp_result_v2.py.jinja2"]
+ assert requested_templates == ["m_decomp_result_v3.py.jinja2"]
def test_successful_run_writes_outputs(
self,
@@ -247,6 +267,34 @@ def test_successful_run_writes_outputs(
assert (out_path / "validations").exists()
assert (out_path / "validations" / "__init__.py").exists()
+ def test_latest_template_writes_validation_modules_for_code_constraints(
+ self,
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+ patch_validate_filename: None,
+ patch_logging: Mock,
+ ) -> None:
+ """Latest template includes code-validation imports and writes modules."""
+ input_file = write_input_file(tmp_path, "Generate JSON.")
+
+ monkeypatch.setattr(
+ "cli.decompose.decompose.pipeline.decompose",
+ lambda **kwargs: make_decomp_result(with_code_validation=True),
+ )
+
+ run(out_dir=tmp_path, out_name="validated_case", input_file=input_file)
+
+ validation_path = tmp_path / "validated_case" / "validations" / "val_fn_1.py"
+ program_path = tmp_path / "validated_case" / "validated_case.py"
+
+ assert validation_path.exists()
+ assert "def validate_input" in validation_path.read_text(encoding="utf-8")
+
+ program = program_path.read_text(encoding="utf-8")
+ assert "from mellea.stdlib.requirements import req" in program
+ assert "from validations.val_fn_1 import validate_input as val_fn_1" in program
+ assert "Keep the answer concise." in program
+
def test_multi_line_input_file_creates_numbered_jobs(
self,
tmp_path: Path,