ADR-100: Specialized usage recorded by tagged extractors in the pipeline
- Status
-
Accepted
- Date
-
2026-07-21
- Authors
-
Netresearch DTT GmbH
Context
ADR-097 routed the specialized dispatches through the pipeline,
but each service still recorded its own usage by calling
Usage directly after the dispatch — a
second write path alongside UsageMiddleware, which records the
token-shaped chat / embedding / vision responses. The specialized responses are
not token-shaped (they measure images, characters, audio seconds), so
Usage skipped them and the service filled the gap itself. Two
recorders, two places to keep the request-count and attribution rules
consistent.
Decision
A specialized service no longer writes usage. Before dispatch it attaches a
Specialized — the stable, dispatch-independent inputs it knows
(model, resolved model / configuration uid, attribution uid, and the input-
derived counters: characters, size, quality, batch size) — to the call-context
metadata. A tagged Usage (one per service, matched on
operation and provider so DALL·E and FAL do not collide) reads that intent
together with the raw response and returns a Provider.
Usage writes it, as the single recorder, after the token path finds
nothing.
The response supplies what the service could not know up front: DALL·E's
gpt-image token object and the number of images returned, Whisper's audio
duration (verbose_ only). Cost is computed in the extractor from the
Specialized exactly as the service did.
A service records usage iff it set an intent. DeepL's language-detection
sub-call and the get / get metadata calls
(ADR-099) set none, so the extractor returns null and nothing is
recorded — the former double-count guard is now structural.
Consequences
- One write path for every AI call:
Usagerecords both the token- shaped responses and the specialized operations. The services drop their directMiddleware trackcalls (andUsage () Dall/EImage Service:: track Image Usage () Whisperare gone).Transcription Service:: track Transcription Usage () Usagegained an autowired iterator of extractors; with none tagged its behaviour is unchanged.Middleware Providerrecords nothing (no extractor claims it).Operation:: Metadata - The recorded rows are unchanged — same service type, provider, metrics, cost,
model / configuration uid and attribution — verified end-to-end: the service
tests now drive a real
Usage+ the service's extractor and assert the same rows they asserted before.Middleware - Adding a specialized provider means adding one extractor tagged
nr_and setting an intent before dispatch; no service touches the usage table.llm. usage_ metrics_ extractor