Allow to specify dynamic prompts
This commit is contained in:
@@ -379,7 +379,15 @@ func (a *Agent) consumeJob(job *Job, role string) {
|
||||
//}
|
||||
// Add custom prompts
|
||||
for _, prompt := range a.options.prompts {
|
||||
message := prompt.Render(a)
|
||||
message, err := prompt.Render(a)
|
||||
if err != nil {
|
||||
xlog.Error("Error rendering prompt", "error", err)
|
||||
continue
|
||||
}
|
||||
if message == "" {
|
||||
xlog.Debug("Prompt is empty, skipping", "agent", a.Character.Name)
|
||||
continue
|
||||
}
|
||||
if !Messages(a.currentConversation).Exist(a.options.systemPrompt) {
|
||||
a.currentConversation = append([]openai.ChatCompletionMessage{
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ type JobResult struct {
|
||||
// The result of a job
|
||||
State []ActionState
|
||||
Conversation []openai.ChatCompletionMessage
|
||||
|
||||
Response string
|
||||
Error error
|
||||
ready chan bool
|
||||
|
||||
@@ -42,11 +42,6 @@ type options struct {
|
||||
resultCallback func(ActionState)
|
||||
}
|
||||
|
||||
type PromptBlock interface {
|
||||
Render(a *Agent) string
|
||||
Role() string
|
||||
}
|
||||
|
||||
func defaultOptions() *options {
|
||||
return &options{
|
||||
periodicRuns: 15 * time.Minute,
|
||||
@@ -182,6 +177,22 @@ func WithPrompts(prompts ...PromptBlock) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithDynamicPrompts is a helper function to create dynamic prompts
|
||||
// Dynamic prompts contains golang code which is executed dynamically
|
||||
// // to render a prompt to the LLM
|
||||
// func WithDynamicPrompts(prompts ...map[string]string) Option {
|
||||
// return func(o *options) error {
|
||||
// for _, p := range prompts {
|
||||
// prompt, err := NewDynamicPrompt(p, "")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// o.prompts = append(o.prompts, prompt)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
|
||||
func WithLLMAPIKey(key string) Option {
|
||||
return func(o *options) error {
|
||||
o.LLMAPI.APIKey = key
|
||||
|
||||
101
core/agent/prompt.go
Normal file
101
core/agent/prompt.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mudler/LocalAgent/pkg/xlog"
|
||||
"github.com/traefik/yaegi/interp"
|
||||
"github.com/traefik/yaegi/stdlib"
|
||||
)
|
||||
|
||||
type PromptBlock interface {
|
||||
Render(a *Agent) (string, error)
|
||||
Role() string
|
||||
}
|
||||
|
||||
type DynamicPrompt struct {
|
||||
config map[string]string
|
||||
goPkgPath string
|
||||
i *interp.Interpreter
|
||||
}
|
||||
|
||||
func NewDynamicPrompt(config map[string]string, goPkgPath string) (*DynamicPrompt, error) {
|
||||
a := &DynamicPrompt{
|
||||
config: config,
|
||||
goPkgPath: goPkgPath,
|
||||
}
|
||||
|
||||
if err := a.initializeInterpreter(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := a.callInit(); err != nil {
|
||||
xlog.Error("Error calling custom action init", "error", err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *DynamicPrompt) callInit() error {
|
||||
if a.i == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
v, err := a.i.Eval(fmt.Sprintf("%s.Init", a.config["name"]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
run := v.Interface().(func() error)
|
||||
|
||||
return run()
|
||||
}
|
||||
|
||||
func (a *DynamicPrompt) initializeInterpreter() error {
|
||||
if _, exists := a.config["code"]; exists && a.i == nil {
|
||||
unsafe := strings.ToLower(a.config["unsafe"]) == "true"
|
||||
i := interp.New(interp.Options{
|
||||
GoPath: a.goPkgPath,
|
||||
Unrestricted: unsafe,
|
||||
})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, exists := a.config["name"]; !exists {
|
||||
a.config["name"] = "custom"
|
||||
}
|
||||
|
||||
_, err := i.Eval(fmt.Sprintf("package %s\n%s", a.config["name"], a.config["code"]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.i = i
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *DynamicPrompt) Render(c *Agent) (string, error) {
|
||||
v, err := a.i.Eval(fmt.Sprintf("%s.Render", a.config["name"]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
run := v.Interface().(func() (string, error))
|
||||
|
||||
return run()
|
||||
}
|
||||
|
||||
func (a *DynamicPrompt) Role() string {
|
||||
v, err := a.i.Eval(fmt.Sprintf("%s.Role", a.config["name"]))
|
||||
if err != nil {
|
||||
return "system"
|
||||
}
|
||||
|
||||
run := v.Interface().(func() string)
|
||||
|
||||
return run()
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/mudler/LocalAgent/core/agent"
|
||||
)
|
||||
|
||||
@@ -14,9 +16,22 @@ type ActionsConfig struct {
|
||||
Config string `json:"config"`
|
||||
}
|
||||
|
||||
type PromptBlocksConfig struct {
|
||||
Type string `json:"type"`
|
||||
Config string `json:"config"`
|
||||
}
|
||||
|
||||
func (d PromptBlocksConfig) ToMap() map[string]string {
|
||||
config := map[string]string{}
|
||||
json.Unmarshal([]byte(d.Config), &config)
|
||||
return config
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
Connector []ConnectorConfig `json:"connectors" form:"connectors" `
|
||||
Actions []ActionsConfig `json:"actions" form:"actions"`
|
||||
Connector []ConnectorConfig `json:"connectors" form:"connectors" `
|
||||
Actions []ActionsConfig `json:"actions" form:"actions"`
|
||||
PromptBlocks []PromptBlocksConfig `json:"promptblocks" form:"promptblocks"`
|
||||
|
||||
// This is what needs to be part of ActionsConfig
|
||||
Model string `json:"model" form:"model"`
|
||||
Name string `json:"name" form:"name"`
|
||||
|
||||
@@ -30,6 +30,7 @@ type AgentPool struct {
|
||||
apiURL, model, localRAGAPI, apiKey string
|
||||
availableActions func(*AgentConfig) func(ctx context.Context) []Action
|
||||
connectors func(*AgentConfig) []Connector
|
||||
promptBlocks func(*AgentConfig) []PromptBlock
|
||||
timeout string
|
||||
}
|
||||
|
||||
@@ -68,6 +69,7 @@ func NewAgentPool(
|
||||
LocalRAGAPI string,
|
||||
availableActions func(*AgentConfig) func(ctx context.Context) []agent.Action,
|
||||
connectors func(*AgentConfig) []Connector,
|
||||
promptBlocks func(*AgentConfig) []PromptBlock,
|
||||
timeout string,
|
||||
) (*AgentPool, error) {
|
||||
// if file exists, try to load an existing pool.
|
||||
@@ -160,6 +162,7 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
|
||||
}
|
||||
|
||||
connectors := a.connectors(config)
|
||||
promptBlocks := a.promptBlocks(config)
|
||||
|
||||
actions := a.availableActions(config)(ctx)
|
||||
|
||||
@@ -183,12 +186,19 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
|
||||
"connectors", connectorLog,
|
||||
)
|
||||
|
||||
// dynamicPrompts := []map[string]string{}
|
||||
// for _, p := range config.DynamicPrompts {
|
||||
// dynamicPrompts = append(dynamicPrompts, p.ToMap())
|
||||
// }
|
||||
|
||||
opts := []Option{
|
||||
WithModel(model),
|
||||
WithLLMAPIURL(a.apiURL),
|
||||
WithContext(ctx),
|
||||
WithPeriodicRuns(config.PeriodicRuns),
|
||||
WithPermanentGoal(config.PermanentGoal),
|
||||
WithPrompts(promptBlocks...),
|
||||
// WithDynamicPrompts(dynamicPrompts...),
|
||||
WithCharacter(Character{
|
||||
Name: name,
|
||||
}),
|
||||
@@ -313,6 +323,7 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
|
||||
}
|
||||
}()
|
||||
|
||||
xlog.Info("Starting connectors", "name", name, "config", config)
|
||||
for _, c := range connectors {
|
||||
go c.Start(agent)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user