mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
fix(bedrock): preserve native converse stop reason
This commit is contained in:
parent
74cad08997
commit
da5699220b
3 changed files with 65 additions and 4 deletions
|
|
@ -22,7 +22,6 @@ from litellm.constants import (
|
|||
from litellm.litellm_core_utils.core_helpers import (
|
||||
filter_exceptions_from_params,
|
||||
filter_internal_params,
|
||||
map_finish_reason,
|
||||
safe_deep_copy,
|
||||
)
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
|
|
@ -2659,7 +2658,7 @@ class AmazonConverseConfig(BaseConfig):
|
|||
|
||||
## HANDLE TOOL CALLS
|
||||
_message: Final = Message(**chat_completion_message)
|
||||
initial_finish_reason = map_finish_reason(completion_response["stopReason"])
|
||||
initial_finish_reason = completion_response["stopReason"]
|
||||
|
||||
# When json_mode filtered out all synthetic tool calls the response
|
||||
# is plain content, not a pending tool invocation. Fix finish_reason
|
||||
|
|
|
|||
|
|
@ -619,6 +619,7 @@ class AWSEventStreamDecoder:
|
|||
tool_use: ChatCompletionToolCallChunk | None = None
|
||||
finish_reason = ""
|
||||
usage: Usage | None = None
|
||||
raw_stop_reason: Final[object] = chunk_data.get("stopReason")
|
||||
provider_specific_fields: dict = {}
|
||||
reasoning_content: str | None = None
|
||||
thinking_blocks: list[ChatCompletionThinkingBlock | ChatCompletionRedactedThinkingBlock] | None = None
|
||||
|
|
@ -642,8 +643,8 @@ class AWSEventStreamDecoder:
|
|||
) = self._handle_converse_delta_event(delta_obj, content_block_index)
|
||||
elif "contentBlockIndex" in chunk_data: # stop block, no 'start' or 'delta' object
|
||||
tool_use = self._handle_converse_stop_event(content_block_index)
|
||||
elif "stopReason" in chunk_data:
|
||||
finish_reason = map_finish_reason(chunk_data.get("stopReason", "stop"))
|
||||
elif isinstance(raw_stop_reason, str):
|
||||
finish_reason = map_finish_reason(raw_stop_reason)
|
||||
self._provider_reasoning_tokens = AmazonConverseConfig.thinking_tokens_from_additional_fields(
|
||||
chunk_data.get("additionalModelResponseFields")
|
||||
)
|
||||
|
|
@ -657,6 +658,12 @@ class AWSEventStreamDecoder:
|
|||
self._thinking_ran = True
|
||||
|
||||
trace: Final = chunk_data.get("trace")
|
||||
choice_provider_specific_fields: Final[dict[str, str] | None] = (
|
||||
{"native_finish_reason": raw_stop_reason} # mutable-ok: response field contract requires a dict
|
||||
if isinstance(raw_stop_reason, str) and raw_stop_reason != finish_reason
|
||||
else None
|
||||
)
|
||||
|
||||
carries_message_content: Final = bool(trace) or any(
|
||||
key in chunk_data for key in ("start", "delta", "contentBlockIndex", "stopReason")
|
||||
)
|
||||
|
|
@ -667,6 +674,7 @@ class AWSEventStreamDecoder:
|
|||
StreamingChoices(
|
||||
finish_reason=finish_reason,
|
||||
index=0, # Always 0 - Bedrock never returns multiple choices
|
||||
provider_specific_fields=choice_provider_specific_fields,
|
||||
delta=Delta(
|
||||
content=text if carries_message_content else None,
|
||||
role="assistant" if carries_message_content else None,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import litellm
|
|||
from litellm import ModelResponse
|
||||
from litellm.litellm_core_utils.prompt_templates.mid_conversation_system import CONVERTED_SYSTEM_NOTE
|
||||
from litellm.llms.bedrock.chat.converse_transformation import AmazonConverseConfig
|
||||
from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder
|
||||
from litellm.types.llms.bedrock import ConverseTokenUsageBlock
|
||||
|
||||
|
||||
|
|
@ -4628,6 +4629,59 @@ def test_transform_response_native_structured_output():
|
|||
assert result.choices[0].finish_reason == "stop"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stop_reason", ("stop_sequence", "end_turn"))
|
||||
def test_transform_response_preserves_raw_bedrock_stop_reason(stop_reason: str):
|
||||
response_json = {
|
||||
"metrics": {"latencyMs": 1},
|
||||
"output": {
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": [{"text": "done"}],
|
||||
}
|
||||
},
|
||||
"stopReason": stop_reason,
|
||||
"usage": {
|
||||
"inputTokens": 10,
|
||||
"outputTokens": 1,
|
||||
"totalTokens": 11,
|
||||
},
|
||||
}
|
||||
raw_response = httpx.Response(
|
||||
200,
|
||||
json=response_json,
|
||||
request=httpx.Request("POST", "https://bedrock.test/converse"),
|
||||
)
|
||||
|
||||
result = AmazonConverseConfig().transform_response(
|
||||
model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
raw_response=raw_response,
|
||||
model_response=ModelResponse(),
|
||||
logging_obj=None,
|
||||
request_data={},
|
||||
messages=[],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
encoding=None,
|
||||
)
|
||||
|
||||
assert result.choices[0].finish_reason == "stop"
|
||||
assert result.choices[0].provider_specific_fields == {
|
||||
"native_finish_reason": stop_reason
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stop_reason", ("stop_sequence", "end_turn"))
|
||||
def test_converse_chunk_parser_preserves_raw_bedrock_stop_reason(stop_reason: str):
|
||||
result = AWSEventStreamDecoder(model="bedrock/claude").converse_chunk_parser(
|
||||
{"stopReason": stop_reason}
|
||||
)
|
||||
|
||||
assert result.choices[0].finish_reason == "stop"
|
||||
assert result.choices[0].provider_specific_fields == {
|
||||
"native_finish_reason": stop_reason
|
||||
}
|
||||
|
||||
|
||||
def test_add_additional_properties_simple_object():
|
||||
"""Object schemas without additionalProperties get it set to false."""
|
||||
schema = {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue