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)