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:
| Kind | Use for |
|---|---|
LLM | Language model inference calls |
CHAIN | Sequential pipeline steps |
AGENT | Autonomous agent actions |
TOOL | Tool/function calls |
EMBEDDING | Vector generation |
RETRIEVER | Document retrieval (RAG) |
RERANKER | Re-ranking operations |
GUARDRAIL | Safety/validation checks |
EVALUATOR | Quality scoring |
UNKNOWN | Unspecified or unexpected span type |
WORKFLOW | Custom pipeline steps (Java only) |
CONVERSATION | Voice/conversational AI (Java/C#) |
VECTOR_DB | Vector 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:
| Method | Description | Available 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 value | Python, C# |
set_tool(name, description, parameters) / SetTool(...) | Attach tool metadata | Python, C# |
set_attributes(dict) / SetAttribute(key, value) | Set custom attributes | All |
setInputValue(span, value) | Set input on span | Java |
setOutputValue(span, value) | Set output on span | Java |
setInputMessages(span, messages) / SetInputMessages(messages) | Set chat message history | Java, C# |
setOutputMessages(span, messages) / SetOutputMessages(messages) | Set response messages | Java, C# |
setTokenCounts(span, in, out, total) / SetTokenCounts(in, out, total) | Set token usage | Java, C# |
setError(span, exception) / SetError(exception) | Record an exception | Java, 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.
Questions & Discussion