Agentcore Metering
Unified module for AI service invocation and statistics in Django projects.
- Uses LiteLLM for completion and cost metering.
- Stores cost as amount + currency (e.g.
cost,cost_currency). - Uses USD as default cost currency from LiteLLM.
Install
- Not on PyPI, install only from GitHub.
From GitHub (editable after clone):
pip install -e git+https://github.com/cloud2ai/agentcore-metering.git
Or, when the host project uses it as a submodule, from repo root:
pip install -e path/to/agentcore-metering
- The host project Dockerfile should iterate over
agentcore/submodules and runpip install -e. - See the host project README for details.
Testing
Install with dev extras, then run pytest from the package root:
pip install -e ".[dev]"
pytest tests -v
- Unit tests cover:
llm_usage_stats(parse, summary, by_model, series)llm_usage(paginated list and query parsing)
- API tests cover:
GET .../token-stats/GET .../llm-usage/llm-configendpoints (auth and response shape)
Backend: usage
- Register
- Add
'agentcore_metering.adapters.django'toINSTALLED_APPS - Add
path('api/v1/admin/', include('agentcore_metering.adapters.django.urls'))to root URLconf
- Add
- Call LLM and persist usage:
from agentcore_metering.adapters.django import LLMTracker content, usage = LLMTracker.call_and_track( messages=[{"role": "user", "content": "..."}], node_name="my_node", state=request_state, )
stateis optional (task/user context).- If
statecontainsuser_id, per-user LLM config (if set) is used. Passmodel_uuidto use a specific config; otherwise the earliest enabled model is used. - For
json_mode=True(non-stream), tracker does JSON repair + validation and retries by default (json_attempts=3; configurable). - For
stream=True, JSON repair is skipped (stream output is unchanged).
- Config
- All config can be managed by admin APIs (global defaults + optional per-user overrides).
- When model_uuid is not provided, resolution uses the earliest enabled config (by is_default then created_at): user scope -> global scope; no settings fallback, raises if no DB config.
- LLM calls go through LiteLLM.
- Cost (USD) is estimated from LiteLLM reference pricing and stored per call and in stats.
Supported providers
- Each provider uses a smallest/cheapest default model to reduce misconfiguration cost.
- You can override
modeland optionallyapi_base/deploymentinconfig. - All providers support
api_base(URL):- official endpoint when omitted
- proxy/forwarding URL when needed
- If you are using an official model endpoint, choose the native provider
for that vendor, for example
minimax. - If you are using a third-party forwarding platform through an
OpenAI Compatible endpoint, choose
openai_compatible. - Do not rely on model strings to infer provider automatically; choose the provider based on the actual integration path.
| Provider | Default model / notes |
|---|---|
openai |
gpt-4o-mini (official default URL) |
azure_openai |
gpt-4o-mini (requires api_base and deployment) |
gemini |
gemini-2.0-flash |
anthropic |
claude-3-5-haiku |
mistral |
mistral-tiny |
dashscope |
qwen-turbo (Alibaba Qwen) |
deepseek |
deepseek-chat |
xai |
grok-3-mini-beta (Grok) |
meta_llama |
Llama-3.3-8B-Instruct |
amazon_nova |
nova-micro-v1 |
nvidia_nim |
meta/llama3-8b (Nemotron / NIM) |
minimax |
MiniMax-M2.1 |
openai_compatible |
Depends on the gateway vendor (OpenAI Compatible endpoint) |
moonshot |
moonshot-v1-8k (Kimi) |
zai |
glm-4.5-flash (Z.AI GLM) |
volcengine |
doubao-pro-32k (ByteDance Doubao) |
openrouter |
google/gemma-2-9b-it:free |
- Config is from DB only (admin API); no Django settings fallback. At least one enabled global or user config is required for calls.
API reference
- Mount under an admin prefix (e.g.
api/v1/admin/). - Auth:
IsAdminUser(staff or superuser), otherwise 403. - If the main project uses drf-spectacular, endpoints appear in
Swagger UI (e.g.
/swagger) under llm-metering.
LLM configuration (global and per-user)
| Method | Path | Description |
|---|---|---|
| GET | .../llm-config/ |
List global LLM configs (ordered by created_at, id) |
| POST | .../llm-config/ |
Create one config. Body: provider, config (optional scope, user_id, is_active) |
| GET | .../llm-config/all/ |
List all LLM configs (global + user). Optional `scope=all |
| GET | .../llm-config/<pk>/ |
Get one config by id |
| PUT | .../llm-config/<pk>/ |
Update one config by id |
| DELETE | .../llm-config/<pk>/ |
Delete one config by id |
| GET | .../llm-config/providers/ |
Per-provider param schema (required/optional/editable keys, default model and api_base) for building provider-specific forms |
| GET | .../llm-config/models/ |
Provider list and model list with capability tags (text-to-text / vision / code / reasoning, etc.) |
| POST | .../llm-config/test/ |
Validate credentials without saving. Body: provider, config |
| POST | .../llm-config/test-call/ |
Run one completion and persist usage. Body: config_uuid (or legacy config_id), prompt, optional max_tokens, optional stream (see below) |
| GET | .../llm-config/users/ |
List per-user configs (optional ?user_id= filter) |
| GET | .../llm-config/users/<user_id>/ |
Get one user's config (404 if not set) |
| PUT | .../llm-config/users/<user_id>/ |
Create or update that user's config |
| DELETE | .../llm-config/users/<user_id>/ |
Remove user config (they fall back to global default) |
-
POST/PUT body
provider(defaultopenai)config(single JSON object, e.g.api_key,model,api_base,deployment,max_tokens,temperature,top_p,request_timeout_seconds,num_retries)num_retriesdefaults to3, accepts0to disable retries, and is applied within the singlerequest_timeout_secondsbudget- for create/list workflows: optional
scope,user_id,is_active, optionalmodel_type - required/optional keys differ by provider (e.g. Azure needs
api_baseanddeployment) - use
GET .../llm-config/providers/to fetch schema - on GET,
config.api_keyandconfig.keyare masked (e.g.sk-**xxxx);is_defaultis true when this config is the current default (earliest enabled global config used when model_uuid is not set), so the frontend can highlight the default model.
-
GET
.../llm-config/providers/- returns
{ "providers": { "<provider>": { "required": [...], "optional": [...], "editable_params": [...], "default_model": "...", "default_api_base": "..." } } } - use it to render provider-specific forms and placeholders
- returns
-
POST
.../llm-config/test/- body:
provider,config(same as PUT) - runs a minimal completion to verify key and endpoint
- success:
200+{ "ok": true } - validation/completion failure:
200+{ "ok": false, "detail": "..." } - invalid payload:
400
- body:
-
POST
.../llm-config/test-call/- body:
config_uuid(preferred) or legacyconfig_id,prompt, optionalmax_tokens(default 512, max 4096), optionalstream(default false) - when
streamis false (default): JSON response. success:{ "ok": true, "content": "...", "usage": { ... } }; failure:{ "ok": false, "detail": "..." } - when
streamis true: response is SSE (Content-Type: text/event-stream). Events:data: {"type":"chunk","content":"<fragment>"}for each content fragment; final eventdata: {"type":"done","ok":true,"usage":{...}}ordata: {"type":"done","ok":false,"detail":"..."} - call is persisted to usage records; streaming calls are recorded with
is_streaming=trueand optional TTFT (first_chunk_at)
- body:
GET .../token-stats/
- Returns aggregates and optional time series.
- Query params are all optional:
| Param | Type | Description |
|---|---|---|
| start_date | string | Start time, ISO or date-only (e.g. 2025-01-01) |
| end_date | string | End time; date-only is end of that day |
| user_id | int | Filter by user id |
| granularity | string | Time bucket: day (hour), month (day), year (month); omit for no series |
| use_series | string | 1 / true / yes: when set with granularity and date range, also return series_by_model from pre-aggregated LLMUsageSeries (for per-model trend charts) |
200 (JSON):
{
"summary": { ... },
"by_model": [ ... ],
"series": null,
"series_by_model": null
}
seriesis non-null only whengranularityis set:{ "granularity": "day", "items": [ { "bucket": "...", "total_tokens": 0, ... } ] }series_by_modelis non-null only whenuse_series=1(ortrue/yes) andgranularityandstart_date/end_dateare set. It is a list of{ "bucket", "model", "call_count", "success_count", "avg_e2e_latency_sec", "avg_ttft_sec", "avg_output_tps", "total_prompt_tokens", "total_completion_tokens", "total_tokens", "total_cached_tokens", "total_reasoning_tokens", "total_cost", "cost_currency" }from the pre-aggregated table (global scope; not filtered byuser_id). Use it for “Token trend by model” and “Cost trend by model” charts.- invalid
granularityreturns400+{ "detail": "..." }
GET / PATCH .../metering-config/
- GET: Returns effective metering config (retention, cleanup and aggregation schedules).
- PATCH: Update config. Body fields are optional:
retention_days(1–3650),cleanup_enabled,cleanup_crontab,aggregation_crontab(five-field cron expressions).
GET 200 (JSON):
{
"retention_days": 365,
"cleanup_enabled": true,
"cleanup_crontab": "0 2 * * *",
"aggregation_crontab": "5 * * * *"
}
- Used by the admin UI “Data settings” / “Scheduled tasks” page to configure how long to keep data and when cleanup/aggregation run.
GET .../llm-usage/
- Returns a paginated usage list.
- Query params:
| Param | Type | Description |
|---|---|---|
| page | int | Page number, default 1 |
| page_size | int | Page size, default 20, max 100 |
| user_id | int | Filter by user id |
| model | string | Model name (icontains) |
| success | string | true / false |
| start_date | string | Start time |
| end_date | string | End time |
200 (JSON):
{
"results": [
{
"id": "uuid",
"user_id": "uuid-or-null",
"username": "string-or-null",
"model": "gpt-4",
"prompt_tokens": 100,
"completion_tokens": 50,
"total_tokens": 150,
"cost": 0.0012,
"cost_currency": "USD",
"success": true,
"error": null,
"created_at": "2025-01-01T12:00:00+00:00",
"metadata": {}
}
],
"total": 100,
"page": 1,
"page_size": 20
}
- UX
- use
token-statsfor dashboards and trend charts - use
llm-usagefor filterable, paginated call logs
- use
Pre-aggregated series (LLMUsageSeries)
- Table:
llm_usage_seriesstores pre-aggregated usage per (granularity, bucket, model). Granularities:hour(for “day” view),day(for “month” view),month(for “year” view). - Fields per row:
bucket,model,call_count,success_count,avg_e2e_latency_sec,avg_ttft_sec,avg_output_tps, token totals (total_prompt_tokens,total_completion_tokens,total_tokens,total_cached_tokens,total_reasoning_tokens),total_cost,cost_currency. - Population: Celery task
aggregate_llm_usage_series_task(hour + day + month in one run when invoked from beat) aggregates fromllm_tracker_usageintollm_usage_series. Use it for fast “by model” trend charts without querying raw usage on each request. - API:
GET .../token-stats/?use_series=1&granularity=...&start_date=...&end_date=...returnsseries_by_model(list of the above rows) for charting.
Metering config and scheduled tasks
- Config model:
MeteringConfig(single row) holdsretention_days(default 365),cleanup_enabled,cleanup_crontab,aggregation_crontab. API:GET/PATCH.../metering-config/(see above). - Celery tasks (require Celery and
agentcore-task; registered with django-celery-beat):- Cleanup:
cleanup_old_llm_usage_task— deletesllm_tracker_usageandllm_usage_seriesolder thanretention_days. No-op ifcleanup_enabledis false. - Aggregation:
aggregate_llm_usage_series_task— runs hour, day, and month aggregation in one go (when called from beat without args); populatesllm_usage_series.
- Cleanup:
- Task tracking: Both tasks register with agentcore-task
TaskTracker(moduleagentcore_metering). Executions appear in the unified task list and stats (status, result, error).
Data and layout
- Tables:
llm_tracker_usage(usage records),llm_usage_series(pre-aggregated series),agentcore_metering_llm_config(LLM provider config),MeteringConfig(retention and cron settings). - Package:
agentcore_metering.adapters.djangois a full Django app (models, views, urls, admin, migrations). The public API for LLM metering lives undertrackers/(e.g.trackers/llm.py); importLLMTrackerfromagentcore_metering.adapters.djangooragentcore_metering.adapters.django.trackers.llm. Additional tracker types may be added undertrackers/(e.g.trackers/other.py).
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 agentcore_metering-0.2.0.tar.gz.
File metadata
- Download URL: agentcore_metering-0.2.0.tar.gz
- Upload date:
- Size: 112.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b71a6f47f5a5f2f81244f6b17be615b281ab5c34b5fc91e86263841905ab586
|
|
| MD5 |
171647ed7e70eada9d17210364e74f2b
|
|
| BLAKE2b-256 |
f48dd56c423b0bb7df39cd9a98d6ce51000af2b9b7989cc4f8b8c3f00b34c37c
|
Provenance
The following attestation bundles were made for agentcore_metering-0.2.0.tar.gz:
Publisher:
release.yml on cloud2ai/agentcore-metering
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentcore_metering-0.2.0.tar.gz -
Subject digest:
1b71a6f47f5a5f2f81244f6b17be615b281ab5c34b5fc91e86263841905ab586 - Sigstore transparency entry: 2764492479
- Sigstore integration time:
-
Permalink:
cloud2ai/agentcore-metering@06b0436a606b29979e8ba477bce71fad6fcbfb92 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/cloud2ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@06b0436a606b29979e8ba477bce71fad6fcbfb92 -
Trigger Event:
push
-
Statement type:
File details
Details for the file agentcore_metering-0.2.0-py3-none-any.whl.
File metadata
- Download URL: agentcore_metering-0.2.0-py3-none-any.whl
- Upload date:
- Size: 113.1 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 |
3b66d7a0ff6fc9b9a8a5478de15f1729d7e382e1d50d2964b3204df6a4bbffc4
|
|
| MD5 |
4f421bff89c48af60a5f1536897f9862
|
|
| BLAKE2b-256 |
bbe1469723242e9026ae219ac0eb9fefa67fe1a832c8e6495564c202464f7c82
|
Provenance
The following attestation bundles were made for agentcore_metering-0.2.0-py3-none-any.whl:
Publisher:
release.yml on cloud2ai/agentcore-metering
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentcore_metering-0.2.0-py3-none-any.whl -
Subject digest:
3b66d7a0ff6fc9b9a8a5478de15f1729d7e382e1d50d2964b3204df6a4bbffc4 - Sigstore transparency entry: 2764492491
- Sigstore integration time:
-
Permalink:
cloud2ai/agentcore-metering@06b0436a606b29979e8ba477bce71fad6fcbfb92 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/cloud2ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@06b0436a606b29979e8ba477bce71fad6fcbfb92 -
Trigger Event:
push
-
Statement type: