fix: return proper HTTP status codes for audio voice endpoints (#3969)
Signed-off-by: Ian Eaves <ian.k.eaves@gmail.com> Signed-off-by: Yueqian Lin <linyueqian@outlook.com> Co-authored-by: Yueqian Lin <linyueqian@outlook.com> Co-authored-by: Yueqian Lin <70319226+linyueqian@users.noreply.github.com>
This commit is contained in:
3 files changed
+340
-61
No files matched your search
@@ -621,7 +621,6 @@ def _voices_upload_multipart_files(kind: str | None) -> dict[str, Any] | None:
|
||||
{"consent": "c1", "name": "v1"},
|
||||
("audio_sample", "speaker_embedding", "provided"),
|
||||
id="neither_audio_nor_embedding",
|
||||
marks=_SKIP_ISSUE_3649,
|
||||
),
|
||||
pytest.param(
|
||||
"wav_ok",
|
||||
@@ -673,7 +672,6 @@ def _voices_upload_multipart_files(kind: str | None) -> dict[str, Any] | None:
|
||||
{"consent": "consent_ok", "name": "evil/name"},
|
||||
("voice name", "invalid voice name"),
|
||||
id="invalid_name_path_sep",
|
||||
marks=_SKIP_ISSUE_3649,
|
||||
),
|
||||
pytest.param(
|
||||
"wav_ok",
|
||||
@@ -698,21 +696,18 @@ def _voices_upload_multipart_files(kind: str | None) -> dict[str, Any] | None:
|
||||
{"consent": "consent_ok", "name": "v_bad_mime_{uuid}"},
|
||||
("mime", "unsupported", "audio/mp4"),
|
||||
id="audio_unsupported_mime",
|
||||
marks=_SKIP_ISSUE_3649,
|
||||
),
|
||||
pytest.param(
|
||||
"wav_short",
|
||||
{"consent": "consent_ok", "name": "v_short_{uuid}"},
|
||||
("too short", "reference audio"),
|
||||
id="audio_too_short",
|
||||
marks=_SKIP_ISSUE_3649,
|
||||
),
|
||||
pytest.param(
|
||||
"wav_bad_body",
|
||||
{"consent": "consent_ok", "name": "v_bad_wav_{uuid}"},
|
||||
("decode", "audio", "Format not recognised"),
|
||||
id="audio_decode_error",
|
||||
marks=_SKIP_ISSUE_3649,
|
||||
),
|
||||
pytest.param(
|
||||
None,
|
||||
@@ -723,7 +718,6 @@ def _voices_upload_multipart_files(kind: str | None) -> dict[str, Any] | None:
|
||||
},
|
||||
("speaker_embedding", "valid JSON"),
|
||||
id="speaker_embedding_invalid_json",
|
||||
marks=_SKIP_ISSUE_3649,
|
||||
),
|
||||
pytest.param(
|
||||
None,
|
||||
@@ -734,7 +728,6 @@ def _voices_upload_multipart_files(kind: str | None) -> dict[str, Any] | None:
|
||||
},
|
||||
("speaker_embedding", "non-empty"),
|
||||
id="speaker_embedding_empty",
|
||||
marks=_SKIP_ISSUE_3649,
|
||||
),
|
||||
pytest.param(
|
||||
None,
|
||||
@@ -745,7 +738,6 @@ def _voices_upload_multipart_files(kind: str | None) -> dict[str, Any] | None:
|
||||
},
|
||||
("speaker_embedding", "finite"),
|
||||
id="speaker_embedding_nan",
|
||||
marks=_SKIP_ISSUE_3649,
|
||||
),
|
||||
pytest.param(
|
||||
None,
|
||||
@@ -756,7 +748,6 @@ def _voices_upload_multipart_files(kind: str | None) -> dict[str, Any] | None:
|
||||
},
|
||||
("expected", "dimensions", "1024"),
|
||||
id="speaker_embedding_wrong_dim",
|
||||
marks=_SKIP_ISSUE_3649,
|
||||
),
|
||||
pytest.param(
|
||||
"wav_ok",
|
||||
@@ -767,7 +758,6 @@ def _voices_upload_multipart_files(kind: str | None) -> dict[str, Any] | None:
|
||||
},
|
||||
("mutually exclusive", "audio_sample", "speaker_embedding"),
|
||||
id="audio_and_embedding_mutually_exclusive",
|
||||
marks=_SKIP_ISSUE_3649,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -29,6 +29,7 @@ from vllm_omni.entrypoints.openai.audio_utils_mixin import AudioMixin
|
||||
from vllm_omni.entrypoints.openai.protocol.audio import (
|
||||
BatchSpeechRequest,
|
||||
CreateAudio,
|
||||
OpenAICreateAudioGenerateRequest,
|
||||
OpenAICreateSpeechRequest,
|
||||
SpeechBatchItem,
|
||||
StreamingSpeechSessionConfig,
|
||||
@@ -2292,26 +2293,260 @@ def test_api_server_create_speech_wraps_error_response_status(mocker: MockerFixt
|
||||
)
|
||||
)
|
||||
|
||||
raw_request = _make_api_server_request(handler, path="/v1/audio/speech")
|
||||
request = OpenAICreateSpeechRequest(input="Hello")
|
||||
|
||||
response = asyncio.run(api_server_module.create_speech(request, raw_request))
|
||||
|
||||
_assert_openai_error_response(response, status_code=400, message="bad request")
|
||||
|
||||
|
||||
def _make_api_server_request(handler, *, method: str = "POST", path: str = "/v1/audio/voices") -> Request:
|
||||
app = FastAPI()
|
||||
app.state.openai_serving_speech = handler
|
||||
scope = {
|
||||
"type": "http",
|
||||
"app": app,
|
||||
"method": "POST",
|
||||
"path": "/v1/audio/speech",
|
||||
"method": method,
|
||||
"path": path,
|
||||
"headers": [],
|
||||
"query_string": b"",
|
||||
"client": ("127.0.0.1", 12345),
|
||||
"server": ("testserver", 80),
|
||||
"scheme": "http",
|
||||
}
|
||||
raw_request = Request(scope)
|
||||
return Request(scope)
|
||||
|
||||
|
||||
def _patch_api_server_base(mocker: MockerFixture):
|
||||
def _fake_create_error_response(message, err_type="BadRequestError", status_code=400, param=None):
|
||||
return ErrorResponse(
|
||||
error=ErrorInfo(
|
||||
message=message,
|
||||
type=err_type,
|
||||
param=param,
|
||||
code=getattr(status_code, "value", status_code),
|
||||
)
|
||||
)
|
||||
|
||||
fake_base = mocker.MagicMock()
|
||||
fake_base.create_error_response.side_effect = _fake_create_error_response
|
||||
mocker.patch.object(api_server_module, "base", return_value=fake_base)
|
||||
return fake_base
|
||||
|
||||
|
||||
def _assert_openai_error_response(
|
||||
response: JSONResponse,
|
||||
*,
|
||||
status_code: int,
|
||||
message: str,
|
||||
err_type: str = "BadRequestError",
|
||||
) -> None:
|
||||
assert isinstance(response, JSONResponse)
|
||||
assert response.status_code == status_code
|
||||
body = json.loads(response.body)
|
||||
assert body["error"]["code"] == status_code
|
||||
assert body["error"]["type"] == err_type
|
||||
assert message in body["error"]["message"]
|
||||
|
||||
|
||||
def test_api_server_list_voices_without_speech_handler_returns_404(mocker: MockerFixture):
|
||||
_patch_api_server_base(mocker)
|
||||
raw_request = _make_api_server_request(None, method="GET")
|
||||
|
||||
response = asyncio.run(api_server_module.list_voices(raw_request))
|
||||
|
||||
_assert_openai_error_response(
|
||||
response, status_code=404, message="does not support Speech API", err_type="NotFoundError"
|
||||
)
|
||||
|
||||
|
||||
def test_api_server_upload_voice_value_error_returns_400(mocker: MockerFixture):
|
||||
_patch_api_server_base(mocker)
|
||||
handler = mocker.MagicMock()
|
||||
handler.upload_voice = mocker.AsyncMock(side_effect=ValueError("Unsupported MIME type: audio/x-m4a"))
|
||||
raw_request = _make_api_server_request(handler)
|
||||
|
||||
response = asyncio.run(
|
||||
api_server_module.upload_voice(
|
||||
raw_request,
|
||||
audio_sample=mocker.MagicMock(),
|
||||
speaker_embedding=None,
|
||||
consent="cons_test",
|
||||
name="probe",
|
||||
)
|
||||
)
|
||||
|
||||
_assert_openai_error_response(response, status_code=400, message="Unsupported MIME type")
|
||||
|
||||
|
||||
def test_api_server_upload_voice_without_speech_handler_returns_404(mocker: MockerFixture):
|
||||
_patch_api_server_base(mocker)
|
||||
raw_request = _make_api_server_request(None)
|
||||
|
||||
response = asyncio.run(
|
||||
api_server_module.upload_voice(
|
||||
raw_request,
|
||||
consent="cons_test",
|
||||
name="probe",
|
||||
)
|
||||
)
|
||||
|
||||
_assert_openai_error_response(
|
||||
response, status_code=404, message="does not support Speech API", err_type="NotFoundError"
|
||||
)
|
||||
|
||||
|
||||
def test_api_server_upload_voice_without_input_returns_400(mocker: MockerFixture):
|
||||
_patch_api_server_base(mocker)
|
||||
raw_request = _make_api_server_request(mocker.MagicMock())
|
||||
|
||||
response = asyncio.run(
|
||||
api_server_module.upload_voice(
|
||||
raw_request,
|
||||
audio_sample=None,
|
||||
speaker_embedding=None,
|
||||
consent="cons_test",
|
||||
name="probe",
|
||||
)
|
||||
)
|
||||
|
||||
_assert_openai_error_response(response, status_code=400, message="must be provided")
|
||||
|
||||
|
||||
def test_api_server_upload_voice_with_audio_and_embedding_returns_400(mocker: MockerFixture):
|
||||
_patch_api_server_base(mocker)
|
||||
raw_request = _make_api_server_request(mocker.MagicMock())
|
||||
|
||||
response = asyncio.run(
|
||||
api_server_module.upload_voice(
|
||||
raw_request,
|
||||
audio_sample=mocker.MagicMock(),
|
||||
speaker_embedding="[0.1]",
|
||||
consent="cons_test",
|
||||
name="probe",
|
||||
)
|
||||
)
|
||||
|
||||
_assert_openai_error_response(response, status_code=400, message="mutually exclusive")
|
||||
|
||||
|
||||
def test_api_server_upload_voice_exception_returns_500(mocker: MockerFixture):
|
||||
_patch_api_server_base(mocker)
|
||||
handler = mocker.MagicMock()
|
||||
handler.upload_voice = mocker.AsyncMock(side_effect=RuntimeError("disk failed"))
|
||||
raw_request = _make_api_server_request(handler)
|
||||
|
||||
response = asyncio.run(
|
||||
api_server_module.upload_voice(
|
||||
raw_request,
|
||||
audio_sample=mocker.MagicMock(),
|
||||
speaker_embedding=None,
|
||||
consent="cons_test",
|
||||
name="probe",
|
||||
)
|
||||
)
|
||||
|
||||
_assert_openai_error_response(
|
||||
response,
|
||||
status_code=500,
|
||||
message="Failed to upload voice",
|
||||
err_type="InternalServerError",
|
||||
)
|
||||
|
||||
|
||||
def test_api_server_delete_voice_without_speech_handler_returns_404(mocker: MockerFixture):
|
||||
_patch_api_server_base(mocker)
|
||||
raw_request = _make_api_server_request(None, method="DELETE", path="/v1/audio/voices/probe")
|
||||
|
||||
response = asyncio.run(api_server_module.delete_voice("probe", raw_request))
|
||||
|
||||
_assert_openai_error_response(
|
||||
response, status_code=404, message="does not support Speech API", err_type="NotFoundError"
|
||||
)
|
||||
|
||||
|
||||
def test_api_server_delete_voice_value_error_returns_400(mocker: MockerFixture):
|
||||
_patch_api_server_base(mocker)
|
||||
handler = mocker.MagicMock()
|
||||
handler.delete_voice = mocker.AsyncMock(side_effect=ValueError("Invalid voice name"))
|
||||
raw_request = _make_api_server_request(handler, method="DELETE", path="/v1/audio/voices/probe")
|
||||
|
||||
response = asyncio.run(api_server_module.delete_voice("probe", raw_request))
|
||||
|
||||
_assert_openai_error_response(response, status_code=400, message="Invalid voice name")
|
||||
|
||||
|
||||
def test_api_server_delete_voice_not_found_returns_404(mocker: MockerFixture):
|
||||
_patch_api_server_base(mocker)
|
||||
handler = mocker.MagicMock()
|
||||
handler.delete_voice = mocker.AsyncMock(return_value=False)
|
||||
raw_request = _make_api_server_request(handler, method="DELETE", path="/v1/audio/voices/missing")
|
||||
|
||||
response = asyncio.run(api_server_module.delete_voice("missing", raw_request))
|
||||
|
||||
_assert_openai_error_response(
|
||||
response,
|
||||
status_code=404,
|
||||
message="Voice 'missing' not found",
|
||||
err_type="NotFoundError",
|
||||
)
|
||||
|
||||
|
||||
def test_api_server_delete_voice_exception_returns_500(mocker: MockerFixture):
|
||||
_patch_api_server_base(mocker)
|
||||
handler = mocker.MagicMock()
|
||||
handler.delete_voice = mocker.AsyncMock(side_effect=RuntimeError("disk failed"))
|
||||
raw_request = _make_api_server_request(handler, method="DELETE", path="/v1/audio/voices/probe")
|
||||
|
||||
response = asyncio.run(api_server_module.delete_voice("probe", raw_request))
|
||||
|
||||
_assert_openai_error_response(
|
||||
response,
|
||||
status_code=500,
|
||||
message="Failed to delete voice",
|
||||
err_type="InternalServerError",
|
||||
)
|
||||
|
||||
|
||||
def test_api_server_create_speech_without_handler_returns_404(mocker: MockerFixture):
|
||||
fake_base = _patch_api_server_base(mocker)
|
||||
raw_request = _make_api_server_request(None, path="/v1/audio/speech")
|
||||
raw_request.app.state.openai_serving_tokenization = fake_base
|
||||
request = OpenAICreateSpeechRequest(input="Hello")
|
||||
|
||||
response = asyncio.run(api_server_module.create_speech(request, raw_request))
|
||||
|
||||
assert isinstance(response, JSONResponse)
|
||||
assert response.status_code == 400
|
||||
_assert_openai_error_response(
|
||||
response, status_code=404, message="does not support Speech API", err_type="NotFoundError"
|
||||
)
|
||||
|
||||
|
||||
def test_api_server_create_speech_batch_without_handler_returns_404(mocker: MockerFixture):
|
||||
fake_base = _patch_api_server_base(mocker)
|
||||
raw_request = _make_api_server_request(None, path="/v1/audio/speech/batch")
|
||||
raw_request.app.state.openai_serving_tokenization = fake_base
|
||||
request = BatchSpeechRequest(items=[SpeechBatchItem(input="hi")])
|
||||
|
||||
response = asyncio.run(api_server_module.create_speech_batch(request, raw_request))
|
||||
|
||||
_assert_openai_error_response(
|
||||
response, status_code=404, message="does not support Speech API", err_type="NotFoundError"
|
||||
)
|
||||
|
||||
|
||||
def test_api_server_create_audio_generate_without_handler_returns_404(mocker: MockerFixture):
|
||||
fake_base = _patch_api_server_base(mocker)
|
||||
raw_request = _make_api_server_request(None, path="/v1/audio/generate")
|
||||
raw_request.app.state.openai_serving_audio_generate = None
|
||||
raw_request.app.state.openai_serving_tokenization = fake_base
|
||||
request = OpenAICreateAudioGenerateRequest(input="a bird singing")
|
||||
|
||||
response = asyncio.run(api_server_module.create_audio_generate(request, raw_request))
|
||||
|
||||
_assert_openai_error_response(
|
||||
response, status_code=404, message="does not support Audio Generate API", err_type="NotFoundError"
|
||||
)
|
||||
|
||||
|
||||
def test_api_server_create_speech_engine_error_response_includes_request_and_stage_id(mocker: MockerFixture):
|
||||
@@ -2325,26 +2560,13 @@ def test_api_server_create_speech_engine_error_response_includes_request_and_sta
|
||||
|
||||
terminate_mock = mocker.patch.object(api_server_module, "terminate_if_errored")
|
||||
|
||||
app = FastAPI()
|
||||
app.state.args = SimpleNamespace(log_error_stack=False)
|
||||
app.state.openai_serving_speech = handler
|
||||
app.state.engine_client = SimpleNamespace(
|
||||
raw_request = _make_api_server_request(handler, path="/v1/audio/speech")
|
||||
raw_request.app.state.args = SimpleNamespace(log_error_stack=False)
|
||||
raw_request.app.state.engine_client = SimpleNamespace(
|
||||
engine=SimpleNamespace(is_alive=lambda: False),
|
||||
errored=True,
|
||||
)
|
||||
app.state.server = SimpleNamespace()
|
||||
scope = {
|
||||
"type": "http",
|
||||
"app": app,
|
||||
"method": "POST",
|
||||
"path": "/v1/audio/speech",
|
||||
"headers": [],
|
||||
"query_string": b"",
|
||||
"client": ("127.0.0.1", 12345),
|
||||
"server": ("testserver", 80),
|
||||
"scheme": "http",
|
||||
}
|
||||
raw_request = Request(scope)
|
||||
raw_request.app.state.server = SimpleNamespace()
|
||||
raw_request.state.request_metadata = SimpleNamespace(request_id="speech-req-1")
|
||||
request = OpenAICreateSpeechRequest(input="Hello")
|
||||
|
||||
|
||||
@@ -347,6 +347,38 @@ def _create_engine_error_json_response(
|
||||
return JSONResponse(content=payload, status_code=status_code)
|
||||
|
||||
|
||||
def _error_response_to_json_response(
|
||||
err: ErrorResponse,
|
||||
*,
|
||||
status_code: HTTPStatus | int | None = None,
|
||||
default_status_code: HTTPStatus | int = HTTPStatus.BAD_REQUEST,
|
||||
) -> JSONResponse:
|
||||
resolved_status = int(
|
||||
status_code
|
||||
if status_code is not None
|
||||
else (err.error.code if err.error and err.error.code is not None else default_status_code)
|
||||
)
|
||||
payload = err.model_dump()
|
||||
if err.error:
|
||||
payload["error"]["code"] = resolved_status
|
||||
return JSONResponse(content=payload, status_code=resolved_status)
|
||||
|
||||
|
||||
def _create_speech_error_json_response(
|
||||
raw_request: Request,
|
||||
message: str,
|
||||
*,
|
||||
err_type: str = "BadRequestError",
|
||||
status_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
|
||||
) -> JSONResponse:
|
||||
err = base(raw_request).create_error_response(
|
||||
message=message,
|
||||
err_type=err_type,
|
||||
status_code=status_code,
|
||||
)
|
||||
return _error_response_to_json_response(err, status_code=status_code)
|
||||
|
||||
|
||||
class _DiffusionServingModels:
|
||||
"""Minimal OpenAIServingModels implementation for diffusion-only servers.
|
||||
|
||||
@@ -1231,14 +1263,16 @@ async def create_speech(request: OpenAICreateSpeechRequest, raw_request: Request
|
||||
status_code=HTTPStatus.NOT_FOUND.value,
|
||||
detail="The model does not support Speech API",
|
||||
)
|
||||
return base_server.create_error_response(message="The model does not support Speech API")
|
||||
err = base_server.create_error_response(
|
||||
message="The model does not support Speech API",
|
||||
err_type="NotFoundError",
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
return _error_response_to_json_response(err, status_code=HTTPStatus.NOT_FOUND)
|
||||
try:
|
||||
result = await handler.create_speech(request, raw_request)
|
||||
if isinstance(result, ErrorResponse):
|
||||
return JSONResponse(
|
||||
content=result.model_dump(),
|
||||
status_code=result.error.code if result.error else 400,
|
||||
)
|
||||
return _error_response_to_json_response(result)
|
||||
return result
|
||||
except (EngineGenerateError, EngineDeadError) as exc:
|
||||
return _create_engine_error_json_response(raw_request, exc)
|
||||
@@ -1267,14 +1301,16 @@ async def create_speech_batch(request: BatchSpeechRequest, raw_request: Request)
|
||||
status_code=HTTPStatus.NOT_FOUND.value,
|
||||
detail="The model does not support Speech API",
|
||||
)
|
||||
return base_server.create_error_response(message="The model does not support Speech API")
|
||||
err = base_server.create_error_response(
|
||||
message="The model does not support Speech API",
|
||||
err_type="NotFoundError",
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
return _error_response_to_json_response(err, status_code=HTTPStatus.NOT_FOUND)
|
||||
try:
|
||||
result = await handler.create_speech_batch(request)
|
||||
if isinstance(result, ErrorResponse):
|
||||
return JSONResponse(
|
||||
content=result.model_dump(),
|
||||
status_code=result.error.code if result.error else 400,
|
||||
)
|
||||
return _error_response_to_json_response(result)
|
||||
return JSONResponse(content=result.model_dump())
|
||||
except (EngineGenerateError, EngineDeadError) as exc:
|
||||
return _create_engine_error_json_response(raw_request, exc)
|
||||
@@ -1305,14 +1341,16 @@ async def create_audio_generate(request: OpenAICreateAudioGenerateRequest, raw_r
|
||||
status_code=HTTPStatus.NOT_FOUND.value,
|
||||
detail="The model does not support Audio Generate API",
|
||||
)
|
||||
return base_server.create_error_response(message="The model does not support Audio Generate API")
|
||||
err = base_server.create_error_response(
|
||||
message="The model does not support Audio Generate API",
|
||||
err_type="NotFoundError",
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
return _error_response_to_json_response(err, status_code=HTTPStatus.NOT_FOUND)
|
||||
try:
|
||||
result = await handler.create_audio_generate(request, raw_request)
|
||||
if isinstance(result, ErrorResponse):
|
||||
return JSONResponse(
|
||||
content=result.model_dump(),
|
||||
status_code=result.error.code if result.error else 400,
|
||||
)
|
||||
return _error_response_to_json_response(result)
|
||||
return result
|
||||
except (EngineGenerateError, EngineDeadError) as exc:
|
||||
return _create_engine_error_json_response(raw_request, exc)
|
||||
@@ -1342,7 +1380,12 @@ async def list_voices(raw_request: Request):
|
||||
"""
|
||||
handler = Omnispeech(raw_request)
|
||||
if handler is None:
|
||||
return base(raw_request).create_error_response(message="The model does not support Speech API")
|
||||
return _create_speech_error_json_response(
|
||||
raw_request,
|
||||
"The model does not support Speech API",
|
||||
err_type="NotFoundError",
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
|
||||
# Get all speakers (both model built-in and uploaded)
|
||||
speakers = sorted(handler.supported_speakers) if handler.supported_speakers else []
|
||||
@@ -1374,6 +1417,7 @@ async def list_voices(raw_request: Request):
|
||||
responses={
|
||||
HTTPStatus.OK.value: {"model": dict},
|
||||
HTTPStatus.BAD_REQUEST.value: {"model": ErrorResponse},
|
||||
HTTPStatus.NOT_FOUND.value: {"model": ErrorResponse},
|
||||
HTTPStatus.INTERNAL_SERVER_ERROR.value: {"model": ErrorResponse},
|
||||
},
|
||||
)
|
||||
@@ -1415,12 +1459,17 @@ async def upload_voice(
|
||||
"""
|
||||
handler = Omnispeech(raw_request)
|
||||
if handler is None:
|
||||
return base(raw_request).create_error_response(message="The model does not support Speech API")
|
||||
return _create_speech_error_json_response(
|
||||
raw_request,
|
||||
"The model does not support Speech API",
|
||||
err_type="NotFoundError",
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
|
||||
try:
|
||||
if speaker_embedding is not None and audio_sample is not None:
|
||||
return base(raw_request).create_error_response(
|
||||
message="'audio_sample' and 'speaker_embedding' are mutually exclusive"
|
||||
return _create_speech_error_json_response(
|
||||
raw_request, "'audio_sample' and 'speaker_embedding' are mutually exclusive"
|
||||
)
|
||||
if speaker_embedding is not None:
|
||||
result = await handler.upload_voice_embedding(speaker_embedding, consent, name)
|
||||
@@ -1433,23 +1482,29 @@ async def upload_voice(
|
||||
speaker_description=speaker_description,
|
||||
)
|
||||
else:
|
||||
return base(raw_request).create_error_response(
|
||||
message="Either 'audio_sample' or 'speaker_embedding' must be provided"
|
||||
return _create_speech_error_json_response(
|
||||
raw_request, "Either 'audio_sample' or 'speaker_embedding' must be provided"
|
||||
)
|
||||
|
||||
return JSONResponse(content={"success": True, "voice": result})
|
||||
|
||||
except ValueError as e:
|
||||
return base(raw_request).create_error_response(message=str(e))
|
||||
return _create_speech_error_json_response(raw_request, str(e))
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to upload voice: {e}")
|
||||
return base(raw_request).create_error_response(message=f"Failed to upload voice: {str(e)}")
|
||||
return _create_speech_error_json_response(
|
||||
raw_request,
|
||||
f"Failed to upload voice: {str(e)}",
|
||||
err_type="InternalServerError",
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/v1/audio/voices/{name}",
|
||||
responses={
|
||||
HTTPStatus.OK.value: {"model": dict},
|
||||
HTTPStatus.BAD_REQUEST.value: {"model": ErrorResponse},
|
||||
HTTPStatus.NOT_FOUND.value: {"model": ErrorResponse},
|
||||
HTTPStatus.INTERNAL_SERVER_ERROR.value: {"model": ErrorResponse},
|
||||
},
|
||||
@@ -1469,24 +1524,36 @@ async def delete_voice(name: str, raw_request: Request):
|
||||
"""
|
||||
handler = Omnispeech(raw_request)
|
||||
if handler is None:
|
||||
return base(raw_request).create_error_response(message="The model does not support Speech API")
|
||||
return _create_speech_error_json_response(
|
||||
raw_request,
|
||||
"The model does not support Speech API",
|
||||
err_type="NotFoundError",
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
|
||||
try:
|
||||
# Delete the voice
|
||||
success = await handler.delete_voice(name)
|
||||
if not success:
|
||||
return JSONResponse(
|
||||
content={"success": False, "error": f"Voice '{name}' not found"},
|
||||
status_code=HTTPStatus.NOT_FOUND.value,
|
||||
return _create_speech_error_json_response(
|
||||
raw_request,
|
||||
f"Voice '{name}' not found",
|
||||
err_type="NotFoundError",
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
|
||||
return JSONResponse(content={"success": True, "message": f"Voice '{name}' deleted successfully"})
|
||||
|
||||
except ValueError as e:
|
||||
return base(raw_request).create_error_response(message=str(e))
|
||||
return _create_speech_error_json_response(raw_request, str(e))
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to delete voice '{name}': {e}")
|
||||
return base(raw_request).create_error_response(message=f"Failed to delete voice: {str(e)}")
|
||||
return _create_speech_error_json_response(
|
||||
raw_request,
|
||||
f"Failed to delete voice: {str(e)}",
|
||||
err_type="InternalServerError",
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
|
||||
@router.websocket("/v1/audio/speech/stream")
|
||||
|
||||
Reference in new issue
Block a user