feat(anthropic): prompt caching via inline cache_control markers#2629
Open
hb-cam wants to merge 1 commit into
Open
feat(anthropic): prompt caching via inline cache_control markers#2629hb-cam wants to merge 1 commit into
hb-cam wants to merge 1 commit into
Conversation
The Anthropic provider sent no cache_control at all, so every call paid full input price on content the engine resends verbatim: fact extraction reuses the same system prompt across every chunk, and the reflect agent loop resends the entire growing conversation on each of its (up to HINDSIGHT_API_REFLECT_MAX_ITERATIONS) iterations. Anthropic cache reads bill at ~10% of base input price. Implement the "inline-marker provider" strategy that LLMInterface.get_or_create_cached_prefix already documents for Anthropic — no engine changes, no new config: - call() and call_with_tools() render the system prompt as a block list with a cache_control breakpoint (a prefix match, so tools + system cache together); schema text-injection happens before marking and lands inside the cached block. - call_with_tools() additionally marks the final message content block, so each agent-loop request's end-marker becomes the next iteration's cache read point. 2 of the 4 allowed breakpoints used. Marking is safe unconditionally: below the model's minimum cacheable prefix the marker is silently ignored (no write premium), and cache_read_input_ tokens already flows through _usage_from_anthropic_response into metrics. One existing assertion updated for the representation change (test_non_strict_keeps_text_injection_fallback checked a substring on system as a string; the schema-in-prompt behavior itself is unchanged and still covered). 5 new tests pin the marker placement on both entry points. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds Anthropic prompt caching to
AnthropicLLMvia inlinecache_controlmarkers — the "inline-marker provider" strategy thatLLMInterface.get_or_create_cached_prefix's docstring already describes for Anthropic. No engine changes, no new config.Today the provider sends no
cache_controlat all, so every call pays full input price on content the engine resends verbatim:HINDSIGHT_API_REFLECT_MAX_ITERATIONS), re-paying the full prefix each time.Anthropic cache reads bill at ~10% of the base input price, so on reflect-heavy workloads (mental-model refresh) this is a large input-cost reduction with no latency penalty.
How
Two breakpoints (of the four Anthropic allows):
cache_control: {type: "ephemeral"}. Caching is a prefix match, so this caches tools + system together. Schema text-injection (the non-strictresponse_formatpath) happens before marking and lands inside the cached block.call_with_tools()only — each agent-loop request's end-marker becomes the next iteration's cache read point. String content converts to a text block (skipped for empty strings, which the API rejects); block-list content (tool results) gets the marker on its last block.Marking is safe unconditionally: below the model's minimum cacheable prefix the marker is silently ignored — no write premium, no error.
cache_read_input_tokensalready flows through_usage_from_anthropic_responseinto metrics/tracing, so cache effectiveness is observable immediately viacached_tokens.Testing
tests/test_anthropic_prompt_caching.py(written first, watched fail): marker placement on both entry points, schema-injection landing inside the cached block, tool-result block-list handling, and the no-system passthrough.test_non_strict_keeps_text_injection_fallbackchecked a substring onsystemas a plain string; the schema-in-prompt behavior itself is unchanged and still covered.test_anthropic_structured_tool_use.py,test_llm_extra_body.py, andtest_mental_model_delta.pypass;ruffandtyclean.🤖 Generated with Claude Code