llm-metrics
Python SDK for the llm-metrics platform. Distribution name llm-metrics,
import name llm_metrics.
Not released yet, so
pip install llm-metricsdoes not work. Nothing else owns that name — it is simply unclaimed until the first upload. Install from source:
git clone https://github.com/aadityasinha-dotcom/llm-metrics
cd llm-metrics
pip install -e . # core
pip install -e ".[openai]" # + OpenAI wrapper
pip install -e ".[langchain]" # + LangChain callback handler
The SDK itself runs on Python 3.9+ with httpx as its only dependency. The
integration extras need 3.10+, because current openai and langchain-core
both dropped 3.9 — CI reflects that split.
Usage
import llm_metrics
from llm_metrics import observe
@observe(as_type="tool")
def search(query: str) -> list[str]: ...
@observe(as_type="generation", name="gpt-4o")
def complete(messages: list[dict]) -> dict: ...
@observe()
def answer(question: str) -> str: # becomes the root of the trace
return complete(search(question))["content"]
Set $LLM_METRICS_API_KEY and that is the whole setup. Nested calls join the
enclosing trace automatically; a call with no trace open starts one.
@observe works bare or called, on sync and async functions, and on generators
and async generators — a streamed completion is timed over the whole stream
rather than over the microseconds it took to build the generator.
Guarantees
- Never blocks the caller. All network I/O runs on a background daemon
thread. Enqueueing an event is a
deque.appendunder a microsecond-held lock. - Never crashes the host app. Transport failures drop events and keep going.
- Bounded memory. The buffer has a hard cap; on overflow the oldest events are discarded rather than growing the queue.
- No client-side cost math. Token counts go up; pricing is applied server-side.
Configuration
LLM_METRICS_API_KEY |
required; without it the SDK is inert and says so once on stderr |
LLM_METRICS_HOST |
defaults to the cloud endpoint |
LLM_METRICS_DEBUG |
set to log dropped batches to stderr |
LLM_METRICS_ENABLED |
set to 0 to make @observe a near no-op (~0.2 us/call) |
Explicit arguments beat environment variables, which beat defaults.
llm_metrics.configure(...) sets the same things in code; calls that only touch
capture flags leave the running buffer and its queued events alone.
Arguments and return values are captured by default and truncated at 2000
characters per value. Turn it off per-decorator with
@observe(capture_input=False) or globally via configure().
Overhead is ~50 us per call with capture on — 0.01% of a 500 ms LLM call.
OpenAI
from openai import OpenAI
from llm_metrics.integrations.openai import wrap_openai
client = wrap_openai(OpenAI())
client.chat.completions.create(model="gpt-4o", messages=[...])
Every completion becomes a generation observation with the model, messages,
response, token counts, and latency — nesting under an enclosing @observe
trace if there is one. Sync and async clients, streaming and not.
Token counts on a streamed response require
stream_options={"include_usage": True} on your call. The integration will not
add it for you: it appends a final chunk with an empty choices list, and code
doing chunk.choices[0] unguarded would start raising the moment it was wrapped.
LangChain
from llm_metrics.integrations.langchain import LlmMetricsTracer
tracer = LlmMetricsTracer()
chain.invoke({"question": "..."}, config={"callbacks": [tracer]})
Chains become spans, LLM calls become generations, tools and retrievers get
their own types. Streamed generations also record time_to_first_token_ms.
One tracer instance is safe to reuse across invocations and share between
threads.
Unlike the rest of the SDK, nesting here comes from LangChain's own
run_id/parent_run_id tree rather than from contextvars — LangChain may
invoke callbacks from a thread where the ambient context is empty. The ambient
context is consulted once, for the root run, so a chain inside an @observe
function joins that trace instead of starting a new one.
Tracing across threads
asyncio tasks inherit the ambient trace and stay siblings under gather, so
nesting works with no extra effort. Threads inherit nothing — a
ThreadPoolExecutor worker starts with no trace and its observations become
orphan roots. Carry the context across explicitly:
ctx = contextvars.copy_context() # stdlib, at the call site
executor.submit(ctx.run, do_work, arg)
snap = context.snapshot() # or, across a queue
with context.adopt(snap):
do_work(arg)
Status
Every module in the CLAUDE.md build order is in place: models.py,
buffer.py, client.py, context.py, decorator.py, and both integrations.
Not yet done: tests/test_contract.py, which needs the API's published
openapi.json. Until it exists, the request envelope ({"events": [...]}),
the Authorization: Bearer header, and the assumption that ingest upserts
(children can arrive in a batch before their parent) are unverified.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llm_metrics-0.1.0.tar.gz.
File metadata
- Download URL: llm_metrics-0.1.0.tar.gz
- Upload date:
- Size: 60.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c66daae05b9bb4446fb5b6b7ec3cda9a18ca5d5209d496ea12504c9dc9b6eb14
|
|
| MD5 |
0ef2e6adc10efceb59bf67867a5c693c
|
|
| BLAKE2b-256 |
cefa88174b2a4703044a608853582c15fbf90c056fc9ee8def532f4c2a65d658
|
Provenance
The following attestation bundles were made for llm_metrics-0.1.0.tar.gz:
Publisher:
publish.yml on aadityasinha-dotcom/llm-metrics
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llm_metrics-0.1.0.tar.gz -
Subject digest:
c66daae05b9bb4446fb5b6b7ec3cda9a18ca5d5209d496ea12504c9dc9b6eb14 - Sigstore transparency entry: 2521175096
- Sigstore integration time:
-
Permalink:
aadityasinha-dotcom/llm-metrics@da1f20413b62a0a029451778ab71c2ed2709c0e9 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/aadityasinha-dotcom
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@da1f20413b62a0a029451778ab71c2ed2709c0e9 -
Trigger Event:
release
-
Statement type:
File details
Details for the file llm_metrics-0.1.0-py3-none-any.whl.
File metadata
- Download URL: llm_metrics-0.1.0-py3-none-any.whl
- Upload date:
- Size: 40.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5de20ca35b8bd0c44280b1fce16f82cbc20fa54c972c8ff2c94c5518309597b8
|
|
| MD5 |
75cc7f1ea776f43cac71b25ca9c6eefc
|
|
| BLAKE2b-256 |
78af8f07ea6f8edd506b8f4f99bac885e8da41c0181093c72db24bf3a7248d9a
|
Provenance
The following attestation bundles were made for llm_metrics-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on aadityasinha-dotcom/llm-metrics
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llm_metrics-0.1.0-py3-none-any.whl -
Subject digest:
5de20ca35b8bd0c44280b1fce16f82cbc20fa54c972c8ff2c94c5518309597b8 - Sigstore transparency entry: 2521176035
- Sigstore integration time:
-
Permalink:
aadityasinha-dotcom/llm-metrics@da1f20413b62a0a029451778ab71c2ed2709c0e9 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/aadityasinha-dotcom
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@da1f20413b62a0a029451778ab71c2ed2709c0e9 -
Trigger Event:
release
-
Statement type: