Before you start
Track 8 read result.plan: the code the model wrote, before any of it ran. This
track reads result.trace, that same code after it ran. Each line of the plan
becomes a step, and each step keeps a record: the statement it executed, the
value that statement produced, and the namespace just before and just after.
A calculator folds its math into a single nested expression, which makes for a short trace. So this track uses a shopping cart instead. Building a cart is a sequence of distinct steps: make it, add an item, add another, total it. The trace gets one step for each.
1. A small shopping-cart agent
# cart.py
from pydantic import BaseModel
from opensymbolicai.blueprints import PlanExecute
from opensymbolicai.core import primitive
class Item(BaseModel):
price: float
quantity: int
class Cart(BaseModel):
items: dict[str, Item] = {}
class ShoppingCart(PlanExecute):
@primitive(read_only=True)
def new_cart(self) -> Cart:
"""Make a new, empty shopping cart."""
return Cart()
@primitive(read_only=True)
def add_item(self, cart: Cart, name: str, price: float, quantity: int) -> Cart:
"""Add an item to the cart with its unit price and quantity."""
return Cart(items={**cart.items, name: Item(price=price, quantity=quantity)})
@primitive(read_only=True)
def cart_total(self, cart: Cart) -> float:
"""Total the cart: sum price times quantity for every item."""
return sum(item.price * item.quantity for item in cart.items.values())Install pydantic alongside the framework:
uv add opensymbolicai-core pydantic2. Walk the trace
# main.py
from cart import ShoppingCart
from opensymbolicai.llm import LLMConfig
QUERY = (
"add 2 apples at 3 dollars each, 2 loaves of bread at 2 dollars each, "
"and 3 cartons of milk at 4 dollars each, then total it up"
)
agent = ShoppingCart(llm=LLMConfig(provider="ollama", model="qwen2.5-coder:7b"))
result = agent.run(QUERY)
print(result.task) # the query
print(result.plan) # the whole program, as a string
for step in result.trace.steps:
print(step.step_number, step.statement)
print(" before:", step.namespace_before)
print(" value: ", step.result_value)
print(" after: ", step.namespace_after)
print("all succeeded:", result.trace.all_succeeded)uv run main.pySample output:
step 1: cart = new_cart()
before: {}
value: items={}
after: {'cart': Cart(items={})}
step 2: cart = add_item(cart, "apples", 3, 2)
before: {'cart': Cart(items={})}
value: items={'apples': Item(price=3.0, quantity=2)}
after: {'cart': Cart(items={'apples': ...})}
...
step 5: total = cart_total(cart)
before: {'cart': Cart(items={...})}
value: 22.0
after: {'cart': ..., 'total': 22.0}
all succeeded: TrueWhat you're looking at
Each step is a TraceStep. Its fields:
step_number: position in the planstatement: the line of code that rannamespace_before: the variable bindings available before this lineresult_value: what the line producednamespace_after: the variable bindings after this line ran
trace.all_succeeded is True when every step ran without error.
trace.failed_steps lists the ones that did not.
Reading the trace, you watch the cart fill up one item at a time.