Repository navigation
fix(bedrock): stop emitting Converse cachePoint blocks for Kimi K3 - #44292
Conversation
Bedrock prices Kimi K3 cache reads through implicit caching but Converse
rejects the explicit cachePoint marker ("This model doesn't support the
cachePoint field"), so any cache_control on the request answered 400.
Mark the three K3 rows supports_prompt_cache_breakpoint: false and have
bedrock_model_accepts_cache_points honor that flag before falling back to
supports_prompt_caching, keeping cached-token pricing intact.
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
bugbot run |
|
bugbot run |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 744961c. Configure here.
TLDR
Problem this solves:
cachePointfor Kimi K3 ("This model doesn't support the cachePoint field")cache_controlon a request tobedrock/*.moonshotai.kimi-k3answered HTTP 400How it solves it:
supports_prompt_cache_breakpoint: falsesupports_prompt_cachingsupports_prompt_cachingstays true, so cached-token pricing is unchangedmodel_info.supports_prompt_cache_breakpoint: falsecovers an inference profile ARN or a model newer than the map, configured asbedrock/<model>orbedrock/converse/<model>; like everymodel_infofield it applies to every deployment of that model stringUser Flow
Before: a developer whose client marks prompts cacheable gets a 400 from every Kimi K3 request
bedrock/us.moonshotai.kimi-k3askimi-k3to the model list, plus the same model behind an application inference profile ARN askimi-k3-profilewithmodel_info: {supports_prompt_cache_breakpoint: false}, and starts the proxy"model": "kimi-k3"(or"kimi-k3-profile") and a system, user, or tool block carrying"cache_control": {"type": "ephemeral"}(or the same marker on an input block of POST https://litellm-domain/v1/responses)BedrockException - {"message":"This model doesn't support the cachePoint field. Remove cachePoint and try again."}ANTHROPIC_BASE_URLandANTHROPIC_MODEL=kimi-k3-profileshowsAPI Error: 400with that same message on every turn, so they have to strip caching from their client to use the modelAfter: the same requests answer 200 and the client keeps its caching markers
bedrock/us.moonshotai.kimi-k3askimi-k3to the model list, plus the same model behind an application inference profile ARN askimi-k3-profilewithmodel_info: {supports_prompt_cache_breakpoint: false}, and starts the proxy"model": "kimi-k3"(or"kimi-k3-profile") and a system, user, or tool block carrying"cache_control": {"type": "ephemeral"}(or the same marker on an input block of POST https://litellm-domain/v1/responses)usagestill reports Bedrock's cached-token countsANTHROPIC_BASE_URLandANTHROPIC_MODEL=kimi-k3-profileanswers every turn, and withANTHROPIC_MODEL=kimi-k3it keeps working as it did beforeLinear ticket
Resolves LIT-9078
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/unit/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Shared setup for both legs, each in its own worktree with its own venv, proxy booted with
--num_workers 2 --detailed_debug, the local cost map, and no database, every call reaching real Bedrock in us-east-1 ($PORTis the leg's random port,$LITELLM_MASTER_KEYthe proxy master key). The three ARNs are three separate application inference profiles overus.moonshotai.kimi-k3, so each deployment is its own model string:Claude Code v2.1.288 is driven in a real tmux session with
ANTHROPIC_BASE_URLat the proxy; the same eleven cases run in the same order on both sides, only the proxy commit differsBefore (a292fd4)
A1. Claude Code with
ANTHROPIC_MODEL=kimi-k3(control)A2. Claude Code with
ANTHROPIC_MODEL=kimi-k3-profile(the flagged inference profile)B. POST /v1/chat/completions,
kimi-k3, cache_control on the system and user blocksC. POST /v1/chat/completions,
kimi-k3, cache_control on a tool definitionD. POST /v1/messages,
kimi-k3, cache_control on the system and user blocks (control)D2. POST /v1/messages,
kimi-k3-profile, the same requestE. POST /v1/responses,
kimi-k3, cache_control on an input blockF. POST /v1/chat/completions,
kimi-k3, no cache_control (control)G. POST /v1/chat/completions,
kimi-k3-profile(flagged ARN)H. POST /v1/chat/completions,
kimi-k3-profile-unflagged(ARN without model_info)I. POST /v1/chat/completions,
kimi-k3-profile-converse(bedrock/converse/ARN, flagged)After (5de008b)
A1. Claude Code with
ANTHROPIC_MODEL=kimi-k3(control)A2. Claude Code with
ANTHROPIC_MODEL=kimi-k3-profile(the flagged inference profile)B. POST /v1/chat/completions,
kimi-k3, cache_control on the system and user blocksC. POST /v1/chat/completions,
kimi-k3, cache_control on a tool definitionD. POST /v1/messages,
kimi-k3, cache_control on the system and user blocks (control)D2. POST /v1/messages,
kimi-k3-profile, the same requestE. POST /v1/responses,
kimi-k3, cache_control on an input blockF. POST /v1/chat/completions,
kimi-k3, no cache_control (control)G. POST /v1/chat/completions,
kimi-k3-profile(flagged ARN)H. POST /v1/chat/completions,
kimi-k3-profile-unflagged(ARN without model_info)I. POST /v1/chat/completions,
kimi-k3-profile-converse(bedrock/converse/ARN, flagged)Observed alongside the run, from the legs' responses and proxy logs:
kimi-k3name answers on both legs:/v1/messagesalready drops cache markers for a model name it does not recognize as Claude, so the 400 the customer hits needs an ARN deployment (A2, D2), which keeps the markers. Their 1.98.x line predates that drop, so plain names 400 there too/v1/messagescalls for A2, three 400 and one 200. After, two calls, both 200, the same as A1kimi-k3-profile-unflagged(H) still 400s on both legs: an ARN carries no model name, so withoutmodel_info.supports_prompt_cache_breakpoint: falsethe gateway has nothing to go on/v1/messagesand/v1/responsesreplies carry a thinking block with an empty signature, and/v1/messagesusage has nocache_*fields for Kimi K3 (pre-existing on both legs, left alone)Last updated: tip 744961c, which only adds the audit's integration test module; the proof, /live-pr-risk, and /audit all ran at 5de008b, the last commit with a product diff
Audit round 1 of 3 (head 5de008b, merge base a292fd4, tests commit 744961c)
Every cell is a checked-in integration test in
tests/integration/providers/test_bedrock_kimi_k3_cache_point_wire.py, run withtests/integration/run.py providersagainst a two-worker proxy with real Postgres and Redis; the only double is a local HTTP peer standing in for the Bedrock runtime that records every Converse body it receives and answers the scripted reply. "red" means the cell's expected-on-head assertion fails on the merge base, which is the proof that the cell measures the fix. Each leg ran one cell at a time (--seed 4106601 --order-seed 9078 --workers 1), the head selection twice with the same seeds. The runs:official-base-a292fd409f-20261003T040408Z(38 red, 27 green, the exact expected shape),official-head1-5de008b605-20261003T040856Z(64 green, S10/S11 red once, see below),official-head2-5de008b605-20261003T041758Z(65 green). The first head run's one red was the_model_idtest helper, not the product: it looked a just-created deployment up throughGET /model/info, which answers from the worker-local router (model_info_v1readsllm_router.model_list), while a second uvicorn worker only learns a/model/newrow on its next DB sync (add_deployment_job, or the Redis config-sync signal), and the test client's keep-alive expires after 2 s idle (GATEWAY_LIMITS), so the lookup rode a fresh connection to the worker that had not synced yet. The helper now reads the id from theLiteLLM_ProxyModelTablerow that/model/newwrote, and per the tests-only rerun rule the four cells that use it (S10/S11, X1, X6, C3) reran at the same tip and seeds asrerun-base-a292fd409f-20261003T043420Z(3 red, 1 green, as expected),rerun-head1-5de008b605-20261003T043622Zandrerun-head2-5de008b605-20261003T043946Z; those rows carry the rerun result (runcolumn), every other row keeps its official result| inv | endpoint | mode | client | scenario | node id | base | head x2 | run | verdict |
| M1 | chat | plain | httpx | YAML
bedrock/us.moonshotai.kimi-k3, 3 cache_control |test_m01_to_m05_every_kimi_k3_form_sends_converse_without_a_cache_point[m-kimi-us]| red | green green | official | PASS || M2 | chat | plain | httpx | YAML
bedrock/converse/global.moonshotai.kimi-k3|test_m01_to_m05_every_kimi_k3_form_sends_converse_without_a_cache_point[m-kimi-global-converse]| red | green green | official | PASS || M3 | chat | plain | httpx | YAML
bedrock/moonshotai.kimi-k3|test_m01_to_m05_every_kimi_k3_form_sends_converse_without_a_cache_point[m-kimi-base]| red | green green | official | PASS || M4 | chat | plain | httpx | YAML
bedrock/us-east-1/us.moonshotai.kimi-k3|test_m01_to_m05_every_kimi_k3_form_sends_converse_without_a_cache_point[m-kimi-regional]| red | green green | official | PASS || M15 | chat | plain | httpx | YAML ARN,
model_infoflag false |test_m01_to_m05_every_kimi_k3_form_sends_converse_without_a_cache_point[m-arn-yaml-flagged]| red | green green | official | PASS || M9 | chat | plain | httpx | Claude control keeps 3 cachePoints |
test_m08_m09_models_that_take_cache_points_still_get_all_three[m-claude-control]| green | green green | official | PASS || M16 | chat | plain | httpx | YAML ARN without model_info keeps 3 |
test_m08_m09_models_that_take_cache_points_still_get_all_three[m-arn-yaml-plain]| green | green green | official | PASS || M10 | chat | plain | httpx | gpt-oss control, never had cachePoints |
test_m10_m11_models_without_caching_never_got_cache_points[m-gpt-oss-control]| green | green green | official | PASS || M11 | chat | plain | httpx | llama4 control, never had cachePoints |
test_m10_m11_models_without_caching_never_got_cache_points[m-llama-control]| green | green green | official | PASS || M14 | chat | plain | httpx | wildcard
bedrock/*, Kimi strips, Claude keeps |test_m14_a_wildcard_deployment_resolves_the_caller_model_before_deciding| red | green green | official | PASS || E1 | chat | plain | httpx | Kimi, cache_control on system, user, tool |
test_e01_to_e06_every_endpoint_reaches_kimi_without_a_cache_point[e-chat-plain]| red | green green | official | PASS || E7 | chat | stream | httpx | Kimi, same markers |
test_e01_to_e06_every_endpoint_reaches_kimi_without_a_cache_point[e-chat-stream]| red | green green | official | PASS || E8 | messages | plain | httpx | Kimi, adapter drops cache_control on both legs |
test_e01_to_e06_every_endpoint_reaches_kimi_without_a_cache_point[e-messages-plain]| green | green green | official | PASS || E8 | messages | stream | httpx | Kimi, same adapter rule |
test_e01_to_e06_every_endpoint_reaches_kimi_without_a_cache_point[e-messages-stream]| green | green green | official | PASS || E9 | responses | plain | httpx | Kimi, cache_control on input block |
test_e01_to_e06_every_endpoint_reaches_kimi_without_a_cache_point[e-responses-plain]| red | green green | official | PASS || E6 | responses | stream | httpx | Kimi, same |
test_e01_to_e06_every_endpoint_reaches_kimi_without_a_cache_point[e-responses-stream]| red | green green | official | PASS || E10 | chat | plain | httpx | Claude control keeps 2 cachePoints |
test_e07_to_e12_every_endpoint_still_sends_claude_its_cache_points[e-chat-plain]| green | green green | official | PASS || E10 | chat | stream | httpx | Claude control keeps 2 |
test_e07_to_e12_every_endpoint_still_sends_claude_its_cache_points[e-chat-stream]| green | green green | official | PASS || E10a | messages | plain | httpx | Claude rides Invoke, 2 cache_control survive |
test_e07_to_e12_every_endpoint_still_sends_claude_its_cache_points[e-messages-plain]| green | green green | official | PASS || E10a | messages | stream | httpx | Claude rides Invoke stream, 2 survive |
test_e07_to_e12_every_endpoint_still_sends_claude_its_cache_points[e-messages-stream]| green | green green | official | PASS || E10 | responses | plain | httpx | Claude control keeps 1 |
test_e07_to_e12_every_endpoint_still_sends_claude_its_cache_points[e-responses-plain]| green | green green | official | PASS || E10 | responses | stream | httpx | Claude control keeps 1 |
test_e07_to_e12_every_endpoint_still_sends_claude_its_cache_points[e-responses-stream]| green | green green | official | PASS || E12 | messages | plain | httpx | flagged ARN strips, plain ARN keeps 2 |
test_e13_e14_messages_keeps_cache_control_for_an_arn_and_the_flag_decides[e-messages-arn-plain]| red | green green | official | PASS || E12 | messages | stream | httpx | same pair, streaming |
test_e13_e14_messages_keeps_cache_control_for_an_arn_and_the_flag_decides[e-messages-arn-stream]| red | green green | official | PASS || E1 | chat | plain | OpenAI SDK sync | Kimi strips |
test_k01_openai_sdk_sync_chat_reaches_kimi_without_a_cache_point| red | green green | official | PASS || E2 | chat | stream | OpenAI SDK async | Kimi strips |
test_k02_openai_sdk_async_chat_stream_reaches_kimi_without_a_cache_point| red | green green | official | PASS || E3 | messages | plain | Anthropic SDK sync | Kimi, adapter rule, both legs strip |
test_k03_anthropic_sdk_sync_messages_reaches_the_model_without_a_cache_point[k03-kimi]| green | green green | official | PASS || E11 | messages | plain | Anthropic SDK sync | flagged ARN strips |
test_k03_anthropic_sdk_sync_messages_reaches_the_model_without_a_cache_point[k03-flagged-arn]| red | green green | official | PASS || E4 | messages | stream | Anthropic SDK async | Kimi, adapter rule, both legs strip |
test_k04_anthropic_sdk_async_stream_reaches_the_model_without_a_cache_point[k04-kimi]| green | green green | official | PASS || E11 | messages | stream | Anthropic SDK async | flagged ARN strips |
test_k04_anthropic_sdk_async_stream_reaches_the_model_without_a_cache_point[k04-flagged-arn]| red | green green | official | PASS || E5 | responses | plain | OpenAI SDK sync | Kimi strips |
test_k05_openai_sdk_sync_responses_reaches_kimi_without_a_cache_point| red | green green | official | PASS || E6 | responses | stream | OpenAI SDK async | Kimi strips |
test_k06_openai_sdk_async_responses_stream_reaches_kimi_without_a_cache_point| red | green green | official | PASS || L1 | chat | plain | httpx | system marker only: Kimi 0, Claude 1 |
test_l01_to_l03_each_marker_location_is_dropped_for_kimi_and_kept_for_claude[l-system]| red | green green | official | PASS || L2 | chat | plain | httpx | user marker only: Kimi 0, Claude 1 |
test_l01_to_l03_each_marker_location_is_dropped_for_kimi_and_kept_for_claude[l-user]| red | green green | official | PASS || L3 | chat | plain | httpx | tool marker only: Kimi 0, Claude 1 |
test_l01_to_l03_each_marker_location_is_dropped_for_kimi_and_kept_for_claude[l-tool]| red | green green | official | PASS || L4 L5 L6 | chat | plain | httpx | YAML injection points: Kimi 0, Claude 1 at system[-1] |
test_l04_l05_gateway_injection_points_are_dropped_for_kimi_and_kept_for_claude| red | green green | official | PASS || L7 | chat | plain | httpx | no markers, body untouched |
test_l06_a_request_without_markers_was_never_touched| green | green green | official | PASS || P1 | chat | plain | httpx | cache read 100, write 50: usage and cost from the Kimi row |
test_p01_kimi_cache_usage_is_still_priced_from_its_own_row| green | green green | official | PASS || P2 | chat | plain | httpx | plain Kimi reply priced from its row |
test_p02_a_plain_kimi_reply_is_priced_from_its_own_row| green | green green | official | PASS || S7 | chat | plain | httpx | 5 KB caller model: 4xx before the peer, liveliness 200 |
test_s07_a_5kb_caller_model_under_the_wildcard_is_a_bounded_4xx_and_liveliness_stays_up| green | green green | official | PASS || S8 | chat | plain | httpx | peer 400 validationException reaches the caller |
test_s08_a_peer_rejection_on_kimi_is_a_400_with_the_message_and_a_failure_row[s08-plain]| green | green green | official | PASS || S8 | chat | stream | httpx | same, streaming |
test_s08_a_peer_rejection_on_kimi_is_a_400_with_the_message_and_a_failure_row[s08-stream]| green | green green | official | PASS || S9 | chat | plain | httpx | bogus key: 401, no peer call |
test_s09_an_unauthenticated_kimi_request_never_reaches_the_peer| green | green green | official | PASS || X2 | chat | plain | httpx | two no-cache sends: two peer calls, two rows |
test_x02_two_identical_uncached_requests_are_two_peer_calls_and_two_rows| green | green green | official | PASS || X3 | chat | plain | httpx | response cache hit answers with fewer peer calls |
test_x03_a_response_cache_hit_still_answers_after_fewer_peer_calls_than_sends| green | green green | official | PASS || X5 | chat | plain | httpx | ARN with
openai.segment, pre-existing family rule |test_x05_an_arn_whose_profile_name_starts_with_openai_dot_is_classified_by_the_pre_existing_family_rule| green | green green | official | PASS || M5 | chat messages responses | plain | httpx | /model/new
bedrock/<arn>flag false |test_m05_to_m07_a_deployment_flag_false_on_an_arn_strips_cache_points_on_every_route[m05-plain-route]| red | green green | official | PASS || M6 | chat messages responses | plain | httpx | /model/new
bedrock/converse/<arn>flag false |test_m05_to_m07_a_deployment_flag_false_on_an_arn_strips_cache_points_on_every_route[m06-converse-route]| red | green green | official | PASS || M7 | chat messages responses | plain | httpx | /model/new
bedrock/converse_like/<arn>flag false |test_m05_to_m07_a_deployment_flag_false_on_an_arn_strips_cache_points_on_every_route[m07-converse-like-route]| red | green green | official | PASS || M8 | chat | plain | httpx | /model/new ARN without flag keeps 3 |
test_m08_an_arn_without_the_flag_still_gets_cache_points| green | green green | official | PASS || M12 | chat | plain | httpx | /model/new Kimi flag true overrides the row |
test_m12_a_deployment_flag_true_on_kimi_overrides_the_cost_map_row| green | green green | official | PASS || M13 X4 | chat | plain | httpx | /model/new Kimi flag null falls back to the row |
test_m13_a_null_deployment_flag_on_kimi_falls_back_to_the_cost_map_row| red | green green | official | PASS || S1 | chat | plain | httpx | flag "true" read as false |
test_s01_to_s06_an_odd_typed_flag_is_read_as_false_never_as_true[s01-string-true]| red | green green | official | PASS || S2 | chat | plain | httpx | flag 1 read as false |
test_s01_to_s06_an_odd_typed_flag_is_read_as_false_never_as_true[s02-int-one]| red | green green | official | PASS || S3 | chat | plain | httpx | flag 0 |
test_s01_to_s06_an_odd_typed_flag_is_read_as_false_never_as_true[s03-int-zero]| red | green green | official | PASS || S4 | chat | plain | httpx | flag [] |
test_s01_to_s06_an_odd_typed_flag_is_read_as_false_never_as_true[s04-empty-list]| red | green green | official | PASS || S5 | chat | plain | httpx | flag "" |
test_s01_to_s06_an_odd_typed_flag_is_read_as_false_never_as_true[s05-empty-string]| red | green green | official | PASS || S6 | chat | plain | httpx | flag 5 KB string |
test_s01_to_s06_an_odd_typed_flag_is_read_as_false_never_as_true[s06-5kb-string]| red | green green | official | PASS || S10 S11 | chat | plain | httpx | two deployments of one ARN share the flag, delete keeps it |
test_s10_s11_two_deployments_of_one_arn_share_the_flag_and_a_delete_leaves_it_in_place| red | green green | rerun | PASS || X1 | chat | plain | httpx | PATCH /model/{id}/update flips the flag to true |
test_x01_flipping_the_flag_to_true_through_a_patch_update_turns_cache_points_back_on| red | green green | rerun | PASS || X6 | chat | plain | httpx | legacy POST /model/update: 200, stored flag unchanged, body unchanged |
test_x06_the_legacy_post_update_answers_200_and_leaves_the_stored_flag_alone| green | green green | rerun | PASS || C1 | chat messages responses | mixed | httpx async | peer outage mid burst, recovery lands every id once |
test_c01_a_peer_outage_mid_traffic_fails_cleanly_and_recovery_lands_every_id_once| red | green green | official | PASS || C2 | chat | stream | httpx async | SIGKILL one worker, survivor and respawn strip |
test_c02_a_worker_killed_mid_traffic_leaves_the_survivor_and_the_respawn_stripping| red | green green | official | PASS || C3 | chat | plain | httpx | proxy restart re-registers the stored flag |
test_c03_a_restart_re_registers_a_stored_deployment_flag_from_the_database| red | green green | rerun | PASS || C4 | chat | stream | httpx async | slow peer, 10 concurrent streams, rows once |
test_c04_a_slow_peer_under_ten_concurrent_streams_completes_every_call_once| red | green green | official | PASS |Live rows V1 and V2 (real Bedrock answers the Kimi K3 request without
cachePointand rejects it with one, for the model id and for an inference profile ARN) are the QA run above at the same two commits and are not repeated hereFindings from the audit, none introduced by this PR: the legacy
POST /model/updateanswers 200 and never writesmodel_info, so asupports_prompt_cache_breakpointflip through it does not land (the Admin UI usesPATCH /model/{id}/update, which does); a truthy non-boolean flag value such as"true"or1is read as false by the gate'sis Truecheck because/model/newdoes not validate the field's typeType
🐛 Bug Fix
Caveats (if any)
Low
supports_prompt_cache_breakpointlands in the model's shared cost-map row, like everymodel_infofield, so it governs every deployment of that model string (a mapped model's built-in row included) and/model/deleteof that deployment does not withdraw it until a restart or cost-map reload. The same scope already applied to thesupports_prompt_cachingread this gate had before the PR, and the capability is a property of the model, not of a deployment, so a deployment-level read (which would need a deployment handle the Converse transformation does not have) is net-negative to add; set the flag on a model string whose deployments all need itcache_controlclient-side (DISABLE_PROMPT_CACHING=1in Claude Code) or in a pre-call hookbraces3.0.3 (GHSA-vfj7-8cjw-p6xm, dashboard dev dependency, no fix published); not from this PR, main's run 36975463472 was green on the same lockfile, and it is not a required checkcache_controlon a Kimi K3 request is now dropped silently rather than answered 400, the same rule this gate already applies to OpenAI-family models on Bedrock: the marker is a translation the gateway emits, not a caller parameter, so a 400 for it would be the gateway failing its own requestmodel_info.supports_prompt_cache_breakpoint: false; flipping that default would silently turn caching off for every ARN deployment of a model that accepts cache points, which is the invariant the gate protectsbedrock/invoke/<model>deployment of a model already in the cost map registers its flag on the built-in row, which the gate does not reach from theinvoke/form, so the flag is ignored there; only Nova runs the invoke route through Converse and Nova accepts cache points, so no model hits this today, and thebedrock/<model>route reads itsupports_prompt_cache_breakpointas a strict boolean: a string"true"sent through/model/newcounts as false, since the router'smodel_infotakes extra fields without validating their type (YAMLtrue/falseparse as booleans, so config files are unaffected); type validation belongs to the/model/newschema, not this gateFinal Attestation
Note
Medium Risk
Changes Bedrock Converse request shaping for all models that use the cache-point gate; behavior shifts for Kimi K3 and any deployment overriding the new flag, though Claude and unmapped models keep prior semantics.
Overview
Fixes 400 errors when clients send
cache_controlto Bedrock Kimi K3 on Converse: the model can bill cached tokens but rejects explicitcachePointblocks.bedrock_model_accepts_cache_pointsnow honors a new cost-map / deployment flagsupports_prompt_cache_breakpoint(strict boolean) beforesupports_prompt_caching, and looks up router-registered keys (bedrock/,bedrock/converse/,bedrock/converse_like/) so inference-profile ARNs can opt out viamodel_info. Kimi K3 rows are set tosupports_prompt_cache_breakpoint: falsewhilesupports_prompt_cachingstays true so pricing of cache read/write tokens is unchanged.Adds broad unit coverage and a large proxy integration suite that asserts Converse bodies omit
cachePointfor Kimi (and still emit them for Claude) across chat, messages, responses, SDKs, and deployment flag edge cases.Reviewed by Cursor Bugbot for commit 744961c. Bugbot is set up for automated code reviews on this repo. Configure here.