tavily-python 0.7.13__tar.gz → 0.7.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tavily_python-0.7.13 → tavily_python-0.7.14}/PKG-INFO +57 -2
- {tavily_python-0.7.13 → tavily_python-0.7.14}/README.md +56 -1
- {tavily_python-0.7.13 → tavily_python-0.7.14}/setup.py +1 -1
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily/async_tavily.py +171 -1
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily/tavily.py +178 -1
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily_python.egg-info/PKG-INFO +57 -2
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily_python.egg-info/SOURCES.txt +1 -0
- tavily_python-0.7.14/tests/test_research.py +149 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/LICENSE +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/setup.cfg +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily/__init__.py +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily/config.py +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily/errors.py +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily/hybrid_rag/__init__.py +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily/hybrid_rag/hybrid_rag.py +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily/utils.py +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily_python.egg-info/dependency_links.txt +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily_python.egg-info/requires.txt +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tavily_python.egg-info/top_level.txt +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tests/test_crawl.py +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tests/test_errors.py +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tests/test_map.py +0 -0
- {tavily_python-0.7.13 → tavily_python-0.7.14}/tests/test_search.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tavily-python
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.14
|
|
4
4
|
Summary: Python wrapper for the Tavily API
|
|
5
5
|
Home-page: https://github.com/tavily-ai/tavily-python
|
|
6
6
|
Author: Tavily AI
|
|
@@ -32,7 +32,7 @@ Dynamic: summary
|
|
|
32
32
|
[](https://github.com/tavily-ai/tavily-python/blob/main/LICENSE)
|
|
33
33
|
[](https://github.com/tavily-ai/tavily-python/actions)
|
|
34
34
|
|
|
35
|
-
The Tavily Python wrapper allows for easy interaction with the Tavily API, offering the full range of our search and
|
|
35
|
+
The Tavily Python wrapper allows for easy interaction with the Tavily API, offering the full range of our search, extract, crawl, map, and research functionalities directly from your Python programs. Easily integrate smart search, content extraction, and research capabilities into your applications, harnessing Tavily's powerful features.
|
|
36
36
|
|
|
37
37
|
## Installing
|
|
38
38
|
|
|
@@ -41,6 +41,7 @@ pip install tavily-python
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
# Tavily Search
|
|
44
|
+
|
|
44
45
|
Search lets you search the web for a given query.
|
|
45
46
|
|
|
46
47
|
## Usage
|
|
@@ -99,6 +100,7 @@ print(answer)
|
|
|
99
100
|
This is how you get accurate and concise answers to questions, in one line of code. Perfect for usage by LLMs!
|
|
100
101
|
|
|
101
102
|
# Tavily Extract
|
|
103
|
+
|
|
102
104
|
Extract web page content from one or more specified URLs.
|
|
103
105
|
|
|
104
106
|
## Usage
|
|
@@ -203,6 +205,59 @@ for result in response["results"]:
|
|
|
203
205
|
|
|
204
206
|
```
|
|
205
207
|
|
|
208
|
+
# Tavily Research
|
|
209
|
+
|
|
210
|
+
Research lets you create comprehensive research reports on any topic, with automatic source gathering, analysis, and structured output.
|
|
211
|
+
|
|
212
|
+
## Usage
|
|
213
|
+
|
|
214
|
+
Below are some code snippets that demonstrate how to interact with our Research API. Each step and component of this code is explained in greater detail in the API Methods section below.
|
|
215
|
+
|
|
216
|
+
### Creating a research task and retrieving results
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from tavily import TavilyClient
|
|
220
|
+
|
|
221
|
+
# Step 1. Instantiating your TavilyClient
|
|
222
|
+
tavily_client = TavilyClient(api_key="tvly-YOUR_API_KEY")
|
|
223
|
+
|
|
224
|
+
# Step 2. Creating a research task
|
|
225
|
+
response = tavily_client.research(
|
|
226
|
+
input="Research the latest developments in AI",
|
|
227
|
+
model="pro",
|
|
228
|
+
citation_format="apa"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Step 3. Retrieving the research results
|
|
232
|
+
request_id = response["request_id"]
|
|
233
|
+
result = tavily_client.get_research(request_id)
|
|
234
|
+
|
|
235
|
+
# Step 4. Printing the research report
|
|
236
|
+
print(f"Status: {result['status']}")
|
|
237
|
+
print(f"Content: {result['content']}")
|
|
238
|
+
print(f"Sources: {len(result['sources'])} sources found")
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Streaming research results
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from tavily import TavilyClient
|
|
245
|
+
|
|
246
|
+
# Step 1. Instantiating your TavilyClient
|
|
247
|
+
tavily_client = TavilyClient(api_key="tvly-YOUR_API_KEY")
|
|
248
|
+
|
|
249
|
+
# Step 2. Creating a streaming research task
|
|
250
|
+
stream = tavily_client.research(
|
|
251
|
+
input="Research the latest developments in AI",
|
|
252
|
+
model="pro",
|
|
253
|
+
stream=True
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Step 3. Processing the stream as it arrives
|
|
257
|
+
for chunk in stream:
|
|
258
|
+
print(chunk.decode('utf-8'))
|
|
259
|
+
```
|
|
260
|
+
|
|
206
261
|
## Documentation
|
|
207
262
|
|
|
208
263
|
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).
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/tavily-ai/tavily-python/blob/main/LICENSE)
|
|
6
6
|
[](https://github.com/tavily-ai/tavily-python/actions)
|
|
7
7
|
|
|
8
|
-
The Tavily Python wrapper allows for easy interaction with the Tavily API, offering the full range of our search and
|
|
8
|
+
The Tavily Python wrapper allows for easy interaction with the Tavily API, offering the full range of our search, extract, crawl, map, and research functionalities directly from your Python programs. Easily integrate smart search, content extraction, and research capabilities into your applications, harnessing Tavily's powerful features.
|
|
9
9
|
|
|
10
10
|
## Installing
|
|
11
11
|
|
|
@@ -14,6 +14,7 @@ pip install tavily-python
|
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
# Tavily Search
|
|
17
|
+
|
|
17
18
|
Search lets you search the web for a given query.
|
|
18
19
|
|
|
19
20
|
## Usage
|
|
@@ -72,6 +73,7 @@ print(answer)
|
|
|
72
73
|
This is how you get accurate and concise answers to questions, in one line of code. Perfect for usage by LLMs!
|
|
73
74
|
|
|
74
75
|
# Tavily Extract
|
|
76
|
+
|
|
75
77
|
Extract web page content from one or more specified URLs.
|
|
76
78
|
|
|
77
79
|
## Usage
|
|
@@ -176,6 +178,59 @@ for result in response["results"]:
|
|
|
176
178
|
|
|
177
179
|
```
|
|
178
180
|
|
|
181
|
+
# Tavily Research
|
|
182
|
+
|
|
183
|
+
Research lets you create comprehensive research reports on any topic, with automatic source gathering, analysis, and structured output.
|
|
184
|
+
|
|
185
|
+
## Usage
|
|
186
|
+
|
|
187
|
+
Below are some code snippets that demonstrate how to interact with our Research API. Each step and component of this code is explained in greater detail in the API Methods section below.
|
|
188
|
+
|
|
189
|
+
### Creating a research task and retrieving results
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from tavily import TavilyClient
|
|
193
|
+
|
|
194
|
+
# Step 1. Instantiating your TavilyClient
|
|
195
|
+
tavily_client = TavilyClient(api_key="tvly-YOUR_API_KEY")
|
|
196
|
+
|
|
197
|
+
# Step 2. Creating a research task
|
|
198
|
+
response = tavily_client.research(
|
|
199
|
+
input="Research the latest developments in AI",
|
|
200
|
+
model="pro",
|
|
201
|
+
citation_format="apa"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Step 3. Retrieving the research results
|
|
205
|
+
request_id = response["request_id"]
|
|
206
|
+
result = tavily_client.get_research(request_id)
|
|
207
|
+
|
|
208
|
+
# Step 4. Printing the research report
|
|
209
|
+
print(f"Status: {result['status']}")
|
|
210
|
+
print(f"Content: {result['content']}")
|
|
211
|
+
print(f"Sources: {len(result['sources'])} sources found")
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Streaming research results
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from tavily import TavilyClient
|
|
218
|
+
|
|
219
|
+
# Step 1. Instantiating your TavilyClient
|
|
220
|
+
tavily_client = TavilyClient(api_key="tvly-YOUR_API_KEY")
|
|
221
|
+
|
|
222
|
+
# Step 2. Creating a streaming research task
|
|
223
|
+
stream = tavily_client.research(
|
|
224
|
+
input="Research the latest developments in AI",
|
|
225
|
+
model="pro",
|
|
226
|
+
stream=True
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Step 3. Processing the stream as it arrives
|
|
230
|
+
for chunk in stream:
|
|
231
|
+
print(chunk.decode('utf-8'))
|
|
232
|
+
```
|
|
233
|
+
|
|
179
234
|
## Documentation
|
|
180
235
|
|
|
181
236
|
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).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
-
from typing import Literal, Sequence, Optional, List, Union
|
|
4
|
+
from typing import Literal, Sequence, Optional, List, Union, AsyncGenerator, Awaitable
|
|
5
5
|
|
|
6
6
|
import httpx
|
|
7
7
|
|
|
@@ -576,3 +576,173 @@ class AsyncTavilyClient:
|
|
|
576
576
|
sorted_results = sorted(all_results, key=lambda x: x["score"], reverse=True)[:max_results]
|
|
577
577
|
|
|
578
578
|
return sorted_results
|
|
579
|
+
|
|
580
|
+
def _research(self,
|
|
581
|
+
input: str,
|
|
582
|
+
model: Literal["mini", "pro", "auto"] = None,
|
|
583
|
+
output_schema: dict = None,
|
|
584
|
+
stream: bool = False,
|
|
585
|
+
citation_format: Literal["numbered", "mla", "apa", "chicago"] = "numbered",
|
|
586
|
+
timeout: Optional[float] = None,
|
|
587
|
+
**kwargs
|
|
588
|
+
) -> Union[AsyncGenerator[bytes, None], Awaitable[dict]]:
|
|
589
|
+
"""
|
|
590
|
+
Internal research method to send the request to the API.
|
|
591
|
+
"""
|
|
592
|
+
data = {
|
|
593
|
+
"input": input,
|
|
594
|
+
"model": model,
|
|
595
|
+
"output_schema": output_schema,
|
|
596
|
+
"stream": stream,
|
|
597
|
+
"citation_format": citation_format,
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
data = {k: v for k, v in data.items() if v is not None}
|
|
601
|
+
|
|
602
|
+
if kwargs:
|
|
603
|
+
data.update(kwargs)
|
|
604
|
+
|
|
605
|
+
if stream:
|
|
606
|
+
async def stream_generator() -> AsyncGenerator[bytes, None]:
|
|
607
|
+
try:
|
|
608
|
+
async with self._client_creator() as client:
|
|
609
|
+
async with client.stream(
|
|
610
|
+
"POST",
|
|
611
|
+
"/research",
|
|
612
|
+
content=json.dumps(data),
|
|
613
|
+
timeout=timeout
|
|
614
|
+
) as response:
|
|
615
|
+
if response.status_code != 200:
|
|
616
|
+
try:
|
|
617
|
+
error_text = await response.aread()
|
|
618
|
+
error_text = error_text.decode('utf-8') if isinstance(error_text, bytes) else error_text
|
|
619
|
+
except Exception:
|
|
620
|
+
error_text = "Unknown error"
|
|
621
|
+
|
|
622
|
+
if response.status_code == 429:
|
|
623
|
+
raise UsageLimitExceededError(error_text)
|
|
624
|
+
elif response.status_code in [403,432,433]:
|
|
625
|
+
raise ForbiddenError(error_text)
|
|
626
|
+
elif response.status_code == 401:
|
|
627
|
+
raise InvalidAPIKeyError(error_text)
|
|
628
|
+
elif response.status_code == 400:
|
|
629
|
+
raise BadRequestError(error_text)
|
|
630
|
+
else:
|
|
631
|
+
raise Exception(f"Error {response.status_code}: {error_text}")
|
|
632
|
+
|
|
633
|
+
async for chunk in response.aiter_bytes():
|
|
634
|
+
if chunk:
|
|
635
|
+
yield chunk
|
|
636
|
+
except httpx.TimeoutException:
|
|
637
|
+
raise TimeoutError(timeout)
|
|
638
|
+
except Exception as e:
|
|
639
|
+
raise Exception(f"Error during research stream: {str(e)}")
|
|
640
|
+
|
|
641
|
+
return stream_generator()
|
|
642
|
+
else:
|
|
643
|
+
async def _make_request():
|
|
644
|
+
async with self._client_creator() as client:
|
|
645
|
+
try:
|
|
646
|
+
response = await client.post("/research", content=json.dumps(data), timeout=timeout)
|
|
647
|
+
except httpx.TimeoutException:
|
|
648
|
+
raise TimeoutError(timeout)
|
|
649
|
+
|
|
650
|
+
if response.status_code == 200:
|
|
651
|
+
return response.json()
|
|
652
|
+
else:
|
|
653
|
+
detail = ""
|
|
654
|
+
try:
|
|
655
|
+
detail = response.json().get("detail", {}).get("error", None)
|
|
656
|
+
except Exception:
|
|
657
|
+
pass
|
|
658
|
+
|
|
659
|
+
if response.status_code == 429:
|
|
660
|
+
raise UsageLimitExceededError(detail)
|
|
661
|
+
elif response.status_code in [403,432,433]:
|
|
662
|
+
raise ForbiddenError(detail)
|
|
663
|
+
elif response.status_code == 401:
|
|
664
|
+
raise InvalidAPIKeyError(detail)
|
|
665
|
+
elif response.status_code == 400:
|
|
666
|
+
raise BadRequestError(detail)
|
|
667
|
+
else:
|
|
668
|
+
raise response.raise_for_status()
|
|
669
|
+
|
|
670
|
+
return _make_request()
|
|
671
|
+
|
|
672
|
+
async def research(self,
|
|
673
|
+
input: str,
|
|
674
|
+
model: Literal["mini", "pro", "auto"] = None,
|
|
675
|
+
output_schema: dict = None,
|
|
676
|
+
stream: bool = False,
|
|
677
|
+
citation_format: Literal["numbered", "mla", "apa", "chicago"] = "numbered",
|
|
678
|
+
timeout: Optional[float] = None,
|
|
679
|
+
**kwargs
|
|
680
|
+
) -> Union[dict, AsyncGenerator[bytes, None]]:
|
|
681
|
+
"""
|
|
682
|
+
Research method to create a research task.
|
|
683
|
+
|
|
684
|
+
Args:
|
|
685
|
+
input: The research task description (required).
|
|
686
|
+
model: Research depth - must be either 'mini', 'pro', or 'auto'.
|
|
687
|
+
output_schema: Schema for the 'structured_output' response format (JSON Schema dict).
|
|
688
|
+
stream: Whether to stream the research task.
|
|
689
|
+
citation_format: Citation format - must be either 'numbered', 'mla', 'apa', or 'chicago'.
|
|
690
|
+
timeout: Optional HTTP request timeout in seconds.
|
|
691
|
+
**kwargs: Additional custom arguments.
|
|
692
|
+
|
|
693
|
+
Returns:
|
|
694
|
+
When stream=False: dict - the response dictionary.
|
|
695
|
+
When stream=True: AsyncGenerator[bytes, None] - iterate over this to get streaming chunks.
|
|
696
|
+
"""
|
|
697
|
+
result = self._research(
|
|
698
|
+
input=input,
|
|
699
|
+
model=model,
|
|
700
|
+
output_schema=output_schema,
|
|
701
|
+
stream=stream,
|
|
702
|
+
citation_format=citation_format,
|
|
703
|
+
timeout=timeout,
|
|
704
|
+
**kwargs
|
|
705
|
+
)
|
|
706
|
+
if stream:
|
|
707
|
+
return result # Don't await the result, it's an AsyncGenerator that will be lazy and only execute when iterated over with async for
|
|
708
|
+
else:
|
|
709
|
+
return await result
|
|
710
|
+
|
|
711
|
+
async def get_research(self,
|
|
712
|
+
request_id: str
|
|
713
|
+
) -> dict:
|
|
714
|
+
"""
|
|
715
|
+
Get research results by request_id.
|
|
716
|
+
|
|
717
|
+
Args:
|
|
718
|
+
request_id: The research request ID.
|
|
719
|
+
|
|
720
|
+
Returns:
|
|
721
|
+
dict: Research response containing request_id, created_at, completed_at, status, content, and sources.
|
|
722
|
+
"""
|
|
723
|
+
async with self._client_creator() as client:
|
|
724
|
+
try:
|
|
725
|
+
response = await client.get(f"/research/{request_id}")
|
|
726
|
+
except Exception as e:
|
|
727
|
+
raise Exception(f"Error getting research: {e}")
|
|
728
|
+
|
|
729
|
+
if response.status_code in (200, 202):
|
|
730
|
+
data = response.json()
|
|
731
|
+
return data
|
|
732
|
+
else:
|
|
733
|
+
detail = ""
|
|
734
|
+
try:
|
|
735
|
+
detail = response.json().get("detail", {}).get("error", None)
|
|
736
|
+
except Exception:
|
|
737
|
+
pass
|
|
738
|
+
|
|
739
|
+
if response.status_code == 429:
|
|
740
|
+
raise UsageLimitExceededError(detail)
|
|
741
|
+
elif response.status_code in [403,432,433]:
|
|
742
|
+
raise ForbiddenError(detail)
|
|
743
|
+
elif response.status_code == 401:
|
|
744
|
+
raise InvalidAPIKeyError(detail)
|
|
745
|
+
elif response.status_code == 400:
|
|
746
|
+
raise BadRequestError(detail)
|
|
747
|
+
else:
|
|
748
|
+
raise response.raise_for_status()
|
|
@@ -2,7 +2,7 @@ import requests
|
|
|
2
2
|
import json
|
|
3
3
|
import warnings
|
|
4
4
|
import os
|
|
5
|
-
from typing import Literal, Sequence, Optional, List, Union
|
|
5
|
+
from typing import Literal, Sequence, Optional, List, Union, Generator
|
|
6
6
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
7
7
|
from .utils import get_max_items_from_list
|
|
8
8
|
from .errors import UsageLimitExceededError, InvalidAPIKeyError, MissingAPIKeyError, BadRequestError, ForbiddenError, TimeoutError
|
|
@@ -565,6 +565,183 @@ class TavilyClient:
|
|
|
565
565
|
|
|
566
566
|
return sorted_results
|
|
567
567
|
|
|
568
|
+
def _research(self,
|
|
569
|
+
input: str,
|
|
570
|
+
model: Literal["mini", "pro", "auto"] = None,
|
|
571
|
+
output_schema: dict = None,
|
|
572
|
+
stream: bool = False,
|
|
573
|
+
citation_format: Literal["numbered", "mla", "apa", "chicago"] = "numbered",
|
|
574
|
+
timeout: Optional[float] = None,
|
|
575
|
+
**kwargs
|
|
576
|
+
) -> Union[dict, Generator[bytes, None, None]]:
|
|
577
|
+
"""
|
|
578
|
+
Internal research method to send the request to the API.
|
|
579
|
+
"""
|
|
580
|
+
data = {
|
|
581
|
+
"input": input,
|
|
582
|
+
"model": model,
|
|
583
|
+
"output_schema": output_schema,
|
|
584
|
+
"stream": stream,
|
|
585
|
+
"citation_format": citation_format,
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
data = {k: v for k, v in data.items() if v is not None}
|
|
589
|
+
|
|
590
|
+
if kwargs:
|
|
591
|
+
data.update(kwargs)
|
|
592
|
+
|
|
593
|
+
if stream:
|
|
594
|
+
try:
|
|
595
|
+
response = requests.post(
|
|
596
|
+
self.base_url + "/research",
|
|
597
|
+
data=json.dumps(data),
|
|
598
|
+
headers=self.headers,
|
|
599
|
+
timeout=timeout,
|
|
600
|
+
proxies=self.proxies,
|
|
601
|
+
stream=True
|
|
602
|
+
)
|
|
603
|
+
except requests.exceptions.Timeout:
|
|
604
|
+
raise TimeoutError(timeout)
|
|
605
|
+
|
|
606
|
+
if response.status_code != 200:
|
|
607
|
+
detail = ""
|
|
608
|
+
try:
|
|
609
|
+
detail = response.json().get("detail", {}).get("error", None)
|
|
610
|
+
except Exception:
|
|
611
|
+
pass
|
|
612
|
+
|
|
613
|
+
if response.status_code == 429:
|
|
614
|
+
raise UsageLimitExceededError(detail)
|
|
615
|
+
elif response.status_code in [403,432,433]:
|
|
616
|
+
raise ForbiddenError(detail)
|
|
617
|
+
elif response.status_code == 401:
|
|
618
|
+
raise InvalidAPIKeyError(detail)
|
|
619
|
+
elif response.status_code == 400:
|
|
620
|
+
raise BadRequestError(detail)
|
|
621
|
+
else:
|
|
622
|
+
raise response.raise_for_status()
|
|
623
|
+
|
|
624
|
+
def stream_generator() -> Generator[bytes, None, None]:
|
|
625
|
+
try:
|
|
626
|
+
for chunk in response.iter_content(chunk_size=None):
|
|
627
|
+
if chunk:
|
|
628
|
+
yield chunk
|
|
629
|
+
finally:
|
|
630
|
+
response.close()
|
|
631
|
+
|
|
632
|
+
return stream_generator()
|
|
633
|
+
else:
|
|
634
|
+
try:
|
|
635
|
+
response = requests.post(
|
|
636
|
+
self.base_url + "/research",
|
|
637
|
+
data=json.dumps(data),
|
|
638
|
+
headers=self.headers,
|
|
639
|
+
timeout=timeout,
|
|
640
|
+
proxies=self.proxies
|
|
641
|
+
)
|
|
642
|
+
except requests.exceptions.Timeout:
|
|
643
|
+
raise TimeoutError(timeout)
|
|
644
|
+
|
|
645
|
+
if response.status_code == 200:
|
|
646
|
+
return response.json()
|
|
647
|
+
else:
|
|
648
|
+
detail = ""
|
|
649
|
+
try:
|
|
650
|
+
detail = response.json().get("detail", {}).get("error", None)
|
|
651
|
+
except Exception:
|
|
652
|
+
pass
|
|
653
|
+
|
|
654
|
+
if response.status_code == 429:
|
|
655
|
+
raise UsageLimitExceededError(detail)
|
|
656
|
+
elif response.status_code in [403,432,433]:
|
|
657
|
+
raise ForbiddenError(detail)
|
|
658
|
+
elif response.status_code == 401:
|
|
659
|
+
raise InvalidAPIKeyError(detail)
|
|
660
|
+
elif response.status_code == 400:
|
|
661
|
+
raise BadRequestError(detail)
|
|
662
|
+
else:
|
|
663
|
+
raise response.raise_for_status()
|
|
664
|
+
|
|
665
|
+
def research(self,
|
|
666
|
+
input: str,
|
|
667
|
+
model: Literal["mini", "pro", "auto"] = None,
|
|
668
|
+
output_schema: dict = None,
|
|
669
|
+
stream: bool = False,
|
|
670
|
+
citation_format: Literal["numbered", "mla", "apa", "chicago"] = "numbered",
|
|
671
|
+
timeout: Optional[float] = None,
|
|
672
|
+
**kwargs
|
|
673
|
+
) -> Union[dict, Generator[bytes, None, None]]:
|
|
674
|
+
"""
|
|
675
|
+
Research method to create a research task.
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
input: The research task or question to investigate (required).
|
|
679
|
+
model: The model used by the research agent - must be either 'mini', 'pro', or 'auto'.
|
|
680
|
+
output_schema: Schema for the 'structured_output' response format (JSON Schema dict).
|
|
681
|
+
stream: Whether to stream the research task.
|
|
682
|
+
citation_format: Citation format - must be either 'numbered', 'mla', 'apa', or 'chicago'.
|
|
683
|
+
timeout: Optional HTTP request timeout in seconds.
|
|
684
|
+
**kwargs: Additional custom arguments.
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
dict: Response containing request_id, created_at, status, input, and model.
|
|
688
|
+
"""
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
response_dict = self._research(
|
|
692
|
+
input=input,
|
|
693
|
+
model=model,
|
|
694
|
+
output_schema=output_schema,
|
|
695
|
+
stream=stream,
|
|
696
|
+
citation_format=citation_format,
|
|
697
|
+
timeout=timeout,
|
|
698
|
+
**kwargs
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
return response_dict
|
|
702
|
+
|
|
703
|
+
def get_research(self,
|
|
704
|
+
request_id: str
|
|
705
|
+
) -> dict:
|
|
706
|
+
"""
|
|
707
|
+
Get research results by request_id.
|
|
708
|
+
|
|
709
|
+
Args:
|
|
710
|
+
request_id: The research request ID.
|
|
711
|
+
|
|
712
|
+
Returns:
|
|
713
|
+
dict: Research response containing request_id, created_at, completed_at, status, content, and sources.
|
|
714
|
+
"""
|
|
715
|
+
try:
|
|
716
|
+
response = requests.get(
|
|
717
|
+
self.base_url + f"/research/{request_id}",
|
|
718
|
+
headers=self.headers,
|
|
719
|
+
proxies=self.proxies,
|
|
720
|
+
)
|
|
721
|
+
except Exception as e:
|
|
722
|
+
raise Exception(f"Error getting research: {e}")
|
|
723
|
+
|
|
724
|
+
if response.status_code in (200, 202):
|
|
725
|
+
data = response.json()
|
|
726
|
+
return data
|
|
727
|
+
else:
|
|
728
|
+
detail = ""
|
|
729
|
+
try:
|
|
730
|
+
detail = response.json().get("detail", {}).get("error", None)
|
|
731
|
+
except Exception:
|
|
732
|
+
pass
|
|
733
|
+
|
|
734
|
+
if response.status_code == 429:
|
|
735
|
+
raise UsageLimitExceededError(detail)
|
|
736
|
+
elif response.status_code in [403,432,433]:
|
|
737
|
+
raise ForbiddenError(detail)
|
|
738
|
+
elif response.status_code == 401:
|
|
739
|
+
raise InvalidAPIKeyError(detail)
|
|
740
|
+
elif response.status_code == 400:
|
|
741
|
+
raise BadRequestError(detail)
|
|
742
|
+
else:
|
|
743
|
+
raise response.raise_for_status()
|
|
744
|
+
|
|
568
745
|
|
|
569
746
|
class Client(TavilyClient):
|
|
570
747
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tavily-python
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.14
|
|
4
4
|
Summary: Python wrapper for the Tavily API
|
|
5
5
|
Home-page: https://github.com/tavily-ai/tavily-python
|
|
6
6
|
Author: Tavily AI
|
|
@@ -32,7 +32,7 @@ Dynamic: summary
|
|
|
32
32
|
[](https://github.com/tavily-ai/tavily-python/blob/main/LICENSE)
|
|
33
33
|
[](https://github.com/tavily-ai/tavily-python/actions)
|
|
34
34
|
|
|
35
|
-
The Tavily Python wrapper allows for easy interaction with the Tavily API, offering the full range of our search and
|
|
35
|
+
The Tavily Python wrapper allows for easy interaction with the Tavily API, offering the full range of our search, extract, crawl, map, and research functionalities directly from your Python programs. Easily integrate smart search, content extraction, and research capabilities into your applications, harnessing Tavily's powerful features.
|
|
36
36
|
|
|
37
37
|
## Installing
|
|
38
38
|
|
|
@@ -41,6 +41,7 @@ pip install tavily-python
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
# Tavily Search
|
|
44
|
+
|
|
44
45
|
Search lets you search the web for a given query.
|
|
45
46
|
|
|
46
47
|
## Usage
|
|
@@ -99,6 +100,7 @@ print(answer)
|
|
|
99
100
|
This is how you get accurate and concise answers to questions, in one line of code. Perfect for usage by LLMs!
|
|
100
101
|
|
|
101
102
|
# Tavily Extract
|
|
103
|
+
|
|
102
104
|
Extract web page content from one or more specified URLs.
|
|
103
105
|
|
|
104
106
|
## Usage
|
|
@@ -203,6 +205,59 @@ for result in response["results"]:
|
|
|
203
205
|
|
|
204
206
|
```
|
|
205
207
|
|
|
208
|
+
# Tavily Research
|
|
209
|
+
|
|
210
|
+
Research lets you create comprehensive research reports on any topic, with automatic source gathering, analysis, and structured output.
|
|
211
|
+
|
|
212
|
+
## Usage
|
|
213
|
+
|
|
214
|
+
Below are some code snippets that demonstrate how to interact with our Research API. Each step and component of this code is explained in greater detail in the API Methods section below.
|
|
215
|
+
|
|
216
|
+
### Creating a research task and retrieving results
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from tavily import TavilyClient
|
|
220
|
+
|
|
221
|
+
# Step 1. Instantiating your TavilyClient
|
|
222
|
+
tavily_client = TavilyClient(api_key="tvly-YOUR_API_KEY")
|
|
223
|
+
|
|
224
|
+
# Step 2. Creating a research task
|
|
225
|
+
response = tavily_client.research(
|
|
226
|
+
input="Research the latest developments in AI",
|
|
227
|
+
model="pro",
|
|
228
|
+
citation_format="apa"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Step 3. Retrieving the research results
|
|
232
|
+
request_id = response["request_id"]
|
|
233
|
+
result = tavily_client.get_research(request_id)
|
|
234
|
+
|
|
235
|
+
# Step 4. Printing the research report
|
|
236
|
+
print(f"Status: {result['status']}")
|
|
237
|
+
print(f"Content: {result['content']}")
|
|
238
|
+
print(f"Sources: {len(result['sources'])} sources found")
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Streaming research results
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from tavily import TavilyClient
|
|
245
|
+
|
|
246
|
+
# Step 1. Instantiating your TavilyClient
|
|
247
|
+
tavily_client = TavilyClient(api_key="tvly-YOUR_API_KEY")
|
|
248
|
+
|
|
249
|
+
# Step 2. Creating a streaming research task
|
|
250
|
+
stream = tavily_client.research(
|
|
251
|
+
input="Research the latest developments in AI",
|
|
252
|
+
model="pro",
|
|
253
|
+
stream=True
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Step 3. Processing the stream as it arrives
|
|
257
|
+
for chunk in stream:
|
|
258
|
+
print(chunk.decode('utf-8'))
|
|
259
|
+
```
|
|
260
|
+
|
|
206
261
|
## Documentation
|
|
207
262
|
|
|
208
263
|
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).
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
BASE_URL = "https://api.tavily.com"
|
|
4
|
+
|
|
5
|
+
dummy_queued_response = {
|
|
6
|
+
"request_id": "test-request-123",
|
|
7
|
+
"created_at": "2024-01-01T00:00:00Z",
|
|
8
|
+
"status": "pending",
|
|
9
|
+
"input": "Research the latest developments in AI",
|
|
10
|
+
"model": "mini"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
dummy_research_response = {
|
|
14
|
+
"request_id": "test-request-123",
|
|
15
|
+
"created_at": "2024-01-01T00:00:00Z",
|
|
16
|
+
"completed_at": "2024-01-01T00:05:00Z",
|
|
17
|
+
"status": "completed",
|
|
18
|
+
"content": "This is a comprehensive research report on AI developments...",
|
|
19
|
+
"sources": [
|
|
20
|
+
{
|
|
21
|
+
"title": "AI Research Paper",
|
|
22
|
+
"url": "https://example.com/ai-paper",
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def validate_research_default(request, response):
|
|
28
|
+
assert request.method == "POST"
|
|
29
|
+
assert request.url == f"{BASE_URL}/research"
|
|
30
|
+
assert request.headers["Authorization"] == "Bearer tvly-test"
|
|
31
|
+
assert request.headers["X-Client-Source"] == "tavily-python"
|
|
32
|
+
assert request.json().get('input') == "Research the latest developments in AI"
|
|
33
|
+
assert response == dummy_queued_response
|
|
34
|
+
|
|
35
|
+
def validate_research_specific(request, response):
|
|
36
|
+
assert request.method == "POST"
|
|
37
|
+
assert request.url == f"{BASE_URL}/research"
|
|
38
|
+
assert request.headers["Authorization"] == "Bearer tvly-test"
|
|
39
|
+
assert request.headers["X-Client-Source"] == "tavily-python"
|
|
40
|
+
|
|
41
|
+
request_json = request.json()
|
|
42
|
+
for key, value in {
|
|
43
|
+
"input": "Research the latest developments in AI",
|
|
44
|
+
"model": "mini",
|
|
45
|
+
"citation_format": "apa",
|
|
46
|
+
"stream": True,
|
|
47
|
+
"output_schema": {
|
|
48
|
+
"title": "ResearchReport",
|
|
49
|
+
"description": "A structured research report",
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"summary": {"type": "string"},
|
|
53
|
+
"key_points": {"type": "array", "items": {"type": "string"}}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}.items():
|
|
57
|
+
assert request_json.get(key) == value
|
|
58
|
+
|
|
59
|
+
assert response == dummy_queued_response
|
|
60
|
+
|
|
61
|
+
def validate_get_research(request, response):
|
|
62
|
+
assert request.method == "GET"
|
|
63
|
+
assert request.url == f"{BASE_URL}/research/test-request-123"
|
|
64
|
+
assert request.headers["Authorization"] == "Bearer tvly-test"
|
|
65
|
+
assert request.headers["X-Client-Source"] == "tavily-python"
|
|
66
|
+
assert response == dummy_research_response
|
|
67
|
+
|
|
68
|
+
def test_sync_research_defaults(sync_interceptor, sync_client):
|
|
69
|
+
sync_interceptor.set_response(200, json=dummy_queued_response)
|
|
70
|
+
response = sync_client.research("Research the latest developments in AI")
|
|
71
|
+
request = sync_interceptor.get_request()
|
|
72
|
+
validate_research_default(request, response)
|
|
73
|
+
|
|
74
|
+
def test_sync_research_specific(sync_interceptor, sync_client):
|
|
75
|
+
sync_interceptor.set_response(200, json=dummy_queued_response)
|
|
76
|
+
response = sync_client.research(
|
|
77
|
+
input="Research the latest developments in AI",
|
|
78
|
+
model="mini",
|
|
79
|
+
citation_format="apa",
|
|
80
|
+
stream=True,
|
|
81
|
+
timeout=300,
|
|
82
|
+
output_schema={
|
|
83
|
+
"title": "ResearchReport",
|
|
84
|
+
"description": "A structured research report",
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"summary": {"type": "string"},
|
|
88
|
+
"key_points": {"type": "array", "items": {"type": "string"}}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
request = sync_interceptor.get_request()
|
|
94
|
+
# When stream=True, response is a generator
|
|
95
|
+
assert hasattr(response, '__iter__') and not isinstance(response, (str, dict))
|
|
96
|
+
validate_research_specific(request, dummy_queued_response)
|
|
97
|
+
|
|
98
|
+
def test_sync_get_research(sync_interceptor, sync_client):
|
|
99
|
+
sync_interceptor.set_response(200, json=dummy_research_response)
|
|
100
|
+
response = sync_client.get_research("test-request-123")
|
|
101
|
+
request = sync_interceptor.get_request()
|
|
102
|
+
validate_get_research(request, response)
|
|
103
|
+
|
|
104
|
+
def test_async_research_defaults(async_interceptor, async_client):
|
|
105
|
+
async_interceptor.set_response(200, json=dummy_queued_response)
|
|
106
|
+
response = asyncio.run(async_client.research("Research the latest developments in AI"))
|
|
107
|
+
request = async_interceptor.get_request()
|
|
108
|
+
validate_research_default(request, response)
|
|
109
|
+
|
|
110
|
+
def test_async_research_specific(async_interceptor, async_client):
|
|
111
|
+
async_interceptor.set_response(200, json=dummy_queued_response)
|
|
112
|
+
|
|
113
|
+
async def run_test():
|
|
114
|
+
response = await async_client.research(
|
|
115
|
+
input="Research the latest developments in AI",
|
|
116
|
+
model="mini",
|
|
117
|
+
citation_format="apa",
|
|
118
|
+
stream=True,
|
|
119
|
+
timeout=300,
|
|
120
|
+
output_schema={
|
|
121
|
+
"title": "ResearchReport",
|
|
122
|
+
"description": "A structured research report",
|
|
123
|
+
"type": "object",
|
|
124
|
+
"properties": {
|
|
125
|
+
"summary": {"type": "string"},
|
|
126
|
+
"key_points": {"type": "array", "items": {"type": "string"}}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
# When stream=True, response is an async generator
|
|
131
|
+
assert hasattr(response, '__aiter__')
|
|
132
|
+
try:
|
|
133
|
+
async for chunk in response:
|
|
134
|
+
# Just consume one chunk to trigger the request
|
|
135
|
+
break
|
|
136
|
+
except StopAsyncIteration:
|
|
137
|
+
pass
|
|
138
|
+
return response
|
|
139
|
+
|
|
140
|
+
response = asyncio.run(run_test())
|
|
141
|
+
request = async_interceptor.get_request()
|
|
142
|
+
validate_research_specific(request, dummy_queued_response)
|
|
143
|
+
|
|
144
|
+
def test_async_get_research(async_interceptor, async_client):
|
|
145
|
+
async_interceptor.set_response(200, json=dummy_research_response)
|
|
146
|
+
response = asyncio.run(async_client.get_research("test-request-123"))
|
|
147
|
+
request = async_interceptor.get_request()
|
|
148
|
+
validate_get_research(request, response)
|
|
149
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|