Convert easy, high-accuracy training prompts into harder compositional problems by sequentially chaining multiple prompts together. Use Composition-RL to maintain effective learning signals during RL training when many prompts achieve near-perfect accuracy, enabling curriculum learning through progressive compositional depths.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Convert easy, high-accuracy training prompts into harder compositional problems by sequentially chaining multiple prompts together. Use Composition-RL to maintain effective learning signals during RL training when many prompts achieve near-perfect accuracy, enabling curriculum learning through progressive compositional depths.
Composition-RL: Compose Your Verifiable Prompts for RL of LLMs
Problem Context
During reinforcement learning of language models on mathematical and reasoning tasks, many prompts become too easy—models achieve 100% accuracy, making these prompts uninformative for further learning. This creates a plateau where gradient signals vanish. Standard curriculum learning solutions are expensive, requiring new data collection or external scoring. Composition-RL addresses this by algorithmically combining existing easy prompts into harder compositional ones without new annotation.
Core Concept
Composition-RL automatically merges K existing prompts into a single compositional prompt that requires solving all K sub-problems sequentially. A two-prompt composition works as follows:
Extract the answer from prompt 1 and create a symbolic definition
Modify prompt 2 to use a variable instead of a concrete value
Link the two by replacing the variable with the symbolic value from prompt 1
The key insight is that solving the composed problem requires solving each sub-problem in sequence, creating a multiplicative difficulty increase without manual curation.
Architecture Overview
Prompt parsing: Extract structure and numerical values from existing prompts
Sequential linking: Chain solutions from earlier prompts into later prompts
Difficulty scaling: Compose progressively (depth 2, then depth 3)
Validation: Verify composed prompts are solvable and distinct from originals
Curriculum stages: Train on original → depth-2 compositions → depth-3 compositions
Implementation
Step 1: Parse problem structure and extract answers
"""Parse mathematical problem structure for composition."""
def
__init__
self
self
r'[-+]?\d+(?:\.\d+)?'
self
r'[a-zA-Z_]\w*'
def
parse_problem
self, problem_text: str
"""
Extract structure from a problem.
Assumes verifiable problems with clear answers.
"""
# Simple extraction: assume last sentence/line is answer
'\n'
# Find answer (last line or line with specific markers)
None
None
for
in
reversed
if
'Answer:'
in
or
'answer:'
in
or
'='
in
r'=\s*(.+?)(?:\s*[.,]|$)'
if
1
break
if
is
None
1
# Determine answer type
if
match
r'[-+]?\d+(?:\.\d+)?$'
'numeric'
else
'symbolic'
return
'\n'
1
# Template without answer
def
extract_numerical_values
self, problem_text: str
List
Tuple
str
str
"""Extract all numerical values and their context."""
for
match
in
self
match
0
max
0
match
20
min
len
match
20
return
Step 2: Compose two prompts sequentially
Link answer from first prompt into second prompt.
classPromptComposer:
"""Compose multiple prompts into single difficult problem."""def__init__(self, parser: ProblemStructureParser):
self.parser = parser
defcompose_two_prompts(
self,
prompt_1: str,
prompt_2: str,
variable_name: str = "X") -> Tuple[str, str]:
"""
Compose two prompts into one.
Strategy:
1. Parse prompt_1, extract answer as value V
2. In prompt_2, replace a numerical constant with variable
3. Create composed prompt that solves p1, then uses answer in p2
Args:
prompt_1: First problem
prompt_2: Second problem to modify
variable_name: Name for the variable linking problems
Returns:
(composed_prompt, expected_answer)
"""# Parse both problems
parsed_1 = self.parser.parse_problem(prompt_1)
parsed_2 = self.parser.parse_problem(prompt_2)
answer_1 = parsed_1.answer_value
# Find a numerical value in prompt_2 to replace
numerical_values = self.parser.extract_numerical_values(prompt_2)
ifnot numerical_values:
# No numeric value to replace, use direct substitution# Modify template of prompt_2 to reference the variable
modified_prompt_2 = prompt_2.replace(
parsed_2.answer_value,
""# Will be determined by solving prompt_1
)
else:
# Replace first numerical value with variable
value_to_replace, _ = numerical_values[0]
# Create modified prompt_2
modified_prompt_2 = prompt_2.replace(
value_to_replace,
f"{variable_name} (obtained from the first problem)",
count=1
)
# Compose into single problem
composed_prompt = f"""Solve the following two-part problem:
Part 1: {prompt_1.strip()}
Part 2: {modified_prompt_2.strip()}
Let {variable_name} be the answer to Part 1. Use this value in Part 2 to find the final answer.
What is the final answer?"""# Composed answer requires solving bothreturn composed_prompt, parsed_2.answer_value # Final answer from part 2defcompose_k_prompts(
self,
prompts: List[str],
max_depth: int = 3) -> List[Tuple[str, str]]:
"""
Compose K prompts with progressive chaining.
For 3 prompts: (p1, p2, p3) -> p1's answer feeds p2, p2's answer feeds p3
"""
composed = []
# Generate compositions of depth 2, 3, ..., min(max_depth, len(prompts))for depth inrange(2, min(max_depth + 1, len(prompts) + 1)):
subset = prompts[:depth]
# Progressive chaining
current_prompt = subset[0]
answers_chain = [self.parser.parse_problem(subset[0]).answer_value]
for i inrange(1, len(subset)):
next_prompt = subset[i]
current_prompt, final_answer = self.compose_two_prompts(
current_prompt, next_prompt,
variable_name=f"X{i}"
)
answers_chain.append(final_answer)
composed.append((current_prompt, answers_chain[-1]))
return composed
Step 3: Validate composed prompts
Ensure composed problems are solvable and distinct.
classCompositionValidator:
"""Validate composed prompts for training."""def__init__(self, verifier_model):
"""
Args:
verifier_model: Model that verifies if answer is correct.
Returns True if correct, False otherwise.
"""self.verifier_model = verifier_model
defvalidate_composition(
self,
composed_prompt: str,
expected_answer: str,
original_prompts: List[str]
) -> Dict[str, bool]:
"""
Validate that composed prompt is solvable.
Returns:
Dict with keys: 'is_valid', 'is_distinct', 'solvable'
"""# Check 1: Is it structurally distinct from originals?
is_distinct = all(
composed_prompt.strip() != orig.strip()
for orig in original_prompts
)
# Check 2: Does the composed prompt appear solvable?# (Quick check: answer_format is consistent)
answer_format_valid = isinstance(expected_answer, str)
# Check 3: Verify with model (sample generation)
solvable = self._test_solvability(composed_prompt, expected_answer)
return {
'is_valid': is_distinct and answer_format_valid,
'is_distinct': is_distinct,
'solvable': solvable
}
def_test_solvability(
self,
prompt: str,
expected_answer: str,
num_samples: int = 3) -> bool:
"""
Quick test: can model generate the expected answer?
"""for _ inrange(num_samples):
response = self.verifier_model.generate(
prompt, max_tokens=500, temperature=0.7
)
# Check if response contains expected answerifself._answer_matches(response, expected_answer):
returnTruereturnFalse @staticmethoddef_answer_matches(response: str, expected: str) -> bool:
"""Check if response contains expected answer."""
response_clean = re.sub(r'[^a-zA-Z0-9.-]', '', response.lower())
expected_clean = re.sub(r'[^a-zA-Z0-9.-]', '', expected.lower())
return expected_clean in response_clean
Step 4: Build curriculum with progressive composition