- a pack hands the model a fraction of what the API actually sent
- the boundary adds no meaningful latency of its own
ToolCall is those two claims as numbers,
recorded on every invocation — including the ones that fail, and the ones that
never leave the process.
All of it is local. A record goes to the on_call sink you name and to the charter
logger; there is no default sink, no phone-home, and no history. Comparing this
week’s numbers against last week’s needs storage, and this library deliberately
has none.
A line per call
The runtime emits one INFO record per call, whose message is the rendered line:enable_call_log.py
A summary at the end of a run
CallCollector is a sink that keeps
the records; format_call_summary
renders them:
call_summary.py
Forwarding to something that keeps history
on_call takes any callable — the CallSink
protocol. ToolCall.to_dict() uses
OpenTelemetry’s attribute names where a convention exists, so forwarding needs no
translation table:
otel_sink.py
What is measured, and why that decomposition
Outcome, not just status code
Two of these have no equivalent anywhere else in a normal stack. The three
reached_network: false outcomes are calls that cost nothing at the API — a
number no HTTP-level metric can produce, because nothing HTTP happened.
envelope_error is the opposite surprise: an API that answers 200 and puts the
failure in the body, which every generic HTTP metric records as a success. See
envelopes.
The split between credential_unavailable and credential_rejected is drawn on
reached_network, not on the status code the exception carries. A failed OAuth
refresh raises a CredentialError carrying a 400 from the authorization
server; filing that as “rejected” would report that the API turned you away when
the API was never contacted. (One nuance worth knowing: a failed refresh did make
a network call, just not to the tool’s API. reached_network is about the tool’s
own request.)
Validation is why the record is minted in
Tool.ainvoke rather than in the executor:
validate_input runs before the executor is reached, so a record minted any
deeper would miss every rejected call.
Timings, split by owner
These fields exist to be acted on. Optimization is the other side of this page: which lever moves which number.
A single duration would conflate four things with different owners; a slow
credential provider and a slow API are the same number to anyone who only measured
the total.
overhead_ms is the one that answers is this layer in my way? — and a
boundary layer should be able to answer that from the user’s own logs, on demand.
Sizes on both sides
saved_bytes and saved_ratio are derived. A negative saving is not hidden: a
response handler that adds more than it removes is worth seeing.
Three things this deliberately does not do:
- It does not report tokens. Tokens need a tokeniser this library does not have, and a bytes-per-token constant would turn a measurement into a guess with a decimal point in it.
- It does not claim bandwidth saved.
payload_bytesis read after decompression. The currency here is the model’s context window, not egress. - It does not re-serialise the payload to make the ratio prettier.
payload_bytesis the response as the server formatted it andcontext_bytesis compact JSON, so against an API that pretty-prints, part of the difference is that API’s whitespace.
Cost
Timings are a handful ofperf_counter reads and are always taken. Sizes cost a
serialisation of the arguments and of the result, so they are only computed when
something is listening — a sink is attached, or the charter logger is enabled for
INFO. With neither, the size fields are None and nothing extra is paid.
Where this stops
Everything above is derivable from one process’s own run. The moment a number needs yesterday — a baseline, “p95 doubled”, “this response grew three fields” — it needs storage, and storing your traffic is not something a library that promises it cannot see your data gets to do. That line is the same one drawn in the package docstring, and it is structural rather than commercial.Related
ToolCall— every field, grouped by identity, outcome, timings and sizesCallSinkandCallCollector— whaton_callacceptsformat_call_lineandformat_call_summary— the renderers- Logging — the
charterlogger and its levels