Skip to main content
Use manual spans for work that does not go through an instrumented SDK, such as CLI subprocess calls, custom LLM clients, tool execution, provider routing, or a high-level agent loop. Child spans created by instrumented SDKs inside the manual span are parented through OpenTelemetry context propagation.

Parent Span Around Automatic Child Spans

import { agentSpan, setup } from "@inference/tracing";
import OpenAI from "openai";

const tracing = await setup({ modules: { openai: OpenAI } });
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

await agentSpan(
  tracing.tracer,
  { name: "RefundReviewAgent", system: "openai" },
  async (span) => {
    const ticket = { id: "ticket_123", orderId: "ABC-123" };
    span.setInput(ticket);
    const response = await client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [
        { role: "user", content: `Review refund for ${ticket.orderId}` },
      ],
    });
    span.setOutput({ decision: response.choices[0]?.message.content });
    if (response.usage != null) {
      span.recordTokens({
        prompt: response.usage.prompt_tokens ?? 0,
        completion: response.usage.completion_tokens ?? 0,
      });
    }
  },
);

await tracing.shutdown();

CLI Or Subprocess Work

When a tool has no instrumentable SDK, wrap the process call and set input and output yourself.
TypeScript
import { agentSpan, setup } from "@inference/tracing";
import { execFile } from "node:child_process";
import { promisify } from "node:util";

const execFileP = promisify(execFile);
const tracing = await setup();
const prompt = "Reply with just the word hello.";

await agentSpan(
  tracing.tracer,
  { name: "ClaudeCode", system: "anthropic", spanName: "claude-code.invocation" },
  async (span) => {
    span.setInput(prompt);
    const { stdout } = await execFileP("claude", ["-p", prompt], {
      encoding: "utf-8",
      timeout: 60_000,
    });
    span.setOutput(stdout.trim());
  },
);

await tracing.shutdown();

Custom Span Kind

Use manual_span() in Python when the operation should be a CHAIN, TOOL, RETRIEVER, or another OpenInference span kind.
Python
from inference_catalyst_tracing import SpanKindValues, manual_span, setup

tracing = setup()

with manual_span(
    tracing.tracer,
    name="provider_router/select_model",
    span_kind=SpanKindValues.CHAIN,
    system="internal",
    input={"route": "support", "priority": "high"},
    metadata={"customer_tier": "enterprise"},
    tags=["router", "support"],
) as span:
    selected = choose_model_for_request()
    span.set_output({"model": selected.model, "reason": selected.reason})

tracing.shutdown()