Skip to content

Add Anthropic prompt-cache clear-then-finalize - #280

Merged
Zongwei9888 merged 2 commits into
HKUDS:mainfrom
raymondginger2018-sudo:pr/anthropic-cache
Oct 28, 2026
Merged

Zongwei9888 merged 2 commits into
HKUDS:mainfrom
raymondginger2018-sudo:pr/anthropic-cache

Conversation

@raymondginger2018-sudo

@raymondginger2018-sudo raymondginger2018-sudo commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Hardens Anthropic prompt-cache marker handling: the message breakpoint is written clear-then-finalize — strip every stale cache_control from the converted conversation, then place ONE fresh marker on the latest non-system message (instead of the hardcoded messages[-2] write point, which missed the frontier whenever the tail shape changed).

Changes

  • core/providers/anthropic.py (+83, the only production file):
    • _strip_cache_control — remove stale markers from a message dict and from any content-block payload (string or block-list form).
    • _place_cache_control — copy a message and mark its last block; wraps plain-string content into a single marked text block; image-only/empty content left unchanged.
    • The caching path now clears the converted list, then walks backwards past system messages to mark the true conversation frontier.
  • tests/test_anthropic_cache_control.py — rewritten: every test drives AnthropicProvider._build_kwargs (the production entry, which runs the provider's own _convert_messages) and asserts on the converted Anthropic-format payload inside kwargs["messages"].

Tests

  • tests/test_anthropic_cache_control.py — 8 passed on this branch. On main (cd7ab47d): 3 failed / 5 passed — the three failures are exactly the frontier-placement cases (latest-message marker, trailing system note, stale-marker strip); the five that pass on both revisions are invariant guards (marker budget ≤ 4, tool-marker positions, string-system wrap, disabled-cache path, tools-absent path) that pin behavior the change must not disturb.
  • Provider batch: 117 passed.
  • Lint/format parity with main: ruff check and ruff format --check show zero delta (the 19 ruff findings are pre-existing on both sides).
  • pre-commit (ruff 0.15.21 + ruff-format + whitespace hooks) passed at commit time.

Notes

  • Review response: the earlier cacheHint annotation and the agent_setup block-list system prompt are fully dropped. core/agent_setup.py, core/providers/openai_compat.py and core/providers/openai_responses/converters.py are byte-identical to main; no cacheHint reference remains anywhere in the diff.

@Zongwei9888

Copy link
Copy Markdown
Collaborator

Thanks @raymondginger2018-sudo for digging into our prompt caching. The message-breakpoint change is a genuine improvement: moving it from messages[-2] to the last block of the last message matches Anthropic's documented multi-turn pattern, and we'd like to take it.

The agent_setup split we can't merge as is. system_preamble() is never empty (it always carries the memory-usage text), so every run now builds a block-list system prompt, and every OpenAI-compatible provider (DeepSeek, GLM, DashScope, Ollama...) receives messages[0].content as an array instead of a string. DeepSeek's API reference lists system content as a string. The text the model sees also changes: the skills block loses its \n\n separator, and the Responses path joins with four newlines. On Anthropic, the stable breakpoint covers only about 230 tokens beyond the tool breakpoint, and after /compact it re-writes more than main does.

What we'd accept:

  1. The frontier change on its own, with a test that goes through _build_kwargs using converted messages (the current tests pass OpenAI-format messages that _build_kwargs never produces, and 10 of the 16 pass on main unchanged).
  2. No change to the system prompt type in agent_setup, and no cacheHint plumbing.
  3. Comments that don't reference ZCode or a NameError regression this repo never had.
  4. The unrelated openai_compat reformatting dropped.

Happy to review a slimmed-down version. Thanks again!

…nt blocks

- AnthropicProvider: strip stale cache_control markers, then place the
  single latest marker (budget 4, type ephemeral) on the newest non-system
  message before send; non-caching path strips system cache hints too.
- agent_setup: emit block-list system prompts carrying cacheHint stable/
  dynamic annotations (never leaked to any API).
- openai_compat: _sanitize_messages walks content blocks stripping
  cacheHint before request build.
- openai_responses: converter joins text blocks and strips cacheHint.

All new behavior is opt-in/annotation-driven; default flows unchanged.
Address review on PR HKUDS#280:

- The stable/dynamic cacheHint split is gone: the system prompt stays
  flat again, and the marker-stripping hooks in the OpenAI-compatible
  provider and the Responses converters are removed with it.
- The anthropic clear-then-finalize change stays: strip every stale
  marker from the replayed list, then place one fresh marker on the
  latest non-system message instead of a hardcoded messages[-2].
- The cache tests now drive _build_kwargs with converted messages
  instead of reaching into _prepare_request_kwargs, and the legacy
  cacheHint tests are deleted.
@raymondginger2018-sudo raymondginger2018-sudo changed the title Add Anthropic prompt-cache clear-then-finalize + cacheHint stripping Add Anthropic prompt-cache clear-then-finalize Oct 27, 2026
@raymondginger2018-sudo

Copy link
Copy Markdown
Contributor Author

Thanks @Zongwei9888 — slimmed down exactly as requested; pushed as 68dfed87 (force-push, branch rewritten). Point by point:

1. Frontier change on its own, tests through _build_kwargs with converted messages

Net diff vs main is now exactly two files: core/providers/anthropic.py (+83 — clear-then-finalize only) and a rewritten tests/test_anthropic_cache_control.py. Every test calls AnthropicProvider._build_kwargs — the production entry that runs the provider's own _convert_messages — and asserts on the converted Anthropic-format payload in kwargs["messages"], so nothing inspects the OpenAI-shaped input structures _build_kwargs never emits.

Discrimination vs main: on cd7ab47d the file runs 3 failed / 5 passed — the three failures are exactly the frontier cases (test_frontier_marker_lands_on_latest_message, test_trailing_system_note_does_not_steal_the_frontier, test_stale_markers_are_stripped_and_one_frontier_marker_remains). The five that pass on both revisions are invariant guards (marker budget ≤ 4, tool-marker positions, string-system wrap, disabled-cache path, tools-absent path) that pin behavior the change must not disturb — they are not claimed as evidence of the change. On this branch: 8/8 green; provider batch 117 passed.

2. No agent_setup system-prompt type change, no cacheHint plumbing

core/agent_setup.py, core/providers/openai_compat.py and core/providers/openai_responses/converters.py are byte-identical to main again. tests/test_cache_hint_system_blocks.py is deleted, and no cacheHint reference remains anywhere in the diff.

3. Comments

No ZCode or NameError references — git diff main is clean of both.

4. Unrelated openai_compat reformatting dropped

Same as (2): that file no longer appears in the diff.

Local checks on 68dfed87: pre-commit (ruff + ruff-format + whitespace hooks) passed at commit time; ruff check and ruff format --check show zero delta vs main (the 19 ruff findings are pre-existing on both sides).

PR title and description updated to match the slimmed scope. Happy to adjust further.

@Zongwei9888
Zongwei9888 merged commit a9905c4 into HKUDS:main Oct 28, 2026
12 checks passed
@Zongwei9888

Copy link
Copy Markdown
Collaborator

Thanks @raymondginger2018-sudo, merged as a9905c4. The breakpoint now sits on the latest message and the tests go through _build_kwargs, which is exactly what we were after. Thanks for the quick turnaround.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants