diff --git a/core/agent/actions.go b/core/agent/actions.go index 8d58794..a004586 100644 --- a/core/agent/actions.go +++ b/core/agent/actions.go @@ -2,7 +2,9 @@ package agent import ( "context" + "encoding/json" "fmt" + "os" "github.com/mudler/LocalAgent/core/action" "github.com/mudler/LocalAgent/pkg/xlog" @@ -87,6 +89,10 @@ func (a *Agent) decision( return nil, err } + if err := a.saveConversation(append(conversation, msg), "decision"); err != nil { + xlog.Error("Error saving conversation", "error", err) + } + return &decisionResult{actionParams: params, actioName: msg.ToolCalls[0].Function.Name, message: msg.Content}, nil } @@ -113,6 +119,26 @@ func (m Messages) Exist(content string) bool { return false } +func (m Messages) Save(path string) error { + content, err := json.MarshalIndent(m, "", " ") + if err != nil { + return err + } + + f, err := os.Create(path) + if err != nil { + return err + } + + defer f.Close() + + if _, err := f.Write(content); err != nil { + return err + } + + return nil +} + func (a *Agent) generateParameters(ctx context.Context, pickTemplate string, act Action, c []openai.ChatCompletionMessage, reasoning string) (*decisionResult, error) { stateHUD, err := renderTemplate(pickTemplate, a.prepareHUD(), a.systemInternalActions(), reasoning) diff --git a/core/agent/agent.go b/core/agent/agent.go index 88d4ffd..fef642c 100644 --- a/core/agent/agent.go +++ b/core/agent/agent.go @@ -177,7 +177,7 @@ func (a *Agent) ResetConversation() { // store into memory the conversation before pruning it // TODO: Shall we summarize the conversation into a bullet list of highlights // using the LLM instead? - a.saveCurrentConversationInMemory() + a.saveCurrentConversation() a.currentConversation = []openai.ChatCompletionMessage{} } @@ -382,7 +382,7 @@ func (a *Agent) consumeJob(job *Job, role string) { Content: reasoning, }) job.Result.Conversation = a.currentConversation - a.saveCurrentConversationInMemory() + a.saveCurrentConversation() job.Result.SetResponse(reasoning) job.Result.Finish(nil) return @@ -532,7 +532,7 @@ func (a *Agent) consumeJob(job *Job, role string) { } a.currentConversation = append(a.currentConversation, msg) - a.saveCurrentConversationInMemory() + a.saveCurrentConversation() job.Result.SetResponse(msg.Content) job.Result.Conversation = a.currentConversation job.Result.Finish(nil) @@ -612,7 +612,7 @@ func (a *Agent) consumeJob(job *Job, role string) { a.currentConversation = append(a.currentConversation, msg) job.Result.Conversation = a.currentConversation job.Result.SetResponse(msg.Content) - a.saveCurrentConversationInMemory() + a.saveCurrentConversation() job.Result.Finish(nil) return } @@ -642,7 +642,7 @@ func (a *Agent) consumeJob(job *Job, role string) { job.Result.SetResponse(msg.Content) xlog.Info("Response from LLM", "response", msg.Content, "agent", a.Character.Name) job.Result.Conversation = a.currentConversation - a.saveCurrentConversationInMemory() + a.saveCurrentConversation() job.Result.Finish(nil) } diff --git a/core/agent/knowledgebase.go b/core/agent/knowledgebase.go index cc6a3bd..29104da 100644 --- a/core/agent/knowledgebase.go +++ b/core/agent/knowledgebase.go @@ -2,6 +2,9 @@ package agent import ( "fmt" + "os" + "path/filepath" + "time" "github.com/mudler/LocalAgent/pkg/xlog" "github.com/sashabaranov/go-openai" @@ -60,9 +63,25 @@ func (a *Agent) knowledgeBaseLookup() { }}, a.currentConversation...) } -func (a *Agent) saveCurrentConversationInMemory() { +func (a *Agent) saveConversation(m Messages, prefix string) error { + if a.options.conversationsPath == "" { + return nil + } + dateTime := time.Now().Format("2006-01-02-15-04-05") + fileName := a.Character.Name + "-" + dateTime + ".json" + if prefix != "" { + fileName = prefix + "-" + fileName + } + os.MkdirAll(a.options.conversationsPath, os.ModePerm) + return m.Save(filepath.Join(a.options.conversationsPath, fileName)) +} + +func (a *Agent) saveCurrentConversation() { + + if err := a.saveConversation(a.currentConversation, ""); err != nil { + xlog.Error("Error saving conversation", "error", err) + } - if !a.options.enableLongTermMemory && !a.options.enableSummaryMemory { xlog.Debug("Long term memory is disabled", "agent", a.Character.Name) return diff --git a/core/agent/options.go b/core/agent/options.go index 2971a97..2b93d9b 100644 --- a/core/agent/options.go +++ b/core/agent/options.go @@ -40,6 +40,8 @@ type options struct { // callbacks reasoningCallback func(ActionCurrentState) bool resultCallback func(ActionState) + + conversationsPath string } func defaultOptions() *options { @@ -97,6 +99,13 @@ func WithTimeout(timeout string) Option { } } +func WithConversationsPath(path string) Option { + return func(o *options) error { + o.conversationsPath = path + return nil + } +} + func EnableKnowledgeBaseWithResults(results int) Option { return func(o *options) error { o.enableKB = true diff --git a/core/state/pool.go b/core/state/pool.go index 7be60fb..2f9212e 100644 --- a/core/state/pool.go +++ b/core/state/pool.go @@ -32,6 +32,7 @@ type AgentPool struct { connectors func(*AgentConfig) []Connector promptBlocks func(*AgentConfig) []PromptBlock timeout string + conversationLogs string } type Status struct { @@ -94,6 +95,7 @@ func NewAgentPool( availableActions: availableActions, promptBlocks: promptBlocks, timeout: timeout, + conversationLogs: filepath.Join(directory, "conversations"), }, nil } @@ -116,6 +118,7 @@ func NewAgentPool( promptBlocks: promptBlocks, availableActions: availableActions, timeout: timeout, + conversationLogs: filepath.Join(directory, "conversations"), }, nil } @@ -272,6 +275,10 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error opts = append(opts, EnableHUD) } + if a.conversationLogs != "" { + opts = append(opts, WithConversationsPath(a.conversationLogs)) + } + if config.StandaloneJob { opts = append(opts, EnableStandaloneJob) } diff --git a/services/actions/search.go b/services/actions/search.go index 3fe963e..b5e7e20 100644 --- a/services/actions/search.go +++ b/services/actions/search.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "strings" "github.com/mudler/LocalAgent/core/action" "github.com/sashabaranov/go-openai/jsonschema" @@ -59,7 +60,16 @@ func (a *SearchAction) Run(ctx context.Context, params action.ActionParams) (act rxStrict := xurls.Strict() urls := rxStrict.FindAllString(res, -1) - return action.ActionResult{Result: res, Metadata: map[string]interface{}{MetadataUrls: urls}}, nil + results := []string{} + for _, u := range urls { + // remove //duckduckgo.com/l/?uddg= from the url + u = strings.ReplaceAll(u, "//duckduckgo.com/l/?uddg=", "") + // remove everything with &rut=.... at the end + u = strings.Split(u, "&rut=")[0] + results = append(results, u) + } + + return action.ActionResult{Result: res, Metadata: map[string]interface{}{MetadataUrls: results}}, nil } func (a *SearchAction) Definition() action.ActionDefinition {