From 3a10830e9027aca83f4332ebe0b23247562b6312 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Mon, 29 Jun 2026 15:02:17 -0400 Subject: [PATCH] fix(bedrock): use adaptive thinking + output_config.effort for Opus 4.8 The handoff spec wired Bedrock Converse thinking as {type: enabled, budget_tokens: N}, but Opus 4.7+ rejects that with a ValidationException: thinking.type "enabled" is not supported; it requires thinking.type "adaptive" plus output_config.effort. Verified by live invoke against us.anthropic.claude-opus-4-8 (account 328440206208, us-east-1): the enabled+budget shape 400s, adaptive+effort returns normally. Map profile effort to additional_model_request_fields: {thinking: {type: adaptive, display: summarized}, output_config: {effort: }} reusing anthropic_thinking_for/anthropic_effort_for. Update the two subagent-model tests asserting the old shape. --- agent/utils/model.py | 19 ++++++++++++------- tests/test_agent_subagent_models.py | 6 ++++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/agent/utils/model.py b/agent/utils/model.py index d007a66b..6fc469d4 100644 --- a/agent/utils/model.py +++ b/agent/utils/model.py @@ -184,13 +184,18 @@ def provider_model_kwargs( if effort is not None: kwargs["effort"] = effort elif model_id.startswith("bedrock_converse:"): - budget = {"low": 4000, "medium": 8000, "high": 16000, "xhigh": 32000, "max": 60000}.get( - profile_effort or "" - ) - if budget is not None: - kwargs["additional_model_request_fields"] = { - "thinking": {"type": "enabled", "budget_tokens": budget} - } + # Opus 4.7+ on Bedrock rejects thinking.type "enabled"/budget_tokens with a + # ValidationException; it requires adaptive thinking plus output_config.effort, + # passed through Converse's additional_model_request_fields. + fields: dict[str, object] = {} + thinking = anthropic_thinking_for(profile_effort) + if thinking is not None: + fields["thinking"] = thinking + effort = anthropic_effort_for(profile_effort) + if effort is not None: + fields["output_config"] = {"effort": effort} + if fields: + kwargs["additional_model_request_fields"] = fields elif model_id.startswith("google_genai:") and is_gemini_3_family(model_id): thinking_level = google_thinking_level_for(profile_effort) if thinking_level is not None: diff --git a/tests/test_agent_subagent_models.py b/tests/test_agent_subagent_models.py index 73c4aaae..7d317f88 100644 --- a/tests/test_agent_subagent_models.py +++ b/tests/test_agent_subagent_models.py @@ -81,7 +81,8 @@ async def test_agent_uses_profile_subagent_model_override() -> None: main_call = make_model.call_args_list[0] assert main_call.args == ("bedrock_converse:us.anthropic.claude-opus-4-8",) assert main_call.kwargs["additional_model_request_fields"] == { - "thinking": {"type": "enabled", "budget_tokens": 16000} + "thinking": {"type": "adaptive", "display": "summarized"}, + "output_config": {"effort": "high"}, } subagent_call = make_model.call_args_list[1] @@ -153,5 +154,6 @@ async def test_agent_subagent_inherits_profile_model_override_without_explicit_p assert make_model.call_args_list[0].args == ("bedrock_converse:us.anthropic.claude-opus-4-8",) assert make_model.call_args_list[1].args == ("bedrock_converse:us.anthropic.claude-opus-4-8",) assert make_model.call_args_list[1].kwargs["additional_model_request_fields"] == { - "thinking": {"type": "enabled", "budget_tokens": 16000} + "thinking": {"type": "adaptive", "display": "summarized"}, + "output_config": {"effort": "high"}, }