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/json"export OTEL_EXPORTER_OTLP_TRACES_HEADERS="authorization=Bearer ${TALLY_KEY}"Three things trip people up:
- Use the
_TRACES_settings. The shorterOTEL_EXPORTER_OTLP_ENDPOINTadds/v1/tracesto the URL, which is the wrong address. - The format must be JSON. ai-tally does not accept the binary protobuf format or gRPC. The Python OpenTelemetry exporter only sends protobuf, so from Python use the Python SDK instead.
- Collector users: use the
otlphttpexporter withencoding: jsonandcompression: none.
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 tally.hash_account your-customer-id. |
gen_ai.session_id |
sess_42 |
Groups calls from one user session. |
- The provider attribute. ai-tally reads
gen_ai.system, not the newergen_ai.provider.name. Setgen_ai.system. - Missing values. If a price value is missing, the call is still saved, but its cost shows as a blank, never $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. |
What ai-tally never stores
Section titled “What ai-tally never stores”- Text. Attributes that hold text are dropped before anything is saved: any whose name ends in
prompt,completion,messages,content,textorbody. - Personal details. A span containing an email address, or attributes named like
email,user_idorphone, is refused. Hash ids before you send them.
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.