fix(transcription): honor model-config language/translate + OpenAI language form field (#10731)

fix(transcription): honor model-config language/translate, add language form field

The /v1/audio/transcriptions endpoint read only input.Language /
input.Translate from the parsed request, and the request middleware never
populates those from a multipart upload -- nor did it read a `language`
form field. As a result the model config's parameters.language /
parameters.translate (a valid PredictionOptions field under `parameters:`)
were silently ignored, and multilingual models like canary defaulted to
translating into English even when the YAML set language: ru,
translate: false (#10655).

Resolve both with clear precedence: the request form field wins, then any
language on the parsed request, then the model config default. This also
makes the endpoint honor OpenAI's `language` form parameter, which was
not read before.

Applies to both the streaming and non-streaming paths (the resolved
values are built into the shared TranscriptionRequest). Note this ensures
the language/translate flags reach the backend; whether a given engine
acts on them is up to the backend.

Closes #10655


Assisted-by: Claude:claude-opus-4-8 [Claude Code]

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
LocalAI [bot]
2026-07-07 23:16:25 +02:00
committed by GitHub
parent 97175f4b5a
commit cd65a1f645
2 changed files with 82 additions and 2 deletions

View File

@@ -33,6 +33,29 @@ import (
// @Param stream formData boolean false "stream partial results as SSE"
// @Success 200 {object} map[string]string "Response"
// @Router /v1/audio/transcriptions [post]
// resolveTranscriptionLanguage picks the transcription language. The request
// form field (OpenAI's `language` param) wins, then any language carried on the
// parsed request, and finally the model config default (parameters.language).
func resolveTranscriptionLanguage(formLanguage, requestLanguage, configLanguage string) string {
if formLanguage != "" {
return formLanguage
}
if requestLanguage != "" {
return requestLanguage
}
return configLanguage
}
// resolveTranscriptionTranslate picks the translate flag: the request form
// field (`translate`, when a valid bool) wins over the model config default
// (parameters.translate).
func resolveTranscriptionTranslate(formTranslate string, configTranslate bool) bool {
if b, err := strconv.ParseBool(formTranslate); err == nil {
return b
}
return configTranslate
}
func TranscriptEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, appConfig *config.ApplicationConfig) echo.HandlerFunc {
return func(c echo.Context) error {
input, ok := c.Get(middleware.CONTEXT_LOCALS_KEY_LOCALAI_REQUEST).(*schema.OpenAIRequest)
@@ -112,10 +135,19 @@ func TranscriptEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, app
xlog.Debug("Audio file copied", "dst", dst)
// Language/translate resolve with the request form field taking
// precedence over the model config default (parameters.language /
// parameters.translate). Previously only the parsed request was read,
// which for a multipart upload never carries these, so config-level
// settings were silently ignored and multilingual models (e.g. canary)
// defaulted to English translation. (#10655)
language := resolveTranscriptionLanguage(c.FormValue("language"), input.Language, config.Language)
translate := resolveTranscriptionTranslate(c.FormValue("translate"), config.Translate)
req := backend.TranscriptionRequest{
Audio: dst,
Language: input.Language,
Translate: input.Translate,
Language: language,
Translate: translate,
Diarize: diarize,
Prompt: prompt,
Temperature: temperature,

View File

@@ -0,0 +1,48 @@
package openai
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Transcription language/translate resolution", func() {
Describe("resolveTranscriptionLanguage", func() {
It("prefers the request form field over everything", func() {
Expect(resolveTranscriptionLanguage("en", "fr", "ru")).To(Equal("en"))
})
It("falls back to the parsed request when the form field is empty", func() {
Expect(resolveTranscriptionLanguage("", "fr", "ru")).To(Equal("fr"))
})
It("falls back to the model config default when nothing else is set", func() {
// The reporter set parameters.language: ru in the model YAML but sent
// no language form field; the config value must be honored. (#10655)
Expect(resolveTranscriptionLanguage("", "", "ru")).To(Equal("ru"))
})
It("returns empty when no source provides a language", func() {
Expect(resolveTranscriptionLanguage("", "", "")).To(Equal(""))
})
})
Describe("resolveTranscriptionTranslate", func() {
It("honors a request form field of true", func() {
Expect(resolveTranscriptionTranslate("true", false)).To(BeTrue())
})
It("honors a request form field of false over a config default of true", func() {
Expect(resolveTranscriptionTranslate("false", true)).To(BeFalse())
})
It("falls back to the model config default when the form field is absent", func() {
// parameters.translate: true in the YAML must be honored when no form
// field overrides it.
Expect(resolveTranscriptionTranslate("", true)).To(BeTrue())
})
It("defaults to the config value (false) when neither is set", func() {
Expect(resolveTranscriptionTranslate("", false)).To(BeFalse())
})
})
})