mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
feat(sdk): expose generation and provider params on Options
Adds programmatic overrides on kit.Options for the model/provider knobs that were previously only reachable through viper.Set() — letting SDK consumers (web apps, services, embedded agents) configure kit fully in-code without polluting global viper state or shipping .kit.yml. Generation parameters: - MaxTokens int (max output tokens per response) - ThinkingLevel string (off/low/medium/high) - Temperature *float32 - TopP *float32 - TopK *int32 - FrequencyPenalty *float32 - PresencePenalty *float32 Sampling params use pointer types so explicit 0 is distinguishable from unset; nil leaves provider/per-model defaults in place. Provider configuration: - ProviderAPIKey string - ProviderURL string - TLSSkipVerify bool Implementation just pushes Options values into viper inside New(), so all existing downstream code (BuildProviderConfig, SetModel, modelSettings lookups, runtime model switching) picks them up uniformly without any new code paths. Tests added for MaxTokens, ThinkingLevel, and ProviderAPIKey.
This commit is contained in:
+105
@@ -821,6 +821,70 @@ type Options struct {
|
||||
Tools []Tool // Custom tool set. If empty, AllTools() is used.
|
||||
ExtraTools []Tool // Additional tools added alongside core/MCP/extension tools.
|
||||
|
||||
// Generation parameters. These override the corresponding values from
|
||||
// .kit.yml / KIT_* environment variables. Leaving a field at its
|
||||
// zero/nil value means "use the configured default", which in turn
|
||||
// falls back to per-model defaults (modelSettings / customModels) and
|
||||
// finally to the SDK defaults registered in setSDKDefaults().
|
||||
//
|
||||
// Pointer types are used for sampling parameters so the SDK can
|
||||
// distinguish "explicitly set to 0" from "leave alone".
|
||||
|
||||
// MaxTokens overrides the maximum output tokens per LLM response.
|
||||
// 0 = use the configured default (SDK default is 4096). Bump this
|
||||
// when generating long outputs (HTML artifacts, large refactors,
|
||||
// etc.) to avoid silent truncation mid-tool-call. The cap also
|
||||
// applies after model switches via [Kit.SetModel].
|
||||
MaxTokens int
|
||||
|
||||
// ThinkingLevel sets the reasoning effort for models that support
|
||||
// extended thinking. Valid values: "off", "low", "medium", "high".
|
||||
// "" = use the configured default (SDK default is "off"). Use
|
||||
// [Kit.SetThinkingLevel] to change at runtime.
|
||||
ThinkingLevel string
|
||||
|
||||
// Temperature controls sampling randomness (typically 0.0–2.0).
|
||||
// nil = leave provider/per-model default in place. Pointer type
|
||||
// so explicit 0.0 (deterministic) is distinguishable from "unset".
|
||||
Temperature *float32
|
||||
|
||||
// TopP is the nucleus-sampling cutoff (0.0–1.0).
|
||||
// nil = leave provider/per-model default in place.
|
||||
TopP *float32
|
||||
|
||||
// TopK limits sampling to the top K tokens.
|
||||
// nil = leave provider/per-model default in place.
|
||||
TopK *int32
|
||||
|
||||
// FrequencyPenalty discourages repeated tokens (OpenAI-family models).
|
||||
// nil = leave provider/per-model default in place.
|
||||
FrequencyPenalty *float32
|
||||
|
||||
// PresencePenalty discourages repeating topics (OpenAI-family models).
|
||||
// nil = leave provider/per-model default in place.
|
||||
PresencePenalty *float32
|
||||
|
||||
// Provider configuration. These override values normally read from
|
||||
// .kit.yml or provider-specific environment variables. Useful when
|
||||
// loading credentials from a secrets manager, pointing at custom
|
||||
// OpenAI-compatible endpoints (LiteLLM, vLLM, Azure OpenAI, internal
|
||||
// proxies), or running against self-hosted infrastructure.
|
||||
|
||||
// ProviderAPIKey overrides the API key used to authenticate with the
|
||||
// model provider. "" = use the value from config or the
|
||||
// provider-specific environment variable.
|
||||
ProviderAPIKey string
|
||||
|
||||
// ProviderURL overrides the provider endpoint. "" = use the provider's
|
||||
// default URL.
|
||||
ProviderURL string
|
||||
|
||||
// TLSSkipVerify disables TLS certificate verification on provider
|
||||
// HTTP clients. Only set this for self-signed certificates in
|
||||
// development. Once enabled here it cannot be disabled via Options
|
||||
// (use the config file or env var to opt back out).
|
||||
TLSSkipVerify bool
|
||||
|
||||
// SkipConfig, when true, skips loading .kit.yml configuration files.
|
||||
// Viper defaults (setSDKDefaults) and environment variables (KIT_*)
|
||||
// are still applied. Use this for fully programmatic configuration.
|
||||
@@ -1047,6 +1111,47 @@ func New(ctx context.Context, opts *Options) (*Kit, error) {
|
||||
}
|
||||
viper.Set("stream", opts.Streaming)
|
||||
|
||||
// Generation parameter overrides. Each Options field, when set,
|
||||
// is pushed into viper here so the existing downstream code
|
||||
// (BuildProviderConfig, SetModel, modelSettings lookups) picks
|
||||
// it up uniformly. Pointer-typed sampling params use viper.Set
|
||||
// only when non-nil so that nil means "leave provider/per-model
|
||||
// default in place" (BuildProviderConfig keys off viper.IsSet).
|
||||
if opts.MaxTokens > 0 {
|
||||
viper.Set("max-tokens", opts.MaxTokens)
|
||||
}
|
||||
if opts.ThinkingLevel != "" {
|
||||
viper.Set("thinking-level", opts.ThinkingLevel)
|
||||
}
|
||||
if opts.Temperature != nil {
|
||||
viper.Set("temperature", *opts.Temperature)
|
||||
}
|
||||
if opts.TopP != nil {
|
||||
viper.Set("top-p", *opts.TopP)
|
||||
}
|
||||
if opts.TopK != nil {
|
||||
viper.Set("top-k", *opts.TopK)
|
||||
}
|
||||
if opts.FrequencyPenalty != nil {
|
||||
viper.Set("frequency-penalty", *opts.FrequencyPenalty)
|
||||
}
|
||||
if opts.PresencePenalty != nil {
|
||||
viper.Set("presence-penalty", *opts.PresencePenalty)
|
||||
}
|
||||
|
||||
// Provider overrides. TLSSkipVerify only takes effect when true —
|
||||
// callers wanting to force-disable should use the config file or
|
||||
// env var instead.
|
||||
if opts.ProviderAPIKey != "" {
|
||||
viper.Set("provider-api-key", opts.ProviderAPIKey)
|
||||
}
|
||||
if opts.ProviderURL != "" {
|
||||
viper.Set("provider-url", opts.ProviderURL)
|
||||
}
|
||||
if opts.TLSSkipVerify {
|
||||
viper.Set("tls-skip-verify", true)
|
||||
}
|
||||
|
||||
// Resolve working directory for context/skill discovery.
|
||||
cwd = opts.SessionDir
|
||||
if cwd == "" {
|
||||
|
||||
@@ -54,6 +54,79 @@ func TestNewWithOptions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewWithGenerationOptions verifies that the SDK-only generation
|
||||
// parameter overrides on Options propagate all the way through to the
|
||||
// agent without requiring any viper.Set workarounds in caller code.
|
||||
func TestNewWithGenerationOptions(t *testing.T) {
|
||||
if os.Getenv("ANTHROPIC_API_KEY") == "" {
|
||||
t.Skip("Skipping test: ANTHROPIC_API_KEY not set")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// MaxTokens override — keep ThinkingLevel off so Anthropic's thinking
|
||||
// budget doesn't auto-bump MaxTokens above what we configured.
|
||||
t.Run("MaxTokens", func(t *testing.T) {
|
||||
const want = 12345
|
||||
host, err := kit.New(ctx, &kit.Options{
|
||||
Model: "anthropic/claude-sonnet-4-5-20250929",
|
||||
Quiet: true,
|
||||
MaxTokens: want,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Kit: %v", err)
|
||||
}
|
||||
defer func() { _ = host.Close() }()
|
||||
|
||||
if got := host.MaxTokens(); got != want {
|
||||
t.Errorf("Options.MaxTokens=%d did not propagate; Kit.MaxTokens()=%d", want, got)
|
||||
}
|
||||
})
|
||||
|
||||
// ThinkingLevel override — verified via the public getter, which
|
||||
// reads back the configured (not provider-derived) level.
|
||||
t.Run("ThinkingLevel", func(t *testing.T) {
|
||||
const want = "high"
|
||||
host, err := kit.New(ctx, &kit.Options{
|
||||
Model: "anthropic/claude-sonnet-4-5-20250929",
|
||||
Quiet: true,
|
||||
ThinkingLevel: want,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Kit: %v", err)
|
||||
}
|
||||
defer func() { _ = host.Close() }()
|
||||
|
||||
if got := host.GetThinkingLevel(); got != want {
|
||||
t.Errorf("Options.ThinkingLevel=%q did not propagate; Kit.GetThinkingLevel()=%q", want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestNewWithProviderOptions verifies that programmatic provider overrides
|
||||
// (API key, URL) take effect without env vars or config files.
|
||||
func TestNewWithProviderOptions(t *testing.T) {
|
||||
if os.Getenv("ANTHROPIC_API_KEY") == "" {
|
||||
t.Skip("Skipping test: ANTHROPIC_API_KEY not set")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Use the real key but pass it via Options instead of env. Kit should
|
||||
// authenticate successfully — proving the override reached the provider.
|
||||
apiKey := os.Getenv("ANTHROPIC_API_KEY")
|
||||
|
||||
host, err := kit.New(ctx, &kit.Options{
|
||||
Model: "anthropic/claude-sonnet-4-5-20250929",
|
||||
Quiet: true,
|
||||
ProviderAPIKey: apiKey,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Kit with ProviderAPIKey option: %v", err)
|
||||
}
|
||||
defer func() { _ = host.Close() }()
|
||||
}
|
||||
|
||||
func TestSessionManagement(t *testing.T) {
|
||||
if os.Getenv("ANTHROPIC_API_KEY") == "" {
|
||||
t.Skip("Skipping test: ANTHROPIC_API_KEY not set")
|
||||
|
||||
Reference in New Issue
Block a user