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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ response = tavily_client.search("Who is Leo Messi?")
print(response)
```

### Using exact match to find specific names or phrases

```python
from tavily import TavilyClient

client = TavilyClient(api_key="tvly-YOUR_API_KEY")

# Use exact_match=True to only return results containing the exact phrase(s) inside quotes
response = client.search(
query='"John Smith" CEO Acme Corp',
exact_match=True
)
print(response)
```

This is equivalent to directly querying our REST API.

### Generating context for a RAG Application
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.21',
version='0.7.22',
url='https://github.com/tavily-ai/tavily-python',
author='Tavily AI',
author_email='support@tavily.com',
Expand Down
4 changes: 4 additions & 0 deletions tavily/async_tavily.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ async def _search(
auto_parameters: bool = None,
include_favicon: bool = None,
include_usage: bool = None,
exact_match: bool = None,
**kwargs,
) -> dict:
"""
Expand All @@ -111,6 +112,7 @@ async def _search(
"auto_parameters": auto_parameters,
"include_favicon": include_favicon,
"include_usage": include_usage,
"exact_match": exact_match,
}

data = {k: v for k, v in data.items() if v is not None}
Expand Down Expand Up @@ -164,6 +166,7 @@ async def search(self,
auto_parameters: bool = None,
include_favicon: bool = None,
include_usage: bool = None,
exact_match: bool = None,
**kwargs, # Accept custom arguments
) -> dict:
"""
Expand All @@ -188,6 +191,7 @@ async def search(self,
auto_parameters=auto_parameters,
include_favicon=include_favicon,
include_usage=include_usage,
exact_match=exact_match,
**kwargs,
)

Expand Down
4 changes: 4 additions & 0 deletions tavily/tavily.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def _search(self,
auto_parameters: bool = None,
include_favicon: bool = None,
include_usage: bool = None,
exact_match: bool = None,
**kwargs
) -> dict:
"""
Expand All @@ -95,6 +96,7 @@ def _search(self,
"auto_parameters": auto_parameters,
"include_favicon": include_favicon,
"include_usage": include_usage,
"exact_match": exact_match,
}

data = {k: v for k, v in data.items() if v is not None}
Expand Down Expand Up @@ -151,6 +153,7 @@ def search(self,
auto_parameters: bool = None,
include_favicon: bool = None,
include_usage: bool = None,
exact_match: bool = None,
**kwargs, # Accept custom arguments
) -> dict:
"""
Expand All @@ -175,6 +178,7 @@ def search(self,
auto_parameters=auto_parameters,
include_favicon=include_favicon,
include_usage=include_usage,
exact_match=exact_match,
**kwargs)
response_dict.setdefault("results", [])
return response_dict
Expand Down
59 changes: 57 additions & 2 deletions tests/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def validate_specific(request, response):
assert request.headers["Authorization"] == "Bearer tvly-test"
assert request.headers["X-Client-Source"] == "tavily-python"
assert request.timeout == 10

request_json = request.json()
for key, value in {
"query": "What is Tavily?",
Expand All @@ -44,7 +44,8 @@ def validate_specific(request, response):
"exclude_domains": ["example.com"],
"include_answer": "advanced",
"include_raw_content": True,
"include_images": True
"include_images": True,
"exact_match": True
}.items():
assert request_json.get(key) == value

Expand All @@ -69,6 +70,7 @@ def test_sync_search_specific(sync_interceptor, sync_client):
include_answer="advanced",
include_raw_content=True,
include_images=True,
exact_match=True,
timeout=10
)

Expand All @@ -94,8 +96,61 @@ def test_async_search_specific(async_interceptor, async_client):
include_answer="advanced",
include_raw_content=True,
include_images=True,
exact_match=True,
timeout=10
))

request = async_interceptor.get_request()
validate_specific(request, response)

def test_sync_search_exact_match_not_sent_by_default(sync_interceptor, sync_client):
sync_interceptor.set_response(200, json=dummy_response)
sync_client.search("What is Tavily?")
request = sync_interceptor.get_request()
assert "exact_match" not in request.json()

def test_sync_search_exact_match_true(sync_interceptor, sync_client):
sync_interceptor.set_response(200, json=dummy_response)
sync_client.search("What is Tavily?", exact_match=True)
request = sync_interceptor.get_request()
assert request.json()["exact_match"] is True

def test_sync_search_exact_match_false(sync_interceptor, sync_client):
sync_interceptor.set_response(200, json=dummy_response)
sync_client.search("What is Tavily?", exact_match=False)
request = sync_interceptor.get_request()
assert request.json()["exact_match"] is False

def test_async_search_exact_match_not_sent_by_default(async_interceptor, async_client):
async_interceptor.set_response(200, json=dummy_response)
asyncio.run(async_client.search("What is Tavily?"))
request = async_interceptor.get_request()
assert "exact_match" not in request.json()

def test_async_search_exact_match_true(async_interceptor, async_client):
async_interceptor.set_response(200, json=dummy_response)
asyncio.run(async_client.search("What is Tavily?", exact_match=True))
request = async_interceptor.get_request()
assert request.json()["exact_match"] is True

def test_async_search_exact_match_false(async_interceptor, async_client):
async_interceptor.set_response(200, json=dummy_response)
asyncio.run(async_client.search("What is Tavily?", exact_match=False))
request = async_interceptor.get_request()
assert request.json()["exact_match"] is False

def test_sync_search_exact_match_query_quotes_escaped_in_payload(sync_interceptor, sync_client):
sync_interceptor.set_response(200, json=dummy_response)
sync_client.search('"John Smith" CEO Acme Corp', exact_match=True)
request = sync_interceptor.get_request()
# The raw JSON payload should have escaped quotes for the quoted phrase
assert r'\"John Smith\"' in request.body
# But the parsed query should preserve the original quotes
assert request.json()["query"] == '"John Smith" CEO Acme Corp'

def test_async_search_exact_match_query_quotes_escaped_in_payload(async_interceptor, async_client):
async_interceptor.set_response(200, json=dummy_response)
asyncio.run(async_client.search('"John Smith" CEO Acme Corp', exact_match=True))
request = async_interceptor.get_request()
assert r'\"John Smith\"' in request.body
assert request.json()["query"] == '"John Smith" CEO Acme Corp'