Skip to content

Documentation navigation

Usage and Cost

Read run and session usage, distinguish provider counts from billed usage, and handle incomplete totals.

#Usage and Cost

Two different questions get asked about the same run, and they have two different answers.

  • What did this run use? — response.usage and response.token_usage. These are the platform's own counts for the work you asked for.
  • What did the model providers charge for? — response.provider_usage. These are the numbers each provider reported for the calls it served, broken out by provider and model, so you can multiply them by a published price list and get a figure you can compare with anything else.

They are not the same number and they are not meant to be. A run can be served by more than one model, a provider counts some tokens the platform does not bill you for, and cached input is counted differently from fresh input. Use the first to understand your own consumption, the second to price the run.

#Provider-reported usage for one run

Python
from maivn import Agent
from maivn.messages import HumanMessage

agent = Agent(name='analyst', api_key='your-api-key')
response = agent.invoke([HumanMessage(content='Summarize the attached notes.')])

provider_usage = response.provider_usage
if provider_usage is not None:
    print(provider_usage.input_tokens, 'input /', provider_usage.output_tokens, 'output')
    for row in provider_usage.by_model:
        print(row.provider, row.model, row.calls, row.input_tokens, row.output_tokens)

provider_usage is a ProviderUsage, importable from maivn along with ProviderModelUsage:

Python
from maivn import ProviderModelUsage, ProviderUsage
FieldTypeMeaning
scope'run' or 'session'Whether these numbers cover one invocation or a whole session.
settledboolWhether more usage can still arrive. See Settled.
provider_counts_completeboolFalse when at least one call's provider count was missing and the platform's own count stood in for it.
callsintHow many model calls these totals cover.
input_tokensintInput tokens the providers reported.
output_tokensintOutput tokens the providers reported.
cache_read_input_tokensintInput tokens served from the provider's prompt cache.
cache_creation_input_tokensintInput tokens written to the provider's prompt cache.
by_modeltuple[ProviderModelUsage, ...]The same totals split per provider and model, sorted by provider then model.
as_ofdatetime | NoneWhen the totals were read. Set on a session total; absent on a run.

Each ProviderModelUsage row carries provider, model, calls, and the same four token counts. Rows are the unit you price: each model has its own rates, so summing two models into one number would apply one price to tokens that were not bought at the same rate.

response.provider_usage is None when the service you are talking to does not publish provider counts. That is a missing reading, not a free run — do not treat None as zero.

#Settled

A run's provider_usage always has settled=False, and that is not a defect.

Some work continues after you get your answer. If memory is enabled, for example, the platform extracts what is worth remembering from the conversation after the response is already on its way back to you. That work calls models, and those calls report their token counts after your run's numbers were written. A run's block can only ever report what had arrived by the time the response was built.

settled=True means nothing further can be added: every model call recorded for that scope has reported its final numbers.

#The total for a session

Agent.session_usage() answers for the whole session — the run itself plus any follow-up work the platform started for it.

Python
usage = agent.session_usage(response.session_id)
print(usage.calls, 'calls,', usage.input_tokens, 'input tokens')
print('final' if usage.settled else 'still arriving')

An async twin is available as agent.asession_usage(...), and Client exposes the same pair.

If settled is False, the total is a floor, not a final figure. Wait a moment and ask again:

Python
import time

for attempt in range(12):
    usage = agent.session_usage(response.session_id)
    if usage.settled:
        break
    if attempt < 11:
        time.sleep(5)
else:
    print("Usage is still provisional; check again later.")

Completion time depends on the work still running. This loop bounds the number of reads; each read also remains subject to the client HTTP timeout. Keep an unsettled result marked provisional.

A session you are not entitled to read is reported as not found, the same as a session id that never existed.

#Pricing a run

Provider counts plus a price list is all you need:

Python
RATES_PER_MILLION = {
    ('provider-a', 'model-alpha'): {'input': 3.00, 'output': 15.00},
}

usage = agent.session_usage(response.session_id)
total = 0.0
for row in usage.by_model:
    rates = RATES_PER_MILLION[(row.provider, row.model)]
    total += row.input_tokens / 1_000_000 * rates['input']
    total += row.output_tokens / 1_000_000 * rates['output']

print(f'{total:.4f} estimated')

Two things to keep honest:

  • Price the cache counts at their own rates when your price list has them. Cached input is usually cheaper than fresh input and written cache is usually dearer, so folding them into input_tokens will give you the wrong figure in both directions.
  • If provider_counts_complete is False, say so alongside the number. Part of that total is the platform's own count standing in for a provider count that never arrived, and an estimate built on it is an estimate, not a receipt.

Read settled before you publish a figure. Pricing an unsettled total gives you a number that was correct for a moment and is now too low.