Class BetaRefusalFallbackInterceptor

  • All Implemented Interfaces:
    com.anthropic.core.http.Interceptor

    
    public final class BetaRefusalFallbackInterceptor
     implements Interceptor
                        

    An Interceptor that retries refused beta /v1/messages requests down a fallback chain.

    Only requests made through the beta API surface (client.beta().messages()) are handled; non-beta client.messages() requests pass through untouched.

    When a non-streaming response comes back with stop_reason: "refusal", the request is retried with each entry of the fallback chain applied over the original params — passing along the refusal's fallback_credit_token, which refunds the retry's cache-miss cost — until a model accepts or the chain is exhausted. A message served by a fallback retry mirrors the server-side stitched envelope: one fallback content block per model boundary (from: the model that refused, as the caller spelled it; to: the next entry's model) is prepended to the serving hop's content.

    When a streaming response ends in stop_reason: "refusal", a second request is issued to the fallback model — carrying the refused model's partial output as a trailing assistant prefill when the refusal grants one (fallback_has_prefill_claim), plus the refusal's fallback_credit_token — and the fallback's events are spliced onto the still-open stream, so the client sees one continuous message in the server-side fallbacks wire shape: a fallback content block at each model boundary, monotonic block indices, and per-hop usage.iterations on the final message_delta. Only model is honored from each entry on this path: the credit token is redeemable only against the refused request's body, so the other per-entry overrides (max_tokens, thinking, ...) would be rejected.

    The fallback-credit beta the credit tokens require is sent by default on every request the interceptor handles — the original request included, since refusals only carry a fallback_credit_token when the beta is enabled; the Builder.betas option controls this.

    In both modes a fallback that itself refuses with a fresh credit token continues down the chain. A streaming fallback whose prefill the server rejects (HTTP 400) is retried once without it; a streaming fallback whose request fails outright is skipped — its token was never redeemed, so it carries to the next entry. A streaming refusal with no fallback_credit_token to retry is surfaced to the client and reported once per interceptor with a warning on System.err; one that exhausts the chain is surfaced silently — its terminal message_delta already reports every hop.

    To keep later requests on the model that accepted, pass a BetaFallbackState via the RequestOptions.Builder.fallbackState request option; requests sharing that state start directly at the pinned fallback. Reuse one state across whatever scope the pin should apply to — typically a conversation.

    Example:

    AnthropicClient client = AnthropicOkHttpClient.builder()
        .fromEnv()
        .addInterceptor(BetaRefusalFallbackInterceptor.builder()
            .addFallback(Model.CLAUDE_OPUS_4_8)
            .build())
        .build();
    
    BetaFallbackState fallbackState = BetaFallbackState.create();
    BetaMessage message = client.beta().messages().create(
        params, RequestOptions.builder().fallbackState(fallbackState).build());