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.