Add long term memory

This commit is contained in:
mudler
2024-12-17 19:53:07 +01:00
parent d053dc249c
commit 2561f2f175
6 changed files with 104 additions and 47 deletions

View File

@@ -68,6 +68,7 @@ func (a *Agent) decision(
Tools: tools,
ToolChoice: toolchoice,
}
resp, err := a.client.CreateChatCompletion(ctx, decision)
if err != nil {
return nil, err
@@ -96,6 +97,14 @@ func (m Messages) ToOpenAI() []openai.ChatCompletionMessage {
return []openai.ChatCompletionMessage(m)
}
func (m Messages) String() string {
s := ""
for _, cc := range m {
s += cc.Role + ": " + cc.Content + "\n"
}
return s
}
func (m Messages) Exist(content string) bool {
for _, cc := range m {
if cc.Content == content {

View File

@@ -151,21 +151,59 @@ func (a *Agent) SetConversation(conv []openai.ChatCompletionMessage) {
a.currentConversation = conv
}
func (a *Agent) askLLM(ctx context.Context, conversation []openai.ChatCompletionMessage) (openai.ChatCompletionMessage, error) {
resp, err := a.client.CreateChatCompletion(ctx,
openai.ChatCompletionRequest{
Model: a.options.LLMAPI.Model,
Messages: conversation,
},
)
if err != nil {
return openai.ChatCompletionMessage{}, err
}
if len(resp.Choices) != 1 {
return openai.ChatCompletionMessage{}, fmt.Errorf("no enough choices: %w", err)
}
return resp.Choices[0].Message, nil
}
func (a *Agent) ResetConversation() {
a.Lock()
defer a.Unlock()
xlog.Info("Resetting conversation", "agent", a.Character.Name)
// store into memory the conversation before pruning it
// TODO: Shall we summarize the conversation into a bullet list of highlights
// using the LLM instead?
if a.options.enableKB {
for _, message := range a.currentConversation {
if message.Role == "user" {
if err := a.options.ragdb.Store(message.Content); err != nil {
xlog.Error("Error storing into memory", "error", err)
if a.options.enableLongTermMemory {
xlog.Info("Saving conversation", "agent", a.Character.Name, "conversation size", len(a.currentConversation))
if a.options.enableSummaryMemory {
msg, err := a.askLLM(a.context.Context, []openai.ChatCompletionMessage{{
Role: "user",
Content: "Summarize the conversation below, keep the highlights as a bullet list:\n" + Messages(a.currentConversation).String(),
}})
if err != nil {
xlog.Error("Error summarizing conversation", "error", err)
}
if err := a.options.ragdb.Store(msg.Content); err != nil {
xlog.Error("Error storing into memory", "error", err)
}
} else {
for _, message := range a.currentConversation {
if message.Role == "user" {
if err := a.options.ragdb.Store(message.Content); err != nil {
xlog.Error("Error storing into memory", "error", err)
}
}
}
}
}
a.currentConversation = []openai.ChatCompletionMessage{}
@@ -612,34 +650,15 @@ func (a *Agent) consumeJob(job *Job, role string) {
return
}
resp, err := a.client.CreateChatCompletion(ctx,
openai.ChatCompletionRequest{
Model: a.options.LLMAPI.Model,
// Force the AI to response without using any tool
// Why: some models might be silly enough to attempt to call tools even if evaluated
// that a reply was not necessary anymore
Messages: append(a.currentConversation, openai.ChatCompletionMessage{
Role: "system",
Content: "The assistant needs to reply without using any tool.",
// + replyResponse.Message,
},
),
//Messages: a.currentConversation,
},
)
msg, err := a.askLLM(ctx, append(a.currentConversation, openai.ChatCompletionMessage{
Role: "system",
Content: "The assistant needs to reply without using any tool.",
}))
if err != nil {
job.Result.Finish(err)
return
}
if len(resp.Choices) != 1 {
job.Result.Finish(fmt.Errorf("no enough choices: %w", err))
return
}
// display OpenAI's response to the original question utilizing our function
msg := resp.Choices[0].Message
// If we didn't got any message, we can use the response from the action
if chosenAction.Definition().Name.Is(action.ReplyActionName) && msg.Content == "" ||
strings.Contains(msg.Content, "<tool_call>") {
@@ -661,9 +680,6 @@ func (a *Agent) periodicallyRun(timer *time.Timer) {
// Remember always to reset the timer - if we don't the agent will stop..
defer timer.Reset(a.options.periodicRuns)
if !a.options.standaloneJob {
return
}
a.StopAction()
xlog.Debug("Agent is running periodically", "agent", a.Character.Name)
@@ -675,18 +691,24 @@ func (a *Agent) periodicallyRun(timer *time.Timer) {
xlog.Info("START -- Periodically run is starting")
if len(a.CurrentConversation()) != 0 {
// Here the LLM could decide to store some part of the conversation too in the memory
evaluateMemory := NewJob(
WithText(
`Evaluate the current conversation and decide if we need to store some relevant informations from it`,
),
WithReasoningCallback(a.options.reasoningCallback),
WithResultCallback(a.options.resultCallback),
)
a.consumeJob(evaluateMemory, SystemRole)
// if len(a.CurrentConversation()) != 0 {
// // Here the LLM could decide to store some part of the conversation too in the memory
// evaluateMemory := NewJob(
// WithText(
// `Evaluate the current conversation and decide if we need to store some relevant informations from it`,
// ),
// WithReasoningCallback(a.options.reasoningCallback),
// WithResultCallback(a.options.resultCallback),
// )
// a.consumeJob(evaluateMemory, SystemRole)
// a.ResetConversation()
// }
if !a.options.standaloneJob {
a.ResetConversation()
return
}
// Here we go in a loop of

View File

@@ -14,12 +14,12 @@ type llmOptions struct {
}
type options struct {
LLMAPI llmOptions
character Character
randomIdentityGuidance string
randomIdentity bool
userActions Actions
enableHUD, standaloneJob, showCharacter, enableKB bool
LLMAPI llmOptions
character Character
randomIdentityGuidance string
randomIdentity bool
userActions Actions
enableHUD, standaloneJob, showCharacter, enableKB, enableSummaryMemory, enableLongTermMemory bool
canStopItself bool
initiateConversations bool
@@ -127,6 +127,16 @@ var EnablePersonality = func(o *options) error {
return nil
}
var EnableSummaryMemory = func(o *options) error {
o.enableSummaryMemory = true
return nil
}
var EnableLongTermMemory = func(o *options) error {
o.enableLongTermMemory = true
return nil
}
func WithRAGDB(db RAGDB) Option {
return func(o *options) error {
o.ragdb = db

View File

@@ -28,4 +28,6 @@ type AgentConfig struct {
KnowledgeBaseResults int `json:"kb_results" form:"kb_results"`
CanStopItself bool `json:"can_stop_itself" form:"can_stop_itself"`
SystemPrompt string `json:"system_prompt" form:"system_prompt"`
LongTermMemory bool `json:"long_term_memory" form:"long_term_memory"`
SummaryLongTermMemory bool `json:"summary_long_term_memory" form:"summary_long_term_memory"`
}

View File

@@ -248,6 +248,15 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
if config.StandaloneJob {
opts = append(opts, EnableStandaloneJob)
}
if config.LongTermMemory {
opts = append(opts, EnableLongTermMemory)
}
if config.SummaryLongTermMemory {
opts = append(opts, EnableSummaryMemory)
}
if config.CanStopItself {
opts = append(opts, CanStopItself)
}

View File

@@ -104,6 +104,11 @@
<label for="random_identity" class="block text-lg font-medium text-gray-400">Random Identity</label>
<input type="checkbox" name="random_identity" id="random_identity" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
<label for="long_term_memory" class="block text-lg font-medium text-gray-400">Long term memory</label>
<input type="checkbox" name="long_term_memory" id="long_term_memory" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
<label for="summary_long_term_memory" class="block text-lg font-medium text-gray-400">Long term memory (summarize!)</label>
<input type="checkbox" name="summary_long_term_memory" id="summary_long_term_memory" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
</div>
<div class="mb-4">
<label for="identity_guidance" class="block text-lg font-medium text-gray-400">Identity Guidance</label>