RFC: prompt caching needs a breakpoint in the right place, not just an exposed flag #2779
Replies: 3 comments
|
Two corrections to my own list, plus one thing I missed. All checked against source. Item 4 is already done. Item 2 needs more than hoisting. pr-agent builds a single system message from a plain string, and the hook sets The easy win is Two things temper that. A single-PR run writes four entries and reads none, since Update: I have edited the RFC body above to fold these corrections into the proposal itself, so it now reads as one proposal rather than proposal plus errata. The issue edit history has the before and after if you want to see exactly what changed. |
|
On the break-even arithmetic in The case against — the 21.7% / 52.6% thresholds fall out of I keep a table that re-reads OpenRouter's catalog daily. Of the 40 non-batch models on it today, every one publishes a cache-read price, and as a fraction of list input those come out:
Through the same formula,
So the numbers in the RFC are the floor rather than the typical case: 19 of the 40 need a higher hit rate than stated — up to 3.4 points more at the 5-minute TTL and 4.7 at the hour. That doesn't change the conclusion for Anthropic itself, which is 0.1. But PR-Agent reaches models through litellm, and on a route like Grok 4.6 (0.250) or Kimi K2.7 Code (0.254) the "quiet repo pays the write premium for nothing" region is meaningfully wider than the note implies — worth a sentence if the doc lands as written. One assumption I expected to break and it doesn't: for the 8 models on the table that publish a separate long-context tier, the cache-read ratio is identical either side of the step — Grok 4.20 is 0.160 below 200k and 0.160 above, Gemini 3.1 Pro Preview 0.100 / 0.100, GPT-5.5 0.100 / 0.100. The absolute bill steps, the break-even hit rate does not. So a PR big enough to push the prompt past 200k needs no separate arithmetic, which is one fewer case for item 5 to document. To check any of it: curl -s https://cdn.jsdelivr.net/gh/xyzs996/llm-api-pricing@main/data/prices.csvColumns are |
|
Closing this as duplicated and completed. The same RFC exists as issue #2601, which is where the work was actually tracked, and that closed as completed on 23-08-2026. One item from here is still live and has its own issue: the model gate cannot match a Bedrock application inference profile, since it tests |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
On Anthropic and Bedrock, prompt caching needs an explicit cache breakpoint. #2610 landed the config pass-through that lets one be declared, which was the right first step.
On its own it wins little, because the cacheable region depends on where shared content sits in the prompt and on where a breakpoint can physically land. And as merged it cannot be switched on at all by callers addressing Claude through a Bedrock application inference profile. There is one exception, described below, that is worth taking regardless.
Revised 12-08-2026: #2610 landed the pass-through, so the old item 1 is done; added the model-gate item in its place. See the edit history for what changed.
Where a breakpoint can land
PR-Agent builds a single system message from a plain string:
Given a string, litellm's
AnthropicCacheControlHooksetscache_controlon the message as a whole; given a list it sets it on the last content block. The injection point'sindexselects a message, not a content block. So the breakpoint always lands at the end of the system prompt, and a single system message cannot be split.One consequence is already correct: the diff sits in the user message, after the breakpoint, so the variable content is outside the cached region with no work needed.
The other consequence is that the cached prefix is the whole system prompt, so the cache key is whatever that prompt varies on.
What that means per tool
A single-chunk run makes four calls, not three, because each
/improvechunk runs a suggestions call plus a mandatory self-reflect call./describerepo_context,skills_context,extra_instructions,custom_labels_class/reviewnum_pr_files/improvesuggestionsnum_code_suggestions/improvereflectThe reflect prompt is the interesting one. It interpolates no variables, so it is a static block of roughly 1.7k tokens that is byte-identical for every repo and every PR. It is the only globally shared prefix in the pipeline, it clears the 1,024 token minimum on the models that matter, and it is cacheable today with no prompt restructuring at all.
/reviewis the opposite case.num_pr_filesmoves its prefix on nearly every PR, so its cache entry is written and almost never read.The placement problem
For the other three calls, tool-specific text comes first in the prompt files and
skills_context/extra_instructionsare interpolated after it. Two consequences:/describe,/reviewand/improvediverge in the first few lines, and caching is prefix-based. Same conclusion as [Feature Request] Prompt caching #1353, and correct for the current layout.Hoisting the shared context into a leading block is necessary but not sufficient, because of the end-of-message constraint above: reordering leaves the breakpoint downstream of both the shared block and the tool-specific text. Getting a genuine cross-tool prefix needs either the system content emitted as structured content blocks with
cache_controlon the shared one, or the tool-specific text moved out of the system message.Landed, with one gap
#2610 merged the pass-through on 08-08-2026, superseding #2405. It takes a native TOML array, so the
TypeErrorfromjson.loadson a Dynaconf list is gone; it resolves and validates the value ahead of the retry wrapper, so a malformed config raisesValueErrorinstead of being wrapped as anAPIErrorand retried; it usessetdefaultrather than overwriting kwargs; and it ships unit tests.The gap is the gate. The kwarg is only passed when
"claude" in model.lower(). Callers addressing Claude through a Bedrock application inference profile passbedrock/converse/arn:aws:bedrock:<region>:<account>:application-inference-profile/<id>, which contains noclaude, so the feature silently no-ops and logs a debug line. That is the same failure mode as item 5 below, and astemperaturehandling before it: a capability gated on a model name that an opaque profile ARN can never satisfy. Gating on the provider prefix, or honouring an explicit opt-in when the config is set, would fix it.To do
is_claude_4_5_on_bedrockno longer uses a hardcoded name list; since #31929 it readscache_creation_input_token_cost_above_1hrfrom the pricing JSON, so Sonnet 5 and Opus 5 pick up the 1h TTL from litellm 1.93.0 andrequirements.txtis past that. The gap left to document is that the lookup needs a resolvable model name, so callers addressing Bedrock through an application inference profile ARN get an opaque id with no pricing entry and silently fall back to 5m. Nothing in feat(litellm): pass-through cache_control_injection_points for Anthropic prompt caching #2610 mentions TTL, so this is still open.The case against
Caching is not free. Writes cost 1.25x at 5 minutes and 2x at 1 hour, reads cost 0.1x, so break-even is a 21.7 or 52.6 per cent hit rate. Below that this raises the bill, and a user enabling it on a quiet repo gets no signal.
A single PR run writes four entries and reads none, since the four system prompts all differ. Reads only arrive from later runs. Chunked suggestions calls do not help each other either:
parallel_callsdispatches them throughasyncio.gather, and a cache entry only becomes available once the first response begins, so concurrent siblings each write their own.Item 3 also reorders prompt content, which carries a quality risk worth testing rather than assuming.
To be clear, #1353 asked to cache the diff across commands. That is still infeasible and this does not revive it. Only the leading shared context, and the static reflect prompt, become cacheable.
Items 1 and 2 are both small and self-contained. Happy to send a patch for either; item 3 is the one I would rather agree first.
All reactions