OpenTelemetry (OTel)
Send traces from any OTel-instrumented AI framework directly to Voxmith
Voxmith runs a hosted OTLP collector at otel.voxmith.com. Any framework that emits OpenTelemetry spans can ship them straight to Voxmith: no SDK required.
Your AI App → otel.voxmith.com → Voxmith DashboardQuick Setup
Set two environment variables before starting your app:
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.voxmith.com
OTEL_EXPORTER_OTLP_HEADERS="X-Voxmith-Org-ID=<your-org-id>"Get your org ID from app.voxmith.com.
For SDKs that take an explicit URL, use https://otel.voxmith.com/v1/traces.
Framework Guides
Custom Tool Spans
If your stack doesn't have a framework integration, wrap each agent / tool call in an OTel span. Name the span with a tool. prefix so Voxmith classifies it as a tool call.
1. Install
Already have an OTel SDK and the OTLP exporter installed? Skip.
No OTel yet?
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-httpnpm install @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-proto @opentelemetry/api2. Wire the OTLP exporter pointing at Voxmith
Already have an OTel TracerProvider? Append Voxmith as an additional span processor:
from opentelemetry import trace
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(
endpoint="https://otel.voxmith.com/v1/traces",
headers={"X-Voxmith-Org-ID": os.environ["VOXMITH_ORG_ID"]},
)
)
)import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
provider.addSpanProcessor(
new BatchSpanProcessor(
new OTLPTraceExporter({
url: 'https://otel.voxmith.com/v1/traces',
headers: { 'X-Voxmith-Org-ID': process.env.VOXMITH_ORG_ID! },
}),
),
);No OTel yet? Full setup:
import os
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
provider = TracerProvider(resource=Resource.create({"service.name": "my-app"}))
provider.add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(
endpoint="https://otel.voxmith.com/v1/traces",
headers={"X-Voxmith-Org-ID": os.environ["VOXMITH_ORG_ID"]},
)
)
)
trace.set_tracer_provider(provider)import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'https://otel.voxmith.com/v1/traces',
headers: { 'X-Voxmith-Org-ID': process.env.VOXMITH_ORG_ID! },
}),
}).start();3. Wrap each tool call in a span with userId / sessionId
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("tool.search_web") as span:
span.set_attribute("voxmith.user_id", "user-42")
span.set_attribute("voxmith.session_id", "conv-abc123")
span.set_attribute("input", query)
try:
result = search_web(query)
span.set_attribute("output", str(result))
except Exception as exc:
span.record_exception(exc)
raiseimport { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('my-app');
const span = tracer.startSpan('tool.search_web');
span.setAttribute('voxmith.user_id', 'user-42');
span.setAttribute('voxmith.session_id', 'conv-abc123');
span.setAttribute('input', query);
try {
const result = await searchWeb(query);
span.setAttribute('output', JSON.stringify(result));
} catch (err) {
span.setStatus({ code: SpanStatusCode.ERROR });
throw err;
} finally {
span.end();
}Additional properties
Voxmith recognizes these OTel fields and preserves non-reserved custom span attributes as event metadata:
| Type | Fields |
|---|---|
| Parent span | parentSpanId on the child span |
| GenAI convention attributes | gen_ai.request.model, gen_ai.response.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens |
| Custom metadata | customer.tier, feature.name, or another non-reserved OTel attribute |
Note: If you don't set
voxmith.session_idor another supported session attribute, Voxmith uses the trace ID as the session ID. Each trace therefore appears as a separate session. Reuse a stable session ID to group multiple traces into one conversation.
Send a trace with curl
This sends one completion span with its input/output and GenAI attributes, plus
a child tool.get_weather span with its own input/output:
curl https://otel.voxmith.com/v1/traces \
-X POST \
-H "X-Voxmith-Org-ID: $VOXMITH_ORG_ID" \
-H "Content-Type: application/json" \
--data-raw '{
"resourceSpans": [{
"resource": {
"attributes": [{
"key": "service.name",
"value": { "stringValue": "support-agent" }
}]
},
"scopeSpans": [{
"scope": { "name": "custom-otel-example" },
"spans": [{
"traceId": "0102030405060708090a0b0c0d0e0f10",
"spanId": "0102030405060708",
"name": "chat.completion",
"startTimeUnixNano": "1700000000000000000",
"endTimeUnixNano": "1700000002000000000",
"attributes": [{
"key": "voxmith.user_id",
"value": { "stringValue": "user-42" }
}, {
"key": "voxmith.session_id",
"value": { "stringValue": "conversation-abc123" }
}, {
"key": "input",
"value": { "stringValue": "What is the weather in San Francisco?" }
}, {
"key": "output",
"value": { "stringValue": "It is 72°F and sunny." }
}, {
"key": "gen_ai.request.model",
"value": { "stringValue": "gpt-5" }
}, {
"key": "gen_ai.response.model",
"value": { "stringValue": "gpt-5-2025-08-07" }
}, {
"key": "gen_ai.usage.input_tokens",
"value": { "intValue": "120" }
}, {
"key": "gen_ai.usage.output_tokens",
"value": { "intValue": "48" }
}, {
"key": "customer.tier",
"value": { "stringValue": "enterprise" }
}]
}, {
"traceId": "0102030405060708090a0b0c0d0e0f10",
"spanId": "1112131415161718",
"parentSpanId": "0102030405060708",
"name": "tool.get_weather",
"startTimeUnixNano": "1700000000200000000",
"endTimeUnixNano": "1700000001800000000",
"attributes": [{
"key": "voxmith.session_id",
"value": { "stringValue": "conversation-abc123" }
}, {
"key": "input",
"value": { "stringValue": "San Francisco" }
}, {
"key": "output",
"value": { "stringValue": "72°F and sunny" }
}, {
"key": "feature.name",
"value": { "stringValue": "weather" }
}]
}]
}]
}]
}'A successful export returns HTTP 200.
What appears in Voxmith
- Events: every received span/event.
- Conversations: spans grouped by
voxmith.session_id,session.id, or framework-specific session metadata. - Tool calls: spans named with
tool.*or framework tool-call attributes. - Errors: spans marked failed or exceptions recorded on spans.
Verify
- Start your app with the OTel exporter configured.
- Run one agent turn or tool call.
- Open app.voxmith.com.
- Check Events first, then Conversations or Tool calls.
Troubleshooting
- No data: confirm the endpoint is
https://otel.voxmith.com/v1/traceswhen your SDK expects a trace URL. - Wrong org: confirm
X-Voxmith-Org-IDis attached to the OTLP exporter. - No input/output text: your framework may not export message content by default, or you may have disabled it.
Next steps
- Events: verify the spans Voxmith received.
- Tool calls: inspect tool reliability and latency.
- Intents: organize production conversations by what users wanted.