Files
agents_and_robots/tools/knowledge_test.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

189 lines
4.5 KiB
Go

package tools
import (
"context"
"testing"
"github.com/enmanuel/agents/pkg/knowledge"
)
// mockKnowledgeStore implements KnowledgeStore for testing.
type mockKnowledgeStore struct {
docs map[string]knowledge.Document
}
func newMockKnowledgeStore() *mockKnowledgeStore {
return &mockKnowledgeStore{docs: make(map[string]knowledge.Document)}
}
func (m *mockKnowledgeStore) Search(_ context.Context, query string, limit int) ([]knowledge.SearchResult, error) {
var results []knowledge.SearchResult
for _, d := range m.docs {
if len(results) >= limit {
break
}
results = append(results, knowledge.SearchResult{
Slug: d.Slug,
Title: d.Title,
Snippet: d.Content[:min(len(d.Content), 50)],
Rank: 1.0,
})
}
return results, nil
}
func (m *mockKnowledgeStore) Get(_ context.Context, slug string) (*knowledge.Document, error) {
d, ok := m.docs[slug]
if !ok {
return nil, &notFoundError{slug}
}
return &d, nil
}
func (m *mockKnowledgeStore) Put(_ context.Context, doc knowledge.Document) error {
m.docs[doc.Slug] = doc
return nil
}
func (m *mockKnowledgeStore) List(_ context.Context) ([]knowledge.Document, error) {
var docs []knowledge.Document
for _, d := range m.docs {
docs = append(docs, d)
}
return docs, nil
}
type notFoundError struct{ slug string }
func (e *notFoundError) Error() string { return "not found: " + e.slug }
func TestKnowledgeSearchTool(t *testing.T) {
store := newMockKnowledgeStore()
store.docs["go-patterns"] = knowledge.Document{
Slug: "go-patterns", Title: "Go Patterns", Content: "Use interfaces",
}
tool := NewKnowledgeSearch(store)
if tool.Def.Name != "knowledge_search" {
t.Errorf("name = %q, want knowledge_search", tool.Def.Name)
}
// Missing query
r := tool.Exec(context.Background(), map[string]any{})
if r.Err == nil {
t.Error("expected error for missing query")
}
// Valid search
r = tool.Exec(context.Background(), map[string]any{"query": "go"})
if r.Err != nil {
t.Errorf("unexpected error: %v", r.Err)
}
if r.Output == "" {
t.Error("expected non-empty output")
}
}
func TestKnowledgeReadTool(t *testing.T) {
store := newMockKnowledgeStore()
store.docs["test-doc"] = knowledge.Document{
Slug: "test-doc", Title: "Test", Content: "Hello world",
}
tool := NewKnowledgeRead(store)
// Missing slug
r := tool.Exec(context.Background(), map[string]any{})
if r.Err == nil {
t.Error("expected error for missing slug")
}
// Valid read
r = tool.Exec(context.Background(), map[string]any{"slug": "test-doc"})
if r.Err != nil {
t.Errorf("unexpected error: %v", r.Err)
}
if r.Output != "Hello world" {
t.Errorf("output = %q, want %q", r.Output, "Hello world")
}
// Not found
r = tool.Exec(context.Background(), map[string]any{"slug": "nope"})
if r.Err == nil {
t.Error("expected error for nonexistent doc")
}
}
func TestKnowledgeWriteTool(t *testing.T) {
store := newMockKnowledgeStore()
tool := NewKnowledgeWrite(store)
// Missing params
r := tool.Exec(context.Background(), map[string]any{"slug": "test"})
if r.Err == nil {
t.Error("expected error for missing content")
}
// Valid write
r = tool.Exec(context.Background(), map[string]any{
"slug": "new-doc",
"content": "# New Doc\nSome content",
})
if r.Err != nil {
t.Errorf("unexpected error: %v", r.Err)
}
if _, ok := store.docs["new-doc"]; !ok {
t.Error("document was not stored")
}
}
func TestKnowledgeListTool(t *testing.T) {
store := newMockKnowledgeStore()
tool := NewKnowledgeList(store)
// Empty
r := tool.Exec(context.Background(), map[string]any{})
if r.Err != nil {
t.Errorf("unexpected error: %v", r.Err)
}
if r.Output != "knowledge base is empty" {
t.Errorf("expected empty message, got %q", r.Output)
}
// With docs
store.docs["doc1"] = knowledge.Document{Slug: "doc1", Title: "Doc 1"}
r = tool.Exec(context.Background(), map[string]any{})
if r.Err != nil {
t.Errorf("unexpected error: %v", r.Err)
}
if r.Output == "knowledge base is empty" {
t.Error("expected non-empty output after adding docs")
}
}
func TestGetInt(t *testing.T) {
tests := []struct {
args map[string]any
key string
want int
}{
{map[string]any{"n": float64(5)}, "n", 5},
{map[string]any{"n": 3}, "n", 3},
{map[string]any{"n": "str"}, "n", 0},
{map[string]any{}, "n", 0},
}
for _, tt := range tests {
got := getInt(tt.args, tt.key)
if got != tt.want {
t.Errorf("getInt(%v, %q) = %d, want %d", tt.args, tt.key, got, tt.want)
}
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}