feat: upload audio, get a private Swedish transcript, discuss it (#33, ADR-033) #39
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -42,6 +43,16 @@ type UploadConfig struct {
|
||||
// NOT the summarizer default: koala/phi4-mini has an 8,192-token context and
|
||||
// returns HTTP 400 on an hour of speech (~13,000 tokens).
|
||||
UploadChatModel string
|
||||
// ReconcileBaseModel turns on two-engine transcription (ADR-034): the engine whose
|
||||
// TEXT is the base of the merged transcript (e.g. iguana/pianissimo). TranscribeModel
|
||||
// stays the other engine (e.g. kb-whisper). Empty = off.
|
||||
ReconcileBaseModel string
|
||||
// ReconcileModel is the LOCAL chat model that chooses between the two engines'
|
||||
// disagreements. Required when ReconcileBaseModel is set.
|
||||
ReconcileModel string
|
||||
// ReconcileCheckOrder asks the model twice per dispute with the reading order
|
||||
// swapped and keeps only agreement. It doubles the time; off by default.
|
||||
ReconcileCheckOrder bool
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -49,7 +60,7 @@ const (
|
||||
defaultUploadMaxMinutes = 90
|
||||
defaultTranscribeModel = "iguana/kb-whisper"
|
||||
defaultTranscribeFallbackModel = "berget/kb-whisper"
|
||||
defaultTranscribeTimeout = 30 * time.Minute
|
||||
defaultTranscribeTimeout = 60 * time.Minute
|
||||
defaultMaxUploadedChars = 90000
|
||||
defaultUploadChatModel = "iguana/gemma4-26b"
|
||||
)
|
||||
@@ -62,6 +73,8 @@ func loadUploadConfig() (UploadConfig, error) {
|
||||
FFmpegBin: envOr("TAPIR_FFMPEG_BIN", "ffmpeg"),
|
||||
FFprobeBin: envOr("TAPIR_FFPROBE_BIN", "ffprobe"),
|
||||
UploadChatModel: envOr("TAPIR_UPLOAD_CHAT_MODEL", defaultUploadChatModel),
|
||||
ReconcileBaseModel: os.Getenv("TAPIR_RECONCILE_BASE_MODEL"),
|
||||
ReconcileModel: os.Getenv("TAPIR_RECONCILE_MODEL"),
|
||||
}
|
||||
|
||||
if v := os.Getenv("TAPIR_UPLOADS_ENABLED"); v != "" {
|
||||
@@ -93,6 +106,17 @@ func loadUploadConfig() (UploadConfig, error) {
|
||||
}
|
||||
u.TranscribeTimeout = timeout
|
||||
|
||||
if v := os.Getenv("TAPIR_RECONCILE_CHECK_ORDER"); v != "" {
|
||||
on, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return UploadConfig{}, fmt.Errorf("config: TAPIR_RECONCILE_CHECK_ORDER=%q: %w", v, err)
|
||||
}
|
||||
u.ReconcileCheckOrder = on
|
||||
}
|
||||
if err := u.validateReconcile(); err != nil {
|
||||
return UploadConfig{}, err
|
||||
}
|
||||
|
||||
chars, err := intOr("TAPIR_MAX_UPLOADED_TRANSCRIPT_CHARS", defaultMaxUploadedChars)
|
||||
if err != nil {
|
||||
return UploadConfig{}, err
|
||||
@@ -116,3 +140,40 @@ func positiveInt(key string, fallback int) (int, error) {
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// localPrefixes are the host prefixes of the models that run on the homelab. Private
|
||||
// meeting content may be sent to these and to nothing else for reconciliation
|
||||
// (ADR-034): a cloud alias is refused at start-up rather than discovered later.
|
||||
var localPrefixes = []string{"koala/", "iguana/"}
|
||||
|
||||
func isLocalModel(alias string) bool {
|
||||
for _, p := range localPrefixes {
|
||||
if strings.HasPrefix(alias, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validateReconcile checks the two-engine settings. They are inert while no base
|
||||
// engine is set; once one is, the setup must be complete and entirely local.
|
||||
func (u UploadConfig) validateReconcile() error {
|
||||
if u.ReconcileBaseModel == "" {
|
||||
return nil
|
||||
}
|
||||
if u.ReconcileModel == "" {
|
||||
return fmt.Errorf("config: TAPIR_RECONCILE_BASE_MODEL is set but TAPIR_RECONCILE_MODEL (the adjudicator) is not")
|
||||
}
|
||||
for _, m := range []struct{ name, alias string }{
|
||||
{"TAPIR_RECONCILE_BASE_MODEL", u.ReconcileBaseModel},
|
||||
{"TAPIR_RECONCILE_MODEL", u.ReconcileModel},
|
||||
} {
|
||||
if !isLocalModel(m.alias) {
|
||||
return fmt.Errorf("config: %s=%q is not a local model (koala/ or iguana/): private meeting content must not leave the homelab for reconciliation", m.name, m.alias)
|
||||
}
|
||||
}
|
||||
if u.ReconcileBaseModel == u.TranscribeModel {
|
||||
return fmt.Errorf("config: TAPIR_RECONCILE_BASE_MODEL and TAPIR_TRANSCRIBE_MODEL are both %q: two engines must differ", u.TranscribeModel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user