Read the execution trace
The plan after it ran, step by step, in result.trace. Each step records the statement, the value it produced, and the namespace before and after.
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.