2026-05-07 20:56:18 +00:00
|
|
|
// Package chatbot holds the FastAPI agent service client configuration.
|
|
|
|
|
//
|
|
|
|
|
// The struct, env tags, and validation rules are sourced directly from
|
|
|
|
|
// plans/chatbot_plan_codex.v10.md §8 (Configuration). Required string
|
|
|
|
|
// fields use the standard `required,notEmpty` env tag pair so the
|
|
|
|
|
// caarlos0/env loader rejects empty values before Validate runs; the
|
|
|
|
|
// numeric fields default to plan-mandated values and are checked by
|
|
|
|
|
// Validate.
|
|
|
|
|
package chatbot
|
|
|
|
|
|
|
|
|
|
import "fmt"
|
|
|
|
|
|
|
|
|
|
// StateMaxBytes is the per-session state cap enforced when persisting
|
|
|
|
|
// updated_session_state from a FastAPI response. Plan §8 derived value.
|
|
|
|
|
// 64 KiB matches the plan literal `64 * 1024`.
|
|
|
|
|
const StateMaxBytes = 64 * 1024
|
|
|
|
|
|
2026-05-28 19:23:39 +00:00
|
|
|
// MaxTurnCharsCeiling is the largest legal value for MaxTurnChars.
|
|
|
|
|
// It mirrors the OpenAPI schema constraint on the prompt body at
|
|
|
|
|
// serviceAPIs/queryAPI.yaml (BotTurnAddRequest.prompt.maxLength=65536).
|
|
|
|
|
// Setting CHATBOT_MAX_TURN_CHARS above this ceiling would create a
|
|
|
|
|
// silent inconsistency: the schema validator would reject oversized
|
|
|
|
|
// prompts before the service-layer length guard ran, leaving operators
|
|
|
|
|
// with no signal that the configured cap is unreachable. Any change to
|
|
|
|
|
// this value must be made in lockstep with the OpenAPI spec.
|
|
|
|
|
const MaxTurnCharsCeiling = 65536
|
|
|
|
|
|
2026-05-07 20:56:18 +00:00
|
|
|
// ChatbotConfig is the queryAPI-side configuration block for the FastAPI
|
|
|
|
|
// chatbot agent service. Fields, env-var names, defaults, and the
|
|
|
|
|
// notEmpty/required tags are dictated verbatim by plan §8.
|
|
|
|
|
type ChatbotConfig struct {
|
|
|
|
|
// ServiceURL is the base URL of the FastAPI agent service. No
|
|
|
|
|
// envDefault: production must not have a silent default.
|
|
|
|
|
ServiceURL string `env:"CHATBOT_SERVICE_URL,required,notEmpty"`
|
|
|
|
|
|
|
|
|
|
// APIKey is the bearer credential the backend presents on each call.
|
|
|
|
|
// No envDefault for the same reason as ServiceURL.
|
|
|
|
|
APIKey string `env:"CHATBOT_API_KEY,required,notEmpty"`
|
|
|
|
|
|
|
|
|
|
// RequestTimeoutSeconds is the per-call wall-clock budget for the
|
|
|
|
|
// FastAPI client. Default 60s per plan §8.
|
|
|
|
|
RequestTimeoutSeconds int `env:"CHATBOT_REQUEST_TIMEOUT_SECONDS" envDefault:"60"`
|
|
|
|
|
|
|
|
|
|
// MaxTurnChars caps prompt and completion length in runes. Default
|
|
|
|
|
// 2000 per plan §8.
|
|
|
|
|
MaxTurnChars int `env:"CHATBOT_MAX_TURN_CHARS" envDefault:"2000"`
|
|
|
|
|
|
|
|
|
|
// RecentTurns is the conversation-history length sent to FastAPI on
|
|
|
|
|
// each request and the upper bound on session-list preview turns.
|
|
|
|
|
// Plan §8 mandates the range [1, 5]; default is 5.
|
|
|
|
|
RecentTurns int `env:"CHATBOT_RECENT_TURNS" envDefault:"5"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate enforces the numeric range constraints from plan §8.
|
|
|
|
|
// ServiceURL/APIKey emptiness is enforced by the env loader's notEmpty
|
|
|
|
|
// tag; duplicating it here would mask loader-time rejection. The error
|
|
|
|
|
// message names the offending field so test-eng's table assertions match
|
|
|
|
|
// on substring.
|
|
|
|
|
func (c ChatbotConfig) Validate() error {
|
|
|
|
|
if c.RequestTimeoutSeconds < 1 {
|
|
|
|
|
return fmt.Errorf("chatbot config: RequestTimeoutSeconds must be >= 1, got %d", c.RequestTimeoutSeconds)
|
|
|
|
|
}
|
|
|
|
|
if c.MaxTurnChars < 1 {
|
|
|
|
|
return fmt.Errorf("chatbot config: MaxTurnChars must be >= 1, got %d", c.MaxTurnChars)
|
|
|
|
|
}
|
2026-05-28 19:23:39 +00:00
|
|
|
if c.MaxTurnChars > MaxTurnCharsCeiling {
|
|
|
|
|
return fmt.Errorf(
|
|
|
|
|
"chatbot config: MaxTurnChars must be <= %d (OpenAPI prompt.maxLength), got %d",
|
|
|
|
|
MaxTurnCharsCeiling, c.MaxTurnChars)
|
|
|
|
|
}
|
2026-05-07 20:56:18 +00:00
|
|
|
if c.RecentTurns < 1 || c.RecentTurns > 5 {
|
|
|
|
|
return fmt.Errorf("chatbot config: RecentTurns must be in [1, 5], got %d", c.RecentTurns)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// InflightGraceSeconds is the grace window used by AddTurn to abandon a
|
|
|
|
|
// stuck in_flight turn. Plan §8 sets it to twice the request timeout so a
|
|
|
|
|
// single retry cycle that exhausts the timeout is not abandoned mid-flight.
|
|
|
|
|
func (c ChatbotConfig) InflightGraceSeconds() int {
|
|
|
|
|
return 2 * c.RequestTimeoutSeconds
|
|
|
|
|
}
|