Documentation
¶
Overview ¶
Package promptbuilder provides a safe, injection-resistant prompt construction library that leverages Go's standard encoding packages to automatically handle escaping and formatting. Similar to SQL prepared statements, but for LLM prompts.
Overview ¶
The promptbuilder package allows developers to construct prompts with dynamic content while preventing prompt injection attacks. It achieves this by:
- Using compile-time type safety to ensure templates come from developers
- Automatically escaping user-provided data through standard encoders
- Preventing transitive substitutions through single-pass tokenization
- Immutable prompt instances - all binding methods return new instances
Basic Usage ¶
Create a prompt template with placeholders and bind values to them:
import "chainguard.dev/driftlessaf/agents/promptbuilder"
// Templates must be literal strings (compile-time safety)
p, err := promptbuilder.NewPrompt(`
Analyze the following data:
{{data}}
Instructions: {{instructions}}
`)
if err != nil {
// Handle invalid template error
}
// Bind user data as JSON (automatically escaped)
p, err = p.BindJSON("data", userData)
if err != nil {
// Handle binding error
}
// Bind developer-controlled literal strings
p, err = p.BindStringLiteral("instructions", "Find patterns")
if err != nil {
// Handle binding error
}
// Build the final prompt
result, err := p.Build()
if err != nil {
// Handle unbound placeholder error
}
Binding Methods ¶
The package provides multiple binding methods for different data formats:
// BindStringLiteral - For developer-controlled strings only
p, err = p.BindStringLiteral("key", "literal value")
// BindJSON - Marshals data as indented JSON
p, err = p.BindJSON("data", struct{Name string}{"Alice"})
// BindXML - Marshals data as indented XML
p, err = p.BindXML("config", xmlStruct)
// BindYAML - Marshals data as YAML
p, err = p.BindYAML("settings", yamlData)
Each method also has a Must variant that panics on error:
p = p.MustBindStringLiteral("key", "value")
p = p.MustBindJSON("data", jsonData)
p = p.MustBindXML("config", xmlData)
p = p.MustBindYAML("settings", yamlData)
Template Syntax ¶
Templates use {{name}} placeholders for bindings. Valid binding names must contain only letters, digits, and underscores. Invalid identifiers will cause NewPrompt to return an error.
Valid examples:
- {{data}}
- {{user_input}}
- {{item1}}
Invalid examples:
- {{}} (empty)
- {{test-case}} (contains hyphen)
- {{test.value}} (contains dot)
Bindable Interface ¶
Types can implement the Bindable interface to provide custom binding logic. Executors expect request types to implement this interface so that prompts can be bound to the specific data in each request:
type Bindable interface {
Bind(prompt *Prompt) (*Prompt, error)
}
The package provides a Noop implementation that returns the prompt unchanged, useful as an embedded field for types that conditionally bind values or when no binding is needed:
type MyRequest struct {
promptbuilder.Noop // Provides default Bind implementation
Data string
}
Security Properties ¶
1. No Raw User Input - User data must go through encoders (XML, JSON, or YAML) 2. Type-Safe Literals - stringLiteral ensures only developer literals bypass encoding 3. Automatic Escaping - Encoding libraries handle all escaping for user data 4. No Transitive Substitution - Single-pass tokenization prevents recursive replacement 5. Immutable Prompts - All operations return new instances, preventing mutation
Must Functions ¶
For convenience in package-level variables and when templates are known to be valid:
var template = promptbuilder.MustNewPrompt(`Hello {{name}}!`)
The Must helper can wrap any (*Prompt, error) returning function:
p := promptbuilder.Must(promptbuilder.NewPrompt(literalTemplate))
Error Handling ¶
The package returns errors for:
- Invalid template syntax (malformed placeholders)
- Binding to non-existent placeholders
- Attempting to rebind already-bound placeholders
- Building with unbound placeholders
- Marshaling failures in BindJSON/BindXML/BindYAML
Thread Safety ¶
Prompt instances are immutable after creation. Binding methods return new instances, making the original safe to share across goroutines. However, concurrent calls to Build() on the same instance are safe only because the instance is immutable.
Index ¶
- type Bindable
- type Noop
- type Prompt
- func (p *Prompt) BindJSON(name string, data any) (*Prompt, error)
- func (p *Prompt) BindStringLiteral(name string, value stringLiteral) (*Prompt, error)
- func (p *Prompt) BindXML(name string, data any) (*Prompt, error)
- func (p *Prompt) BindYAML(name string, data any) (*Prompt, error)
- func (p *Prompt) Build() (string, error)
- func (p *Prompt) GetBindings() map[string]struct{}
- func (p *Prompt) MustBindJSON(name string, data any) *Prompt
- func (p *Prompt) MustBindStringLiteral(name string, value stringLiteral) *Prompt
- func (p *Prompt) MustBindXML(name string, data any) *Prompt
- func (p *Prompt) MustBindYAML(name string, data any) *Prompt
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Bindable ¶
type Bindable interface {
// Bind takes a prompt and returns a new prompt with bound values.
// The implementation should bind any necessary values from the receiver to the prompt.
// This allows executors to pass request-specific data into prompt templates.
Bind(prompt *Prompt) (*Prompt, error)
}
Bindable represents a type that can bind values to a Prompt. Executors expect request types to implement this interface so that prompts can be bound to the specific data in each request.
type Noop ¶
type Noop struct{}
Noop is a no-op implementation of Bindable that passes through the prompt unchanged
type Prompt ¶
type Prompt struct {
// contains filtered or unexported fields
}
Prompt represents a template with bindable placeholders
func Must ¶
Must is a helper that wraps a call to a function returning (*Prompt, error) and panics if the error is non-nil. It is intended for use in variable initializations such as:
var p = promptbuilder.Must(promptbuilder.NewPrompt(`Hello {{name}}`))
func MustNewPrompt ¶
func MustNewPrompt(template stringLiteral) *Prompt
MustNewPrompt creates a new prompt from a template literal and panics on error. This is syntactic sugar for Must(NewPrompt(...))
Example ¶
ExampleMustNewPrompt demonstrates creating a prompt that panics on error
package main
import (
"fmt"
"chainguard.dev/driftlessaf/agents/promptbuilder"
)
func main() {
// This is safe for package-level variables with known-good templates
var template = promptbuilder.MustNewPrompt(`Analyze: {{data}}`)
bindings := template.GetBindings()
fmt.Printf("Template has %d binding\n", len(bindings))
}
Output: Template has 1 binding
func NewPrompt ¶
NewPrompt creates a new prompt from a template literal and parses bindings
Example ¶
ExampleNewPrompt demonstrates creating a new prompt template
package main
import (
"fmt"
"log"
"chainguard.dev/driftlessaf/agents/promptbuilder"
)
func main() {
p, err := promptbuilder.NewPrompt(`Hello {{name}}, welcome to {{service}}!`)
if err != nil {
log.Fatal(err)
}
bindings := p.GetBindings()
fmt.Printf("Found %d bindings\n", len(bindings))
}
Output: Found 2 bindings
func (*Prompt) BindJSON ¶
BindJSON binds structured data to a placeholder by marshaling it as JSON The data parameter can be any type that json.Marshal accepts Returns a new Prompt with the binding applied
Example ¶
ExamplePrompt_BindJSON demonstrates binding structured data as JSON
package main
import (
"fmt"
"log"
"chainguard.dev/driftlessaf/agents/promptbuilder"
)
func main() {
p := promptbuilder.MustNewPrompt(`Process this user data:
{{user_data}}`)
userData := map[string]any{
"name": "Alice",
"age": 30,
"tags": []string{"developer", "go"},
}
p, err := p.BindJSON("user_data", userData)
if err != nil {
log.Fatal(err)
}
result, err := p.Build()
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
Output: Process this user data: { "age": 30, "name": "Alice", "tags": [ "developer", "go" ] }
func (*Prompt) BindStringLiteral ¶
BindStringLiteral binds a literal string value to a placeholder The value comes from the developer, not from user input Returns a new Prompt with the binding applied
Example ¶
ExamplePrompt_BindStringLiteral demonstrates binding literal string values
package main
import (
"fmt"
"log"
"chainguard.dev/driftlessaf/agents/promptbuilder"
)
func main() {
p := promptbuilder.MustNewPrompt(`System: {{instructions}}
User: {{query}}`)
// Bind developer-provided literal strings
p, err := p.BindStringLiteral("instructions", "You are a helpful assistant.")
if err != nil {
log.Fatal(err)
}
p, err = p.BindStringLiteral("query", "What is the weather?")
if err != nil {
log.Fatal(err)
}
result, err := p.Build()
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
Output: System: You are a helpful assistant. User: What is the weather?
func (*Prompt) BindXML ¶
BindXML binds structured data to a placeholder by marshaling it as XML The data parameter can be any type that xml.Marshal accepts Returns a new Prompt with the binding applied
Example ¶
ExamplePrompt_BindXML demonstrates binding structured data as XML
package main
import (
"fmt"
"log"
"chainguard.dev/driftlessaf/agents/promptbuilder"
)
func main() {
type User struct {
Name string `xml:"name"`
Age int `xml:"age"`
}
p := promptbuilder.MustNewPrompt(`User profile:
{{profile}}`)
user := User{Name: "Bob", Age: 25}
p, err := p.BindXML("profile", user)
if err != nil {
log.Fatal(err)
}
result, err := p.Build()
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
Output: User profile: <User> <name>Bob</name> <age>25</age> </User>
func (*Prompt) BindYAML ¶
BindYAML binds structured data to a placeholder by marshaling it as YAML The data parameter can be any type that yaml.Marshal accepts Returns a new Prompt with the binding applied
Example ¶
ExamplePrompt_BindYAML demonstrates binding structured data as YAML
package main
import (
"fmt"
"log"
"chainguard.dev/driftlessaf/agents/promptbuilder"
)
func main() {
p := promptbuilder.MustNewPrompt(`Configuration:
{{config}}`)
config := map[string]any{
"database": map[string]string{
"host": "localhost",
"port": "5432",
},
"debug": true,
}
p, err := p.BindYAML("config", config)
if err != nil {
log.Fatal(err)
}
result, err := p.Build()
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
Output: Configuration: database: host: localhost port: "5432" debug: true
func (*Prompt) Build ¶
Build constructs the final prompt, returning an error if any bindings are unbound
func (*Prompt) GetBindings ¶
GetBindings returns the names of all bindings found in the template as a set This is useful for testing and debugging
func (*Prompt) MustBindJSON ¶
MustBindJSON binds structured data as JSON to a placeholder and panics on error. This is syntactic sugar for Must(p.BindJSON(...))
func (*Prompt) MustBindStringLiteral ¶
MustBindStringLiteral binds a literal string value to a placeholder and panics on error. This is syntactic sugar for Must(p.BindStringLiteral(...))
Example ¶
ExamplePrompt_MustBindStringLiteral demonstrates the Must variant for binding literals
package main
import (
"fmt"
"log"
"chainguard.dev/driftlessaf/agents/promptbuilder"
)
func main() {
p := promptbuilder.MustNewPrompt(`Hello {{name}}!`)
// Chain Must methods for fluent API when you know bindings will succeed
p = p.MustBindStringLiteral("name", "World")
result, err := p.Build()
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
Output: Hello World!
func (*Prompt) MustBindXML ¶
MustBindXML binds structured data as XML to a placeholder and panics on error. This is syntactic sugar for Must(p.BindXML(...))