mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
Merge PR #26595 into agent staging branch
This commit is contained in:
commit
75245de895
4 changed files with 156 additions and 4 deletions
|
|
@ -156,5 +156,17 @@ class OVHCloudAudioTranscriptionConfig(BaseAudioTranscriptionConfig):
|
|||
text = response_json.get("text") or response_json.get("transcript") or ""
|
||||
response = TranscriptionResponse(text=text)
|
||||
|
||||
# OVHCloud field migration (deadline: 2026-05-11):
|
||||
# `duration` is replaced by `seconds` in STT responses.
|
||||
# Prefer `seconds`, fall back to `duration`, normalize to `duration`
|
||||
# so downstream consumers see a consistent key.
|
||||
duration = (
|
||||
response_json["seconds"]
|
||||
if "seconds" in response_json and response_json["seconds"] is not None
|
||||
else response_json.get("duration")
|
||||
)
|
||||
if duration is not None:
|
||||
response_json["duration"] = duration
|
||||
|
||||
response._hidden_params = response_json
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
|
|||
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
|
||||
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
|
||||
|
||||
|
|
@ -98,10 +99,16 @@ class OVHCloudChatCompletionStreamingHandler(BaseModelResponseIterator):
|
|||
|
||||
new_choices = []
|
||||
for choice in chunk["choices"]:
|
||||
if "delta" in choice and "reasoning" in choice["delta"]:
|
||||
choice["delta"]["reasoning_content"] = choice["delta"].get(
|
||||
"reasoning"
|
||||
)
|
||||
if "delta" in choice:
|
||||
delta = choice["delta"]
|
||||
# OVHCloud field migration (deadline: 2026-05-11):
|
||||
# `reasoning_content` is replaced by `reasoning`.
|
||||
# Normalise to `reasoning_content` so downstream consumers
|
||||
# see a consistent key during the transition window.
|
||||
reasoning_new = delta.get("reasoning")
|
||||
reasoning_legacy = delta.get("reasoning_content")
|
||||
if reasoning_new is not None and reasoning_legacy is None:
|
||||
delta["reasoning_content"] = reasoning_new
|
||||
new_choices.append(choice)
|
||||
|
||||
return ModelResponseStream(
|
||||
|
|
|
|||
|
|
@ -54,3 +54,61 @@ def test_ovhcloud_audio_transcription_config_installed():
|
|||
|
||||
assert config is not None
|
||||
assert isinstance(config, BaseAudioTranscriptionConfig)
|
||||
|
||||
|
||||
|
||||
class TestOVHCloudDurationFieldMigration:
|
||||
"""Tests for OVHCloud duration -> seconds field migration."""
|
||||
|
||||
def test_seconds_field_mapped_to_duration(self):
|
||||
"""New `seconds` field should be normalized to `duration`."""
|
||||
from litellm.llms.ovhcloud.audio_transcription.transformation import (
|
||||
OVHCloudAudioTranscriptionConfig,
|
||||
)
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
config = OVHCloudAudioTranscriptionConfig()
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {
|
||||
"text": "Hello world",
|
||||
"seconds": 3.14,
|
||||
}
|
||||
|
||||
result = config.transform_audio_transcription_response(mock_response)
|
||||
|
||||
assert result.text == "Hello world"
|
||||
assert result._hidden_params["duration"] == 3.14
|
||||
|
||||
def test_legacy_duration_field_still_works(self):
|
||||
"""Legacy `duration` field should still be accepted."""
|
||||
from litellm.llms.ovhcloud.audio_transcription.transformation import (
|
||||
OVHCloudAudioTranscriptionConfig,
|
||||
)
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
config = OVHCloudAudioTranscriptionConfig()
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {
|
||||
"text": "Hello world",
|
||||
"duration": 2.71,
|
||||
}
|
||||
|
||||
result = config.transform_audio_transcription_response(mock_response)
|
||||
|
||||
assert result.text == "Hello world"
|
||||
assert result._hidden_params["duration"] == 2.71
|
||||
|
||||
|
||||
|
||||
def test_seconds_zero_mapped_to_duration(self):
|
||||
"""seconds=0.0 must not be treated as falsy and lost."""
|
||||
from litellm.llms.ovhcloud.audio_transcription.transformation import (
|
||||
OVHCloudAudioTranscriptionConfig,
|
||||
)
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
config = OVHCloudAudioTranscriptionConfig()
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"text": "silence", "seconds": 0.0}
|
||||
result = config.transform_audio_transcription_response(mock_response)
|
||||
assert result._hidden_params["duration"] == 0.0
|
||||
|
|
@ -292,3 +292,78 @@ def test_ovhcloud_with_custom_base_url():
|
|||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
|
||||
|
||||
class TestOVHCloudReasoningFieldMigration:
|
||||
"""Tests for OVHCloud reasoning_content -> reasoning field migration."""
|
||||
|
||||
def test_streaming_new_reasoning_field(self):
|
||||
"""New `reasoning` field should be mapped to `reasoning_content`."""
|
||||
handler = OVHCloudChatCompletionStreamingHandler(
|
||||
streaming_response=iter([]),
|
||||
sync_stream=True,
|
||||
)
|
||||
chunk = {
|
||||
"id": "test-id",
|
||||
"created": 1234567890,
|
||||
"model": "test-model",
|
||||
"choices": [
|
||||
{
|
||||
"delta": {
|
||||
"role": "assistant",
|
||||
"reasoning": "Let me think...",
|
||||
},
|
||||
"index": 0,
|
||||
}
|
||||
],
|
||||
}
|
||||
result = handler.chunk_parser(chunk)
|
||||
assert result.choices[0]["delta"]["reasoning_content"] == "Let me think..."
|
||||
|
||||
def test_streaming_legacy_reasoning_content_unchanged(self):
|
||||
"""Legacy `reasoning_content` field should pass through untouched."""
|
||||
handler = OVHCloudChatCompletionStreamingHandler(
|
||||
streaming_response=iter([]),
|
||||
sync_stream=True,
|
||||
)
|
||||
chunk = {
|
||||
"id": "test-id",
|
||||
"created": 1234567890,
|
||||
"model": "test-model",
|
||||
"choices": [
|
||||
{
|
||||
"delta": {
|
||||
"role": "assistant",
|
||||
"reasoning_content": "Already correct field.",
|
||||
},
|
||||
"index": 0,
|
||||
}
|
||||
],
|
||||
}
|
||||
result = handler.chunk_parser(chunk)
|
||||
assert result.choices[0]["delta"]["reasoning_content"] == "Already correct field."
|
||||
|
||||
def test_streaming_both_fields_legacy_wins(self):
|
||||
"""When both fields present, existing `reasoning_content` is not overwritten."""
|
||||
handler = OVHCloudChatCompletionStreamingHandler(
|
||||
streaming_response=iter([]),
|
||||
sync_stream=True,
|
||||
)
|
||||
chunk = {
|
||||
"id": "test-id",
|
||||
"created": 1234567890,
|
||||
"model": "test-model",
|
||||
"choices": [
|
||||
{
|
||||
"delta": {
|
||||
"reasoning": "new field",
|
||||
"reasoning_content": "legacy field",
|
||||
},
|
||||
"index": 0,
|
||||
}
|
||||
],
|
||||
}
|
||||
result = handler.chunk_parser(chunk)
|
||||
assert result.choices[0]["delta"]["reasoning_content"] == "legacy field"
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue