Multi-Tool Pipeline
On this page
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