8d89a762fb
Cada tool ahora vive en su propio subpackage dentro de tools/ (clock, file, http, knowledgetools, matrix, memorytools, ssh, weather) en lugar de archivos planos en el paquete raíz tools/. Esto mejora la organización, permite imports selectivos y reduce acoplamiento entre tools. El paquete tools/ raíz conserva los tipos base (Def, Param, Result, ToolFunc, Tool, Registry). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
201 lines
6.9 KiB
Go
201 lines
6.9 KiB
Go
package memorytools
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/enmanuel/agents/pkg/memory"
|
|
"github.com/enmanuel/agents/tools"
|
|
)
|
|
|
|
// MemoryStore is the subset of memory.Store needed by memory tools.
|
|
type MemoryStore interface {
|
|
SaveFact(ctx context.Context, fact memory.Fact) error
|
|
RecallFacts(ctx context.Context, agentID, subject string, key *string) ([]memory.Fact, error)
|
|
DeleteFacts(ctx context.Context, agentID, subject string, key *string) error
|
|
}
|
|
|
|
// WindowClearer allows tools to clear the conversation window for a room.
|
|
type WindowClearer interface {
|
|
ClearWindow(roomID string)
|
|
}
|
|
|
|
// RoomContext is a thread-safe holder for the current room ID.
|
|
// Set by the runtime before each event handling; read by memory_clear_context.
|
|
type RoomContext struct {
|
|
mu sync.RWMutex
|
|
roomID string
|
|
}
|
|
|
|
// Set updates the current room ID.
|
|
func (rc *RoomContext) Set(roomID string) {
|
|
rc.mu.Lock()
|
|
rc.roomID = roomID
|
|
rc.mu.Unlock()
|
|
}
|
|
|
|
// Get returns the current room ID.
|
|
func (rc *RoomContext) Get() string {
|
|
rc.mu.RLock()
|
|
defer rc.mu.RUnlock()
|
|
return rc.roomID
|
|
}
|
|
|
|
// NewMemorySave creates a tool that saves a fact to long-term memory.
|
|
func NewMemorySave(agentID string, store MemoryStore) tools.Tool {
|
|
return tools.Tool{
|
|
Def: tools.Def{
|
|
Name: "memory_save",
|
|
Description: "Save a fact to long-term memory. Use this to remember important information about users, topics, or preferences.",
|
|
Parameters: []tools.Param{
|
|
{Name: "subject", Type: "string", Description: "The subject this fact is about (e.g. a username, a topic)", Required: true},
|
|
{Name: "key", Type: "string", Description: "The fact key (e.g. 'favorite_language', 'timezone')", Required: true},
|
|
{Name: "value", Type: "string", Description: "The fact value to store", Required: true},
|
|
},
|
|
},
|
|
Exec: func(ctx context.Context, args map[string]any) tools.Result {
|
|
subject := tools.GetString(args, "subject")
|
|
key := tools.GetString(args, "key")
|
|
value := tools.GetString(args, "value")
|
|
if subject == "" || key == "" || value == "" {
|
|
return tools.Result{Err: fmt.Errorf("memory_save: subject, key, and value are required")}
|
|
}
|
|
err := store.SaveFact(ctx, memory.Fact{
|
|
AgentID: agentID,
|
|
Subject: subject,
|
|
Key: key,
|
|
Value: value,
|
|
})
|
|
if err != nil {
|
|
return tools.Result{Err: fmt.Errorf("memory_save: %w", err)}
|
|
}
|
|
return tools.Result{Output: fmt.Sprintf("saved: %s.%s = %s", subject, key, value)}
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewMemoryRecall creates a tool that retrieves facts from long-term memory.
|
|
func NewMemoryRecall(agentID string, store MemoryStore) tools.Tool {
|
|
return tools.Tool{
|
|
Def: tools.Def{
|
|
Name: "memory_recall",
|
|
Description: "Recall facts from long-term memory about a subject. Omit key to get all facts for the subject.",
|
|
Parameters: []tools.Param{
|
|
{Name: "subject", Type: "string", Description: "The subject to recall facts about", Required: true},
|
|
{Name: "key", Type: "string", Description: "Optional specific fact key to recall", Required: false},
|
|
},
|
|
},
|
|
Exec: func(ctx context.Context, args map[string]any) tools.Result {
|
|
subject := tools.GetString(args, "subject")
|
|
if subject == "" {
|
|
return tools.Result{Err: fmt.Errorf("memory_recall: subject is required")}
|
|
}
|
|
var keyPtr *string
|
|
if k := tools.GetString(args, "key"); k != "" {
|
|
keyPtr = &k
|
|
}
|
|
facts, err := store.RecallFacts(ctx, agentID, subject, keyPtr)
|
|
if err != nil {
|
|
return tools.Result{Err: fmt.Errorf("memory_recall: %w", err)}
|
|
}
|
|
if len(facts) == 0 {
|
|
return tools.Result{Output: fmt.Sprintf("no facts found for subject %q", subject)}
|
|
}
|
|
var sb strings.Builder
|
|
for _, f := range facts {
|
|
fmt.Fprintf(&sb, "%s.%s = %s\n", f.Subject, f.Key, f.Value)
|
|
}
|
|
return tools.Result{Output: sb.String()}
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewMemoryForget creates a tool that deletes facts from long-term memory.
|
|
func NewMemoryForget(agentID string, store MemoryStore) tools.Tool {
|
|
return tools.Tool{
|
|
Def: tools.Def{
|
|
Name: "memory_forget",
|
|
Description: "Delete facts from long-term memory. Omit key to delete all facts for the subject.",
|
|
Parameters: []tools.Param{
|
|
{Name: "subject", Type: "string", Description: "The subject whose facts to delete", Required: true},
|
|
{Name: "key", Type: "string", Description: "Optional specific fact key to delete; omit to delete all", Required: false},
|
|
},
|
|
},
|
|
Exec: func(ctx context.Context, args map[string]any) tools.Result {
|
|
subject := tools.GetString(args, "subject")
|
|
if subject == "" {
|
|
return tools.Result{Err: fmt.Errorf("memory_forget: subject is required")}
|
|
}
|
|
var keyPtr *string
|
|
if k := tools.GetString(args, "key"); k != "" {
|
|
keyPtr = &k
|
|
}
|
|
err := store.DeleteFacts(ctx, agentID, subject, keyPtr)
|
|
if err != nil {
|
|
return tools.Result{Err: fmt.Errorf("memory_forget: %w", err)}
|
|
}
|
|
if keyPtr != nil {
|
|
return tools.Result{Output: fmt.Sprintf("forgot %s.%s", subject, *keyPtr)}
|
|
}
|
|
return tools.Result{Output: fmt.Sprintf("forgot all facts about %s", subject)}
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewMemoryClearContext creates a tool that clears the conversation window.
|
|
func NewMemoryClearContext(clearer WindowClearer, roomCtx *RoomContext) tools.Tool {
|
|
return tools.Tool{
|
|
Def: tools.Def{
|
|
Name: "memory_clear_context",
|
|
Description: "Clear the conversation context window. Useful to start fresh. Omit room_id to clear the current room.",
|
|
Parameters: []tools.Param{
|
|
{Name: "room_id", Type: "string", Description: "Optional room ID to clear; defaults to current room", Required: false},
|
|
},
|
|
},
|
|
Exec: func(ctx context.Context, args map[string]any) tools.Result {
|
|
roomID := tools.GetString(args, "room_id")
|
|
if roomID == "" {
|
|
roomID = roomCtx.Get()
|
|
}
|
|
if roomID == "" {
|
|
return tools.Result{Err: fmt.Errorf("memory_clear_context: no room_id provided and no current room")}
|
|
}
|
|
clearer.ClearWindow(roomID)
|
|
return tools.Result{Output: fmt.Sprintf("conversation context cleared for room %s", roomID)}
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewMemorySummary creates a tool that saves an important summary to long-term memory.
|
|
func NewMemorySummary(agentID string, store MemoryStore) tools.Tool {
|
|
return tools.Tool{
|
|
Def: tools.Def{
|
|
Name: "memory_summary",
|
|
Description: "Save an important summary or takeaway from the current conversation to long-term memory.",
|
|
Parameters: []tools.Param{
|
|
{Name: "text", Type: "string", Description: "The summary text to save", Required: true},
|
|
},
|
|
},
|
|
Exec: func(ctx context.Context, args map[string]any) tools.Result {
|
|
text := tools.GetString(args, "text")
|
|
if text == "" {
|
|
return tools.Result{Err: fmt.Errorf("memory_summary: text is required")}
|
|
}
|
|
key := time.Now().UTC().Format("2006-01-02T15:04:05")
|
|
err := store.SaveFact(ctx, memory.Fact{
|
|
AgentID: agentID,
|
|
Subject: "_summary",
|
|
Key: key,
|
|
Value: text,
|
|
})
|
|
if err != nil {
|
|
return tools.Result{Err: fmt.Errorf("memory_summary: %w", err)}
|
|
}
|
|
return tools.Result{Output: "summary saved"}
|
|
},
|
|
}
|
|
}
|