AI Agent Tool Use Patterns: 7 Architectures That Actually Work in Production

March 9, 2026 ยท 14 min read AI Agents Architecture

Every AI agent needs tools. Without them, your agent is just a chatbot โ€” eloquent but powerless. The difference between a demo and a product is how well your agent uses its tools.

After working with hundreds of agent developers, we've identified seven tool use patterns that consistently work in production. Each solves a different problem, and knowing when to use which pattern is the difference between an agent that works and one that frustrates users.

Pattern 1: Single-Shot Tool Call

When to use: Simple, deterministic tasks. One input โ†’ one tool โ†’ one output.

Example: "What's on this webpage?"

import openai

client = openai.OpenAI()

tools = [
    {
        "type": "function",
        "function": {
            "name": "scrape_url",
            "description": "Scrape a webpage and return its content",
            "parameters": {
                "type": "object",
                "properties": {
                    "url": {"type": "string", "description": "URL to scrape"},
                    "format": {"type": "string", "enum": ["text", "markdown"]}
                },
                "required": ["url"]
            }
        }
    }
]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Summarize https://example.com/blog/post"}],
    tools=tools,
)

Pros: Simple, fast, predictable cost.
Cons: Can't handle multi-step tasks.

Pattern 2: ReAct Loop (Reason + Act)

When to use: Tasks that require multiple steps, where each step depends on previous results.

def react_loop(agent, task, max_steps=10):
    messages = [{"role": "user", "content": task}]

    for step in range(max_steps):
        response = agent.chat(messages, tools=TOOLS)

        if response.finish_reason == "stop":
            return response.content  # Agent is done

        # Agent wants to use a tool
        tool_call = response.tool_calls[0]
        result = execute_tool(tool_call)

        messages.append(response.message)
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": result
        })

    return "Max steps reached"

The agent reasons about what to do, acts (calls a tool), observes the result, then reasons again. This is the most common pattern in production agents.

Pros: Flexible, handles complex tasks.
Cons: Unpredictable number of steps (and cost). Can loop.

Pattern 3: Parallel Tool Calls

When to use: Independent data gathering from multiple sources.

# Modern LLMs support parallel tool calls natively
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": "Compare pricing: ScrapingBee vs Bright Data vs WebPerception"
    }],
    tools=tools,
    parallel_tool_calls=True,
)

# Agent returns 3 tool calls simultaneously โ€” execute in parallel
import asyncio

async def execute_parallel(tool_calls):
    tasks = [execute_tool_async(tc) for tc in tool_calls]
    return await asyncio.gather(*tasks)

Pros: 3x faster than sequential. Same cost.
Cons: Only works for independent operations.

Pattern 4: Tool Chaining (Pipeline)

When to use: Multi-stage data processing where each stage transforms the output.

# Stage 1: Search
search_results = scrape_url("https://news.ycombinator.com/item?id=hiring")

# Stage 2: Extract structured data
companies = extract_data(
    url="https://news.ycombinator.com/item?id=hiring",
    prompt="Extract company names, roles, locations, and salary ranges"
)

# Stage 3: Enrich each company
for company in companies:
    details = scrape_url(company["website"])
    company["about"] = extract_data(
        url=company["website"],
        prompt="Extract company size, funding, tech stack"
    )

# Stage 4: Score and rank
ranked = score_companies(companies, criteria=user_preferences)

Pros: Clear data flow, each stage is testable.
Cons: Brittle โ€” if stage 2 fails, everything downstream fails.

Pattern 5: Fallback Chain

When to use: When primary tools might fail and you need alternatives.

async def get_stock_price(symbol: str) -> dict:
    # Try primary source
    try:
        price = await extract_data(
            url=f"https://finance.yahoo.com/quote/{symbol}",
            prompt=f"Extract current stock price for {symbol}"
        )
        if price:
            return {"source": "yahoo", "price": price}
    except Exception:
        pass

    # Fallback to alternative
    try:
        price = await extract_data(
            url=f"https://www.google.com/finance/quote/{symbol}:NASDAQ",
            prompt=f"Extract current stock price for {symbol}"
        )
        if price:
            return {"source": "google", "price": price}
    except Exception:
        pass

    return {"error": "All sources failed"}

Pros: Resilient. Production-ready.
Cons: More API calls on failure.

Pattern 6: Human-in-the-Loop

When to use: High-stakes actions where the agent should confirm before proceeding.

class MonitoringAgent:
    async def check_prices(self):
        price_data = await extract_data(
            url="https://competitor.com/pricing",
            prompt="Extract the base plan monthly price"
        )

        price = float(price_data["price"])

        if price < self.alert_threshold:
            screenshot = await screenshot_url("https://competitor.com/pricing")

            # Human-in-the-loop: alert, don't act
            await notify_human(
                f"โš ๏ธ Competitor dropped to ${price}!",
                screenshot=screenshot
            )
            # Agent STOPS here โ€” human decides

Pros: Safety for high-stakes decisions. Builds trust.
Cons: Latency โ€” human needs to respond.

Pattern 7: Multi-Agent Delegation

When to use: Complex tasks that benefit from specialized agents working together.

from crewai import Agent, Task, Crew

researcher = Agent(
    role="Research Analyst",
    tools=[scrape_url, extract_data],
    goal="Gather comprehensive data on target companies"
)

analyst = Agent(
    role="Market Analyst",
    tools=[extract_data],
    goal="Analyze market positioning and competitive dynamics"
)

writer = Agent(
    role="Report Writer",
    tools=[screenshot_url],
    goal="Produce a clear, data-backed market report"
)

crew = Crew(
    agents=[researcher, analyst, writer],
    tasks=[
        Task(description="Research top 5 AI startups", agent=researcher),
        Task(description="Analyze competitive positioning", agent=analyst),
        Task(description="Write market report with screenshots", agent=writer),
    ],
)

report = crew.kickoff()

Pros: Separation of concerns. Each agent is focused and testable.
Cons: Complex orchestration. Higher cost. Debugging is harder.

Choosing the Right Pattern

PatternBest ForComplexityCost
Single-ShotSimple lookupsLow$
ReAct LoopMulti-step researchMedium$$
ParallelIndependent data gatheringMedium$
PipelineData processing workflowsMedium$$
FallbackProduction reliabilityMedium$-$$
Human-in-LoopHigh-stakes decisionsHigh$$
Multi-AgentComplex analysisHigh$$$

Key Takeaways

  1. Start with Pattern 1 (single-shot) and add complexity only when needed
  2. Pattern 2 (ReAct) is your default for most production agents
  3. Always implement Pattern 5 (fallback) in production โ€” sites go down
  4. Pattern 6 (human-in-loop) for anything involving money or public communication
  5. Pattern 7 (multi-agent) only when you genuinely need specialization

The best agents aren't the most complex โ€” they're the ones that use the right pattern for the job.

Give Your Agent Eyes on the Web

Scrape, screenshot, and extract structured data from any URL. 100 free calls/month.

Get Your API Key โ†’