Repository: tmc/langchaingo
Pull request #57 (2023-04-24 22:03:15)

prompts: simplify prompts package
This change simplifies the concepts and implementation of the prompts package. I want to keep things as simple as possible as long as possible.

### PR Checklist

- [x] I am an existing contributor.



Patch:
diff --git a/.golangci.yaml b/.golangci.yaml
index 42566c14..616add80 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -26,7 +26,19 @@ linters:
 linters-settings:
   funlen:
     lines: 90
-
+  depguard:
+    list-type: denylist
+    packages:
+      - github.com/samber/lo # Use exp packages or internal utilities instead.
+    additional-guards:
+      - list-type: denylist
+        include-go-root: false
+        packages:
+          - github.com/stretchr/testify
+        # Specify rules by which the linter ignores certain files for consideration.
+        ignore-file-rules:
+          - "**/*_test.go"
+          - "**/mock/**/*.go"
 run:
   skip-dirs:
     - 'exp'
diff --git a/Makefile b/Makefile
index 25277019..de61f529 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ test:
 
 .PHONY: lint
 lint:
-	golangci-lint run --color=always --sort-results --skip-dirs=exp ./...
+	golangci-lint run --color=always --sort-results ./...
 
 .PHONY: lint-exp
 lint-exp:
@@ -33,3 +33,10 @@ test-cover:
 .PHONY: run-pkgsite
 run-pkgsite:
 	go run golang.org/x/pkgsite/cmd/pkgsite@latest
+
+.PHONY: clean
+clean: clean-lint-cache
+
+.PHONY: clean-lint-cache
+clean-lint-cache:
+	golangci-lint cache clean
diff --git a/go.mod b/go.mod
index bbd5468f..c46ff9c9 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,6 @@ go 1.20
 
 require (
 	github.com/google/uuid v1.3.0
-	github.com/samber/lo v1.38.1
 	github.com/stretchr/testify v1.8.2
 )
 
@@ -14,4 +13,7 @@ require (
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
-require golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
+require (
+	github.com/google/go-cmp v0.5.9
+	golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17
+)
diff --git a/go.sum b/go.sum
index f93c1886..52666a42 100644
--- a/go.sum
+++ b/go.sum
@@ -1,12 +1,12 @@
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
-github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
diff --git a/prompts/prompt.go b/prompts/prompt.go
deleted file mode 100644
index 0e00c27b..00000000
--- a/prompts/prompt.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package prompts
-
-import (
-	"github.com/tmc/langchaingo/schema"
-)
-
-var _ schema.PromptValue = StringPromptValue{}
-
-// StringPromptValue is a prompt value that is a string.
-type StringPromptValue struct {
-	value string
-}
-
-func (v StringPromptValue) String() string {
-	return v.value
-}
-
-func (v StringPromptValue) Messages() []schema.ChatMessage {
-	return []schema.ChatMessage{
-		schema.HumanChatMessage{Text: v.value},
-	}
-}
-
-// NewStringPromptValue creates a new StringPromptValue.
-func NewStringPromptValue(value string) StringPromptValue {
-	return StringPromptValue{value: value}
-}
diff --git a/prompts/prompt_template.go b/prompts/prompt_template.go
index 061fb9f4..1e754021 100644
--- a/prompts/prompt_template.go
+++ b/prompts/prompt_template.go
@@ -3,316 +3,104 @@ package prompts
 import (
 	"errors"
 	"fmt"
-	"strings"
 
-	"github.com/samber/lo"
 	"github.com/tmc/langchaingo/schema"
+	"golang.org/x/exp/maps"
 )
 
 var (
-	ErrInputVariableReserved      = errors.New("cannot have input variable")
-	ErrPartialVariableReserved    = errors.New("cannot have partial variable")
+	// ErrInputVariableReserved is returned when there is a conflict with a reserved variable name.
+	ErrInputVariableReserved = errors.New("conflict with reserved variable name")
+	// ErrInvalidPartialVariableType is returned when the partial variable is not a string or a function.
 	ErrInvalidPartialVariableType = errors.New("invalid partial variable type")
 )
 
-// PromptTemplateBaseOptions are the options that are common to all prompt templates.
-type PromptTemplateBaseOptions struct {
-	// A list of variable names the prompt template expects
+// PromptTemplate contains common fields for all prompt templates.
+type PromptTemplate struct {
+	// Template is the prompt template.
+	Template string
+
+	// A list of variable names the prompt template expects.
 	InputVariables []string
-	// How to parse the output of calling an LLM on this formatted prompt
+
+	// TemplateFormat is the format of the prompt template.
+	TemplateFormat TemplateFormat
+
+	// OutputParser is a function that parses the output of the prompt template.
 	OutputParser schema.OutputParser[any]
-	// Partial variables
+
+	// PartialVariables represents a map of variable names to values or functions that return values.
+	// If the value is a function, it will be called when the prompt template is rendered.
 	PartialVariables map[string]any
 }
 
-// checkInputVariables validates the input variable names do not include restricted names.
-func checkInputVariables(inputVariables []string) error {
-	for _, variable := range inputVariables {
-		if variable == "stop" {
-			return ErrInputVariableReserved
-		}
-	}
-
-	return nil
+// NewPromptTemplate returns a new prompt template.
+func NewPromptTemplate(template string, inputVars []string) (*PromptTemplate, error) {
+	return &PromptTemplate{
+		Template:       template,
+		InputVariables: inputVars,
+		TemplateFormat: TemplateFormatGoTemplate,
+	}, nil
 }
 
-// checkPartialVariables validates the partial variable names do not include restricted names.
-func checkPartialVariables(partialValues map[string]any) error {
-	for k := range partialValues {
-		if k == "stop" {
-			return ErrPartialVariableReserved
-		}
-	}
-
-	return nil
-}
+var (
+	_ Formatter      = (*PromptTemplate)(nil)
+	_ FormatPrompter = (*PromptTemplate)(nil)
+)
 
-// applyPromptTemplateBaseOptions applies the input options to the default options.
-func applyPromptTemplateBaseOptions(input PromptTemplateBaseOptions) (PromptTemplateBaseOptions, error) {
-	opts := PromptTemplateBaseOptions{
-		InputVariables:   make([]string, 0),
-		OutputParser:     nil,
-		PartialVariables: make(map[string]any),
-	}
-	if len(input.InputVariables) != 0 {
-		if err := checkInputVariables(input.InputVariables); err != nil {
-			return opts, err
-		}
-		opts.InputVariables = append(opts.InputVariables, input.InputVariables...)
+// Format formats the prompt template and returns a string value.
+func (p PromptTemplate) Format(values map[string]any) (string, error) {
+	if err := checkInputVariables(p.InputVariables); err != nil {
+		return "", err
 	}
-	if input.OutputParser != nil {
-		opts.OutputParser = input.OutputParser
+	if err := checkPartialVariables(p.PartialVariables); err != nil {
+		return "", err
 	}
-	if len(input.PartialVariables) != 0 {
-		if err := checkPartialVariables(input.PartialVariables); err != nil {
-			return opts, err
-		}
-
-		for k, v := range input.PartialVariables {
-			opts.PartialVariables[k] = v
-		}
+	resolvedValues, err := resolvePartialValues(p.PartialVariables, values)
+	if err != nil {
+		return "", err
 	}
-
-	return opts, nil
+	return RenderTemplate(p.Template, p.TemplateFormat, resolvedValues)
 }
 
-// TemplateType is the string type key uniquely identifying this class
-// of prompt template.
-type TemplateType string
-
-const (
-	// TemplateTypePrompt is the type for SerializedPromptTemplate.
-	TemplateTypePrompt TemplateType = "prompt"
-	// TemplateTypeFewShot is the type for SerializedFewShotTemplate.
-	TemplateTypeFewShot TemplateType = "few_shot"
-	// TemplateTypeMessage is the type for SerializedMessagePromptTemplate.
-	TemplateTypeMessage TemplateType = "message"
-	// TemplateTypeChatPrompt is the type for SerializedChatPromptTemplate.
-	TemplateTypeChatPrompt TemplateType = "chat_prompt"
-)
-
-// PromptTemplater is an interface for prompt templates. It is also equivalent to
-// BasePromptTemplate in langchain or BasePromptTemplate in langchainjs.
-type PromptTemplater interface {
-	MergePartialAndUserVariables(userVariables map[string]any) (map[string]any, error)
-	// Format formats the prompt given the input values.
-	Format(values map[string]any) (string, error)
-	// FormatPromptValue formats the prompt given the input values and return a formatted prompt value.
-	FormatPromptValue(values map[string]any) (schema.PromptValue, error)
-	// GetPromptType returns the string type key uniquely identifying this class of prompt template.
-	GetPromptType() TemplateType
-}
-
-// formatPromptValue formats the prompt given the input values and return a formatted prompt value.
-func formatPromptValue(promptTemplater PromptTemplater, values map[string]any) (schema.PromptValue, error) {
-	formattedPrompt, err := promptTemplater.Format(values)
+// FormatPrompt formats the prompt template and returns a string prompt value.
+func (p PromptTemplate) FormatPrompt(values map[string]any) (schema.PromptValue, error) { //nolint:ireturn
+	f, err := p.Format(values)
 	if err != nil {
 		return nil, err
 	}
-
-	return NewStringPromptValue(formattedPrompt), nil
+	return StringPromptValue(f), nil
 }
 
-// mergePartialAndUserVariables merges the partial variables and user variables. This is the common
-// and default implementation for PromptTemplater.
-func mergePartialAndUserVariables(
-	partialVariables map[string]any,
-	userVariables map[string]any,
-) (map[string]any, error) {
-	if partialVariables == nil {
-		partialVariables = make(map[string]any)
-	}
-
-	partialValues := make(map[string]any)
-	for k, v := range partialVariables {
-		switch val := v.(type) {
+func resolvePartialValues(partialValues map[string]any, values map[string]any) (map[string]any, error) {
+	resolvedValues := make(map[string]any)
+	for variable, value := range partialValues {
+		switch value := value.(type) {
 		case string:
-			partialValues[k] = val
-		case func() any:
-			partialValues[k] = val()
+			resolvedValues[variable] = value
+		case func() string:
+			resolvedValues[variable] = value()
 		default:
-			return make(map[string]any), fmt.Errorf("%w: %T of %v", ErrInvalidPartialVariableType, val, val)
+			return nil, fmt.Errorf("%w: %v", ErrInvalidPartialVariableType, variable)
 		}
 	}
-
-	allKwargs := make(map[string]any, len(partialValues)+len(userVariables))
-	for k, v := range partialValues {
-		allKwargs[k] = v
-	}
-	for k, v := range userVariables {
-		allKwargs[k] = v
+	for variable, value := range values {
+		resolvedValues[variable] = value
 	}
-
-	return allKwargs, nil
-}
-
-// PromptTemplateOptions is the option to create a PromptTemplate. Is is also equivalent to
-// PromptTemplate.
-type PromptTemplateOptions struct {
-	PromptTemplateBaseOptions
-
-	// The propmt template
-	Template string
-	// The format of the prompt template. Options are 'go-template', 'f-string', 'jinja-2'
-	TemplateFormat TemplateFormat
-	// Whether or not to try validating the template on initialization
-	ValidateTemplate bool
+	return resolvedValues, nil
 }
 
-var _ PromptTemplater = PromptTemplate{}
-
-// PromptTemplate is the default implementation of PromptTemplater.
-type PromptTemplate struct {
-	opts PromptTemplateOptions
-}
-
-// NewPromptTemplate creates a new PromptTemplate from the given option.
-func NewPromptTemplate(inputOpts PromptTemplateOptions) (*PromptTemplate, error) {
-	baseOpts, err := applyPromptTemplateBaseOptions(inputOpts.PromptTemplateBaseOptions)
-	if err != nil {
-		return nil, err
-	}
-
-	opts := PromptTemplateOptions{
-		PromptTemplateBaseOptions: baseOpts,
-		TemplateFormat:            TemplateFormatGoTemplate,
-		ValidateTemplate:          true,
-	}
-	if inputOpts.Template != "" {
-		opts.Template = inputOpts.Template
-	}
-	if inputOpts.TemplateFormat != "" && inputOpts.TemplateFormat != TemplateFormatGoTemplate {
-		opts.TemplateFormat = inputOpts.TemplateFormat
-	}
-	if !inputOpts.ValidateTemplate {
-		opts.ValidateTemplate = inputOpts.ValidateTemplate
-	}
-
-	p := &PromptTemplate{opts: opts}
-
-	if p.opts.ValidateTemplate {
-		totalInputVariables := make([]string, 0, len(p.opts.InputVariables)+len(p.opts.PartialVariables))
-		totalInputVariables = append(totalInputVariables, p.opts.InputVariables...)
-		totalInputVariables = append(totalInputVariables, lo.Keys(p.opts.PartialVariables)...)
-		err = CheckValidTemplate(p.opts.Template, p.opts.TemplateFormat, totalInputVariables)
-		if err != nil {
-			return nil, err
+// checkInputVariables validates the input variable names do not include restricted names.
+func checkInputVariables(inputVariables []string) error {
+	for _, variable := range inputVariables {
+		if variable == "stop" {
+			return fmt.Errorf("%w: %v", ErrInputVariableReserved, variable)
 		}
 	}
-
-	return p, nil
-}
-
-func (p PromptTemplate) MergePartialAndUserVariables(userVariables map[string]any) (map[string]any, error) {
-	return mergePartialAndUserVariables(p.opts.PartialVariables, userVariables)
-}
-
-func (p PromptTemplate) GetPromptType() TemplateType {
-	return TemplateTypePrompt
-}
-
-func (p PromptTemplate) Format(values map[string]any) (string, error) {
-	allValues, err := p.MergePartialAndUserVariables(values)
-	if err != nil {
-		return "", err
-	}
-
-	return RenderTemplate(p.opts.Template, p.opts.TemplateFormat, allValues)
-}
-
-func (p PromptTemplate) FormatPromptValue(values map[string]any) (schema.PromptValue, error) {
-	formattedPrompt, err := p.Format(values)
-	if err != nil {
-		return nil, err
-	}
-
-	return NewStringPromptValue(formattedPrompt), nil
-}
-
-// PromptTemplateFromExamplesOption is the option to create a PromptTemplate from examples.
-type PromptTemplateFromExamplesOption struct {
-	// List of examples to use in the prompt.
-	Examples []string
-	// String to go after the list of examples. Should generally set up the user's input.
-	Suffix string
-	// A list of variable names the final prompt template will expect
-	InputVariables []string
-	// The separator to use in between examples
-	ExampleSeparator string
-	// String that should go before any examples. Generally includes examples.
-	Prefix string
-}
-
-// NewPromptTemplateFromExamples takes examples in list format with prefix and suffix
-// to create a prompt. It is equivalent to PromptTemplate.from_examples in langchain and
-// PromptTemplate.fromExamples in langchainjs.
-//
-// Intended to be used a way to dynamically create a prompt from examples.
-func NewPromptTemplateFromExamples(inputOpts PromptTemplateFromExamplesOption) (*PromptTemplate, error) {
-	opts := PromptTemplateFromExamplesOption{
-		Examples:         make([]string, 0),
-		InputVariables:   make([]string, 0),
-		ExampleSeparator: "\n\n",
-	}
-	if inputOpts.Examples != nil {
-		opts.Examples = inputOpts.Examples
-	}
-	if inputOpts.Suffix != "" {
-		opts.Suffix = inputOpts.Suffix
-	}
-	if inputOpts.InputVariables != nil {
-		opts.InputVariables = inputOpts.InputVariables
-	}
-	if inputOpts.ExampleSeparator != "" {
-		opts.ExampleSeparator = inputOpts.ExampleSeparator
-	}
-	if inputOpts.Prefix != "" {
-		opts.Prefix = inputOpts.Prefix
-	}
-
-	templateSlice := make([]string, 0, len(inputOpts.Examples)+1+1)
-	templateSlice = append(templateSlice, inputOpts.Prefix)
-	templateSlice = append(templateSlice, inputOpts.Examples...)
-	templateSlice = append(templateSlice, inputOpts.Suffix)
-	template := strings.Join(templateSlice, inputOpts.ExampleSeparator)
-	return NewPromptTemplate(PromptTemplateOptions{
-		PromptTemplateBaseOptions: PromptTemplateBaseOptions{
-			InputVariables: inputOpts.InputVariables,
-		},
-		Template: template,
-	})
-}
-
-// PromptTemplateFromFStringOption is the option to create a PromptTemplate from an f-string template.
-type PromptTemplateFromFStringOption struct {
-	// The f-string template
-	Template string
-	// Whether or not to try validating the template on initialization
-	ValidateTemplate bool
-	// How to parse the output of calling an LLM on this formatted prompt
-	OutputParser schema.OutputParser[any]
-	// Partial variables
-	PartialVariables map[string]any
-}
-
-// NewPromptTemplateFromFStringTemplate loads prompt template from a template f-string. It is equivalent
-// to PromptTemplate.from_template in langchain and PromptTemplate.fromTemplate in langchainjs.
-func NewPromptTemplateFromFStringTemplate(PromptTemplateFromFStringOption) (*PromptTemplate, error) {
-	// TODO: implement, after f-string template is implemented
-	panic("not implemented")
-}
-
-// PromptTemplateFromFileOption is the option to create a PromptTemplate from a file.
-type PromptTemplateFromFileOption struct {
-	// The path to the file containing the template
-	TemplateFile string
-	// A list of variable names the prompt template expects
-	InputVariables []string
+	return nil
 }
 
-// NewPromptTemplateFromFile loads prompt template from a file. It is equivalent to
-// PromptTemplate.from_file in langchain.
-func NewPromptTemplateFromFile(PromptTemplateFromFileOption) (*PromptTemplate, error) {
-	// TODO: implement
-	panic("not implemented")
+// checkPartialVariables validates the partial variable names do not include restricted names.
+func checkPartialVariables(partialValues map[string]any) error {
+	return checkInputVariables(maps.Keys(partialValues))
 }
diff --git a/prompts/prompts.go b/prompts/prompts.go
new file mode 100644
index 00000000..a7132d8d
--- /dev/null
+++ b/prompts/prompts.go
@@ -0,0 +1,13 @@
+package prompts
+
+import "github.com/tmc/langchaingo/schema"
+
+// Formatter is an interface for formatting a map of values into a string.
+type Formatter interface {
+	Format(values map[string]any) (string, error)
+}
+
+// FormatPrompter is an interface for formatting a map of values into a prompt.
+type FormatPrompter interface {
+	FormatPrompt(values map[string]any) (schema.PromptValue, error)
+}
diff --git a/prompts/string_prompt.go b/prompts/string_prompt.go
new file mode 100644
index 00000000..60cc8353
--- /dev/null
+++ b/prompts/string_prompt.go
@@ -0,0 +1,21 @@
+package prompts
+
+import (
+	"github.com/tmc/langchaingo/schema"
+)
+
+var _ schema.PromptValue = StringPromptValue("")
+
+// StringPromptValue is a prompt value that is a string.
+type StringPromptValue string
+
+func (v StringPromptValue) String() string {
+	return string(v)
+}
+
+// Messages returns a single-element ChatMessage slice.
+func (v StringPromptValue) Messages() []schema.ChatMessage {
+	return []schema.ChatMessage{
+		schema.HumanChatMessage{Text: string(v)},
+	}
+}
diff --git a/prompts/templates.go b/prompts/templates.go
index 4de4db4e..e2529929 100644
--- a/prompts/templates.go
+++ b/prompts/templates.go
@@ -6,50 +6,41 @@ import (
 	"strings"
 	"text/template"
 
-	"github.com/samber/lo"
+	"golang.org/x/exp/maps"
 )
 
 // ErrInvalidTemplateFormat is the error when the template format is invalid and
 // not supported.
 var ErrInvalidTemplateFormat = errors.New("invalid template format")
 
-// TemplateFormat is the format of the template, options: go-template, f-string, jinja2.
+// TemplateFormat is the format of the template.
 type TemplateFormat string
 
 const (
-	// TemplateFormatGoTemplate is the format for Go Template
-	// TemplateFormatGoTemplate.
+	// TemplateFormatGoTemplate is the format for go-template.
 	TemplateFormatGoTemplate TemplateFormat = "go-template"
-	// TemplateFormatFString is the format for f-string
-	// TODO: Add support for f-string.
-	TemplateFormatFString TemplateFormat = "f-string"
-	// TemplateFormatJinja2 is the format for jinja2
-	// TODO: Add support for jinja2.
-	TemplateFormatJinja2 TemplateFormat = "jinja2"
 )
 
-// Interpolator is the function that interpolates the given template with the given values.
-type Interpolator func(string, TemplateFormat, map[string]any) (string, error)
+// interpolator is the function that interpolates the given template with the given values.
+type interpolator func(template string, values map[string]any) (string, error)
 
-// DefaultFormatterMapping is the default mapping of TemplateFormat to Interpolator.
-var DefaultFormatterMapping = map[TemplateFormat]Interpolator{
+// defaultFormatterMapping is the default mapping of TemplateFormat to interpolator.
+var defaultformatterMapping = map[TemplateFormat]interpolator{ //nolint:gochecknoglobals
 	TemplateFormatGoTemplate: interpolateGoTemplate,
 }
 
 // interpolateGoTemplate interpolates the given template with the given values by using
 // text/template.
-func interpolateGoTemplate(tmpl string, _ TemplateFormat, values map[string]any) (string, error) {
+func interpolateGoTemplate(tmpl string, values map[string]any) (string, error) {
 	parsedTmpl, err := template.New("template").Parse(tmpl)
 	if err != nil {
 		return "", err
 	}
-
 	sb := new(strings.Builder)
 	err = parsedTmpl.Execute(sb, values)
 	if err != nil {
 		return "", err
 	}
-
 	return sb.String(), nil
 }
 
@@ -57,14 +48,14 @@ func newInvalidTemplateError(gotTemplateFormat TemplateFormat) error {
 	return fmt.Errorf("%w, got: %s, should be one of %s",
 		ErrInvalidTemplateFormat,
 		gotTemplateFormat,
-		lo.Keys(DefaultFormatterMapping),
+		maps.Keys(defaultformatterMapping),
 	)
 }
 
 // CheckValidTemplate checks if the template is valid through checking whether the given
 // TemplateFormat is available and whether the template can be rendered.
 func CheckValidTemplate(template string, templateFormat TemplateFormat, inputVariables []string) error {
-	_, ok := DefaultFormatterMapping[templateFormat]
+	_, ok := defaultformatterMapping[templateFormat]
 	if !ok {
 		return newInvalidTemplateError(templateFormat)
 	}
@@ -80,10 +71,10 @@ func CheckValidTemplate(template string, templateFormat TemplateFormat, inputVar
 
 // RenderTemplate renders the template with the given values.
 func RenderTemplate(tmpl string, tmplFormat TemplateFormat, values map[string]any) (string, error) {
-	formatter, ok := DefaultFormatterMapping[tmplFormat]
+	formatter, ok := defaultformatterMapping[tmplFormat]
 	if !ok {
 		return "", newInvalidTemplateError(tmplFormat)
 	}
 
-	return formatter(tmpl, tmplFormat, values)
+	return formatter(tmpl, values)
 }
diff --git a/schema/output_parsers.go b/schema/output_parsers.go
index 834fc417..817bb5a4 100644
--- a/schema/output_parsers.go
+++ b/schema/output_parsers.go
@@ -1,5 +1,6 @@
 package schema
 
+// OutputParser is an interface for parsing the output of an LLM call.
 type OutputParser[T any] interface {
 	// Parse parses the output of an LLM call.
 	Parse(text string) (T, error)


Test patch:
diff --git a/prompts/prompt_template_test.go b/prompts/prompt_template_test.go
index 98d5e74a..32521912 100644
--- a/prompts/prompt_template_test.go
+++ b/prompts/prompt_template_test.go
@@ -1,457 +1,78 @@
 package prompts
 
 import (
+	"fmt"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-	"github.com/tmc/langchaingo/schema"
+	"github.com/google/go-cmp/cmp"
 )
 
-func TestCheckInputVariables(t *testing.T) {
+func TestPromptTemplateFormatPrompt(t *testing.T) {
 	t.Parallel()
-
-	err := checkInputVariables([]string{"stop"})
-	require.Error(t, err)
-	assert.ErrorIs(t, err, ErrInputVariableReserved)
-
-	err = checkInputVariables([]string{"test"})
-	require.NoError(t, err)
-}
-
-func TestCheckPartialVariables(t *testing.T) {
-	t.Parallel()
-
-	err := checkPartialVariables(map[string]interface{}{"stop": "test"})
-	require.Error(t, err)
-	assert.ErrorIs(t, err, ErrPartialVariableReserved)
-
-	err = checkPartialVariables(map[string]interface{}{"test": "test"})
-	require.NoError(t, err)
-}
-
-var _ schema.OutputParser[any] = testEmptyOutputParser{}
-
-type testEmptyOutputParser struct{}
-
-func (t testEmptyOutputParser) Parse(_ string) (any, error) {
-	return nil, nil
-}
-
-func (t testEmptyOutputParser) GetFormatInstructions() string {
-	return ""
-}
-
-func (t testEmptyOutputParser) Type() string {
-	return ""
-}
-
-func TestApplyPromptTemplateBaseOptions(t *testing.T) {
-	t.Parallel()
-
-	_, err := applyPromptTemplateBaseOptions(PromptTemplateBaseOptions{
-		InputVariables: []string{"stop"},
-	})
-	require.Error(t, err)
-	assert.ErrorIs(t, err, ErrInputVariableReserved)
-
-	_, err = applyPromptTemplateBaseOptions(PromptTemplateBaseOptions{
-		PartialVariables: map[string]any{"stop": "test"},
-	})
-	require.Error(t, err)
-	assert.ErrorIs(t, err, ErrPartialVariableReserved)
-
-	opts, err := applyPromptTemplateBaseOptions(PromptTemplateBaseOptions{
-		InputVariables:   []string{"test"},
-		OutputParser:     testEmptyOutputParser{},
-		PartialVariables: map[string]any{"test": "test"},
-	})
-	require.NoError(t, err)
-	assert.Equal(t, PromptTemplateBaseOptions{
-		InputVariables:   []string{"test"},
-		OutputParser:     testEmptyOutputParser{},
-		PartialVariables: map[string]any{"test": "test"},
-	}, opts)
-}
-
-func TestFormatPromptValue(t *testing.T) {
-	t.Parallel()
-
-	t.Run("Error", func(t *testing.T) {
-		t.Parallel()
-
-		pt, err := NewPromptTemplate(PromptTemplateOptions{
-			PromptTemplateBaseOptions: PromptTemplateBaseOptions{
-				InputVariables: []string{"testFormatPromptValue"},
-			},
-			Template:         "Hello, {{ .test }}",
-			ValidateTemplate: true,
-		})
-		require.NoError(t, err)
-
-		pt.opts.Template = "Hello, {{{ .testFormatPromptValue }}"
-		_, err = formatPromptValue(pt, map[string]any{
-			"testFormatPromptValue": "world",
-		})
-		require.Error(t, err)
-	})
-
-	t.Run("Success", func(t *testing.T) {
-		t.Parallel()
-
-		pt, err := NewPromptTemplate(PromptTemplateOptions{
-			PromptTemplateBaseOptions: PromptTemplateBaseOptions{
-				InputVariables: []string{"testFormatPromptValue"},
-			},
-			Template:         "Hello, {{ .testFormatPromptValue }}",
-			ValidateTemplate: true,
-		})
-		require.NoError(t, err)
-
-		spv, err := formatPromptValue(pt, map[string]any{
-			"testFormatPromptValue": "world",
-		})
-		require.NoError(t, err)
-		assert.Equal(t, "Hello, world", spv.String())
-	})
-}
-
-func TestMergePartialAndUserVariables(t *testing.T) {
-	t.Parallel()
-
-	type test struct {
-		name             string
-		partialVariables map[string]any
-		userVariables    map[string]any
-		mergedVariables  map[string]any
-		err              error
-	}
-
-	testCases := []test{
-		{
-			name:             "NilPartialVariables",
-			partialVariables: nil,
-			userVariables: map[string]any{
-				"test": "world",
-			},
-			mergedVariables: map[string]any{
-				"test": "world",
-			},
-		},
+	cases := []struct {
+		name        string
+		template    string
+		inputVars   []string
+		partialVars map[string]any
+		vars        map[string]any
+		expected    string
+		wantErr     bool
+	}{
+		{"empty", "", nil, nil, nil, "", false},
+		{"ok-input-var", "", []string{"foobar"}, nil, nil, "", false},
+		{"bad-input-var", "", []string{"stop"}, nil, nil, "", true}, // expect a error.
+		{"hello world", "hello world", nil, nil, nil, "hello world", false},
+		{"basic", "hello {{.name}}", nil, nil, map[string]any{
+			"name": "richard",
+		}, "hello richard", false},
+		{"partials", "hello {{.name}}", nil, map[string]any{
+			"name": "richard",
+		}, nil, "hello richard", false},
 		{
-			name: "Success",
-			partialVariables: map[string]interface{}{
-				"test":  "test",
-				"test2": "test2",
-				"test3": func() any {
-					return "test3"
-				},
-			},
-			userVariables: map[string]any{
-				"test": "world",
+			"partials w func", "{{.greeting}} {{.name}}", nil,
+			map[string]any{
+				"name": func() string { return "richard" },
 			},
-			mergedVariables: map[string]any{
-				"test":  "world",
-				"test2": "test2",
-				"test3": "test3",
+			map[string]any{
+				"greeting": "hello",
 			},
+			"hello richard", false,
 		},
-	}
-
-	for _, tc := range testCases {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			t.Parallel()
-
-			mergedVariables, err := mergePartialAndUserVariables(tc.partialVariables, tc.userVariables)
-			require.NoError(t, err)
-			assert.Equal(t, tc.mergedVariables, mergedVariables)
-		})
-	}
-
-	errTestCases := []test{
 		{
-			name: "InvalidPartialVariableType",
-			partialVariables: map[string]interface{}{
-				"test": 1234,
+			"partials w err", "{{.greeting}} {{.name}}", nil,
+			map[string]any{
+				"name": func() string { return "richard" },
+				"stop": "foobar",
 			},
-			userVariables: map[string]any{
-				"test": "world",
+			map[string]any{
+				"greeting": "hello",
 			},
-			mergedVariables: make(map[string]any),
-			err:             ErrInvalidPartialVariableType,
+			"", true, // expect an error
 		},
 	}
 
-	for _, tc := range errTestCases {
+	for _, tc := range cases {
 		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
-
-			mergedVariables, err := mergePartialAndUserVariables(tc.partialVariables, tc.userVariables)
-			require.Error(t, err)
-			assert.Empty(t, mergedVariables)
-			assert.ErrorIs(t, err, tc.err)
+			p := PromptTemplate{
+				Template:         tc.template,
+				TemplateFormat:   TemplateFormatGoTemplate,
+				InputVariables:   tc.inputVars,
+				PartialVariables: tc.partialVars,
+			}
+			fp, err := p.FormatPrompt(tc.vars)
+			if (err != nil) != tc.wantErr {
+				t.Errorf("PromptTemplate.FormatPrompt() error = %v, wantErr %v", err, tc.wantErr)
+				return
+			}
+			if tc.wantErr {
+				return
+			}
+			got := fmt.Sprint(fp)
+			if cmp.Diff(tc.expected, got) != "" {
+				t.Errorf("unexpected prompt output (-want +got):\n%s", cmp.Diff(tc.expected, got))
+			}
 		})
 	}
 }
-
-func TestNewPromptTemplate(t *testing.T) {
-	t.Parallel()
-
-	_, err := NewPromptTemplate(PromptTemplateOptions{
-		PromptTemplateBaseOptions: PromptTemplateBaseOptions{
-			InputVariables: []string{"stop"},
-		},
-	})
-	require.Error(t, err)
-	assert.ErrorIs(t, err, ErrInputVariableReserved)
-
-	_, err = NewPromptTemplate(PromptTemplateOptions{
-		Template:         "Hello, {{ .test }}",
-		TemplateFormat:   "unknown",
-		ValidateTemplate: true,
-	})
-	require.Error(t, err)
-	assert.ErrorIs(t, err, ErrInvalidTemplateFormat)
-
-	_, err = NewPromptTemplate(PromptTemplateOptions{
-		Template:         "Hello, {{ .test }}",
-		ValidateTemplate: false,
-	})
-	require.NoError(t, err)
-}
-
-func TestPromptTemplateMergePartialAndUserVariables(t *testing.T) {
-	t.Parallel()
-
-	type test struct {
-		name             string
-		partialVariables map[string]any
-		userVariables    map[string]any
-		mergedVariables  map[string]any
-		err              error
-	}
-
-	testCases := []test{
-		{
-			name:             "NilPartialVariables",
-			partialVariables: nil,
-			userVariables:    map[string]any{"test": "world"},
-			mergedVariables:  map[string]any{"test": "world"},
-		},
-		{
-			name: "Success",
-			partialVariables: map[string]interface{}{
-				"test":  "test",
-				"test2": "test2",
-				"test3": func() any {
-					return "test3"
-				},
-			},
-			userVariables: map[string]any{"test": "world"},
-			mergedVariables: map[string]any{
-				"test":  "world",
-				"test2": "test2",
-				"test3": "test3",
-			},
-		},
-	}
-
-	for _, tc := range testCases {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			t.Parallel()
-
-			pt, err := NewPromptTemplate(PromptTemplateOptions{
-				PromptTemplateBaseOptions: PromptTemplateBaseOptions{
-					PartialVariables: tc.partialVariables,
-				},
-			})
-			require.NoError(t, err)
-			require.NotNil(t, pt)
-
-			mergedVariables, err := pt.MergePartialAndUserVariables(tc.userVariables)
-			require.NoError(t, err)
-			assert.Equal(t, tc.mergedVariables, mergedVariables)
-		})
-	}
-
-	errTestCases := []test{
-		{
-			name:             "InvalidPartialVariableType",
-			partialVariables: map[string]interface{}{"test": 1234},
-			userVariables:    map[string]any{"test": "world"},
-			mergedVariables:  make(map[string]any),
-			err:              ErrInvalidPartialVariableType,
-		},
-	}
-
-	for _, tc := range errTestCases {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			t.Parallel()
-
-			pt, err := NewPromptTemplate(PromptTemplateOptions{
-				PromptTemplateBaseOptions: PromptTemplateBaseOptions{
-					PartialVariables: tc.partialVariables,
-				},
-			})
-			require.NoError(t, err)
-			require.NotNil(t, pt)
-
-			mergedVariables, err := pt.MergePartialAndUserVariables(tc.userVariables)
-			require.Error(t, err)
-			assert.Empty(t, mergedVariables)
-			assert.ErrorIs(t, err, tc.err)
-		})
-	}
-}
-
-func TestPromptTemplateGetPromptType(t *testing.T) {
-	t.Parallel()
-
-	pt, err := NewPromptTemplate(PromptTemplateOptions{})
-	require.NoError(t, err)
-	assert.Equal(t, TemplateTypePrompt, pt.GetPromptType())
-}
-
-func TestPromptTemplateFormat(t *testing.T) {
-	t.Parallel()
-
-	t.Run("Error", func(t *testing.T) {
-		t.Parallel()
-
-		pt, err := NewPromptTemplate(PromptTemplateOptions{
-			PromptTemplateBaseOptions: PromptTemplateBaseOptions{
-				InputVariables: []string{"test"},
-				PartialVariables: map[string]any{
-					"test": 1234,
-				},
-			},
-			Template:         "Hello, {{ .test }}",
-			ValidateTemplate: true,
-		})
-		if err != nil {
-			t.Fatalf("Expected nil, got %v", err)
-		}
-
-		pt.opts.Template = "Hello, {{{ .test }}"
-		_, err = pt.Format(map[string]any{
-			"test": "world",
-		})
-		if err == nil {
-			t.Fatal("Expected error, got nil")
-		}
-	})
-
-	t.Run("Success", func(t *testing.T) {
-		t.Parallel()
-
-		pt, err := NewPromptTemplate(PromptTemplateOptions{
-			PromptTemplateBaseOptions: PromptTemplateBaseOptions{
-				InputVariables: []string{"test"},
-			},
-			Template:         "Hello, {{ .test }}",
-			ValidateTemplate: true,
-		})
-		if err != nil {
-			t.Fatalf("Expected nil, got %v", err)
-		}
-
-		str, err := pt.Format(map[string]any{
-			"test": "world",
-		})
-		if err != nil {
-			t.Fatalf("Expected nil, got %v", err)
-		}
-		if str != "Hello, world" {
-			t.Errorf("Expected 'Hello, world', got %s", str)
-		}
-	})
-}
-
-func TestPromptTemplateFormatPromptValue(t *testing.T) {
-	t.Parallel()
-
-	t.Run("Error", func(t *testing.T) {
-		t.Parallel()
-
-		pt, err := NewPromptTemplate(PromptTemplateOptions{
-			PromptTemplateBaseOptions: PromptTemplateBaseOptions{
-				InputVariables: []string{"test"},
-				PartialVariables: map[string]any{
-					"test": 1234,
-				},
-			},
-			Template:         "Hello, {{ .test }}",
-			ValidateTemplate: true,
-		})
-		if err != nil {
-			t.Fatalf("Expected nil, got %v", err)
-		}
-
-		pt.opts.Template = "Hello, {{{ .test }}"
-		_, err = pt.FormatPromptValue(map[string]any{
-			"test": "world",
-		})
-		if err == nil {
-			t.Fatal("Expected error, got nil")
-		}
-	})
-
-	t.Run("Success", func(t *testing.T) {
-		t.Parallel()
-
-		pt, err := NewPromptTemplate(PromptTemplateOptions{
-			PromptTemplateBaseOptions: PromptTemplateBaseOptions{
-				InputVariables: []string{"test"},
-			},
-			Template:         "Hello, {{ .test }}",
-			ValidateTemplate: true,
-		})
-		if err != nil {
-			t.Fatalf("Expected nil, got %v", err)
-		}
-
-		str, err := pt.FormatPromptValue(map[string]any{
-			"test": "world",
-		})
-		if err != nil {
-			t.Fatalf("Expected nil, got %v", err)
-		}
-		if str.String() != "Hello, world" {
-			t.Errorf("Expected 'Hello, world', got %s", str.String())
-		}
-	})
-}
-
-func TestNewPromptTemplateFromExamples(t *testing.T) {
-	t.Parallel()
-
-	pt, err := NewPromptTemplateFromExamples(PromptTemplateFromExamplesOption{
-		Examples:         []string{"- example 1", "- example 2"},
-		Suffix:           "Thank you!",
-		InputVariables:   []string{"test"},
-		ExampleSeparator: "\n\n",
-		Prefix:           "Hello, {{ .test }}, output as:",
-	})
-	require.NoError(t, err)
-	assert.Equal(t, pt.opts.Template, "Hello, {{ .test }}, output as:\n\n- example 1\n\n- example 2\n\nThank you!")
-}
-
-func TestNewPromptTemplateFromFStringTemplate(t *testing.T) {
-	t.Parallel()
-
-	require.PanicsWithValue(t, "not implemented", func() {
-		_, _ = NewPromptTemplateFromFStringTemplate(PromptTemplateFromFStringOption{})
-	})
-}
-
-func TestNewPromptTemplateFromFile(t *testing.T) {
-	t.Parallel()
-
-	require.PanicsWithValue(t, "not implemented", func() {
-		_, _ = NewPromptTemplateFromFile(PromptTemplateFromFileOption{})
-	})
-}
diff --git a/prompts/prompt_test.go b/prompts/prompt_test.go
index 7ef99b91..5da5ead2 100644
--- a/prompts/prompt_test.go
+++ b/prompts/prompt_test.go
@@ -10,11 +10,11 @@ import (
 func TestStringPromptValueString(t *testing.T) {
 	t.Parallel()
 
-	spv := NewStringPromptValue("")
+	spv := StringPromptValue("")
 	str := spv.String()
 	assert.Empty(t, str)
 
-	spv = NewStringPromptValue("test")
+	spv = StringPromptValue("test")
 	str = spv.String()
 	assert.Equal(t, "test", str)
 }
@@ -22,11 +22,11 @@ func TestStringPromptValueString(t *testing.T) {
 func TestStringPromptValueMessages(t *testing.T) {
 	t.Parallel()
 
-	spv := NewStringPromptValue("")
+	spv := StringPromptValue("")
 	msgs := spv.Messages()
 	require.Len(t, msgs, 1)
 
-	spv = NewStringPromptValue("test")
+	spv = StringPromptValue("test")
 	msgs = spv.Messages()
 	require.Len(t, msgs, 1)
 	assert.Equal(t, "test", msgs[0].GetText())
diff --git a/prompts/templates_test.go b/prompts/templates_test.go
index 784a1bfc..dc441815 100644
--- a/prompts/templates_test.go
+++ b/prompts/templates_test.go
@@ -53,7 +53,7 @@ func TestInterpolateGoTemplate(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
 
-			actual, err := interpolateGoTemplate(tc.template, TemplateFormatGoTemplate, tc.templateValues)
+			actual, err := interpolateGoTemplate(tc.template, tc.templateValues)
 			require.NoError(t, err)
 			assert.Equal(t, tc.expected, actual)
 		})
@@ -79,7 +79,7 @@ func TestInterpolateGoTemplate(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
 
-			_, err := interpolateGoTemplate(tc.template, TemplateFormatGoTemplate, map[string]any{})
+			_, err := interpolateGoTemplate(tc.template, map[string]any{})
 			require.Error(t, err)
 			assert.EqualError(t, err, tc.errValue)
 		})