_CORE
AI & Agentic Systems Core Information Systems Cloud & Platform Engineering Data Platform & Integration Security & Compliance QA, Testing & Observability IoT, Automation & Robotics Mobile & Digital Banking & Finance Insurance Public Administration Defense & Security Healthcare Energy & Utilities Telco & Media Manufacturing Logistics & E-commerce Retail & Loyalty
References Technologies Blog Know-how Tools
About Collaboration Careers
CS EN
Let's talk

Tool Use Patterns

10. 06. 2023 4 min read intermediate

Large language models and AI agents revolutionize how we interact with tools and automate complex tasks. Proper understanding of their usage patterns is key to maximizing efficiency and minimizing errors.

Tool Use Patterns: Effective Tool Utilization in LLM Agents

Modern language models have gained the ability to use external tools – from API calls through database queries to complex system operations. The key to success isn’t just technical implementation, but especially proper design patterns that ensure reliability and efficiency of the entire system.

ReAct Pattern: Fundamental Building Block

ReAct (Reasoning + Acting) represents a fundamental pattern for tool use. The model proceeds in cycles: analyzes the situation, decides on action, executes it, and evaluates the result. This pattern ensures transparency of decision processes and enables debugging.

class ReActAgent:
    def __init__(self, llm, tools):
        self.llm = llm
        self.tools = {tool.name: tool for tool in tools}

    def run(self, task):
        context = f"Task: {task}\n"
        max_iterations = 10

        for i in range(max_iterations):
            # Reasoning phase
            response = self.llm.generate(
                f"{context}\nWhat should I do next? Think step by step."
            )

            if "FINAL_ANSWER:" in response:
                return self.extract_final_answer(response)

            # Acting phase
            if "ACTION:" in response:
                action, args = self.parse_action(response)
                result = self.tools[action].execute(args)
                context += f"Action: {action}({args})\nResult: {result}\n"

        return "Max iterations reached"

Chain of Tools Pattern

For more complex tasks, we need to chain multiple tools together. Chain of Tools pattern defines explicit operation order and data passing between individual steps.

class ToolChain:
    def __init__(self, steps):
        self.steps = steps

    async def execute(self, initial_data):
        current_data = initial_data

        for step in self.steps:
            try:
                current_data = await step.process(current_data)
                self.log_step(step.name, current_data)
            except Exception as e:
                return self.handle_chain_failure(step, e, current_data)

        return current_data

# Usage for data pipeline
pipeline = ToolChain([
    DatabaseQuery("SELECT * FROM users WHERE active=1"),
    DataTransform("normalize_emails"),
    APICall("send_newsletter"),
    LogResults("newsletter_campaign")
])

result = await pipeline.execute({"campaign_id": "2024-01"})

Tool Selection Pattern

When we have multiple tools available for the same task type, we need an intelligent selection mechanism. Tool Selection pattern combines tool metadata with contextual model decision-making.

class ToolSelector:
    def __init__(self, tools):
        self.tools = tools
        self.usage_stats = defaultdict(lambda: {"success": 0, "failure": 0})

    def select_tool(self, task_description, context):
        # Get candidates by capability
        candidates = [t for t in self.tools if t.can_handle(task_description)]

        if len(candidates) == 1:
            return candidates[0]

        # LLM decision with metrics
        tool_info = []
        for tool in candidates:
            stats = self.usage_stats[tool.name]
            success_rate = stats["success"] / (stats["success"] + stats["failure"] + 1)

            tool_info.append({
                "name": tool.name,
                "description": tool.description,
                "success_rate": success_rate,
                "avg_latency": tool.avg_latency
            })

        selection_prompt = f"""
        Task: {task_description}
        Context: {context}

        Available tools:
        {json.dumps(tool_info, indent=2)}

        Select the most appropriate tool and explain why.
        """

        response = self.llm.generate(selection_prompt)
        return self.parse_tool_selection(response, candidates)

Error Recovery Pattern

Robust tool use systems must elegantly handle errors. Error Recovery pattern implements hierarchy of fallback strategies – from simple retry through alternative tools to graceful degradation.

class ResilientToolExecutor:
    def __init__(self, primary_tool, fallback_tools=None):
        self.primary_tool = primary_tool
        self.fallback_tools = fallback_tools or []
        self.retry_config = {"max_attempts": 3, "backoff": 2}

    async def execute_with_recovery(self, args):
        # Attempt with primary tool
        for attempt in range(self.retry_config["max_attempts"]):
            try:
                result = await self.primary_tool.execute(args)
                return {"success": True, "result": result, "tool_used": self.primary_tool.name}

            except RetryableError as e:
                if attempt < self.retry_config["max_attempts"] - 1:
                    await asyncio.sleep(self.retry_config["backoff"] ** attempt)
                    continue

            except NonRetryableError:
                break

        # Fallback tools
        for fallback_tool in self.fallback_tools:
            if fallback_tool.can_substitute(self.primary_tool):
                try:
                    adapted_args = self.adapt_args(args, fallback_tool)
                    result = await fallback_tool.execute(adapted_args)
                    return {
                        "success": True, 
                        "result": result, 
                        "tool_used": fallback_tool.name,
                        "fallback": True
                    }
                except Exception:
                    continue

        return {"success": False, "error": "All recovery attempts failed"}

Parallel Tool Execution

For performance-critical applications, parallelization is key. This pattern enables concurrent execution of independent tools with intelligent resource management and dependency handling.

class ParallelToolManager:
    def __init__(self, max_concurrency=5):
        self.max_concurrency = max_concurrency
        self.semaphore = asyncio.Semaphore(max_concurrency)

    async def execute_parallel(self, tool_tasks):
        # Split by dependencies
        independent_tasks = []
        dependent_tasks = []

        for task in tool_tasks:
            if task.dependencies:
                dependent_tasks.append(task)
            else:
                independent_tasks.append(task)

        # Parallel execution of independent tasks
        results = {}
        independent_results = await asyncio.gather(*[
            self._execute_with_semaphore(task) for task in independent_tasks
        ], return_exceptions=True)

        # Update results
        for task, result in zip(independent_tasks, independent_results):
            results[task.id] = result

        # Sequential processing of dependent tasks
        for task in dependent_tasks:
            if self._dependencies_satisfied(task, results):
                result = await self._execute_with_semaphore(task)
                results[task.id] = result

        return results

    async def _execute_with_semaphore(self, task):
        async with self.semaphore:
            return await task.tool.execute(task.args)

Practical Implementation Tips

When implementing tool use patterns, we recommend:

  • Monitoring and logging: Log all tool calls including latency and error rates
  • Tool versioning: Implement versioning for backward compatibility
  • Resource management: Use connection pooling and rate limiting
  • Testing: Mock external tools for unit tests
  • Security: Validate all inputs and implement sandboxing

Summary

Tool Use Patterns represent a critical component of modern LLM applications. ReAct pattern provides foundation for reasoning, while advanced patterns like Chain of Tools, Tool Selection and Error Recovery ensure robustness and scalability. Proper implementation of these patterns, combined with thorough monitoring and testing, creates reliable systems capable of handling complex real-world tasks. The key to success is balancing flexibility with predictability and always having a plan B for situations where primary tools fail.

tool usereactpatterns
Share:

CORE SYSTEMS tým

Stavíme core systémy a AI agenty, které drží provoz. 15 let zkušeností s enterprise IT.