Automatically save all conversations

This commit is contained in:
Ettore Di Giacinto
2025-03-04 22:22:16 +01:00
parent 758a73e8ab
commit d288755444
6 changed files with 79 additions and 8 deletions

View File

@@ -2,7 +2,9 @@ package agent
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"os"
"github.com/mudler/LocalAgent/core/action" "github.com/mudler/LocalAgent/core/action"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
@@ -87,6 +89,10 @@ func (a *Agent) decision(
return nil, err 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 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 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) { 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) stateHUD, err := renderTemplate(pickTemplate, a.prepareHUD(), a.systemInternalActions(), reasoning)

View File

@@ -177,7 +177,7 @@ func (a *Agent) ResetConversation() {
// store into memory the conversation before pruning it // store into memory the conversation before pruning it
// TODO: Shall we summarize the conversation into a bullet list of highlights // TODO: Shall we summarize the conversation into a bullet list of highlights
// using the LLM instead? // using the LLM instead?
a.saveCurrentConversationInMemory() a.saveCurrentConversation()
a.currentConversation = []openai.ChatCompletionMessage{} a.currentConversation = []openai.ChatCompletionMessage{}
} }
@@ -382,7 +382,7 @@ func (a *Agent) consumeJob(job *Job, role string) {
Content: reasoning, Content: reasoning,
}) })
job.Result.Conversation = a.currentConversation job.Result.Conversation = a.currentConversation
a.saveCurrentConversationInMemory() a.saveCurrentConversation()
job.Result.SetResponse(reasoning) job.Result.SetResponse(reasoning)
job.Result.Finish(nil) job.Result.Finish(nil)
return return
@@ -532,7 +532,7 @@ func (a *Agent) consumeJob(job *Job, role string) {
} }
a.currentConversation = append(a.currentConversation, msg) a.currentConversation = append(a.currentConversation, msg)
a.saveCurrentConversationInMemory() a.saveCurrentConversation()
job.Result.SetResponse(msg.Content) job.Result.SetResponse(msg.Content)
job.Result.Conversation = a.currentConversation job.Result.Conversation = a.currentConversation
job.Result.Finish(nil) job.Result.Finish(nil)
@@ -612,7 +612,7 @@ func (a *Agent) consumeJob(job *Job, role string) {
a.currentConversation = append(a.currentConversation, msg) a.currentConversation = append(a.currentConversation, msg)
job.Result.Conversation = a.currentConversation job.Result.Conversation = a.currentConversation
job.Result.SetResponse(msg.Content) job.Result.SetResponse(msg.Content)
a.saveCurrentConversationInMemory() a.saveCurrentConversation()
job.Result.Finish(nil) job.Result.Finish(nil)
return return
} }
@@ -642,7 +642,7 @@ func (a *Agent) consumeJob(job *Job, role string) {
job.Result.SetResponse(msg.Content) job.Result.SetResponse(msg.Content)
xlog.Info("Response from LLM", "response", msg.Content, "agent", a.Character.Name) xlog.Info("Response from LLM", "response", msg.Content, "agent", a.Character.Name)
job.Result.Conversation = a.currentConversation job.Result.Conversation = a.currentConversation
a.saveCurrentConversationInMemory() a.saveCurrentConversation()
job.Result.Finish(nil) job.Result.Finish(nil)
} }

View File

@@ -2,6 +2,9 @@ package agent
import ( import (
"fmt" "fmt"
"os"
"path/filepath"
"time"
"github.com/mudler/LocalAgent/pkg/xlog" "github.com/mudler/LocalAgent/pkg/xlog"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
@@ -60,8 +63,24 @@ func (a *Agent) knowledgeBaseLookup() {
}}, a.currentConversation...) }}, 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 { if !a.options.enableLongTermMemory && !a.options.enableSummaryMemory {
xlog.Debug("Long term memory is disabled", "agent", a.Character.Name) xlog.Debug("Long term memory is disabled", "agent", a.Character.Name)

View File

@@ -40,6 +40,8 @@ type options struct {
// callbacks // callbacks
reasoningCallback func(ActionCurrentState) bool reasoningCallback func(ActionCurrentState) bool
resultCallback func(ActionState) resultCallback func(ActionState)
conversationsPath string
} }
func defaultOptions() *options { 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 { func EnableKnowledgeBaseWithResults(results int) Option {
return func(o *options) error { return func(o *options) error {
o.enableKB = true o.enableKB = true

View File

@@ -32,6 +32,7 @@ type AgentPool struct {
connectors func(*AgentConfig) []Connector connectors func(*AgentConfig) []Connector
promptBlocks func(*AgentConfig) []PromptBlock promptBlocks func(*AgentConfig) []PromptBlock
timeout string timeout string
conversationLogs string
} }
type Status struct { type Status struct {
@@ -94,6 +95,7 @@ func NewAgentPool(
availableActions: availableActions, availableActions: availableActions,
promptBlocks: promptBlocks, promptBlocks: promptBlocks,
timeout: timeout, timeout: timeout,
conversationLogs: filepath.Join(directory, "conversations"),
}, nil }, nil
} }
@@ -116,6 +118,7 @@ func NewAgentPool(
promptBlocks: promptBlocks, promptBlocks: promptBlocks,
availableActions: availableActions, availableActions: availableActions,
timeout: timeout, timeout: timeout,
conversationLogs: filepath.Join(directory, "conversations"),
}, nil }, nil
} }
@@ -272,6 +275,10 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
opts = append(opts, EnableHUD) opts = append(opts, EnableHUD)
} }
if a.conversationLogs != "" {
opts = append(opts, WithConversationsPath(a.conversationLogs))
}
if config.StandaloneJob { if config.StandaloneJob {
opts = append(opts, EnableStandaloneJob) opts = append(opts, EnableStandaloneJob)
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"log/slog" "log/slog"
"strings"
"github.com/mudler/LocalAgent/core/action" "github.com/mudler/LocalAgent/core/action"
"github.com/sashabaranov/go-openai/jsonschema" "github.com/sashabaranov/go-openai/jsonschema"
@@ -59,7 +60,16 @@ func (a *SearchAction) Run(ctx context.Context, params action.ActionParams) (act
rxStrict := xurls.Strict() rxStrict := xurls.Strict()
urls := rxStrict.FindAllString(res, -1) 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 { func (a *SearchAction) Definition() action.ActionDefinition {