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

Part of the Agent Architecture series on DataGate.ch.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert