Session Pooling and Connection Reuse

When making HTTP requests, a significant portion of the latency is consumed by establishing the TCP connection and negotiating the SSL/TLS handshake. If your application makes multiple sequential requests to the Tavily API, establishing a brand new connection for every single query is highly inefficient.

The Tavily Python SDK utilizes Session Pooling to solve this. Under the hood, TavilyClient wraps a requests.Session() object, and AsyncTavilyClient wraps an httpx.AsyncClient(). These underlying objects maintain a pool of active, keep-alive TCP connections to Tavily's servers.

Why Context Managers Matter

To ensure that connection pools are properly utilized and that network resources (sockets) are released back to the operating system when you are done, it is highly recommended to use Python's context managers (with and async with).

When you use a context manager, the client is cleanly initialized upon entry and securely closed upon exit, even if an exception occurs within the block.

Synchronous Session Pooling

from tavily import TavilyClient
import time

queries = ["Apple stock", "Microsoft stock", "Tesla stock"]

# The context manager opens the session
with TavilyClient() as client:
    start_time = time.time()

    for query in queries:
        # The 1st request negotiates TLS.
        # The 2nd and 3rd requests reuse the exact same TCP socket, 
        # resulting in significantly lower latency.
        response = client.search(query)
        print(f"Searched: {query}")

    print(f"Total time: {time.time() - start_time:.2f} seconds")
# The context manager automatically calls client.close() here

Manual Management

If your application architecture (like a long-running class instance) prevents the use of context managers, you must manually manage the lifecycle by calling .close() when your application shuts down.

class MyAIAgent:
    def __init__(self):
        # Open the session pool
        self.tavily = TavilyClient()

    def do_work(self, query):
        return self.tavily.search(query)

    def shutdown(self):
        # Explicitly release resources
        self.tavily.close()

Asynchronous Context Managers

Asynchronous applications are particularly sensitive to connection leaks. Always use async with when managing short-lived AsyncTavilyClient instances.

import asyncio
from tavily import AsyncTavilyClient

async def pipeline(queries):
    # Note the 'async with' syntax
    async with AsyncTavilyClient() as client:
        # All these tasks share the same underlying httpx connection pool
        tasks = [client.search(q) for q in queries]
        results = await asyncio.gather(*tasks)
        return results

Best Practices for Web Servers

If you are deploying a web server (e.g., FastAPI, Django) that handles thousands of requests per minute, do not instantiate a new client inside every route handler.

Instead, create a single global TavilyClient (or AsyncTavilyClient) when the server starts up, and share that single instance across all incoming web requests. This maximizes the efficiency of the connection pool.

# FastAPI Example
from fastapi import FastAPI
from tavily import AsyncTavilyClient
from contextlib import asynccontextmanager

# Global client reference
tavily_client = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: Initialize the global client pool
    global tavily_client
    tavily_client = AsyncTavilyClient()
    yield
    # Shutdown: Close the global client pool
    await tavily_client.close()

app = FastAPI(lifespan=lifespan)

@app.get("/search")
async def search_endpoint(query: str):
    # Reuse the global client pool for incredibly fast response times
    return await tavily_client.search(query)