Reading a trace
Once fetch_spans has found a trace, two calls read it whole: the tree, then the bodies.
The tree
Section titled “The tree”tree = client.fetch_trace_spans(trace_id, run="grpo-7b-v2") # run=None for a project trace span_id parent_span_id ordinal kind op_name status token_count0 0 0 0 sub_agent rollout ok 8121 1 0 0 llm_call policy ok 6202 2 0 1 tool_call search ok 03 3 2 0 artifact results.json ok 04 4 0 2 sub_agent verifier ok 1925 5 4 0 llm_call verifier.policy ok 192Every span of the trace, root first and then by span id, which is creation order. The columns are those of
fetch_spans, and attributes= adds attribute columns
the same way. Build the tree by attaching each row to its parent_span_id (0 is the root) and ordering siblings
by ordinal. A span with status running has not been closed: still in progress, or its producer stopped before
closing it.
The bodies
Section titled “The bodies”from metrana.query import TracePayloadKind
payloads = client.fetch_span_payloads(trace_id, tree["span_id"].tolist(), run="grpo-7b-v2")
payloads[(1, TracePayloadKind.LLM_PROMPT)].body # the prompt, as text (JSON if it was logged as a list)payloads[(1, TracePayloadKind.LLM_COMPLETION)].bodypayloads[(0, TracePayloadKind.FINAL_OUTPUT)].body # the root's own bodies live on span 0payloads[(0, TracePayloadKind.ATTRIBUTES)].body # {"reward": 0.73, "rollout": "A"}fetch_span_payloads returns a SpanPayloads mapping (span_id, kind) to a SpanPayload, for the spans asked
(span_ids is one id or several; the default 0 is the root) and the kinds asked (kinds=, every kind logged when
empty). A kind the span never logged is simply absent.
| Kind | Where it appears | Body |
|---|---|---|
LLM_PROMPT, LLM_COMPLETION |
LLM-call spans, and a trace rooted at an LLM call (span 0) | text |
TOOL_INPUT, TOOL_OUTPUT |
Tool-call spans, and a tool-rooted trace | text |
THOUGHT |
Thought spans | text |
TASK_INPUT, FINAL_OUTPUT |
Span 0 of an agent-shaped trace | text |
INPUT, OUTPUT |
Any node: sub-agents, iterations, functions, @op calls |
text |
ATTRIBUTES |
Any node | a dict of the logged attributes |
Text bodies are strings; what was logged as a list or dict comes back as its JSON, so json.loads restores it.
SpanPayload also carries data_length (the whole body’s size as the logger stated it), truncated (ingestion cut
it at the 16 MiB cap) and complete.
Payloads are read in windows of 16 MiB and stitched, so a long completion arrives whole; at most 100 spans are read per request, which the method batches for you.