package knowledgetools import ( "context" "fmt" "strings" "github.com/enmanuel/agents/pkg/knowledge" "github.com/enmanuel/agents/tools" ) // KnowledgeStore is the subset of knowledge.Store needed by knowledge tools. type KnowledgeStore interface { Search(ctx context.Context, query string, limit int) ([]knowledge.SearchResult, error) Get(ctx context.Context, slug string) (*knowledge.Document, error) Put(ctx context.Context, doc knowledge.Document) error List(ctx context.Context) ([]knowledge.Document, error) } // NewKnowledgeSearch creates a tool that searches the knowledge base. func NewKnowledgeSearch(store KnowledgeStore) tools.Tool { return tools.Tool{ Def: tools.Def{ Name: "knowledge_search", Description: "Search your knowledge base for relevant documents. Returns matching snippets ranked by relevance.", Parameters: []tools.Param{ {Name: "query", Type: "string", Description: "Search terms or phrase", Required: true}, {Name: "limit", Type: "integer", Description: "Max results (default 5)", Required: false}, }, }, Exec: func(ctx context.Context, args map[string]any) tools.Result { query := tools.GetString(args, "query") if query == "" { return tools.Result{Err: fmt.Errorf("knowledge_search: query is required")} } limit := tools.GetInt(args, "limit") if limit <= 0 { limit = 5 } results, err := store.Search(ctx, query, limit) if err != nil { return tools.Result{Err: fmt.Errorf("knowledge_search: %w", err)} } if len(results) == 0 { return tools.Result{Output: "no documents found matching your query"} } var sb strings.Builder for i, r := range results { fmt.Fprintf(&sb, "%d. **%s** (`%s`)\n %s\n", i+1, r.Title, r.Slug, r.Snippet) } return tools.Result{Output: sb.String()} }, } } // NewKnowledgeRead creates a tool that reads a knowledge document. func NewKnowledgeRead(store KnowledgeStore) tools.Tool { return tools.Tool{ Def: tools.Def{ Name: "knowledge_read", Description: "Read the full content of a knowledge document by its slug.", Parameters: []tools.Param{ {Name: "slug", Type: "string", Description: "Document slug (e.g. \"go-patterns\")", Required: true}, }, }, Exec: func(ctx context.Context, args map[string]any) tools.Result { slug := tools.GetString(args, "slug") if slug == "" { return tools.Result{Err: fmt.Errorf("knowledge_read: slug is required")} } doc, err := store.Get(ctx, slug) if err != nil { return tools.Result{Err: fmt.Errorf("knowledge_read: %w", err)} } return tools.Result{Output: doc.Content} }, } } // NewKnowledgeWrite creates a tool that writes a knowledge document. func NewKnowledgeWrite(store KnowledgeStore) tools.Tool { return tools.Tool{ Def: tools.Def{ Name: "knowledge_write", Description: "Create or update a knowledge document. Use this to save new knowledge or improve existing documents.", Parameters: []tools.Param{ {Name: "slug", Type: "string", Description: "Document slug (lowercase, hyphens, e.g. \"matrix-tips\")", Required: true}, {Name: "content", Type: "string", Description: "Full markdown content of the document", Required: true}, }, }, Exec: func(ctx context.Context, args map[string]any) tools.Result { slug := tools.GetString(args, "slug") content := tools.GetString(args, "content") if slug == "" || content == "" { return tools.Result{Err: fmt.Errorf("knowledge_write: slug and content are required")} } err := store.Put(ctx, knowledge.Document{ Slug: slug, Content: content, }) if err != nil { return tools.Result{Err: fmt.Errorf("knowledge_write: %w", err)} } return tools.Result{Output: fmt.Sprintf("document saved: %s (%d bytes)", slug, len(content))} }, } } // NewKnowledgeList creates a tool that lists all knowledge documents. func NewKnowledgeList(store KnowledgeStore) tools.Tool { return tools.Tool{ Def: tools.Def{ Name: "knowledge_list", Description: "List all documents in your knowledge base with their titles.", Parameters: []tools.Param{}, }, Exec: func(ctx context.Context, args map[string]any) tools.Result { docs, err := store.List(ctx) if err != nil { return tools.Result{Err: fmt.Errorf("knowledge_list: %w", err)} } if len(docs) == 0 { return tools.Result{Output: "knowledge base is empty"} } var sb strings.Builder for _, d := range docs { fmt.Fprintf(&sb, "- `%s`: %s (updated %s)\n", d.Slug, d.Title, d.UpdatedAt.Format("2006-01-02")) } return tools.Result{Output: sb.String()} }, } }