From cd10d497c913130718685184e6ce2e67b0657293 Mon Sep 17 00:00:00 2001 From: Somay-kousis Date: Thu, 15 Oct 2026 00:41:23 +0530 Subject: [PATCH 1/4] Python: test that BedrockChatClient reports the requested model --- .../bedrock/tests/test_bedrock_client.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/python/packages/bedrock/tests/test_bedrock_client.py b/python/packages/bedrock/tests/test_bedrock_client.py index bd698efac7a..ced0153f29b 100644 --- a/python/packages/bedrock/tests/test_bedrock_client.py +++ b/python/packages/bedrock/tests/test_bedrock_client.py @@ -103,6 +103,37 @@ async def test_get_response_invokes_bedrock_runtime() -> None: assert response.usage_details and response.usage_details["input_token_count"] == 10 +class _StubConverseWithoutModelId(_StubBedrockRuntime): + """Returns the real Converse response shape, which does not echo the model id.""" + + def converse(self, **kwargs: Any) -> dict[str, Any]: + self.calls.append(kwargs) + return { + "output": {"message": {"role": "assistant", "content": [{"text": "hi"}]}}, + "stopReason": "end_turn", + "usage": {"inputTokens": 3, "outputTokens": 1, "totalTokens": 4}, + } + + +@pytest.mark.parametrize("client_model", ["amazon.titan-text", None]) +async def test_get_response_reports_the_requested_model(client_model: str | None) -> None: + """A per-call model override must be the model reported on the response, like the streaming path.""" + stub = _StubConverseWithoutModelId() + client = BedrockChatClient( + model=client_model, + region="us-west-2", + client=stub, # pyrefly: ignore[bad-argument-type] # ty: ignore[invalid-argument-type] # pyright: ignore[reportArgumentType] + ) + + response = await client.get_response( + messages=[Message(role="user", contents=[Content.from_text(text="hello")])], + options={"model": "us.openai.gpt-6-sol"}, + ) + + assert stub.calls[0]["modelId"] == "us.openai.gpt-6-sol" + assert response.model == "us.openai.gpt-6-sol" + + async def test_stream_yields_updates_as_converse_stream_events_arrive() -> None: """stream=True should use ConverseStream and yield text, tool call, finish and usage updates per event.""" stub = _StubBedrockStreamRuntime([ From f7c5e868e05aa1641f8ef339fbc8adf6dfb43e67 Mon Sep 17 00:00:00 2001 From: Somay-kousis Date: Thu, 15 Oct 2026 00:42:24 +0530 Subject: [PATCH 2/4] Python: fix(bedrock): report the requested model on non-streaming responses --- .../bedrock/agent_framework_bedrock/_chat_client.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/python/packages/bedrock/agent_framework_bedrock/_chat_client.py b/python/packages/bedrock/agent_framework_bedrock/_chat_client.py index 7b908039028..455c271ee54 100644 --- a/python/packages/bedrock/agent_framework_bedrock/_chat_client.py +++ b/python/packages/bedrock/agent_framework_bedrock/_chat_client.py @@ -420,7 +420,7 @@ async def _stream() -> AsyncIterable[ChatResponseUpdate]: async def _get_response() -> ChatResponse: raw_response = await asyncio.to_thread(self._invoke_converse, request) - return self._process_converse_response(raw_response, options) + return self._process_converse_response(raw_response, options, model=request["modelId"]) return _get_response() @@ -758,9 +758,12 @@ def _redacted_reasoning_content( ) def _process_converse_response( - self, response: dict[str, Any], options: Mapping[str, Any] | None = None + self, response: dict[str, Any], options: Mapping[str, Any] | None = None, *, model: str | None = None ) -> ChatResponse: - """Convert Bedrock Converse API response to ChatResponse.""" + """Convert Bedrock Converse API response to ChatResponse. + + Converse does not return the model id, so ``model`` should be the ``modelId`` the request was sent with. + """ output = response.get("output") or {} message = output.get("message") or {} content_blocks = message.get("content") or [] @@ -770,7 +773,7 @@ def _process_converse_response( usage_details = self._parse_usage(usage_source) finish_reason = self._map_finish_reason(output.get("completionReason") or response.get("stopReason")) response_id = response.get("responseId") or message.get("id") - model = response.get("modelId") or output.get("modelId") or self.model + model = response.get("modelId") or output.get("modelId") or model or self.model return ChatResponse( response_id=response_id, messages=[chat_message], From c9af528e2102011fbdecbb7f17abebc0c3d35852 Mon Sep 17 00:00:00 2001 From: Somay-kousis Date: Tue, 20 Oct 2026 02:33:55 +0530 Subject: [PATCH 3/4] Python: test that streaming and non-streaming report the same Bedrock model --- .../bedrock/tests/test_bedrock_client.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/python/packages/bedrock/tests/test_bedrock_client.py b/python/packages/bedrock/tests/test_bedrock_client.py index ced0153f29b..2f798328206 100644 --- a/python/packages/bedrock/tests/test_bedrock_client.py +++ b/python/packages/bedrock/tests/test_bedrock_client.py @@ -134,6 +134,42 @@ async def test_get_response_reports_the_requested_model(client_model: str | None assert response.model == "us.openai.gpt-6-sol" +class _StubConverseAndStreamWithoutModelId(_StubConverseWithoutModelId): + def converse_stream(self, **kwargs: Any) -> dict[str, Any]: + self.calls.append(kwargs) + return { + "stream": _StubEventStream([ + {"messageStart": {"role": "assistant"}}, + {"contentBlockDelta": {"delta": {"text": "hi"}, "contentBlockIndex": 0}}, + {"contentBlockStop": {"contentBlockIndex": 0}}, + {"messageStop": {"stopReason": "end_turn"}}, + ]) + } + + +@pytest.mark.parametrize("stream", [False, True]) +async def test_streaming_and_non_streaming_report_the_same_model(stream: bool) -> None: + """Both paths must report the per-call model override, not the client default.""" + stub = _StubConverseAndStreamWithoutModelId() + client = BedrockChatClient( + model="amazon.titan-text", + region="us-west-2", + client=stub, # pyrefly: ignore[bad-argument-type] # ty: ignore[invalid-argument-type] # pyright: ignore[reportArgumentType] + ) + messages = [Message(role="user", contents=[Content.from_text(text="hello")])] + options = {"model": "us.openai.gpt-6-sol"} + + if stream: + response_stream = client.get_response(messages=messages, options=options, stream=True) + response = await response_stream.get_final_response() + else: + response = await client.get_response(messages=messages, options=options) + + assert stub.calls[0]["modelId"] == "us.openai.gpt-6-sol" + assert response.text == "hi" + assert response.model == "us.openai.gpt-6-sol" + + async def test_stream_yields_updates_as_converse_stream_events_arrive() -> None: """stream=True should use ConverseStream and yield text, tool call, finish and usage updates per event.""" stub = _StubBedrockStreamRuntime([ From 6c367b6b1f47d97d6bb5ac054f16a5dc547bde62 Mon Sep 17 00:00:00 2001 From: Somay-kousis Date: Thu, 22 Oct 2026 17:42:05 +0530 Subject: [PATCH 4/4] Python: fix type checks in the Bedrock streaming parity test --- .../bedrock/tests/test_bedrock_client.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/python/packages/bedrock/tests/test_bedrock_client.py b/python/packages/bedrock/tests/test_bedrock_client.py index 2f798328206..4cdb6bf89ea 100644 --- a/python/packages/bedrock/tests/test_bedrock_client.py +++ b/python/packages/bedrock/tests/test_bedrock_client.py @@ -137,14 +137,13 @@ async def test_get_response_reports_the_requested_model(client_model: str | None class _StubConverseAndStreamWithoutModelId(_StubConverseWithoutModelId): def converse_stream(self, **kwargs: Any) -> dict[str, Any]: self.calls.append(kwargs) - return { - "stream": _StubEventStream([ - {"messageStart": {"role": "assistant"}}, - {"contentBlockDelta": {"delta": {"text": "hi"}, "contentBlockIndex": 0}}, - {"contentBlockStop": {"contentBlockIndex": 0}}, - {"messageStop": {"stopReason": "end_turn"}}, - ]) - } + events: list[dict[str, Any]] = [ + {"messageStart": {"role": "assistant"}}, + {"contentBlockDelta": {"delta": {"text": "hi"}, "contentBlockIndex": 0}}, + {"contentBlockStop": {"contentBlockIndex": 0}}, + {"messageStop": {"stopReason": "end_turn"}}, + ] + return {"stream": _StubEventStream(events)} @pytest.mark.parametrize("stream", [False, True]) @@ -157,13 +156,12 @@ async def test_streaming_and_non_streaming_report_the_same_model(stream: bool) - client=stub, # pyrefly: ignore[bad-argument-type] # ty: ignore[invalid-argument-type] # pyright: ignore[reportArgumentType] ) messages = [Message(role="user", contents=[Content.from_text(text="hello")])] - options = {"model": "us.openai.gpt-6-sol"} if stream: - response_stream = client.get_response(messages=messages, options=options, stream=True) + response_stream = client.get_response(messages=messages, options={"model": "us.openai.gpt-6-sol"}, stream=True) response = await response_stream.get_final_response() else: - response = await client.get_response(messages=messages, options=options) + response = await client.get_response(messages=messages, options={"model": "us.openai.gpt-6-sol"}) assert stub.calls[0]["modelId"] == "us.openai.gpt-6-sol" assert response.text == "hi"