Skip to content

Variable Providers API Reference

Complete reference for dynamic variable resolution.

type Provider interface {
Name() string
Provide(ctx context.Context) (map[string]string, error)
}
func (p Provider) Name() string

Returns the provider identifier for logging and debugging.

func (p Provider) Provide(ctx context.Context) (map[string]string, error)

Resolves variables dynamically. Called before each template render.

Returns:

  • map[string]string: Variables to inject into template context
  • error: Error if resolution fails
func WithVariableProvider(provider variables.Provider) Option

Configures a variable provider for dynamic variable resolution.

Example:

conv, _ := sdk.Open("./pack.json", "chat",
sdk.WithVariableProvider(variables.NewTimeProvider()),
)

Multiple providers can be configured:

conv, _ := sdk.Open("./pack.json", "chat",
sdk.WithVariableProvider(variables.NewTimeProvider()),
sdk.WithVariableProvider(&myRAGProvider{}),
)

Injects current time and date information.

func NewTimeProvider() Provider

Variables Provided:

VariableFormatExample
current_time15:04:05”14:30:45”
current_date2006-01-02”2025-01-15”
current_datetime2006-01-02 15:04:05”2025-01-15 14:30:45”
day_of_weekMonday”Wednesday”
timezoneZone name”America/New_York”

Example:

provider := variables.NewTimeProvider()
conv, _ := sdk.Open("./pack.json", "chat",
sdk.WithVariableProvider(provider),
)
// In pack template:
// "The current time is {{current_time}} on {{day_of_week}}."

Extracts variables from conversation state metadata.

func NewStateProvider(store statestore.Store, conversationID string) Provider

Parameters:

  • store: State store containing conversation metadata
  • conversationID: ID of the conversation to read from

Variables Provided:

Any key-value pairs stored in the conversation’s metadata.

Example:

store := statestore.NewMemoryStore()
convID := "conv-123"
// Store some metadata
state, _ := store.Load(ctx, convID)
state.Metadata["user_tier"] = "premium"
state.Metadata["language"] = "en"
store.Save(ctx, convID, state)
// Use StateProvider
provider := variables.NewStateProvider(store, convID)
conv, _ := sdk.Open("./pack.json", "chat",
sdk.WithStateStore(store),
sdk.WithConversationID(convID),
sdk.WithVariableProvider(provider),
)
// In pack template:
// "User tier: {{user_tier}}, Language: {{language}}"

Combines multiple providers in sequence.

func NewChainProvider(providers ...Provider) Provider

Resolution Order:

  1. Providers are called in order
  2. Later providers override earlier ones for the same key
  3. Errors from any provider stop the chain

Example:

chain := variables.NewChainProvider(
variables.NewTimeProvider(), // Base time variables
variables.NewStateProvider(store, convID), // User state
&myRAGProvider{}, // RAG context
)
conv, _ := sdk.Open("./pack.json", "chat",
sdk.WithVariableProvider(chain),
)
type MyProvider struct {
data map[string]string
}
func (p *MyProvider) Name() string {
return "my_provider"
}
func (p *MyProvider) Provide(ctx context.Context) (map[string]string, error) {
return p.data, nil
}
type DatabaseProvider struct {
db *sql.DB
userID string
}
func NewDatabaseProvider(db *sql.DB, userID string) *DatabaseProvider {
return &DatabaseProvider{db: db, userID: userID}
}
func (p *DatabaseProvider) Name() string {
return "database"
}
func (p *DatabaseProvider) Provide(ctx context.Context) (map[string]string, error) {
var name, email string
err := p.db.QueryRowContext(ctx,
"SELECT name, email FROM users WHERE id = $1",
p.userID,
).Scan(&name, &email)
if err != nil {
return nil, fmt.Errorf("failed to load user: %w", err)
}
return map[string]string{
"user_name": name,
"user_email": email,
}, nil
}
type RAGProvider struct {
vectorDB VectorDatabase
topK int
}
func NewRAGProvider(db VectorDatabase, topK int) *RAGProvider {
return &RAGProvider{vectorDB: db, topK: topK}
}
func (p *RAGProvider) Name() string {
return "rag"
}
func (p *RAGProvider) Provide(ctx context.Context) (map[string]string, error) {
// Get query from context (set by pipeline)
query, ok := ctx.Value("current_query").(string)
if !ok || query == "" {
return nil, nil
}
results, err := p.vectorDB.Search(ctx, query, p.topK)
if err != nil {
return nil, err
}
var context strings.Builder
for i, r := range results {
context.WriteString(fmt.Sprintf("[%d] %s\n", i+1, r.Text))
}
return map[string]string{
"rag_context": context.String(),
"num_sources": strconv.Itoa(len(results)),
}, nil
}
type WeatherProvider struct {
apiKey string
location string
}
func (p *WeatherProvider) Name() string {
return "weather"
}
func (p *WeatherProvider) Provide(ctx context.Context) (map[string]string, error) {
resp, err := http.Get(fmt.Sprintf(
"https://api.weather.com/v1/current?location=%s&key=%s",
p.location, p.apiKey,
))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var weather struct {
Temp float64 `json:"temperature"`
Condition string `json:"condition"`
}
json.NewDecoder(resp.Body).Decode(&weather)
return map[string]string{
"weather_temp": fmt.Sprintf("%.1f°C", weather.Temp),
"weather_condition": weather.Condition,
}, nil
}
func (p *OptionalProvider) Provide(ctx context.Context) (map[string]string, error) {
result, err := p.fetchData(ctx)
if err != nil {
// Log but don't fail
log.Printf("Provider %s failed: %v", p.Name(), err)
return map[string]string{
"optional_data": "Not available",
}, nil
}
return result, nil
}
func (p *SlowProvider) Provide(ctx context.Context) (map[string]string, error) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
result, err := p.slowOperation(ctx)
if errors.Is(err, context.DeadlineExceeded) {
return map[string]string{
"slow_data": "Loading...",
}, nil
}
if err != nil {
return nil, err
}
return result, nil
}

When multiple providers are configured:

  1. Static variables from SetVar() / SetVars() are set first
  2. Variable providers are called in configuration order
  3. Each provider can override variables from previous providers
  4. Final variables are used for template rendering
conv.SetVar("greeting", "Hello") // Static: greeting = "Hello"
// Provider 1: greeting = "Hi", name = "User"
// Provider 2: name = "Alice"
// Final: greeting = "Hi", name = "Alice"
  1. Keep Providers Fast: Called on every Send(), avoid slow operations
  2. Cache Expensive Lookups: Use TTL-based caching for external calls
  3. Handle Errors Gracefully: Return defaults instead of failing
  4. Use Timeouts: Prevent slow providers from blocking
  5. Log Failures: Track provider issues for debugging
  6. Test Independently: Unit test providers separately