All tutorials
Track 14·Blueprints

Your first decomposition

Teach the planner with a worked example via @decomposition. The decorator tags a method body with the natural-language intent it answers.

intermediate10 min
Video coming soon
Browse this tutorial's folder in tutorials-pygithub.com/OpenSymbolicAI/tutorials-py/tree/main/14-first-decomposition

Before you start

Until now the planner had two things to go on: your primitive signatures and their docstrings. You can give it a third: worked examples. A decomposition is a method you write that composes your primitives into an answer, tagged with the natural-language query it answers. The planner never runs it; it reads it as a pattern.

1. The cart, plus a describe primitive and two examples#

The shopping cart from Track 9 gains one more primitive, describe, which turns a cart and its total into a sentence. Then come the new part: two methods marked with @decomposition, each composing the primitives into a worked example.

python
# cart.py
from opensymbolicai.core import decomposition, primitive

class ShoppingCart(PlanExecute):
    # new_cart, add_item, cart_total as before ...

    @primitive(read_only=True)
    def describe(self, cart: Cart, total: float) -> str:
        """Describe the cart's items and what they come to, as a sentence."""
        parts = [
            f"{item.quantity} {name} at {item.price:g} dollars each"
            for name, item in cart.items.items()
        ]
        return f"{' and '.join(parts)} is {total:g} dollars"

    @decomposition(
        intent="what do 2 apples at 3 dollars each and 1 loaf of bread at 2 dollars come to?"
    )
    def _example_small_order(self) -> str:
        cart = self.new_cart()
        cart = self.add_item(cart, "apples", 3, 2)
        cart = self.add_item(cart, "bread", 2, 1)
        total = self.cart_total(cart)
        text = self.describe(cart, total)
        return text

    @decomposition(intent="how much for 4 bananas at 2 dollars each?")
    def _example_single_item(self) -> str:
        cart = self.new_cart()
        cart = self.add_item(cart, "bananas", 2, 4)
        total = self.cart_total(cart)
        text = self.describe(cart, total)
        return text

Each decomposition is ordinary Python: a method that calls your primitives through self and returns a result. You will never call them yourself, and neither will the planner. Their job is to be read.

The library puts both into the planner's prompt under ## Example Decompositions, stripping self. so they read like a plan. Take the decompositions out of cart.py and this section disappears.

2. The planner follows the pattern#

Run some orders the examples never mention, each phrased its own way:

python
# main.py
QUERIES = [
    "ring up 5 oranges at 1 dollar each and 2 yogurts at 3 dollars each",
    "what's the damage on 3 notebooks at 4 dollars each and 2 pens at 1 dollar each?",
    "I'll take 10 eggs at 1 dollar each and 1 cake at 12 dollars",
]

for query in QUERIES:
    result = agent.run(query)
    print(result.plan)
    print(result.result)
bash
uv run main.py

Sample output for the first query:

text
cart = new_cart()
cart = add_item(cart, "oranges", 1, 5)
cart = add_item(cart, "yogurts", 3, 2)
total = cart_total(cart)
text = describe(cart, total)
return text

5 oranges at 1 dollars each and 2 yogurts at 3 dollars each is 11 dollars

The planner reused the pattern: make a cart, add items, total, describe. None of these queries matched an example word for word.

Takeaway#

@decomposition is how you show the planner how the pieces fit together. The intent is the query the example stands for, and the body is the plan that answers it. Write one example per distinct task shape you want the planner to follow reliably.