diff --git a/pkg/config/latest/validate.go b/pkg/config/latest/validate.go index 6d2131418..57329b67e 100644 --- a/pkg/config/latest/validate.go +++ b/pkg/config/latest/validate.go @@ -108,8 +108,8 @@ func (t *Toolset) validate() error { if t.URL != "" && t.Type != "a2a" && t.Type != "openapi" { return errors.New("url can only be used with type 'a2a' or 'openapi'") } - if t.Name != "" && (t.Type != "mcp" && t.Type != "a2a") { - return errors.New("name can only be used with type 'mcp' or 'a2a'") + if t.Name != "" && (t.Type != "mcp" && t.Type != "a2a" && t.Type != "rag") { + return errors.New("name can only be used with type 'mcp', 'a2a', or 'rag'") } if t.RAGConfig != nil && t.Type != "rag" { return errors.New("rag_config can only be used with type 'rag'") diff --git a/pkg/config/rags.go b/pkg/config/rags.go index 4cc6a3c18..1f000054a 100644 --- a/pkg/config/rags.go +++ b/pkg/config/rags.go @@ -24,7 +24,7 @@ func resolveRAGDefinitions(cfg *latest.Config) error { return fmt.Errorf("agent '%s' references non-existent RAG definition '%s'", agent.Name, ts.Ref) } - applyRAGDefaults(ts, &def.Toolset) + applyRAGDefaults(ts, &def.Toolset, ts.Ref) } } @@ -32,7 +32,7 @@ func resolveRAGDefinitions(cfg *latest.Config) error { } // applyRAGDefaults fills empty fields in ts from def. Toolset values win. -func applyRAGDefaults(ts, def *latest.Toolset) { +func applyRAGDefaults(ts, def *latest.Toolset, refName string) { // Clear the ref since it's been resolved ts.Ref = "" @@ -51,4 +51,10 @@ func applyRAGDefaults(ts, def *latest.Toolset) { if ts.Name == "" { ts.Name = def.Name } + // If name is still empty after applying defaults from the definition, + // use the ref key (e.g., "rag1") so that multiple RAG tools get unique + // tool names instead of all defaulting to "rag". + if ts.Name == "" { + ts.Name = refName + } } diff --git a/pkg/rag/strategy/vector_store.go b/pkg/rag/strategy/vector_store.go index ad04623ac..524e27d26 100644 --- a/pkg/rag/strategy/vector_store.go +++ b/pkg/rag/strategy/vector_store.go @@ -813,6 +813,15 @@ func (s *VectorStore) addPathToWatcher(ctx context.Context, path string) error { } func (s *VectorStore) watchLoop(ctx context.Context, docPaths []string) { + // Capture watcher reference at goroutine start to avoid racing with Close() + // which sets s.watcher = nil under watcherMu. + s.watcherMu.Lock() + watcher := s.watcher + s.watcherMu.Unlock() + if watcher == nil { + return + } + var debounceTimer *time.Timer debounceDuration := 2 * time.Second pendingChanges := make(map[string]bool) @@ -929,7 +938,7 @@ func (s *VectorStore) watchLoop(ctx context.Context, docPaths []string) { slog.Info("File watcher stopped", "strategy", s.name) return - case event, ok := <-s.watcher.Events: + case event, ok := <-watcher.Events: if !ok { return } @@ -940,8 +949,10 @@ func (s *VectorStore) watchLoop(ctx context.Context, docPaths []string) { if event.Op&fsnotify.Create != 0 { s.watcherMu.Lock() - if err := s.addPathToWatcher(ctx, event.Name); err != nil { - slog.Debug("Could not watch new path", "path", event.Name, "error", err) + if s.watcher != nil { + if err := s.addPathToWatcher(ctx, event.Name); err != nil { + slog.Debug("Could not watch new path", "path", event.Name, "error", err) + } } s.watcherMu.Unlock() } @@ -974,7 +985,7 @@ func (s *VectorStore) watchLoop(ctx context.Context, docPaths []string) { } debounceTimer = time.AfterFunc(debounceDuration, processChanges) - case err, ok := <-s.watcher.Errors: + case err, ok := <-watcher.Errors: if !ok { return }