mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
fix(responses): match guardrail tools by ordinal in one pass
Sort the chat-tool keys once and number duplicates with groupby instead of rescanning every preceding key per position, so the guardrail merge stays O(n log n) on client-supplied tool lists. Drop the comment that restated the unsupported-tool warning in the Responses-to-chat transformation.
This commit is contained in:
parent
d7ee215c57
commit
dc12e4c2b4
3 changed files with 24 additions and 6 deletions
|
|
@ -1,5 +1,5 @@
|
|||
from collections.abc import Iterable, Mapping, Sequence
|
||||
from itertools import accumulate, chain
|
||||
from itertools import accumulate, chain, groupby
|
||||
from types import MappingProxyType
|
||||
from typing import Final, TypeAlias
|
||||
|
||||
|
|
@ -47,7 +47,11 @@ def _chat_tool_key(tool: Tool) -> str:
|
|||
|
||||
def _indexed_keys(tools: Sequence[Tool]) -> tuple[IndexedKey, ...]:
|
||||
keys: Final = tuple(_chat_tool_key(tool) for tool in tools)
|
||||
return tuple((key, keys[:position].count(key)) for position, key in enumerate(keys))
|
||||
positions_by_key: Final = groupby(sorted(range(len(keys)), key=keys.__getitem__), key=keys.__getitem__)
|
||||
ordinal_by_position: Final = MappingProxyType(
|
||||
{position: ordinal for _, positions in positions_by_key for ordinal, position in enumerate(positions)}
|
||||
)
|
||||
return tuple((key, ordinal_by_position[position]) for position, key in enumerate(keys))
|
||||
|
||||
|
||||
def _namespace_members(namespace: Tool) -> tuple[Tool, ...]:
|
||||
|
|
|
|||
|
|
@ -1903,10 +1903,6 @@ class LiteLLMCompletionResponsesConfig:
|
|||
converted: Final = convert_custom_tool_to_function_tool(tool)
|
||||
return ResponsesToolChatForm(chat_tools=() if converted is None else (converted,), web_search_options=None)
|
||||
if tool_type in ("computer_use", "image_generation", "shell"):
|
||||
# Drop unsupported Responses-API-only tool types that have no
|
||||
# Chat Completions equivalent. Passing them through verbatim
|
||||
# causes providers to reject the request with "'function' is a
|
||||
# required property".
|
||||
verbose_logger.warning(
|
||||
"Dropping Responses API tool of type '%s': it has no Chat Completions "
|
||||
"equivalent and the target provider would reject the request.",
|
||||
|
|
|
|||
|
|
@ -56,6 +56,24 @@ def test_duplicate_function_names_are_matched_by_ordinal():
|
|||
assert list(merged) == [original[0]]
|
||||
|
||||
|
||||
def test_interleaved_duplicate_names_keep_their_own_ordinals():
|
||||
original = [
|
||||
_function("dup", "a"),
|
||||
_function("other", "x"),
|
||||
_function("dup", "b"),
|
||||
_function("dup", "c"),
|
||||
_function("other", "y"),
|
||||
]
|
||||
groups = _groups(original)
|
||||
flat = _flat(groups)
|
||||
edited = {**flat[3], "function": {**flat[3]["function"], "description": "changed"}}
|
||||
|
||||
merged = merge_guardrailed_tools(original, groups, [*flat[:3], edited, flat[4]])
|
||||
|
||||
assert list(merged) == [*original[:3], {**_function("dup", "changed"), "strict": False}, original[4]]
|
||||
assert all(merged[position] is original[position] for position in (0, 1, 2, 4))
|
||||
|
||||
|
||||
def test_edited_mcp_tool_is_rewritten():
|
||||
original = [{"type": "mcp", "server_label": "deepwiki", "server_url": "https://mcp.deepwiki.com/mcp"}]
|
||||
groups = _groups(original)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue