Sessions are the foundation of the rLLM SDK’s trace collection system. They provide automatic tracking of all LLM calls within a context, along with metadata management and nested context support.
Session Basics
Creating a Session
Use the session() context manager to create a session with auto-generated name:
from rllm.sdk import session, get_chat_client
llm = get_chat_client(api_key="sk-...")
with session(experiment="v1") as sess:
response = llm.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Hello"}]
)
# Access collected traces
print(f"Collected {len(sess.llm_calls)} traces")
for trace in sess.llm_calls:
print(f"Model: {trace.model}, Tokens: {trace.tokens}")
The session name is automatically generated (e.g., sess_abc123def456). All metadata passed as keyword arguments is attached to every trace.
Accessing Session Data
with session(experiment="v1", task="greeting") as sess:
llm.chat.completions.create(...)
# Get all traces
traces = sess.llm_calls # List[Trace]
# Get traces as steps (with reward field)
steps = sess.steps # List[StepView]
# Session properties
print(sess.name) # Session name
print(sess.metadata) # {"experiment": "v1", "task": "greeting"}
print(len(sess)) # Number of traces
Session Backends
The SDK supports two session backends, configured via rllm/sdk/config.yaml:
# config.yaml
session_backend: "contextvar" # or "opentelemetry"
ContextVar Backend (Default)
Uses Python’s contextvars for in-process context tracking:
from rllm.sdk import session, get_current_session
# Create session
with session(experiment="v1") as sess:
# Get current session from anywhere in call stack
current = get_current_session()
assert current is sess
llm.chat.completions.create(...)
Features:
- Thread-safe context propagation
- Lightweight in-memory buffer
- Works within a single process
- Ideal for local development and testing
OpenTelemetry Backend
Uses W3C baggage for distributed tracing across processes:
from rllm.sdk.session import otel_session, configure_default_tracer
# Configure tracer once per process
configure_default_tracer(service_name="my-agent")
# Sessions use W3C baggage for context propagation
with otel_session(name="client") as client_session:
# HTTP calls automatically carry session context via baggage headers
httpx.post("http://server/api", ...)
# On the server side:
with otel_session(name="handler") as server_session:
llm.chat.completions.create(...)
# server_session automatically inherits client's UID chain
Features:
- W3C baggage as single source of truth
- Automatic context propagation across HTTP boundaries
- Span-based session UIDs for distributed tracing
- Compatible with OpenTelemetry observability tools
Installation:
Attach metadata to all traces in a session:
with session(
experiment="v1",
task="math_solver",
user_id="alice",
model_version="1.0"
):
llm.chat.completions.create(...)
# All traces include the metadata
Nested sessions automatically merge metadata:
with session(experiment="v1", environment="production"):
# Outer session metadata
with session(task="math", user="alice"):
# Merged metadata: {
# "experiment": "v1",
# "environment": "production",
# "task": "math",
# "user": "alice"
# }
llm.chat.completions.create(...)
with session(task="coding"):
# Merged metadata: {
# "experiment": "v1",
# "environment": "production",
# "task": "coding" # Overrides parent
# }
llm.chat.completions.create(...)
Inner sessions can override parent metadata by using the same key.
from rllm.sdk import get_current_metadata, get_current_session_name
with session(experiment="v1", task="greeting"):
# Get current metadata from anywhere
metadata = get_current_metadata()
print(metadata) # {"experiment": "v1", "task": "greeting"}
# Get current session name
name = get_current_session_name()
print(name) # "sess_abc123def456"
Session Context API
ContextVarSession
The default session implementation:
from rllm.sdk import ContextVarSession
sess = ContextVarSession(
name=None, # Auto-generated if None
formatter=None, # Optional trace formatter (deprecated)
persistent_tracers=None, # Optional persistent tracers
**metadata # Session metadata
)
with sess:
llm.chat.completions.create(...)
# Properties
traces = sess.llm_calls # List[Trace]
steps = sess.steps # List[StepView]
uid = sess._uid # Unique session ID
chain = sess._session_uid_chain # UID hierarchy
# Methods
sess.clear_calls() # Clear all traces
Key Properties:
name: str - Session name (auto-generated or inherited)
_uid: str - Internal unique ID for this session instance
_session_uid_chain: list[str] - UID chain for hierarchy support
metadata: dict - Session metadata
storage: SessionBuffer - In-memory trace buffer
llm_calls: list[Trace] - All traces from this session and nested children
steps: list[StepView] - All traces converted to StepView format
OpenTelemetrySession
Distributed session implementation:
from rllm.sdk.session import otel_session, configure_default_tracer
# Configure once per process
configure_default_tracer(service_name="my-agent")
# Create session
with otel_session(
name="workflow",
experiment="v1",
task="search"
) as sess:
# Properties read from baggage
name = sess.name # From baggage
metadata = sess.metadata # From baggage
uid_chain = sess._session_uid_chain # From baggage
# Async trace access
traces = await sess.llm_calls_async()
Key Features:
- Baggage is the single source of truth for state
- Session object manages baggage lifecycle
- Properties read directly from baggage (works cross-process)
- Span attributes are write-only copies for observability
Context Helpers
from rllm.sdk import (
get_current_session,
get_current_session_name,
get_current_metadata,
get_active_session_uids,
)
with session(experiment="v1") as sess:
# Get current session instance (ContextVar backend only)
current = get_current_session()
# Get session name (works with all backends)
name = get_current_session_name()
# Get merged metadata
metadata = get_current_metadata()
# Get active session UID chain
uids = get_active_session_uids() # [outer_uid, ..., inner_uid]
get_current_session() only works with the ContextVar backend. For OpenTelemetry, use get_current_metadata(), get_current_session_name(), or get_active_session_uids() instead.
Session Buffers
Sessions use lightweight in-memory buffers to store traces:
from rllm.sdk import SessionBuffer
buffer = SessionBuffer()
# Add trace
buffer.add_trace(trace, session_uid="ctx_abc", session_name="sess_123")
# Get traces for a session
traces = buffer.get_traces(session_uid="ctx_abc", session_name="sess_123")
# Clear traces
buffer.clear(session_uid="ctx_abc", session_name="sess_123")
Key Features:
- Automatic parent-child hierarchy tracking
- Parent sessions see traces from nested children
- Ephemeral storage (in-memory only)
- Thread-safe access
Advanced Usage
Runtime Session Wrapping
Wrap agent functions with automatic session context:
from rllm.sdk.session import wrap_with_session_context
async def agent_func(input_data):
# Agent logic with LLM calls
result = await llm.chat.completions.create(...)
return result
# Wrap with session context
wrapped_fn = wrap_with_session_context(
agent_func,
tracer_service_name="my-agent"
)
# Call with metadata
metadata = {"session_name": "sess_123", "experiment": "v1"}
output, session_uid = await wrapped_fn(metadata, input_data)
This is useful for integrating with execution engines that manage metadata externally.
Custom Storage Backends
Implement custom storage for persistent trace collection:
from rllm.sdk import SqliteTracer
tracer = SqliteTracer(db_path="./traces.db")
# Traces are automatically persisted to SQLite
with session(experiment="v1") as sess:
llm.chat.completions.create(...)
# Traces saved to database
Configuration
Backend Selection
Check or configure the session backend:
from rllm.sdk.session import SESSION_BACKEND
print(SESSION_BACKEND) # "contextvar" or "opentelemetry"
To change the backend, edit rllm/sdk/config.yaml:
session_backend: "opentelemetry"
Session Protocol
All session implementations follow the SessionProtocol:
from rllm.sdk.session import SessionProtocol
class CustomSession(SessionProtocol):
name: str
metadata: dict[str, Any]
@property
def llm_calls(self) -> list[dict[str, Any]]:
# Implementation
pass
def clear_calls(self) -> None:
# Implementation
pass
def __enter__(self) -> SessionProtocol:
# Implementation
pass
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
# Implementation
pass
Best Practices
- Use metadata for experiment tracking: Tag sessions with experiment names, versions, and configurations
- Leverage nested sessions: Organize complex workflows with hierarchical sessions
- Choose the right backend: Use ContextVar for local development, OpenTelemetry for distributed systems
- Access metadata dynamically: Use
get_current_metadata() to access context from deep in the call stack
- Clear sessions when appropriate: For long-running processes, clear session buffers to manage memory
Next Steps