Tracing is the foundation of observability in LangSmith. It captures the execution flow of your LLM applications as hierarchical trees of runs, allowing you to debug, monitor, and optimize your applications.
What is a trace?
A trace represents the complete execution path of a request through your application. It’s composed of individual runs, which are units of work like LLM calls, tool invocations, or chain executions. Each run can have child runs, forming a tree structure that reflects your application’s execution hierarchy.
Trace (Root Run)
├── Chain Run
│ ├── LLM Run
│ └── Tool Run
│ └── Retriever Run
└── Parser Run
Run types
LangSmith supports several run types to categorize different operations in your application:
llm - Large language model calls (e.g., OpenAI, Anthropic)
chain - Sequences of operations or workflows
tool - Function or tool invocations
retriever - Document retrieval operations
embedding - Text embedding generation
prompt - Prompt formatting or templating
parser - Output parsing operations
While these are the most common types, you can use any string value for run_type to suit your application’s needs.
Creating traces
Using the @traceable decorator
The simplest way to create traces is with the @traceable decorator:
from langsmith import traceable
@traceable(run_type="llm")
def call_llm(prompt: str) -> str:
# Your LLM call logic
return "response"
@traceable(run_type="chain")
def my_workflow(user_input: str) -> str:
# This creates a parent run
result = call_llm(user_input) # This creates a child run
return result
# When called, this automatically creates a trace
my_workflow("Hello, world!")
Using RunTree directly
For more control, you can use the RunTree class:
from langsmith.run_trees import RunTree
# Create a root run
run = RunTree(
name="my_application",
run_type="chain",
inputs={"query": "What is LangSmith?"},
)
# Create a child run
child = run.create_child(
name="llm_call",
run_type="llm",
inputs={"prompt": "What is LangSmith?"},
)
# End the child run
child.end(outputs={"response": "LangSmith is a platform..."})
# End the parent run
run.end(outputs={"answer": "LangSmith is a platform..."})
# Post to LangSmith
run.post()
Run properties
Each run captures comprehensive information about the execution:
id - Unique identifier (UUID v7, time-ordered)
name - Human-readable name for the run
run_type - Category of the operation
inputs - Input data passed to the operation
outputs - Output data produced by the operation
start_time / end_time - Execution timing
error - Error message if the run failed
tags - Labels for filtering and organization
metadata - Additional context and information
trace_id - ID of the root run in the trace
parent_run_id - ID of the parent run
dotted_order - Hierarchical ordering string
Trace organization with dotted order
LangSmith uses a dotted order system to maintain the execution sequence of runs within a trace. This ensures traces are displayed in the correct chronological order.
The dotted order is a string composed of timestamp and UUID segments separated by dots:
20230914T223155647Z1b64098b-4ab7-43f6-afee-992304f198d8
For child runs, the parent’s dotted order is prepended:
Parent: 20230914T223155647Z1b64098b-4ab7-43f6-afee-992304f198d8
Child 1: 20230914T223155647Z1b64098b-4ab7-43f6-afee-992304f198d8.20230914T223155649Z809ed3a2-0172-4f4d-8a02-a64e9b7a0f8a
Child 2: 20230914T223155647Z1b64098b-4ab7-43f6-afee-992304f198d8.20230914T223155650Zc8d9f4c5-6c5a-4b2d-9b1c-3d9d7a7c5c7c
This allows traces to be sorted and displayed in execution order, even when runs arrive at the server out of order.
Enrich your traces with additional context:
@traceable(
run_type="chain",
tags=["production", "experiment-v2"],
metadata={"user_id": "123", "version": "2.0"}
)
def my_function(input: str):
return process(input)
# Or add dynamically during execution
from langsmith import get_current_run_tree
@traceable(run_type="chain")
def my_function(input: str):
run = get_current_run_tree()
run.add_tags(["dynamic-tag"])
run.add_metadata({"custom_field": "value"})
return process(input)
Distributed tracing
For distributed systems (e.g., microservices), you can propagate trace context across process boundaries using headers:
from langsmith.run_trees import RunTree
# Service A: Create a run and export headers
run = RunTree(name="service_a", run_type="chain", inputs={"data": "value"})
headers = run.to_headers()
# Pass headers to Service B (e.g., via HTTP)
# headers = {"langsmith-trace": "...", "baggage": "..."}
# Service B: Continue the trace from headers
parent_run = RunTree.from_headers(headers)
child_run = parent_run.create_child(
name="service_b",
run_type="tool",
inputs={"received": "data"}
)
The baggage header propagates metadata, tags, and project names across distributed traces.
Error handling
When an error occurs, it’s automatically captured in the trace:
@traceable(run_type="llm")
def call_llm(prompt: str):
try:
# LLM call that might fail
return llm.generate(prompt)
except Exception as e:
# Error is automatically captured by @traceable
raise
# Or manually set error information
run = RunTree(name="my_run", run_type="llm", inputs={"prompt": "test"})
try:
result = risky_operation()
run.end(outputs={"result": result})
except Exception as e:
run.end(error=str(e))
run.post()
Next steps