Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,53 @@ for chunk in stream:
print(chunk.decode('utf-8'))
```

## Advanced: Custom Session / Client Injection

For enterprise environments that proxy Tavily traffic through an API gateway (e.g., for centralized auth, logging, or policy enforcement), you can pass a pre-configured HTTP session instead of a Tavily API key.

### Sync (custom `requests.Session`)

```python
import requests
from tavily import TavilyClient

# Pre-configure a session with your gateway's auth
session = requests.Session()
session.headers["Authorization"] = "Bearer your-gateway-token"
session.headers["X-Subscription-Key"] = "your-subscription-key"

# No Tavily API key needed — auth is handled by the session
client = TavilyClient(
session=session,
api_base_url="https://your-gateway.com/tavily",
)

response = client.search("latest AI research")
```

### Async (custom `httpx.AsyncClient`)

```python
import httpx
from tavily import AsyncTavilyClient

# Pre-configure an async client with your gateway's auth
custom_client = httpx.AsyncClient(
headers={"Authorization": "Bearer your-gateway-token"},
base_url="https://your-gateway.com/tavily",
)

client = AsyncTavilyClient(client=custom_client)

response = await client.search("latest AI research")
```

**Key behaviors:**
- If a custom session/client is provided, `api_key` is optional
- Custom session headers take precedence over SDK defaults (e.g., your `Authorization` won't be overwritten)
- Custom session proxies take precedence over SDK proxy settings
- The SDK will **not** close externally-provided sessions — you manage the lifecycle

## Documentation

For a complete guide on how to use the different endpoints and their parameters, please head to our [Python API Reference](https://docs.tavily.com/sdk/python/reference).
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='tavily-python',
version='0.7.22',
version='0.7.23',
url='https://github.com/tavily-ai/tavily-python',
author='Tavily AI',
author_email='[email protected]',
Expand Down
69 changes: 42 additions & 27 deletions tavily/async_tavily.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,63 @@ def __init__(self, api_key: Optional[str] = None,
proxies: Optional[dict[str, str]] = None,
api_base_url: Optional[str] = None,
client_source: Optional[str] = None,
project_id: Optional[str] = None):
project_id: Optional[str] = None,
client: Optional[httpx.AsyncClient] = None):
if api_key is None:
api_key = os.getenv("TAVILY_API_KEY")

if not api_key:
if not api_key and client is None:
raise MissingAPIKeyError()

proxies = proxies or {}
tavily_project = project_id or os.getenv("TAVILY_PROJECT")

mapped_proxies = {
"http://": proxies.get("http", os.getenv("TAVILY_HTTP_PROXY")),
"https://": proxies.get("https", os.getenv("TAVILY_HTTPS_PROXY")),
self._api_base_url = api_base_url or "https://api.tavily.com"
self._company_info_tags = company_info_tags

default_headers = {
"Content-Type": "application/json",
**({"Authorization": f"Bearer {api_key}"} if api_key else {}),
"X-Client-Source": client_source or "tavily-python",
**({"X-Project-ID": tavily_project} if tavily_project else {})
}

mapped_proxies = {key: value for key, value in mapped_proxies.items() if value}
self._external_client = client is not None

if client is not None:
self._client = client
# Only set headers that aren't already configured on the external client
for key, value in default_headers.items():
if key not in self._client.headers:
self._client.headers[key] = value
# Set base_url if the external client doesn't have one
if not str(self._client.base_url):
self._client.base_url = self._api_base_url
else:
proxies = proxies or {}

proxy_mounts = (
{scheme: httpx.AsyncHTTPTransport(proxy=proxy) for scheme, proxy in mapped_proxies.items()}
if mapped_proxies
else None
)
mapped_proxies = {
"http://": proxies.get("http", os.getenv("TAVILY_HTTP_PROXY")),
"https://": proxies.get("https", os.getenv("TAVILY_HTTPS_PROXY")),
}

tavily_project = project_id or os.getenv("TAVILY_PROJECT")
mapped_proxies = {key: value for key, value in mapped_proxies.items() if value}

self._api_base_url = api_base_url or "https://api.tavily.com"
proxy_mounts = (
{scheme: httpx.AsyncHTTPTransport(proxy=proxy) for scheme, proxy in mapped_proxies.items()}
if mapped_proxies
else None
)

# Create a persistent client for connection pooling
self._client = httpx.AsyncClient(
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
"X-Client-Source": client_source or "tavily-python",
**({"X-Project-ID": tavily_project} if tavily_project else {})
},
base_url=self._api_base_url,
mounts=proxy_mounts
)
self._company_info_tags = company_info_tags
self._client = httpx.AsyncClient(
headers=default_headers,
base_url=self._api_base_url,
mounts=proxy_mounts
)

async def close(self):
"""Close the client and release connection pool resources."""
await self._client.aclose()
if not self._external_client:
await self._client.aclose()

async def __aenter__(self):
return self
Expand Down
9 changes: 6 additions & 3 deletions tavily/hybrid_rag/hybrid_rag.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from typing import Union, Optional, Literal

import requests
from tavily import TavilyClient

try:
Expand Down Expand Up @@ -76,7 +77,8 @@ def __init__(
embeddings_field: str = 'embeddings',
content_field: str = 'content',
embedding_function: Optional[callable] = None,
ranking_function: Optional[callable] = None
ranking_function: Optional[callable] = None,
session: Optional[requests.Session] = None
):
'''
A client for performing hybrid RAG using both the Tavily API and a local database collection.
Expand All @@ -90,9 +92,10 @@ def __init__(
content_field (str): The name of the field in the collection that contains the content.
embedding_function (callable): If provided, this function will be used to generate embeddings for the search query and documents.
ranking_function (callable): If provided, this function will be used to rerank the combined results.
session (requests.Session): If provided, this pre-configured session will be used for HTTP requests. When set, api_key is optional.
'''
self.tavily = TavilyClient(api_key)

self.tavily = TavilyClient(api_key, session=session)

if db_provider != 'mongodb':
raise ValueError("Only MongoDB is currently supported as a database provider.")
Expand Down
21 changes: 14 additions & 7 deletions tavily/tavily.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ class TavilyClient:
Tavily API client class.
"""

def __init__(self, api_key: Optional[str] = None, proxies: Optional[dict[str, str]] = None, api_base_url: Optional[str] = None, client_source: Optional[str] = None, project_id: Optional[str] = None):
def __init__(self, api_key: Optional[str] = None, proxies: Optional[dict[str, str]] = None, api_base_url: Optional[str] = None, client_source: Optional[str] = None, project_id: Optional[str] = None, session: Optional[requests.Session] = None):
if api_key is None:
api_key = os.getenv("TAVILY_API_KEY")

if not api_key:
if not api_key and session is None:
raise MissingAPIKeyError()

resolved_proxies = {
Expand All @@ -32,19 +32,26 @@ def __init__(self, api_key: Optional[str] = None, proxies: Optional[dict[str, st

self.headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}",
**({"Authorization": f"Bearer {self.api_key}"} if self.api_key else {}),
"X-Client-Source": client_source or "tavily-python",
**({"X-Project-ID": tavily_project} if tavily_project else {})
}

self.session = requests.Session()
self.session.headers.update(self.headers)
self._external_session = session is not None
self.session = session if session is not None else requests.Session()
# For external sessions, only set headers that aren't already configured
for key, value in self.headers.items():
if key not in self.session.headers:
self.session.headers[key] = value
if self.proxies:
self.session.proxies.update(self.proxies)
for protocol, url in self.proxies.items():
if protocol not in self.session.proxies:
self.session.proxies[protocol] = url

def close(self):
"""Close the session and release resources."""
self.session.close()
if not self._external_session:
self.session.close()

def __enter__(self):
return self
Expand Down
Loading