mirror of
https://github.com/mudler/LocalAI.git
synced 2026-09-30 01:54:31 -04:00
fix(openai): reject an unknown audio response_format as a 400 before the backend runs
Transcription and diarization checked response_format only after the backend had run, and returned a plain error for an unknown value. Failover counts a plain error as a target failure, so one request with a bad response_format ran the backend on every target of a chain and tripped all of them. Check the format before the backend runs and answer 400. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-5-5 [Claude Code]
This commit is contained in:
1 parent
10f7b1ca60
commit
f20cc16033
3 files changed
+42
-4
No files matched your search
@@ -50,6 +50,22 @@ var _ = Describe("audio upload endpoints reject bad uploads as client errors", f
|
||||
Expect(he.Code).To(Equal(http.StatusBadRequest))
|
||||
}
|
||||
|
||||
// An unknown response_format is the caller's fault too, and must be
|
||||
// rejected before the backend runs: a failover chain would otherwise
|
||||
// transcribe on every target and count the error against each of them.
|
||||
for name, ec := range map[string]endpointCase{
|
||||
"transcription": cases["transcription"],
|
||||
"diarization": cases["diarization"],
|
||||
} {
|
||||
It(name+": unknown response_format", func() {
|
||||
body := "--xyz\r\nContent-Disposition: form-data; name=\"response_format\"\r\n\r\nbogus\r\n" +
|
||||
"--xyz\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.wav\"\r\n\r\nRIFF\r\n--xyz--\r\n"
|
||||
var err error
|
||||
Expect(func() { err = run(ec, "multipart/form-data; boundary=xyz", body) }).NotTo(Panic(), "the backend must not be reached")
|
||||
expectBadRequest(err)
|
||||
})
|
||||
}
|
||||
|
||||
for name, ec := range cases {
|
||||
It(name+": multipart content type without a boundary", func() {
|
||||
expectBadRequest(run(ec, "multipart/form-data", ""))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -75,6 +74,13 @@ func DiarizationEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, ap
|
||||
if responseFormat == "" {
|
||||
responseFormat = schema.DiarizationResponseFormatJson
|
||||
}
|
||||
switch responseFormat {
|
||||
case schema.DiarizationResponseFormatJson, schema.DiarizationResponseFormatJsonVerbose, schema.DiarizationResponseFormatRTTM:
|
||||
default:
|
||||
// Checked before the backend runs, for the same reason as in
|
||||
// TranscriptEndpoint.
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "invalid response_format (expected: json, verbose_json, rttm)")
|
||||
}
|
||||
|
||||
file, err := uploadedFile(c, "file")
|
||||
if err != nil {
|
||||
@@ -126,7 +132,7 @@ func DiarizationEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, ap
|
||||
case schema.DiarizationResponseFormatJsonVerbose:
|
||||
return c.JSON(http.StatusOK, result)
|
||||
default:
|
||||
return errors.New("invalid response_format (expected: json, verbose_json, rttm)")
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "invalid response_format (expected: json, verbose_json, rttm)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package openai
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
@@ -117,6 +116,13 @@ func TranscriptEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, app
|
||||
}
|
||||
}
|
||||
|
||||
// Reject an unknown format before the backend runs: the backend work
|
||||
// would be wasted, and a failover chain would count the error
|
||||
// against every target.
|
||||
if !stream && !validTranscriptionResponseFormat(responseFormat) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "invalid response_format")
|
||||
}
|
||||
|
||||
// retrieve the file data from the request
|
||||
file, err := uploadedFile(c, "file")
|
||||
if err != nil {
|
||||
@@ -230,11 +236,21 @@ func TranscriptEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, app
|
||||
}
|
||||
return c.JSON(http.StatusOK, trs)
|
||||
default:
|
||||
return errors.New("invalid response_format")
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "invalid response_format")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validTranscriptionResponseFormat(f schema.TranscriptionResponseFormatType) bool {
|
||||
switch f {
|
||||
case "", schema.TranscriptionResponseFormatLrc, schema.TranscriptionResponseFormatText,
|
||||
schema.TranscriptionResponseFormatSrt, schema.TranscriptionResponseFormatVtt,
|
||||
schema.TranscriptionResponseFormatJson, schema.TranscriptionResponseFormatJsonVerbose:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// streamTranscription emits OpenAI-format SSE events for a transcription
|
||||
// request: one `transcript.text.delta` per backend chunk, a final
|
||||
// `transcript.text.done` with the assembled text, and `[DONE]`. Backends that
|
||||
|
||||
Reference in new issue
Block a user