mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
Metadata: fix 401 when audio/transcriptions (#17023)
* Metadata: fix 401 when audio/transcriptions * check if str, CR fixes
This commit is contained in:
parent
650b18974f
commit
cfd35d3b14
2 changed files with 204 additions and 0 deletions
|
|
@ -39,6 +39,8 @@ async def _read_request_body(request: Optional[Request]) -> Dict:
|
|||
|
||||
if "form" in content_type:
|
||||
parsed_body = dict(await request.form())
|
||||
if "metadata" in parsed_body and isinstance(parsed_body["metadata"], str):
|
||||
parsed_body["metadata"] = json.loads(parsed_body["metadata"])
|
||||
else:
|
||||
# Read the request body
|
||||
body = await request.body()
|
||||
|
|
|
|||
|
|
@ -93,6 +93,208 @@ async def test_form_data_parsing():
|
|||
assert not hasattr(mock_request, "body") or not mock_request.body.called
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_form_data_with_json_metadata():
|
||||
"""
|
||||
Test that form data with a JSON-encoded metadata field is correctly parsed.
|
||||
|
||||
When form data includes a 'metadata' field, it comes as a JSON string that needs
|
||||
to be parsed into a Python dictionary (lines 42-43 of http_parsing_utils.py).
|
||||
"""
|
||||
# Create a mock request with form data containing JSON metadata
|
||||
mock_request = MagicMock()
|
||||
|
||||
# Metadata is sent as a JSON string in form data
|
||||
metadata_json_string = json.dumps({
|
||||
"user_id": "12345",
|
||||
"request_type": "audio_transcription",
|
||||
"tags": ["urgent", "production"],
|
||||
"custom_field": {"nested": "value"}
|
||||
})
|
||||
|
||||
test_data = {
|
||||
"model": "whisper-1",
|
||||
"file": "audio.mp3",
|
||||
"metadata": metadata_json_string # This is a JSON string, not a dict
|
||||
}
|
||||
|
||||
# Mock the form method to return the test data as an awaitable
|
||||
mock_request.form = AsyncMock(return_value=test_data)
|
||||
mock_request.headers = {"content-type": "multipart/form-data"}
|
||||
mock_request.scope = {}
|
||||
|
||||
# Parse the form data
|
||||
result = await _read_request_body(mock_request)
|
||||
|
||||
# Verify the metadata was parsed from JSON string to dict
|
||||
assert "metadata" in result
|
||||
assert isinstance(result["metadata"], dict)
|
||||
assert result["metadata"]["user_id"] == "12345"
|
||||
assert result["metadata"]["request_type"] == "audio_transcription"
|
||||
assert result["metadata"]["tags"] == ["urgent", "production"]
|
||||
assert result["metadata"]["custom_field"] == {"nested": "value"}
|
||||
|
||||
# Verify other fields remain unchanged
|
||||
assert result["model"] == "whisper-1"
|
||||
assert result["file"] == "audio.mp3"
|
||||
|
||||
# Verify form() was called
|
||||
mock_request.form.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_form_data_with_invalid_json_metadata():
|
||||
"""
|
||||
Test that form data with invalid JSON in metadata field raises an exception.
|
||||
|
||||
This tests error handling when the metadata field contains malformed JSON.
|
||||
"""
|
||||
# Create a mock request with form data containing invalid JSON metadata
|
||||
mock_request = MagicMock()
|
||||
|
||||
test_data = {
|
||||
"model": "whisper-1",
|
||||
"file": "audio.mp3",
|
||||
"metadata": '{"invalid": json}' # Invalid JSON - unquoted value
|
||||
}
|
||||
|
||||
# Mock the form method to return the test data
|
||||
mock_request.form = AsyncMock(return_value=test_data)
|
||||
mock_request.headers = {"content-type": "multipart/form-data"}
|
||||
mock_request.scope = {}
|
||||
|
||||
# Should raise JSONDecodeError when trying to parse invalid JSON metadata
|
||||
with pytest.raises(json.JSONDecodeError):
|
||||
await _read_request_body(mock_request)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_form_data_without_metadata():
|
||||
"""
|
||||
Test that form data without metadata field works correctly.
|
||||
|
||||
Ensures the metadata parsing logic doesn't break when metadata is absent.
|
||||
"""
|
||||
# Create a mock request with form data without metadata
|
||||
mock_request = MagicMock()
|
||||
|
||||
test_data = {
|
||||
"model": "whisper-1",
|
||||
"file": "audio.mp3",
|
||||
"language": "en"
|
||||
}
|
||||
|
||||
# Mock the form method to return the test data
|
||||
mock_request.form = AsyncMock(return_value=test_data)
|
||||
mock_request.headers = {"content-type": "application/x-www-form-urlencoded"}
|
||||
mock_request.scope = {}
|
||||
|
||||
# Parse the form data
|
||||
result = await _read_request_body(mock_request)
|
||||
|
||||
# Verify all fields are preserved as-is
|
||||
assert result == test_data
|
||||
assert "metadata" not in result
|
||||
assert result["model"] == "whisper-1"
|
||||
assert result["file"] == "audio.mp3"
|
||||
assert result["language"] == "en"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_form_data_with_empty_metadata():
|
||||
"""
|
||||
Test that form data with empty JSON object in metadata field is parsed correctly.
|
||||
"""
|
||||
# Create a mock request with form data containing empty metadata
|
||||
mock_request = MagicMock()
|
||||
|
||||
test_data = {
|
||||
"model": "whisper-1",
|
||||
"file": "audio.mp3",
|
||||
"metadata": "{}" # Empty JSON object as string
|
||||
}
|
||||
|
||||
# Mock the form method to return the test data
|
||||
mock_request.form = AsyncMock(return_value=test_data)
|
||||
mock_request.headers = {"content-type": "multipart/form-data"}
|
||||
mock_request.scope = {}
|
||||
|
||||
# Parse the form data
|
||||
result = await _read_request_body(mock_request)
|
||||
|
||||
# Verify the metadata was parsed to an empty dict
|
||||
assert "metadata" in result
|
||||
assert isinstance(result["metadata"], dict)
|
||||
assert result["metadata"] == {}
|
||||
assert result["model"] == "whisper-1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_form_data_with_dict_metadata():
|
||||
"""
|
||||
Test that form data with metadata already as a dict is not parsed again.
|
||||
|
||||
This handles edge cases where metadata might already be a dictionary
|
||||
(shouldn't happen in normal form data, but defensive coding).
|
||||
"""
|
||||
# Create a mock request with form data where metadata is already a dict
|
||||
mock_request = MagicMock()
|
||||
|
||||
metadata_dict = {
|
||||
"user_id": "12345",
|
||||
"tags": ["test"]
|
||||
}
|
||||
|
||||
test_data = {
|
||||
"model": "whisper-1",
|
||||
"file": "audio.mp3",
|
||||
"metadata": metadata_dict # Already a dict, not a string
|
||||
}
|
||||
|
||||
# Mock the form method to return the test data
|
||||
mock_request.form = AsyncMock(return_value=test_data)
|
||||
mock_request.headers = {"content-type": "multipart/form-data"}
|
||||
mock_request.scope = {}
|
||||
|
||||
# Parse the form data
|
||||
result = await _read_request_body(mock_request)
|
||||
|
||||
# Verify the metadata remains as a dict and is not parsed
|
||||
assert "metadata" in result
|
||||
assert isinstance(result["metadata"], dict)
|
||||
assert result["metadata"] == metadata_dict
|
||||
assert result["metadata"]["user_id"] == "12345"
|
||||
assert result["model"] == "whisper-1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_form_data_with_none_metadata():
|
||||
"""
|
||||
Test that form data with None metadata value is handled gracefully.
|
||||
"""
|
||||
# Create a mock request with form data where metadata is None
|
||||
mock_request = MagicMock()
|
||||
|
||||
test_data = {
|
||||
"model": "whisper-1",
|
||||
"file": "audio.mp3",
|
||||
"metadata": None # None value
|
||||
}
|
||||
|
||||
# Mock the form method to return the test data
|
||||
mock_request.form = AsyncMock(return_value=test_data)
|
||||
mock_request.headers = {"content-type": "multipart/form-data"}
|
||||
mock_request.scope = {}
|
||||
|
||||
# Parse the form data
|
||||
result = await _read_request_body(mock_request)
|
||||
|
||||
# Verify the metadata remains None (not parsed)
|
||||
assert "metadata" in result
|
||||
assert result["metadata"] is None
|
||||
assert result["model"] == "whisper-1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_request_body():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue