Skip to main content
Lesson 13 of 14 · Function Calling with Grok

Multi-Tool Pipeline

Lesson 13 of 14 Updated July 29, 2026 Developer
On this page
  1. Orchestrating tools in sequence
  2. Pipeline architecture
  3. Example: competitive intelligence pipeline
  4. Pipeline execution
  5. Combining server + client tools in a pipeline
  6. Pattern: fan-out / fan-in
  7. Handling failures in a pipeline
  8. Key takeaways

Orchestrating tools in sequence

The weather agent from the previous lesson used independent tools. In a multi-tool pipeline, the results from one tool feed the next call. The model orchestrates the sequence automatically—your role is to provide the tools and execute the calls.

Pipeline architecture

A typical pipeline follows this schema:

Search → Analysis → Action → Notification

Each step produces data consumed by the next. The model handles this orchestration naturally thanks to the conversation context.

Example: competitive intelligence pipeline

Imagine a pipeline that monitors competitor prices and generates a report:

tools = [
    {
        "type": "function",
        "function": {
            "name": "scrape_competitor_prices",
            "description": "Retrieve current prices from a competitor "
                          "for a list of products. Returns an array "
                          "of {product, price, availability}.",
            "parameters": {
                "type": "object",
                "properties": {
                    "competitor": {
                        "type": "string",
                        "description": "Competitor name"
                    },
                    "products": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "List of products to check"
                    }
                },
                "required": ["competitor", "products"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "compare_with_our_prices",
            "description": "Compare competitor prices with our prices. "
                          "Returns percentage gaps and adjustment "
                          "recommendations.",
            "parameters": {
                "type": "object",
                "properties": {
                    "competitor_prices": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "product": {"type": "string"},
                                "price": {"type": "number"}
                            }
                        }
                    }
                },
                "required": ["competitor_prices"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "generate_report",
            "description": "Generate a PDF report with the benchmark "
                          "analysis results. Returns the report URL.",
            "parameters": {
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "data": {"type": "object"},
                    "format": {
                        "type": "string",
                        "enum": ["pdf", "csv", "json"]
                    }
                },
                "required": ["title", "data"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "send_notification",
            "description": "Send an email or Slack notification "
                          "with a summary and a link to the report.",
            "parameters": {
                "type": "object",
                "properties": {
                    "channel": {
                        "type": "string",
                        "enum": ["email", "slack"]
                    },
                    "recipient": {"type": "string"},
                    "message": {"type": "string"},
                    "attachment_url": {"type": "string"}
                },
                "required": ["channel", "recipient", "message"]
            }
        }
    }
]

Pipeline execution

When the user asks, “Analyze CompetitorX prices for our top 5 products and send the report to the sales team,” the model orchestrates:

Turn 1 : scrape_competitor_prices(competitor="CompetitorX", products=[...])
Turn 2 : compare_with_our_prices(competitor_prices=[results from turn 1])
Turn 3 : generate_report(title="CompetitorX Price Monitoring", data={...})
Turn 4 : send_notification(channel="slack", recipient="#sales", ...)

Each turn uses the results from the previous one. The model naturally passes data from one tool to the next.

Combining server + client tools in a pipeline

The most powerful pipelines combine server and client tools:

tools = [
    {"type": "web_search"},  # Server: web search
    {
        "type": "function",
        "function": {
            "name": "extract_data",
            "description": "Extract structured data from raw text",
            "parameters": { ... }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "save_to_crm",
            "description": "Save extracted data to the CRM",
            "parameters": { ... }
        }
    }
]

Flow: the model performs a web search (automatic on the server side), then calls your extraction function (client side), then saves to the CRM (client side).

Pattern: fan-out / fan-in

Some pipelines require a fan-out pattern (multiple parallel calls) followed by a fan-in (aggregation of results):

         ┌→ scrape_site_A ─┐
Request ─┤→ scrape_site_B ─┤→ aggregate_results → generate_report
         └→ scrape_site_C ─┘

The model handles this naturally with parallel calls:

Turn 1 : [scrape_site_A, scrape_site_B, scrape_site_C]  (parallel)
Turn 2 : aggregate_results(data=[result_A, result_B, result_C])
Turn 3 : generate_report(aggregated_data={...})

Handling failures in a pipeline

A robust pipeline handles failures at every step:

def execute_with_retry(name, args, max_retries=2):
    for attempt in range(max_retries + 1):
        try:
            result = execute_function(name, args)
            if "error" not in result:
                return result
        except Exception:
            pass

        if attempt < max_retries:
            time.sleep(1)  # Wait before retry

    return {
        "error": f"Failed after {max_retries + 1} attempts",
        "function": name,
        "partial_data": None
    }

When a step fails, the model can:

  • Retry with adjusted parameters
  • Skip the step and continue with partial data
  • Inform the user and request instructions

Key takeaways

  • A multi-tool pipeline chains the results from one tool to the next
  • The model orchestrates the sequence automatically thanks to the context
  • The fan-out/fan-in pattern combines parallel calls and aggregation
  • Combine server tools (web_search) and client tools (your functions) for hybrid pipelines
  • Handle failures at every step with retry and fallback
  • Each tool in the pipeline must have a clear, single responsibility