ontocast.onto.run_meter¶
Per-run token meter with enforceable limits.
A :class:RunMeter counts tokens for one run as they are spent, across every
concurrent content unit, and refuses the next provider call once a limit is
reached: a cap on a token count, or a caller's limit policy. The meter knows
nothing about money; a caller that prices tokens does so in its limit.
The meter is bound to the running task with :func:use_run_meter. asyncio
tasks (and LangGraph nodes) copy the context they start in, so every unit of a
fan-out records into the same object.
A refusal is an exception, not a state field: :class:BudgetExceededError
propagates out of the unit loops and best-effort steps, the fan-out re-raises it
once every sibling unit has finished, and it leaves the graph run (astream /
ainvoke) carrying the meter's snapshot. The meter itself also stays readable
after the run, so a caller can settle from meter.snapshot() either way.
Deliberately a leaf module with no OntoCast imports, so :mod:ontocast.tool.llm
can import it without reaching back into :mod:ontocast.tool.
Attributes¶
TOKEN_FIELDS = tuple(TokenTotals.model_fields)
module-attribute
¶
logger = logging.getLogger(__name__)
module-attribute
¶
Type Aliases¶
RunLimit = Callable[[Mapping[str, TokenTotals]], str | None]
¶
Classes¶
BudgetExceededError
¶
Bases: RuntimeError
A run reached a token cap or its meter's limit.
Raised before the provider call that would have exceeded it, so that call is never made. Calls already in flight finish and are recorded, so the final usage can pass the limit by what they spent.
Attributes:
| Name | Type | Description |
|---|---|---|
snapshot |
The meter's state when the call was refused. |
Source code in ontocast/onto/run_meter.py
RunMeter
¶
Thread- and task-safe token meter for one run.
check(), called before every provider call, raises
:class:BudgetExceededError once any field summed over models reaches its
token_caps entry, or once limit returns a reason. After the first
refusal every later check() refuses too.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
limit
|
RunLimit | None
|
The caller's policy, given a copy of the counts per model before each provider call; returns a reason to stop, or None. Called outside the meter's lock, so it may read the meter; an exception it raises propagates to the call it was guarding. |
None
|
token_caps
|
dict[str, int] | None
|
Limits on token counts summed over models, keyed by field name. |
None
|
on_usage
|
Callable[[RunMeterSnapshot], None] | None
|
Called with a snapshot after usage is recorded, at most once
every |
None
|
min_interval_s
|
float
|
Minimum seconds between two |
0.0
|
clock
|
Callable[[], float]
|
Monotonic time source for the throttle. |
monotonic
|
Raises:
| Type | Description |
|---|---|
ValueError
|
On an unknown field name in |
Source code in ontocast/onto/run_meter.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 | |
Attributes¶
exceeded
property
¶
Why the meter refused a call, or None if it has not.
limit = limit
instance-attribute
¶
min_interval_s = min_interval_s
instance-attribute
¶
on_usage = on_usage
instance-attribute
¶
token_caps = dict(token_caps or {})
instance-attribute
¶
usage_by_model = {}
instance-attribute
¶
Methods:¶
__init__(*, limit=None, token_caps=None, on_usage=None, min_interval_s=0.0, clock=time.monotonic)
¶
Source code in ontocast/onto/run_meter.py
check()
¶
Raise :class:BudgetExceededError if a limit has been reached.
Source code in ontocast/onto/run_meter.py
flush()
¶
from_token_limits(*, max_input_tokens=None, max_output_tokens=None)
classmethod
¶
A meter capping input and output, or None if unlimited.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_input_tokens
|
int | None
|
Cap on provider input tokens over all models. |
None
|
max_output_tokens
|
int | None
|
Cap on provider output tokens over all models. |
None
|
Source code in ontocast/onto/run_meter.py
record(model, usage)
¶
Add usage to model's totals and notify on_usage if due.
snapshot()
¶
totals()
¶
RunMeterSnapshot
¶
Bases: TypedDict
JSON-serialisable state of a :class:RunMeter.
Source code in ontocast/onto/run_meter.py
TokenTotals
¶
Bases: BaseModel
Token counts for one model, split by where each response came from.
input to reasoning count provider calls made in this run, as the
provider reports them: cache_read and cache_write are counted inside
input, reasoning inside output. replayed_* count responses
served by the OntoCast disk cache, for which no provider call was made.
Source code in ontocast/onto/run_meter.py
Attributes¶
cache_read = Field(default=0, description="Input tokens served from the provider's prompt cache.")
class-attribute
instance-attribute
¶
cache_write = Field(default=0, description="Input tokens written to the provider's prompt cache.")
class-attribute
instance-attribute
¶
calls = Field(default=0, description='Provider calls that returned.')
class-attribute
instance-attribute
¶
input = Field(default=0, description='Input tokens of provider calls, cache reads included.')
class-attribute
instance-attribute
¶
output = Field(default=0, description='Output tokens of provider calls, reasoning included.')
class-attribute
instance-attribute
¶
reasoning = Field(default=0, description='Reasoning tokens of provider calls.')
class-attribute
instance-attribute
¶
replayed_input = Field(default=0, description='Input tokens of responses replayed from disk cache.')
class-attribute
instance-attribute
¶
replayed_output = Field(default=0, description='Output tokens of responses replayed from disk cache.')
class-attribute
instance-attribute
¶
replays = Field(default=0, description='Responses replayed from disk cache.')
class-attribute
instance-attribute
¶
Methods:¶
Functions:¶
active_run_meter()
¶
run_meter_scope(*, max_input_tokens=None, max_output_tokens=None)
¶
Bind a token-capped meter for one document unless one is already bound.
An already-bound meter is yielded unchanged and the caps are ignored: the caller that bound it owns the limits. With no meter bound and no cap set, yields None and meters nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_input_tokens
|
int | None
|
Cap on provider input tokens for the document. |
None
|
max_output_tokens
|
int | None
|
Cap on provider output tokens for the document. |
None
|
Source code in ontocast/onto/run_meter.py
use_run_meter(meter)
¶
Meter every provider call made inside this block with meter.
On exit the meter is flushed. If the meter refused a call inside the block
and the block itself raised nothing, :class:BudgetExceededError is raised
here, so a refusal that a best-effort step swallowed still stops the run.