All tutorials
Track 9·Observability

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.

beginner10 min
Video coming soon
Browse this tutorial's folder in tutorials-pygithub.com/OpenSymbolicAI/tutorials-py/tree/main/09-read-the-trace

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#

python
# 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:

bash
uv add opensymbolicai-core pydantic

2. Walk the trace#

python
# 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)
bash
uv run main.py

Sample output:

text
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: True

What you're looking at#

Each step is a TraceStep. Its fields:

  • step_number: position in the plan
  • statement: the line of code that ran
  • namespace_before: the variable bindings available before this line
  • result_value: what the line produced
  • namespace_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.