feat(config): add OnboardSummarizerModel + OnboardMaxVideoSeconds (ADR-028)
Two knobs for the onboarding-burst quality work: - TAPIR_ONBOARD_SUMMARIZER_MODEL (default iguana/gemma4-26b): the stronger model the burst leads its chain with; empty collapses the burst onto the shared processor (lookupOr, so explicit-empty disables). - TAPIR_ONBOARD_MAX_VIDEO_SECONDS (default 14400/4h): upper duration bound for burst picks; 0 disables, negative clamps to 0. Table-driven tests cover defaults, explicit, disable, and invalid input.
This commit is contained in:
@@ -120,6 +120,21 @@ type Config struct {
|
||||
// caption rate gate (ADR-014) — the cap bounds count, never the pacing. Default 3.
|
||||
OnboardSummarizeCount int
|
||||
|
||||
// OnboardSummarizerModel is the summarizer alias the connect-time burst leads
|
||||
// its chain with (ADR-028) — a stronger model is affordable on the ≤3 summaries
|
||||
// that form a new user's first impression. It heads a burst-specific chain;
|
||||
// the standard chain (ADR-022) follows as resilience. Empty (or equal to
|
||||
// SummarizerModel) collapses the burst back onto the shared processor — the
|
||||
// reversibility lever. Default iguana/gemma4-26b (the brain-validated model).
|
||||
OnboardSummarizerModel string
|
||||
|
||||
// OnboardMaxVideoSeconds upper-bounds the duration of a video the onboarding
|
||||
// burst will pick (ADR-028), so the burst does not spend a scarce caption fetch
|
||||
// on a multi-hour livestream VOD that passed the live filter once it ended. Only
|
||||
// a KNOWN duration outside [MinVideoSeconds, this] is dropped; a NULL/unknown
|
||||
// duration is kept (degrade-open). 0 disables the upper bound. Default 14400 (4h).
|
||||
OnboardMaxVideoSeconds int
|
||||
|
||||
// DiscoveryInterval, when > 0, makes `serve` run in-process scheduled discovery
|
||||
// for ALL users on that cadence (ADR-018). Zero/unset = disabled, so dev and
|
||||
// tests never auto-fetch. Single-replica assumption — see cmdServe.
|
||||
@@ -169,6 +184,8 @@ const (
|
||||
defaultAutoSummarizeWindow = 7 * 24 * time.Hour
|
||||
defaultOnboardSummarizeCount = 3
|
||||
maxOnboardSummarizeCount = 5
|
||||
defaultOnboardSummarizerModel = "iguana/gemma4-26b"
|
||||
defaultOnboardMaxVideoSeconds = 14400 // 4h
|
||||
)
|
||||
|
||||
// Load reads the environment into a Config, applying defaults. It does not
|
||||
@@ -181,6 +198,7 @@ func Load() (Config, error) {
|
||||
GatewayURL: envOr("TAPIR_GATEWAY_URL", defaultGatewayURL),
|
||||
GatewayKey: os.Getenv("TAPIR_GATEWAY_KEY"),
|
||||
SummarizerModel: envOr("TAPIR_SUMMARIZER_MODEL", defaultSummarizerModel),
|
||||
OnboardSummarizerModel: lookupOr("TAPIR_ONBOARD_SUMMARIZER_MODEL", defaultOnboardSummarizerModel),
|
||||
FallbackModel: lookupOr("TAPIR_FALLBACK_MODEL", defaultFallbackModel),
|
||||
CloudFallbackModel: lookupOr("TAPIR_CLOUD_FALLBACK_MODEL", defaultCloudFallbackModel),
|
||||
DBDSN: os.Getenv("TAPIR_DB_DSN"),
|
||||
@@ -286,6 +304,15 @@ func Load() (Config, error) {
|
||||
}
|
||||
c.OnboardSummarizeCount = onboard
|
||||
|
||||
onboardMax, err := intOr("TAPIR_ONBOARD_MAX_VIDEO_SECONDS", defaultOnboardMaxVideoSeconds)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
if onboardMax < 0 {
|
||||
onboardMax = 0
|
||||
}
|
||||
c.OnboardMaxVideoSeconds = onboardMax
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -214,3 +214,62 @@ func TestValidateForAuth_PassesWhenComplete(t *testing.T) {
|
||||
t.Errorf("ValidateForAuth: unexpected error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_OnboardSummarizerModel(t *testing.T) {
|
||||
cases := []struct {
|
||||
name, env string
|
||||
set bool
|
||||
want string
|
||||
}{
|
||||
{"default", "", false, defaultOnboardSummarizerModel},
|
||||
{"explicit", "koala/some-model", true, "koala/some-model"},
|
||||
{"empty disables (collapses to shared processor)", "", true, ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
env := map[string]string{}
|
||||
if c.set {
|
||||
env["TAPIR_ONBOARD_SUMMARIZER_MODEL"] = c.env
|
||||
}
|
||||
setEnv(t, env)
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.OnboardSummarizerModel != c.want {
|
||||
t.Fatalf("OnboardSummarizerModel = %q, want %q", cfg.OnboardSummarizerModel, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_OnboardMaxVideoSeconds(t *testing.T) {
|
||||
cases := []struct {
|
||||
name, env string
|
||||
want int
|
||||
}{
|
||||
{"default", "", defaultOnboardMaxVideoSeconds},
|
||||
{"explicit", "7200", 7200},
|
||||
{"zero disables", "0", 0},
|
||||
{"negative clamps to zero", "-9", 0},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
setEnv(t, map[string]string{"TAPIR_ONBOARD_MAX_VIDEO_SECONDS": c.env})
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.OnboardMaxVideoSeconds != c.want {
|
||||
t.Fatalf("OnboardMaxVideoSeconds = %d, want %d", cfg.OnboardMaxVideoSeconds, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_OnboardMaxVideoSecondsInvalid(t *testing.T) {
|
||||
setEnv(t, map[string]string{"TAPIR_ONBOARD_MAX_VIDEO_SECONDS": "long"})
|
||||
if _, err := Load(); err == nil {
|
||||
t.Fatal("Load: want error for non-numeric TAPIR_ONBOARD_MAX_VIDEO_SECONDS")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user