prompts

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 5, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Copyright 2026 Teradata

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Package prompts provides prompt management for Loom agents.

All prompts (system prompts, tool descriptions, error messages, pattern templates) are externalized via PromptRegistry implementations. This enables:

  • Version control (track prompt changes)
  • A/B testing (test variants without code changes)
  • Hot-reload (update prompts without restarts)
  • Localization (i18n support)

Example usage:

registry := prompts.NewFileRegistry("./prompts")
systemPrompt, err := registry.Get(ctx, "agent.system.base", map[string]interface{}{
    "backend_type": "teradata",
    "session_id": "sess-123",
})

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetSessionIDFromContext

func GetSessionIDFromContext(ctx context.Context) string

GetSessionIDFromContext retrieves the session ID from context. Returns "default" if not found. This is exported so other packages (like patterns) can use the same session ID logic.

func Interpolate

func Interpolate(template string, vars map[string]interface{}) string

Interpolate performs safe variable substitution in a prompt template.

Uses {{.variable_name}} syntax (like Go templates but simpler). All values are escaped to prevent prompt injection attacks.

Example:

template := "You are a {{.role}} agent for {{.backend_type}}"
result := Interpolate(template, map[string]interface{}{
    "role": "SQL",
    "backend_type": "Teradata",
})
// Returns: "You are a SQL agent for Teradata"

func WithSessionID

func WithSessionID(ctx context.Context, sessionID string) context.Context

WithSessionID adds a session ID to the context.

Types

type ABTestingRegistry

type ABTestingRegistry struct {
	// contains filtered or unexported fields
}

ABTestingRegistry wraps a PromptRegistry with automatic variant selection.

Example:

fileRegistry := prompts.NewFileRegistry("./prompts")
selector := prompts.NewHashSelector() // Consistent per session
abRegistry := prompts.NewABTestingRegistry(fileRegistry, selector)

// Automatically selects variant based on session ID
prompt, _ := abRegistry.GetForSession(ctx, "agent.system", "sess-123", vars)

func NewABTestingRegistry

func NewABTestingRegistry(underlying PromptRegistry, selector VariantSelector) *ABTestingRegistry

NewABTestingRegistry creates an A/B testing registry wrapper.

func (*ABTestingRegistry) Get

func (r *ABTestingRegistry) Get(ctx context.Context, key string, vars map[string]interface{}) (string, error)

Get retrieves a prompt by key with automatic variant selection based on context. Uses "default" as session ID if not found in context.

func (*ABTestingRegistry) GetForSession

func (r *ABTestingRegistry) GetForSession(ctx context.Context, key string, sessionID string, vars map[string]interface{}) (string, error)

GetForSession retrieves a prompt with variant selection based on session ID.

func (*ABTestingRegistry) GetMetadata

func (r *ABTestingRegistry) GetMetadata(ctx context.Context, key string) (*PromptMetadata, error)

GetMetadata retrieves prompt metadata without the content.

func (*ABTestingRegistry) GetWithVariant

func (r *ABTestingRegistry) GetWithVariant(ctx context.Context, key string, variant string, vars map[string]interface{}) (string, error)

GetWithVariant retrieves a specific variant (bypasses selector).

func (*ABTestingRegistry) List

func (r *ABTestingRegistry) List(ctx context.Context, filters map[string]string) ([]string, error)

List lists all available prompt keys, optionally filtered.

func (*ABTestingRegistry) Reload

func (r *ABTestingRegistry) Reload(ctx context.Context) error

Reload reloads prompts from the underlying registry.

func (*ABTestingRegistry) Watch

func (r *ABTestingRegistry) Watch(ctx context.Context) (<-chan PromptUpdate, error)

Watch returns a channel that receives updates when prompts change.

type CachedRegistry

type CachedRegistry struct {
	// contains filtered or unexported fields
}

CachedRegistry wraps a PromptRegistry with an in-memory TTL cache.

This reduces load on the underlying registry (file I/O, HTTP requests, etc.) and improves performance for frequently accessed prompts.

Example:

fileRegistry := prompts.NewFileRegistry("./prompts")
cachedRegistry := prompts.NewCachedRegistry(fileRegistry, 5*time.Minute)

// First call: cache miss, loads from file
prompt1, _ := cachedRegistry.Get(ctx, "agent.system", vars)

// Second call: cache hit, instant
prompt2, _ := cachedRegistry.Get(ctx, "agent.system", vars)

func NewCachedRegistry

func NewCachedRegistry(underlying PromptRegistry, ttl time.Duration) *CachedRegistry

NewCachedRegistry creates a new cached registry with the given TTL.

A typical TTL is 5-10 minutes for production use, or 1 minute for development with frequent prompt changes.

func (*CachedRegistry) Get

func (c *CachedRegistry) Get(ctx context.Context, key string, vars map[string]interface{}) (string, error)

Get retrieves a prompt by key with variable interpolation. Uses cached content if available and not expired.

func (*CachedRegistry) GetMetadata

func (c *CachedRegistry) GetMetadata(ctx context.Context, key string) (*PromptMetadata, error)

GetMetadata retrieves prompt metadata without the content. Uses cached metadata if available and not expired.

func (*CachedRegistry) GetWithVariant

func (c *CachedRegistry) GetWithVariant(ctx context.Context, key string, variant string, vars map[string]interface{}) (string, error)

GetWithVariant retrieves a specific variant for A/B testing. Uses cached content if available and not expired.

func (*CachedRegistry) Invalidate

func (c *CachedRegistry) Invalidate()

Invalidate clears the entire cache.

func (*CachedRegistry) InvalidateKey

func (c *CachedRegistry) InvalidateKey(key string)

InvalidateKey clears cache entries for a specific prompt key (all variants).

func (*CachedRegistry) List

func (c *CachedRegistry) List(ctx context.Context, filters map[string]string) ([]string, error)

List lists all available prompt keys, optionally filtered. This is NOT cached as it's typically used less frequently.

func (*CachedRegistry) Reload

func (c *CachedRegistry) Reload(ctx context.Context) error

Reload reloads prompts from the underlying registry and clears the cache.

func (*CachedRegistry) ResetStats

func (c *CachedRegistry) ResetStats()

ResetStats resets hit/miss counters to zero.

func (*CachedRegistry) Stats

func (c *CachedRegistry) Stats() (hits, misses uint64)

Stats returns cache hit/miss statistics.

Example:

hits, misses := cachedRegistry.Stats()
hitRate := float64(hits) / float64(hits+misses)
fmt.Printf("Cache hit rate: %.2f%%\n", hitRate*100)

func (*CachedRegistry) Watch

func (c *CachedRegistry) Watch(ctx context.Context) (<-chan PromptUpdate, error)

Watch returns a channel that receives updates when prompts change. Updates automatically invalidate the cache.

type ExplicitSelector

type ExplicitSelector struct {
	// contains filtered or unexported fields
}

ExplicitSelector always returns the specified variant.

Example:

selector := prompts.NewExplicitSelector("concise")
variant, _ := selector.SelectVariant(ctx, "agent.system", [...], "sess-123")
// Returns: "concise"

func NewExplicitSelector

func NewExplicitSelector(variant string) *ExplicitSelector

NewExplicitSelector creates a selector that always returns the specified variant.

func (*ExplicitSelector) SelectVariant

func (s *ExplicitSelector) SelectVariant(ctx context.Context, key string, variants []string, sessionID string) (string, error)

type FileRegistry

type FileRegistry struct {
	// contains filtered or unexported fields
}

FileRegistry loads prompts from YAML files in a directory.

Directory structure:

prompts/
  agent/
    system.yaml          # Key: "agent.system"
    system.concise.yaml  # Key: "agent.system", variant: "concise"
  tools/
    execute_sql.yaml     # Key: "tools.execute_sql"

YAML format:

---
key: agent.system
version: 2.1.0
author:
description: Base system prompt for SQL agents
tags: [agent, system, sql]
variants: [default, concise, verbose]
variables: [backend_type, session_id, cost_threshold]
---
You are a {{.backend_type}} agent...
Example
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/teradata-labs/loom/pkg/prompts"
)

func main() {
	// Create a temporary directory for this example
	tmpDir, err := os.MkdirTemp("", "prompts-example-")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	// Create a sample prompt file
	promptContent := `---
key: agent.system.base
version: 1.0.0
author: developer@example.com
description: Base system prompt for agents
tags: [agent, system]
variants: [default]
variables: [backend_type, session_id, cost_threshold]
created_at: 2025-01-01T00:00:00Z
updated_at: 2025-01-17T00:00:00Z
---
You are a {{.backend_type}} agent.
Session: {{.session_id}}
Cost threshold: ${{.cost_threshold}}`

	// Write the prompt file
	agentDir := filepath.Join(tmpDir, "agent")
	if err := os.MkdirAll(agentDir, 0755); err != nil {
		log.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(agentDir, "system.yaml"), []byte(promptContent), 0644); err != nil {
		log.Fatal(err)
	}

	// Create registry and load prompts
	registry := prompts.NewFileRegistry(tmpDir)
	ctx := context.Background()

	if err := registry.Reload(ctx); err != nil {
		log.Fatal(err)
	}

	// Get prompt with variable interpolation
	vars := map[string]interface{}{
		"backend_type":   "PostgreSQL",
		"session_id":     "sess-abc123",
		"cost_threshold": 50.00,
	}

	prompt, err := registry.Get(ctx, "agent.system.base", vars)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(prompt)
}
Output:

You are a PostgreSQL agent.
Session: sess-abc123
Cost threshold: $50
Example (Variants)
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/teradata-labs/loom/pkg/prompts"
)

func main() {
	tmpDir, err := os.MkdirTemp("", "prompts-variants-")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	// Create default variant
	defaultContent := `---
key: greeting
version: 1.0.0
author: developer@example.com
description: Greeting prompt
tags: [greeting]
variants: [default, concise]
variables: [name]
created_at: 2025-01-01T00:00:00Z
updated_at: 2025-01-01T00:00:00Z
---
Hello {{.name}}, welcome to our system!`

	// Create concise variant
	conciseContent := `---
key: greeting
version: 1.0.0
author: developer@example.com
description: Greeting prompt
tags: [greeting]
variants: [default, concise]
variables: [name]
created_at: 2025-01-01T00:00:00Z
updated_at: 2025-01-01T00:00:00Z
---
Hi {{.name}}!`

	if err := os.WriteFile(filepath.Join(tmpDir, "greeting.yaml"), []byte(defaultContent), 0644); err != nil {
		log.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(tmpDir, "greeting.concise.yaml"), []byte(conciseContent), 0644); err != nil {
		log.Fatal(err)
	}

	registry := prompts.NewFileRegistry(tmpDir)
	ctx := context.Background()

	if err := registry.Reload(ctx); err != nil {
		log.Fatal(err)
	}

	vars := map[string]interface{}{"name": "Alice"}

	// Get default variant
	defaultPrompt, _ := registry.GetWithVariant(ctx, "greeting", "default", vars)
	fmt.Println("Default:", defaultPrompt)

	// Get concise variant
	concisePrompt, _ := registry.GetWithVariant(ctx, "greeting", "concise", vars)
	fmt.Println("Concise:", concisePrompt)

}
Output:

Default: Hello Alice, welcome to our system!
Concise: Hi Alice!

func NewFileRegistry

func NewFileRegistry(rootDir string) *FileRegistry

NewFileRegistry creates a new file-based prompt registry.

Example:

registry := prompts.NewFileRegistry("./prompts")
if err := registry.Reload(ctx); err != nil {
    log.Fatal(err)
}

func (*FileRegistry) Get

func (r *FileRegistry) Get(ctx context.Context, key string, vars map[string]interface{}) (string, error)

Get retrieves a prompt by key with variable interpolation. Returns the default variant.

func (*FileRegistry) GetMetadata

func (r *FileRegistry) GetMetadata(ctx context.Context, key string) (*PromptMetadata, error)

GetMetadata retrieves prompt metadata without the content.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/teradata-labs/loom/pkg/prompts"
)

func main() {
	tmpDir, err := os.MkdirTemp("", "prompts-metadata-")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	promptContent := `---
key: agent.system
version: 2.1.0
author:
description: Base system prompt for SQL agents
tags: [agent, system, sql]
variants: [default, concise]
variables: [backend_type, session_id]
created_at: 2025-01-01T00:00:00Z
updated_at: 2025-01-17T00:00:00Z
---
You are a {{.backend_type}} agent.`

	if err := os.WriteFile(filepath.Join(tmpDir, "agent.yaml"), []byte(promptContent), 0644); err != nil {
		log.Fatal(err)
	}

	registry := prompts.NewFileRegistry(tmpDir)
	ctx := context.Background()

	if err := registry.Reload(ctx); err != nil {
		log.Fatal(err)
	}

	metadata, err := registry.GetMetadata(ctx, "agent.system")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Key: %s\n", metadata.Key)
	fmt.Printf("Version: %s\n", metadata.Version)
	if metadata.Author != "" {
		fmt.Printf("Author: %s\n", metadata.Author)
	}
	fmt.Printf("Tags: %v\n", metadata.Tags)
	fmt.Printf("Variables: %v\n", metadata.Variables)

}
Output:

Key: agent.system
Version: 2.1.0
Tags: [agent system sql]
Variables: [backend_type session_id]

func (*FileRegistry) GetWithVariant

func (r *FileRegistry) GetWithVariant(ctx context.Context, key string, variant string, vars map[string]interface{}) (string, error)

GetWithVariant retrieves a specific variant for A/B testing.

func (*FileRegistry) List

func (r *FileRegistry) List(ctx context.Context, filters map[string]string) ([]string, error)

List lists all available prompt keys, optionally filtered.

Filters:

  • "tag": "agent"
  • "prefix": "tool."
Example
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/teradata-labs/loom/pkg/prompts"
)

func main() {
	tmpDir, err := os.MkdirTemp("", "prompts-list-")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	// Create prompts with different tags
	testPrompts := []struct {
		filename string
		key      string
		tags     string
	}{
		{"agent.yaml", "agent.system", "[agent, system]"},
		{"tool1.yaml", "tool.execute", "[tool, sql]"},
		{"tool2.yaml", "tool.schema", "[tool, metadata]"},
	}

	for _, p := range testPrompts {
		content := `---
key: ` + p.key + `
version: 1.0.0
author: test@example.com
description: Test prompt
tags: ` + p.tags + `
variants: [default]
variables: []
created_at: 2025-01-01T00:00:00Z
updated_at: 2025-01-01T00:00:00Z
---
Content.`
		if err := os.WriteFile(filepath.Join(tmpDir, p.filename), []byte(content), 0644); err != nil {
			log.Fatal(err)
		}
	}

	registry := prompts.NewFileRegistry(tmpDir)
	ctx := context.Background()

	if err := registry.Reload(ctx); err != nil {
		log.Fatal(err)
	}

	// List all prompts
	all, _ := registry.List(ctx, nil)
	fmt.Printf("All prompts: %d\n", len(all))

	// List only tool prompts
	tools, _ := registry.List(ctx, map[string]string{"tag": "tool"})
	fmt.Printf("Tool prompts: %d\n", len(tools))

	// List prompts by prefix
	agentPrompts, _ := registry.List(ctx, map[string]string{"prefix": "agent."})
	fmt.Printf("Agent prompts: %d\n", len(agentPrompts))

}
Output:

All prompts: 3
Tool prompts: 2
Agent prompts: 1

func (*FileRegistry) Reload

func (r *FileRegistry) Reload(ctx context.Context) error

Reload reloads prompts from the filesystem.

func (*FileRegistry) Watch

func (r *FileRegistry) Watch(ctx context.Context) (<-chan PromptUpdate, error)

Watch returns a channel that receives updates when prompts change. Uses fsnotify to watch for file changes in the prompts directory.

type HashSelector

type HashSelector struct{}

HashSelector uses consistent hashing based on session ID. Same session always gets the same variant (deterministic).

Example:

selector := prompts.NewHashSelector()
variant, _ := selector.SelectVariant(ctx, "agent.system", ["default", "concise"], "sess-123")
// sess-123 always gets the same variant

func NewHashSelector

func NewHashSelector() *HashSelector

NewHashSelector creates a hash-based selector.

func (*HashSelector) SelectVariant

func (s *HashSelector) SelectVariant(ctx context.Context, key string, variants []string, sessionID string) (string, error)

type PromptContent

type PromptContent struct {
	Metadata PromptMetadata
	Content  string // The actual prompt text
}

PromptContent represents the full prompt with metadata.

type PromptMetadata

type PromptMetadata struct {
	// Key is the unique identifier for this prompt.
	// Example: "agent.system.base", "tool.execute_sql.description"
	Key string

	// Version using semantic versioning (e.g., "2.1.0").
	Version string

	// Author of the prompt (email or username).
	Author string

	// Description of what this prompt does.
	Description string

	// Tags for categorization and search.
	Tags []string

	// Variants available for A/B testing.
	// Example: ["default", "concise", "verbose"]
	Variants []string

	// Variables that can be interpolated in the prompt.
	// Example: ["backend_type", "session_id", "cost_threshold"]
	Variables []string

	// Timestamps
	CreatedAt time.Time
	UpdatedAt time.Time
}

PromptMetadata contains information about a prompt.

type PromptRegistry

type PromptRegistry interface {
	// Get retrieves a prompt by key with variable interpolation.
	//
	// Variables are safely substituted using {{.variable_name}} syntax.
	// Returns the default variant unless GetWithVariant is used.
	//
	// Example:
	//   prompt, err := registry.Get(ctx, "agent.system.base", map[string]interface{}{
	//       "backend_type": "teradata",
	//       "session_id": "sess-123",
	//   })
	Get(ctx context.Context, key string, vars map[string]interface{}) (string, error)

	// GetWithVariant retrieves a specific variant for A/B testing.
	//
	// Example:
	//   prompt, err := registry.GetWithVariant(ctx, "agent.system.base", "concise", vars)
	GetWithVariant(ctx context.Context, key string, variant string, vars map[string]interface{}) (string, error)

	// GetMetadata retrieves prompt metadata without the content.
	GetMetadata(ctx context.Context, key string) (*PromptMetadata, error)

	// List lists all available prompt keys, optionally filtered.
	//
	// Filters can include:
	//   - "tag": "agent"
	//   - "prefix": "tool."
	List(ctx context.Context, filters map[string]string) ([]string, error)

	// Reload reloads prompts from the source.
	// Useful for manually triggering updates.
	Reload(ctx context.Context) error

	// Watch returns a channel that receives updates when prompts change.
	// Used for hot-reload functionality.
	//
	// Example:
	//   updates, err := registry.Watch(ctx)
	//   for update := range updates {
	//       log.Printf("Prompt %s updated to version %s", update.Key, update.Version)
	//   }
	Watch(ctx context.Context) (<-chan PromptUpdate, error)
}

PromptRegistry manages prompt retrieval and lifecycle.

Implementations can load from files, HTTP APIs (Promptio), databases, etc. All prompts support variable interpolation and A/B testing variants.

type PromptUpdate

type PromptUpdate struct {
	Key       string
	Version   string
	Action    string // "created", "modified", "deleted", "error"
	Timestamp time.Time
	Error     error // Set if Action is "error"
}

PromptUpdate represents a change notification for a prompt. Sent via Watch() channel when prompts are updated.

type RandomSelector

type RandomSelector struct {
	// contains filtered or unexported fields
}

RandomSelector randomly selects a variant (uniform distribution).

Example:

selector := prompts.NewRandomSelector()
variant, _ := selector.SelectVariant(ctx, "agent.system", ["default", "concise"], "sess-123")
// 50% chance of each variant

func NewRandomSelector

func NewRandomSelector(seed int64) *RandomSelector

NewRandomSelector creates a random selector with a given seed. Pass 0 for a random seed based on current time. Note: Uses math/rand (not crypto/rand) as A/B testing doesn't require cryptographic randomness.

func (*RandomSelector) SelectVariant

func (s *RandomSelector) SelectVariant(ctx context.Context, key string, variants []string, sessionID string) (string, error)

type VariantSelector

type VariantSelector interface {
	// SelectVariant chooses a variant from the available options.
	SelectVariant(ctx context.Context, key string, variants []string, sessionID string) (string, error)
}

VariantSelector determines which prompt variant to use for A/B testing.

Strategies:

  • Explicit: User specifies the variant (no selection logic)
  • Hash-based: Deterministic based on session ID (consistent experience)
  • Random: Random selection (true A/B testing)
  • Weighted: Weighted random (e.g., 80% default, 20% experimental)

type WeightedSelector

type WeightedSelector struct {
	// contains filtered or unexported fields
}

WeightedSelector randomly selects based on weights.

Example:

// 80% default, 20% experimental
selector := prompts.NewWeightedSelector(map[string]int{
    "default": 80,
    "experimental": 20,
})

func NewWeightedSelector

func NewWeightedSelector(weights map[string]int, seed int64) *WeightedSelector

NewWeightedSelector creates a weighted random selector. Weights don't need to sum to 100 - they're relative.

Example:

selector := prompts.NewWeightedSelector(map[string]int{
    "default": 4,      // 80% (4/5)
    "experimental": 1, // 20% (1/5)
})

func (*WeightedSelector) SelectVariant

func (s *WeightedSelector) SelectVariant(ctx context.Context, key string, variants []string, sessionID string) (string, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL