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.
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.
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.
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.
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.
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. 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 ¶
- func GetSessionIDFromContext(ctx context.Context) string
- func Interpolate(template string, vars map[string]interface{}) string
- func WithSessionID(ctx context.Context, sessionID string) context.Context
- type ABTestingRegistry
- func (r *ABTestingRegistry) Get(ctx context.Context, key string, vars map[string]interface{}) (string, error)
- func (r *ABTestingRegistry) GetForSession(ctx context.Context, key string, sessionID string, vars map[string]interface{}) (string, error)
- func (r *ABTestingRegistry) GetMetadata(ctx context.Context, key string) (*PromptMetadata, error)
- func (r *ABTestingRegistry) GetWithVariant(ctx context.Context, key string, variant string, vars map[string]interface{}) (string, error)
- func (r *ABTestingRegistry) List(ctx context.Context, filters map[string]string) ([]string, error)
- func (r *ABTestingRegistry) Reload(ctx context.Context) error
- func (r *ABTestingRegistry) Watch(ctx context.Context) (<-chan PromptUpdate, error)
- type CachedRegistry
- func (c *CachedRegistry) Get(ctx context.Context, key string, vars map[string]interface{}) (string, error)
- func (c *CachedRegistry) GetMetadata(ctx context.Context, key string) (*PromptMetadata, error)
- func (c *CachedRegistry) GetWithVariant(ctx context.Context, key string, variant string, vars map[string]interface{}) (string, error)
- func (c *CachedRegistry) Invalidate()
- func (c *CachedRegistry) InvalidateKey(key string)
- func (c *CachedRegistry) List(ctx context.Context, filters map[string]string) ([]string, error)
- func (c *CachedRegistry) Reload(ctx context.Context) error
- func (c *CachedRegistry) ResetStats()
- func (c *CachedRegistry) Stats() (hits, misses uint64)
- func (c *CachedRegistry) Watch(ctx context.Context) (<-chan PromptUpdate, error)
- type ExplicitSelector
- type FileRegistry
- func (r *FileRegistry) Get(ctx context.Context, key string, vars map[string]interface{}) (string, error)
- func (r *FileRegistry) GetMetadata(ctx context.Context, key string) (*PromptMetadata, error)
- func (r *FileRegistry) GetWithVariant(ctx context.Context, key string, variant string, vars map[string]interface{}) (string, error)
- func (r *FileRegistry) List(ctx context.Context, filters map[string]string) ([]string, error)
- func (r *FileRegistry) Reload(ctx context.Context) error
- func (r *FileRegistry) Watch(ctx context.Context) (<-chan PromptUpdate, error)
- type HashSelector
- type PromptContent
- type PromptMetadata
- type PromptRegistry
- type PromptUpdate
- type RandomSelector
- type VariantSelector
- type WeightedSelector
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GetSessionIDFromContext ¶
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 ¶
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"
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) 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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)
})