promptloom

package module
v0.0.0-...-bf4f3fe Latest Latest
Warning

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

Go to latest
Published: Jan 27, 2026 License: MIT Imports: 13 Imported by: 0

README

PromptLoom

PromptLoom

Go Reference Go Report Card Test codecov

A modular prompt management system for LLM applications in Go.


Why PromptLoom?

As LLM applications grow, prompt management becomes a problem:

  • Prompts scattered across your codebase as string literals
  • Copy-pasted variations for different models
  • Conditional logic buried in code (if isPremium { prompt += "..." })
  • Model-specific quirks hardcoded everywhere

PromptLoom treats prompts as configuration:

  • Define reusable components, compose them into profiles
  • Conditionally include sections based on runtime context
  • Inherit settings across model-specific profiles
  • Keep prompt logic in YAML, business logic in code

Installation

go get github.com/lirancohen/promptloom

Quick Start

package main

import (
    "embed"
    "fmt"
    "log"

    "github.com/lirancohen/promptloom"
    "github.com/lirancohen/promptloom/storage"
)

//go:embed prompts/components/*.yaml prompts/profiles/*.yaml
var promptsFS embed.FS

func main() {
    registry := promptloom.NewRegistry()
    store := storage.NewEmbedStorage(promptsFS, "prompts")

    if err := registry.LoadFromFS(store.FS(), store.RootPath()); err != nil {
        log.Fatal(err)
    }

    assembler := promptloom.NewAssembler(registry)
    ctx := promptloom.NewContext().
        SetFlag("is_premium", true).
        SetValue("user_name", "Alice")

    prompt, err := assembler.Assemble("my-profile", ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(prompt)
}

Core Concepts

Components

Reusable prompt fragments defined in YAML:

name: safety_rules
instructions: |
  SAFETY GUIDELINES:
  - Never provide harmful information
  - Respect user privacy

Profiles

Combine components into complete prompts:

name: assistant
inherits: base          # Optional inheritance
components:
  - safety_rules
  - greeting
  - instructions

Context

Runtime values that control conditional assembly:

ctx := promptloom.NewContext().
    SetFlag("is_premium", true).
    SetValue("user_name", "Alice").
    SetNumber("max_tokens", 4096)

Conditions

Components and sections can be conditionally included:

name: premium_features
condition: is_premium && tier == 'gold'
instructions: |
  Premium features enabled.

Supports: &&, ||, !, ==, !=, >, <, >=, <=, in, exists

Templates

Inject context values into prompts:

name: greeting
template: true
instructions: |
  Hello {{.Values.user_name}}!
  {{if .Flags.is_premium}}Welcome back, premium member!{{end}}

Ordered Sections

Fine-grained control within a component:

name: features
instructions: Base instructions.
ordered_sections:
  - name: critical
    priority: 100           # Higher = appears first
    content: Always show this.
  - name: premium
    priority: 50
    condition: is_premium   # Section-level condition
    content: Premium content.

Directory Structure

prompts/
├── components/     # Reusable prompt fragments
│   ├── base.yaml
│   └── safety.yaml
└── profiles/       # Complete prompt configurations
    ├── claude.yaml
    └── gpt4.yaml

Assembly Methods

// Basic assembly
prompt, err := assembler.Assemble("profile", ctx)

// With debug trace
prompt, trace, err := assembler.AssembleWithTrace("profile", ctx)
fmt.Println(trace.IncludedComponents)
fmt.Println(trace.SkippedComponents)

// Structured messages for chat APIs
messages, err := assembler.AssembleMessages("profile", ctx)

Validation

// Check for circular inheritance
if err := registry.Validate(); err != nil {
    log.Fatal(err)
}

// Also verify all referenced components exist
if err := registry.ValidateStrict(); err != nil {
    log.Fatal(err)
}

Thread Safety

Registry and Assembler are safe for concurrent use. Call assembler.ClearCache() if you modify profiles at runtime.

Documentation

See docs/REFERENCE.md for complete YAML schemas, all assembly methods, provider formatters, and advanced features.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Acknowledgments

Carefully crafted with the help of Claude Code.

License

MIT License - see the LICENSE file for details.

Documentation

Overview

Package promptloom provides a modular prompt management system for LLM applications.

PromptLoom allows you to define reusable prompt components, organize them into profiles with inheritance, and assemble complete prompts based on runtime context.

Core Concepts

Components are reusable prompt fragments defined in YAML:

name: greeting
description: A friendly greeting component
instructions: |
  Hello! I'm here to help you today.

Profiles combine components and can inherit from other profiles:

name: assistant
inherits: base
components:
  - greeting
  - instructions
  - formatting

Context provides runtime values for conditional assembly:

ctx := promptloom.NewContext().
    SetFlag("has_agent", true).
    SetValue("user_name", "Alice").
    SetNumber("max_tokens", 4096)

Basic Usage

Load components and profiles from a filesystem:

registry := promptloom.NewRegistry()
store := storage.NewEmbedStorage(promptsFS, "prompts")
if err := registry.LoadFromFS(store.FS(), store.RootPath()); err != nil {
    log.Fatal(err)
}

Assemble a prompt:

assembler := promptloom.NewAssembler(registry)
ctx := promptloom.NewContext().SetFlag("verbose", true)
prompt, err := assembler.Assemble("my-profile", ctx)

Conditional Components

Components can have conditions that determine when they're included:

name: agent_mode
condition: has_agent
instructions: |
  You have access to tools and can take actions.

Conditions support boolean logic, comparisons, and advanced operators:

Boolean operators:

  • Simple flags: "has_agent"
  • Negation: "!has_agent"
  • AND: "has_agent && is_premium"
  • OR: "has_agent || is_demo"
  • Parentheses: "(a || b) && c" for grouping

Comparison operators:

  • String equality: "tier == 'premium'", "model != 'gpt-3'"
  • Numeric: "count > 10", "limit <= 100", "value == 42"

Membership operator:

  • In arrays: "tier in ['free', 'premium', 'enterprise']"
  • Numeric arrays: "priority in [1, 2, 3]"

Existence checks:

  • Exists: "tier exists" (true if variable is set, even if empty)
  • Not exists: "legacy_field !exists"

Dot notation for nested Data access:

  • Simple: "user.role == 'admin'"
  • Deep: "config.features.beta exists"
  • With operators: "user.role in ['admin', 'mod']"

Ordered Sections with Conditions and Priorities

Components can have ordered sections with individual conditions and priorities:

name: features
instructions: Base instructions.
ordered_sections:
  - name: safety
    priority: 100
    content: Safety guidelines (always first).
  - name: premium
    priority: 50
    condition: is_premium
    content: Premium features enabled.
  - name: basic
    priority: 25
    content: Basic features.

Sections are sorted by priority (highest first) and can have their own condition expressions using the same syntax as component conditions. Sections with equal priority maintain their definition order. Sections inherit both the component's role and template setting.

Component Dependencies

Components can declare dependencies on other components using depends_on:

name: advanced_features
depends_on: base_features    # Single dependency
instructions: |
  Advanced features that require base features.

name: premium_advanced
depends_on: [base, premium]  # Multiple dependencies (all required)
instructions: |
  Premium advanced features.

If any dependency was not included (due to condition evaluation or not being in the profile), the dependent component is automatically skipped. Dependencies must appear earlier in the profile's component list. This naturally prevents circular dependencies.

Template Variables

Enable template processing to inject Context values into prompts:

name: personalized
template: true
instructions: |
  Hello {{.Values.user_name}}! You have {{.Numbers.message_count}} messages.
  {{if .Flags.premium}}Premium features are enabled.{{end}}

Available template variables:

  • {{.Flags.name}} - boolean flags
  • {{.Values.name}} - string values
  • {{.Numbers.name}} - integer values
  • {{.Data.name}} - arbitrary data
  • {{.Component.Name}} - current component name
  • {{.Component.Data.key}} - component's custom data

Profile Inheritance

Profiles can inherit from parent profiles:

name: claude-sonnet
inherits: claude
overrides:
  preferred_keywords:
    - "step by step"

Child profiles inherit components and overrides from parents, with child values taking precedence.

Provider-Aware Profiles

Profiles can be organized by LLM provider using a naming convention. Provider-specific variants use the pattern "{base}.{provider}":

# profiles/assistant.yaml - universal base
name: assistant
components:
  - base_instructions
  - safety_rules

# profiles/assistant.anthropic.yaml - Anthropic variant
name: assistant.anthropic
inherits: assistant           # REQUIRED for provider variants
provider: anthropic
components:
  - anthropic_persona

When assembling with a provider set in context, the assembler automatically resolves to the provider-specific variant if it exists:

ctx := promptloom.NewContext().SetProvider("anthropic")
messages, _ := assembler.AssembleMessages("assistant", ctx)
// Uses "assistant.anthropic" because it exists

ctx2 := promptloom.NewContext().SetProvider("unknown")
messages2, _ := assembler.AssembleMessages("assistant", ctx2)
// Falls back to "assistant" because "assistant.unknown" doesn't exist

Provider variant profiles MUST inherit from their base profile. This is validated at registry validation time. The naming convention ensures that updating the base profile automatically affects all variants through inheritance.

Helper methods for querying provider variants:

providers := registry.GetProviderVariants("assistant")
// Returns []string{"anthropic", "openai"} if those variants exist

exists := registry.HasProviderVariant("assistant", "anthropic")
// Returns true if assistant.anthropic exists and inherits from assistant

Validation

Validate the registry to catch configuration errors:

if err := registry.Validate(); err != nil {
    log.Fatal(err) // Catches circular inheritance
}

// Strict mode also checks for unknown component references
if err := registry.ValidateStrict(); err != nil {
    log.Fatal(err)
}

Debugging

Use AssembleWithTrace to debug complex profiles and access profile metadata:

prompt, trace, err := assembler.AssembleWithTrace("profile", ctx)
fmt.Println("Included:", trace.IncludedComponents)
fmt.Println("Skipped:", trace.SkippedComponents)
fmt.Println("Conditions:", trace.ConditionResults)

// Access resolved profile with overrides and metadata
profile := trace.ResolvedProfile
keywords := profile.Overrides.PreferredKeywords
modelID := profile.GetDataString("model_id")

Message-Based Assembly

For LLM APIs that expect structured messages (like OpenAI, Anthropic), use AssembleMessages to get a slice of Message structs instead of a single string:

messages, err := assembler.AssembleMessages("profile", ctx)
// messages is []Message with Role and Content fields

Components can specify a role (system, user, assistant):

name: user_input
role: user
instructions: |
  What is the weather today?

If no role is specified, components default to "system". Adjacent components with the same role are automatically merged into a single message:

// Two system components back-to-back become one system message
messages, _ := assembler.AssembleMessages("profile", ctx)
// messages[0].Role == "system"
// messages[0].Content == "First component.\n\nSecond component."

ModelInstructions and Overrides.Suffix are appended to the last system message. If no system message exists (all components are user/assistant roles), these values are skipped as they are supplementary to system content.

Provider Formatters

Different LLM APIs have different message formats. Optional sub-packages under format/ convert PromptLoom messages to provider-specific formats:

import "github.com/lirancohen/promptloom/format/openai"
import "github.com/lirancohen/promptloom/format/anthropic"

messages, _ := assembler.AssembleMessages("profile", ctx)

// Convert to OpenAI format (keeps system messages in array)
openaiMsgs := openai.Format(messages)

// Convert to Anthropic format (extracts system to top-level field)
anthropicReq := anthropic.Format(messages)

OpenAI's format keeps system messages in the messages array, while Anthropic requires system messages as a separate top-level parameter. The formatters handle these differences automatically.

Users can implement the format.Formatter interface for custom providers.

Prompt Chaining

For multi-step workflows where one LLM response feeds into the next prompt, reuse the same Context object and set intermediate results as values:

ctx := promptloom.NewContext()

// Step 1: Analyze
messages1, _ := assembler.AssembleMessages("analyze", ctx)
response1 := callLLM(messages1)

// Step 2: Use analysis in next prompt
ctx.SetValue("analysis", response1)
messages2, _ := assembler.AssembleMessages("synthesize", ctx)
response2 := callLLM(messages2)

The "synthesize" profile uses a template component to inject the previous response:

name: synthesize_prompt
template: true
instructions: |
  Based on this analysis:
  {{.Values.analysis}}

  Please synthesize the key findings.

This pattern keeps orchestration logic in your application code while PromptLoom handles prompt composition. You can chain any number of steps by continuing to set values and assemble new prompts.

Assembly Behavior

Assemble fails fast on missing components and validates inputs automatically:

prompt, err := assembler.Assemble("profile", ctx)
if err != nil {
    log.Fatal(err) // Fails if component missing or required inputs missing
}

If a profile has an InputSchema, defaults are applied and required inputs are validated automatically during assembly. No separate validation call needed.

Profile Metadata

Profiles can store arbitrary metadata with type-safe accessors:

name: my-profile
metadata:
  model_id: "gpt-4"
  max_tokens: 4096
  preferred_keywords: ["helpful", "concise"]

profile := registry.GetProfile("my-profile")
modelID := profile.GetDataString("model_id")
maxTokens := profile.GetDataInt("max_tokens")
keywords := profile.GetDataStrings("preferred_keywords")

Version and Tags

Profiles can have versions and tags for organization and inheritance:

name: assistant-v2
version: "2.0.0"
tags:
  - production
  - vision
  - premium

Tags are inherited when a profile inherits from another. Access tags directly via profile.Tags or use slices.Contains for membership checks.

Input Schema

Profiles can declare expected inputs for documentation and validation:

name: my-profile
input_schema:
  inputs:
    - name: user_name
      type: string
      required: true
      description: The user's display name
    - name: is_premium
      type: flag
      default: false

When a profile has an InputSchema, Assemble automatically:

  • Applies default values to the context
  • Validates required inputs are present
  • Checks input types match the schema

If validation fails, Assemble returns an error. No separate validation call needed.

Input types: "flag" (bool), "string", "number" (int), "data" (any)

Configurable Directory Structure

By default, LoadFromFS expects "components" and "profiles" directories. Customize these with options:

registry.LoadFromFS(fsys, rootPath,
    WithComponentsDir("snippets"),
    WithProfilesDir("configs"),
)

Storage Backends

PromptLoom supports multiple storage backends:

  • storage.NewMemoryStorage() - In-memory storage for testing
  • storage.NewEmbedStorage(fs, path) - Embedded files (go:embed)

Thread Safety

Registry and Assembler are safe for concurrent use. The Assembler caches resolved profiles for performance; use ClearCache() if you modify profiles after initial assembly.

Example
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	// Create a memory storage for this example
	store := storage.NewMemoryStorage()
	store.AddComponent("greeting", `
name: greeting
instructions: Hello! I'm your AI assistant.
`)
	store.AddComponent("task", `
name: task
instructions: How can I help you today?
`)
	store.AddProfile("assistant", `
name: assistant
components:
  - greeting
  - task
`)

	// Load into registry
	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())

	// Assemble the prompt
	assembler := promptloom.NewAssembler(registry)
	ctx := promptloom.NewContext()
	prompt, _ := assembler.Assemble("assistant", ctx)

	fmt.Println(prompt)
}
Output:
Hello! I'm your AI assistant.

How can I help you today?

Index

Examples

Constants

View Source
const (
	InputTypeFlag   = "flag"
	InputTypeString = "string"
	InputTypeNumber = "number"
	InputTypeData   = "data"
)

InputType constants for input field types.

View Source
const RoleAssistant = "assistant"

RoleAssistant is the role for assistant messages.

View Source
const RoleSystem = "system"

RoleSystem is the role for system messages (instructions to the model).

View Source
const RoleUser = "user"

RoleUser is the role for user messages.

Variables

This section is empty.

Functions

This section is empty.

Types

type Assembler

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

Assembler builds prompts from components based on profiles and context.

func NewAssembler

func NewAssembler(registry *Registry) *Assembler

NewAssembler creates a new assembler with the given registry. Assemble will fail if any referenced component is not found.

func (*Assembler) Assemble

func (a *Assembler) Assemble(profileName string, ctx *Context) (string, error)

Assemble builds a complete prompt for the given profile and context. Returns an error if any referenced component is not found. If the profile has an InputSchema, defaults are applied and required inputs are validated automatically before assembly.

Example (Conditional)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()
	store.AddComponent("base", `
name: base
instructions: Base instructions.
`)
	store.AddComponent("premium", `
name: premium
condition: is_premium
instructions: Premium features enabled.
`)
	store.AddProfile("app", `
name: app
components:
  - base
  - premium
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())
	assembler := promptloom.NewAssembler(registry)

	// Without premium flag
	ctx := promptloom.NewContext()
	prompt, _ := assembler.Assemble("app", ctx)
	fmt.Println("Without premium:")
	fmt.Println(prompt)

	// With premium flag
	ctx = promptloom.NewContext().SetFlag("is_premium", true)
	prompt, _ = assembler.Assemble("app", ctx)
	fmt.Println("\nWith premium:")
	fmt.Println(prompt)
}
Output:
Without premium:
Base instructions.

With premium:
Base instructions.

Premium features enabled.
Example (EnhancedConditions)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()
	store.AddComponent("basic", `
name: basic
instructions: Basic instructions.
`)
	store.AddComponent("premium", `
name: premium
condition: tier == 'premium'
instructions: Premium features enabled.
`)
	store.AddComponent("high_volume", `
name: high_volume
condition: request_count > 100
instructions: High volume mode active.
`)
	store.AddProfile("app", `
name: app
components:
  - basic
  - premium
  - high_volume
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())
	assembler := promptloom.NewAssembler(registry)

	// Free tier with low volume
	ctx := promptloom.NewContext().
		SetValue("tier", "free").
		SetNumber("request_count", 50)
	prompt, _ := assembler.Assemble("app", ctx)
	fmt.Println("Free tier:")
	fmt.Println(prompt)

	// Premium tier with high volume
	ctx = promptloom.NewContext().
		SetValue("tier", "premium").
		SetNumber("request_count", 150)
	prompt, _ = assembler.Assemble("app", ctx)
	fmt.Println("\nPremium tier:")
	fmt.Println(prompt)
}
Output:
Free tier:
Basic instructions.

Premium tier:
Basic instructions.

Premium features enabled.

High volume mode active.
Example (MissingComponent)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()
	store.AddComponent("greeting", `
name: greeting
instructions: Hello!
`)
	store.AddProfile("valid", `
name: valid
components:
  - greeting
`)
	store.AddProfile("invalid", `
name: invalid
components:
  - greeting
  - missing_component
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())
	assembler := promptloom.NewAssembler(registry)

	// Valid profile succeeds
	prompt, err := assembler.Assemble("valid", promptloom.NewContext())
	if err == nil {
		fmt.Println("Valid profile:", prompt)
	}

	// Invalid profile fails (missing component)
	_, err = assembler.Assemble("invalid", promptloom.NewContext())
	if err != nil {
		fmt.Println("Invalid profile error:", err)
	}
}
Output:
Valid profile: Hello!
Invalid profile error: component not found: missing_component

func (*Assembler) AssembleMessages

func (a *Assembler) AssembleMessages(profileName string, ctx *Context) ([]Message, error)

AssembleMessages builds a prompt as a slice of messages for the given profile and context. Components are grouped by role, with adjacent same-role components merged into single messages. Returns an error if any referenced component is not found.

func (*Assembler) AssembleMessagesWithTrace

func (a *Assembler) AssembleMessagesWithTrace(profileName string, ctx *Context) ([]Message, *AssemblyTrace, error)

AssembleMessagesWithTrace builds a prompt as messages and returns detailed trace information. Returns an error if any referenced component is not found.

func (*Assembler) AssembleWithTrace

func (a *Assembler) AssembleWithTrace(profileName string, ctx *Context) (string, *AssemblyTrace, error)

AssembleWithTrace builds a prompt and returns detailed trace information about the assembly process. Useful for debugging complex profiles. Returns an error if any referenced component is not found. If the profile has an InputSchema, defaults are applied and required inputs are validated automatically before assembly.

Example (ConditionalComponents)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()
	store.AddComponent("always", `
name: always
instructions: Always included.
`)
	store.AddComponent("conditional", `
name: conditional
condition: enabled
instructions: Conditionally included.
`)
	store.AddProfile("test", `
name: test
components:
  - always
  - conditional
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())
	assembler := promptloom.NewAssembler(registry)

	ctx := promptloom.NewContext().SetFlag("enabled", false)
	_, trace, _ := assembler.AssembleWithTrace("test", ctx)

	fmt.Println("Included:", trace.IncludedComponents)
	fmt.Println("Skipped:")
	for _, s := range trace.SkippedComponents {
		fmt.Printf("  - %s: %s\n", s.Name, s.Reason)
	}
}
Output:
Included: [always]
Skipped:
  - conditional: condition "enabled" is false
Example (WithMetadata)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()
	store.AddComponent("greeting", `
name: greeting
instructions: Hello! I'm your AI assistant.
`)
	store.AddProfile("assistant", `
name: assistant
components:
  - greeting
metadata:
  model_id: "gpt-4"
  provider: "openai"
  max_tokens: 4096
overrides:
  preferred_keywords:
    - helpful
    - concise
  suffix: "Always be helpful."
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())
	assembler := promptloom.NewAssembler(registry)

	prompt, trace, _ := assembler.AssembleWithTrace("assistant", promptloom.NewContext())

	profile := trace.ResolvedProfile
	fmt.Println("Prompt:", prompt)
	fmt.Println("Model:", profile.GetDataString("model_id"))
	fmt.Println("Keywords:", profile.Overrides.PreferredKeywords)
}
Output:
Prompt: Hello! I'm your AI assistant.

Always be helpful.
Model: gpt-4
Keywords: [helpful concise]

func (*Assembler) ClearCache

func (a *Assembler) ClearCache()

ClearCache clears the resolved profile cache. Call this if you modify profiles in the registry after assembly.

func (*Assembler) GetAvoidKeywords

func (a *Assembler) GetAvoidKeywords(profileName string) []string

GetAvoidKeywords returns keywords to avoid for a profile.

func (*Assembler) GetInputSchema

func (a *Assembler) GetInputSchema(profileName string) *InputSchema

GetInputSchema returns the effective InputSchema for a profile (with inheritance resolved). Returns nil if the profile doesn't exist or has no InputSchema. This method is useful for introspection and documentation generation.

func (*Assembler) GetOverrides

func (a *Assembler) GetOverrides(profileName string) *ProfileOverrides

GetOverrides returns the effective overrides for a profile (with inheritance resolved). The returned pointer references the cached profile's Overrides field; callers should not modify it.

func (*Assembler) GetPreferredKeywords

func (a *Assembler) GetPreferredKeywords(profileName string) []string

GetPreferredKeywords returns preferred keywords for a profile.

type AssemblyTrace

type AssemblyTrace struct {
	// ProfileName is the name of the profile being assembled
	ProfileName string

	// ResolvedProfile is the effective profile after inheritance resolution
	ResolvedProfile *Profile

	// IncludedComponents lists components that were included in the output
	IncludedComponents []string

	// SkippedComponents lists components that were skipped with reasons
	SkippedComponents []SkippedComponent

	// IncludedSections lists sections that were included
	IncludedSections []IncludedSection

	// SkippedSections lists sections that were skipped with reasons
	SkippedSections []SkippedSection

	// ConditionResults maps condition expressions to their evaluation results
	ConditionResults map[string]bool

	// ConditionErrors records errors that occurred during condition evaluation
	ConditionErrors []ConditionError
}

AssemblyTrace captures details about the prompt assembly process.

type Component

type Component struct {
	// Name is the unique identifier for this component
	Name string `yaml:"name"`

	// Description explains what this component does
	Description string `yaml:"description"`

	// Condition is an optional expression that determines if this component is included
	// Supports: simple flags (has_agent), negation (!has_agent), AND (a && b), OR (a || b)
	Condition string `yaml:"condition,omitempty"`

	// DependsOn specifies other components that must be included for this component to be included.
	// If any dependency was not included (due to condition evaluation or not being in the profile),
	// this component is automatically skipped. Dependencies must appear earlier in the profile's
	// component list. Accepts either a single string or a list of strings in YAML:
	//   depends_on: foo           # single dependency
	//   depends_on: [foo, bar]    # multiple dependencies
	DependsOn StringOrSlice `yaml:"depends_on,omitempty"`

	// Role specifies the message role for this component when using AssembleMessages.
	// Valid values: "system", "user", "assistant". Defaults to "system" if not specified.
	// Adjacent components with the same role are merged into a single message.
	// All ordered sections within a component inherit this role and the template setting.
	Role string `yaml:"role,omitempty"`

	// Instructions is the main prompt text for this component
	Instructions string `yaml:"instructions"`

	// Template enables text/template processing for Instructions and OrderedSections.
	// When true, the text is parsed as a Go template with access to Context data.
	// Available template variables:
	//   {{.Flags.flag_name}} - boolean flags
	//   {{.Values.value_name}} - string values
	//   {{.Numbers.number_name}} - integer values
	//   {{.Data.data_name}} - arbitrary data
	//   {{.Component.Name}}, {{.Component.Data}} - component metadata
	Template bool `yaml:"template,omitempty"`

	// OrderedSections contains sections with explicit ordering, conditions, and priorities.
	// Sections are processed in priority order (highest first), and each section can have
	// its own condition expression. This is the recommended format for new components.
	//
	// Example YAML:
	//   ordered_sections:
	//     - name: premium
	//       condition: tier == 'premium'
	//       priority: 100
	//       content: Premium features enabled.
	//     - name: basic
	//       priority: 50
	//       content: Basic instructions.
	OrderedSections []Section `yaml:"ordered_sections,omitempty"`

	// Data holds arbitrary structured data for use in templates.
	// May be nil if no data is defined. Use GetDataString, GetDataStrings, etc.
	// for type-safe access with nil handling.
	Data map[string]any `yaml:"data,omitempty"`
}

Component represents a reusable prompt fragment. Components can have conditions that determine when they're included.

Example (OrderedSections)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()
	store.AddComponent("features", `
name: features
instructions: Available features based on your plan.
ordered_sections:
  - name: critical
    priority: 100
    content: Core features available to all users.
  - name: premium
    priority: 50
    condition: is_premium
    content: Premium features unlocked!
  - name: trial
    priority: 25
    condition: is_trial
    content: Trial features (limited time).
`)
	store.AddProfile("app", `
name: app
components:
  - features
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())
	assembler := promptloom.NewAssembler(registry)

	// Premium user sees both critical and premium sections
	ctx := promptloom.NewContext().SetFlag("is_premium", true)
	prompt, _ := assembler.Assemble("app", ctx)
	fmt.Println(prompt)
}
Output:
Available features based on your plan.

Core features available to all users.

Premium features unlocked!
Example (OrderedSections_priority)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()
	store.AddComponent("instructions", `
name: instructions
instructions: System instructions.
ordered_sections:
  - name: safety
    priority: 100
    content: "[SAFETY] Always prioritize user safety."
  - name: helpful
    priority: 75
    content: "[HELPFUL] Be helpful and informative."
  - name: concise
    priority: 50
    content: "[CONCISE] Keep responses brief."
`)
	store.AddProfile("assistant", `
name: assistant
components:
  - instructions
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())
	assembler := promptloom.NewAssembler(registry)

	// Sections are ordered by priority (highest first)
	prompt, _ := assembler.Assemble("assistant", promptloom.NewContext())
	fmt.Println(prompt)
}
Output:
System instructions.

[SAFETY] Always prioritize user safety.

[HELPFUL] Be helpful and informative.

[CONCISE] Keep responses brief.
Example (Template)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()
	store.AddComponent("greeting", `
name: greeting
template: true
instructions: |
  Hello, {{.Values.user_name}}!
  You have {{.Numbers.message_count}} unread messages.
`)
	store.AddProfile("test", `
name: test
components:
  - greeting
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())
	assembler := promptloom.NewAssembler(registry)

	ctx := promptloom.NewContext().
		SetValue("user_name", "Bob").
		SetNumber("message_count", 5)

	prompt, _ := assembler.Assemble("test", ctx)
	fmt.Println(prompt)
}
Output:
Hello, Bob!
You have 5 unread messages.

func (*Component) GetData

func (c *Component) GetData(key string) any

GetData returns the raw value from component data, or nil if not found.

func (*Component) GetDataBool

func (c *Component) GetDataBool(key string) bool

GetDataBool returns a bool value from component data, or false if not found.

func (*Component) GetDataInt

func (c *Component) GetDataInt(key string) int

GetDataInt returns an int value from component data, or 0 if not found.

func (*Component) GetDataString

func (c *Component) GetDataString(key string) string

GetDataString returns a string value from component data, or empty string if not found.

func (*Component) GetDataStrings

func (c *Component) GetDataStrings(key string) []string

GetDataStrings returns a string slice from component data, or nil if not found.

func (*Component) GetDependencies

func (c *Component) GetDependencies() []string

GetDependencies returns the component's dependencies as a slice. Returns nil if no dependencies are defined.

func (*Component) GetRole

func (c *Component) GetRole() string

GetRole returns the component's role, defaulting to "system" if not specified.

type ComponentData

type ComponentData struct {
	// Name is the component's name
	Name string

	// Description is the component's description
	Description string

	// Data is the component's custom data
	Data map[string]any
}

ComponentData contains component metadata available in templates.

type ConditionError

type ConditionError struct {
	// Condition is the original condition expression
	Condition string

	// Phase indicates where the error occurred: "compile" or "run"
	Phase string

	// Err is the underlying error
	Err error
}

ConditionError records an error that occurred during condition evaluation.

func (*ConditionError) Error

func (e *ConditionError) Error() string

Error implements the error interface.

func (*ConditionError) Unwrap

func (e *ConditionError) Unwrap() error

Unwrap returns the underlying error.

type Context

type Context struct {
	// Flags are boolean conditions (e.g., "has_agent", "is_premium")
	Flags map[string]bool

	// Values are string values (e.g., "model_name", "user_locale")
	Values map[string]string

	// Numbers are numeric values (e.g., "scene_count", "max_tokens")
	Numbers map[string]int

	// Data holds arbitrary structured data
	Data map[string]any

	// Provider identifies the LLM provider for automatic profile resolution.
	// When set, assembly of profile "X" will use "X.provider" if it exists.
	Provider string
}

Context holds runtime values used for condition evaluation and template rendering.

Example
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
)

func main() {
	ctx := promptloom.NewContext().
		SetFlag("premium", true).
		SetFlag("verbose", false).
		SetValue("user_name", "Alice").
		SetNumber("max_tokens", 4096).
		SetData("preferences", map[string]string{"theme": "dark"})

	fmt.Println("Premium:", ctx.GetFlag("premium"))
	fmt.Println("User:", ctx.GetValue("user_name"))
	fmt.Println("Tokens:", ctx.GetNumber("max_tokens"))
}
Output:
Premium: true
User: Alice
Tokens: 4096
Example (Batch_setters)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
)

func main() {
	ctx := promptloom.NewContext().
		SetFlags(map[string]bool{
			"premium": true,
			"verbose": false,
		}).
		SetValues(map[string]string{
			"tier":      "gold",
			"user_name": "Alice",
		}).
		SetNumbers(map[string]int{
			"max_tokens": 4096,
			"timeout":    30,
		})

	fmt.Println("Premium:", ctx.GetFlag("premium"))
	fmt.Println("Tier:", ctx.GetValue("tier"))
	fmt.Println("Max Tokens:", ctx.GetNumber("max_tokens"))
}
Output:
Premium: true
Tier: gold
Max Tokens: 4096

func NewContext

func NewContext() *Context

NewContext creates a new empty context.

func (*Context) GetData

func (c *Context) GetData(name string) any

GetData returns arbitrary data.

func (*Context) GetFlag

func (c *Context) GetFlag(name string) bool

GetFlag returns a boolean flag value (defaults to false).

func (*Context) GetNestedValue

func (c *Context) GetNestedValue(path string) (any, bool)

GetNestedValue retrieves a value from nested Data using dot notation. For example, "user.profile.name" accesses Data["user"]["profile"]["name"]. Returns the value and true if found, nil and false otherwise. The maximum nesting depth is bounded by the path string length.

func (*Context) GetNumber

func (c *Context) GetNumber(name string) int

GetNumber returns a numeric value (defaults to 0).

func (*Context) GetProvider

func (c *Context) GetProvider() string

GetProvider returns the LLM provider.

func (*Context) GetValue

func (c *Context) GetValue(name string) string

GetValue returns a string value (defaults to empty string).

func (*Context) HasData

func (c *Context) HasData(name string) bool

HasData returns true if the data key exists in the context (regardless of its value).

func (*Context) HasFlag

func (c *Context) HasFlag(name string) bool

HasFlag returns true if the flag exists in the context (regardless of its value).

func (*Context) HasNumber

func (c *Context) HasNumber(name string) bool

HasNumber returns true if the number exists in the context.

func (*Context) HasValue

func (c *Context) HasValue(name string) bool

HasValue returns true if the value exists in the context.

func (*Context) SetData

func (c *Context) SetData(name string, value any) *Context

SetData sets arbitrary data.

func (*Context) SetFlag

func (c *Context) SetFlag(name string, value bool) *Context

SetFlag sets a boolean flag.

func (*Context) SetFlags

func (c *Context) SetFlags(flags map[string]bool) *Context

SetFlags sets multiple boolean flags at once.

func (*Context) SetNumber

func (c *Context) SetNumber(name string, value int) *Context

SetNumber sets a numeric value.

func (*Context) SetNumbers

func (c *Context) SetNumbers(numbers map[string]int) *Context

SetNumbers sets multiple numeric values at once.

func (*Context) SetProvider

func (c *Context) SetProvider(provider string) *Context

SetProvider sets the LLM provider for automatic profile resolution. When set, assembly of profile "X" will use "X.provider" if it exists.

func (*Context) SetValue

func (c *Context) SetValue(name, value string) *Context

SetValue sets a string value.

func (*Context) SetValues

func (c *Context) SetValues(values map[string]string) *Context

SetValues sets multiple string values at once.

type IncludedSection

type IncludedSection struct {
	// ComponentName is the parent component
	ComponentName string

	// SectionName is the section that was included
	SectionName string
}

IncludedSection records which sections were included.

type InputField

type InputField struct {
	// Name is the input field name (used in Context)
	Name string `yaml:"name"`

	// Type is the expected type: "flag", "string", "number", or "data"
	Type string `yaml:"type,omitempty"`

	// Required indicates if this input must be provided
	Required bool `yaml:"required,omitempty"`

	// Default is the default value if not provided (for documentation)
	Default any `yaml:"default,omitempty"`

	// Description explains what this input is for
	Description string `yaml:"description,omitempty"`
}

InputField describes a single expected input.

type InputSchema

type InputSchema struct {
	// Inputs lists the expected input fields
	Inputs []InputField `yaml:"inputs,omitempty"`
}

InputSchema declares the expected inputs for a profile or component. This serves as documentation and enables validation in strict mode.

Example YAML:

input_schema:
  inputs:
    - name: user_name
      type: string
      required: true
      description: The user's display name
    - name: is_premium
      type: flag
      default: false
    - name: max_tokens
      type: number
      required: true
Example
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()
	store.AddComponent("greeting", `
name: greeting
template: true
instructions: |
  Hello, {{.Values.user_name}}!
  Your tier: {{.Values.tier}}
`)
	store.AddProfile("app", `
name: app
components:
  - greeting
input_schema:
  inputs:
    - name: user_name
      type: string
      required: true
      description: The user's display name
    - name: tier
      type: string
      required: true
      description: User subscription tier
    - name: is_premium
      type: flag
      required: false
      description: Whether user has premium features
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())
	assembler := promptloom.NewAssembler(registry)

	// Get input schema for documentation
	profile := registry.GetProfile("app")
	fmt.Println("Required inputs:")
	for _, name := range profile.InputSchema.RequiredInputNames() {
		input := profile.InputSchema.GetInput(name)
		fmt.Printf("  - %s: %s\n", name, input.Description)
	}

	// Validation happens automatically during Assemble
	ctx := promptloom.NewContext().
		SetValue("user_name", "Alice").
		SetValue("tier", "gold")

	prompt, err := assembler.Assemble("app", ctx)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Println("\nGenerated prompt:")
	fmt.Println(prompt)
}
Output:
Required inputs:
  - user_name: The user's display name
  - tier: User subscription tier

Generated prompt:
Hello, Alice!
Your tier: gold

func (*InputSchema) ApplyDefaults

func (s *InputSchema) ApplyDefaults(ctx *Context)

ApplyDefaults fills in missing optional inputs with their default values. This should be called before Validate() to ensure defaults are applied.

func (*InputSchema) GetInput

func (s *InputSchema) GetInput(name string) *InputField

GetInput returns the InputField for the given name, or nil if not found.

func (*InputSchema) RequiredInputNames

func (s *InputSchema) RequiredInputNames() []string

RequiredInputNames returns the names of all required inputs.

func (*InputSchema) Validate

func (s *InputSchema) Validate(ctx *Context) error

Validate checks if the given context satisfies the input schema. Returns nil if valid, or an error describing what's missing or type mismatches.

type LoadOption

type LoadOption func(*loadOptions)

LoadOption configures the behavior of LoadFromFS.

func WithComponentsDir

func WithComponentsDir(dir string) LoadOption

WithComponentsDir sets the directory name for components (default: "components").

func WithProfilesDir

func WithProfilesDir(dir string) LoadOption

WithProfilesDir sets the directory name for profiles (default: "profiles").

type Message

type Message struct {
	// Role is the message role: "system", "user", or "assistant".
	// This maps directly to the role field expected by most LLM APIs.
	Role string

	// Content is the text content of the message.
	Content string
}

Message represents a single message in a conversation. Used by AssembleMessages to return structured prompts instead of a single string.

type Profile

type Profile struct {
	// Name is the unique identifier for this profile
	Name string `yaml:"name"`

	// Description explains what this profile is for
	Description string `yaml:"description"`

	// Version is the semantic version of this profile (e.g., "1.0.0", "2.1.0-beta").
	// Inherited when a profile inherits from another.
	Version string `yaml:"version,omitempty"`

	// Tags are labels for organizing profiles (e.g., ["production", "vision"]).
	// Tags are merged during inheritance. Use slices.Contains for membership checks.
	Tags []string `yaml:"tags,omitempty"`

	// Inherits specifies a parent profile to inherit from
	Inherits string `yaml:"inherits,omitempty"`

	// Provider identifies the LLM provider this profile is optimized for (e.g., "anthropic", "openai").
	// Used for automatic profile resolution: when context has a provider set,
	// assembly of "X" will use "X.provider" if it exists.
	Provider string `yaml:"provider,omitempty"`

	// Components lists the component names to include (in order)
	Components []string `yaml:"components"`

	// Overrides contains profile-specific overrides for component settings
	Overrides ProfileOverrides `yaml:"overrides"`

	// ModelInstructions contains additional model-specific instructions
	ModelInstructions string `yaml:"model_instructions,omitempty"`

	// Metadata holds arbitrary profile-level data (e.g., model info, fidelity settings).
	// May be nil if no metadata is defined. Use GetDataString, GetDataStrings, etc.
	// for type-safe access with nil handling.
	Metadata map[string]any `yaml:"metadata,omitempty"`

	// InputSchema declares the expected inputs for this profile.
	// Used for documentation and validation in strict mode.
	InputSchema *InputSchema `yaml:"input_schema,omitempty"`
}

Profile represents a model-specific prompt configuration. Profiles can inherit from other profiles to share common settings.

Example (Inheritance)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()
	store.AddComponent("base", `
name: base
instructions: Base component.
`)
	store.AddProfile("parent", `
name: parent
components:
  - base
overrides:
  suffix: Parent suffix.
`)
	store.AddProfile("child", `
name: child
inherits: parent
overrides:
  suffix: Child suffix.
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath())
	assembler := promptloom.NewAssembler(registry)

	// Child inherits components from parent but overrides suffix
	prompt, _ := assembler.Assemble("child", promptloom.NewContext())
	fmt.Println(prompt)
}
Output:
Base component.

Child suffix.

func (*Profile) GetData

func (p *Profile) GetData(key string) any

GetData returns the raw value from metadata, or nil if not found.

func (*Profile) GetDataBool

func (p *Profile) GetDataBool(key string) bool

GetDataBool returns a bool value from metadata, or false if not found.

func (*Profile) GetDataInt

func (p *Profile) GetDataInt(key string) int

GetDataInt returns an int value from metadata, or 0 if not found.

func (*Profile) GetDataString

func (p *Profile) GetDataString(key string) string

GetDataString returns a string value from metadata, or empty string if not found.

Example
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
)

func main() {
	profile := &promptloom.Profile{
		Name: "example",
		Metadata: map[string]any{
			"model_id":        "gpt-4",
			"provider":        "openai",
			"max_tokens":      4096,
			"supports_vision": true,
		},
	}

	fmt.Println("Model:", profile.GetDataString("model_id"))
	fmt.Println("Provider:", profile.GetDataString("provider"))
	fmt.Println("Max Tokens:", profile.GetDataInt("max_tokens"))
	fmt.Println("Vision:", profile.GetDataBool("supports_vision"))
}
Output:
Model: gpt-4
Provider: openai
Max Tokens: 4096
Vision: true

func (*Profile) GetDataStrings

func (p *Profile) GetDataStrings(key string) []string

GetDataStrings returns a string slice from metadata, or nil if not found.

type ProfileOverrides

type ProfileOverrides struct {
	// PreferredKeywords are keywords the model responds well to
	PreferredKeywords []string `yaml:"preferred_keywords,omitempty"`

	// AvoidKeywords are keywords that cause issues with this model
	AvoidKeywords []string `yaml:"avoid_keywords,omitempty"`

	// Suffix is additional text appended to the prompt
	Suffix string `yaml:"suffix,omitempty"`

	// Custom holds arbitrary override data
	Custom map[string]any `yaml:"custom,omitempty"`
}

ProfileOverrides contains settings that override component defaults.

type Registry

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

Registry holds all loaded prompt components and profiles.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new empty registry.

func (*Registry) GetComponent

func (r *Registry) GetComponent(name string) *Component

GetComponent returns a component by name.

func (*Registry) GetProfile

func (r *Registry) GetProfile(name string) *Profile

GetProfile returns a profile by name.

func (*Registry) GetProfilesByTag

func (r *Registry) GetProfilesByTag(tag string) []*Profile

GetProfilesByTag returns all profiles that have the specified tag. The returned slice contains pointers to the registry's internal profiles. Callers must not modify the returned profiles; doing so affects the registry's state. For safe modification, copy any data you need before mutating.

func (*Registry) GetProviderVariants

func (r *Registry) GetProviderVariants(baseName string) []string

GetProviderVariants returns all provider names that have variants for the base profile. For example, if "assistant.anthropic" and "assistant.openai" exist, calling GetProviderVariants("assistant") returns ["anthropic", "openai"].

func (*Registry) HasProviderVariant

func (r *Registry) HasProviderVariant(baseName, provider string) bool

HasProviderVariant checks if a specific provider variant exists for the base profile. Returns true only if the variant exists AND properly inherits from the base.

func (*Registry) ListComponents

func (r *Registry) ListComponents() []string

ListComponents returns all component names.

func (*Registry) ListProfiles

func (r *Registry) ListProfiles() []string

ListProfiles returns all profile names.

func (*Registry) LoadFromFS

func (r *Registry) LoadFromFS(fsys fs.FS, rootPath string, opts ...LoadOption) error

LoadFromFS loads components and profiles from a filesystem. By default, it expects a "components" directory for components and a "profiles" directory for profiles. Use WithComponentsDir and WithProfilesDir to customize.

Example (CustomDirs)
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
	"github.com/lirancohen/promptloom/storage"
)

func main() {
	store := storage.NewMemoryStorage()

	// Add component to custom "snippets" directory
	store.AddFile("snippets/greeting.yaml", `
name: greeting
instructions: Hello from custom directory!
`)

	// Add profile to custom "configs" directory
	store.AddFile("configs/main.yaml", `
name: main
components:
  - greeting
`)

	registry := promptloom.NewRegistry()
	_ = registry.LoadFromFS(store.FS(), store.RootPath(),
		promptloom.WithComponentsDir("snippets"),
		promptloom.WithProfilesDir("configs"),
	)

	assembler := promptloom.NewAssembler(registry)
	prompt, _ := assembler.Assemble("main", promptloom.NewContext())
	fmt.Println(prompt)
}
Output:
Hello from custom directory!

func (*Registry) RegisterComponent

func (r *Registry) RegisterComponent(c *Component)

RegisterComponent adds a component to the registry. Panics if the component is nil or has an empty name.

func (*Registry) RegisterProfile

func (r *Registry) RegisterProfile(p *Profile)

RegisterProfile adds a profile to the registry. Panics if the profile is nil or has an empty name.

func (*Registry) Validate

func (r *Registry) Validate() error

Validate checks the registry for configuration errors. By default, it only checks for circular inheritance. Use ValidateStrict() or pass options to enable additional checks.

Example
package main

import (
	"fmt"

	"github.com/lirancohen/promptloom"
)

func main() {
	registry := promptloom.NewRegistry()

	// Register a profile with circular inheritance
	registry.RegisterProfile(&promptloom.Profile{Name: "a", Inherits: "b"})
	registry.RegisterProfile(&promptloom.Profile{Name: "b", Inherits: "a"})

	err := registry.Validate()
	if err != nil {
		fmt.Println("Validation error detected")
	}
}
Output:
Validation error detected

func (*Registry) ValidateConditions

func (r *Registry) ValidateConditions(ctx *Context) []error

ValidateConditions validates all condition expressions in the registry against the given context. This allows upfront detection of malformed conditions before assembly. Returns a slice of errors for any invalid conditions found. Returns nil if all conditions are valid.

func (*Registry) ValidateStrict

func (r *Registry) ValidateStrict() error

ValidateStrict checks the registry for all configuration errors including unknown component and profile references.

func (*Registry) ValidateWithOptions

func (r *Registry) ValidateWithOptions(opts ValidateOptions) error

ValidateWithOptions checks the registry with the specified options.

type Section

type Section struct {
	// Name is the unique identifier for this section within the component
	Name string `yaml:"name"`

	// Content is the text content of this section
	Content string `yaml:"content"`

	// Condition is an optional expression that determines if this section is included.
	// Supports the same syntax as component conditions:
	//   - Simple flags: "has_agent"
	//   - Negation: "!has_agent"
	//   - AND: "has_agent && is_premium"
	//   - OR: "has_agent || is_demo"
	//   - String comparisons: "tier == 'premium'"
	//   - Numeric comparisons: "count > 10"
	Condition string `yaml:"condition,omitempty"`

	// Priority determines the order of inclusion (higher = included earlier).
	// Sections are sorted by priority in descending order, so higher priority
	// sections appear before lower priority sections in the assembled prompt.
	// Default is 0. Sections with equal priority maintain their definition order.
	Priority int `yaml:"priority,omitempty"`
}

Section represents an ordered, conditional section within a component. Sections allow for fine-grained control over prompt content with support for conditions and priority-based ordering.

type SkippedComponent

type SkippedComponent struct {
	// Name is the component name
	Name string

	// Reason explains why it was skipped
	Reason string
}

SkippedComponent records why a component was not included.

type SkippedSection

type SkippedSection struct {
	// ComponentName is the parent component
	ComponentName string

	// SectionName is the section that was skipped
	SectionName string

	// Reason explains why it was skipped
	Reason string
}

SkippedSection records which sections were skipped and why.

type StringOrSlice

type StringOrSlice []string

StringOrSlice is a type that can unmarshal from either a YAML string or sequence. This provides flexible YAML syntax: depends_on: foo OR depends_on: [foo, bar]

func (*StringOrSlice) UnmarshalYAML

func (s *StringOrSlice) UnmarshalYAML(value *yaml.Node) error

UnmarshalYAML implements yaml.Unmarshaler for StringOrSlice.

type TemplateData

type TemplateData struct {
	// Flags contains boolean flags from the context
	Flags map[string]bool

	// Values contains string values from the context
	Values map[string]string

	// Numbers contains integer values from the context
	Numbers map[string]int

	// Data contains arbitrary data from the context
	Data map[string]any

	// Component contains metadata about the current component
	Component ComponentData
}

TemplateData is the data structure passed to component templates.

type ValidateOptions

type ValidateOptions struct {
	// Strict enables strict mode which reports errors for:
	// - Profiles referencing unknown components
	// - Profiles inheriting from unknown parents
	// In lenient mode (default), these are silently ignored at assembly time.
	Strict bool
}

ValidateOptions configures validation behavior.

type ValidationError

type ValidationError struct {
	Errors []string
}

ValidationError represents one or more validation issues found in the registry.

func (*ValidationError) Add

func (e *ValidationError) Add(format string, args ...any)

Add appends an error message to the validation error.

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) HasErrors

func (e *ValidationError) HasErrors() bool

HasErrors returns true if any validation errors were recorded.

Directories

Path Synopsis
Package format provides utilities for converting PromptLoom messages to provider-specific formats.
Package format provides utilities for converting PromptLoom messages to provider-specific formats.
anthropic
Package anthropic provides formatting for Anthropic Messages API.
Package anthropic provides formatting for Anthropic Messages API.
openai
Package openai provides formatting for OpenAI Chat Completions API.
Package openai provides formatting for OpenAI Chat Completions API.
Package storage provides storage interfaces for prompt components and profiles.
Package storage provides storage interfaces for prompt components and profiles.

Jump to

Keyboard shortcuts

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