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.usageandresponse.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
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:
from maivn import ProviderModelUsage, ProviderUsageEach 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.
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:
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:
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_tokenswill give you the wrong figure in both directions. - If
provider_counts_completeisFalse, 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.
#Related
- Shared Data Models —
InvokeResponse,token_usage, and the rest of the response shapes - Billing — your organization's plan and its billing-period counters
- Billing and Usage guide — quotas, limits, and what happens when you reach them