Getting Started¶
Installing¶
L2P can be installed with pip:
pip install l2p
Using L2P¶
First things first, import the whole L2P library, or necessary modules (see L2P):
from l2p import *
# OR
from l2p import DomainBuilder, ProblemBuilder, PromptBuilder, FeedbackBuilder
from l2p.validators.domain import DomainValidator
from l2p.validators.problem import ProblemValidator
# util functions
from l2p.utils.pddl_types import *
from l2p.utils.pddl_format import *
from l2p.utils.pddl_parser import *
from l2p.utils.pddl_prompt import *
L2P requires access to an LLM. Set up your LLM class and methods using the abstract BaseLLM(ABC) class. In this case, we have set up OpenAI’s models to our library for quickstart.
export OPENAI_API_KEY='YOUR-KEY' # e.g. OPENAI_API_KEY='sk-123456'
engine = "gpt-4o-mini"
api_key = os.environ.get('OPENAI_API_KEY')
openai_llm = OPENAI(model=engine, api_key=api_key)
Build PDDL domain components using the DomainBuilder class. This is an example of extracting PDDL types:
1import os
2from l2p import UnifiedLLM
3from l2p import DomainBuilder
4from l2p import PDDLType
5
6llm = UnifiedLLM(
7 provider="openai",
8 model="gpt-5-nano",
9 api_key=os.environ.get('OPENAI_API_KEY'))
10
11domain_builder = DomainBuilder() # instantiate DomainBuilder
12
13# context description
14domain_desc = "The AI agent here is a mechanical robot arm that can pick and " \
15"place the blocks. Only one block may be moved at a time: it may either " \
16"be placed on the table or placed atop another block. Because of this, " \
17"any blocks that are, at a given time, under another block cannot be moved."
18
19# extract types via LLM
20results, llm_output = domain_builder.formalize_component(
21 model=llm,
22 component_class=PDDLType,
23 description=domain_desc
24 # prompt_template=[...] by default loads pre-defined prompt template
25)
26
27print(results[PDDLType])
Generated types output:
[
PDDLType(name='block', parent='object', desc='A block to pickup'),
PDDLType(name='table', parent='object', desc='Table where blocks are set down'),
PDDLType(name='robot_arm', parent='object', desc='Robot arm that manipulates blocks')
]
Build PDDL problem components using the ProblemBuilder class. This is an example of extracting PDDL initial states:
1from l2p import ProblemBuilder
2from l2p import InitialState, Predicate, PDDLType
3from l2p import format_initial_state
4
5problem_builder = ProblemBuilder() # instantiate ProblemBuilder
6
7# context
8problem_desc = "There are four blocks currently. The blue block is on the red " \
9"which is on the yellow. The yellow and the green are on the table. I want " \
10"the red on top of the green."
11types = [PDDLType(name='block', parent='object', desc='A block to pickup')]
12predicates = [
13 Predicate(name="on", params=[{"variable": "?b1", "type": "block"}, {"variable": "?b2", "type": "block"}], desc="Block ?b1 is on block ?b2."),
14 Predicate(name="on-table", params=[{"variable": "?b", "type": "block"}], desc="Block ?b is on table."),
15 Predicate(name="holding", params=[{"variable": "?b", "type": "block"}], desc="Agent is holding block ?b."),
16 Predicate(name="clear", params=[{"variable": "?b", "type": "block"}], desc="Block ?b is clear."),
17 Predicate(name="arm-empty", params=[], desc="Arm is empty.")
18]
19
20# extract initial states via LLM
21results, llm_output = problem_builder.formalize_component(
22 model=llm,
23 component_class=InitialState,
24 description=problem_desc,
25 # prompt_template=[...] by default loads pre-defined prompt template
26 types=types, # context kwargs
27 predicates=predicates # context kwargs
28)
29
30initial_state = results[InitialState][0]
31
32print(format_initial_state(init=initial_state))
Generated initial states:
(on blue red)
(on red yellow)
(on-table yellow)
(on-table green)
(clear blue)
(clear green)
Build LLM feedback components using the FeedbackBuilder class. This is an example of
using LLM-driven diagnosis to validate generated types:
1from l2p import FeedbackBuilder
2
3feedback_builder = FeedbackBuilder() # instantiate FeedbackBuilder
4
5# context description
6domain_desc = "The AI agent here is a mechanical robot arm that can pick and " \
7"place the blocks. Only one block may be moved at a time: it may either " \
8"be placed on the table or placed atop another block. Because of this, " \
9"any blocks that are, at a given time, under another block cannot be moved."
10
11# failure artifact (simulated generated LLM output)
12types = [
13 PDDLType(name='block', parent='object', desc='A block to pickup.'),
14 PDDLType(name='carpet', parent='object', desc='Just some carpet.')
15]
16
17# simulate a validation failure for diagnosis
18errors=["Unnecessary type 'carpet' does not relate to any action."]
19
20# extract diagnosis via LLM
21diagnosis, llm_output = feedback_builder.llm_diagnose(
22 model=llm,
23 errors=errors,
24 artifact=types,
25 description=domain_desc,
26)
27
28print(diagnosis)
Generated diagnosis:
{
"summary": "The validator failed because the domain includes an unused type 'carpet' that does not relate to any action, causing the generation to be invalid.",
"identified_errors": [
{
"error_type": "UnnecessaryType",
"location_in_json": "types[1].name",
"validator_message": "Unnecessary type 'carpet' does not relate to any action.",
"root_cause_analysis": "The types section defines 'carpet' but no action, predicate, or goal references it; it was likely included by mistake or left over from a template."
}
],
"repair_plan": [
"Step 1: Remove the entire type entry for 'carpet' from the types array (types[1]).",
"Step 2: If 'carpet' is intentionally part of the domain, create or reference at least one action or predicate that uses the carpet type so that it is related to an action.",
"Step 3: Re-run the validator to ensure the error is resolved and that all remaining types are used by actions or constraints.",
"Step 4: Ensure the JSON structure remains valid (proper commas, brackets) after removal or modification."
]
}
Below are other runnable usage examples. This is the general setup to build domain predicates:
1from l2p.domain_builder import DomainBuilder
2from l2p.utils.pddl_types import Predicate
3
4db = DomainBuilder()
5results, raw = db.formalize_component(
6 model=llm,
7 component_class=Predicate,
8 description="Model predicates for blocksworld.",
9 types=[PDDLType(name="block", parent="object")]
10)
11predicates = results[Predicate]
The following output is:
(clear ?x - block)
(arm-empty )
(holding ?x - block)
(on ?x - block ?y - block)
(on-table ?x - block)
Here is how you would setup a PDDL problem:
1from l2p.problem_builder import ProblemBuilder
2from l2p.utils.pddl_types import ProblemDetails, PDDLType, Predicate
3
4pb = ProblemBuilder() # instantiate ProblemBuilder class
5
6# context
7types = [PDDLType(name="block", parent="object")]
8predicates = [
9 Predicate(name="on", params=[
10 {"variable": "?x", "type": "block"},
11 {"variable": "?y", "type": "block"}
12 ]),
13 Predicate(name="on-table", params=[{"variable": "?x", "type": "block"}]),
14 Predicate(name="holding", params=[{"variable": "?x", "type": "block"}]),
15 Predicate(name="clear", params=[{"variable": "?x", "type": "block"}]),
16 Predicate(name="arm-empty", params=[])
17]
18
19problem_desc = """
20You have 3 blocks.
21b2 is on top of b3.
22b3 is on top of b1.
23b1 is on the table.
24b2 is clear.
25Your arm is empty.
26Your goal is to move the blocks.
27b2 should be on top of b3.
28b3 should be on top of b1.
29"""
30
31# generate problem
32results, llm_output = pb.formalize_component(
33 model=llm,
34 component_class=ProblemDetails, # component to generate
35 description=problem_desc,
36 types=types, # pass in kwargs context
37 predicates=predicates # pass in kwargs context
38)
39
40# parse out problem from dictionary
41problem = results[ProblemDetails]
42
43# format problem in PDDL format
44problem_str = pb.generate_problem(problem[0])
45
46print(problem_str)
The following output is:
(define (problem blocks-problem)
(:domain blocks-world)
(:objects b1 b2 b3 - block)
(:init
(on b2 b3)
(on b3 b1)
(on-table b1)
(clear b2)
(arm-empty)
)
(:goal
(and (on b2 b3) (on b3 b1))
)
)
*IMPORTANT* It is highly recommended to use the base template found in Templates in your final prompt to properly extract LLM output into the designated Python formats from these methods.
For terminal-based workflows, including interactive generation, validation, and planning, see the CLI documentation.