secert updated
This commit is contained in:
@@ -131,3 +131,78 @@ func TestTheEnvironmentsOwnFileBeatsTheSharedOne(t *testing.T) {
|
||||
t.Fatalf("the environment's own file does not take precedence: %v", order)
|
||||
}
|
||||
}
|
||||
|
||||
// One variable, not four.
|
||||
//
|
||||
// The assistant sat switched off for days because `ASSISTANT_PROVIDER` had not
|
||||
// been typed into a hosting platform's environment tab — a variable whose only
|
||||
// correct value is "openai", because every endpoint this server speaks is
|
||||
// OpenAI-compatible. The base URL and the model had one right answer too.
|
||||
//
|
||||
// So three of the four are constants now. The key is the only one that varies
|
||||
// between deployments and the only one that cannot live in the repository.
|
||||
func TestTheKeyAloneSwitchesTheAssistantOn(t *testing.T) {
|
||||
for _, name := range []string{
|
||||
"ASSISTANT_PROVIDER", "ASSISTANT_BASE_URL", "ASSISTANT_MODEL",
|
||||
"ASSISTANT_MODEL_BALANCED", "ASSISTANT_MODEL_FAST", "ASSISTANT_MODEL_DEEP",
|
||||
} {
|
||||
t.Setenv(name, "")
|
||||
}
|
||||
t.Setenv("ASSISTANT_API_KEY", "gsk_not-a-real-key")
|
||||
|
||||
cfg := AssistantConfig{
|
||||
Provider: assistantProvider(),
|
||||
BaseURL: env("ASSISTANT_BASE_URL", defaultAssistantBaseURL),
|
||||
APIKey: env("ASSISTANT_API_KEY", ""),
|
||||
Balanced: env("ASSISTANT_MODEL_BALANCED", env("ASSISTANT_MODEL", defaultAssistantModel)),
|
||||
}
|
||||
|
||||
if !cfg.Enabled() {
|
||||
t.Fatalf("the key alone did not switch it on: %s", cfg.Why())
|
||||
}
|
||||
if cfg.Provider != "openai" {
|
||||
t.Fatalf("provider defaulted to %q", cfg.Provider)
|
||||
}
|
||||
if cfg.ModelFor("fast") != defaultAssistantModel {
|
||||
t.Fatalf("the fast tier fell through to %q", cfg.ModelFor("fast"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoKeyIsStillOffAndSaysWhich(t *testing.T) {
|
||||
// The defaults must not make an unconfigured deployment look ready. Without
|
||||
// a key every question would reach Groq and come back 401, which reads as
|
||||
// the assistant being broken rather than as not being set up.
|
||||
cfg := AssistantConfig{
|
||||
Provider: defaultAssistantProvider,
|
||||
BaseURL: defaultAssistantBaseURL,
|
||||
Balanced: defaultAssistantModel,
|
||||
}
|
||||
if cfg.Enabled() {
|
||||
t.Fatal("reported ready with no key")
|
||||
}
|
||||
if !strings.Contains(cfg.Why(), "ASSISTANT_API_KEY") {
|
||||
t.Fatalf("did not name the one variable left to set: %q", cfg.Why())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEachDefaultIsStillOverridable(t *testing.T) {
|
||||
// Running against Ollama on a laptop must not need a code change.
|
||||
t.Setenv("ASSISTANT_PROVIDER", "ollama")
|
||||
t.Setenv("ASSISTANT_BASE_URL", "http://localhost:11434/v1")
|
||||
t.Setenv("ASSISTANT_MODEL", "llama3")
|
||||
|
||||
cfg := AssistantConfig{
|
||||
Provider: assistantProvider(),
|
||||
BaseURL: env("ASSISTANT_BASE_URL", defaultAssistantBaseURL),
|
||||
APIKey: env("ASSISTANT_API_KEY", ""),
|
||||
Balanced: env("ASSISTANT_MODEL_BALANCED", env("ASSISTANT_MODEL", defaultAssistantModel)),
|
||||
}
|
||||
|
||||
if cfg.Provider != "ollama" || cfg.Balanced != "llama3" {
|
||||
t.Fatalf("an override was ignored: %+v", cfg)
|
||||
}
|
||||
// Local endpoints need no key, so this must be on without one.
|
||||
if !cfg.Enabled() {
|
||||
t.Fatalf("a local model was refused: %s", cfg.Why())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,20 +245,59 @@ func (a AssistantConfig) ModelFor(tier string) string {
|
||||
return a.Balanced
|
||||
}
|
||||
|
||||
// What Nearle Buddy runs on unless a deployment says otherwise.
|
||||
//
|
||||
// These are in the code rather than in the environment because they are not
|
||||
// secrets and not deployment-specific — they are what this product uses. Every
|
||||
// variable that has one right answer is a variable somebody has to remember,
|
||||
// get past a platform's UI, and then re-enter on the next environment; three of
|
||||
// the four were exactly that, and the assistant sat switched off for days
|
||||
// because one of them had not been typed.
|
||||
//
|
||||
// The API key is the one that genuinely varies and genuinely cannot live here.
|
||||
const (
|
||||
defaultAssistantProvider = "openai"
|
||||
defaultAssistantBaseURL = "https://api.groq.com/openai/v1"
|
||||
defaultAssistantModel = "openai/gpt-oss-120b"
|
||||
)
|
||||
|
||||
// assistantProvider reads the provider, defaulting to the one shape this
|
||||
// server speaks.
|
||||
//
|
||||
// A deployment that names a model and a key has said what it wants; making it
|
||||
// also name a protocol it has no choice about is a variable that exists only to
|
||||
// be forgotten.
|
||||
// Every endpoint here is OpenAI-compatible — Groq, Ollama, Together and OpenAI
|
||||
// itself — so the base URL is what actually distinguishes them. Naming a
|
||||
// protocol you have no choice about is a variable that exists only to be
|
||||
// forgotten.
|
||||
func assistantProvider() string {
|
||||
if named := strings.ToLower(strings.TrimSpace(env("ASSISTANT_PROVIDER", ""))); named != "" {
|
||||
return named
|
||||
}
|
||||
if strings.TrimSpace(env("ASSISTANT_MODEL_BALANCED", env("ASSISTANT_MODEL", ""))) != "" {
|
||||
return "openai"
|
||||
return defaultAssistantProvider
|
||||
}
|
||||
|
||||
// AssistantFromEnv reads the assistant's settings, defaults and all.
|
||||
//
|
||||
// Exported and used by `Load` rather than written inline there, because the
|
||||
// live tests need the SAME reading. They used to build this struct by hand from
|
||||
// `os.Getenv`, which meant they skipped silently the moment a default was
|
||||
// introduced — they were testing a configuration production no longer uses,
|
||||
// and the one time that mattered was the day the provider stopped being
|
||||
// required and nothing noticed.
|
||||
//
|
||||
// Three of the four fields have one right answer and come from the constants
|
||||
// above. The key varies between deployments and is the only one that cannot
|
||||
// live in this repository.
|
||||
func AssistantFromEnv() AssistantConfig {
|
||||
return AssistantConfig{
|
||||
Provider: assistantProvider(),
|
||||
BaseURL: env("ASSISTANT_BASE_URL", defaultAssistantBaseURL),
|
||||
APIKey: env("ASSISTANT_API_KEY", ""),
|
||||
Fast: env("ASSISTANT_MODEL_FAST", ""),
|
||||
// ASSISTANT_MODEL alone still sets every tier, for a deployment that
|
||||
// wants one model everywhere but not this one.
|
||||
Balanced: env("ASSISTANT_MODEL_BALANCED", env("ASSISTANT_MODEL", defaultAssistantModel)),
|
||||
Deep: env("ASSISTANT_MODEL_DEEP", ""),
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsProduction is true under APP_ENV=production.
|
||||
@@ -317,20 +356,7 @@ func Load() (*Config, error) {
|
||||
BaseURL: env("EMBEDDING_BASE_URL", ""),
|
||||
},
|
||||
|
||||
Assistant: AssistantConfig{
|
||||
// Defaults to "openai" when a model is named, because every endpoint
|
||||
// this speaks is OpenAI-compatible and the base URL is what actually
|
||||
// distinguishes them. One less variable to set, and one less way to
|
||||
// have the assistant silently off.
|
||||
Provider: assistantProvider(),
|
||||
BaseURL: env("ASSISTANT_BASE_URL", ""),
|
||||
APIKey: env("ASSISTANT_API_KEY", ""),
|
||||
Fast: env("ASSISTANT_MODEL_FAST", ""),
|
||||
// ASSISTANT_MODEL alone sets every tier, for a deployment that has
|
||||
// not thought about tiers yet.
|
||||
Balanced: env("ASSISTANT_MODEL_BALANCED", env("ASSISTANT_MODEL", "")),
|
||||
Deep: env("ASSISTANT_MODEL_DEEP", ""),
|
||||
},
|
||||
Assistant: AssistantFromEnv(),
|
||||
|
||||
POSTokenSecret: env("POS_TOKEN_SECRET", ""),
|
||||
JWTSecret: env("JWT_SECRET_KEY", ""),
|
||||
|
||||
Reference in New Issue
Block a user