OpenTelemetry
OpenTelemetry is a common standard for tracing, which means recording what your app does. A trace is made of spans, and a span is one traced unit of work, like one AI call. If your app already sends traces, point them at ai-tally. It reads the AI calls in them and works out what each one cost.
Settings
Section titled “Settings”Set these where your app sends its traces:
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://ingest.ai-tally.com/v1/otlp/traces"export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL="http/protobuf"export OTEL_EXPORTER_OTLP_TRACES_HEADERS="authorization=Bearer%20${TALLY_KEY}"Three things to get right:
- Use the
_TRACES_settings. The shorterOTEL_EXPORTER_OTLP_ENDPOINTadds/v1/tracesto the URL, so set the full traces address withOTEL_EXPORTER_OTLP_TRACES_ENDPOINT. - Send OTLP over HTTP. ai-tally reads both encodings,
http/protobufandhttp/json, plain or gzip-compressed. The standard HTTP exporters for Python and Go, and the Collector’sotlphttpexporter, sendhttp/protobufby default. Use HTTP rather than gRPC. - Collector users: use the
otlphttpexporter with its default settings.
The API key needs write scope.
What ai-tally reads
Section titled “What ai-tally reads”These attributes, the named values on a span, tell ai-tally what a call was:
| Attribute | Example | Why |
|---|---|---|
gen_ai.system |
openai |
The AI provider. Needed for a price. |
gen_ai.request.model or gen_ai.response.model |
gpt-4o-mini |
The model. Needed for a price. |
gen_ai.operation.name |
chat |
The kind of call (see below). |
gen_ai.usage.input_tokens |
12 |
Tokens sent in. Needed for a price. |
gen_ai.usage.output_tokens |
5 |
Tokens sent back. Needed for a price, except for embeddings. |
gen_ai.usage.cached_input_tokens |
8 |
Input tokens the provider served from its cache, which cost less. |
gen_ai.feature_tag |
support-chat |
Which feature made the call. |
gen_ai.account_id_hash |
64-character code | Which customer, as a hashed customer id that cannot be traced back. Make it with python -m aitally.hash_account your-customer-id. |
gen_ai.session_id |
sess_42 |
Groups calls from one user session. |
- The provider attribute. ai-tally reads the provider from
gen_ai.system. If your library sets the newergen_ai.provider.name, setgen_ai.systemas well. - Missing values. If a price value is missing, the call is still saved, with a blank cost instead of $0.
Kinds of call
Section titled “Kinds of call”gen_ai.operation.name decides where a call is counted in the dashboard:
| Value | Counted as |
|---|---|
chat |
LLM |
embeddings |
Embeddings |
tool |
Tool calls. Send the cost in gen_ai.tool.cost_micro_usd, in whole micro-dollars. |
vector |
Vector DB. Send the cost in gen_ai.tool.cost_micro_usd. |
Text and personal details
Section titled “Text and personal details”- Text stays out. Attributes that hold text are dropped before anything is saved: any whose name ends in
prompt,completion,messages,content,textorbody. - Hash personal details before you send them. ai-tally accepts hashed ids, and refuses a span holding an email address or an attribute named like
email,user_idorphone.
Limits
Section titled “Limits”- Span size. Each span can be up to 64 KiB.
- Sending too fast. A
429reply means slow down. Wait the number of seconds in itsRetry-Afterheader, then send again.
- Nothing showing up? See Check it works.