mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix(bedrock/realtime): mint response/item ids so toolUse is not dropped
Nova Sonic starts tool turns with contentStart role=TOOL, so ASSISTANT-only id init never runs and transform_tool_use_event used to return empty events even after parsing content. Mint resp_/item_ ids when missing so function_call_arguments.done reaches the client with non-empty args
This commit is contained in:
parent
95239b9c68
commit
06cbbbb79a
2 changed files with 26 additions and 14 deletions
|
|
@ -1026,11 +1026,9 @@ class BedrockRealtimeConfig(BaseRealtimeConfig):
|
||||||
verbose_logger.debug("Handling toolUse")
|
verbose_logger.debug("Handling toolUse")
|
||||||
tool_use = event["toolUse"]
|
tool_use = event["toolUse"]
|
||||||
|
|
||||||
if not current_output_item_id or not current_response_id:
|
response_id = current_response_id or f"resp_{uuid.uuid4()}"
|
||||||
return [], "", ""
|
item_id = current_output_item_id or f"item_{uuid.uuid4()}"
|
||||||
|
|
||||||
# Parse the tool input. Nova 2 Sonic sends arguments in `content`;
|
|
||||||
# fall back to `input` for backward compatibility.
|
|
||||||
tool_input = {}
|
tool_input = {}
|
||||||
raw_input = tool_use["content"] if "content" in tool_use else tool_use.get("input")
|
raw_input = tool_use["content"] if "content" in tool_use else tool_use.get("input")
|
||||||
if raw_input:
|
if raw_input:
|
||||||
|
|
@ -1042,15 +1040,13 @@ class BedrockRealtimeConfig(BaseRealtimeConfig):
|
||||||
tool_call_id = tool_use.get("toolUseId", "")
|
tool_call_id = tool_use.get("toolUseId", "")
|
||||||
tool_name = tool_use.get("toolName", "")
|
tool_name = tool_use.get("toolName", "")
|
||||||
|
|
||||||
# Create a function call arguments done event
|
|
||||||
# This is a custom event format that matches what clients expect
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
function_call_event: dict[str, Any] = {
|
function_call_event: dict[str, Any] = {
|
||||||
"type": "response.function_call_arguments.done",
|
"type": "response.function_call_arguments.done",
|
||||||
"event_id": f"event_{uuid.uuid4()}",
|
"event_id": f"event_{uuid.uuid4()}",
|
||||||
"response_id": current_response_id,
|
"response_id": response_id,
|
||||||
"item_id": current_output_item_id,
|
"item_id": item_id,
|
||||||
"output_index": 0,
|
"output_index": 0,
|
||||||
"call_id": tool_call_id,
|
"call_id": tool_call_id,
|
||||||
"name": tool_name,
|
"name": tool_name,
|
||||||
|
|
|
||||||
|
|
@ -614,16 +614,30 @@ class TestBedrockRealtimeResponseTransformation:
|
||||||
assert args["location"] == "San Francisco"
|
assert args["location"] == "San Francisco"
|
||||||
|
|
||||||
def test_transform_tool_use_event_directly(self):
|
def test_transform_tool_use_event_directly(self):
|
||||||
"""Test transform_tool_use_event directly for guard clause and input parsing"""
|
"""Test transform_tool_use_event directly for input parsing and missing IDs"""
|
||||||
config = BedrockRealtimeConfig()
|
config = BedrockRealtimeConfig()
|
||||||
|
|
||||||
# Guard clause: missing IDs returns empty result
|
# Missing IDs still emit a function call (Nova Sonic starts tools with role=TOOL)
|
||||||
events, tool_call_id, tool_name = config.transform_tool_use_event(
|
events, tool_call_id, tool_name = config.transform_tool_use_event(
|
||||||
{"toolUse": {}}, None, "resp_123"
|
{
|
||||||
|
"toolUse": {
|
||||||
|
"toolUseId": "tool_call_no_ids",
|
||||||
|
"toolName": "get_weather",
|
||||||
|
"content": json.dumps({"location": "Seattle"}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
assert events == []
|
assert len(events) == 1
|
||||||
assert tool_call_id == ""
|
assert events[0]["type"] == "response.function_call_arguments.done"
|
||||||
assert tool_name == ""
|
assert events[0]["call_id"] == "tool_call_no_ids"
|
||||||
|
assert events[0]["name"] == "get_weather"
|
||||||
|
assert events[0]["response_id"].startswith("resp_")
|
||||||
|
assert events[0]["item_id"].startswith("item_")
|
||||||
|
assert json.loads(events[0]["arguments"]) == {"location": "Seattle"}
|
||||||
|
assert tool_call_id == "tool_call_no_ids"
|
||||||
|
assert tool_name == "get_weather"
|
||||||
|
|
||||||
# JSON string content is parsed and converted to a function call event
|
# JSON string content is parsed and converted to a function call event
|
||||||
events, tool_call_id, tool_name = config.transform_tool_use_event(
|
events, tool_call_id, tool_name = config.transform_tool_use_event(
|
||||||
|
|
@ -641,6 +655,8 @@ class TestBedrockRealtimeResponseTransformation:
|
||||||
assert events[0]["type"] == "response.function_call_arguments.done"
|
assert events[0]["type"] == "response.function_call_arguments.done"
|
||||||
assert events[0]["call_id"] == "tool_call_123"
|
assert events[0]["call_id"] == "tool_call_123"
|
||||||
assert events[0]["name"] == "get_weather"
|
assert events[0]["name"] == "get_weather"
|
||||||
|
assert events[0]["response_id"] == "resp_123"
|
||||||
|
assert events[0]["item_id"] == "item_123"
|
||||||
assert json.loads(events[0]["arguments"]) == {"location": "San Francisco"}
|
assert json.loads(events[0]["arguments"]) == {"location": "San Francisco"}
|
||||||
|
|
||||||
# Invalid JSON content falls back to empty arguments
|
# Invalid JSON content falls back to empty arguments
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue