""" Extract tool names from request body by route/call type. Used by auth (check_tools_allowlist) and ToolPolicyGuardrail so tool-format knowledge lives in one place. Uses guardrail translation handlers where available, with standalone extractors for generate_content and MCP. """ from typing import Any, Dict, List from litellm.litellm_core_utils.api_route_to_call_types import get_call_types_for_route from litellm.llms import load_guardrail_translation_mappings from litellm.types.utils import CallTypes # Call types that have no guardrail translation handler; we use standalone extractors STANDALONE_EXTRACTORS: Dict[str, Any] = {} def _extract_generate_content_tool_names(data: dict) -> List[str]: """Google generateContent: tools[].functionDeclarations[].name""" names: List[str] = [] for tool in data.get("tools") or []: if not isinstance(tool, dict): continue for decl in tool.get("functionDeclarations") or []: if isinstance(decl, dict) and decl.get("name"): names.append(str(decl["name"])) return names def _extract_mcp_tool_names(data: dict) -> List[str]: """MCP call_tool: name or mcp_tool_name in body""" names: List[str] = [] name = data.get("name") or data.get("mcp_tool_name") if name: names.append(str(name)) return names def _register_standalone_extractors() -> None: if STANDALONE_EXTRACTORS: return STANDALONE_EXTRACTORS[ CallTypes.generate_content.value ] = _extract_generate_content_tool_names STANDALONE_EXTRACTORS[ CallTypes.agenerate_content.value ] = _extract_generate_content_tool_names STANDALONE_EXTRACTORS[CallTypes.call_mcp_tool.value] = _extract_mcp_tool_names # Tool-capable call types (routes that can send tools in the request) TOOL_CAPABLE_CALL_TYPES = frozenset( { CallTypes.completion.value, CallTypes.acompletion.value, CallTypes.responses.value, CallTypes.aresponses.value, CallTypes.anthropic_messages.value, CallTypes.generate_content.value, CallTypes.agenerate_content.value, CallTypes.call_mcp_tool.value, } ) def extract_request_tool_names(route: str, data: dict) -> List[str]: """ Extract tool names from the request body for the given route. Uses guardrail translation handlers when available, else standalone extractors for generate_content and MCP. Returns [] for non-tool-capable routes or when no tools are present. """ call_types = get_call_types_for_route(route) if not call_types: return [] _register_standalone_extractors() mappings = load_guardrail_translation_mappings() for call_type in call_types: if not isinstance(call_type, CallTypes): continue if call_type.value not in TOOL_CAPABLE_CALL_TYPES: continue if call_type.value in STANDALONE_EXTRACTORS: return STANDALONE_EXTRACTORS[call_type.value](data) handler_cls = mappings.get(call_type) if handler_cls is not None: names = handler_cls().extract_request_tool_names(data) if names: return names return []