fix(bedrock): let aux calls omit the Converse maxTokens cap

The Bedrock Converse shim hardcoded 'else 4096' when the caller passed no
max_tokens, so auxiliary vision descriptions stayed capped at 4096 tokens
on the Bedrock wire even after #75253 removed the vision call sites' own
caps (#10809 was only partially fixed there).

Converse's inferenceConfig.maxTokens is optional; when omitted, Bedrock
defaults to the model's maximum allowed output. Thread an explicit
max_tokens=None through build_converse_kwargs/call_converse to omit the
field, and drop an all-empty inferenceConfig from the wire request
entirely. The 4096 default is unchanged for every existing caller (main
transport passes params.get('max_tokens', 4096) explicitly), so only
no-cap aux calls opt in.

Surfaced during review of #75253.
This commit is contained in:
kshitij
2026-08-15 12:39:21 +05:30
parent 30c469b153
commit 5ef52273cd
4 changed files with 94 additions and 7 deletions
+6 -1
View File
@@ -2213,7 +2213,12 @@ class _BedrockCompletionsAdapter:
model=model,
messages=messages,
tools=kwargs.get("tools"),
max_tokens=int(max_tokens) if max_tokens else 4096,
# Omitted/None caller cap → None: build_converse_kwargs then omits
# inferenceConfig.maxTokens so Bedrock uses the model's maximum
# allowed output, matching the no-cap-by-default policy every
# other aux wire already follows (#10809: vision descriptions
# stayed capped at the shim's old hardcoded 4096 on Bedrock).
max_tokens=int(max_tokens) if max_tokens else None,
temperature=kwargs.get("temperature"),
top_p=kwargs.get("top_p"),
stop_sequences=stop,
+18 -6
View File
@@ -1019,7 +1019,7 @@ def build_converse_kwargs(
model: str,
messages: List[Dict],
tools: Optional[List[Dict]] = None,
max_tokens: int = 4096,
max_tokens: Optional[int] = 4096,
temperature: Optional[float] = None,
top_p: Optional[float] = None,
stop_sequences: Optional[List[str]] = None,
@@ -1028,16 +1028,24 @@ def build_converse_kwargs(
"""Build kwargs for ``bedrock-runtime.converse()`` or ``converse_stream()``.
Converts OpenAI-format inputs to Converse API parameters.
``max_tokens=None`` omits ``inferenceConfig.maxTokens`` entirely, in which
case Bedrock defaults to the model's maximum allowed output — the Converse
field is optional per the AWS API reference. The default stays 4096 so
existing callers are unaffected; callers that want the model's full output
budget (e.g. uncapped auxiliary vision calls) pass ``None`` explicitly.
"""
system_prompt, converse_messages = convert_messages_to_converse(messages)
cache_enabled = _model_supports_prompt_cache(model)
inference_config: Dict[str, Any] = {}
if max_tokens is not None:
inference_config["maxTokens"] = max_tokens
kwargs: Dict[str, Any] = {
"modelId": model,
"messages": converse_messages,
"inferenceConfig": {
"maxTokens": max_tokens,
},
"inferenceConfig": inference_config,
}
if system_prompt:
@@ -1086,6 +1094,10 @@ def build_converse_kwargs(
if guardrail_config:
kwargs["guardrailConfig"] = guardrail_config
if not kwargs["inferenceConfig"]:
# inferenceConfig is optional on the wire; don't send an empty object.
del kwargs["inferenceConfig"]
return kwargs
@@ -1094,7 +1106,7 @@ def call_converse(
model: str,
messages: List[Dict],
tools: Optional[List[Dict]] = None,
max_tokens: int = 4096,
max_tokens: Optional[int] = 4096,
temperature: Optional[float] = None,
top_p: Optional[float] = None,
stop_sequences: Optional[List[str]] = None,
@@ -1135,7 +1147,7 @@ def call_converse_stream(
model: str,
messages: List[Dict],
tools: Optional[List[Dict]] = None,
max_tokens: int = 4096,
max_tokens: Optional[int] = 4096,
temperature: Optional[float] = None,
top_p: Optional[float] = None,
stop_sequences: Optional[List[str]] = None,
+34
View File
@@ -389,6 +389,40 @@ class TestBuildConverseKwargs:
assert "toolConfig" in kwargs
assert len(kwargs["toolConfig"]["tools"]) == 1
def test_default_max_tokens_stays_4096(self):
"""Callers that don't pass max_tokens keep the historical 4096 cap —
the None-omission behavior is strictly opt-in."""
from agent.bedrock_adapter import build_converse_kwargs
kwargs = build_converse_kwargs(
model="test-model", messages=[{"role": "user", "content": "Hi"}],
)
assert kwargs["inferenceConfig"]["maxTokens"] == 4096
def test_max_tokens_none_omits_cap(self):
"""max_tokens=None omits inferenceConfig.maxTokens so Bedrock uses the
model's maximum allowed output (the Converse field is optional)."""
from agent.bedrock_adapter import build_converse_kwargs
kwargs = build_converse_kwargs(
model="test-model",
messages=[{"role": "user", "content": "Hi"}],
max_tokens=None,
temperature=0.1,
)
assert "maxTokens" not in kwargs["inferenceConfig"]
# Other inference params still flow through.
assert kwargs["inferenceConfig"]["temperature"] == 0.1
def test_max_tokens_none_and_no_sampling_drops_empty_inference_config(self):
"""When every inference param is absent, don't send an empty
inferenceConfig object on the wire."""
from agent.bedrock_adapter import build_converse_kwargs
kwargs = build_converse_kwargs(
model="test-model",
messages=[{"role": "user", "content": "Hi"}],
max_tokens=None,
)
assert "inferenceConfig" not in kwargs
+36
View File
@@ -437,3 +437,39 @@ class TestAuxiliaryClientBedrockResolution:
# got-final-object downgrade path handles the rest.
assert resp is sentinel
assert mock_converse.call_count == 1
def test_bedrock_shim_uncapped_when_caller_omits_max_tokens(self, monkeypatch):
"""No caller max_tokens → the shim passes None through and the wire
request carries no inferenceConfig.maxTokens, so Bedrock uses the
model's maximum allowed output (#10809 on the Bedrock wire).
Guards against the shim's old hardcoded ``else 4096`` fallback, which
kept aux vision descriptions capped after the vision call sites
dropped their own caps."""
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAIO...MPLE")
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY")
from agent.auxiliary_client import BedrockAuxiliaryClient
client = BedrockAuxiliaryClient("us-east-1", "openai.gpt-oss-20b-1:0")
boto3_client = MagicMock()
with patch("agent.bedrock_adapter._get_bedrock_runtime_client",
return_value=boto3_client), \
patch("agent.bedrock_adapter.normalize_converse_response"):
# Aux vision-style call: no max_tokens key at all.
client.chat.completions.create(
model="openai.gpt-oss-20b-1:0",
messages=[{"role": "user", "content": "describe"}],
temperature=0.1,
)
wire_kwargs = boto3_client.converse.call_args.kwargs
assert "maxTokens" not in wire_kwargs.get("inferenceConfig", {})
# An explicit caller cap still lands on the wire unchanged.
client.chat.completions.create(
model="openai.gpt-oss-20b-1:0",
messages=[{"role": "user", "content": "describe"}],
max_tokens=1234,
)
wire_kwargs = boto3_client.converse.call_args.kwargs
assert wire_kwargs["inferenceConfig"]["maxTokens"] == 1234