Manual trace creation with hierarchical run trees for fine-grained control over tracing.
RunTree provides a programmatic API for creating hierarchical traces manually, giving you fine-grained control over trace structure and timing. While traceable() is recommended for most use cases, RunTree is essential when you need explicit control over trace creation, distributed tracing across services, or custom event logging.
When to use RunTree:
import { RunTree } from "langsmith";
import { isRunTree, isRunnableConfigLike, convertToDottedOrderFormat } from "langsmith";Create and manage hierarchical trace structures manually.
/**
* Hierarchical run tree for manual tracing
*/
class RunTree {
/** Unique run ID */
id: string;
/** Run name */
name: string;
/** Run type */
run_type?: string;
/** Project name */
project_name?: string;
/** Parent run */
parent_run?: RunTree;
/** Child runs */
child_runs: RunTree[];
/** Start timestamp (ms) */
start_time: number;
/** End timestamp (ms) */
end_time?: number;
/** Input data */
inputs: KVMap;
/** Output data */
outputs?: KVMap;
/** Error message if failed */
error?: string;
/** Metadata */
metadata?: KVMap;
/** Tags */
tags?: string[];
/** Dotted order for distributed tracing */
dotted_order?: string;
/** Trace ID for distributed tracing */
trace_id?: string;
/** Client instance */
client?: Client;
/** Attachments */
attachments?: Attachments;
/** LLM invocation parameters */
invocation_params?: InvocationParamsSchema;
/**
* Create new run tree
* @param config - Run tree configuration
*/
constructor(config: RunTreeConfig);
/**
* Create child run
* @param config - Partial configuration (inherits from parent)
* @returns Child RunTree instance
*/
createChild(config: Partial<RunTreeConfig>): RunTree;
/**
* End the run
* @param outputs - Output data
* @param error - Error message if failed
* @param endTime - End timestamp (defaults to Date.now())
* @param metadata - Additional metadata to merge
*/
end(
outputs?: KVMap,
error?: string,
endTime?: number,
metadata?: KVMap
): Promise<void>;
/**
* Post run to LangSmith API
* @param excludeChildRuns - If true, only post this run (not children)
*/
postRun(excludeChildRuns?: boolean): Promise<void>;
/**
* Update run via PATCH request
*/
patchRun(): Promise<void>;
/**
* Convert to JSON representation
*/
toJSON(): object;
/**
* Add event to run
* @param event - Run event
*/
addEvent(event: RunEvent): void;
/**
* Convert to headers for distributed tracing
* @param headers - Existing headers to merge with
* @returns Headers with trace context
*/
toHeaders(headers?: Record<string, string>): Record<string, string>;
/**
* Create from LangChain runnable config
* @param parentConfig - Parent runnable config
* @param props - Additional run tree properties
* @returns RunTree instance
*/
static fromRunnableConfig(
parentConfig: RunnableConfigLike,
props: Partial<RunTreeConfig>
): RunTree;
/**
* Create from dotted order string
* @param dottedOrder - Dotted order format string
* @returns RunTree instance
*/
static fromDottedOrder(dottedOrder: string): RunTree;
/**
* Create from trace headers
* @param headers - HTTP headers containing trace context
* @param inheritArgs - Additional configuration
* @returns RunTree instance or undefined if headers don't contain trace context
*/
static fromHeaders(
headers: Record<string, string>,
inheritArgs?: Partial<RunTreeConfig>
): RunTree | undefined;
}
interface RunTreeConfig {
/** Run name (required) */
name: string;
/** Run type */
run_type?: string;
/** Run ID (auto-generated if not provided) */
id?: string;
/** Project name */
project_name?: string;
/** Parent run */
parent_run?: RunTree | typeof ROOT;
/** Parent run ID */
parent_run_id?: string;
/** Child runs */
child_runs?: RunTree[];
/** Client instance */
client?: Client;
/** Start timestamp in milliseconds or ISO string */
start_time?: number | string;
/** End timestamp in milliseconds or ISO string */
end_time?: number | string;
/** Extra metadata */
extra?: KVMap;
/** Metadata */
metadata?: KVMap;
/** Tags */
tags?: string[];
/** Error message */
error?: string;
/** Input data */
inputs?: KVMap;
/** Output data */
outputs?: KVMap;
/** Reference example ID for evaluation */
reference_example_id?: string;
/** Serialized representation */
serialized?: object;
/** Whether tracing is enabled for this run */
tracingEnabled?: boolean;
/** End callback */
on_end?: (runTree: RunTree) => void;
/** Execution order */
execution_order?: number;
/** Child execution order */
child_execution_order?: number;
/** Dotted order for distributed tracing */
dotted_order?: string;
/** Trace ID */
trace_id?: string;
/** Attachments */
attachments?: Attachments;
/** Replicas for distributed runs */
replicas?: Replica[];
/** Distributed parent ID */
distributedParentId?: string;
}
type KVMap = Record<string, any>;
type Attachments = Record<string, AttachmentData>;
interface AttachmentData {
mime_type: string;
data: string | Uint8Array;
}
type Replica = ProjectReplica | WriteReplica;
type ProjectReplica = [string, KVMap | undefined];
interface WriteReplica {
apiUrl?: string;
apiKey?: string;
workspaceId?: string;
projectName?: string;
updates?: KVMap | undefined;
fromEnv?: boolean;
reroot?: boolean;
}import { RunTree } from "langsmith";
// Create root run
const parentRun = new RunTree({
name: "parent-operation",
run_type: "chain",
inputs: { query: "What is AI?" },
});
// Create child runs
const llmRun = parentRun.createChild({
name: "llm-call",
run_type: "llm",
inputs: { prompt: "What is AI?" },
});
await llmRun.end({ response: "AI is..." });
await llmRun.postRun();
// End parent run
await parentRun.end({ result: "Complete" });
await parentRun.postRun();
// With error handling
const errorRun = new RunTree({
name: "failing-operation",
run_type: "tool",
});
try {
const result = await riskyOperation();
await errorRun.end({ result });
} catch (error) {
await errorRun.end(undefined, error.message);
}
await errorRun.postRun();Add timestamped events to runs for detailed logging.
interface RunEvent {
/** Event name */
name: string;
/** Event timestamp in milliseconds */
time: number;
/** Event metadata */
kwargs?: KVMap;
}import { RunTree } from "langsmith";
const run = new RunTree({
name: "complex-operation",
run_type: "chain",
});
// Add events during execution
run.addEvent({
name: "retrieval_started",
time: Date.now(),
kwargs: { query: "search term" },
});
const docs = await retrieveDocuments();
run.addEvent({
name: "retrieval_completed",
time: Date.now(),
kwargs: { num_docs: docs.length },
});
await run.end({ result: docs });
await run.postRun();Check if a value is a RunTree instance.
/**
* Type guard for RunTree
* @param x - Value to check
* @returns True if value is a RunTree
*/
function isRunTree(x: any): x is RunTree;import { isRunTree, RunTree } from "langsmith";
function logRun(run: any) {
if (isRunTree(run)) {
console.log(`Run: ${run.name} (${run.id})`);
}
}Create RunTree from LangChain runnable config.
/**
* Type guard for LangChain runnable config
* @param x - Value to check
* @returns True if value is a runnable config
*/
function isRunnableConfigLike(x: any): x is RunnableConfigLike;
interface RunnableConfigLike {
callbacks?: any;
tags?: string[];
metadata?: KVMap;
run_name?: string;
[key: string]: any;
}import { RunTree } from "langsmith";
// From LangChain config
const langchainConfig = {
callbacks: callbackManager,
tags: ["langchain"],
metadata: { version: "1.0" },
};
const runTree = RunTree.fromRunnableConfig(langchainConfig, {
name: "my-chain",
run_type: "chain",
});Propagate trace context across services using headers or dotted order.
/**
* Convert epoch and run ID to dotted order format
* @param epoch - Epoch timestamp
* @param runId - Run ID
* @param executionOrder - Execution order (optional)
* @returns Dotted order string
*/
function convertToDottedOrderFormat(
epoch: number,
runId: string,
executionOrder?: number
): string;import { RunTree } from "langsmith";
// Service A: Create run and export headers
const runA = new RunTree({
name: "service-a",
run_type: "chain",
});
const headers = runA.toHeaders();
// Send headers to Service B
// Service B: Receive and continue trace
const runB = RunTree.fromHeaders(headers, {
name: "service-b",
run_type: "chain",
});
// Runs are now linked in the same trace
await runB.end({ result: "done" });
await runB.postRun();
// Alternative: Use dotted order
const dottedOrder = convertToDottedOrderFormat(
Date.now(),
runA.id,
1
);
const runC = RunTree.fromDottedOrder(dottedOrder);import { RunTree } from "langsmith";
import express from "express";
import axios from "axios";
// Service A: API Gateway
const app = express();
app.post("/process", async (req, res) => {
const run = new RunTree({
name: "api-gateway",
run_type: "chain",
inputs: req.body,
});
try {
// Call Service B with trace context
const response = await axios.post(
"http://service-b/process",
req.body,
{
headers: run.toHeaders(),
}
);
await run.end({ result: response.data });
await run.postRun();
res.json(response.data);
} catch (error) {
await run.end(undefined, error.message);
await run.postRun();
res.status(500).json({ error: error.message });
}
});
// Service B: Processing Service
const serviceB = express();
serviceB.post("/process", async (req, res) => {
// Continue trace from headers
const parentRun = RunTree.fromHeaders(req.headers);
const run = parentRun
? parentRun.createChild({
name: "processing-service",
run_type: "chain",
inputs: req.body,
})
: new RunTree({
name: "processing-service",
run_type: "chain",
inputs: req.body,
});
try {
const result = await processData(req.body);
await run.end({ result });
await run.postRun();
res.json(result);
} catch (error) {
await run.end(undefined, error.message);
await run.postRun();
res.status(500).json({ error: error.message });
}
});traceable() for automatic tracing of functions (recommended for most cases)RunTree for:
// Post runs as you complete them to avoid memory buildup
const parent = new RunTree({ name: "parent", run_type: "chain" });
for (let i = 0; i < 1000; i++) {
const child = parent.createChild({ name: `child-${i}`, run_type: "tool" });
await child.end({ result: i });
await child.postRun(); // Post immediately
}
await parent.end();
await parent.postRun();const run = new RunTree({ name: "operation", run_type: "chain" });
try {
const result = await riskyOperation();
await run.end({ result });
} catch (error) {
// Log error in run
await run.end(undefined, error.message);
throw error; // Re-throw if needed
} finally {
// Always post the run
await run.postRun();
}import { RunTree } from "langsmith";
// Create a complex hierarchical trace
const pipeline = new RunTree({
name: "rag-pipeline",
run_type: "chain",
inputs: { question: "What is quantum computing?" },
});
// Query understanding
const understanding = pipeline.createChild({
name: "query-understanding",
run_type: "chain",
});
const expansion = understanding.createChild({
name: "query-expansion",
run_type: "llm",
});
await expansion.end({ expanded: ["quantum computing", "qubits", "superposition"] });
await expansion.postRun();
await understanding.end({ processed: true });
await understanding.postRun();
// Retrieval
const retrieval = pipeline.createChild({
name: "document-retrieval",
run_type: "retriever",
});
const search = retrieval.createChild({
name: "vector-search",
run_type: "tool",
});
await search.end({ documents: ["doc1", "doc2", "doc3"] });
await search.postRun();
const reranking = retrieval.createChild({
name: "reranking",
run_type: "tool",
});
await reranking.end({ rankedDocs: ["doc2", "doc1"] });
await reranking.postRun();
await retrieval.end({ finalDocs: ["doc2", "doc1"] });
await retrieval.postRun();
// Generation
const generation = pipeline.createChild({
name: "answer-generation",
run_type: "llm",
});
generation.addEvent({
name: "context_prepared",
time: Date.now(),
kwargs: { docCount: 2 },
});
await generation.end({ answer: "Quantum computing is..." });
await generation.postRun();
// Complete pipeline
await pipeline.end({ answer: "Quantum computing is..." });
await pipeline.postRun();import { RunTree } from "langsmith";
const run = new RunTree({
name: "analysis",
run_type: "chain",
inputs: { data: "..." },
metadata: {
userId: "user-123",
environment: "production",
version: "1.2.0",
},
tags: ["analysis", "production"],
attachments: {
"input.pdf": {
mime_type: "application/pdf",
data: pdfBuffer,
},
},
});
// Add more metadata during execution
await run.end(
{ result: "..." },
undefined,
undefined,
{
duration_ms: 1234,
tokens_used: 500,
}
);
await run.postRun();traceable() decorator