Skip to main content

keld

Drop-in OTLP telemetry for applications that call the openai or anthropic Python clients directly, boto3's Bedrock runtime client, or any OpenAI-compatible endpoint (OpenRouter, vLLM, Together, Groq, Ollama, ...) via a custom base_url.

Call keld.init() once at startup and every inference call your app already makes is reported to a Keld Atlas backend as usage telemetry — zero call-site changes. openai is observed via the official OpenTelemetry instrumentation (class-level, covering every client instance); anthropic and bedrock clients are patched in place. Requests still go straight to the real provider, untouched. Keld is never in the request path: no added latency of consequence, and an Atlas outage can never break your app.

Install

pip install "keld[openai]"

The [openai] extra installs the OpenTelemetry instrumentation that openai reporting requires. Without it, openai calls are not reported (the SDK logs one warning and your app runs normally). Apps that only use anthropic/bedrock can install the bare package:

pip install keld

Python 3.10 or newer.

The only runtime dependency of the bare package is httpx. Installing this SDK never pulls in openai, anthropic, or boto3: it patches whichever of them your app already has.

To install an unreleased commit straight from the repo:

pip install "keld[openai] @ git+https://github.com/ncx-ai/atlas-telemetry-python@main"

Usage

import keld

keld.init(
    ingest_token="kagt_...",              # or ATLAS_INGEST_TOKEN env var
    endpoint="https://atlas.example.com", # or ATLAS_ENDPOINT env var
    environment="production",             # optional
    repo="acme/checkout-service",         # optional
)

Call this before (or after — see "Clients constructed before init()" below) you construct your openai.OpenAI() / anthropic.Anthropic() / boto3.client("bedrock- runtime") client. From then on, every chat.completions.create, messages.create, invoke_model, and converse call — streaming or not — is reported.

Clients constructed before init()

If an anthropic or bedrock client already exists by the time init() runs (common in scripts and notebooks, or when a client is built inside a third-party library you don't control), pass it explicitly:

keld.init(
    ingest_token="kagt_...",
    endpoint="https://atlas.example.com",
    clients=[my_anthropic_client, my_bedrock_client],
)

openai clients never need this: the OpenTelemetry instrumentation wraps the openai resource classes, so clients constructed before or after init() are both covered (passing one in clients is harmless and does nothing).

Custom / self-hosted OpenAI-compatible endpoints

OpenRouter, vLLM, Together, Groq, Ollama and similar gateways are attributed automatically from the endpoint's host (via the span's server.address; Ollama is also recognized by its registered port 11434). An unrecognized host is reported as hosted_vllm with a one-time warning — a confidently-wrong provider label would price at $0 while looking like real data. For such endpoints pass an explicit override:

keld.init(..., provider="ollama")

Configuration

All of the following can be set as init() keyword arguments or as environment variables (the kwarg wins if both are set):

Env var init() kwarg Required What it is
ATLAS_INGEST_TOKEN ingest_token Yes A Keld Atlas agent key (kagt_...), obtained from the Atlas Integrations page. This is not an inference API key — see below.
ATLAS_ENDPOINT endpoint Yes Your Keld Atlas deployment's base URL, e.g. https://atlas.example.com. The SDK appends /v1/logs itself.
ATLAS_ENVIRONMENT environment No Freeform deploy environment label (production, staging, ...); feeds Atlas's CapEx/OpEx classification.
ATLAS_REPO repo No Freeform string identifying the calling application/repo, e.g. acme/checkout-service.
— gzip No Default True. Gzips the OTLP export body and sends Content-Encoding: gzip; pass gzip=False to send plain JSON.

There is no env var for clients= or provider= — those only make sense as explicit init() arguments.

ATLAS_INGEST_TOKEN is not your provider API key

This is the single most common point of confusion, so to be unmissable:

  • ATLAS_INGEST_TOKEN (kagt_...) is a Keld Atlas credential. It authenticates telemetry reports to Atlas. Get it from your Atlas admin's Integrations page.
  • Your OpenAI API key, Anthropic API key, and AWS credentials (for Bedrock) are configured completely separately, the normal way each provider's own client/SDK already expects (OPENAI_API_KEY, ANTHROPIC_API_KEY, AWS credential chain, etc).
  • This SDK never sees, reads, or transmits your inference provider credentials. It only reads the response of calls your own client makes with your own credentials — token counts, model id, request id, timing — never request/response content, and never your keys.

What gets sent (and what doesn't)

Every call is reported as one OTLP log record: provider, model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, duration_ms, request_id, and stream (anthropic/bedrock streaming calls only — openai records on this branch carry no stream marker) — plus a session.id / event.sequence / prompt.id the SDK mints itself. Token counts always come from the provider's own response; the SDK never estimates them.

Never sent: prompt text, response/completion text, message bodies, or cost_usd. Atlas recomputes cost server-side from a price table — sending a cost figure here would be redundant at best and wrong (stale/incomplete) at worst.

Failure modes — the SDK is designed to disappear

  • init() never raises. A client it can't find or patch is skipped with one warning log; the rest of your app runs exactly as if unpatched.
  • init() is idempotent — call it twice and the second call is a no-op.
  • A bug in our extraction/export code is caught and logged; it can never surface to or break your call. A real error from the provider always propagates to you unchanged, exactly as if the SDK weren't installed — it's also recorded as a zero-token event with an error attribute, so error rates stay visible in Atlas.
  • Export runs on a background thread over a bounded queue that drops the oldest entries under backpressure. It never blocks your request thread and never blocks on network I/O.
  • A slow or unreachable Atlas endpoint costs you nothing beyond a few dropped telemetry rows — never added request latency, never a failed provider call.

Streaming

OpenAI only includes token usage on a streamed response when the request carries stream_options={"include_usage": True}. The SDK injects that by default — the one place it modifies an outgoing request (telemetry capture itself never touches requests; it's OpenTelemetry-based). Every chunk, including the resulting usage-only chunk with empty choices, still passes through to your code unchanged. A caller's own stream_options is merged with, never replaced. Opt out with:

keld.init(..., inject_stream_usage=False)

The consequence of opting out: streamed openai rows report zero tokens. Opt out if your OpenAI-compatible gateway rejects stream_options.

Anthropic and Bedrock streaming carry usage in their own event/metadata shapes and need no request changes — usage is accumulated from the stream as it passes through.

Bedrock specifics

  • request_id (the AWS request id) is always sent for Bedrock calls — Atlas uses it to de-duplicate against a separately-deployed AWS-side CloudWatch collector, if your org runs one.
  • model is sent exactly as passed to invoke_model/converse (including inference profile ids/ARNs) — Atlas normalizes these server-side, this SDK never transforms them.
  • invoke_model's response body is a one-shot stream; the SDK reads it once to extract usage and replaces it with an equivalent fresh stream over the same bytes, so your own response["body"].read() still works exactly as if unpatched.

Development

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[test]"
pytest -v

The contract test (tests/test_contract.py) asserts the SDK's OTLP export payload is structurally identical to tests/fixtures/atlas-sdk-otlp-export.golden.json, a copy of the normative fixture published by the keld-atlas backend (services/api/tests/fixtures/atlas-sdk-otlp-export.golden.json). If you touch the wire format, that fixture — and its counterpart in keld-atlas — is the thing to keep in sync; a diff there is the signal the contract moved, not something to "fix" locally.

Releasing to PyPI

Releases are published by .github/workflows/release.yml using PyPI Trusted Publishing, so no API token is stored in this repo. One-time setup registers the publisher at https://pypi.org/manage/project/keld/settings/publishing/ (Your projects → keld → Manage → Publishing): owner ncx-ai, repository atlas-telemetry-python, workflow release.yml, environment pypi. Add a second entry on TestPyPI with environment testpypi for dry runs.

Use that project page, not the account-level pending publisher form. Pending publishers only accept names that do not exist on PyPI yet; keld does, so that form fails with "project already exists". PyPI returns the same message whether you own the project or not, so a failure there is about which form you used, or about your role.

That step needs the PyPI Owner role on the project. A Maintainer can upload releases and nothing else, so a Maintainer can neither add a trusted publisher nor yank a release.

keld on PyPI previously held the Keld CLI at 0.1.0. This SDK starts at 1.0.0: PyPI versions are immutable, so the CLI's release can be yanked but never replaced.

To cut a release:

  1. Bump __version__ in src/keld/_version.py. It is the single source of truth; pyproject.toml reads it.

  2. Move the Unreleased notes into a new version section in CHANGELOG.md.

  3. Merge to main, then tag and push:

    git tag v0.1.0 && git push origin v0.1.0
    

The workflow verifies the tag matches __version__, builds an sdist and a wheel, runs the suite against the built wheel on Python 3.10 through 3.13, and only then publishes. Running the workflow manually (Actions → Release → Run workflow) does the same build and publishes to TestPyPI instead.

License

MIT

Metadata

Release files for keld 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for keld 1.0.0
File Size Uploaded
keld-1.0.0.tar.gz 39.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for keld 1.0.0
File Interpreter ABI Platform
keld-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 66.3 kB

Release files / keld-1.0.0.tar.gz

Download URL keld-1.0.0.tar.gz
Size 39.6 kB
Tags Source
SHA-256 checksum
How to use checksums
1409205cc590a2fd7f431bd021df06e26e20a762a468843f8445b4bb8bf2323d
BLAKE2b-256 checksum
How to use checksums
bfa3aaef4fa11e8df12f170d266f92f74239dd042cfb8c8e56443f5d84021537
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release files / keld-1.0.0-py3-none-any.whl

Download URL keld-1.0.0-py3-none-any.whl
Size 26.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3d5e3c0dfe37eedb56841b55e7b047173988f1e83096e2917279700b5c3d8f6c
BLAKE2b-256 checksum
How to use checksums
7c40d5912660a916c389fc0c705efebf5e7e31b65722ba54cccc67b4a8170c3d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release history Release notifications | RSS feed

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

This release

1.0.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page