feat(reminders): add reminder system to perform long-term goals in the background (#176)
* feat(reminders): add self-ability to set reminders Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(reminders): surface reminders result to the user as new conversations Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Fixups * Subscribe all connectors to agents new messages Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Set reminders in the list * fix(telegram): do not always auth Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Small fixups * Improve UX Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
committed by
GitHub
parent
490bf998a4
commit
9a90153dc6
193
core/action/reminder.go
Normal file
193
core/action/reminder.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
const (
|
||||
ReminderActionName = "set_reminder"
|
||||
ListRemindersName = "list_reminders"
|
||||
RemoveReminderName = "remove_reminder"
|
||||
)
|
||||
|
||||
func NewReminder() *ReminderAction {
|
||||
return &ReminderAction{}
|
||||
}
|
||||
|
||||
func NewListReminders() *ListRemindersAction {
|
||||
return &ListRemindersAction{}
|
||||
}
|
||||
|
||||
func NewRemoveReminder() *RemoveReminderAction {
|
||||
return &RemoveReminderAction{}
|
||||
}
|
||||
|
||||
type ReminderAction struct{}
|
||||
type ListRemindersAction struct{}
|
||||
type RemoveReminderAction struct{}
|
||||
|
||||
type RemoveReminderParams struct {
|
||||
Index int `json:"index"`
|
||||
}
|
||||
|
||||
func (a *ReminderAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
result := types.ReminderActionResponse{}
|
||||
err := params.Unmarshal(&result)
|
||||
if err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
|
||||
// Validate the cron expression
|
||||
parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
|
||||
_, err = parser.Parse(result.CronExpr)
|
||||
if err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
|
||||
// Calculate next run time
|
||||
now := time.Now()
|
||||
schedule, _ := parser.Parse(result.CronExpr) // We can ignore the error since we validated above
|
||||
nextRun := schedule.Next(now)
|
||||
|
||||
// Set the reminder details
|
||||
result.LastRun = now
|
||||
result.NextRun = nextRun
|
||||
// IsRecurring is set by the user through the action parameters
|
||||
|
||||
// Store the reminder in the shared state
|
||||
if sharedState.Reminders == nil {
|
||||
sharedState.Reminders = make([]types.ReminderActionResponse, 0)
|
||||
}
|
||||
sharedState.Reminders = append(sharedState.Reminders, result)
|
||||
|
||||
return types.ActionResult{
|
||||
Result: "Reminder set successfully",
|
||||
Metadata: map[string]interface{}{
|
||||
"reminder": result,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ListRemindersAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
if sharedState.Reminders == nil || len(sharedState.Reminders) == 0 {
|
||||
return types.ActionResult{
|
||||
Result: "No reminders set",
|
||||
}, nil
|
||||
}
|
||||
|
||||
var result strings.Builder
|
||||
result.WriteString("Current reminders:\n")
|
||||
for i, reminder := range sharedState.Reminders {
|
||||
status := "one-time"
|
||||
if reminder.IsRecurring {
|
||||
status = "recurring"
|
||||
}
|
||||
result.WriteString(fmt.Sprintf("%d. %s (Next run: %s, Status: %s)\n",
|
||||
i+1,
|
||||
reminder.Message,
|
||||
reminder.NextRun.Format(time.RFC3339),
|
||||
status))
|
||||
}
|
||||
|
||||
return types.ActionResult{
|
||||
Result: result.String(),
|
||||
Metadata: map[string]interface{}{
|
||||
"reminders": sharedState.Reminders,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *RemoveReminderAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
var removeParams RemoveReminderParams
|
||||
err := params.Unmarshal(&removeParams)
|
||||
if err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
|
||||
if sharedState.Reminders == nil || len(sharedState.Reminders) == 0 {
|
||||
return types.ActionResult{
|
||||
Result: "No reminders to remove",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Convert from 1-based index to 0-based
|
||||
index := removeParams.Index - 1
|
||||
if index < 0 || index >= len(sharedState.Reminders) {
|
||||
return types.ActionResult{}, fmt.Errorf("invalid reminder index: %d", removeParams.Index)
|
||||
}
|
||||
|
||||
// Remove the reminder
|
||||
removed := sharedState.Reminders[index]
|
||||
sharedState.Reminders = append(sharedState.Reminders[:index], sharedState.Reminders[index+1:]...)
|
||||
|
||||
return types.ActionResult{
|
||||
Result: fmt.Sprintf("Removed reminder: %s", removed.Message),
|
||||
Metadata: map[string]interface{}{
|
||||
"removed_reminder": removed,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ReminderAction) Plannable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *ListRemindersAction) Plannable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *RemoveReminderAction) Plannable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *ReminderAction) Definition() types.ActionDefinition {
|
||||
return types.ActionDefinition{
|
||||
Name: ReminderActionName,
|
||||
Description: "Set a reminder for the agent to wake up and perform a task based on a cron schedule. Examples: '0 0 * * *' (daily at midnight), '0 */2 * * *' (every 2 hours), '0 0 * * 1' (every Monday at midnight)",
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"message": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The message or task to be reminded about",
|
||||
},
|
||||
"cron_expr": {
|
||||
Type: jsonschema.String,
|
||||
Description: "Cron expression for scheduling (e.g. '0 0 * * *' for daily at midnight). Format: 'second minute hour day month weekday'",
|
||||
},
|
||||
"is_recurring": {
|
||||
Type: jsonschema.Boolean,
|
||||
Description: "Whether this reminder should repeat according to the cron schedule (true) or trigger only once (false)",
|
||||
},
|
||||
},
|
||||
Required: []string{"message", "cron_expr", "is_recurring"},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ListRemindersAction) Definition() types.ActionDefinition {
|
||||
return types.ActionDefinition{
|
||||
Name: ListRemindersName,
|
||||
Description: "List all currently set reminders with their next scheduled run times",
|
||||
Properties: map[string]jsonschema.Definition{},
|
||||
Required: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *RemoveReminderAction) Definition() types.ActionDefinition {
|
||||
return types.ActionDefinition{
|
||||
Name: RemoveReminderName,
|
||||
Description: "Remove a reminder by its index number (use list_reminders to see the index)",
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"index": {
|
||||
Type: jsonschema.Integer,
|
||||
Description: "The index number of the reminder to remove (1-based)",
|
||||
},
|
||||
},
|
||||
Required: []string{"index"},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user