feat(transcribe): a non-Swedish upload skips the Swedish-only base engine and is not reconciled (#61)
The other engine transcribes alone, then the cloud fallback if it fails and the job is still live. A cancelled job never reaches the fallback (mutation-checked). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,9 @@ func (e *Ensemble) Plan() []progress.Planned {
|
|||||||
// progress.Reporter in ctx (a no-op when nobody listens).
|
// progress.Reporter in ctx (a no-op when nobody listens).
|
||||||
func (e *Ensemble) Transcribe(ctx context.Context, wavPath string) (domain.Transcription, error) {
|
func (e *Ensemble) Transcribe(ctx context.Context, wavPath string) (domain.Transcription, error) {
|
||||||
rep := progress.From(ctx)
|
rep := progress.From(ctx)
|
||||||
|
if domain.LanguageFrom(ctx) != domain.LangSwedish {
|
||||||
|
return e.transcribeOther(ctx, rep, wavPath)
|
||||||
|
}
|
||||||
b, berr := e.run(ctx, rep, "engine_a", e.base, wavPath)
|
b, berr := e.run(ctx, rep, "engine_a", e.base, wavPath)
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return domain.Transcription{}, err
|
return domain.Transcription{}, err
|
||||||
@@ -89,6 +92,28 @@ func (e *Ensemble) Transcribe(ctx context.Context, wavPath string) (domain.Trans
|
|||||||
return e.single(f, e.fallback.Name(), "both local engines failed"), nil
|
return e.single(f, e.fallback.Name(), "both local engines failed"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transcribeOther handles a recording that is not Swedish (tapir#61). The base engine is
|
||||||
|
// Swedish-only, so it never receives the audio, and with one reading there is nothing to
|
||||||
|
// reconcile: the other engine transcribes alone, then the cloud fallback if it fails and the
|
||||||
|
// job is still live. The phases that did not run are shown as skipped by the tracker.
|
||||||
|
func (e *Ensemble) transcribeOther(ctx context.Context, rep progress.Reporter, wavPath string) (domain.Transcription, error) {
|
||||||
|
t, err := e.run(ctx, rep, "engine_b", e.alt, wavPath)
|
||||||
|
if err == nil {
|
||||||
|
t.Endpoint = e.alt.Name()
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
if e.fallback == nil || ctx.Err() != nil {
|
||||||
|
return domain.Transcription{}, fmt.Errorf("%s: %w", e.alt.Name(), err)
|
||||||
|
}
|
||||||
|
f, ferr := e.run(ctx, rep, "fallback", e.fallback, wavPath)
|
||||||
|
if ferr != nil {
|
||||||
|
return domain.Transcription{}, errors.Join(
|
||||||
|
fmt.Errorf("%s: %w", e.alt.Name(), err), fmt.Errorf("%s: %w", e.fallback.Name(), ferr))
|
||||||
|
}
|
||||||
|
f.Endpoint = e.fallback.Name()
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
// run is one engine call, reported as a phase. A job that was stopped is not a broken
|
// run is one engine call, reported as a phase. A job that was stopped is not a broken
|
||||||
// engine, so a cancelled context is never reported as a failed phase.
|
// engine, so a cancelled context is never reported as a failed phase.
|
||||||
func (e *Ensemble) run(ctx context.Context, rep progress.Reporter, key string, be Backend, wav string) (domain.Transcription, error) {
|
func (e *Ensemble) run(ctx context.Context, rep progress.Reporter, key string, be Backend, wav string) (domain.Transcription, error) {
|
||||||
|
|||||||
@@ -204,3 +204,52 @@ func TestEnsemble_AShutdownDuringTheSecondEngineIsAnErrorNotASingleEngineSuccess
|
|||||||
assert.ErrorIs(t, err, context.Canceled, "a cancelled job must not be recorded as 'kb-whisper failed'")
|
assert.ErrorIs(t, err, context.Canceled, "a cancelled job must not be recorded as 'kb-whisper failed'")
|
||||||
assert.Empty(t, got.Text)
|
assert.Empty(t, got.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The base engine is Swedish-only. A recording that is not Swedish must never reach it, and
|
||||||
|
// there is no second opinion to reconcile: the other engine transcribes alone (tapir#61).
|
||||||
|
|
||||||
|
func autoCtx() context.Context { return domain.WithLanguage(context.Background(), domain.LangAuto) }
|
||||||
|
|
||||||
|
func TestEnsemble_AnAutoLanguageUploadNeverReachesTheBaseEngineAndIsNotReconciled(t *testing.T) {
|
||||||
|
adj := &verdictAdj{v: reconcile.VerdictAlt}
|
||||||
|
r := newEnsemble("base text", "alt text", nil, nil, true, adj)
|
||||||
|
|
||||||
|
got, err := r.e.Transcribe(autoCtx(), "/a.wav")
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"iguana/kb-whisper"}, *r.calls, "only the other engine is called")
|
||||||
|
assert.Zero(t, adj.calls, "nothing is adjudicated")
|
||||||
|
assert.Equal(t, "alt text", got.Text)
|
||||||
|
assert.Equal(t, "iguana/kb-whisper", got.Endpoint)
|
||||||
|
assert.Nil(t, got.Reconciliation, "reconciliation does not apply, so none is recorded")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsemble_AnAutoLanguageUploadFallsBackWhenItsEngineFailsAndStillSkipsTheBaseEngine(t *testing.T) {
|
||||||
|
r := newEnsemble("base text", "alt text", nil, errors.New("engine down"), true, &verdictAdj{})
|
||||||
|
|
||||||
|
got, err := r.e.Transcribe(autoCtx(), "/a.wav")
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"iguana/kb-whisper", "berget/kb-whisper"}, *r.calls)
|
||||||
|
assert.Equal(t, "berget/kb-whisper", got.Endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsemble_AnAutoLanguageUploadWithNoFallbackFailsWithoutTouchingTheBaseEngine(t *testing.T) {
|
||||||
|
r := newEnsemble("base text", "alt text", nil, errors.New("engine down"), false, &verdictAdj{})
|
||||||
|
|
||||||
|
_, err := r.e.Transcribe(autoCtx(), "/a.wav")
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, []string{"iguana/kb-whisper"}, *r.calls)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsemble_ACancelledAutoLanguageJobDoesNotReachTheFallback(t *testing.T) {
|
||||||
|
r := newEnsemble("base text", "alt text", nil, errors.New("engine down"), true, &verdictAdj{})
|
||||||
|
ctx, cancel := context.WithCancel(autoCtx())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
_, err := r.e.Transcribe(ctx, "/a.wav")
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.NotContains(t, *r.calls, "berget/kb-whisper", "a stopped job must not ship its audio to a cloud endpoint")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user