Skip to main content
A trajectory is one OTel span that stays open across a multi-step turn. Subsequent bento.track_ai and tool_span calls in the same task become children of it, so the whole flow shows up as one trace instead of N independent rows. This is an advanced feature. For most apps, an integration or tracking events is enough. Trajectories matter when one trace must span multiple LLM calls and tool calls within a single logical turn. When bento.instrument() is active, spans the Google ADK integration captures share the trajectory’s trace_id. One with bento.begin(...) block stitches manual and integration spans into one trace.

bento.begin()

The context-manager form auto-finishes on exit and records exceptions on the trajectory span.
The imperative form is available for code that can’t use with:

Parenting and isolation

Trajectories detach from any outer OTel context, the same as a bare track_ai. So they’re root spans in their own trace even when called inside a customer’s existing FastAPI / Django / etc. instrumented stack. track_ai calls inside a begin()/finish() block parent to the trajectory.
Trajectories must be finished in reverse open order (LIFO). Finishing an outer trajectory while an inner one is still open raises RuntimeError. Use with bento.begin(...) as i: to guarantee correct nesting.

Decorators

Both decorators wrap function-shaped work, and both work on sync and async functions.

@bento.interaction

Wraps a function in bento.begin() / interaction.finish(). The function name becomes the event name; the return value becomes output.value. It does not auto-capture arguments, which are often non-trivial to serialize and frequently contain sensitive data. To record them, call interaction.update(input=...) from inside the function, or use @bento.tool, which does capture.

@bento.tool

Wraps a function in bento.tool_span(...). The function’s bound arguments become input.value (as a JSON dict), and the return value becomes output.value. The span carries openinference.span.kind="tool" so downstream classification picks it up. Set capture_input=False or capture_output=False for sensitive args or huge return payloads.

Tool spans

The module-level helper opens a tool span without threading an Interaction handle through code:
Same parenting rule as track_ai: parents to the active trajectory if begin() is open in this task, else becomes a detached root. Exceptions raised inside the block are recorded on the span (status=ERROR, exception event) and re-raised.

ToolSpan API

A ToolSpan carries two methods:

Interaction API

An Interaction is the handle begin() returns: