From 1e50925d9b454ece8db9944b641dbfb7a95f0707 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 14 Apr 2026 19:36:57 -0700 Subject: [PATCH] [Test] add request-body mock test for bedrock gpt-oss tool schema Complements the stubbed-out live integration test by verifying the outgoing Bedrock Converse request body for GPT-OSS is well-formed when the caller supplies a tool schema with OpenAI-style metadata ($id, $schema, additionalProperties, strict): - correct converse URL for bedrock/converse/openai.gpt-oss-20b-1:0 - toolConfig.tools[0].toolSpec has the expected name/description - inputSchema.json keeps type/properties/required and strips fields Bedrock does not accept --- tests/llm_translation/test_bedrock_gpt_oss.py | 95 ++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/tests/llm_translation/test_bedrock_gpt_oss.py b/tests/llm_translation/test_bedrock_gpt_oss.py index 226cc360b9..0a595ad711 100644 --- a/tests/llm_translation/test_bedrock_gpt_oss.py +++ b/tests/llm_translation/test_bedrock_gpt_oss.py @@ -1,14 +1,16 @@ from base_llm_unit_tests import BaseLLMChatTest +import json import pytest import sys import os -from unittest.mock import patch, MagicMock +from unittest.mock import patch, Mock, MagicMock sys.path.insert( 0, os.path.abspath("../..") ) # Adds the parent directory to the system path import litellm from litellm.llms.bedrock.chat.converse_transformation import AmazonConverseConfig +from litellm.llms.custom_httpx.http_handler import HTTPHandler class TestBedrockGPTOSS(BaseLLMChatTest): @@ -22,9 +24,98 @@ class TestBedrockGPTOSS(BaseLLMChatTest): pass def test_function_calling_with_tool_response(self): - """Bedrock GPT-OSS intermittently emits truncated toolUse.input deltas; the underlying code path is already covered by the Claude, Nova, and Llama Converse suites in test_bedrock_completion.py / test_bedrock_llama.py.""" + """Bedrock GPT-OSS intermittently emits truncated toolUse.input deltas on + the live endpoint, which makes the inherited live integration test flaky. + The accumulation side is covered deterministically by + tests/test_litellm/llms/bedrock/chat/test_invoke_handler.py::test_transform_tool_calls_index; + the GPT-OSS-specific request-body transformation is covered by + test_function_calling_request_body_gpt_oss below. + """ pass + def test_function_calling_request_body_gpt_oss(self): + """Verify the Bedrock Converse request body is well-formed for GPT-OSS when the + caller supplies a tool schema with OpenAI-style metadata ($id, $schema, + additionalProperties, strict). Bedrock only accepts a trimmed JSON Schema in + toolSpec.inputSchema.json, so the extra fields must be stripped and the + required shape preserved. + """ + client = HTTPHandler() + + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the weather in a city", + "parameters": { + "$id": "https://some/internal/name", + "$schema": "https://json-schema.org/draft-07/schema", + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "The city to get the weather for", + } + }, + "required": ["city"], + "additionalProperties": False, + }, + "strict": True, + }, + } + ] + + with patch.object(client, "post", new=Mock()) as mock_post: + try: + litellm.completion( + model="bedrock/converse/openai.gpt-oss-20b-1:0", + messages=[ + {"role": "user", "content": "How is the weather in Mumbai?"} + ], + tools=tools, + aws_region_name="us-west-2", + client=client, + ) + except Exception: + # We only care about the outgoing request; the mocked post returns + # a Mock that can't be parsed as a real Converse response. + pass + + mock_post.assert_called_once() + call_kwargs = mock_post.call_args.kwargs + + assert call_kwargs["url"].endswith( + "/model/openai.gpt-oss-20b-1%3A0/converse" + ), call_kwargs["url"] + + request_body = json.loads(call_kwargs["data"]) + + assert "toolConfig" in request_body + tool_specs = request_body["toolConfig"]["tools"] + assert len(tool_specs) == 1 + tool_spec = tool_specs[0]["toolSpec"] + assert tool_spec["name"] == "get_weather" + assert tool_spec["description"] == "Get the weather in a city" + + input_schema = tool_spec["inputSchema"]["json"] + assert input_schema["type"] == "object" + assert input_schema["required"] == ["city"] + assert input_schema["properties"]["city"]["type"] == "string" + + # Bedrock's toolSpec.inputSchema.json only accepts type/properties/required; + # the OpenAI-style metadata must not leak through. + for stripped_field in ("$id", "$schema", "additionalProperties", "strict"): + assert ( + stripped_field not in input_schema + ), f"{stripped_field} should be stripped before hitting Bedrock" + + assert request_body["messages"][0]["role"] == "user" + assert ( + request_body["messages"][0]["content"][0]["text"] + == "How is the weather in Mumbai?" + ) + def test_prompt_caching(self): """ Remove override once we have access to Bedrock prompt caching