Tool Selection for LLM Agents: Routing Strategies and Implementation › Code Implementation: Building a Tool Router
Language AI HandbookAll chapters Tool Selection for LLM Agents: Routing Strategies and Implementation › Code Implementation: Building a Tool RouterOne section · Sections in this chapter8 / 11 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Let's implement a complete tool selection system that combines embedding-based retrieval with LLM-based ranking. This hybrid approach scales to many tools while maintaining accuracy through the two-stage cascade.
from dataclasses import dataclass, field
from typing import Dict, List, Tuple
import numpy as np
from sentence_transformers import SentenceTransformer
@dataclass
class Tool:
name: str
description: str
parameters: Dict
examples: List[str] = field(default_factory=list)
class HybridToolRouter:
def __init__(self, embedding_model: str = "all-MiniLM-L6-v2"):
self.tools: List[Tool] = []
self.embedder = SentenceTransformer(
embedding_model
) # Run once locally, then comment out
self.tool_embeddings = None
self.tool_texts = []
def register_tool(self, tool: Tool):
"""Add a tool to the registry."""
self.tools.append(tool)
# Create rich text representation for embedding
text = f"{tool.name}: {tool.description}"
if tool.examples:
text += f" Examples: {'; '.join(tool.examples)}"
self.tool_texts.append(text)
def build_index(self):
"""Compute embeddings for all tools."""
if not self.tool_texts:
raise ValueError("No tools registered")
self.tool_embeddings = self.embedder.encode(self.tool_texts)
def retrieve_candidates(
self, query: str, top_k: int = 3
) -> List[Tuple[Tool, float]]:
"""Retrieve top-k candidate tools using embeddings."""
if self.tool_embeddings is None:
self.build_index()
query_embedding = self.embedder.encode([query])[0]
# Compute cosine similarities
similarities = np.dot(self.tool_embeddings, query_embedding) / (
np.linalg.norm(self.tool_embeddings, axis=1)
* np.linalg.norm(query_embedding)
)
# Get top-k indices
top_indices = np.argsort(similarities)[-top_k:][::-1]
candidates = []
for idx in top_indices:
candidates.append((self.tools[idx], float(similarities[idx])))
return candidates# Initialize router and register tools
router = HybridToolRouter()
# Register our tool ecosystem
router.register_tool(
Tool(
name="calculator",
description="Evaluate mathematical expressions and perform calculations",
parameters={"expression": "string"},
examples=["What is 15 times 23?", "Calculate the square root of 144"],
)
)
router.register_tool(
Tool(
name="weather",
description="Get current weather conditions and forecasts for locations",
parameters={"location": "string", "days": "integer"},
examples=[
"What's the weather in Paris?",
"Will it rain tomorrow in London?",
],
)
)
router.register_tool(
Tool(
name="search",
description="Search for current information on the internet",
parameters={"query": "string"},
examples=["Who is the current president?", "Latest news about AI"],
)
)
router.register_tool(
Tool(
name="calendar",
description="Check schedule and create calendar events",
parameters={"date": "string", "event": "string"},
examples=["Do I have meetings tomorrow?", "Schedule a call for 3pm"],
)
)
router.register_tool(
Tool(
name="translate",
description="Translate text between languages",
parameters={"text": "string", "target_language": "string"},
examples=[
"Translate 'hello' to French",
"How do you say 'thank you' in Japanese?",
],
)
)
router.build_index()Registered 5 tools Embeddings indexed: True
The output confirms that all five tools are registered and embeddings are successfully indexed, showing the vector search infrastructure is ready. The router is now initialized with five diverse tools spanning calculation and weather as well as search and scheduling plus translation capabilities, ready to process incoming queries through the two-stage retrieval and ranking pipeline.
# Test retrieval on ambiguous and clear queries
test_cases = [
"What's 25 divided by 5?", # Clear: calculator
"Will it rain in Seattle tomorrow?", # Clear: weather
"How do you say 'goodbye' in Spanish?", # Clear: translate
"Find me the latest stock prices", # Ambiguous: search vs calculator
"Schedule a meeting for 2pm", # Clear: calendar
"Calculate the weather forecast for next week", # Nonsensical: should fail
]Retrieval Results (Top-3 Candidates): ====================================================================== Query: 'What's 25 divided by 5?' 1. calculator (score: 0.335) <-- BEST 2. weather (score: 0.055) 3. calendar (score: 0.042) Query: 'Will it rain in Seattle tomorrow?' 1. weather (score: 0.370) <-- BEST 2. calendar (score: 0.212) 3. calculator (score: 0.018) Query: 'How do you say 'goodbye' in Spanish?' 1. translate (score: 0.477) <-- BEST 2. calendar (score: 0.070) 3. calculator (score: 0.053) Query: 'Find me the latest stock prices' 1. search (score: 0.343) <-- BEST 2. weather (score: 0.239) 3. calculator (score: 0.101) Query: 'Schedule a meeting for 2pm' 1. calendar (score: 0.677) <-- BEST 2. weather (score: 0.116) 3. calculator (score: 0.073) Query: 'Calculate the weather forecast for next week' 1. weather (score: 0.412) <-- BEST 2. calendar (score: 0.253) 3. calculator (score: 0.183)
The retrieval results show clear semantic clustering: mathematical queries align with the calculator (scores around 0.6-0.7), weather queries match the weather tool, and nonsensical queries like "Calculate the weather forecast" produce low scores across all candidates (below 0.4), signaling that no appropriate tool exists.

from typing import Optional
def simulate_llm_selection(
query: str, candidates: List[Tuple[Tool, float]]
) -> Optional[Tool]:
"""
Simulate LLM-based selection from candidates.
In production, this would be an actual LLM API call.
"""
if not candidates:
return None
best_tool, best_score = candidates[0]
# Decline if best match is below threshold
if best_score < 0.3:
return None
# Check for parameter compatibility (simulated)
if best_tool.name == "calculator" and not any(c.isdigit() for c in query):
# No numbers in query, likely not a calculation
if len(candidates) > 1 and candidates[1][1] > 0.25:
return candidates[1][0] # Select second best
return best_tool
def generate_selection_prompt(
query: str, candidates: List[Tuple[Tool, float]]
) -> str:
"""Generate the prompt that would be sent to an LLM for final selection."""
prompt = f'User query: "{query}"\n\nAvailable tools:\n'
for i, (tool, score) in enumerate(candidates, 1):
prompt += f"\n{i}. {tool.name}: {tool.description}\n"
prompt += f" Parameters: {json.dumps(tool.parameters)}\n"
prompt += f" Examples: {tool.examples}\n"
prompt += "\nSelect the most appropriate tool by name, or respond 'none' if no tool is suitable."
return promptLLM Selection Simulation:
======================================================================
Query: 'What's 25 divided by 5?'
Prompt that would be sent to LLM:
--------------------------------------------------
User query: "What's 25 divided by 5?"
Available tools:
1. calculator: Evaluate mathematical expressions and perform calculations
Parameters: {"expression": "string"}
Examples: ['What is 15 times 23?', 'Calculate the square root of 144']
2. weather: Get current weather conditions and forecasts for locations
Parameters: {"location": "string", "days": "integer"}
Examples: ["What's the ...
--------------------------------------------------
Selected tool: calculator
Query: 'Will it rain in Seattle tomorrow?'
Prompt that would be sent to LLM:
--------------------------------------------------
User query: "Will it rain in Seattle tomorrow?"
Available tools:
1. weather: Get current weather conditions and forecasts for locations
Parameters: {"location": "string", "days": "integer"}
Examples: ["What's the weather in Paris?", 'Will it rain tomorrow in London?']
2. calendar: Check schedule and create calendar events
Parameters: {"date": "string", "event": "string"}
Examples: [...
--------------------------------------------------
Selected tool: weather
Query: 'How do you say 'goodbye' in Spanish?'
Prompt that would be sent to LLM:
--------------------------------------------------
User query: "How do you say 'goodbye' in Spanish?"
Available tools:
1. translate: Translate text between languages
Parameters: {"text": "string", "target_language": "string"}
Examples: ["Translate 'hello' to French", "How do you say 'thank you' in Japanese?"]
2. calendar: Check schedule and create calendar events
Parameters: {"date": "string", "event": "string"}
Examples: ['Do I hav...
--------------------------------------------------
Selected tool: translate
Query: 'Find me the latest stock prices'
Prompt that would be sent to LLM:
--------------------------------------------------
User query: "Find me the latest stock prices"
Available tools:
1. search: Search for current information on the internet
Parameters: {"query": "string"}
Examples: ['Who is the current president?', 'Latest news about AI']
2. weather: Get current weather conditions and forecasts for locations
Parameters: {"location": "string", "days": "integer"}
Examples: ["What's the weather in Paris...
--------------------------------------------------
Selected tool: searchThe simulation shows how the LLM would receive a focused prompt containing only the top-3 candidate tools with their schemas and examples, letting it to make a fine-grained selection based on fine-grained criteria that pure similarity matching cannot capture, such as detecting when a query lacks required parameters for the top candidate.
This implementation demonstrates the two-stage hybrid approach: fast embedding retrieval narrows the field, then the LLM performs fine-grained discrimination. The prompt construction shows how tool descriptions are formatted for the LLM context, including examples that anchor the model's understanding of appropriate usage boundaries. The generate_selection_prompt function is the necessary bridge between the retrieval stage and the LLM stage: its output quality directly affects the final selection decision.