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,
    })

TypeScript uses OpenTelemetry’s standard startActiveSpan pattern:

import { trace } from "@opentelemetry/api";

const tracer = trace.getTracer("my-app");

// Manual span creation
tracer.startActiveSpan("rag-pipeline", (span) => {
  span.setAttribute("gen_ai.span.kind", "CHAIN");
  span.setAttribute("input.value", question);

  const docs = retrieve(question);
  const result = generate(question, docs);

  span.setAttribute("output.value", result);
  span.end();
  return result;
});

Context management functions let you set session, user, and metadata:

import {
  setSession, setUser, setMetadata, setTags,
  getAttributesFromContext
} from "@traceai/fi-core";
import { context } from "@opentelemetry/api";

const ctx = setSession(context.active(), { sessionId: "sess-123" });
const ctx2 = setUser(ctx, { userId: "user-456" });

context.with(ctx2, () => {
  // All spans created here inherit session and user
  tracer.startActiveSpan("operation", (span) => {
    // span automatically gets session.id and user.id
    span.end();
  });
});

Java offers both lambda-based and manual span creation:

import ai.traceai.FITracer;
import ai.traceai.FISpanKind;

FITracer tracer = TraceAI.getTracer();

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

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

    String answer = tracer.trace("generate", FISpanKind.LLM, (lSpan) -> {
        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.context.Context;

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