mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
Merge d777169d3d into dd31692282
This commit is contained in:
commit
fb461850cf
2 changed files with 194 additions and 4 deletions
|
|
@ -2,6 +2,7 @@
|
|||
Handler for transforming /chat/completions api requests to litellm.responses requests
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from collections.abc import AsyncIterator, Callable, Iterable, Iterator, Mapping, Sequence
|
||||
|
|
@ -361,6 +362,17 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
# Unknown or unsupported type
|
||||
return None, index
|
||||
|
||||
@staticmethod
|
||||
def _normalize_tool_call_id(tool_call_id: object) -> str | None:
|
||||
if tool_call_id is None:
|
||||
return None
|
||||
tool_call_id_str: Final = str(tool_call_id)
|
||||
if len(tool_call_id_str) <= 64:
|
||||
return tool_call_id_str
|
||||
prefix: Final = tool_call_id_str[:31]
|
||||
digest: Final = hashlib.sha256(tool_call_id_str.encode("utf-8")).hexdigest()[:32]
|
||||
return f"{prefix}_{digest}"
|
||||
|
||||
def convert_chat_completion_messages_to_responses_api(
|
||||
self, messages: list["AllMessageValues"]
|
||||
) -> tuple[list[object], str | None]:
|
||||
|
|
@ -374,6 +386,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
if isinstance(tool_call, dict)
|
||||
and not tool_call.get("function")
|
||||
and isinstance(tool_call.get("custom"), dict)
|
||||
and "id" in tool_call
|
||||
)
|
||||
|
||||
leading_system_count: Final = next(
|
||||
|
|
@ -429,7 +442,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
input_items.append(
|
||||
ResponseCustomToolCallOutputParam(
|
||||
type="custom_tool_call_output",
|
||||
call_id=tool_call_id,
|
||||
call_id=self._normalize_tool_call_id(tool_call_id) or "",
|
||||
output=content if isinstance(content, str) else tool_output,
|
||||
)
|
||||
)
|
||||
|
|
@ -437,7 +450,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
input_items.append(
|
||||
FunctionCallOutput(
|
||||
type="function_call_output",
|
||||
call_id=tool_call_id,
|
||||
call_id=self._normalize_tool_call_id(tool_call_id),
|
||||
output=tool_output,
|
||||
)
|
||||
)
|
||||
|
|
@ -457,7 +470,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
if function:
|
||||
input_tool_call: dict[str, object] = {
|
||||
"type": "function_call",
|
||||
"call_id": tool_call["id"],
|
||||
"call_id": self._normalize_tool_call_id(tool_call.get("id")),
|
||||
}
|
||||
if "name" in function:
|
||||
input_tool_call["name"] = function["name"]
|
||||
|
|
@ -468,7 +481,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
input_items.append(
|
||||
ResponseCustomToolCallParam(
|
||||
type="custom_tool_call",
|
||||
call_id=tool_call["id"],
|
||||
call_id=self._normalize_tool_call_id(tool_call.get("id")) or "",
|
||||
name=custom.get("name", ""),
|
||||
input=custom.get("input", ""),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4526,6 +4526,182 @@ def test_map_optional_params_verbosity_merges_into_text():
|
|||
assert verbosity_only_request["text"] == {"verbosity": "low"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"tool_call_id,is_custom",
|
||||
[
|
||||
(None, False),
|
||||
("call_short_123", False),
|
||||
("call_" + "a" * 59, False),
|
||||
("call_" + "a" * 60, False),
|
||||
(
|
||||
"call_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
False,
|
||||
),
|
||||
(
|
||||
"custom_tool_call_id_exceeding_the_standard_responses_api_sixty_four_character_length_limit",
|
||||
True,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_convert_chat_completion_messages_to_responses_api_normalizes_overlong_tool_call_ids(
|
||||
tool_call_id: str | None,
|
||||
is_custom: bool,
|
||||
):
|
||||
import hashlib
|
||||
|
||||
from litellm.completion_extras.litellm_responses_transformation.transformation import (
|
||||
LiteLLMResponsesTransformationHandler,
|
||||
)
|
||||
|
||||
expected_id: Final = (
|
||||
None
|
||||
if tool_call_id is None
|
||||
else tool_call_id
|
||||
if len(tool_call_id) <= 64
|
||||
else f"{tool_call_id[:31]}_{hashlib.sha256(tool_call_id.encode('utf-8')).hexdigest()[:32]}"
|
||||
)
|
||||
|
||||
handler: Final = LiteLLMResponsesTransformationHandler()
|
||||
assistant_tool_call: Final[dict[str, object]] = (
|
||||
{
|
||||
"id": tool_call_id,
|
||||
"type": "custom",
|
||||
"custom": {"name": "example_custom_tool", "input": "{}"},
|
||||
}
|
||||
if is_custom
|
||||
else {
|
||||
"id": tool_call_id,
|
||||
"type": "function",
|
||||
"function": {"name": "example_tool", "arguments": "{}"},
|
||||
}
|
||||
)
|
||||
|
||||
messages: Final[list[dict[str, object]]] = [
|
||||
{"role": "user", "content": "Run tool"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": [assistant_tool_call],
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call_id,
|
||||
"content": "tool execution result",
|
||||
},
|
||||
]
|
||||
|
||||
input_items, _ = handler.convert_chat_completion_messages_to_responses_api(messages)
|
||||
|
||||
if is_custom:
|
||||
tool_call_item: Final = next(
|
||||
item for item in input_items if isinstance(item, dict) and item.get("type") == "custom_tool_call"
|
||||
)
|
||||
call_id: Final = tool_call_item.get("call_id")
|
||||
else:
|
||||
func_tool_call_item: Final = next(
|
||||
item for item in input_items if isinstance(item, dict) and item.get("type") == "function_call"
|
||||
)
|
||||
call_id = func_tool_call_item.get("call_id")
|
||||
|
||||
assert call_id == expected_id
|
||||
if call_id is not None:
|
||||
assert len(str(call_id)) <= 64
|
||||
|
||||
if is_custom:
|
||||
custom_output_item: Final = next(
|
||||
item for item in input_items if isinstance(item, dict) and item.get("type") == "custom_tool_call_output"
|
||||
)
|
||||
output_call_id: Final = custom_output_item.get("call_id")
|
||||
else:
|
||||
func_output_item: Final = next(
|
||||
item for item in input_items if isinstance(item, dict) and item.get("type") == "function_call_output"
|
||||
)
|
||||
output_call_id = func_output_item.get("call_id")
|
||||
|
||||
assert output_call_id == expected_id
|
||||
assert output_call_id == call_id
|
||||
|
||||
|
||||
def test_convert_chat_completion_messages_to_responses_api_overlong_collision_resistance():
|
||||
from litellm.completion_extras.litellm_responses_transformation.transformation import (
|
||||
LiteLLMResponsesTransformationHandler,
|
||||
)
|
||||
|
||||
handler: Final = LiteLLMResponsesTransformationHandler()
|
||||
id_1: Final = "call_" + "x" * 60 + "_1"
|
||||
id_2: Final = "call_" + "x" * 60 + "_2"
|
||||
|
||||
messages: Final[list[dict[str, object]]] = [
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": [
|
||||
{"id": id_1, "type": "function", "function": {"name": "f1", "arguments": "{}"}},
|
||||
{"id": id_2, "type": "function", "function": {"name": "f2", "arguments": "{}"}},
|
||||
],
|
||||
},
|
||||
{"role": "tool", "tool_call_id": id_1, "content": "res1"},
|
||||
{"role": "tool", "tool_call_id": id_2, "content": "res2"},
|
||||
]
|
||||
|
||||
input_items, _ = handler.convert_chat_completion_messages_to_responses_api(messages)
|
||||
calls: Final = [item for item in input_items if isinstance(item, dict) and item.get("type") == "function_call"]
|
||||
outputs: Final = [
|
||||
item for item in input_items if isinstance(item, dict) and item.get("type") == "function_call_output"
|
||||
]
|
||||
|
||||
assert len(calls) == 2
|
||||
assert len(outputs) == 2
|
||||
assert calls[0]["call_id"] != calls[1]["call_id"]
|
||||
assert calls[0]["call_id"] == outputs[0].get("call_id")
|
||||
assert calls[1]["call_id"] == outputs[1].get("call_id")
|
||||
assert len(str(calls[0]["call_id"])) <= 64
|
||||
assert len(str(calls[1]["call_id"])) <= 64
|
||||
|
||||
|
||||
def test_convert_chat_completion_messages_to_responses_api_mixed_custom_and_function_output_types():
|
||||
import hashlib
|
||||
|
||||
from litellm.completion_extras.litellm_responses_transformation.transformation import (
|
||||
LiteLLMResponsesTransformationHandler,
|
||||
)
|
||||
|
||||
handler: Final = LiteLLMResponsesTransformationHandler()
|
||||
custom_raw_id: Final = "custom_call_" + "y" * 60
|
||||
function_raw_id: Final = f"{custom_raw_id[:31]}_{hashlib.sha256(custom_raw_id.encode('utf-8')).hexdigest()[:32]}"
|
||||
|
||||
messages: Final[list[dict[str, object]]] = [
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": custom_raw_id,
|
||||
"type": "custom",
|
||||
"custom": {"name": "c_tool", "input": "{}"},
|
||||
},
|
||||
{
|
||||
"id": function_raw_id,
|
||||
"type": "function",
|
||||
"function": {"name": "f_tool", "arguments": "{}"},
|
||||
},
|
||||
],
|
||||
},
|
||||
{"role": "tool", "tool_call_id": custom_raw_id, "content": "custom_res"},
|
||||
{"role": "tool", "tool_call_id": function_raw_id, "content": "func_res"},
|
||||
]
|
||||
|
||||
input_items, _ = handler.convert_chat_completion_messages_to_responses_api(messages)
|
||||
custom_outputs: Final = [
|
||||
item for item in input_items if isinstance(item, dict) and item.get("type") == "custom_tool_call_output"
|
||||
]
|
||||
function_outputs: Final = [
|
||||
item for item in input_items if isinstance(item, dict) and item.get("type") == "function_call_output"
|
||||
]
|
||||
|
||||
assert len(custom_outputs) == 1
|
||||
assert len(function_outputs) == 1
|
||||
assert custom_outputs[0].get("call_id") == function_raw_id
|
||||
assert function_outputs[0].get("call_id") == function_raw_id
|
||||
|
||||
|
||||
def test_response_completed_carries_the_served_service_tier():
|
||||
from litellm.completion_extras.litellm_responses_transformation.transformation import (
|
||||
OpenAiResponsesToChatCompletionStreamIterator,
|
||||
|
|
@ -4560,3 +4736,4 @@ def test_every_bridged_chunk_after_response_created_carries_the_served_service_t
|
|||
relayed = [iterator.chunk_parser(event).model_dump().get("service_tier") for event in events]
|
||||
|
||||
assert relayed == ["default"] * len(events), relayed
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue