FITracer & custom spans

Create custom spans, set span kinds, and use FITracer decorators.

Beyond auto-instrumentation, FITracer lets you create custom spans for your own logic - agent steps, chain stages, tool calls, or any operation you want to trace.

Span Kinds

All languages share the same span kinds:

KindUse for
LLMLanguage model inference calls
CHAINSequential pipeline steps
AGENTAutonomous agent actions
TOOLTool/function calls
EMBEDDINGVector generation
RETRIEVERDocument retrieval (RAG)
RERANKERRe-ranking operations
GUARDRAILSafety/validation checks
EVALUATORQuality scoring
UNKNOWNUnspecified or unexpected span type
WORKFLOWCustom pipeline steps (Java only)
CONVERSATIONVoice/conversational AI (Java/C#)
VECTOR_DBVector database operations (Java/C#)

Decorators and Convenience Methods

Python’s FITracer provides decorators for clean span creation:

from fi_instrumentation import register
from fi_instrumentation.fi_types import ProjectType

trace_provider = register(
    project_name="my-project",
    project_type=ProjectType.OBSERVE,
)
tracer = trace_provider.get_tracer(__name__)

# Use the FITracer wrapper for decorators
from fi_instrumentation import FITracer
fi_tracer = FITracer(tracer)

@fi_tracer.agent(name="research-agent")
def research_agent(query):
    # This entire function becomes an AGENT span
    results = search(query)
    return summarize(results)

@fi_tracer.chain(name="rag-pipeline")
def rag_pipeline(question):
    docs = retrieve(question)
    return generate(question, docs)

@fi_tracer.tool(
    name="web-search",
    description="Searches the web",
    parameters={"query": {"type": "string"}}
)
def web_search(query):
    return requests.get(f"https://api.search.com?q={query}").json()

You can also use context managers for manual span creation:

from fi_instrumentation.fi_types import FiSpanKindValues

with fi_tracer.start_as_current_span(
    "llm-call",
    fi_span_kind=FiSpanKindValues.LLM,
) as span:
    span.set_input(value="What is Python?")
    response = call_llm("What is Python?")
    span.set_output(value=response)
    span.set_attributes({
        "gen_ai.request.model": "gpt-4o",
        "gen_ai.usage.input_tokens": 10,
        "gen_ai.usage.output_tokens": 150,
    })

Wrap your provider’s tracer with FITracer to apply context attributes and privacy settings to custom spans. startActiveSpan uses OpenTelemetry’s callback pattern; await your work and end the span in finally, including when the work fails.

For a Node.js application, install the packages below and set FI_API_KEY and FI_SECRET_KEY as described in Set up tracing. This example uses @traceai/fi-core 1.1.0 or later and OpenTelemetry 2.x.

npm install "@traceai/fi-core@^1.1.0" "@opentelemetry/api@^1.9.0" "@opentelemetry/context-async-hooks@^2.0.1"
import { context, SpanStatusCode } from "@opentelemetry/api";
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
import { FITracer, ProjectType, register, setSession, setUser } from "@traceai/fi-core";

// Initialize once at application startup, before handling requests.
const contextManager = new AsyncLocalStorageContextManager().enable();
context.setGlobalContextManager(contextManager);
const provider = register({
  projectName: "my-project",
  projectType: ProjectType.OBSERVE,
});
const tracer = new FITracer({
  tracer: provider.getTracer("my-app"),
  traceConfig: { hideInputs: true, hideOutputs: true, hideErrorDetails: true },
});

async function getPolicy(topic: string): Promise<string> {
  return tracer.startActiveSpan("get-policy", async (span) => {
    try {
      span.setAttribute("gen_ai.span.kind", "TOOL");
      span.setAttribute("input.value", topic);

      const policies = new Map([["returns", "Returns are accepted within 30 days."]]);
      const policy = await Promise.resolve(policies.get(topic));
      if (!policy) throw new Error("Unknown policy topic");

      span.setAttribute("output.value", policy);
      span.setStatus({ code: SpanStatusCode.OK });
      return policy;
    } catch (error) {
      span.recordException(error instanceof Error ? error : String(error));
      span.setStatus({ code: SpanStatusCode.ERROR });
      throw error;
    } finally {
      span.end();
    }
  });
}

async function main() {
  const requestContext = setUser(
    setSession(context.active(), { sessionId: "sess-123" }),
    { userId: "user-456" },
  );
  try {
    const policy = await context.with(requestContext, () => getPolicy("returns"));
    console.log(policy);
  } finally {
    // Flush and close the exporter when this short-lived script finishes.
    await provider.shutdown();
    contextManager.disable();
  }
}

void main();

In Future AGI, open my-project and inspect the get-policy span. It has session.id and user.id, while input, output, and error text are hidden by the example’s privacy settings. Change those settings only for content you intend to collect. If the lookup fails, the span records the failure and the caller still receives the error.

setSession, setUser, setMetadata, and setTags return a context containing the values you supply. Activate that context with context.with. Spans created through FITracer.startActiveSpan copy those values into span attributes, including across awaited work when a context manager is enabled. Raw OpenTelemetry spans do not automatically copy these attributes or apply FITracer masking.

Note

If your application already has an OpenTelemetry provider and context manager, reuse them instead of initializing another pair. Wrap the existing provider’s tracer with FITracer. In a server, shut down the provider only when the application exits, not after each request.

Java offers both lambda-based and manual span creation. First initialize TraceAI using the Java example in Register a tracer provider. The trace callback takes no arguments; use Span.current() inside it to access the active span.

import ai.traceai.FITracer;
import ai.traceai.FISpanKind;
import ai.traceai.TraceAI;
import io.opentelemetry.api.trace.Span;
import java.util.List;

FITracer tracer = TraceAI.getTracer();

// Lambda-based - auto-manages span lifecycle
String result = tracer.trace("rag-pipeline", FISpanKind.CHAIN, () -> {
    Span span = Span.current();
    tracer.setInputValue(span, question);

    String docs = tracer.trace("retrieve", FISpanKind.RETRIEVER, () -> {
        Span rSpan = Span.current();
        tracer.setInputValue(rSpan, question);
        var retrieved = vectorDb.search(question);
        tracer.setOutputValue(rSpan, tracer.toJson(retrieved));
        return retrieved;
    });

    String answer = tracer.trace("generate", FISpanKind.LLM, () -> {
        Span lSpan = Span.current();
        tracer.setInputMessages(lSpan, List.of(
            tracer.message("system", "Answer using the context."),
            tracer.message("user", question)
        ));
        var resp = llm.generate(question, docs);
        tracer.setOutputMessages(lSpan, List.of(
            tracer.message("assistant", resp)
        ));
        tracer.setTokenCounts(lSpan, 50, 200, 250);
        return resp;
    });

    tracer.setOutputValue(span, answer);
    return answer;
});

Manual span creation for more control:

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;

Span span = tracer.startSpan("tool-call", FISpanKind.TOOL);
try {
    tracer.setInputValue(span, inputJson);
    String result = executeTool(inputJson);
    tracer.setOutputValue(span, result);
    span.setStatus(StatusCode.OK);
} catch (Exception e) {
    tracer.setError(span, e);
} finally {
    span.end();
}

C# provides typed convenience methods for each span kind:

var tracer = TraceAI.Register(opts =>
{
    opts.ProjectName = "my-project";
    opts.ProjectType = ProjectType.Observe;
});

// Convenience methods for each span kind
var result = tracer.Chain("rag-pipeline", span =>
{
    span.SetInput("What is quantum computing?");

    var docs = tracer.Tool("vector-search", toolSpan =>
    {
        toolSpan.SetTool("search", "Searches vector DB");
        toolSpan.SetInput("quantum computing");
        var results = vectorDb.Search("quantum computing");
        toolSpan.SetOutput(results);
        return results;
    });

    var answer = tracer.Llm("generate", llmSpan =>
    {
        llmSpan.SetAttribute(SemanticConventions.GenAiRequestModel, "gpt-4o");
        llmSpan.SetInputMessages(new List<Dictionary<string, string>>
        {
            FITracer.Message("user", "What is quantum computing?")
        });
        var resp = llm.Generate("What is quantum computing?", docs);
        llmSpan.SetOutputMessages(new List<Dictionary<string, string>>
        {
            FITracer.Message("assistant", resp)
        });
        llmSpan.SetTokenCounts(50, 200, 250);
        return resp;
    });

    span.SetOutput(answer);
    return answer;
});

// Async variants
await tracer.AgentAsync("research-agent", async span =>
{
    span.SetInput("Research topic X");
    var result = await RunResearchAsync("topic X");
    span.SetOutput(result);
});

Manual span creation:

using var span = tracer.StartSpan("custom-op", FISpanKind.Chain);
span.SetInput("input data");
span.SetOutput("output data");
// span.Dispose() ends the span automatically

FISpan Methods

All languages provide methods on the span object for setting structured data:

MethodDescriptionAvailable in
set_input(value, mime_type=) / SetInput(value, mimeType)Set span input value (text or JSON). mime_type accepts "text/plain" or "application/json"Python, C#
set_output(value, mime_type=) / SetOutput(value, mimeType)Set span output valuePython, C#
set_tool(name, description, parameters) / SetTool(...)Attach tool metadataPython, C#
set_attributes(dict) / SetAttribute(key, value)Set custom attributesAll
setInputValue(span, value)Set input on spanJava
setOutputValue(span, value)Set output on spanJava
setInputMessages(span, messages) / SetInputMessages(messages)Set chat message historyJava, C#
setOutputMessages(span, messages) / SetOutputMessages(messages)Set response messagesJava, C#
setTokenCounts(span, in, out, total) / SetTokenCounts(in, out, total)Set token usageJava, C#
setError(span, exception) / SetError(exception)Record an exceptionJava, C#

Note

In Java, these methods live on FITracer and take the span as the first argument (e.g. tracer.setInputValue(span, value)). In Python and C#, they’re called directly on the span object.

Was this page helpful?

Questions & Discussion