litellm/tests/llm_translation/test_bedrock_invoke_tests.py
Mateo Wang b4aee2c7dd
test(vcr): close out the remaining VCR live-call leaks (#29603)
* Fix remaining VCR live-call leaks

* test(vcr): dedupe live-test helpers and drop spurious kwargs

Extract the duplicated isVertexQuotaError/runVertexRequestOrSkip Vertex
quota-skip helpers into tests/pass_through_tests/vertex_test_helpers.js and the
duplicated _skip_live_prompt_caching_test guard into tests/_live_test_helpers.py
so each lives in one place. In test_aarun_thread_litellm, build a separate
message_data carrying role/content for add_message and a thread_data without
them for run_thread/run_thread_stream/get_messages, which no longer receive the
spurious message fields.

* test(overhead): assert mock transport is exercised in non-streaming and stream tests
2026-06-03 13:46:43 -07:00

157 lines
5.8 KiB
Python

from base_llm_unit_tests import BaseLLMChatTest
import pytest
import sys
import os
sys.path.insert(
0, os.path.abspath("../..")
) # Adds the parent directory to the system path
import litellm
from litellm.types.llms.bedrock import BedrockInvokeNovaRequest
@pytest.mark.flaky(retries=3, delay=5)
class TestBedrockInvokeClaudeJson(BaseLLMChatTest):
def get_base_completion_call_args(self) -> dict:
litellm._turn_on_debug()
return {
"model": "bedrock/invoke/us.anthropic.claude-haiku-4-5-20251001-v1:0",
}
def test_tool_call_no_arguments(self, tool_call_no_arguments):
"""Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833"""
pass
class TestBedrockInvokeNovaJson(BaseLLMChatTest):
def get_base_completion_call_args(self) -> dict:
return {
"model": "bedrock/invoke/us.amazon.nova-micro-v1:0",
}
def test_tool_call_no_arguments(self, tool_call_no_arguments):
"""Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833"""
pass
@pytest.fixture(autouse=True)
def skip_non_json_tests(self, request):
if not "json" in request.function.__name__.lower():
pytest.skip(
f"Skipping non-JSON test: {request.function.__name__} does not contain 'json'"
)
def test_json_response_pydantic_obj(self):
if os.environ.get("LITELLM_RUN_LIVE_BEDROCK_NOVA_JSON_TESTS") != "1":
pytest.skip("Live Bedrock Nova response-schema E2E tests are opt-in")
if os.environ.get("CASSETTE_REDIS_URL"):
pytest.skip(
"Live Bedrock Nova response-schema E2E tests cannot run under VCR replay"
)
super().test_json_response_pydantic_obj()
def test_nova_invoke_remove_empty_system_messages():
"""Test that _remove_empty_system_messages removes empty system list."""
input_request = BedrockInvokeNovaRequest(
messages=[{"content": [{"text": "Hello"}], "role": "user"}],
system=[],
inferenceConfig={"temperature": 0.7},
)
litellm.AmazonInvokeNovaConfig()._remove_empty_system_messages(input_request)
assert "system" not in input_request
assert "messages" in input_request
assert "inferenceConfig" in input_request
def test_nova_invoke_filter_allowed_fields():
"""
Test that _filter_allowed_fields only keeps fields defined in BedrockInvokeNovaRequest.
Nova Invoke does not allow `additionalModelRequestFields` and `additionalModelResponseFieldPaths` in the request body.
This test ensures that these fields are not included in the request body.
"""
_input_request = {
"messages": [{"content": [{"text": "Hello"}], "role": "user"}],
"system": [{"text": "System prompt"}],
"inferenceConfig": {"temperature": 0.7},
"additionalModelRequestFields": {"this": "should be removed"},
"additionalModelResponseFieldPaths": ["this", "should", "be", "removed"],
}
input_request = BedrockInvokeNovaRequest(**_input_request)
result = litellm.AmazonInvokeNovaConfig()._filter_allowed_fields(input_request)
assert "additionalModelRequestFields" not in result
assert "additionalModelResponseFieldPaths" not in result
assert "messages" in result
assert "system" in result
assert "inferenceConfig" in result
def test_nova_invoke_streaming_chunk_parsing():
"""
Test that the AWSEventStreamDecoder correctly handles Nova's /bedrock/invoke/ streaming format
where content is nested under 'contentBlockDelta'.
"""
from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder
# Initialize the decoder with a Nova model
decoder = AWSEventStreamDecoder(model="bedrock/invoke/us.amazon.nova-micro-v1:0")
# Test case 1: Text content in contentBlockDelta
nova_text_chunk = {
"contentBlockDelta": {
"delta": {"text": "Hello, how can I help?"},
"contentBlockIndex": 0,
}
}
result = decoder._chunk_parser(nova_text_chunk)
assert result.choices[0].delta.content == "Hello, how can I help?"
assert result.choices[0].index == 0
assert not result.choices[0].finish_reason
assert result.choices[0].delta.tool_calls is None
# Test case 2: Tool use start in contentBlockDelta
nova_tool_start_chunk = {
"contentBlockDelta": {
"start": {"toolUse": {"name": "get_weather", "toolUseId": "tool_1"}},
"contentBlockIndex": 1,
}
}
result = decoder._chunk_parser(nova_tool_start_chunk)
assert result.choices[0].delta.content == ""
assert result.choices[0].index == 0
assert result.choices[0].delta.tool_calls is not None
assert result.choices[0].delta.tool_calls[0].type == "function"
assert result.choices[0].delta.tool_calls[0].function.name == "get_weather"
assert result.choices[0].delta.tool_calls[0].id == "tool_1"
# Test case 3: Tool use arguments in contentBlockDelta
nova_tool_args_chunk = {
"contentBlockDelta": {
"delta": {"toolUse": {"input": '{"location": "New York"}'}},
"contentBlockIndex": 2,
}
}
result = decoder._chunk_parser(nova_tool_args_chunk)
assert result.choices[0].delta.content == ""
assert result.choices[0].index == 0
assert result.choices[0].delta.tool_calls is not None
assert (
result.choices[0].delta.tool_calls[0].function.arguments
== '{"location": "New York"}'
)
# Test case 4: Stop reason in contentBlockDelta
nova_stop_chunk = {
"contentBlockDelta": {
"stopReason": "tool_use",
}
}
result = decoder._chunk_parser(nova_stop_chunk)
print(result)
assert result.choices[0].finish_reason == "tool_calls"