Skip to main content

l2p : LLM-driven Planning Model library kit

GitHub repo PyPI License

This library is a collection of tools for PDDL model generation extracted from natural language driven by large language models. This library is an expansion from the survey paper LLMs as Planning Formalizers: A Survey for Leveraging Large Language Models to Construct Automated Planning Specifications.

L2P is an offline, natural language-to-planning system (that wraps an LLM backend) to support domain-agnostic planning. It does this via creating an intermediate PDDL representation of the domain and task, which can then be solved by a classical planner. To stay up to date with the most current papers, please visit here.

Full library documentation can be found: L2P Documention

Quickstart

This is the general setup to build domain predicates:

import os
from l2p import UnifiedLLM
from l2p.domain_builder import DomainBuilder
from l2p.utils.pddl_types import Predicate, PDDLType
from l2p.utils.pddl_format import format_predicates

# set up LLM
api_key = os.getenv("OPENAI_API_KEY")
llm = UnifiedLLM(provider="openai", model="gpt-5-nano", api_key=api_key)

db = DomainBuilder() # instantiate DomainBuilder class

# context
types = [PDDLType(name="block", parent="object")]
desc =  "I want you to model predicates from a standard PDDL blocksworld domain."

# generate predicates
results, raw_output = db.formalize_component(
    model=llm,
    component_class=Predicate, # component to generate
    description=desc,
    types=types                # pass in kwargs context
)

# parse out predicates list from dictionary
predicates = results[Predicate]
predicates_str = format_predicates(predicates) # format nicely

print(predicates_str)

# OUTPUT:
# (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:

from l2p.problem_builder import ProblemBuilder
from l2p.utils.pddl_types import ProblemDetails, PDDLType, Predicate

pb = ProblemBuilder() # instantiate ProblemBuilder class

# context
types = [PDDLType(name="block", parent="object")]
predicates = [
    Predicate(name="on", params=[
        {"variable": "?x", "type": "block"},
        {"variable": "?y", "type": "block"}
        ]),
    Predicate(name="on-table", params=[{"variable": "?x", "type": "block"}]),
    Predicate(name="holding", params=[{"variable": "?x", "type": "block"}]),
    Predicate(name="clear", params=[{"variable": "?x", "type": "block"}]),
    Predicate(name="arm-empty", params=[])
]

problem_desc = """
You have 3 blocks. 
b2 is on top of b3. 
b3 is on top of b1. 
b1 is on the table. 
b2 is clear. 
Your arm is empty. 
Your goal is to move the blocks. 
b2 should be on top of b3. 
b3 should be on top of b1. 
"""

# generate problem
results, llm_output = pb.formalize_component(
    model=llm,
    component_class=ProblemDetails, # component to generate
    description=problem_desc,
    types=types,            # pass in kwargs context
    predicates=predicates   # pass in kwargs context
)

# parse out problem from dictionary
problem = results[ProblemDetails]

# format problem in PDDL format
problem_str = pb.generate_problem(problem[0])

print(problem_str)

# OUTPUT:
# (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))
#    )
# )

Installation and Setup

Currently, this repo has been tested for Python 3.11.10 but should be fine to install newer versions.

You can set up a Python environment using either Conda or venv and install the dependencies via the following steps.

Conda

conda create -n L2P python=3.11.10
conda activate L2P
pip install -r requirements.txt

venv

python3.11.10 -m venv env
source env/bin/activate
pip install -r requirements.txt

These environments can then be exited with conda deactivate and deactivate respectively. The instructions below assume that a suitable environemnt is active.

API keys

L2P requires access to an LLM. L2P provides support for models compatible with OpenAI SDK or LLM. To configure these, provide the necessary API-key in an environment variable.

export OPENAI_API_KEY='YOUR-KEY' # e.g. OPENAI_API_KEY='sk-123456'
export CLAUDE_API_KEY='...'
export DEEPSEEK_API_KEY='...'
export OLLAMA_API_KEY='...'

We can then use the OPENAI class for OpenAI-SDK supported models OR UnifiedLLM class (recommended). Refer to here for more information:

import os
from l2p.llm.openai import OPENAI
from l2p.llm.unified import UnifiedLLM

api_key = os.getenv("OPENAI_API_KEY")

# OPENAI SDK BACKEND
llm = OPENAI(
    provider="openai",
    model="gpt-5-nano",
    config_path="l2p/llm/utils/openaiSDK.yaml", # LLM configs stored here 
    api_key=api_key
)

# LLM BACKEND
llm = UnifiedLLM(
    provider="openai",
    model="gpt-5-nano",
    config_path="l2p/llm/utils/llm.yaml", # LLM configs stored here
    api_key=api_key
)

response = llm.query("Hello, world!")
print(response)

Ollama

Additionally, we have included support for using local Ollama models under UnifiedLLM. One can set up their environment like so:

from l2p.llm.unified import UnifiedLLM

llm = UnifiedLLM(
    provider="ollama",
    model="llama2:7b",
    config_path="l2p/llm/utils/llm.yaml" # Ollama model configs stored here 
)

response = llm.query("Hello, world!")
print(response)

Users can refer to l2p/llm/utils/llm.yaml (for UnifiedLLM) or l2p/llm/utils/openaiSDK.yaml (for OPENAI) to better understand (and create their own) model configuration options, including tokenizer settings, generation parameters, and provider-specific settings.

l2p/llm/base.py contains an abstract class and method for implementing any model classes in the case of other third-party LLM uses.

External Planner Support

L2P contains an abstract class Planner found in l2p/planner_builder.py. Users can use this class to run planners on top to solve for generated domain and problems.

For ease of use, our library contains submodule FastDownward. Fast Downward is a domain-independent classical planning system that users can run their PDDL domain and problem files on. The motivation is that the majority of papers involving PDDL-LLM usage uses this library as their planner.

IMPORTANT FastDownward is a submodule in L2P. To use the planner, you must clone the GitHub repo of FastDownward and run the executable_path to that directory.

Here is a quick test set up:

from l2p.planner_builder import FastDownward

domain_file = "<PATH_TO>/domain.pddl"
problem_file = "<PATH_TO>/problem.pddl"

# instantiate FastDownward class
planner = FastDownward(executable_path="<PATH_TO>/downward/fast-downward.py")

# run plan
plan_result = planner.run_planner(
    domain_file=domain_file,
    problem_file=problem_file,
    alias="lama-first"
)

print(plan_result.is_successful)
print(plan_result.plan)

Additionally, L2P also supports Unified Planning backend. Users must first download: pip install unified-planning. After installing unified-planning library, you must install specific planner: pip install 'unified-planning[engine]' to pass into the solver function.

from l2p.planner_builder import UnifiedPlanning

planner = UnifiedPlanning()

plan_result = planner.run_planner(
    domain_path="<PATH_TO>/domain.pddl",
    problem_path="<PATH_TO>/problem.pddl",
    engine="aries" # specific planning backend
)

print(plan_result.is_successful)
print(plan_result.plan)

Agentic CLI (for LLM agents & automation)

The fastest way to build PDDL models is passing full JSON to non-interactive commands:

# 1. Look up the JSON schema an LLM should follow
l2p schema domain --examples

# 2. Assemble and render the full PDDL domain
l2p build domain --data '{
  "name":"blocksworld",
  "types":[{"name":"block","parent":"object"}],
  "predicates":[
    {"name":"clear","params":[{"variable":"?x","type":"block"}]},
    {"name":"on","params":[{"variable":"?x","type":"block"},{"variable":"?y","type":"block"}]}
  ],
  "actions":[
    {"name":"stack","params":[{"variable":"?x","type":"block"},{"variable":"?y","type":"block"}],
     "preconditions":{"conditions":["(clear ?y)","(holding ?x)"]},
     "effects":{"add":["(on ?x ?y)"],"delete":["(clear ?y)","(holding ?x)"]}}
  ]
}' -o domain.pddl

# 3. Validate the generated file
l2p validate domain domain.pddl

# 4. Run a planner on it
l2p plan --domain @domain.pddl --problem @problem.pddl --planner fast-downward --json

Contact

Please contact 20mt1@queensu.ca for questions, comments, or feedback about the L2P library.

Metadata

Release files for l2p 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for l2p 0.4.1
File Size Uploaded
l2p-0.4.1.tar.gz 130.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for l2p 0.4.1
File Interpreter ABI Platform
l2p-0.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 280.8 kB

Release files / l2p-0.4.1.tar.gz

Download URL l2p-0.4.1.tar.gz
Size 130.0 kB
Tags Source
SHA-256 checksum
How to use checksums
b9c79f5a577bb25b334d4dc6d4ff09c611fc96a5fa7f42a028dd8666bd2f164d
BLAKE2b-256 checksum
How to use checksums
d030d962ac80614f959d225f4683c71a8c878c9f642c72a810ee4c3832643c0d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 4, 2026.

Transparency log

Release files / l2p-0.4.1-py3-none-any.whl

Download URL l2p-0.4.1-py3-none-any.whl
Size 150.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
13b0f3d9bd29d1eedc3e01bebd822c9ccc9daa3c17cf5e37e68fa18ac01989ee
BLAKE2b-256 checksum
How to use checksums
f44360693bdbf7eb0f9cd02dff3ce0345087f333d7b8e9bc22cb83969fe6610f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 4, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.1 This release

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.0

2 release files

0.1.8

2 release files

0.1.7

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page