Skip to main content

Dynamic tool search for Google ADK — load tools on demand instead of all at once

Project description

adk-tool-search

Dynamic tool search for Google ADK — load tools on demand instead of all at once.

Implements the Anthropic Tool Search pattern for Google's Agent Development Kit (ADK). Instead of loading all tool definitions into context upfront, the agent discovers and loads tools on demand using BM25 search.

Why?

Problem Impact
Context bloat A typical multi-MCP setup can consume 50k+ tokens in tool definitions before the agent does any work
Tool selection accuracy LLM ability to pick the right tool degrades past 30-50 tools
Gemini's 100-tool limit Hard cap on function declarations in the Gemini API

This library reduces context usage by ~95% and keeps tool selection accurate across hundreds of tools.

How it works

┌─────────────────────────────────────────────────────┐
│  Startup                                            │
│  1. Fetch tools from MCP servers / register funcs   │
│  2. Index all tools in BM25 registry                │
│  3. Agent starts with only: search_tools, load_tool │
└─────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────┐
│  Runtime (per user request)                         │
│  1. Agent calls search_tools("weather forecast")    │
│  2. Registry returns top-5 matches (name + snippet) │
│  3. Agent calls load_tool("get_forecast")           │
│  4. after_tool_callback injects tool into agent     │
│  5. Agent calls get_forecast(location="Tokyo")      │
└─────────────────────────────────────────────────────┘

Install

pip install adk-tool-search

Development setup

git clone https://github.com/manojlds/adk-tool-search.git
cd adk-tool-search
uv sync --all-extras

Quick start

With plain Python functions

from google.adk.agents import LlmAgent
from adk_tool_search import ToolRegistry, create_search_and_load_tools, create_dynamic_loader_callback

def get_weather(location: str) -> dict:
    """Get current weather for a location."""
    return {"location": location, "temp": 22, "condition": "sunny"}

def send_email(to: str, subject: str, body: str) -> dict:
    """Send an email."""
    return {"status": "sent"}

# 1. Register tools in the search index
registry = ToolRegistry()
registry.register_many([get_weather, send_email])

# 2. Create search + load tools
search_tools, load_tool = create_search_and_load_tools(registry)

# 3. Create agent with only the search/load tools
agent_ref: list[LlmAgent] = []
callback = create_dynamic_loader_callback(registry, agent_ref)

agent = LlmAgent(
    name="Assistant",
    model="gemini-2.5-flash",
    instruction="Use search_tools to find tools, load_tool to activate them, then call them.",
    tools=[search_tools, load_tool],
    after_tool_callback=callback,
)
agent_ref.append(agent)

With MCP servers

from google.adk.agents import LlmAgent
from google.adk.tools.mcp import MCPToolset, StdioConnectionParams
from adk_tool_search import ToolRegistry, create_search_and_load_tools, create_dynamic_loader_callback

# Fetch tools from MCP server (but don't give to agent)
mcp = MCPToolset(connection_params=StdioConnectionParams(command="npx", args=["-y", "@modelcontextprotocol/server-github"]))
mcp_tools = await mcp.get_tools()

# Index all MCP tools
registry = ToolRegistry()
registry.register_many(mcp_tools)

# Wire up the agent
search_tools, load_tool = create_search_and_load_tools(registry)
agent_ref: list[LlmAgent] = []
callback = create_dynamic_loader_callback(registry, agent_ref)

agent = LlmAgent(
    name="GitHubAssistant",
    model="gemini-2.5-flash",
    instruction="Use search_tools to find tools, load_tool to activate them, then call them.",
    tools=[search_tools, load_tool],
    after_tool_callback=callback,
)
agent_ref.append(agent)

Using the helper factory

For convenience, create_tool_search_agent wraps the above into a single call:

from adk_tool_search import ToolRegistry, create_tool_search_agent

registry = ToolRegistry()
registry.register_many([get_weather, send_email])

agent = create_tool_search_agent(
    name="Assistant",
    model="gemini-2.5-flash",
    registry=registry,
)

Examples

# Plain function tools demo
uv run python examples/function_tools_demo.py

# MCP server demo (requires GITHUB_TOKEN)
GITHUB_TOKEN=ghp_... uv run python examples/mcp_demo.py

API

ToolRegistry

  • register(tool) — Register a single tool (function, ADK tool, or MCP tool)
  • register_many(tools) — Register multiple tools (rebuilds index once)
  • search(query, n=5) — BM25 search, returns ["name: snippet", ...]
  • get_tool(name) — Get tool object by exact name
  • tool_count / tool_names — Introspection properties

create_search_and_load_tools(registry)

Returns (search_tools, load_tool) — the two lightweight functions to give your agent.

create_dynamic_loader_callback(registry, agent_ref)

Returns an after_tool_callback that intercepts load_tool calls and injects tools into the agent. agent_ref is a single-element list you append the agent to after creation.

create_tool_search_agent(...) (helper)

  • name, model — Standard Agent params
  • registry — A populated ToolRegistry
  • instruction — Optional custom instruction
  • always_available_tools — Tools that skip deferred loading
  • **agent_kwargs — Forwarded to Agent()

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

adk_tool_search-0.1.0.tar.gz (8.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

adk_tool_search-0.1.0-py3-none-any.whl (6.9 kB view details)

Uploaded Python 3

File details

Details for the file adk_tool_search-0.1.0.tar.gz.

File metadata

  • Download URL: adk_tool_search-0.1.0.tar.gz
  • Upload date:
  • Size: 8.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for adk_tool_search-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9a9454b1bfc9ca7359bd31c5e75c591d6bb11b0548f5f9243f0ed238eb9bd8b7
MD5 bb24916c34104cad608bc2b59fc577a0
BLAKE2b-256 f772b12051fa87b3ea59924da07b8aad4574b973ccfcd1ca580220084188864f

See more details on using hashes here.

Provenance

The following attestation bundles were made for adk_tool_search-0.1.0.tar.gz:

Publisher: publish.yml on manojlds/adk-tool-search

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file adk_tool_search-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for adk_tool_search-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1b6a3d361ba9808e2b0bc58ffb0c6ba0ebd45908dea15b281fc06064cd10425b
MD5 f28efccbfd59de36952f08e23b9e63e8
BLAKE2b-256 1fced8c1cdc74725faaa26cb75c83abc302c4fae3d833f2ebe278f941c217ae9

See more details on using hashes here.

Provenance

The following attestation bundles were made for adk_tool_search-0.1.0-py3-none-any.whl:

Publisher: publish.yml on manojlds/adk-tool-search

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page