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.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
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