Skip to content

cache_control for LiteLLM-routed providers #3008

Description

@Jash271

Please read this first

Describe the feature

Add a first-class ModelSettings.cache_system_prompt: bool | None flag. When set to True, the leading role=system message is emitted as a structured text content part tagged with cache_control: {"type": "ephemeral"} — the exact shape LiteLLM documents for Anthropic native, Bedrock, OpenRouter → Claude/Gemini, and Vertex AI. Those providers honor the marker as a prompt-caching breakpoint; subsequent calls with an identical prefix skip re-tokenization and bill at the cached rate.

Why this is needed

Long, stable system prompts are the common shape in Agents SDK apps, and for LiteLLM-routed providers the missing cache_control marker is the single biggest lever on input-token cost.

Today there is no SDK-side way to emit it:

  1. Two sites in src/agents/models/chatcmpl_converter.py (_normalize_input_content_part_alias and extract_all_content) reconstruct text parts with only type and text, silently dropping any extra key. So even hand-written call_model_input_filter hooks that attach cache_control fail — the marker is stripped before reaching LiteLLM.
  2. There is no ergonomic opt-in on ModelSettings. Per the closing comment on Support Anthropic Prompt Caching #905, the only existing workaround is to fork LitellmModel into a full custom-model implementation — heavyweight, brittle, and every consumer reinvents the same wrapper.

Empirical impact

Measured against a ~36k-char production system prompt via OpenRouter → Gemini 2.5 Pro with a patch applied: turn 2 reports 9,499 cached_tokens out of 9,544 prompt_tokens with the flag on, and 0 with it off.

Proposed API

Agent(
    instructions="<long system prompt>",
    model_settings=ModelSettings(cache_system_prompt=True),
)

The field sits alongside the existing prompt_cache_retention so the two caching paths have symmetric surface area. Default is None: when unset, all three Chat Completions-style paths (OpenAIChatCompletionsModel, LitellmModel, AnyLlmModel) produce byte-identical payloads to today — no behavior change for existing callers.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions