mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix(bedrock/realtime): skip message output_item.done on TOOL contentEnd
Minted tool ids must not unlock unpaired message completion events after TOOL-first turns; only text/audio content ends emit output_item.done
This commit is contained in:
parent
86dcc6eb17
commit
053973160d
2 changed files with 70 additions and 11 deletions
|
|
@ -871,6 +871,9 @@ class BedrockRealtimeConfig(BaseRealtimeConfig):
|
|||
if not current_output_item_id or not current_response_id:
|
||||
return [], current_delta_chunks
|
||||
|
||||
if content_end.get("type") == "TOOL" or current_delta_type not in ("text", "audio"):
|
||||
return [], current_delta_chunks
|
||||
|
||||
returned_messages: list[OpenAIRealtimeEvents] = []
|
||||
|
||||
# Send appropriate done event based on type
|
||||
|
|
@ -903,7 +906,7 @@ class BedrockRealtimeConfig(BaseRealtimeConfig):
|
|||
)
|
||||
returned_messages.append(content_part_done)
|
||||
|
||||
elif current_delta_type == "audio":
|
||||
else:
|
||||
audio_done = OpenAIRealtimeResponseAudioDone(
|
||||
type="response.audio.done",
|
||||
content_index=0,
|
||||
|
|
|
|||
|
|
@ -684,6 +684,35 @@ class TestBedrockRealtimeResponseTransformation:
|
|||
logging_obj = MagicMock()
|
||||
logging_obj.litellm_trace_id = "trace_123"
|
||||
|
||||
state = {
|
||||
"session_configuration_request": json.dumps({"configured": True}),
|
||||
"current_output_item_id": None,
|
||||
"current_response_id": None,
|
||||
"current_conversation_id": "conv_123",
|
||||
"current_delta_chunks": [],
|
||||
"current_item_chunks": [],
|
||||
"current_delta_type": None,
|
||||
}
|
||||
|
||||
content_start_result = config.transform_realtime_response(
|
||||
json.dumps({"event": {"contentStart": {"role": "TOOL", "type": "TOOL"}}}),
|
||||
"amazon.nova-2-sonic-v1:0",
|
||||
logging_obj,
|
||||
realtime_response_transform_input=state,
|
||||
)
|
||||
assert content_start_result["response"] == []
|
||||
assert content_start_result["current_delta_type"] is None
|
||||
state.update(
|
||||
{
|
||||
"current_output_item_id": content_start_result["current_output_item_id"],
|
||||
"current_response_id": content_start_result["current_response_id"],
|
||||
"current_conversation_id": content_start_result["current_conversation_id"],
|
||||
"current_delta_chunks": content_start_result["current_delta_chunks"],
|
||||
"current_item_chunks": content_start_result["current_item_chunks"],
|
||||
"current_delta_type": content_start_result["current_delta_type"],
|
||||
}
|
||||
)
|
||||
|
||||
tool_use_message = {
|
||||
"event": {
|
||||
"toolUse": {
|
||||
|
|
@ -698,15 +727,7 @@ class TestBedrockRealtimeResponseTransformation:
|
|||
json.dumps(tool_use_message),
|
||||
"amazon.nova-2-sonic-v1:0",
|
||||
logging_obj,
|
||||
realtime_response_transform_input={
|
||||
"session_configuration_request": json.dumps({"configured": True}),
|
||||
"current_output_item_id": None,
|
||||
"current_response_id": None,
|
||||
"current_conversation_id": "conv_123",
|
||||
"current_delta_chunks": [],
|
||||
"current_item_chunks": [],
|
||||
"current_delta_type": "text",
|
||||
},
|
||||
realtime_response_transform_input=state,
|
||||
)
|
||||
|
||||
assert len(result["response"]) == 1
|
||||
|
|
@ -720,7 +741,6 @@ class TestBedrockRealtimeResponseTransformation:
|
|||
assert function_call["item_id"] == result["current_output_item_id"]
|
||||
assert json.loads(function_call["arguments"]) == {"location": "Seattle"}
|
||||
|
||||
# A follow-up contentEnd must reuse the same persisted ids
|
||||
content_end_message = {
|
||||
"event": {
|
||||
"contentEnd": {
|
||||
|
|
@ -745,6 +765,42 @@ class TestBedrockRealtimeResponseTransformation:
|
|||
)
|
||||
assert follow_up["current_response_id"] == result["current_response_id"]
|
||||
assert follow_up["current_output_item_id"] == result["current_output_item_id"]
|
||||
assert follow_up["response"] == []
|
||||
assert all(msg["type"] != "response.output_item.done" for msg in follow_up["response"])
|
||||
|
||||
def test_tool_content_end_does_not_emit_message_output_item_done(self):
|
||||
"""Minted tool ids must not unlock unpaired message output_item.done on TOOL contentEnd"""
|
||||
config = BedrockRealtimeConfig()
|
||||
logging_obj = MagicMock()
|
||||
logging_obj.litellm_trace_id = "trace_123"
|
||||
|
||||
content_end_message = {
|
||||
"event": {
|
||||
"contentEnd": {
|
||||
"stopReason": "TOOL_USE",
|
||||
"type": "TOOL",
|
||||
}
|
||||
}
|
||||
}
|
||||
result = config.transform_realtime_response(
|
||||
json.dumps(content_end_message),
|
||||
"amazon.nova-2-sonic-v1:0",
|
||||
logging_obj,
|
||||
realtime_response_transform_input={
|
||||
"session_configuration_request": json.dumps({"configured": True}),
|
||||
"current_output_item_id": "item_minted_for_tool",
|
||||
"current_response_id": "resp_minted_for_tool",
|
||||
"current_conversation_id": "conv_123",
|
||||
"current_delta_chunks": [],
|
||||
"current_item_chunks": [],
|
||||
"current_delta_type": "text",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["response"] == []
|
||||
assert result["current_response_id"] == "resp_minted_for_tool"
|
||||
assert result["current_output_item_id"] == "item_minted_for_tool"
|
||||
assert result["current_delta_type"] == "text"
|
||||
|
||||
def test_transform_content_end_text(self):
|
||||
"""Test contentEnd for text response"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue