Tool Use and Function Calling Best Practices
Reviewed: June 4, 2026
Published: May 26, 2026 | Tool Use Function Calling Agent Architecture
Tool use is what separates AI agents from chatbots. But poorly designed tool interfaces are the #1 source of agent failures in production. This guide covers patterns that work.
Tool Design Principles
# ❌ BAD: Vague tool description, no error handling
{
"name": "search",
"description": "Search for stuff",
"parameters": {"query": "what to search for"}
}
# ✅ GOOD: Specific description, typed parameters, clear constraints
{
"name": "search_knowledge_base",
"description": "Search the company knowledge base for documentation, FAQs, and guides. Returns top 5 relevant passages with source URLs. Use for: product questions, troubleshooting, policy lookups. NOT for: real-time data, user account info.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query (2-20 words). Be specific.",
"minLength": 5,
"maxLength": 200
},
"category": {
"type": "string",
"enum": ["docs", "faq", "policy", "all"],
"description": "Filter by content type"
},
"max_results": {
"type": "integer",
"minimum": 1,
"maximum": 10,
"default": 5
}
},
"required": ["query"]
}
}
Error Handling Patterns
class RobustToolCaller:
"""Handle tool failures gracefully."""
def call_with_retry(self, tool_name: str, params: dict,
max_retries: int = 3) -> dict:
for attempt in range(max_retries):
try:
result = self.execute_tool(tool_name, params)
# Validate result structure
if not self._validate_result(tool_name, result):
raise ToolResultError(f"Invalid result from {tool_name}")
return {"success": True, "result": result}
except RateLimitError:
wait = 2 ** attempt
time.sleep(wait)
continue
except ToolResultError as e:
# Don't retry on validation errors
return {"success": False, "error": str(e), "retryable": False}
except Exception as e:
if attempt == max_retries - 1:
return {"success": False, "error": str(e), "retryable": True}
continue
def parallel_call(self, calls: list[dict]) -> list[dict]:
"""Execute independent tool calls in parallel."""
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
futures = {
executor.submit(self.call_with_retry, c["tool"], c["params"]): c
for c in calls
}
results = []
for future in concurrent.futures.as_completed(futures):
call = futures[future]
try:
result = future.result(timeout=30)
results.append({"call": call, **result})
except concurrent.futures.TimeoutError:
results.append({
"call": call,
"success": False,
"error": "Timeout after 30s"
})
return results
Tool Selection Strategy
class ToolSelector:
"""Intelligent tool selection to reduce token waste."""
def __init__(self, available_tools: list[dict]):
self.tools = {t["name"]: t for t in available_tools}
# Pre-compute tool embeddings for semantic matching
self.tool_embeddings = self._embed_tools(available_tools)
def select_tools(self, task: str, max_tools: int = 3) -> list[str]:
"""Select the most relevant tools for a task."""
task_embedding = self._embed(task)
scores = []
for name, emb in self.tool_embeddings.items():
similarity = self._cosine_sim(task_embedding, emb)
scores.append((name, similarity))
scores.sort(key=lambda x: x[1], reverse=True)
return [name for name, _ in scores[:max_tools]]
def get_minimal_tool_set(self, plan: list[str]) -> list[dict]:
"""Return only the tools needed for a specific plan."""
needed = set()
for step in plan:
# Extract tool mentions from plan steps
for tool_name in self.tools:
if tool_name in step.lower():
needed.add(tool_name)
return [self.tools[name] for name in needed]
Best Practices Checklist
- Tool descriptions should be specific about what the tool does AND doesn’t do
- Always include parameter constraints (min/max, enums, patterns)
- Return structured errors, not free-text error messages
- Implement timeouts on every tool call
- Cache idempotent tool results to save tokens
- Log every tool call with inputs, outputs, and latency
- Version your tool schemas — don’t break existing agents
Part of the Agent Architecture series on DataGate.ch.
