mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-01 13:42:20 -04:00
* feat: add distributed mode (experimental) Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix data races, mutexes, transactions Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactorings Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fixups Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix events and tool stream in agent chat Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * use ginkgo Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(cron): compute correctly time boundaries avoiding re-triggering Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * enhancements, refactorings Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * do not flood of healthy checks Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * do not list obvious backends as text backends Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * tests fixups Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactoring and consolidation Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Drop redundant healthcheck Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * enhancements, refactorings Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
108 lines
2.8 KiB
Go
108 lines
2.8 KiB
Go
package templates
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"text/template"
|
|
|
|
"github.com/mudler/LocalAI/pkg/utils"
|
|
|
|
"github.com/Masterminds/sprig/v3"
|
|
)
|
|
|
|
// Keep this in sync with config.TemplateConfig. Is there a more idiomatic way to accomplish this in go?
|
|
// Technically, order doesn't _really_ matter, but the count must stay in sync, see tests/integration/reflect_test.go
|
|
type TemplateType int
|
|
|
|
type templateCache struct {
|
|
mu sync.Mutex
|
|
templatesPath string
|
|
templates map[TemplateType]map[string]*template.Template
|
|
}
|
|
|
|
func newTemplateCache(templatesPath string) *templateCache {
|
|
tc := &templateCache{
|
|
templatesPath: templatesPath,
|
|
templates: make(map[TemplateType]map[string]*template.Template),
|
|
}
|
|
return tc
|
|
}
|
|
|
|
func (tc *templateCache) initializeTemplateMapKey(tt TemplateType) {
|
|
if _, ok := tc.templates[tt]; !ok {
|
|
tc.templates[tt] = make(map[string]*template.Template)
|
|
}
|
|
}
|
|
|
|
func (tc *templateCache) existsInModelPath(s string) bool {
|
|
return utils.ExistsInPath(tc.templatesPath, s)
|
|
}
|
|
func (tc *templateCache) loadTemplateIfExists(templateType TemplateType, templateName string) error {
|
|
|
|
// Check if the template was already loaded
|
|
if _, ok := tc.templates[templateType][templateName]; ok {
|
|
return nil
|
|
}
|
|
|
|
// Check if the model path exists
|
|
// skip any error here - we run anyway if a template does not exist
|
|
modelTemplateFile := fmt.Sprintf("%s.tmpl", templateName)
|
|
|
|
dat := ""
|
|
file := filepath.Join(tc.templatesPath, modelTemplateFile)
|
|
|
|
// Security check
|
|
if err := utils.VerifyPath(modelTemplateFile, tc.templatesPath); err != nil {
|
|
return fmt.Errorf("template file outside path: %s", file)
|
|
}
|
|
|
|
// can either be a file in the system or a string with the template
|
|
if tc.existsInModelPath(modelTemplateFile) {
|
|
d, err := os.ReadFile(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dat = string(d)
|
|
} else {
|
|
dat = templateName
|
|
}
|
|
|
|
// Parse the template
|
|
tmpl, err := template.New("prompt").Funcs(sprig.FuncMap()).Parse(dat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tc.templates[templateType][templateName] = tmpl
|
|
|
|
return nil
|
|
}
|
|
|
|
func (tc *templateCache) evaluateTemplate(templateType TemplateType, templateNameOrContent string, in any) (string, error) {
|
|
tc.mu.Lock()
|
|
defer tc.mu.Unlock()
|
|
|
|
tc.initializeTemplateMapKey(templateType)
|
|
m, ok := tc.templates[templateType][templateNameOrContent]
|
|
if !ok {
|
|
// return "", fmt.Errorf("template not loaded: %s", templateName)
|
|
loadErr := tc.loadTemplateIfExists(templateType, templateNameOrContent)
|
|
if loadErr != nil {
|
|
return "", loadErr
|
|
}
|
|
m = tc.templates[templateType][templateNameOrContent] // ok is not important since we check m on the next line, and wealready checked
|
|
}
|
|
if m == nil {
|
|
return "", fmt.Errorf("failed loading a template for %s", templateNameOrContent)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := m.Execute(&buf, in); err != nil {
|
|
return "", err
|
|
}
|
|
return buf.String(), nil
|
|
}
|