Configuration¶
TraceVerde is configured via environment variables or programmatically. All settings have sensible defaults - most users only need to set OTEL_SERVICE_NAME and OTEL_EXPORTER_OTLP_ENDPOINT.
A complete sample.env template is included in the repository.
Core Settings¶
| Variable | Default | Description |
|---|---|---|
OTEL_SERVICE_NAME |
genai-app |
Service name for traces and metrics |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:4318 |
OTLP endpoint URL. Leave empty for console output |
OTEL_EXPORTER_OTLP_HEADERS |
Headers in key1=val1,key2=val2 format |
|
OTEL_EXPORTER_OTLP_CERTIFICATE |
CA certificate file used to verify an HTTPS OTLP endpoint | |
OTEL_EXPORTER_OTLP_TIMEOUT |
60 |
OTLP exporter timeout in seconds |
OTEL_EXPORTER_OTLP_PROTOCOL |
http/protobuf |
Protocol: http/protobuf (default) or grpc |
OTEL_SERVICE_INSTANCE_ID |
Instance identifier (container ID, pod name) | |
OTEL_ENVIRONMENT |
dev |
Deployment environment (dev, staging, production) |
GENAI_OTEL_LOG_LEVEL |
INFO |
Logging level (DEBUG, INFO, WARNING, ERROR) |
GENAI_FAIL_ON_ERROR |
false |
Raise exceptions on instrumentation errors |
For a TLS-enabled platform deployment, set OTEL_EXPORTER_OTLP_ENDPOINT to the
HTTPS OTLP base URL and set OTEL_EXPORTER_OTLP_CERTIFICATE to the CA
certificate file that signed the endpoint. The exporter appends the signal path
(v1/traces or v1/metrics) to the base URL.
Feature Toggles¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_COST_TRACKING |
true |
Automatic cost calculation for LLM calls |
GENAI_ENABLE_GPU_METRICS |
true |
GPU utilization, memory, temperature, power metrics |
GENAI_ENABLE_MCP_INSTRUMENTATION |
true |
Database, cache, vector DB, queue instrumentation |
GENAI_ENABLE_HTTP_INSTRUMENTATION |
false |
HTTP/API instrumentation (disabled to avoid OTLP conflicts) |
GENAI_ENABLE_SESSION_PROPAGATION |
true |
Copy session.id / user.id from a genai_otel.session() block or the parent span onto every span |
GENAI_SESSION_BAGGAGE_KEYS |
(none) | Which of session.id, user.id this service sends to the services it calls, as W3C baggage |
GENAI_SESSION_BAGGAGE_ACCEPT |
(none) | Which of session.id, user.id this service accepts from inbound baggage. Only where callers are trusted |
GENAI_ENABLE_AGENT_GRAPH_ATTRIBUTES |
true |
Add agent.name and graph.node.* to agent spans for the Arize AX / Phoenix agent graph |
GENAI_ENABLE_CO2_TRACKING |
false |
CO2 emissions tracking from GPU power consumption |
GENAI_ENABLE_CONTENT_CAPTURE |
false |
Capture prompt/response content as span events |
Instrumentor Selection¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLED_INSTRUMENTORS |
all defaults | Comma-separated list of instrumentors to enable |
Default instrumentors: openai, openrouter, cometapi, anthropic, google.generativeai, boto3, azure.ai.openai, cohere, mistralai, together, groq, ollama, vllm, llamacpp, vertexai, replicate, anyscale, sambanova, sarvamai, elevenlabs, langchain, langgraph, llama_index, transformers, autogen, autogen_agentchat, google_adk, pydantic_ai, openai_agents, bedrock_agents, crewai, smolagents (3.10+), litellm (3.10+), typesafe (3.10+), claude_agent_sdk, agno, microsoft_agent_framework, and strands_agents (3.10+). These four integrations activate only when their corresponding optional extra is installed. antigravity and strands (Strands Harness) are opt-in.
Opt-in instrumentors (not enabled by default; add them to GENAI_ENABLED_INSTRUMENTORS explicitly):
| Name | Purpose |
|---|---|
litellm_latency |
Streaming latency (TTFT/TPOT) for litellm routes that bypass provider SDKs |
litellm routes OpenAI, Azure and OpenAI-compatible traffic through the OpenAI
SDK, which is already instrumented, so those calls are measured on the inner
provider span with no extra configuration. Every other provider (Anthropic,
Bedrock, Gemini, Cohere, HuggingFace, ...) is implemented with litellm's own
HTTP client, which no provider instrumentor sees. Enabling litellm_latency
wraps litellm's own entry points so those routes report TTFT/TPOT too:
The span it creates is the parent of any inner provider span. When an inner span already measured the request, this one records no tokens, cost or latency of its own -- one request is never counted twice. It is opt-in because it participates in token and cost accounting for every litellm call.
Example - enable only specific instrumentors:
Sampling¶
| Variable | Default | Description |
|---|---|---|
GENAI_SAMPLING_RATE |
1.0 |
Trace sampling rate (0.0-1.0). 1.0 = trace everything |
Use lower values in high-traffic production to reduce telemetry volume:
Cost Tracking¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_COST_TRACKING |
true |
Enable/disable cost tracking |
GENAI_CUSTOM_PRICING_JSON |
Custom model pricing (see Cost Tracking guide) |
export GENAI_CUSTOM_PRICING_JSON='{"chat":{"my-model":{"promptPrice":0.001,"completionPrice":0.002}}}'
Custom pricing accepts the sections chat, embeddings, images, audio and decision.
Prices are per 1,000 tokens. A decision section prices an on-prem decision model per input
token; decision spans are priced from that section only, never from chat:
export GENAI_CUSTOM_PRICING_JSON='{"decision":{"Cloudflare/clef-flash":{"promptPrice":0.00001,"completionPrice":0}}}'
Decision Models¶
Decision-model calls (OpenAI Decisions, SystemOne / Jev, Granite Switch's decision adapter,
genai_otel.decision_span) are OpenInference DECISION spans. See
Decision models.
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_CONTENT_CAPTURE |
false |
Also decides what a decision span records: the chosen answer, confidences, every score derived from them and the question names are recorded only when it is on |
GENAI_DECISION_LEGACY_LLM_KIND |
false |
true keeps openinference.span.kind=LLM on SystemOne (Jev) calls for consumers that still need it. Every decision attribute is still emitted. Removed in 2.0 |
GENAI_DECISION_HTTP_MATCHER |
true |
false turns off the path matcher that traces POSTs to /v1/systemone, /decisions/v1/systemone and /v1/decisions made with requests, httpx or httpx2 outside an SDK |
GENAI_DECISION_PROVIDER |
unset | Names the provider behind a host the library cannot recognise (a self-hosted vLLM server, a gateway). A recognised host always wins |
There is no separate instrumentor id: openai covers OpenAI Decisions and Granite Switch,
typesafe covers SystemOne.
GPU Metrics¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_GPU_METRICS |
true |
Enable GPU metrics collection |
GENAI_GPU_COLLECTION_INTERVAL |
5 |
Collection interval in seconds |
GENAI_POWER_COST_PER_KWH |
0.12 |
Electricity cost in USD per kWh |
Common electricity rates: US average ~$0.12/kWh, Europe ~$0.20/kWh, Industrial ~$0.07/kWh.
CO2 Emissions Tracking¶
TraceVerde can track CO2 emissions from GPU power consumption. Two modes are available:
Manual mode: Uses a fixed carbon intensity value you provide. Codecarbon mode: Uses codecarbon for automatic region-based carbon intensity lookup.
CO2 Configuration¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_CO2_TRACKING |
false |
Enable CO2 emissions tracking |
GENAI_CARBON_INTENSITY |
475.0 |
Carbon intensity in gCO2e/kWh (manual fallback) |
GENAI_CO2_USE_MANUAL |
false |
Force manual calculation even with codecarbon installed |
GENAI_CO2_COUNTRY_ISO_CODE |
3-letter ISO country code (e.g., USA, GBR, DEU, IND) |
|
GENAI_CO2_REGION |
Region/state within country (e.g., california, texas) |
|
GENAI_CO2_CLOUD_PROVIDER |
Cloud provider: aws, gcp, azure |
|
GENAI_CO2_CLOUD_REGION |
Cloud region (e.g., us-east-1, europe-west1) |
|
GENAI_CO2_OFFLINE_MODE |
true |
Run codecarbon without external API calls |
GENAI_CO2_TRACKING_MODE |
machine |
machine (all processes) or process (current only) |
GENAI_CODECARBON_LOG_LEVEL |
error |
Codecarbon logging verbosity |
Example: CO2 Tracking for US West Coast¶
export GENAI_ENABLE_CO2_TRACKING=true
export GENAI_CO2_COUNTRY_ISO_CODE=USA
export GENAI_CO2_REGION=california
Example: CO2 Tracking on AWS¶
export GENAI_ENABLE_CO2_TRACKING=true
export GENAI_CO2_CLOUD_PROVIDER=aws
export GENAI_CO2_CLOUD_REGION=us-east-1
Example: Manual Carbon Intensity¶
export GENAI_ENABLE_CO2_TRACKING=true
export GENAI_CO2_USE_MANUAL=true
export GENAI_CARBON_INTENSITY=56.0 # France (mostly nuclear)
Reference carbon intensity values (gCO2e/kWh): - France: ~56 (nuclear) - UK: ~233 - Germany: ~350 - US average: ~420 - China: ~555 - India: ~700
CO2 Metrics¶
When enabled, these metrics are recorded:
| Metric | Unit | Description |
|---|---|---|
gen_ai.co2.emissions |
kgCO2e | Cumulative CO2 emissions |
gen_ai.power.consumption |
kWh | Cumulative power consumption |
gen_ai.power.cost |
USD | Cumulative electricity cost |
Content Capture¶
| Variable | Default | Description |
|---|---|---|
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT |
unset | Cross-library content-capture switch. Takes precedence over GENAI_ENABLE_CONTENT_CAPTURE |
GENAI_ENABLE_CONTENT_CAPTURE |
false |
Capture prompt/response text in spans |
GENAI_CONTENT_MAX_LENGTH |
200 |
Max characters to capture (0 = unlimited) |
GENAI_CAPTURE_EMBEDDING_VECTORS |
false |
Capture the raw embedding vector on embeddings spans |
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is the spelling shared with
other OpenTelemetry GenAI instrumentations, so an application migrating from one
of them keeps working without relearning this switch. It accepts (case-insensitively):
| Value | Effect here |
|---|---|
NO_CONTENT |
No capture |
SPAN_ONLY |
Capture |
SPAN_AND_EVENT |
Capture |
EVENT_ONLY |
No capture |
EVENT_ONLY maps to no capture because this library has a single boolean capture
switch rather than separate span and event sinks -- writing content onto spans
when the operator asked for events only would put it somewhere they did not ask
for. An unrecognised value logs a warning and captures nothing, rather than
falling through to GENAI_ENABLE_CONTENT_CAPTURE: a typo on a privacy switch
should fail closed.
Privacy
Content capture records full prompts and responses. This may expose sensitive data. Ensure proper data handling and access controls before enabling in production.
Shutdown and Flushing¶
| Variable | Default | Description |
|---|---|---|
GENAI_FLUSH_ON_SIGTERM |
false |
Flush pending telemetry when the process receives SIGTERM |
GENAI_SIGTERM_FLUSH_TIMEOUT |
5.0 |
Seconds to wait for that flush |
The OpenTelemetry SDK registers an atexit hook, so a clean exit and an uncaught
exception both flush already. Signals are the gap: Python does not run atexit
handlers when the process is terminated by one, and docker stop and
Kubernetes pod eviction both send SIGTERM. Every rolling restart therefore
drops whatever is still queued in the batch processor - up to 5 seconds or 512
spans by default. Nothing raises, so the loss is silent and dashboards simply
read a little low.
Enable the handler in containerised deployments:
It is off by default because installing a signal handler takes over a slot the host application may want. When enabled it never replaces an existing handler - whatever was registered before is called after the flush - and if nothing was, the default disposition is restored and the signal re-raised, so the process still exits with the conventional status rather than appearing to ignore SIGTERM.
If your application already owns its shutdown path, drain telemetry directly instead and leave the handler off:
SIGKILL, out-of-memory kills and segfaults cannot be handled from inside the
process. To bound the loss window there, shorten the batch delay with the
standard OTEL_BSP_SCHEDULE_DELAY, trading export frequency for exposure.
Host and Instance Identity¶
Spans carry host, OS and instance attributes so that traffic can be attributed
to a machine and to one instance among several running on it. Process attributes
(the OS user, the executable path and the full command line) and host.ip (every
interface address) are opt-in: they describe the machine rather than the service,
and on a workstation that is a description of the person using it. All of them
are OpenTelemetry registry names, controlled by standard OTEL_* variables - see
Resource Attributes
for the full list.
| Variable | Default | Description |
|---|---|---|
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS |
host,os |
SDK detectors to run. Add process for the process attributes (host,os,process), or set otel for none |
GENAI_OTEL_RESOURCE_HOST_IP |
false |
true fills host.ip with the host's non-loopback addresses. A value in OTEL_RESOURCE_ATTRIBUTES is used either way |
OTEL_SERVICE_INSTANCE_ID |
Instance identifier. Recommended - set it to something your orchestrator already guarantees unique and stable, such as a pod name | |
OTEL_RESOURCE_ATTRIBUTES |
Any additional resource attributes, key=value comma-separated. Always wins over what the library detects |
|
GENAI_SERVICE_INSTANCE_ID_MODE |
random |
How service.instance.id is generated when not set explicitly. random or derived |
Changed in 1.35: process attributes and host.ip are opt-in
Until 1.35 the default was host,os,process plus host.ip, and only
GENAI_PROFILE=strict|bfsi|bank left out process. Every profile now
defaults to host,os. To restore the old resource, set
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS=host,os,process and
GENAI_OTEL_RESOURCE_HOST_IP=true.
Choosing how service.instance.id is generated¶
service.instance.id is what separates two instances of the same service on
the same host. If OTEL_SERVICE_INSTANCE_ID (or OTEL_RESOURCE_ATTRIBUTES)
supplies one, it is used unchanged. Otherwise:
random(default) - a version 4 UUID, stable for the life of the process. This is OpenTelemetry's primary recommendation and is the only option guaranteed to distinguish instances that run at the same time. It changes on every restart.-
derived- a version 5 UUID over the host name, the service name and the normalised startup arguments, using the namespace the specification designates for derived instance IDs. The same configured instance keeps its identity across restarts, which matters for any consumer that builds per-instance baselines: a key that changes on restart never accumulates enough history for one instance.Its limitation is the mirror image: instances that differ in no argument - several identical workers started on one host - collapse onto a single ID. Prefer
OTEL_SERVICE_INSTANCE_IDwhere the deployment can supply a real one, and reach forderivedonly where it cannot.
Telling the three apart from a span¶
A consumer reading service.instance.id needs to know whether it can be
trusted as a grouping key, and the value says so on its own - no need to watch
a service across restarts before classifying it:
| Value | Meaning | Safe as a grouping key? |
|---|---|---|
| Not a UUID | An operator assigned it | Yes - stable and unique |
| A version 5 UUID | derived mode |
Yes, but it identifies an instance configuration, not a process: identically-started workers on one host share it |
| A version 4 UUID | Nobody configured anything | No - it changes on every restart. Group on host.name + service.name instead |
The version 4 case deliberately does not distinguish this library's random
mode from the service.instance.id that opentelemetry-sdk 1.44 and later
generate on their own. There is no difference worth acting on: both mean the
instance was never configured, and both change on every restart.
Note the direction the version 4 rule can be wrong in. An operator who sets
OTEL_SERVICE_INSTANCE_ID to a version 4 UUID they generate and manage
themselves has a perfectly stable ID that reads as unconfigured. The cost is
lost per-instance granularity rather than a corrupted grouping, so it fails
safe - but prefer a non-UUID instance ID (a pod name, a worker index) anyway,
since it is also the only kind a human can read in an alert.
process.pid is not an instance identifier
It separates concurrent processes within one lifetime, but a restart changes it. Use it as evidence, never as the key an entity is tracked by.
Semantic Conventions¶
| Variable | Default | Description |
|---|---|---|
OTEL_SEMCONV_STABILITY_OPT_IN |
gen_ai/dup |
Semantic convention mode |
Options:
gen_ai/dup- Dual emission (default): emits the current names and the superseded ones (gen_ai.usage.prompt_tokens,gen_ai.usage.completion_tokens), so existing dashboards keep working during migrationgen_ai- Current conventions only:gen_ai.usage.input_tokens,gen_ai.usage.output_tokens
OpenTelemetry semantic conventions v1.27.0 renamed gen_ai.usage.prompt_tokens
to gen_ai.usage.input_tokens and gen_ai.usage.completion_tokens to
gen_ai.usage.output_tokens
(semantic-conventions#1200).
Backends that consume the current conventions - Arize AX, for example, maps them
onto llm.token_count.* - report zero tokens if only the superseded names are
present. Dual emission is therefore the default: both spellings are sent, so
such backends resolve tokens and cost while dashboards still querying the old
names keep working. Set gen_ai once nothing depends on the superseded names.
See the Arize AX guide for a worked example.
Ollama Server Metrics¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_OLLAMA_SERVER_METRICS |
true |
Poll Ollama's /api/ps for VRAM usage |
OLLAMA_BASE_URL |
http://localhost:11434 |
Ollama server URL |
GENAI_OLLAMA_METRICS_INTERVAL |
5.0 |
Polling interval in seconds |
GENAI_OLLAMA_MAX_VRAM_GB |
auto-detected | Override GPU VRAM size in GB |
Evaluation Features¶
All evaluation features are opt-in and disabled by default. See the Evaluation guide for detailed examples.
PII Detection¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_PII_DETECTION |
false |
Enable PII detection |
GENAI_PII_MODE |
detect |
Mode: detect, redact, or block |
GENAI_PII_THRESHOLD |
0.5 |
Detection confidence threshold (0.0-1.0) |
GENAI_PII_GDPR_MODE |
false |
Add GDPR-specific entity types (IBAN, EU passports) |
GENAI_PII_HIPAA_MODE |
false |
Add HIPAA-specific entity types (medical records) |
GENAI_PII_PCI_DSS_MODE |
false |
Add PCI-DSS entity types (credit cards) |
Toxicity Detection¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_TOXICITY_DETECTION |
false |
Enable toxicity detection |
GENAI_TOXICITY_THRESHOLD |
0.7 |
Score threshold (0.0-1.0) |
GENAI_TOXICITY_USE_PERSPECTIVE_API |
false |
Use Google Perspective API (cloud) instead of Detoxify (local) |
GENAI_TOXICITY_PERSPECTIVE_API_KEY |
Perspective API key (required if using Perspective) | |
GENAI_TOXICITY_BLOCK_ON_DETECTION |
false |
Block requests with toxic content |
GENAI_TOXICITY_QUANTIZE |
false |
Run the local model with 8-bit weights. Faster, but scores shift and verdicts near the threshold can change: test it on your own data |
GENAI_TOXICITY_TORCH_THREADS |
Threads torch may use for the local model. Process-wide: it applies to every torch model in the process | |
GENAI_TOXICITY_MAX_CHARS |
500 |
Characters the local toxicity model analyses: the end of the prompt. 0 follows GENAI_EVALUATION_MAX_CHARS. With no limit the whole text is scored in pieces |
Bias Detection¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_BIAS_DETECTION |
false |
Enable bias detection |
GENAI_BIAS_THRESHOLD |
0.4 |
Detection threshold (0.0-1.0) |
GENAI_BIAS_BLOCK_ON_DETECTION |
false |
Block requests with detected bias |
Prompt Injection Detection¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_PROMPT_INJECTION_DETECTION |
false |
Enable prompt injection detection |
GENAI_PROMPT_INJECTION_THRESHOLD |
0.5 |
Detection threshold (0.0-1.0) |
GENAI_PROMPT_INJECTION_BLOCK_ON_DETECTION |
false |
Block detected injection attempts |
Restricted Topics¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_RESTRICTED_TOPICS |
false |
Enable restricted topics detection |
GENAI_RESTRICTED_TOPICS_THRESHOLD |
0.5 |
Detection threshold (0.0-1.0) |
GENAI_RESTRICTED_TOPICS_BLOCK_ON_DETECTION |
false |
Block restricted topic requests |
Hallucination Detection¶
| Variable | Default | Description |
|---|---|---|
GENAI_ENABLE_HALLUCINATION_DETECTION |
false |
Enable hallucination detection |
GENAI_HALLUCINATION_THRESHOLD |
0.6 |
Detection threshold (0.0-1.0) |
Detector Runtime¶
These apply to every enabled detector. The Evaluation guide explains each.
| Variable | Default | Description |
|---|---|---|
GENAI_EVALUATION_MODE |
background |
background: detectors run on a worker thread after the call has returned. inline: the call waits for them. A block policy is always checked before the request is sent |
GENAI_EVALUATION_MAX_CHARS |
1500 |
Characters the detectors analyse: the end of a prompt and the start of a response. 0 = no limit. A span whose text was cut carries evaluation.truncated. A block policy on prompt injection, restricted topics or bias reads the whole prompt regardless |
GENAI_EVALUATION_QUEUE_SIZE |
1000 |
Spans that may wait for detectors. Past this a span is exported unevaluated, marked evaluation.skipped="overloaded" |
GENAI_EVALUATION_SHUTDOWN_TIMEOUT |
5.0 |
Seconds given to pending evaluations at exit and on uninstrument(). What is left is exported unevaluated, marked evaluation.skipped="shutdown" |
GENAI_EVALUATION_CACHE_SIZE |
256 |
Results each detector remembers, keyed by a digest of the analysed text. 0 disables |
Security Hardening (BFSI / on-prem)¶
For regulated on-prem deployments, GENAI_PROFILE=strict (aliases bfsi, bank)
locks down the posture in one switch: audit content capture stays on, but all
third-party network egress is disabled and runtime model downloads are blocked.
| Variable | Default | Description |
|---|---|---|
GENAI_PROFILE |
strict / bfsi / bank expand to the hardened posture below (empty = no profile) |
|
GENAI_ALLOW_EXTERNAL_EGRESS |
true |
When false, components must not send content/telemetry to any third party (e.g. the toxicity Perspective API is disabled) |
GENAI_AIR_GAPPED |
false |
When true, components must not fetch models/assets at runtime (Detoxify weights, spaCy models) |
Under GENAI_PROFILE=strict the profile sets GENAI_ALLOW_EXTERNAL_EGRESS=false,
GENAI_AIR_GAPPED=true, GENAI_CO2_OFFLINE_MODE=true and
GENAI_TOXICITY_USE_PERSPECTIVE_API=false, and enables content capture for audit
unless you pin GENAI_ENABLE_CONTENT_CAPTURE yourself.
The profile also lifts the limits on analysed text: GENAI_EVALUATION_MAX_CHARS and
GENAI_TOXICITY_MAX_CHARS default to 0 under it, so the PII entity model reads the
whole prompt and toxicity is scored over the whole text. Detection time then grows with
the length of the prompt. A limit you set yourself is kept.
Blocking evaluation (GENAI_PII_MODE=block, *_BLOCK_ON_DETECTION=true) now
intercepts the request before the LLM call and raises
genai_otel.exceptions.PolicyViolationError, instead of only annotating the span
after the response has already returned.
Performance and Metric Verbosity¶
Full per-request detail is always written to span attributes (audit is unaffected). These flags gate only the additional aggregated-metric instruments, which dominate per-call overhead at high throughput.
| Variable | Default | Description |
|---|---|---|
GENAI_METRICS_PROFILE |
standard |
standard = request count, latency, token counter, total cost. full = also token histograms, granular cost counters, finish counters |
GENAI_RECORD_TOKEN_HISTOGRAMS |
false |
Record per-request token distribution histograms |
GENAI_RECORD_GRANULAR_COST_METRICS |
false |
Record prompt/completion/reasoning/cache cost counters (span attributes are always set regardless) |
GENAI_RECORD_FINISH_METRICS |
false |
Record finish-reason / success / failure counters |
GENAI_ENABLE_CONCURRENCY_METRICS |
true |
Track the gen_ai.server.requests.running gauge |
OTEL_METRICS_EXEMPLAR_FILTER |
always_off |
Metric exemplar sampling; set trace_based to re-enable (adds per-measurement cost) |
See benchmarks/
for per-call overhead numbers and the measurement tooling.
Telemetry (Opt-in)¶
Anonymous usage telemetry is disabled by default.
| Variable | Default | Description |
|---|---|---|
TRACEVERDE_TELEMETRY |
false |
Enable anonymous usage reporting |
TRACEVERDE_TELEMETRY_URL |
Telemetry endpoint URL |
See genai_otel/telemetry.py for details on what is collected.
Programmatic Configuration¶
All environment variables can be set programmatically:
import genai_otel
genai_otel.instrument(
# Core
service_name="my-app",
endpoint="http://localhost:4318",
enabled_instrumentors=["openai", "anthropic", "crewai"],
sampling_rate=0.5,
fail_on_error=False,
# Features
enable_gpu_metrics=True,
enable_cost_tracking=True,
enable_mcp_instrumentation=True,
enable_content_capture=False,
# CO2 tracking
enable_co2_tracking=True,
co2_country_iso_code="USA",
co2_region="california",
# GPU
gpu_collection_interval=10,
power_cost_per_kwh=0.15,
# Evaluation
enable_pii_detection=True,
pii_mode="redact",
pii_threshold=0.5,
pii_gdpr_mode=True,
enable_toxicity_detection=True,
toxicity_threshold=0.7,
enable_bias_detection=True,
bias_threshold=0.4,
enable_prompt_injection_detection=True,
prompt_injection_threshold=0.5,
enable_restricted_topics=True,
restricted_topics_threshold=0.5,
enable_hallucination_detection=True,
hallucination_threshold=0.6,
)
Multimodal Observability¶
Capture image/audio/document content parts on spans. See the Multimodal Observability guide for full details.
| Env var | Default | Notes |
|---|---|---|
GENAI_OTEL_MEDIA_CAPTURE_MODE |
off |
off | reference_only | full |
GENAI_OTEL_MEDIA_STORE |
none |
none | filesystem | s3 | minio | seaweed | http |
GENAI_OTEL_MEDIA_STORE_ENDPOINT |
— | URL or local path; required for SeaweedFS and should point to its S3 gateway |
GENAI_OTEL_MEDIA_STORE_BUCKET |
genai-otel-media |
|
GENAI_OTEL_MEDIA_STORE_PREFIX |
traces/{date}/{trace_id}/ |
template |
GENAI_OTEL_MEDIA_STORE_ACCESS_KEY |
— | for s3/minio/seaweed |
GENAI_OTEL_MEDIA_STORE_SECRET_KEY |
— | for s3/minio/seaweed |
GENAI_OTEL_MEDIA_MAX_BYTES |
10485760 (10 MiB) |
per-blob cap |
GENAI_OTEL_MEDIA_ALLOWED_MODALITIES |
image,audio,video,document |
comma-separated |
GENAI_OTEL_MEDIA_REDACTOR |
— | dotted path to callable applied before upload |
The default off keeps text-only behaviour byte-identical with pre-1.0.0.
Session and User Tracking¶
Wrap each conversation turn in genai_otel.session(). Every span started inside the
block, auto-instrumented LLM spans included, gets session.id,
gen_ai.conversation.id and user.id. Use one id per conversation, the same for
every turn:
import genai_otel
genai_otel.instrument()
with genai_otel.session(conversation_id, user_id=user.id):
reply = graph.invoke(state)
It follows the OpenTelemetry context, so it works in async code and across
asyncio.gather. A thread pool needs the context copied
(contextvars.copy_context().run(...)), as with any OpenTelemetry context.
Spans with no block of their own inherit the session from their parent span. A
session a framework resolves (a LangGraph thread_id, a CrewAI crew id) therefore
reaches the nodes, LLM calls and tools beneath it without any code. OpenInference's
using_session() / using_user() are honoured the same way, so existing code that
uses them keeps working.
Across services¶
A session can follow a call into other services as W3C baggage, so their spans join the same session. It is off unless each side opts in, because baggage is sent with every instrumented outbound call (third-party APIs included) and inbound baggage is whatever the caller chose to send:
| Service | Setting |
|---|---|
The caller, which opens genai_otel.session() |
GENAI_SESSION_BAGGAGE_KEYS=session.id |
| An internal service it calls | GENAI_SESSION_BAGGAGE_ACCEPT=session.id |
| A service that does both | both settings |
- Add
user.idto either list to carry the user as well. - Never set
GENAI_SESSION_BAGGAGE_ACCEPTon a service that browsers or other untrusted clients call directly: anyone could choose which session its spans join. - An accepted session ranks below every session the service resolves itself (a
session()block, a LangGraphthread_id, an extractor). Values over 256 characters or with control characters are dropped. - Ids a service accepted are not passed on to the services and APIs it calls unless it also sends them. Other baggage is propagated untouched.
- The receiving service's inbound requests must be instrumented, for example with
opentelemetry-instrumentation-fastapi, so the incoming headers are read. - With neither setting,
instrument()installs the TraceContext propagator only, as before. WhenOTEL_PROPAGATORSis set it is left as configured; the stockbaggagepropagator it may name does not apply these limits. - Do not call
set_global_textmap()afterinstrument(). It replaces the session propagator: with stock W3C baggage, inbound ids are forwarded unfiltered; with trace context alone, no session crosses. A warning is logged at the first span when this happens. To compose your own, includegenai_otel.sessions.SessionBaggagePropagatorinstead ofW3CBaggagePropagator.
For programmatic session/user tracking per call, provide extractor callables:
genai_otel.instrument(
session_id_extractor=lambda instance, args, kwargs: kwargs.get("metadata", {}).get("session_id"),
user_id_extractor=lambda instance, args, kwargs: kwargs.get("metadata", {}).get("user_id"),
)
CrewAI, LangGraph, and LangChain have built-in automatic session ID propagation. See the Multi-Agent Frameworks guide for details.