Repository: tmc/langchaingo
Pull request #450 (2023-12-22 05:14:50)

Feat: support render prompt template with jinjia2
### PR Checklist

- [x] Read the [Contributing documentation](https://github.com/tmc/langchaingo/blob/main/CONTRIBUTING.md).
- [x] Read the [Code of conduct documentation](https://github.com/tmc/langchaingo/blob/main/CODE_OF_CONDUCT.md).
- [x] Name your Pull Request title clearly, concisely, and prefixed with the name of the primarily affected package you changed according to [Good commit messages](https://go.dev/doc/contribute#commit_messages) (such as `memory: add interfaces for X, Y` or `util: add whizzbang helpers`).
- [x] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
- [x] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `Fixes #123`).
- [x] Describes the source of new concepts.
- [x] References existing implementations as appropriate.
- [x] Contains test coverage for new functions.
- [x] Passes all [`golangci-lint`](https://golangci-lint.run/) checks.



Patch:
diff --git a/agents/conversational.go b/agents/conversational.go
index 9daa779d..1a493b7d 100644
--- a/agents/conversational.go
+++ b/agents/conversational.go
@@ -45,7 +45,11 @@ func NewConversationalAgent(llm llms.LanguageModel, tools []tools.Tool, opts ...
 	}
 
 	return &ConversationalAgent{
-		Chain:            chains.NewLLMChain(llm, options.getConversationalPrompt(tools)),
+		Chain: chains.NewLLMChain(
+			llm,
+			options.getConversationalPrompt(tools),
+			chains.WithCallback(options.callbacksHandler),
+		),
 		Tools:            tools,
 		OutputKey:        options.outputKey,
 		CallbacksHandler: options.callbacksHandler,
diff --git a/agents/mrkl.go b/agents/mrkl.go
index 574e8d20..8bdcb4bc 100644
--- a/agents/mrkl.go
+++ b/agents/mrkl.go
@@ -48,7 +48,11 @@ func NewOneShotAgent(llm llms.LanguageModel, tools []tools.Tool, opts ...Creatio
 	}
 
 	return &OneShotZeroAgent{
-		Chain:            chains.NewLLMChain(llm, options.getMrklPrompt(tools)),
+		Chain: chains.NewLLMChain(
+			llm,
+			options.getMrklPrompt(tools),
+			chains.WithCallback(options.callbacksHandler),
+		),
 		Tools:            tools,
 		OutputKey:        options.outputKey,
 		CallbacksHandler: options.callbacksHandler,
diff --git a/callbacks/callbacks.go b/callbacks/callbacks.go
index 56e63dcf..dde823be 100644
--- a/callbacks/callbacks.go
+++ b/callbacks/callbacks.go
@@ -15,10 +15,13 @@ type Handler interface {
 	HandleText(ctx context.Context, text string)
 	HandleLLMStart(ctx context.Context, prompts []string)
 	HandleLLMEnd(ctx context.Context, output llms.LLMResult)
+	HandleLLMError(ctx context.Context, err error)
 	HandleChainStart(ctx context.Context, inputs map[string]any)
 	HandleChainEnd(ctx context.Context, outputs map[string]any)
+	HandleChainError(ctx context.Context, err error)
 	HandleToolStart(ctx context.Context, input string)
 	HandleToolEnd(ctx context.Context, output string)
+	HandleToolError(ctx context.Context, err error)
 	HandleAgentAction(ctx context.Context, action schema.AgentAction)
 	HandleRetrieverStart(ctx context.Context, query string)
 	HandleRetrieverEnd(ctx context.Context, query string, documents []schema.Document)
diff --git a/callbacks/log.go b/callbacks/log.go
index cb828e66..4920a6f4 100644
--- a/callbacks/log.go
+++ b/callbacks/log.go
@@ -29,6 +29,10 @@ func (l LogHandler) HandleLLMEnd(_ context.Context, output llms.LLMResult) {
 	fmt.Println("Exiting LLM with results:", formatLLMResult(output))
 }
 
+func (l LogHandler) HandleLLMError(_ context.Context, err error) {
+	fmt.Println("Exiting LLM with error:", err)
+}
+
 func (l LogHandler) HandleChainStart(_ context.Context, inputs map[string]any) {
 	fmt.Println("Entering chain with inputs:", formatChainValues(inputs))
 }
@@ -37,6 +41,10 @@ func (l LogHandler) HandleChainEnd(_ context.Context, outputs map[string]any) {
 	fmt.Println("Exiting chain with outputs:", formatChainValues(outputs))
 }
 
+func (l LogHandler) HandleChainError(_ context.Context, err error) {
+	fmt.Println("Exiting chain with error:", err)
+}
+
 func (l LogHandler) HandleToolStart(_ context.Context, input string) {
 	fmt.Println("Entering tool with input:", removeNewLines(input))
 }
@@ -45,6 +53,10 @@ func (l LogHandler) HandleToolEnd(_ context.Context, output string) {
 	fmt.Println("Exiting tool with output:", removeNewLines(output))
 }
 
+func (l LogHandler) HandleToolError(_ context.Context, err error) {
+	fmt.Println("Exiting tool with error:", err)
+}
+
 func (l LogHandler) HandleAgentAction(_ context.Context, action schema.AgentAction) {
 	fmt.Println("Agent selected action:", formatAgentAction(action))
 }
diff --git a/callbacks/log_stream.go b/callbacks/log_stream.go
new file mode 100644
index 00000000..f7828cd0
--- /dev/null
+++ b/callbacks/log_stream.go
@@ -0,0 +1,18 @@
+//nolint:forbidigo
+package callbacks
+
+import (
+	"context"
+	"fmt"
+)
+
+// StreamLogHandler is a callback handler that prints to the standard output streaming.
+type StreamLogHandler struct {
+	SimpleHandler
+}
+
+var _ Handler = StreamLogHandler{}
+
+func (StreamLogHandler) HandleStreamingFunc(_ context.Context, chunk []byte) {
+	fmt.Println(string(chunk))
+}
diff --git a/callbacks/simple.go b/callbacks/simple.go
index 0092cc40..d3312af3 100644
--- a/callbacks/simple.go
+++ b/callbacks/simple.go
@@ -15,10 +15,13 @@ var _ Handler = SimpleHandler{}
 func (SimpleHandler) HandleText(context.Context, string)                            {}
 func (SimpleHandler) HandleLLMStart(context.Context, []string)                      {}
 func (SimpleHandler) HandleLLMEnd(context.Context, llms.LLMResult)                  {}
+func (SimpleHandler) HandleLLMError(context.Context, error)                         {}
 func (SimpleHandler) HandleChainStart(context.Context, map[string]any)              {}
 func (SimpleHandler) HandleChainEnd(context.Context, map[string]any)                {}
+func (SimpleHandler) HandleChainError(context.Context, error)                       {}
 func (SimpleHandler) HandleToolStart(context.Context, string)                       {}
 func (SimpleHandler) HandleToolEnd(context.Context, string)                         {}
+func (SimpleHandler) HandleToolError(context.Context, error)                        {}
 func (SimpleHandler) HandleAgentAction(context.Context, schema.AgentAction)         {}
 func (SimpleHandler) HandleRetrieverStart(context.Context, string)                  {}
 func (SimpleHandler) HandleRetrieverEnd(context.Context, string, []schema.Document) {}
diff --git a/chains/chains.go b/chains/chains.go
index 034493e6..de7603ce 100644
--- a/chains/chains.go
+++ b/chains/chains.go
@@ -47,15 +47,11 @@ func Call(ctx context.Context, c Chain, inputValues map[string]any, options ...C
 		callbacksHandler.HandleChainStart(ctx, inputValues)
 	}
 
-	if err := validateInputs(c, fullValues); err != nil {
-		return nil, err
-	}
-
-	outputValues, err := c.Call(ctx, fullValues, options...)
+	outputValues, err := callChain(ctx, c, fullValues, options...)
 	if err != nil {
-		return outputValues, err
-	}
-	if err := validateOutputs(c, outputValues); err != nil {
+		if callbacksHandler != nil {
+			callbacksHandler.HandleChainError(ctx, err)
+		}
 		return outputValues, err
 	}
 
@@ -63,10 +59,30 @@ func Call(ctx context.Context, c Chain, inputValues map[string]any, options ...C
 		callbacksHandler.HandleChainEnd(ctx, outputValues)
 	}
 
-	err = c.GetMemory().SaveContext(ctx, inputValues, outputValues)
+	if err = c.GetMemory().SaveContext(ctx, inputValues, outputValues); err != nil {
+		return outputValues, err
+	}
+
+	return outputValues, nil
+}
+
+func callChain(
+	ctx context.Context,
+	c Chain,
+	fullValues map[string]any,
+	options ...ChainCallOption,
+) (map[string]any, error) {
+	if err := validateInputs(c, fullValues); err != nil {
+		return nil, err
+	}
+
+	outputValues, err := c.Call(ctx, fullValues, options...)
 	if err != nil {
 		return outputValues, err
 	}
+	if err := validateOutputs(c, outputValues); err != nil {
+		return outputValues, err
+	}
 
 	return outputValues, nil
 }
diff --git a/chains/llm.go b/chains/llm.go
index b70c9051..74efae9c 100644
--- a/chains/llm.go
+++ b/chains/llm.go
@@ -29,14 +29,18 @@ var (
 )
 
 // NewLLMChain creates a new LLMChain with an LLM and a prompt.
-func NewLLMChain(llm llms.LanguageModel, prompt prompts.FormatPrompter) *LLMChain {
+func NewLLMChain(llm llms.LanguageModel, prompt prompts.FormatPrompter, opts ...ChainCallOption) *LLMChain {
+	opt := &chainCallOption{}
+	for _, o := range opts {
+		o(opt)
+	}
 	chain := &LLMChain{
-		Prompt:       prompt,
-		LLM:          llm,
-		OutputParser: outputparser.NewSimple(),
-		Memory:       memory.NewSimple(),
-
-		OutputKey: _llmChainDefaultOutputKey,
+		Prompt:           prompt,
+		LLM:              llm,
+		OutputParser:     outputparser.NewSimple(),
+		Memory:           memory.NewSimple(),
+		OutputKey:        _llmChainDefaultOutputKey,
+		CallbacksHandler: opt.CallbackHandler,
 	}
 
 	return chain
diff --git a/chains/options.go b/chains/options.go
index facb6f1a..8ee71e04 100644
--- a/chains/options.go
+++ b/chains/options.go
@@ -3,6 +3,7 @@ package chains
 import (
 	"context"
 
+	"github.com/tmc/langchaingo/callbacks"
 	"github.com/tmc/langchaingo/llms"
 )
 
@@ -33,6 +34,8 @@ type chainCallOption struct {
 	MaxLength int
 	// RepetitionPenalty is the repetition penalty for sampling in an LLM call.
 	RepetitionPenalty float64
+	// CallbackHandler is the callback handler for Chain
+	CallbackHandler callbacks.Handler
 }
 
 // WithModel is an option for LLM.Call.
@@ -119,11 +122,24 @@ func WithStopWords(stopWords []string) ChainCallOption {
 	}
 }
 
+// WithCallback allows setting a custom Callback Handler.
+func WithCallback(callbackHandler callbacks.Handler) ChainCallOption {
+	return func(opts *chainCallOption) {
+		opts.CallbackHandler = callbackHandler
+	}
+}
+
 func getLLMCallOptions(options ...ChainCallOption) []llms.CallOption {
 	opts := &chainCallOption{}
 	for _, option := range options {
 		option(opts)
 	}
+	if opts.StreamingFunc == nil && opts.CallbackHandler != nil {
+		opts.StreamingFunc = func(ctx context.Context, chunk []byte) error {
+			opts.CallbackHandler.HandleStreamingFunc(ctx, chunk)
+			return nil
+		}
+	}
 
 	chainCallOption := []llms.CallOption{
 		llms.WithModel(opts.Model),
diff --git a/embeddings/embedding.go b/embeddings/embedding.go
index f0f06a8c..184164ab 100644
--- a/embeddings/embedding.go
+++ b/embeddings/embedding.go
@@ -3,6 +3,8 @@ package embeddings
 import (
 	"context"
 	"strings"
+
+	"github.com/tmc/langchaingo/internal/util"
 )
 
 // NewEmbedder creates a new Embedder from the given EmbedderClient, with
@@ -74,18 +76,10 @@ func MaybeRemoveNewLines(texts []string, removeNewLines bool) []string {
 
 // BatchTexts splits strings by the length batchSize.
 func BatchTexts(texts []string, batchSize int) [][]string {
-	batchedTexts := make([][]string, len(texts))
-	for i, text := range texts {
-		runeText := []rune(text)
-
-		for j := 0; j < len(runeText); j += batchSize {
-			if j+batchSize >= len(runeText) {
-				batchedTexts[i] = append(batchedTexts[i], string(runeText[j:]))
-				break
-			}
+	batchedTexts := make([][]string, 0, len(texts)/batchSize+1)
 
-			batchedTexts[i] = append(batchedTexts[i], string(runeText[j:j+batchSize]))
-		}
+	for i := 0; i < len(texts); i += batchSize {
+		batchedTexts = append(batchedTexts, texts[i:util.MinInt([]int{i + batchSize, len(texts)})])
 	}
 
 	return batchedTexts
diff --git a/examples/llm-chain-example/go.mod b/examples/llm-chain-example/go.mod
index b3c23be8..029963bb 100644
--- a/examples/llm-chain-example/go.mod
+++ b/examples/llm-chain-example/go.mod
@@ -4,6 +4,10 @@ go 1.19
 
 require github.com/tmc/langchaingo v0.1.1
 
+replace (
+	github.com/tmc/langchaingo v0.1.1 => ../../
+)
+
 require (
 	github.com/Masterminds/goutils v1.1.1 // indirect
 	github.com/Masterminds/semver/v3 v3.2.0 // indirect
diff --git a/examples/llm-chain-example/llm_chain.go b/examples/llm-chain-example/llm_chain.go
index 3f6caa3c..c00ccf55 100644
--- a/examples/llm-chain-example/llm_chain.go
+++ b/examples/llm-chain-example/llm_chain.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/tmc/langchaingo/callbacks"
 	"github.com/tmc/langchaingo/chains"
 	"github.com/tmc/langchaingo/llms/openai"
 	"github.com/tmc/langchaingo/prompts"
@@ -31,7 +32,9 @@ func run() error {
 
 	// If a chain only needs one input we can use the run function to execute chain.
 	ctx := context.Background()
-	out, err := chains.Run(ctx, llmChain, "socks")
+	out, err := chains.Run(ctx, llmChain, "socks",
+		chains.WithCallback(callbacks.StreamLogHandler{}),
+	)
 	if err != nil {
 		return err
 	}
diff --git a/go.mod b/go.mod
index 894d461c..a7eda373 100644
--- a/go.mod
+++ b/go.mod
@@ -28,6 +28,7 @@ require (
 	github.com/cockroachdb/redact v1.1.3 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/dlclark/regexp2 v1.8.1 // indirect
+	github.com/dustin/go-humanize v1.0.1 // indirect
 	github.com/getsentry/sentry-go v0.12.0 // indirect
 	github.com/go-openapi/analysis v0.21.2 // indirect
 	github.com/go-openapi/errors v0.20.3 // indirect
@@ -44,6 +45,7 @@ require (
 	github.com/google/s2a-go v0.1.4 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
 	github.com/googleapis/gax-go/v2 v2.11.0 // indirect
+	github.com/goph/emperror v0.17.2 // indirect
 	github.com/gorilla/css v1.0.0 // indirect
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.2 // indirect
@@ -52,6 +54,7 @@ require (
 	github.com/jackc/pgpassfile v1.0.0 // indirect
 	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
 	github.com/josharian/intern v1.0.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/kennygrant/sanitize v1.2.4 // indirect
 	github.com/kr/pretty v0.3.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
@@ -60,17 +63,22 @@ require (
 	github.com/mitchellh/copystructure v1.0.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/mitchellh/reflectwalk v1.0.0 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/oklog/ulid v1.3.1 // indirect
+	github.com/pelletier/go-toml/v2 v2.0.9 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rogpeppe/go-internal v1.11.0 // indirect
 	github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
 	github.com/shopspring/decimal v1.2.0 // indirect
+	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/spf13/cast v1.3.1 // indirect
 	github.com/temoto/robotstxt v1.1.2 // indirect
 	github.com/tidwall/gjson v1.14.4 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.0 // indirect
+	github.com/yargevad/filepathx v1.0.0 // indirect
 	gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 // indirect
 	gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82 // indirect
 	gitlab.com/golang-commonmark/mdurl v0.0.0-20191124015652-932350d1cb84 // indirect
@@ -106,6 +114,7 @@ require (
 	github.com/metaphorsystems/metaphor-go v0.0.0-20230816231421-43794c04824e
 	github.com/microcosm-cc/bluemonday v1.0.24
 	github.com/milvus-io/milvus-sdk-go/v2 v2.3.2
+	github.com/nikolalohinski/gonja v1.5.3
 	github.com/pgvector/pgvector-go v0.1.1
 	github.com/pinecone-io/go-pinecone v0.3.0
 	github.com/pkoukk/tiktoken-go v0.1.2
@@ -113,7 +122,7 @@ require (
 	github.com/weaviate/weaviate-go-client/v4 v4.8.1
 	gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
 	go.starlark.net v0.0.0-20230302034142-4b1e35fe2254
-	golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea
+	golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
 	google.golang.org/api v0.126.0
 	google.golang.org/grpc v1.57.1
 	google.golang.org/protobuf v1.31.0
diff --git a/go.sum b/go.sum
index 611dc0f0..c2fe1763 100644
--- a/go.sum
+++ b/go.sum
@@ -63,6 +63,7 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
+github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/amikos-tech/chroma-go v0.0.0-20230901221218-d0087270239e h1:RYla80qSHCeP8WL9Hyjfuk9n/KkDcYwzfaYbfc4cqRQ=
 github.com/amikos-tech/chroma-go v0.0.0-20230901221218-d0087270239e/go.mod h1:bPy9xmWK59Ix/nteEqIvPfAI0L07di+uZMb+RiYbles=
@@ -83,7 +84,12 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W
 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
+github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
+github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
+github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -120,6 +126,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn
 github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
 github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -134,11 +142,13 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
+github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
 github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
 github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
@@ -212,6 +222,7 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
 github.com/gocolly/colly v1.2.0 h1:qRz9YAn8FIH0qzgNUw+HT9UN7wm1oF9OBAilwEWpyrI=
 github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
+github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -292,6 +303,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
 github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
+github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
+github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
@@ -332,10 +345,13 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
+github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
 github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
 github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
 github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
@@ -375,11 +391,13 @@ github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsI
 github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@@ -387,6 +405,7 @@ github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpe
 github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
 github.com/metaphorsystems/metaphor-go v0.0.0-20230816231421-43794c04824e h1:4N462rhrxy7KezYYyL3RjJPWlhXiSkfFes0YsMqicd0=
 github.com/metaphorsystems/metaphor-go v0.0.0-20230816231421-43794c04824e/go.mod h1:mDz8kHE7x6Ja95drCQ2T1vLyPRc/t69Cf3wau91E3QU=
+github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
 github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
 github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw=
 github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8=
@@ -405,9 +424,12 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
 github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
 github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
@@ -415,14 +437,20 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c=
+github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4=
 github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
+github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
+github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
 github.com/pgvector/pgvector-go v0.1.1 h1:kqJigGctFnlWvskUiYIvJRNwUtQl/aMSUZVs0YWQe+g=
 github.com/pgvector/pgvector-go v0.1.1/go.mod h1:wLJgD/ODkdtd2LJK4l6evHXTuG+8PxymYAVomKHOWac=
 github.com/pinecone-io/go-pinecone v0.3.0 h1:+t0CiYaaA+JN6YM9QRNlvfLEr2kkGzcVEj/xNmSAON4=
@@ -447,6 +475,7 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
 github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
 github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
 github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ=
 github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -459,9 +488,12 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
 github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -521,6 +553,7 @@ github.com/weaviate/weaviate v1.19.13 h1:Z6wyqVxlPEd7Y46Qvcbo/PRj0D0jT34CtINyrEf
 github.com/weaviate/weaviate v1.19.13/go.mod h1:UdjeUh9IfsYWo6/XHmUoLVYBjhrmQ+bDvMbPIcDAh3Y=
 github.com/weaviate/weaviate-go-client/v4 v4.8.1 h1:oYU+tS9cRyjB0OLV55oN7pnu+EGfa+yIndo3SVlpWJs=
 github.com/weaviate/weaviate-go-client/v4 v4.8.1/go.mod h1:5vfV3ZVIpG48S18vFi85RR+BFq/YvwN9RCUYoU3oVL0=
+github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
 github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
@@ -531,6 +564,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
+github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc=
+github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
@@ -599,8 +634,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
-golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
+golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
+golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -752,6 +787,7 @@ golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuX
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -941,6 +977,7 @@ gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/R
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/internal/util/util.go b/internal/util/util.go
index 39c760ac..54013d2f 100644
--- a/internal/util/util.go
+++ b/internal/util/util.go
@@ -39,3 +39,20 @@ func ListKeys[T any](m map[string]T) []string {
 	}
 	return keys
 }
+
+// MinInt returns the minimum value in nums.
+// If nums is empty, it returns 0.
+func MinInt(nums []int) int {
+	var min int
+	for idx := 0; idx < len(nums); idx++ {
+		item := nums[idx]
+		if idx == 0 {
+			min = item
+			continue
+		}
+		if item < min {
+			min = item
+		}
+	}
+	return min
+}
diff --git a/llms/anthropic/anthropicllm.go b/llms/anthropic/anthropicllm.go
index 82a8da04..0cd4ea9f 100644
--- a/llms/anthropic/anthropicllm.go
+++ b/llms/anthropic/anthropicllm.go
@@ -86,6 +86,9 @@ func (o *LLM) Generate(ctx context.Context, prompts []string, options ...llms.Ca
 			StreamingFunc: opts.StreamingFunc,
 		})
 		if err != nil {
+			if o.CallbacksHandler != nil {
+				o.CallbacksHandler.HandleLLMError(ctx, err)
+			}
 			return nil, err
 		}
 		generations = append(generations, &llms.Generation{
diff --git a/llms/cohere/coherellm.go b/llms/cohere/coherellm.go
index b1b45fd5..e502dc54 100644
--- a/llms/cohere/coherellm.go
+++ b/llms/cohere/coherellm.go
@@ -56,6 +56,9 @@ func (o *LLM) Generate(ctx context.Context, prompts []string, options ...llms.Ca
 			Prompt: prompt,
 		})
 		if err != nil {
+			if o.CallbacksHandler != nil {
+				o.CallbacksHandler.HandleLLMError(ctx, err)
+			}
 			return nil, err
 		}
 
diff --git a/llms/ernie/erniellm.go b/llms/ernie/erniellm.go
index 9d8d2ae6..1e7a3cd7 100644
--- a/llms/ernie/erniellm.go
+++ b/llms/ernie/erniellm.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/tmc/langchaingo/callbacks"
 	"github.com/tmc/langchaingo/llms"
 	"github.com/tmc/langchaingo/llms/ernie/internal/ernieclient"
 	"github.com/tmc/langchaingo/schema"
@@ -17,8 +18,9 @@ var (
 )
 
 type LLM struct {
-	client *ernieclient.Client
-	model  ModelName
+	client           *ernieclient.Client
+	model            ModelName
+	CallbacksHandler callbacks.Handler
 }
 
 var (
@@ -40,8 +42,9 @@ func New(opts ...Option) (*LLM, error) {
 	c, err := newClient(options)
 
 	return &LLM{
-		client: c,
-		model:  options.modelName,
+		client:           c,
+		model:            options.modelName,
+		CallbacksHandler: options.callbacksHandler,
 	}, err
 }
 
@@ -61,22 +64,22 @@ doc: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/flfmc9do2`, ernieclient.ErrNot
 }
 
 // GeneratePrompt implements llms.LanguageModel.
-func (l *LLM) GeneratePrompt(ctx context.Context, promptValues []schema.PromptValue,
+func (o *LLM) GeneratePrompt(ctx context.Context, promptValues []schema.PromptValue,
 	options ...llms.CallOption,
 ) (llms.LLMResult, error) {
-	return llms.GeneratePrompt(ctx, l, promptValues, options...)
+	return llms.GeneratePrompt(ctx, o, promptValues, options...)
 }
 
 // GetNumTokens implements llms.LanguageModel.
-func (l *LLM) GetNumTokens(_ string) int {
+func (o *LLM) GetNumTokens(_ string) int {
 	// todo: not provided yet
 	// see: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu
 	return -1
 }
 
 // Call implements llms.LLM.
-func (l *LLM) Call(ctx context.Context, prompt string, options ...llms.CallOption) (string, error) {
-	r, err := l.Generate(ctx, []string{prompt}, options...)
+func (o *LLM) Call(ctx context.Context, prompt string, options ...llms.CallOption) (string, error) {
+	r, err := o.Generate(ctx, []string{prompt}, options...)
 	if err != nil {
 		return "", err
 	}
@@ -89,7 +92,11 @@ func (l *LLM) Call(ctx context.Context, prompt string, options ...llms.CallOptio
 }
 
 // Generate implements llms.LLM.
-func (l *LLM) Generate(ctx context.Context, prompts []string, options ...llms.CallOption) ([]*llms.Generation, error) {
+func (o *LLM) Generate(ctx context.Context, prompts []string, options ...llms.CallOption) ([]*llms.Generation, error) {
+	if o.CallbacksHandler != nil {
+		o.CallbacksHandler.HandleLLMStart(ctx, prompts)
+	}
+
 	opts := llms.CallOptions{}
 	for _, opt := range options {
 		opt(&opts)
@@ -97,7 +104,7 @@ func (l *LLM) Generate(ctx context.Context, prompts []string, options ...llms.Ca
 
 	generations := make([]*llms.Generation, 0, len(prompts))
 	for _, prompt := range prompts {
-		result, err := l.client.CreateCompletion(ctx, l.getModelPath(opts), &ernieclient.CompletionRequest{
+		result, err := o.client.CreateCompletion(ctx, o.getModelPath(opts), &ernieclient.CompletionRequest{
 			Messages:      []ernieclient.Message{{Role: "user", Content: prompt}},
 			Temperature:   opts.Temperature,
 			TopP:          opts.TopP,
@@ -106,11 +113,18 @@ func (l *LLM) Generate(ctx context.Context, prompts []string, options ...llms.Ca
 			Stream:        opts.StreamingFunc != nil,
 		})
 		if err != nil {
+			if o.CallbacksHandler != nil {
+				o.CallbacksHandler.HandleLLMError(ctx, err)
+			}
 			return nil, err
 		}
 		if result.ErrorCode > 0 {
-			return nil, fmt.Errorf("%w, error_code:%v, erro_msg:%v, id:%v",
+			err = fmt.Errorf("%w, error_code:%v, erro_msg:%v, id:%v",
 				ErrCodeResponse, result.ErrorCode, result.ErrorMsg, result.ID)
+			if o.CallbacksHandler != nil {
+				o.CallbacksHandler.HandleLLMError(ctx, err)
+			}
+			return nil, err
 		}
 
 		generations = append(generations, &llms.Generation{
@@ -125,8 +139,8 @@ func (l *LLM) Generate(ctx context.Context, prompts []string, options ...llms.Ca
 // 1. texts counts less than 16
 // 2. text runes counts less than 384
 // doc: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/alj562vvu
-func (l *LLM) CreateEmbedding(ctx context.Context, texts []string) ([][]float32, error) {
-	resp, e := l.client.CreateEmbedding(ctx, texts)
+func (o *LLM) CreateEmbedding(ctx context.Context, texts []string) ([][]float32, error) {
+	resp, e := o.client.CreateEmbedding(ctx, texts)
 	if e != nil {
 		return nil, e
 	}
@@ -144,8 +158,8 @@ func (l *LLM) CreateEmbedding(ctx context.Context, texts []string) ([][]float32,
 	return emb, nil
 }
 
-func (l *LLM) getModelPath(opts llms.CallOptions) ernieclient.ModelPath {
-	model := l.model
+func (o *LLM) getModelPath(opts llms.CallOptions) ernieclient.ModelPath {
+	model := o.model
 
 	if model == "" {
 		model = ModelName(opts.Model)
diff --git a/llms/ernie/erniellm_option.go b/llms/ernie/erniellm_option.go
index 148e56f3..f4401a1e 100644
--- a/llms/ernie/erniellm_option.go
+++ b/llms/ernie/erniellm_option.go
@@ -1,5 +1,7 @@
 package ernie
 
+import "github.com/tmc/langchaingo/callbacks"
+
 const (
 	ernieAPIKey    = "ERNIE_API_KEY"    //nolint:gosec
 	ernieSecretKey = "ERNIE_SECRET_KEY" //nolint:gosec
@@ -19,10 +21,11 @@ const (
 )
 
 type options struct {
-	apiKey      string
-	secretKey   string
-	accessToken string
-	modelName   ModelName
+	apiKey           string
+	secretKey        string
+	accessToken      string
+	modelName        ModelName
+	callbacksHandler callbacks.Handler
 }
 
 type Option func(*options)
@@ -56,3 +59,10 @@ func WithModelName(modelName ModelName) Option {
 		opts.modelName = modelName
 	}
 }
+
+// WithCallbackHandler passes the callback Handler to the client.
+func WithCallbackHandler(callbacksHandler callbacks.Handler) Option {
+	return func(opts *options) {
+		opts.callbacksHandler = callbacksHandler
+	}
+}
diff --git a/llms/huggingface/huggingfacellm.go b/llms/huggingface/huggingfacellm.go
index d8b2b2e2..1da703a1 100644
--- a/llms/huggingface/huggingfacellm.go
+++ b/llms/huggingface/huggingfacellm.go
@@ -60,6 +60,9 @@ func (o *LLM) Generate(ctx context.Context, prompts []string, options ...llms.Ca
 		Seed:              opts.Seed,
 	})
 	if err != nil {
+		if o.CallbacksHandler != nil {
+			o.CallbacksHandler.HandleLLMError(ctx, err)
+		}
 		return nil, err
 	}
 
diff --git a/llms/llms.go b/llms/llms.go
index 9e18cab7..df1e35a7 100644
--- a/llms/llms.go
+++ b/llms/llms.go
@@ -34,6 +34,8 @@ type Generation struct {
 	Message *schema.AIChatMessage `json:"message"`
 	// GenerationInfo is the generation info. This can contain vendor-specific information.
 	GenerationInfo map[string]any `json:"generation_info"`
+	// StopReason is the reason the generation stopped.
+	StopReason string `json:"stop_reason"`
 }
 
 // LLMResult is the class that contains all relevant information for an LLM Result.
diff --git a/llms/local/localllm.go b/llms/local/localllm.go
index 3a70dff6..767daf1c 100644
--- a/llms/local/localllm.go
+++ b/llms/local/localllm.go
@@ -94,6 +94,9 @@ func (o *LLM) Generate(ctx context.Context, prompts []string, options ...llms.Ca
 			Prompt: prompt,
 		})
 		if err != nil {
+			if o.CallbacksHandler != nil {
+				o.CallbacksHandler.HandleLLMError(ctx, err)
+			}
 			return nil, err
 		}
 
diff --git a/llms/ollama/ollamallm.go b/llms/ollama/ollamallm.go
index fe199be3..38d501bc 100644
--- a/llms/ollama/ollamallm.go
+++ b/llms/ollama/ollamallm.go
@@ -110,6 +110,9 @@ func (o *LLM) Generate(ctx context.Context, prompts []string, options ...llms.Ca
 
 		err := o.client.Generate(ctx, req, fn)
 		if err != nil {
+			if o.CallbacksHandler != nil {
+				o.CallbacksHandler.HandleLLMError(ctx, err)
+			}
 			return []*llms.Generation{}, err
 		}
 
diff --git a/llms/openai/openaillm.go b/llms/openai/openaillm.go
index e0d97cb0..f0c66b6c 100644
--- a/llms/openai/openaillm.go
+++ b/llms/openai/openaillm.go
@@ -22,6 +22,9 @@ var (
 // New returns a new OpenAI LLM.
 func New(opts ...Option) (*LLM, error) {
 	opt, c, err := newClient(opts...)
+	if err != nil {
+		return nil, err
+	}
 	return &LLM{
 		client:           c,
 		CallbacksHandler: opt.callbackHandler,
@@ -65,6 +68,9 @@ func (o *LLM) Generate(ctx context.Context, prompts []string, options ...llms.Ca
 			StreamingFunc:    opts.StreamingFunc,
 		})
 		if err != nil {
+			if o.CallbacksHandler != nil {
+				o.CallbacksHandler.HandleLLMError(ctx, err)
+			}
 			return nil, err
 		}
 		generations = append(generations, &llms.Generation{
diff --git a/llms/openai/openaillm_chat.go b/llms/openai/openaillm_chat.go
index 2651a2d8..a772a1cb 100644
--- a/llms/openai/openaillm_chat.go
+++ b/llms/openai/openaillm_chat.go
@@ -32,6 +32,9 @@ var (
 // NewChat returns a new OpenAI chat LLM.
 func NewChat(opts ...Option) (*Chat, error) {
 	opt, c, err := newClient(opts...)
+	if err != nil {
+		return nil, err
+	}
 	return &Chat{
 		client:           c,
 		CallbacksHandler: opt.callbackHandler,
@@ -106,6 +109,7 @@ func (o *Chat) Generate(ctx context.Context, messageSets [][]schema.ChatMessage,
 			Message:        msg,
 			Text:           msg.Content,
 			GenerationInfo: generationInfo,
+			StopReason:     result.Choices[0].FinishReason,
 		})
 	}
 
diff --git a/llms/vertexai/vertexai_palm_llm.go b/llms/vertexai/vertexai_palm_llm.go
index 4ae5dbb3..d5e77d65 100644
--- a/llms/vertexai/vertexai_palm_llm.go
+++ b/llms/vertexai/vertexai_palm_llm.go
@@ -55,6 +55,9 @@ func (o *LLM) Generate(ctx context.Context, prompts []string, options ...llms.Ca
 		StopSequences: opts.StopWords,
 	})
 	if err != nil {
+		if o.CallbacksHandler != nil {
+			o.CallbacksHandler.HandleLLMError(ctx, err)
+		}
 		return nil, err
 	}
 
diff --git a/prompts/few_shot.go b/prompts/few_shot.go
index 0e0ac8d4..67a0a9eb 100644
--- a/prompts/few_shot.go
+++ b/prompts/few_shot.go
@@ -137,7 +137,7 @@ func (p *FewShotPrompt) Format(values map[string]interface{}) (string, error) {
 	}
 
 	template := p.AssemblePieces(exampleStrings)
-	return defaultformatterMapping[p.TemplateFormat](template, resolvedValues)
+	return defaultFormatterMapping[p.TemplateFormat](template, resolvedValues)
 }
 
 // AssemblePieces assembles the pieces of the few-shot prompt.
diff --git a/prompts/templates.go b/prompts/templates.go
index 523154ff..02c3f6c2 100644
--- a/prompts/templates.go
+++ b/prompts/templates.go
@@ -7,6 +7,7 @@ import (
 	"text/template"
 
 	"github.com/Masterminds/sprig/v3"
+	"github.com/nikolalohinski/gonja"
 	"golang.org/x/exp/maps"
 )
 
@@ -20,14 +21,17 @@ type TemplateFormat string
 const (
 	// TemplateFormatGoTemplate is the format for go-template.
 	TemplateFormatGoTemplate TemplateFormat = "go-template"
+	// TemplateFormatJinjia2 is the format for jinjia2.
+	TemplateFormatJinjia2 TemplateFormat = "jinjia2"
 )
 
 // 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{ //nolint:gochecknoglobals
+var defaultFormatterMapping = map[TemplateFormat]interpolator{ //nolint:gochecknoglobals
 	TemplateFormatGoTemplate: interpolateGoTemplate,
+	TemplateFormatJinjia2:    interpolateJinjia2,
 }
 
 // interpolateGoTemplate interpolates the given template with the given values by using
@@ -48,18 +52,32 @@ func interpolateGoTemplate(tmpl string, values map[string]any) (string, error) {
 	return sb.String(), nil
 }
 
+// interpolateJinjia2 interpolates the given template with the given values by using
+// jinjia2(impl by https://github.com/NikolaLohinski/gonja).
+func interpolateJinjia2(tmpl string, values map[string]any) (string, error) {
+	tpl, err := gonja.FromString(tmpl)
+	if err != nil {
+		return "", err
+	}
+	out, err := tpl.Execute(values)
+	if err != nil {
+		return "", err
+	}
+	return out, nil
+}
+
 func newInvalidTemplateError(gotTemplateFormat TemplateFormat) error {
 	return fmt.Errorf("%w, got: %s, should be one of %s",
 		ErrInvalidTemplateFormat,
 		gotTemplateFormat,
-		maps.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)
 	}
@@ -75,7 +93,7 @@ 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)
 	}
diff --git a/tools/duckduckgo/ddg.go b/tools/duckduckgo/ddg.go
index 5be3ad55..0cd45437 100644
--- a/tools/duckduckgo/ddg.go
+++ b/tools/duckduckgo/ddg.go
@@ -52,6 +52,9 @@ func (t Tool) Call(ctx context.Context, input string) (string, error) {
 		if errors.Is(err, internal.ErrNoGoodResult) {
 			return "No good DuckDuckGo Search Results was found", nil
 		}
+		if t.CallbacksHandler != nil {
+			t.CallbacksHandler.HandleToolError(ctx, err)
+		}
 		return "", err
 	}
 
diff --git a/tools/serpapi/serpapi.go b/tools/serpapi/serpapi.go
index 2890c912..ebca2725 100644
--- a/tools/serpapi/serpapi.go
+++ b/tools/serpapi/serpapi.go
@@ -55,6 +55,10 @@ func (t Tool) Call(ctx context.Context, input string) (string, error) {
 			return "No good Google Search Results was found", nil
 		}
 
+		if t.CallbacksHandler != nil {
+			t.CallbacksHandler.HandleToolError(ctx, err)
+		}
+
 		return "", err
 	}
 
diff --git a/tools/wikipedia/wikipedia.go b/tools/wikipedia/wikipedia.go
index f420f603..55b9d965 100644
--- a/tools/wikipedia/wikipedia.go
+++ b/tools/wikipedia/wikipedia.go
@@ -63,6 +63,22 @@ func (t Tool) Call(ctx context.Context, input string) (string, error) {
 		t.CallbacksHandler.HandleToolStart(ctx, input)
 	}
 
+	result, err := t.searchWiKi(ctx, input)
+	if err != nil {
+		if t.CallbacksHandler != nil {
+			t.CallbacksHandler.HandleToolError(ctx, err)
+		}
+		return "", err
+	}
+
+	if t.CallbacksHandler != nil {
+		t.CallbacksHandler.HandleToolEnd(ctx, result)
+	}
+
+	return result, nil
+}
+
+func (t Tool) searchWiKi(ctx context.Context, input string) (string, error) {
 	searchResult, err := search(ctx, t.TopK, input, t.LanguageCode, t.UserAgent)
 	if err != nil {
 		return "", err
@@ -91,9 +107,5 @@ func (t Tool) Call(ctx context.Context, input string) (string, error) {
 		result += page.Extract
 	}
 
-	if t.CallbacksHandler != nil {
-		t.CallbacksHandler.HandleToolEnd(ctx, result)
-	}
-
 	return result, nil
 }
diff --git a/tools/zapier/zapier.go b/tools/zapier/zapier.go
index 24a23968..be96c669 100644
--- a/tools/zapier/zapier.go
+++ b/tools/zapier/zapier.go
@@ -85,6 +85,9 @@ func (t Tool) Call(ctx context.Context, input string) (string, error) {
 
 	result, err := t.client.ExecuteAsString(ctx, t.actionID, input, t.params)
 	if err != nil {
+		if t.CallbacksHandler != nil {
+			t.CallbacksHandler.HandleToolError(ctx, err)
+		}
 		return "", err
 	}
 
diff --git a/vectorstores/chroma/chroma.go b/vectorstores/chroma/chroma.go
index 0618f2b5..6d3bb1e8 100644
--- a/vectorstores/chroma/chroma.go
+++ b/vectorstores/chroma/chroma.go
@@ -144,10 +144,11 @@ func (s Store) SimilaritySearch(_ context.Context, query string, numDocuments in
 	var sDocs []schema.Document
 	for docsI := range qr.Documents {
 		for docI := range qr.Documents[docsI] {
-			if (1.0 - qr.Distances[docsI][docI]) >= scoreThreshold {
+			if score := 1.0 - qr.Distances[docsI][docI]; score >= scoreThreshold {
 				sDocs = append(sDocs, schema.Document{
 					Metadata:    qr.Metadatas[docsI][docI],
 					PageContent: qr.Documents[docsI][docI],
+					Score:       score,
 				})
 			}
 		}
diff --git a/vectorstores/weaviate/weaviate.go b/vectorstores/weaviate/weaviate.go
index 9bde4631..b2e55c94 100644
--- a/vectorstores/weaviate/weaviate.go
+++ b/vectorstores/weaviate/weaviate.go
@@ -170,6 +170,7 @@ func (s Store) SimilaritySearch(
 	return s.parseDocumentsByGraphQLResponse(res)
 }
 
+//nolint:cyclop
 func (s Store) parseDocumentsByGraphQLResponse(res *models.GraphQLResponse) ([]schema.Document, error) {
 	if len(res.Errors) > 0 {
 		messages := make([]string, 0, len(res.Errors))
@@ -197,10 +198,15 @@ func (s Store) parseDocumentsByGraphQLResponse(res *models.GraphQLResponse) ([]s
 		if !ok {
 			return nil, ErrMissingTextKey
 		}
+		var score float64
+		if additional, ok := itemMap["_additional"].(map[string]any); ok {
+			score, _ = additional["certainty"].(float64)
+		}
 		delete(itemMap, s.textKey)
 		doc := schema.Document{
 			PageContent: pageContent,
 			Metadata:    itemMap,
+			Score:       float32(score),
 		}
 		docs = append(docs, doc)
 	}


Test patch:
diff --git a/embeddings/embedding_test.go b/embeddings/embedding_test.go
index cce72ba7..15549852 100644
--- a/embeddings/embedding_test.go
+++ b/embeddings/embedding_test.go
@@ -14,15 +14,45 @@ func TestBatchTexts(t *testing.T) {
 		batchSize int
 		expected  [][]string
 	}{
+		{
+			texts:     []string{},
+			batchSize: 1,
+			expected:  [][]string{},
+		},
 		{
 			texts:     []string{"foo bar zoo"},
 			batchSize: 4,
-			expected:  [][]string{{"foo ", "bar ", "zoo"}},
+			expected:  [][]string{{"foo bar zoo"}},
 		},
 		{
 			texts:     []string{"foo bar zoo", "foo"},
 			batchSize: 7,
-			expected:  [][]string{{"foo bar", " zoo"}, {"foo"}},
+			expected:  [][]string{{"foo bar zoo", "foo"}},
+		},
+		{
+			texts:     []string{"foo", "bar", "zoo"},
+			batchSize: 2,
+			expected:  [][]string{{"foo", "bar"}, {"zoo"}},
+		},
+		{
+			texts:     []string{"foo", "bar", "zoo", "baz", "qux"},
+			batchSize: 2,
+			expected:  [][]string{{"foo", "bar"}, {"zoo", "baz"}, {"qux"}},
+		},
+		{
+			texts:     []string{"foo", "bar", "zoo", "baz"},
+			batchSize: 2,
+			expected:  [][]string{{"foo", "bar"}, {"zoo", "baz"}},
+		},
+		{
+			texts:     []string{"foo", "bar", "zoo", "baz", "qux"},
+			batchSize: 3,
+			expected:  [][]string{{"foo", "bar", "zoo"}, {"baz", "qux"}},
+		},
+		{
+			texts:     []string{"foo", "bar", "zoo", "baz", "qux"},
+			batchSize: 6,
+			expected:  [][]string{{"foo", "bar", "zoo", "baz", "qux"}},
 		},
 	}
 
diff --git a/internal/util/util_test.go b/internal/util/util_test.go
new file mode 100644
index 00000000..e304cf75
--- /dev/null
+++ b/internal/util/util_test.go
@@ -0,0 +1,37 @@
+package util
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMinInt(t *testing.T) {
+	t.Parallel()
+
+	cases := []struct {
+		nums     []int
+		expected int
+	}{
+		{
+			nums:     []int{1, 2, 3, 23, 34},
+			expected: 1,
+		},
+		{
+			nums:     []int{3, 2, 1, 34, 2213},
+			expected: 1,
+		},
+		{
+			nums:     nil,
+			expected: 0,
+		},
+		{
+			nums:     []int{},
+			expected: 0,
+		},
+	}
+
+	for _, tc := range cases {
+		assert.Equal(t, tc.expected, MinInt(tc.nums))
+	}
+}
diff --git a/prompts/templates_test.go b/prompts/templates_test.go
index 35c54f98..12bf0552 100644
--- a/prompts/templates_test.go
+++ b/prompts/templates_test.go
@@ -1,6 +1,7 @@
 package prompts
 
 import (
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -51,11 +52,20 @@ func TestInterpolateGoTemplate(t *testing.T) {
 	for _, tc := range testCases {
 		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
-			t.Parallel()
-
-			actual, err := interpolateGoTemplate(tc.template, tc.templateValues)
-			require.NoError(t, err)
-			assert.Equal(t, tc.expected, actual)
+			t.Run("go/template", func(t *testing.T) {
+				t.Parallel()
+
+				actual, err := interpolateGoTemplate(tc.template, tc.templateValues)
+				require.NoError(t, err)
+				assert.Equal(t, tc.expected, actual)
+			})
+			t.Run("jinjia2", func(t *testing.T) {
+				t.Parallel()
+
+				actual, err := interpolateJinjia2(strings.ReplaceAll(tc.template, "{{ .", "{{ "), tc.templateValues)
+				require.NoError(t, err)
+				assert.Equal(t, tc.expected, actual)
+			})
 		})
 	}
 
@@ -95,7 +105,7 @@ func TestCheckValidTemplate(t *testing.T) {
 		err := CheckValidTemplate("Hello, {test}", "unknown", []string{"test"})
 		require.Error(t, err)
 		require.ErrorIs(t, err, ErrInvalidTemplateFormat)
-		require.EqualError(t, err, "invalid template format, got: unknown, should be one of [go-template]")
+		require.EqualError(t, err, "invalid template format, got: unknown, should be one of [go-template jinjia2]")
 	})
 
 	t.Run("TemplateErrored", func(t *testing.T) {