Agentcore Metering
Unified module for AI service invocation and statistics in Django projects.
- Uses LiteLLM for completion and cost metering.
- Supports typed decision models (including Jev) via LiteLLM SDK or Gateway; see configuration, calls and 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.5-flash |
anthropic |
claude-haiku-4-5 |
mistral |
mistral-small-4 |
dashscope |
qwen3.8-flash (Alibaba Qwen) |
deepseek |
deepseek-flash |
xai |
grok-4.3 (Grok) |
meta_llama |
Llama-3.3-8B-Instruct |
amazon_nova |
nova-micro-v1 |
nvidia_nim |
nvidia/nemotron-3-nano-30b-a3b (Nemotron / NIM) |
minimax |
MiniMax-M2.7 |
openai_compatible |
Depends on the gateway vendor (OpenAI Compatible endpoint) |
moonshot |
kimi-k2.6 (Kimi) |
zai |
glm-5.3-flash (Z.AI GLM) |
volcengine |
doubao-seed-2.0-mini (ByteDance Doubao) |
openrouter |
openai/gpt-5.6-luna |
- 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
-
Input/output capability metadata
- Save manual overrides in
config.model_info, for example{"supported_modalities": ["text", "image"], "supported_output_modalities": ["text"]}. - Input values:
text,image,audio,video. Output also acceptscode. Arrays must be non-empty. These are metadata, never provider parameters. - Omit
model_infoon update to preserve existing overrides. Supplying it replaces the entire override object;{}ornullrestores automatic detection. Omitting a direction inside the object restores that direction. - Changing provider/model without new overrides clears the old overrides.
- Config read responses add top-level
model_infowith resolved modalities and per-fieldsources(manual,catalog,unknown). Unknown isnull, not a claim that the capability is unsupported. Catalog lists carry known capabilities, not an exhaustive guarantee for a configured gateway. - Automatic detection reads the local LiteLLM registry and curated directory; it does not call a model provider. Manual overrides take precedence.
- Authenticated users can read the directory at
/api/v1/user/llm-config/models; it exposes no saved configs or keys.
- Save manual overrides in
-
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).
Metadata
Release files for agentcore-metering 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| agentcore_metering-0.4.1.tar.gz | 151.6 kB | With provenance | Details for agentcore_metering-0.4.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| agentcore_metering-0.4.1-py3-none-any.whl | Python 3 | none | any | Details for agentcore_metering-0.4.1-py3-none-any.whl |
Total release size: 286.6 kB
Release files / agentcore_metering-0.4.1.tar.gz
| Download URL | agentcore_metering-0.4.1.tar.gz |
|---|---|
| Size | 151.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
521478e6d4211443e19dba81bf9b4e384af960660739fb33e8df5a001e0efcc1
|
|
BLAKE2b-256 checksum How to use checksums |
90f179d42c36d6c0182f0aaece96627d9d35a3f57dcc330beb03126b2add3a4b
|
| 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 Oct 12, 2026.
Transparency logRelease files / agentcore_metering-0.4.1-py3-none-any.whl
| Download URL | agentcore_metering-0.4.1-py3-none-any.whl |
|---|---|
| Size | 135.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ebf0f280c00ccbe8520b14a5da330e317cf6809ac66aa37051c52f27a0a03de8
|
|
BLAKE2b-256 checksum How to use checksums |
a0ff22fd36d22c21173176ce7172d4320d12396c5ec558c0827f8cf797aa2c4e
|
| 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 Oct 12, 2026.
Transparency logSecurity for agentcore-metering 0.4.1
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for agentcore_metering-0.4.1.tar.gz
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 Oct 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.