@@ -47,17 +47,18 @@ func defaultCodeExecutor() codeexecutor.CodeExecutor {
4747
4848// LLMAgent is an agent that uses an LLM to generate responses.
4949type LLMAgent struct {
50- name string
51- mu sync.RWMutex
52- model model.Model
53- models map [string ]model.Model // Registered models for switching
54- description string
55- instruction string
56- systemPrompt string
57- genConfig model.GenerationConfig
58- flow flow.Flow
59- tools []tool.Tool // All tools (user tools + framework tools)
60- userToolNames map [string ]bool // Names of tools explicitly registered by user via WithTools and WithToolSets
50+ name string
51+ mu sync.RWMutex
52+ model model.Model
53+ models map [string ]model.Model // Registered models for switching
54+ description string
55+ instruction string
56+ systemPrompt string
57+ genConfig model.GenerationConfig
58+ flow flow.Flow
59+ tools []tool.Tool // All tools (user tools + framework tools)
60+ userToolNames map [string ]bool // Names of tools explicitly registered
61+ // via WithTools and WithToolSets.
6162 codeExecutor codeexecutor.CodeExecutor
6263 planner planner.Planner
6364 subAgents []agent.Agent // Sub-agents that can be delegated to
@@ -587,21 +588,34 @@ func (a *LLMAgent) Info() agent.Info {
587588 }
588589}
589590
590- // Tools implements the agent.Agent interface.
591- // It returns the list of tools available to the agent, including transfer tools.
592- func (a * LLMAgent ) Tools () []tool.Tool {
591+ // getAllToolsLocked builds the full tool list (user tools plus framework
592+ // tools like transfer_to_agent) under the caller's read lock. It always
593+ // returns a fresh slice so callers can safely use it after releasing the
594+ // lock without data races.
595+ func (a * LLMAgent ) getAllToolsLocked () []tool.Tool {
596+ tools := make ([]tool.Tool , len (a .tools ))
597+ copy (tools , a .tools )
598+
593599 if len (a .subAgents ) == 0 {
594- return a . tools
600+ return tools
595601 }
596602
597- // Create agent info for sub-agents.
598603 agentInfos := make ([]agent.Info , len (a .subAgents ))
599604 for i , subAgent := range a .subAgents {
600605 agentInfos [i ] = subAgent .Info ()
601606 }
602607
603608 transferTool := transfer .New (agentInfos )
604- return append (a .tools , transferTool )
609+ return append (tools , transferTool )
610+ }
611+
612+ // Tools implements the agent.Agent interface. It returns the list of
613+ // tools available to the agent, including transfer tools.
614+ func (a * LLMAgent ) Tools () []tool.Tool {
615+ a .mu .RLock ()
616+ defer a .mu .RUnlock ()
617+
618+ return a .getAllToolsLocked ()
605619}
606620
607621// SubAgents returns the list of sub-agents for this agent.
@@ -620,20 +634,24 @@ func (a *LLMAgent) FindSubAgent(name string) agent.Agent {
620634 return nil
621635}
622636
623- // UserTools returns the list of tools that were explicitly registered by the user
624- // via WithTools and WithToolSets options.
637+ // UserTools returns the list of tools that were explicitly registered
638+ // by the user via WithTools and WithToolSets options.
625639//
626640// User tools (can be filtered):
627641// - Tools registered via WithTools
628642// - Tools registered via WithToolSets
629643//
630644// Framework tools (never filtered, not included in this list):
631- // - knowledge_search / agentic_knowledge_search (auto-added when WithKnowledge is set)
645+ // - knowledge_search / agentic_knowledge_search (auto-added when
646+ // WithKnowledge is set)
632647// - transfer_to_agent (auto-added when WithSubAgents is set)
633648//
634- // This method is used by the tool filtering logic to distinguish user tools from framework tools.
649+ // This method is used by the tool filtering logic to distinguish user
650+ // tools from framework tools.
635651func (a * LLMAgent ) UserTools () []tool.Tool {
636- // Filter user tools from all tools
652+ a .mu .RLock ()
653+ defer a .mu .RUnlock ()
654+
637655 userTools := make ([]tool.Tool , 0 , len (a .userToolNames ))
638656 for _ , t := range a .tools {
639657 if a .userToolNames [t .Declaration ().Name ] {
@@ -645,20 +663,29 @@ func (a *LLMAgent) UserTools() []tool.Tool {
645663
646664// FilterTools filters the list of tools based on the provided filter function.
647665func (a * LLMAgent ) FilterTools (ctx context.Context ) []tool.Tool {
648- filteredTools := make ([]tool.Tool , 0 , len (a .tools ))
666+ a .mu .RLock ()
667+ tools := a .getAllToolsLocked ()
668+ userToolNames := make (map [string ]bool , len (a .userToolNames ))
669+ for name , isUser := range a .userToolNames {
670+ userToolNames [name ] = isUser
671+ }
672+ filter := a .option .toolFilter
673+ a .mu .RUnlock ()
649674
650- for _ , t := range a .Tools () {
651- if ! a .userToolNames [t .Declaration ().Name ] {
652- filteredTools = append (filteredTools , t )
675+ filtered := make ([]tool.Tool , 0 , len (tools ))
676+ for _ , t := range tools {
677+ name := t .Declaration ().Name
678+ if ! userToolNames [name ] {
679+ filtered = append (filtered , t )
653680 continue
654681 }
655- // Apply user tool filter
656- if a . option . toolFilter == nil || a . option . toolFilter (ctx , t ) {
657- filteredTools = append (filteredTools , t )
682+
683+ if filter == nil || filter (ctx , t ) {
684+ filtered = append (filtered , t )
658685 }
659686 }
660687
661- return filteredTools
688+ return filtered
662689}
663690
664691// CodeExecutor returns the code executor used by this agent.
@@ -668,6 +695,92 @@ func (a *LLMAgent) CodeExecutor() codeexecutor.CodeExecutor {
668695 return a .codeExecutor
669696}
670697
698+ // refreshToolsLocked recomputes the aggregated tool list and user tool
699+ // tracking map from the current options. Caller must hold a.mu.Lock.
700+ func (a * LLMAgent ) refreshToolsLocked () {
701+ tools , userToolNames := registerTools (& a .option )
702+ a .tools = tools
703+ a .userToolNames = userToolNames
704+ }
705+
706+ // AddToolSet adds or replaces a tool set at runtime in a
707+ // concurrency-safe way. If another ToolSet with the same Name()
708+ // already exists, it will be replaced. Subsequent invocations of the
709+ // agent will see the updated tool list without recreating the agent.
710+ func (a * LLMAgent ) AddToolSet (toolSet tool.ToolSet ) {
711+ if toolSet == nil {
712+ return
713+ }
714+
715+ name := toolSet .Name ()
716+
717+ a .mu .Lock ()
718+ defer a .mu .Unlock ()
719+
720+ replaced := false
721+ for i , ts := range a .option .ToolSets {
722+ if name != "" && ts .Name () == name {
723+ a .option .ToolSets [i ] = toolSet
724+ replaced = true
725+ break
726+ }
727+ }
728+ if ! replaced {
729+ a .option .ToolSets = append (a .option .ToolSets , toolSet )
730+ }
731+
732+ a .refreshToolsLocked ()
733+ }
734+
735+ // RemoveToolSet removes all tool sets whose Name() matches the given
736+ // name. It returns true if at least one ToolSet was removed. Tools
737+ // from the removed tool sets will no longer be exposed on future
738+ // invocations.
739+ func (a * LLMAgent ) RemoveToolSet (name string ) bool {
740+ a .mu .Lock ()
741+ defer a .mu .Unlock ()
742+
743+ if len (a .option .ToolSets ) == 0 {
744+ return false
745+ }
746+
747+ dst := a .option .ToolSets [:0 ]
748+ removed := false
749+ for _ , ts := range a .option .ToolSets {
750+ if ts .Name () == name {
751+ removed = true
752+ continue
753+ }
754+ dst = append (dst , ts )
755+ }
756+ if ! removed {
757+ return false
758+ }
759+ a .option .ToolSets = dst
760+
761+ a .refreshToolsLocked ()
762+
763+ return true
764+ }
765+
766+ // SetToolSets replaces the agent ToolSets with the provided slice in a
767+ // concurrency-safe way. Subsequent invocations will see tools from
768+ // exactly these ToolSets plus framework tools (knowledge, skills).
769+ func (a * LLMAgent ) SetToolSets (toolSets []tool.ToolSet ) {
770+ a .mu .Lock ()
771+ defer a .mu .Unlock ()
772+
773+ if len (toolSets ) == 0 {
774+ a .option .ToolSets = nil
775+ } else {
776+ copied := make ([]tool.ToolSet , len (toolSets ))
777+ copy (copied , toolSets )
778+ a .option .ToolSets = copied
779+ }
780+
781+ a .refreshToolsLocked ()
782+ }
783+
671784// SetModel sets the model for this agent in a concurrency-safe way.
672785// This allows callers to manage multiple models externally and switch
673786// dynamically during runtime.
0 commit comments