fix: support Cursor IDE tool_choice format {"type": "auto"} (#12168)

* fix: support Cursor IDE tool_choice format {"type": "auto"}

- Update validate_chat_completion_tool_choice to normalize {"type": "auto"} to "auto"
- Handles Cursor IDE sending non-standard tool_choice format
- Add comprehensive tests for tool choice validation

Fixes #12098

* fix: return full tool_choice object for Cursor IDE format

Based on PR feedback, updated validate_chat_completion_tool_choice to return
the full tool_choice dictionary instead of just extracting the type string.
This maintains consistency with downstream code that expects the full object
structure.

- Changed behavior: {"type": "auto"} now returns {"type": "auto"} instead of "auto"
- Updated tests to reflect the new expected behavior
- Ensures compatibility with code that passes tool_choice to optional_params

Addresses feedback from PR #12168
This commit is contained in:
Cole McIntosh 2025-06-30 13:39:58 -06:00 • committed by GitHub
parent 4bb732d30f
commit a7594196cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 71 additions and 0 deletions

View file

@ -6571,6 +6571,14 @@ def validate_chat_completion_tool_choice(
elif isinstance(tool_choice, str):
return tool_choice
elif isinstance(tool_choice, dict):
# Handle Cursor IDE format: {"type": "auto"} -> return as-is
if (
tool_choice.get("type") in ["auto", "none", "required"]
and "function" not in tool_choice
):
return tool_choice
# Standard OpenAI format: {"type": "function", "function": {...}}
if tool_choice.get("type") is None or tool_choice.get("function") is None:
raise Exception(
f"Invalid tool choice, tool_choice={tool_choice}. Please ensure tool_choice follows the OpenAI spec"

View file

@ -0,0 +1,63 @@
import pytest
import sys
import os
sys.path.insert(0, os.path.abspath("../.."))
from litellm.utils import validate_chat_completion_tool_choice
def test_validate_tool_choice_none():
"""Test that None is returned as-is."""
result = validate_chat_completion_tool_choice(None)
assert result is None
def test_validate_tool_choice_string():
"""Test that string values are returned as-is."""
assert validate_chat_completion_tool_choice("auto") == "auto"
assert validate_chat_completion_tool_choice("none") == "none"
assert validate_chat_completion_tool_choice("required") == "required"
def test_validate_tool_choice_standard_dict():
"""Test standard OpenAI format with function."""
tool_choice = {"type": "function", "function": {"name": "my_function"}}
result = validate_chat_completion_tool_choice(tool_choice)
assert result == tool_choice
def test_validate_tool_choice_cursor_format():
"""Test Cursor IDE format: {"type": "auto"} -> {"type": "auto"}."""
assert validate_chat_completion_tool_choice({"type": "auto"}) == {"type": "auto"}
assert validate_chat_completion_tool_choice({"type": "none"}) == {"type": "none"}
assert validate_chat_completion_tool_choice({"type": "required"}) == {"type": "required"}
def test_validate_tool_choice_invalid_dict():
"""Test that invalid dict formats raise exceptions."""
# Missing both type and function
with pytest.raises(Exception) as exc_info:
validate_chat_completion_tool_choice({})
assert "Invalid tool choice" in str(exc_info.value)
# Invalid type value
with pytest.raises(Exception) as exc_info:
validate_chat_completion_tool_choice({"type": "invalid"})
assert "Invalid tool choice" in str(exc_info.value)
# Has type but missing function when type is "function"
with pytest.raises(Exception) as exc_info:
validate_chat_completion_tool_choice({"type": "function"})
assert "Invalid tool choice" in str(exc_info.value)
def test_validate_tool_choice_invalid_type():
"""Test that invalid types raise exceptions."""
with pytest.raises(Exception) as exc_info:
validate_chat_completion_tool_choice(123)
assert "Got=<class 'int'>" in str(exc_info.value)
with pytest.raises(Exception) as exc_info:
validate_chat_completion_tool_choice([])
assert "Got=<class 'list'>" in str(exc_info.value)