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)
// BindUnorderedList / BindOrderedList - Render single-line items as a Markdown list
p, err = p.BindUnorderedList("steps", promptbuilder.UnorderedList{"Check logs", "Summarise failures"})
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)
p = p.MustBindOrderedList("steps", promptbuilder.OrderedList{"Check logs", "Summarise failures"})
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 ¶
- No Raw User Input - User data must go through encoders (XML, JSON, or YAML)
- Type-Safe Literals - stringLiteral ensures only developer literals bypass encoding
- Automatic Escaping - Encoding libraries handle all escaping for user data
- No Transitive Substitution - Single-pass tokenization prevents recursive replacement
- Immutable Prompts - All operations return new instances, preventing mutation
- Line-Safe Lists - List items may carry runtime data; binding rejects line breaks so an item always renders as exactly one list entry
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
- List items containing line breaks in BindUnorderedList/BindOrderedList
- BindPrompt compositions nested beyond the composition depth limit
- Builds whose rendered output exceeds the build size limit
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 OrderedList
- type Prompt
- func (p *Prompt) BindJSON(name string, data any) (*Prompt, error)
- func (p *Prompt) BindOrderedList(name string, items OrderedList) (*Prompt, error)
- func (p *Prompt) BindPrompt(name string, other *Prompt) (*Prompt, error)
- func (p *Prompt) BindStringLiteral(name string, value stringLiteral) (*Prompt, error)
- func (p *Prompt) BindUnorderedList(name string, items UnorderedList) (*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) MustBindOrderedList(name string, items OrderedList) *Prompt
- func (p *Prompt) MustBindPrompt(name string, other *Prompt) *Prompt
- func (p *Prompt) MustBindStringLiteral(name string, value stringLiteral) *Prompt
- func (p *Prompt) MustBindUnorderedList(name string, items UnorderedList) *Prompt
- func (p *Prompt) MustBindXML(name string, data any) *Prompt
- func (p *Prompt) MustBindYAML(name string, data any) *Prompt
- type UnorderedList
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 OrderedList ¶ added in v0.7.64
type OrderedList []string
OrderedList is a list of single-line text items rendered as a numbered Markdown list. It is a distinct type from UnorderedList, so a list declared as one cannot be bound as the other; plain []string values convert to either.
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) BindOrderedList ¶ added in v0.7.64
func (p *Prompt) BindOrderedList(name string, items OrderedList) (*Prompt, error)
BindOrderedList binds items as a numbered Markdown list. Items may carry runtime data but must be single-line: an item containing a line break is rejected, so every item renders as exactly one list entry.
func (*Prompt) BindPrompt ¶ added in v0.7.64
BindPrompt binds another Prompt's Build output to a placeholder. The inner Prompt is built each time the outer one is, so unresolved bindings inside inner surface as Build errors on the outer. Use this to compose Prompts when the content at a placeholder is itself produced by a (literal-built) Prompt.
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) BindUnorderedList ¶ added in v0.7.64
func (p *Prompt) BindUnorderedList(name string, items UnorderedList) (*Prompt, error)
BindUnorderedList binds items as an unordered Markdown list. Items may carry runtime data but must be single-line: an item containing a line break is rejected, so every item renders as exactly one bullet.
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 or the rendered output exceeds the build size limit.
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) MustBindOrderedList ¶ added in v0.7.64
func (p *Prompt) MustBindOrderedList(name string, items OrderedList) *Prompt
MustBindOrderedList binds single-line items as a numbered Markdown list and panics on error.
func (*Prompt) MustBindPrompt ¶ added in v0.7.64
MustBindPrompt binds another Prompt's Build output to a placeholder and panics on error.
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) MustBindUnorderedList ¶ added in v0.7.64
func (p *Prompt) MustBindUnorderedList(name string, items UnorderedList) *Prompt
MustBindUnorderedList binds single-line items as an unordered Markdown list and panics on error.
func (*Prompt) MustBindXML ¶
MustBindXML binds structured data as XML to a placeholder and panics on error. This is syntactic sugar for Must(p.BindXML(...))
type UnorderedList ¶ added in v0.7.64
type UnorderedList []string
UnorderedList is a list of single-line text items rendered as Markdown bullets. It is a distinct type from OrderedList, so a list declared as one cannot be bound as the other; plain []string values convert to either.