How to Implement Active Tool Discovery and Selection in Self-Evolving AI Agents
Active tool discovery and selection enables AI agents to automatically search existing tool libraries, reuse proven solutions when available, and dynamically create new tools when needed, implementing a self-evolving capability loop that eliminates redundant computation.
The ai-agent-book repository demonstrates a production-ready implementation of active tool discovery and selection in Chapter 8's self-evolving tools architecture. This pattern allows agents to persistently expand their capabilities by recognizing reusable task solutions and storing them as discoverable tools for future queries, creating a virtuous cycle of capability accumulation without manual engineering.
The Active Tool Discovery and Selection Workflow
The self-evolving agent in [agent.py](https://github.com/bojieli/ai-agent-book/blob/main/chapter8/self-evolving-tools/agent.py) implements a five-step pipeline that prioritizes tool reuse over redundant computation. According to the source code comments, the workflow follows strict priority: search existing tools first (step 0️⃣), execute immediately if found (step 1️⃣), fall back to primitive methods only when necessary (step 2️⃣), register successful solutions (step 3️⃣), and validate through immediate reuse (step 4️⃣).
Step 1: Searching the Tool Library
Before executing any external operation, the agent queries the persistent tool library using search_tools(query). This method scans JSON definitions stored in the tool_library/ directory and returns matching candidates based on semantic similarity to the task description.
In [tool_manager.py](https://github.com/bojieli/ai-agent-book/blob/main/chapter8/self-evolving-tools/tool_manager.py), the search_tools implementation filters available tools and returns a structured response containing a count field and a tools array, indicating whether suitable candidates exist for immediate execution.
Step 2: Executing Reusable Tools
When search_tools returns a match, the agent immediately invokes execute_tool(name, arguments) to run the stored implementation. This execution path bypasses web searches and code generation entirely, ensuring deterministic, tested behavior for known tasks.
The execute_tool method in tool_manager.py handles loading the tool's code from disk, sandboxing execution in a restricted environment, and returning structured results with a result key to the caller.
Step 3: Fallback to Primitive Tools
If no existing tool addresses the current need, the agent falls back to a predefined set of primitive capabilities. The constant _META_TOOLS in tool_manager.py enumerates these baseline functions: web_search, read_webpage, and code_interpreter implemented in [base_tools.py](https://github.com/bojieli/ai-agent-book/blob/main/chapter8/self-evolving-tools/base_tools.py).
These primitives provide the raw capabilities needed to solve novel problems before they can be encapsulated into reusable tools, serving as the capability foundation for the self-evolving system.
Step 4: Creating New Tools
Upon successfully solving a task with primitive tools, the agent packages the solution using create_tool(name, description, parameters, code). This method validates the generated code syntax using Python's AST module, ensures a def run(**kwargs) entry point exists, and persists the tool definition to disk using JSON Schema for parameter validation.
The create_tool implementation in tool_manager.py—described in source comments as "封装并持久化" (encapsulate and persist)—guarantees that new tools conform to the library's interface requirements and are immediately available for subsequent discovery.
Step 5: Validation and Immediate Reuse
After registration, the agent executes the newly created tool via execute_tool to verify functionality and return the final result. This completes the loop: future identical requests will discover the tool in Step 1, avoiding redundant computation and web searches.
Code Implementation Examples
The following examples demonstrate the active tool discovery pattern using the ToolLibrary class and high-level agent orchestration.
Direct Library Interaction
This example shows the complete discovery, fallback, creation, and reuse cycle:
from tool_manager import ToolLibrary
import base_tools
# Initialize the tool library with persistence directory
lib = ToolLibrary(library_dir="tool_library")
# Step 1: Search for existing tool
search_result = lib.search_tools("calculate days between dates")
if search_result["count"] > 0:
# Step 2: Execute existing tool
tool_name = search_result["tools"][0]["name"]
result = lib.execute_tool(
tool_name,
{"start": "2023-01-01", "end": "2023-12-31"}
)
else:
# Step 3: Fallback to primitive code execution
raw_output = base_tools.code_interpreter(
"from datetime import datetime; "
"start = datetime(2023, 1, 1); "
"end = datetime(2023, 12, 31); "
"print((end - start).days)"
)
# Step 4: Create new tool from successful solution
lib.create_tool(
name="days_between_dates",
description="Calculate days between two ISO format dates",
parameters={
"type": "object",
"properties": {
"start": {"type": "string", "format": "date"},
"end": {"type": "string", "format": "date"}
},
"required": ["start", "end"]
},
code="""
from datetime import datetime
def run(start: str, end: str) -> int:
fmt = "%Y-%m-%d"
d1 = datetime.strptime(start, fmt)
d2 = datetime.strptime(end, fmt)
return (d2 - d1).days
"""
)
# Step 5: Execute newly created tool
result = lib.execute_tool(
"days_between_dates",
{"start": "2023-01-01", "end": "2023-12-31"}
)
print(f"Result: {result['result']}")
High-Level Agent Orchestration
For production use, the SelfEvolvingAgent class automates the entire workflow:
from agent import SelfEvolvingAgent
# Initialize the self-evolving agent
agent = SelfEvolvingAgent()
# The agent automatically handles discovery, fallback, and creation
answer = agent.run(
"How many days elapsed between 2020-03-15 and 2020-07-20?"
)
print(answer)
Key Source Files and Architecture
Understanding the file structure helps navigate the implementation:
-
[
tool_manager.py](https://github.com/bojieli/ai-agent-book/blob/main/chapter8/self-evolving-tools/tool_manager.py): Core library managingsearch_tools(),create_tool(), andexecute_tool(). Handles JSON persistence, code validation, and sandboxed execution. -
[
agent.py](https://github.com/bojieli/ai-agent-book/blob/main/chapter8/self-evolving-tools/agent.py): High-level orchestration implementing the discovery-selection loop with explicit comments marking steps 0️⃣ through 4️⃣ of the workflow. -
[
base_tools.py](https://github.com/bojieli/ai-agent-book/blob/main/chapter8/self-evolving-tools/base_tools.py): Implements primitive_META_TOOLSincludingweb_search,read_webpage, andcode_interpreterthat serve as the capability foundation for tool creation.
Summary
-
Active tool discovery begins with
search_tools()querying persistent JSON definitions in the tool library before attempting any external computation. -
Tool selection prioritizes execution of existing tools via
execute_tool()to ensure deterministic reuse of validated solutions. -
Dynamic creation occurs only after primitive tools solve novel tasks, using
create_tool()to package solutions with validated JSON Schema parameters. -
Immediate persistence makes new tools available for discovery in subsequent requests, creating a self-evolving capability loop.
-
Sandboxed execution in
tool_manager.pyensures tool code runs safely while maintaining access to necessary imports and context.
Frequently Asked Questions
What is the difference between primitive tools and created tools in the ai-agent-book implementation?
Primitive tools are hardcoded capabilities in base_tools.py (web_search, code_interpreter) that provide baseline functionality for unknown tasks. Created tools are dynamically generated Python functions persisted to the tool library that encapsulate specific, reusable task solutions discovered during agent execution.
How does the agent prevent creating duplicate tools for the same task?
The search_tools() method in tool_manager.py performs semantic matching against existing tool descriptions before any creation occurs. If a matching tool exists, the agent executes it immediately via execute_tool(), bypassing the creation fallback entirely and preventing library bloat.
What validation does create_tool() perform on generated code?
The create_tool() method validates that the provided code contains a def run(**kwargs) entry point, checks for syntax errors using Python's AST module, and ensures the parameters conform to JSON Schema standards before persisting the tool definition to the tool_library/ directory.
Can the tool library persist across different agent sessions?
Yes, the ToolLibrary class accepts a library_dir parameter that specifies a filesystem location for JSON tool definitions. These files persist between sessions, enabling long-term accumulation of capabilities across multiple agent runs and system restarts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →