fix(ovhcloud): fix tool calling

This commit is contained in:
Elias TOURNEUX 2026-04-17 09:05:37 -04:00
parent 850fe595ac
commit 844e050768
No known key found for this signature in database
GPG key ID: 2120EF1EFF7B6805
2 changed files with 76 additions and 67 deletions

View file

@ -7,9 +7,8 @@ More information on our website: https://endpoints.ai.cloud.ovh.net
from typing import Optional, Union, List
import httpx
from litellm.utils import ModelResponseStream, _get_model_info_helper
from litellm.utils import ModelResponseStream
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
from litellm._logging import verbose_logger
from litellm.llms.ovhcloud.utils import OVHCloudException
from litellm.llms.base_llm.base_model_iterator import BaseModelResponseIterator
from litellm.llms.base_llm.chat.transformation import BaseLLMException
@ -21,34 +20,6 @@ class OVHCloudChatConfig(OpenAIGPTConfig):
def custom_llm_provider(self) -> Optional[str]:
return "ovhcloud"
def get_supported_openai_params(self, model: str) -> list:
"""
Details about function calling support can be found here:
https://help.ovhcloud.com/csm/en-gb-public-cloud-ai-endpoints-function-calling?id=kb_article_view&sysparm_article=KB0071907
"""
supports_function_calling: Optional[bool] = None
try:
model_info = _get_model_info_helper(model, custom_llm_provider="ovhcloud")
supports_function_calling = model_info.get(
"supports_function_calling", None
)
if supports_function_calling is None:
supports_function_calling = False
except Exception as e:
verbose_logger.debug(f"Error getting supported OpenAI params: {e}")
supports_function_calling = False
optional_params = super().get_supported_openai_params(model)
if supports_function_calling is not True:
verbose_logger.debug(
"You can see our models supporting function_calling in our catalog: https://endpoints.ai.cloud.ovh.net/catalog "
)
optional_params.remove("tools")
optional_params.remove("tool_choice")
optional_params.remove("function_call")
optional_params.remove("response_format")
return optional_params
def get_complete_url(
self,
api_base: Optional[str],

View file

@ -21,6 +21,7 @@ from litellm.llms.ovhcloud.chat.transformation import (
config = OVHCloudChatConfig()
model = "ovhcloud/Mistral-7B-Instruct-v0.3"
class TestOvhCloudChatCompletionStreamingHandler:
def test_chunk_parser_successful(self):
handler = OVHCloudChatCompletionStreamingHandler(
@ -58,7 +59,7 @@ class TestOvhCloudChatCompletionStreamingHandler:
"error": {
"message": "test error",
"code": 400,
}
}
}
with pytest.raises(OVHCloudException) as exc_info:
@ -83,12 +84,10 @@ class TestOvhCloudChatCompletionStreamingHandler:
class TestOVHCloudConfig:
def test_transform_request_basic(self):
"""Test basic request transformation"""
"""Test basic request transformation"""
transformed_request = config.transform_request(
model,
messages=[
{"role": "user", "content": "Hello, world!"}
],
messages=[{"role": "user", "content": "Hello, world!"}],
optional_params={},
litellm_params={},
headers={},
@ -100,7 +99,7 @@ class TestOVHCloudConfig:
]
def test_transform_request_with_extra_body(self):
"""Test request transformation with extra_body parameters"""
"""Test request transformation with extra_body parameters"""
transformed_request = config.transform_request(
model,
messages=[{"role": "user", "content": "Hello, world!"}],
@ -115,60 +114,94 @@ class TestOVHCloudConfig:
]
def test_map_openai_params(self):
"""Test OpenAI parameter mapping"""
"""Test OpenAI parameter mapping"""
non_default_params = {
"temperature": 0.7,
"max_tokens": 100,
"top_p": 0.9,
}
mapped_params = config.map_openai_params(
non_default_params=non_default_params,
optional_params={},
model=model,
drop_params=False,
)
assert mapped_params["temperature"] == 0.7
assert mapped_params["max_tokens"] == 100
assert mapped_params["top_p"] == 0.9
def test_get_error_class(self):
"""Test error class creation"""
"""Test error class creation"""
error = config.get_error_class(
error_message="Test error",
status_code=400,
headers={"Content-Type": "application/json"}
headers={"Content-Type": "application/json"},
)
assert isinstance(error, OVHCloudException)
assert error.message == "Test error"
assert error.status_code == 400
@pytest.mark.parametrize(
"model",
[
"Meta-Llama-3_3-70B-Instruct",
"Meta-Llama-3_1-70B-Instruct",
"Mixtral-8x7B-Instruct-v0.1",
"gpt-oss-120b",
"some-model-not-in-the-cost-map",
],
)
def test_tools_not_filtered_by_static_model_map(self, model):
"""
OVHCloud AI Endpoints are OpenAI-compatible; tools/tool_choice must pass
through for any model. The server is responsible for rejecting unsupported
tool calls — LiteLLM must not strip them based on a stale static catalog.
"""
from litellm.utils import get_optional_params
params = get_optional_params(
model=model,
custom_llm_provider="ovhcloud",
tools=[
{
"type": "function",
"function": {"name": "x", "parameters": {}},
}
],
tool_choice="auto",
)
assert "tools" in params
assert "tool_choice" in params
def test_ovhcloud_integration():
import os
from litellm import completion
api_key = os.getenv("OVHCLOUD_API_KEY")
api_key = os.getenv("OVHCLOUD_API_KEY")
if not api_key:
pytest.skip("OVHCLOUD_API_KEY not set, skipping test")
response = completion(
model,
messages=[{"role": "user", "content": "Say hello in one word"}],
api_key=api_key,
max_tokens=10,
temperature=0.7
temperature=0.7,
)
assert response.choices[0].message.content
assert len(response.choices[0].message.content.strip()) > 0
assert response.model
assert response.usage
assert response.usage.total_tokens > 0
def test_OVHCloud_streaming_integration():
"""
Integration test for streaming - requires real API key
@ -176,22 +209,24 @@ def test_OVHCloud_streaming_integration():
"""
import os
from litellm import completion
api_key = os.getenv("OVHCLOUD_API_KEY")
api_key = os.getenv("OVHCLOUD_API_KEY")
if not api_key:
pytest.skip("OVHCLOUD_API_KEY not set, skipping test")
try:
print(f"🔍 Testing streaming with API key: {api_key[:6]}...{api_key[-4:]} (length: {len(api_key)})")
print(
f"🔍 Testing streaming with API key: {api_key[:6]}...{api_key[-4:]} (length: {len(api_key)})"
)
print(f"🔍 API base URL: {os.getenv('OVHCLOUD_API_BASE')}")
response = completion(
model,
messages=[{"role": "user", "content": "Count from 1 to 5"}],
api_key=api_key,
max_tokens=50,
stream=True
stream=True,
)
chunks = []
@ -215,42 +250,45 @@ def test_OVHCloud_streaming_integration():
print(f"❌ Streaming integration test error details:")
print(f" Error type: {type(e).__name__}")
print(f" Error message: {str(e)}")
if hasattr(e, 'status_code'):
if hasattr(e, "status_code"):
print(f" Status code: {e.status_code}")
if hasattr(e, 'response'):
if hasattr(e, "response"):
print(f" Response: {e.response}")
pytest.fail(f"Streaming integration test failed: {type(e).__name__}: {str(e)}")
def test_ovhcloud_with_custom_base_url():
"""
Test OVHCloud with custom base URL
"""
import os
from litellm import completion
api_key = os.getenv("OVHCLOUD_API_KEY")
api_key = os.getenv("OVHCLOUD_API_KEY")
if not api_key:
pytest.skip("OVHCLOUD_API_KEY not set, skipping test")
custom_base_url = os.getenv("OVHCLOUD_API_BASE", "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1")
custom_base_url = os.getenv(
"OVHCLOUD_API_BASE", "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1"
)
try:
response = completion(
model,
messages=[{"role": "user", "content": "Hello"}],
api_key=api_key,
api_base=custom_base_url,
max_tokens=5
max_tokens=5,
)
assert response.choices[0].message.content
print(f"✅ Custom base URL test passed: {response.choices[0].message.content}")
except Exception as e:
pytest.fail(f"Custom base URL test failed: {str(e)}")
if __name__ == "__main__":
pytest.main([__file__, "-v"])
pytest.main([__file__, "-v"])