Enriching Spans with Attributes, Metadata, and Tags
Capture additional context beyond what standard frameworks provide by enriching your traces with custom attributes, metadata, tags, session IDs, user IDs, and prompt templates.
What it is
Enriching Spans lets you attach custom key/value pairs and structured context to your OpenTelemetry spans so they carry the full picture of what’s happening in your application. You can add raw attributes with set_attribute(), use traceAI Semantic Convention constants for structured LLM data, or use context managers (using_metadata, using_tags, using_session, using_user, using_prompt_template) to propagate attributes automatically to all child spans — without modifying every instrumented function.
Use cases
- Custom attributes — Add business-specific key/value pairs to spans for filtering and debugging in the Future AGI dashboard.
- Semantic conventions — Use traceAI constants like
OUTPUT_VALUEandLLM_OUTPUT_MESSAGESto capture LLM outputs in a structured, queryable schema. - Metadata enrichment — Attach experiment metadata, feature flags, or A/B test identifiers to all spans in a code block.
- Session and user tracking — Associate spans with a session ID and user ID for session replay and per-user analytics.
- Prompt template tracking — Record which prompt template, version, and variables were used in each LLM call.
How to
Add attributes to a span
Attributes are key/value pairs attached directly to the active span. Prefix custom attributes with your company name to avoid conflicts with semantic conventions.
from opentelemetry import trace
current_span = trace.get_current_span()
current_span.set_attribute("operation.value", 1)
current_span.set_attribute("operation.name", "Saying hello!")
current_span.set_attribute("operation.other-stuff", [1, 2])import { trace, context } from "@opentelemetry/api";
const currentSpan = trace.getSpan(context.active());
if (currentSpan) {
currentSpan.setAttribute("mycompany.operation.value", 1);
currentSpan.setAttribute("mycompany.operation.name", "Saying hello!");
currentSpan.setAttribute("mycompany.operation.other-stuff", [1, 2]);
} Use Semantic Convention Attributes
traceAI Semantic Conventions provide structured attribute names for common LLM data. Install the instrumentation package first.
pip install fi-instrumentation-otelnpm install @traceai/fi-core @opentelemetry/api Then set semantic attributes on the current span:
from opentelemetry import trace # Assuming span is current_span or obtained otherwise
from fi_instrumentation.fi_types import SpanAttributes, MessageAttributes # Assuming these constants and 'response' are defined
span = trace.get_current_span() # Example: get current span
if span.is_recording(): # Check if span is recording before setting attributes
span.set_attribute(SpanAttributes.OUTPUT_VALUE, response)
# This shows up under `output_messages` tab on the span page
span.set_attribute(
f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_ROLE}",
"user",
)
span.set_attribute(
f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_CONTENT}",
response,
)import { trace, context } from "@opentelemetry/api";
// Assume 'response' variable is defined, e.g.:
// const response: string = "Some LLM response from Typescript";
// String keys below should match traceAI's expected semantic conventions for Typescript.
const span = trace.getSpan(context.active());
if (span) {
span.setAttribute("output.value", response);
span.setAttribute("llm_output_messages.0.message_role", "user");
span.setAttribute("llm_output_messages.0.message_content", response);
} Use context helpers
Choose the helper that matches what you want to attach, then pick your instrumentation style.
Enrich the current OpenTelemetry context with metadata. All spans created within the block will carry the metadata as a JSON-serialized attribute.
from fi_instrumentation import using_metadata
# Assuming value_1, value_2 are defined
# value_1 = "some data"; value_2 = 123
metadata = {
"key-1": value_1,
"key-2": value_2,
}
with using_metadata(metadata):
# Calls within this block will generate spans with the attributes:
# "metadata" = "{"key-1": value_1, "key-2": value_2, ... }" # JSON serialized
pass # Your code hereimport { context, propagation } from "@opentelemetry/api";
// Assuming value_1, value_2 are defined
// const value_1 = "some_data"; const value_2 = 42;
const metadata = {
"key-1": value_1,
"key-2": value_2,
};
const previousContext = context.active();
const newBaggage = propagation.createBaggage({
"metadata": { value: JSON.stringify(metadata) }
});
const newContextWithMetadata = propagation.setBaggage(previousContext, newBaggage);
context.with(newContextWithMetadata, () => {
// Your code here. Spans created by traceAI auto-instrumentation inside this block
// should pick up the 'metadata' attribute from baggage.
// e.g., myInstrumentedFunction();
}); from fi_instrumentation import using_metadata
# Assuming metadata is defined as above
@using_metadata(metadata)
def call_fn(*args, **kwargs):
# Calls within this function will generate spans with the attributes:
# "metadata" = "{"key-1": value_1, "key-2": value_2, ... }" # JSON serialized
pass # Your function code here Enhance spans with categorical tags. Tags must be provided as a list of strings.
from fi_instrumentation import using_tags
# Assuming tags list is defined
# tags = ["tag_1", "tag_2"]
with using_tags(tags):
# Calls within this block will generate spans with the attributes:
# "tag.tags" = "["tag_1","tag_2",...]"
pass # Your code hereimport { context, propagation } from "@opentelemetry/api";
// Assuming tags list is defined, e.g.:
// const tags = ["tag_A", "tag_B"];
const previousContext = context.active();
const newBaggage = propagation.createBaggage({
"tag.tags": { value: JSON.stringify(tags) } // Stored as JSON string
});
const newContextWithTags = propagation.setBaggage(previousContext, newBaggage);
context.with(newContextWithTags, () => {
// Your code here. Spans created by traceAI auto-instrumentation inside this block
// should pick up the 'tag.tags' attribute from baggage.
// e.g., myInstrumentedFunction();
}); from fi_instrumentation import using_tags
# Assuming tags is defined as above
@using_tags(tags)
def call_fn(*args, **kwargs):
# Calls within this function will generate spans with the attributes:
# "tag.tags" = "["tag_1","tag_2",...]"
pass # Your function code here Set a session identifier for all spans within the context to group related operations under a common session.
from fi_instrumentation import using_session
# Assuming session_id is defined
# session_id = "session_123"
with using_session(session_id):
# Calls within this block will generate spans with the attributes:
# "session.id" = "session_123"
pass # Your code hereimport { context, propagation } from "@opentelemetry/api";
// Assuming session_id is defined, e.g.:
// const session_id = "session_123";
const previousContext = context.active();
const newBaggage = propagation.createBaggage({
"session.id": { value: session_id }
});
const newContextWithSession = propagation.setBaggage(previousContext, newBaggage);
context.with(newContextWithSession, () => {
// Your code here. Spans created by traceAI auto-instrumentation inside this block
// should pick up the 'session.id' attribute from baggage.
// e.g., myInstrumentedFunction();
}); from fi_instrumentation import using_session
# Assuming session_id is defined as above
@using_session(session_id)
def call_fn(*args, **kwargs):
# Calls within this function will generate spans with the attributes:
# "session.id" = "session_123"
pass # Your function code here Set a user identifier for all spans within the context to track operations performed by specific users.
from fi_instrumentation import using_user
# Assuming user_id is defined
# user_id = "user_456"
with using_user(user_id):
# Calls within this block will generate spans with the attributes:
# "user.id" = "user_456"
pass # Your code hereimport { context, propagation } from "@opentelemetry/api";
// Assuming user_id is defined, e.g.:
// const user_id = "user_456";
const previousContext = context.active();
const newBaggage = propagation.createBaggage({
"user.id": { value: user_id }
});
const newContextWithUser = propagation.setBaggage(previousContext, newBaggage);
context.with(newContextWithUser, () => {
// Your code here. Spans created by traceAI auto-instrumentation inside this block
// should pick up the 'user.id' attribute from baggage.
// e.g., myInstrumentedFunction();
}); from fi_instrumentation import using_user
# Assuming user_id is defined as above
@using_user(user_id)
def call_fn(*args, **kwargs):
# Calls within this function will generate spans with the attributes:
# "user.id" = "user_456"
pass # Your function code here Enrich spans with prompt template information to track how prompts are constructed and which variables are used.
from fi_instrumentation import using_prompt_template
# Assuming template, version, and variables are defined
# template = "Hello {name}, your age is {age}"
# version = "v1.0"
# variables = {"name": "Alice", "age": 30}
with using_prompt_template(
template=template,
version=version,
variables=variables
):
# Calls within this block will generate spans with the attributes:
# "llm.prompt_template.template" = "Hello {name}, your age is {age}"
# "llm.prompt_template.version" = "v1.0"
# "llm.prompt_template.variables" = '{"name": "Alice", "age": 30}'
pass # Your code hereimport { context, propagation } from "@opentelemetry/api";
// Assuming template, version, and variables are defined, e.g.:
// const template = "Hello {name}, your age is {age}";
// const version = "v1.0";
// const variables = {"name": "Alice", "age": 30};
const previousContext = context.active();
const newBaggage = propagation.createBaggage({
"llm.prompt_template.template": { value: template },
"llm.prompt_template.version": { value: version },
"llm.prompt_template.variables": { value: JSON.stringify(variables) }
});
const newContextWithPromptTemplate = propagation.setBaggage(previousContext, newBaggage);
context.with(newContextWithPromptTemplate, () => {
// Your code here. Spans created by traceAI auto-instrumentation inside this block
// should pick up the prompt template attributes from baggage.
// e.g., myInstrumentedFunction();
}); from fi_instrumentation import using_prompt_template
# Assuming template, version, and variables are defined as above
@using_prompt_template(
template=template,
version=version,
variables=variables
)
def call_fn(*args, **kwargs):
# Calls within this function will generate spans with the attributes:
# "llm.prompt_template.template" = "Hello {name}, your age is {age}"
# "llm.prompt_template.version" = "v1.0"
# "llm.prompt_template.variables" = '{"name": "Alice", "age": 30}'
pass # Your function code here Combine multiple context managers
Use multiple context managers together to set various attributes simultaneously on all spans within a block.
from fi_instrumentation import using_metadata, using_tags, using_session, using_user
metadata = {"experiment": "A/B test", "version": "2.1"}
tags = ["production", "critical"]
session_id = "session_789"
user_id = "user_101"
with using_metadata(metadata), \
using_tags(tags), \
using_session(session_id), \
using_user(user_id):
# All spans created within this block will have:
# - metadata attributes
# - tag attributes
# - session.id attribute
# - user.id attribute
pass # Your code hereimport { context, propagation } from "@opentelemetry/api";
const metadata = {"experiment": "A/B test", "version": "2.1"};
const tags = ["production", "critical"];
const session_id = "session_789";
const user_id = "user_101";
const previousContext = context.active();
const newBaggage = propagation.createBaggage({
"metadata": { value: JSON.stringify(metadata) },
"tag.tags": { value: JSON.stringify(tags) },
"session.id": { value: session_id },
"user.id": { value: user_id }
});
const newContextWithAllAttributes = propagation.setBaggage(previousContext, newBaggage);
context.with(newContextWithAllAttributes, () => {
// All spans created within this block will have:
// - metadata attributes
// - tag attributes
// - session.id attribute
// - user.id attribute
// e.g., myInstrumentedFunction();
}); Key concepts
set_attribute()— Attaches a key/value pair directly to the active span. Supports strings, numbers, and booleans. Prefix custom attributes with your company name to avoid naming conflicts.- Semantic Conventions — Structured attribute names defined by traceAI for common LLM data (messages, prompt templates, token counts). Use
SpanAttributesandMessageAttributesconstants fromfi_instrumentation.fi_types. - Context attributes (Baggage) — Set at the OpenTelemetry context level so they propagate automatically to all child spans within the block, without modifying instrumented functions.
using_metadata— Attaches a JSON-serialized metadata dictionary to all spans in the context as themetadataattribute.using_tags— Attaches a JSON-serialized list of tag strings to all spans astag.tags.using_session— Setssession.idon all spans in the context for session grouping.using_user— Setsuser.idon all spans in the context for per-user tracking.using_prompt_template— Setsllm.prompt_template.template,llm.prompt_template.version, andllm.prompt_template.variableson all spans in the context.
What you can do next
Set Up Tracing
Register a tracer provider and add instrumentation.
Instrument with traceAI Helpers
Use FITracer decorators and context managers for typed spans.
Set Session & User ID
Group traces into sessions and link them to end users.
Mask Span Attributes
Redact sensitive data with TraceConfig before export.