BASELLM ADAPTER · RESPONSE SHAPE

BaseLlm interface: normalize a provider response shape

A vendor renames its response fields mid-run. See whether your BaseLlm adapter maps them, leaks the raw JSON to callers, or silently returns blanks.

Computed experiment · sixty inspection seconds

Your experiment

A custom BaseLlm adapter maps a fictional vendor’s v1 response into LlmResponse: text into content(), the finish value into finishReason(), token counts into usageMetadata(). From call 5 the vendor’s default becomes v2, which renames all three. Three callers depend on the result: the agent flow, a cost meter and a safety handler. Watch what breaks, then map v2, fail strictly, pass raw JSON or pin the version.

Play replays the computed run over sixty inspection seconds; Next event steps, Restart rewinds, and Inspection time scrubs. Any input change recomputes the run and rewinds it without autoplay.

Event trace

    Computed data

    Final metrics

    Fictional vendor and adapter. In ADK Java, BaseLlm.generateContent(LlmRequest, boolean stream) returns a Flowable of LlmResponse whose accessors return Optionals: content(), finishReason(), usageMetadata() and others. Transport failures arrive as Flowable.error. Call 3 stops at the token limit and call 6 is filtered for safety. No model or vendor API is called.

    One adapter between a vendor and the runtime

    A BaseLlm subclass turns a vendor response into LlmResponse, so the agent flow reads content(), the cost meter reads usageMetadata() and the safety handler reads finishReason() without knowing the vendor. Vendor API version decides when the fictional vendor’s response changes from v1 (output_text, finish, usage.input/output) to v2 (content[].text, stop_reason, usage.prompt_tokens/completion_tokens). Adapter mapping, Unmapped fields and Pin API version v1 in requests decide what the adapter does with it.

    The opening run: silent drift

    The adapter maps v1 fields only and leaves unknown ones empty. Calls 1 to 4 are correct, including call 3’s MAX_TOKENS finish. From call 5 every response is v2: content() is empty, so three users get blank answers; usageMetadata() is missing, so 4 calls (2,070 tokens) go unmetered; and call 6’s content_filter is not mapped to SAFETY, so the safety handler never fires. Nothing threw an exception, so nothing alerted: 4 of 8 calls correct, 8 silent problems.

    Map, fail loudly or pin

    Set Adapter mapping to Map v1 and v2 fields: all 8 calls are correct and every caller sees the same shape. Set Unmapped fields to Fail the call with the v1-only mapping: calls 5 to 8 fail with an unmapped-response error, users see errors, but the drift is caught at the adapter on the first v2 response instead of in next month’s invoice. Tick Pin API version v1 in requests: the vendor keeps answering v1 and all 8 calls are correct, until v1 is retired; with v1 retired the pin is ignored and all 8 calls drift.

    Failure boundary: leaking the vendor shape

    Set Adapter mapping to Pass raw vendor JSON to callers with v1 throughout: every call works, but the verdict is LEAKY ABSTRACTION, because the agent flow, the meter and the safety handler each parse vendor fields themselves. Switch Vendor API version back to the mid-run change: the same 8 silent problems appear, now in three places instead of one, and strict handling cannot help because the adapter no longer looks at the fields. Normalising in one adapter is what makes a single fix possible.

    Reading the run and its limits

    Play replays the adapter setup, eight calls and the outcome over sixty inspection seconds; the table shows each call’s vendor version, true finish and what content(), usageMetadata() and finishReason() returned. Next event, Restart, Speed and Inspection time control the replay, and any input change recomputes it. The vendor and its field names are fictional; real adapters also handle streaming chunks, tool calls and transport errors. Pin the conversion in tests with recorded responses of every version you accept.