ADR-097: Specialized services dispatch through the shared pipeline
- Status
-
Accepted
- Date
-
2026-07-20
- Authors
-
Netresearch DTT GmbH
Context
The specialized services — DALL·E, FAL, Whisper, TTS, DeepL — dispatch HTTP
directly and bypass the middleware pipeline, so they get none of what a chat
call gets: no telemetry row, no correlation id, no circuit breaker, no uniform
error classification. ADR-096 made that reachable by moving the
configuration onto the call context, so a caller without an Llm
entity can now drive the pipeline through a for context.
Decision
Abstract takes the Middleware as a required
dependency and exposes `run`, which runs the actual HTTP dispatch as the pipeline terminal. Each
service builds a Provider
and wraps its dispatch in run.
For a service context (no configuration entity, no budget metadata) most
middleware self-disable: fallback has no chain, cache and idempotency have no
key, the guardrail passes a non-completion result through, and the budget
middleware is inert because the per-call budget is still enforced by
enforce before dispatch. What the pipeline adds is a telemetry row
with a correlation id and the provider circuit breaker — a flapping image
or speech endpoint now trips and fails fast like a chat provider.
All five services are migrated: DALL·E (generate/generate-multiple/
variations/edit), FAL (generate/generate-multiple), Whisper
(transcribe/transcribe-from-content/translate-to-English), TTS (synthesize)
and DeepL (translate/translate-batch). Each wraps its dispatch in
run with a for context labelled by its operation
(Provider / Transcription / Speech
/ Translation).
Consequences
- Breaking:
Abstractgained a requiredSpecialized Service Middlewareconstructor parameter (afterPipeline budget). A subclass or manual construction must pass it; an emptyService Middlewareis a valid pass-through for tests that do not exercise the lifecycle.Pipeline ( []) - DALL·E calls now write a telemetry row (operation
image, the provider and model, a correlation id) and are guarded by the circuit breaker. No other behaviour changes: usage is still recorded by the service's owntrack(unifying that into a pipeline extractor is a later step), and the budget is still enforced byImage Usage () enforcebefore dispatch — the budget middleware stays inert for a service context, so there is no double check.Budget () Classes/now depends onSpecialized Classes/. That is the point — the specialized services join the shared lifecycle rather than reimplementing it.Provider/ Middleware - Deferred, each its own step: applying input-guardrail screening to the
specialized prompts; the fail-closed dispatch
seam that makes a forgotten lifecycle wrapper throw rather than spend
unmetered (it can only be switched on once all five services route through
run); and folding the per-service usage recording into a tagged pipeline extractor.Lifecycle ()