Files
agents_and_robots/tools/knowledge.go
T
egutierrez 69607b3a65 feat: añadir sistema de knowledge por agente
Implementa una base de conocimiento persistente por agente siguiendo
el patrón pure core / impure shell:

- pkg/knowledge/: tipos puros (Document, Store interface)
- shell/knowledge/: FileStore con SQLite para indexación y archivos .md
- tools/knowledge.go: 4 tools LLM (search, read, write, list)
- tools/knowledge_test.go: tests unitarios de las tools
- internal/config/schema.go: nuevo KnowledgeToolCfg en ToolsCfg
- agents/runtime.go: inicialización del store y registro de tools
- agents/*/knowledge/about-me.md: documentos semilla para cada agente

Cada agente puede buscar, leer, crear y actualizar documentos de
conocimiento. Los archivos .md viven en agents/<id>/knowledge/ y se
indexan en SQLite (agents/<id>/data/knowledge.db).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:02:39 +00:00

154 lines
4.5 KiB
Go

package tools
import (
"context"
"fmt"
"strings"
"github.com/enmanuel/agents/pkg/knowledge"
)
// 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) Tool {
return Tool{
Def: Def{
Name: "knowledge_search",
Description: "Search your knowledge base for relevant documents. Returns matching snippets ranked by relevance.",
Parameters: []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) Result {
query := getString(args, "query")
if query == "" {
return Result{Err: fmt.Errorf("knowledge_search: query is required")}
}
limit := getInt(args, "limit")
if limit <= 0 {
limit = 5
}
results, err := store.Search(ctx, query, limit)
if err != nil {
return Result{Err: fmt.Errorf("knowledge_search: %w", err)}
}
if len(results) == 0 {
return 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 Result{Output: sb.String()}
},
}
}
// NewKnowledgeRead creates a tool that reads a knowledge document.
func NewKnowledgeRead(store KnowledgeStore) Tool {
return Tool{
Def: Def{
Name: "knowledge_read",
Description: "Read the full content of a knowledge document by its slug.",
Parameters: []Param{
{Name: "slug", Type: "string", Description: "Document slug (e.g. \"go-patterns\")", Required: true},
},
},
Exec: func(ctx context.Context, args map[string]any) Result {
slug := getString(args, "slug")
if slug == "" {
return Result{Err: fmt.Errorf("knowledge_read: slug is required")}
}
doc, err := store.Get(ctx, slug)
if err != nil {
return Result{Err: fmt.Errorf("knowledge_read: %w", err)}
}
return Result{Output: doc.Content}
},
}
}
// NewKnowledgeWrite creates a tool that writes a knowledge document.
func NewKnowledgeWrite(store KnowledgeStore) Tool {
return Tool{
Def: Def{
Name: "knowledge_write",
Description: "Create or update a knowledge document. Use this to save new knowledge or improve existing documents.",
Parameters: []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) Result {
slug := getString(args, "slug")
content := getString(args, "content")
if slug == "" || content == "" {
return 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 Result{Err: fmt.Errorf("knowledge_write: %w", err)}
}
return 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) Tool {
return Tool{
Def: Def{
Name: "knowledge_list",
Description: "List all documents in your knowledge base with their titles.",
Parameters: []Param{},
},
Exec: func(ctx context.Context, args map[string]any) Result {
docs, err := store.List(ctx)
if err != nil {
return Result{Err: fmt.Errorf("knowledge_list: %w", err)}
}
if len(docs) == 0 {
return 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 Result{Output: sb.String()}
},
}
}
// getInt extracts an integer argument by name, returning 0 if missing or wrong type.
func getInt(args map[string]any, key string) int {
v, ok := args[key]
if !ok {
return 0
}
switch n := v.(type) {
case float64:
return int(n)
case int:
return n
default:
return 0
}
}