Skip to content

Changelog

View as Markdown

Changes that affect how you connect or what you see, newest first.

The SDK is on PyPI. Install it with pip install ai-tally and import it as aitally. Upgrade with pip install -U ai-tally, a lockfile bump, or Dependabot or Renovate. See Python SDK.

tally run, a zero-code way to connect (preview). Put tally run -- in front of the command that starts your Python app, and its AI calls are recorded with no ai-tally code in your app and no proxy. It names each feature after your FastAPI, Starlette, Flask or Django route, records Celery tasks and RQ jobs, and reads your customer id from a header, a JWT claim or request state. As a preview, its options and output may change.

Settings live in tally.toml, your pyproject.toml or TALLY_* variables, and TALLY_DISABLED=1 turns it off with the same command. Each process writes a startup line to stderr saying what it records. See Run your app with tally run.

tally doctor shows what leaves your app. tally doctor -- <your start command> runs your app under tally run and prints every field its first calls would send. It sends to ai-tally only when you add --send.

Gemini and Bedrock are recorded automatically. Alongside openai and anthropic, the SDK now records google-genai (models.generate_content and its stream) and Bedrock converse and converse_stream through boto3. If you added record_llm_call after those calls yourself, you can delete that line.

Direct calls to OpenAI and Anthropic are recorded. A call your app sends to the OpenAI or Anthropic API with httpx itself is recorded once, from the model and token counts in the response. Calls through the official clients are still recorded by them, once. Set TALLY_HTTPX_GAP=0 to turn this off.

Choose when each library is recorded. aitally.init(instrument={"openai"}) records only the libraries you name. With aitally.init(instrument=False), aitally.patch("openai") starts recording one library when you choose.

Supported versions are listed. Each release is tested on Python 3.10 to 3.13. This release supports openai 2.x and 3.x, anthropic 1.x and 2.x, google-genai 2.x and 3.x, boto3 1.x from 1.34.116, fastapi 0.x from 0.115.0, starlette 0.x from 0.37.2 and 1.x, flask 2.x and 3.x, django 5.x and 6.x, celery 5.x, and rq 2.x and 3.x. See Supported versions.

Fixes, with no change to your code. Upgrade and they apply:

  • Model calls run without waiting on the hashing key. The key that hashes your customer ids is refreshed in the background, so an async app’s event loop keeps moving.
  • Forked workers keep recording. Workers forked after aitally.init(), such as gunicorn --preload, Celery prefork or multiprocessing with fork, each send their own calls, and each call is sent once.
  • gevent workers start cleanly under gunicorn -k gevent --preload.
  • next() and anext() work on recorded streams, along with every other way of reading a stream.
  • import aitally is faster. It loads only what recording needs.

For organizations using local replay capture: turning capture off now stops new samples and keeps the ones already saved on your server. Your retention setting deletes them as before.

OpenTelemetry accepts protobuf. ai-tally reads OTLP over HTTP in both encodings, http/protobuf and http/json, plain or gzip-compressed. The standard Python, Go and Collector exporters now work with their default settings. See OpenTelemetry.

The Python SDK and OpenTelemetry send to https://ingest.ai-tally.com. Both use your API key. The SDK sends there by default, so tally.init() needs no endpoint. For OpenTelemetry, set OTEL_EXPORTER_OTLP_TRACES_ENDPOINT to https://ingest.ai-tally.com/v1/otlp/traces with http/json. The SDK is installed from GitHub; pip install tally installs an unrelated package. See the Quickstart.

Dated model names are priced. A model name that ends in a release date, like gpt-4o-2024-08-06, is now priced at the rate for its base model. Before, those calls showed a blank cost. See Prices and blanks.

The proxy records compressed answers. Some AI providers send compressed answers. The proxy now reads token counts from them too. Before, those calls were recorded with no model and no tokens, so their cost was blank. Your app still receives the answer uncompressed. See Proxy.