पाठ 4 / 31
Traces, Spans and Observations
यह पाठ अभी हिंदी में अनुवादित नहीं है, इसलिए अंग्रेज़ी में दिखाया जा रहा है।
Describe a trace as a tree of timed steps and read parent-child relationships in a trace view.
लेखक: Rahul Agarwal · 15 साल से वेब डेवलपर
A trace is a tree of steps
A trace represents one end-to-end unit of work, usually one user request such as "answer this chat message". Inside it are spans: timed steps with a name, start and end time, input, output and metadata. Spans nest. The top-level span (the root) might be chat-request; under it sit children such as retrieve-docs and llm-call, and an agent step can have its own children for each tool call. Every span except the root records its parent, which is how the tool draws the tree, and each span's start and end times show how long that step took. Naming differs between tools: Langfuse calls the steps observations (with types such as span, generation and event), while LangSmith calls each step a run and the tree a run tree. OpenTelemetry simply calls them spans. The idea is the same everywhere: one tree per request, so you can open a slow or wrong answer and see exactly which step misbehaved.
From request to trace tree
A request enters the app, the root span opens, child spans record retrieval and generation, and the finished tree is sent to the observability backend in the background.
Tracing nesting with a toy tracer
This tiny tracer keeps a stack of open spans, which is roughly how SDKs track the current parent. Follow how each span finds its parent.
from contextlib import contextmanager
stack = []
@contextmanager
def span(name):
parent = stack[-1] if stack else None
stack.append(name)
print(f"start {name} (parent={parent})")
try:
yield
finally:
stack.pop()
print(f"end {name}")
with span("chat-request"):
with span("retrieve-docs"):
pass
with span("llm-call"):
pass
- Line 16: Entering the outer `with` opens the root span. The stack is empty, so it has no parent.
- Line 17: The nested `with` opens a child. The top of the stack is `chat-request`, so that becomes the parent.
- Line 18: The block finishes, the `finally` pops the span and records its end.
- Line 19: The second child opens. The stack again holds only the root, so it is a sibling of the retrieval span, not its child.
- Line 20: The LLM span closes and pops.
- Line 16: Leaving the outer block closes the root last: a parent always ends after its children.
Running the toy tracer
The real output of the flow above. Real SDKs use context variables instead of a global list so async tasks and threads keep separate stacks.
from contextlib import contextmanager
stack = []
@contextmanager
def span(name):
parent = stack[-1] if stack else None
stack.append(name)
print(f"start {name} (parent={parent})")
try:
yield
finally:
stack.pop()
print(f"end {name}")
with span("chat-request"):
with span("retrieve-docs"):
pass
with span("llm-call"):
pass
Output:
start chat-request (parent=None) start retrieve-docs (parent=chat-request) end retrieve-docs start llm-call (parent=chat-request) end llm-call end chat-request
त्वरित जाँच: In a trace tree, what is the relationship between a retrieval span and an LLM span that both run directly inside the root span?
- The LLM span is the parent of the retrieval span
- They belong to two different traces
- They are siblings: both are children of the root span
- The retrieval span is the root
Answer
They are siblings: both are children of the root span — Both were opened while the root was the current span, so both record the root as their parent, making them siblings.