tavily-python 0.7.12__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.12 → tavily_python-0.7.14}/PKG-INFO +58 -3
- {tavily_python-0.7.12 → tavily_python-0.7.14}/README.md +56 -1
- {tavily_python-0.7.12 → tavily_python-0.7.14}/setup.py +2 -2
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily/async_tavily.py +184 -21
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily/errors.py +1 -1
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily/tavily.py +193 -25
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily_python.egg-info/PKG-INFO +58 -3
- {tavily_python-0.7.12 → 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.12 → tavily_python-0.7.14}/LICENSE +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/setup.cfg +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily/__init__.py +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily/config.py +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily/hybrid_rag/__init__.py +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily/hybrid_rag/hybrid_rag.py +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily/utils.py +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily_python.egg-info/dependency_links.txt +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily_python.egg-info/requires.txt +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tavily_python.egg-info/top_level.txt +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tests/test_crawl.py +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tests/test_errors.py +0 -0
- {tavily_python-0.7.12 → tavily_python-0.7.14}/tests/test_map.py +0 -0
- {tavily_python-0.7.12 → 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
|
|
@@ -8,7 +8,7 @@ Author-email: support@tavily.com
|
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: requests
|
|
@@ -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).
|
|
@@ -5,7 +5,7 @@ with open('README.md', 'r', encoding='utf-8') as f:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='tavily-python',
|
|
8
|
-
version='0.7.
|
|
8
|
+
version='0.7.14',
|
|
9
9
|
url='https://github.com/tavily-ai/tavily-python',
|
|
10
10
|
author='Tavily AI',
|
|
11
11
|
author_email='support@tavily.com',
|
|
@@ -19,5 +19,5 @@ setup(
|
|
|
19
19
|
'License :: OSI Approved :: MIT License',
|
|
20
20
|
'Operating System :: OS Independent',
|
|
21
21
|
],
|
|
22
|
-
python_requires='>=3.
|
|
22
|
+
python_requires='>=3.8',
|
|
23
23
|
)
|
|
@@ -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
|
|
|
@@ -66,7 +66,7 @@ class AsyncTavilyClient:
|
|
|
66
66
|
include_answer: Union[bool, Literal["basic", "advanced"]] = None,
|
|
67
67
|
include_raw_content: Union[bool, Literal["markdown", "text"]] = None,
|
|
68
68
|
include_images: bool = None,
|
|
69
|
-
timeout:
|
|
69
|
+
timeout: float = 60,
|
|
70
70
|
country: str = None,
|
|
71
71
|
auto_parameters: bool = None,
|
|
72
72
|
include_favicon: bool = None,
|
|
@@ -141,7 +141,7 @@ class AsyncTavilyClient:
|
|
|
141
141
|
include_answer: Union[bool, Literal["basic", "advanced"]] = None,
|
|
142
142
|
include_raw_content: Union[bool, Literal["markdown", "text"]] = None,
|
|
143
143
|
include_images: bool = None,
|
|
144
|
-
timeout:
|
|
144
|
+
timeout: float = 60,
|
|
145
145
|
country: str = None,
|
|
146
146
|
auto_parameters: bool = None,
|
|
147
147
|
include_favicon: bool = None,
|
|
@@ -183,7 +183,7 @@ class AsyncTavilyClient:
|
|
|
183
183
|
include_images: bool = None,
|
|
184
184
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
185
185
|
format: Literal["markdown", "text"] = None,
|
|
186
|
-
timeout:
|
|
186
|
+
timeout: float = 30,
|
|
187
187
|
include_favicon: bool = None,
|
|
188
188
|
**kwargs
|
|
189
189
|
) -> dict:
|
|
@@ -205,8 +205,6 @@ class AsyncTavilyClient:
|
|
|
205
205
|
if kwargs:
|
|
206
206
|
data.update(kwargs)
|
|
207
207
|
|
|
208
|
-
timeout = min(timeout, 120)
|
|
209
|
-
|
|
210
208
|
async with self._client_creator() as client:
|
|
211
209
|
try:
|
|
212
210
|
response = await client.post("/extract", content=json.dumps(data), timeout=timeout)
|
|
@@ -239,7 +237,7 @@ class AsyncTavilyClient:
|
|
|
239
237
|
include_images: bool = None,
|
|
240
238
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
241
239
|
format: Literal["markdown", "text"] = None,
|
|
242
|
-
timeout:
|
|
240
|
+
timeout: float = 30,
|
|
243
241
|
include_favicon: bool = None,
|
|
244
242
|
**kwargs, # Accept custom arguments
|
|
245
243
|
) -> dict:
|
|
@@ -247,7 +245,6 @@ class AsyncTavilyClient:
|
|
|
247
245
|
Combined extract method.
|
|
248
246
|
include_favicon: If True, include the favicon in the extraction results.
|
|
249
247
|
"""
|
|
250
|
-
timeout = min(timeout, 120)
|
|
251
248
|
response_dict = await self._extract(urls,
|
|
252
249
|
include_images,
|
|
253
250
|
extract_depth,
|
|
@@ -279,7 +276,7 @@ class AsyncTavilyClient:
|
|
|
279
276
|
include_images: bool = None,
|
|
280
277
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
281
278
|
format: Literal["markdown", "text"] = None,
|
|
282
|
-
timeout:
|
|
279
|
+
timeout: float = 150,
|
|
283
280
|
include_favicon: bool = None,
|
|
284
281
|
**kwargs
|
|
285
282
|
) -> dict:
|
|
@@ -300,6 +297,7 @@ class AsyncTavilyClient:
|
|
|
300
297
|
"include_images": include_images,
|
|
301
298
|
"extract_depth": extract_depth,
|
|
302
299
|
"format": format,
|
|
300
|
+
"timeout": timeout,
|
|
303
301
|
"include_favicon": include_favicon,
|
|
304
302
|
}
|
|
305
303
|
|
|
@@ -308,8 +306,6 @@ class AsyncTavilyClient:
|
|
|
308
306
|
|
|
309
307
|
data = {k: v for k, v in data.items() if v is not None}
|
|
310
308
|
|
|
311
|
-
timeout = min(timeout, 120)
|
|
312
|
-
|
|
313
309
|
async with self._client_creator() as client:
|
|
314
310
|
try:
|
|
315
311
|
response = await client.post("/crawl", content=json.dumps(data), timeout=timeout)
|
|
@@ -350,7 +346,7 @@ class AsyncTavilyClient:
|
|
|
350
346
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
351
347
|
include_images: bool = None,
|
|
352
348
|
format: Literal["markdown", "text"] = None,
|
|
353
|
-
timeout:
|
|
349
|
+
timeout: float = 150,
|
|
354
350
|
include_favicon: bool = None,
|
|
355
351
|
**kwargs
|
|
356
352
|
) -> dict:
|
|
@@ -358,7 +354,6 @@ class AsyncTavilyClient:
|
|
|
358
354
|
Combined crawl method.
|
|
359
355
|
|
|
360
356
|
"""
|
|
361
|
-
timeout = min(timeout, 120)
|
|
362
357
|
response_dict = await self._crawl(url,
|
|
363
358
|
max_depth=max_depth,
|
|
364
359
|
max_breadth=max_breadth,
|
|
@@ -390,7 +385,7 @@ class AsyncTavilyClient:
|
|
|
390
385
|
exclude_domains: Sequence[str] = None,
|
|
391
386
|
allow_external: bool = None,
|
|
392
387
|
include_images: bool = None,
|
|
393
|
-
timeout:
|
|
388
|
+
timeout: float = 150,
|
|
394
389
|
**kwargs
|
|
395
390
|
) -> dict:
|
|
396
391
|
"""
|
|
@@ -408,6 +403,7 @@ class AsyncTavilyClient:
|
|
|
408
403
|
"exclude_domains": exclude_domains,
|
|
409
404
|
"allow_external": allow_external,
|
|
410
405
|
"include_images": include_images,
|
|
406
|
+
"timeout": timeout,
|
|
411
407
|
}
|
|
412
408
|
|
|
413
409
|
if kwargs:
|
|
@@ -415,8 +411,6 @@ class AsyncTavilyClient:
|
|
|
415
411
|
|
|
416
412
|
data = {k: v for k, v in data.items() if v is not None}
|
|
417
413
|
|
|
418
|
-
timeout = min(timeout, 120)
|
|
419
|
-
|
|
420
414
|
async with self._client_creator() as client:
|
|
421
415
|
try:
|
|
422
416
|
response = await client.post("/map", content=json.dumps(data), timeout=timeout)
|
|
@@ -455,14 +449,13 @@ class AsyncTavilyClient:
|
|
|
455
449
|
exclude_domains: Sequence[str] = None,
|
|
456
450
|
allow_external: bool = None,
|
|
457
451
|
include_images: bool = None,
|
|
458
|
-
timeout:
|
|
452
|
+
timeout: float = 150,
|
|
459
453
|
**kwargs
|
|
460
454
|
) -> dict:
|
|
461
455
|
"""
|
|
462
456
|
Combined map method.
|
|
463
457
|
|
|
464
458
|
"""
|
|
465
|
-
timeout = min(timeout, 120)
|
|
466
459
|
response_dict = await self._map(url,
|
|
467
460
|
max_depth=max_depth,
|
|
468
461
|
max_breadth=max_breadth,
|
|
@@ -488,7 +481,7 @@ class AsyncTavilyClient:
|
|
|
488
481
|
include_domains: Sequence[str] = None,
|
|
489
482
|
exclude_domains: Sequence[str] = None,
|
|
490
483
|
max_tokens: int = 4000,
|
|
491
|
-
timeout:
|
|
484
|
+
timeout: float = 60,
|
|
492
485
|
country: str = None,
|
|
493
486
|
include_favicon: bool = None,
|
|
494
487
|
**kwargs, # Accept custom arguments
|
|
@@ -529,7 +522,7 @@ class AsyncTavilyClient:
|
|
|
529
522
|
max_results: int = 5,
|
|
530
523
|
include_domains: Sequence[str] = None,
|
|
531
524
|
exclude_domains: Sequence[str] = None,
|
|
532
|
-
timeout:
|
|
525
|
+
timeout: float = 60,
|
|
533
526
|
country: str = None,
|
|
534
527
|
include_favicon: bool = None,
|
|
535
528
|
**kwargs, # Accept custom arguments
|
|
@@ -559,7 +552,7 @@ class AsyncTavilyClient:
|
|
|
559
552
|
query: str,
|
|
560
553
|
search_depth: Literal["basic", "advanced"] = "advanced",
|
|
561
554
|
max_results: int = 5,
|
|
562
|
-
timeout:
|
|
555
|
+
timeout: float = 60,
|
|
563
556
|
country: str = None,
|
|
564
557
|
) -> Sequence[dict]:
|
|
565
558
|
""" Company information search method. Search depth is advanced by default to get the best answer. """
|
|
@@ -583,3 +576,173 @@ class AsyncTavilyClient:
|
|
|
583
576
|
sorted_results = sorted(all_results, key=lambda x: x["score"], reverse=True)[:max_results]
|
|
584
577
|
|
|
585
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()
|
|
@@ -17,7 +17,7 @@ class InvalidAPIKeyError(Exception):
|
|
|
17
17
|
super().__init__(message)
|
|
18
18
|
|
|
19
19
|
class TimeoutError(Exception):
|
|
20
|
-
def __init__(self, timeout:
|
|
20
|
+
def __init__(self, timeout: float):
|
|
21
21
|
super().__init__(f"Request timed out after {timeout} seconds.")
|
|
22
22
|
|
|
23
23
|
class MissingAPIKeyError(Exception):
|
|
@@ -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
|
|
@@ -49,7 +49,7 @@ class TavilyClient:
|
|
|
49
49
|
include_answer: Union[bool, Literal["basic", "advanced"]] = None,
|
|
50
50
|
include_raw_content: Union[bool, Literal["markdown", "text"]] = None,
|
|
51
51
|
include_images: bool = None,
|
|
52
|
-
timeout:
|
|
52
|
+
timeout: float = 60,
|
|
53
53
|
country: str = None,
|
|
54
54
|
auto_parameters: bool = None,
|
|
55
55
|
include_favicon: bool = None,
|
|
@@ -126,7 +126,7 @@ class TavilyClient:
|
|
|
126
126
|
include_answer: Union[bool, Literal["basic", "advanced"]] = None,
|
|
127
127
|
include_raw_content: Union[bool, Literal["markdown", "text"]] = None,
|
|
128
128
|
include_images: bool = None,
|
|
129
|
-
timeout:
|
|
129
|
+
timeout: float = 60,
|
|
130
130
|
country: str = None,
|
|
131
131
|
auto_parameters: bool = None,
|
|
132
132
|
include_favicon: bool = None,
|
|
@@ -135,7 +135,7 @@ class TavilyClient:
|
|
|
135
135
|
"""
|
|
136
136
|
Combined search method.
|
|
137
137
|
"""
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
response_dict = self._search(query,
|
|
140
140
|
search_depth=search_depth,
|
|
141
141
|
topic=topic,
|
|
@@ -167,7 +167,7 @@ class TavilyClient:
|
|
|
167
167
|
include_images: bool = None,
|
|
168
168
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
169
169
|
format: Literal["markdown", "text"] = None,
|
|
170
|
-
timeout:
|
|
170
|
+
timeout: float = 30,
|
|
171
171
|
include_favicon: bool = None,
|
|
172
172
|
**kwargs
|
|
173
173
|
) -> dict:
|
|
@@ -188,8 +188,6 @@ class TavilyClient:
|
|
|
188
188
|
if kwargs:
|
|
189
189
|
data.update(kwargs)
|
|
190
190
|
|
|
191
|
-
timeout = min(timeout, 120)
|
|
192
|
-
|
|
193
191
|
try:
|
|
194
192
|
response = requests.post(self.base_url + "/extract", data=json.dumps(data), headers=self.headers, timeout=timeout, proxies=self.proxies)
|
|
195
193
|
except requests.exceptions.Timeout:
|
|
@@ -220,14 +218,13 @@ class TavilyClient:
|
|
|
220
218
|
include_images: bool = None,
|
|
221
219
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
222
220
|
format: Literal["markdown", "text"] = None,
|
|
223
|
-
timeout:
|
|
221
|
+
timeout: float = 30,
|
|
224
222
|
include_favicon: bool = None,
|
|
225
223
|
**kwargs, # Accept custom arguments
|
|
226
224
|
) -> dict:
|
|
227
225
|
"""
|
|
228
226
|
Combined extract method.
|
|
229
227
|
"""
|
|
230
|
-
timeout = min(timeout, 120)
|
|
231
228
|
response_dict = self._extract(urls,
|
|
232
229
|
include_images,
|
|
233
230
|
extract_depth,
|
|
@@ -258,7 +255,7 @@ class TavilyClient:
|
|
|
258
255
|
include_images: bool = None,
|
|
259
256
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
260
257
|
format: Literal["markdown", "text"] = None,
|
|
261
|
-
timeout:
|
|
258
|
+
timeout: float = 150,
|
|
262
259
|
include_favicon: bool = None,
|
|
263
260
|
**kwargs
|
|
264
261
|
) -> dict:
|
|
@@ -280,6 +277,7 @@ class TavilyClient:
|
|
|
280
277
|
"include_images": include_images,
|
|
281
278
|
"extract_depth": extract_depth,
|
|
282
279
|
"format": format,
|
|
280
|
+
"timeout": timeout,
|
|
283
281
|
"include_favicon": include_favicon,
|
|
284
282
|
}
|
|
285
283
|
|
|
@@ -287,8 +285,6 @@ class TavilyClient:
|
|
|
287
285
|
data.update(kwargs)
|
|
288
286
|
|
|
289
287
|
data = {k: v for k, v in data.items() if v is not None}
|
|
290
|
-
|
|
291
|
-
timeout = min(timeout, 120)
|
|
292
288
|
|
|
293
289
|
try:
|
|
294
290
|
response = requests.post(
|
|
@@ -330,7 +326,7 @@ class TavilyClient:
|
|
|
330
326
|
include_images: bool = None,
|
|
331
327
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
332
328
|
format: Literal["markdown", "text"] = None,
|
|
333
|
-
timeout:
|
|
329
|
+
timeout: float = 150,
|
|
334
330
|
include_favicon: bool = None,
|
|
335
331
|
**kwargs
|
|
336
332
|
) -> dict:
|
|
@@ -338,7 +334,6 @@ class TavilyClient:
|
|
|
338
334
|
Combined crawl method.
|
|
339
335
|
include_favicon: If True, include the favicon in the crawl results.
|
|
340
336
|
"""
|
|
341
|
-
timeout = min(timeout, 120)
|
|
342
337
|
response_dict = self._crawl(url,
|
|
343
338
|
max_depth=max_depth,
|
|
344
339
|
max_breadth=max_breadth,
|
|
@@ -370,7 +365,7 @@ class TavilyClient:
|
|
|
370
365
|
exclude_domains: Sequence[str] = None,
|
|
371
366
|
allow_external: bool = None,
|
|
372
367
|
include_images: bool = None,
|
|
373
|
-
timeout:
|
|
368
|
+
timeout: float = 150,
|
|
374
369
|
**kwargs
|
|
375
370
|
) -> dict:
|
|
376
371
|
"""
|
|
@@ -388,6 +383,7 @@ class TavilyClient:
|
|
|
388
383
|
"exclude_domains": exclude_domains,
|
|
389
384
|
"allow_external": allow_external,
|
|
390
385
|
"include_images": include_images,
|
|
386
|
+
"timeout": timeout,
|
|
391
387
|
}
|
|
392
388
|
|
|
393
389
|
if kwargs:
|
|
@@ -395,8 +391,6 @@ class TavilyClient:
|
|
|
395
391
|
|
|
396
392
|
data = {k: v for k, v in data.items() if v is not None}
|
|
397
393
|
|
|
398
|
-
timeout = min(timeout, 120)
|
|
399
|
-
|
|
400
394
|
try:
|
|
401
395
|
response = requests.post(
|
|
402
396
|
self.base_url + "/map", data=json.dumps(data), headers=self.headers, timeout=timeout, proxies=self.proxies)
|
|
@@ -435,14 +429,13 @@ class TavilyClient:
|
|
|
435
429
|
exclude_domains: Sequence[str] = None,
|
|
436
430
|
allow_external: bool = None,
|
|
437
431
|
include_images: bool = None,
|
|
438
|
-
timeout:
|
|
432
|
+
timeout: float = 150,
|
|
439
433
|
**kwargs
|
|
440
434
|
) -> dict:
|
|
441
435
|
"""
|
|
442
436
|
Combined map method.
|
|
443
437
|
|
|
444
438
|
"""
|
|
445
|
-
timeout = min(timeout, 120)
|
|
446
439
|
response_dict = self._map(url,
|
|
447
440
|
max_depth=max_depth,
|
|
448
441
|
max_breadth=max_breadth,
|
|
@@ -468,7 +461,7 @@ class TavilyClient:
|
|
|
468
461
|
include_domains: Sequence[str] = None,
|
|
469
462
|
exclude_domains: Sequence[str] = None,
|
|
470
463
|
max_tokens: int = 4000,
|
|
471
|
-
timeout:
|
|
464
|
+
timeout: float = 60,
|
|
472
465
|
country: str = None,
|
|
473
466
|
include_favicon: bool = None,
|
|
474
467
|
**kwargs, # Accept custom arguments
|
|
@@ -481,7 +474,7 @@ class TavilyClient:
|
|
|
481
474
|
|
|
482
475
|
Returns a string of JSON containing the search context up to context limit.
|
|
483
476
|
"""
|
|
484
|
-
|
|
477
|
+
|
|
485
478
|
response_dict = self._search(query,
|
|
486
479
|
search_depth=search_depth,
|
|
487
480
|
topic=topic,
|
|
@@ -510,7 +503,7 @@ class TavilyClient:
|
|
|
510
503
|
max_results: int = 5,
|
|
511
504
|
include_domains: Sequence[str] = None,
|
|
512
505
|
exclude_domains: Sequence[str] = None,
|
|
513
|
-
timeout:
|
|
506
|
+
timeout: float = 60,
|
|
514
507
|
country: str = None,
|
|
515
508
|
include_favicon: bool = None,
|
|
516
509
|
**kwargs, # Accept custom arguments
|
|
@@ -518,7 +511,6 @@ class TavilyClient:
|
|
|
518
511
|
"""
|
|
519
512
|
Q&A search method. Search depth is advanced by default to get the best answer.
|
|
520
513
|
"""
|
|
521
|
-
timeout = min(timeout, 120)
|
|
522
514
|
response_dict = self._search(query,
|
|
523
515
|
search_depth=search_depth,
|
|
524
516
|
topic=topic,
|
|
@@ -541,11 +533,10 @@ class TavilyClient:
|
|
|
541
533
|
search_depth: Literal["basic",
|
|
542
534
|
"advanced"] = "advanced",
|
|
543
535
|
max_results: int = 5,
|
|
544
|
-
timeout:
|
|
536
|
+
timeout: float = 60,
|
|
545
537
|
country: str = None,
|
|
546
538
|
) -> Sequence[dict]:
|
|
547
539
|
""" Company information search method. Search depth is advanced by default to get the best answer. """
|
|
548
|
-
timeout = min(timeout, 120)
|
|
549
540
|
def _perform_search(topic):
|
|
550
541
|
return self._search(query,
|
|
551
542
|
search_depth=search_depth,
|
|
@@ -574,6 +565,183 @@ class TavilyClient:
|
|
|
574
565
|
|
|
575
566
|
return sorted_results
|
|
576
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
|
+
|
|
577
745
|
|
|
578
746
|
class Client(TavilyClient):
|
|
579
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
|
|
@@ -8,7 +8,7 @@ Author-email: support@tavily.com
|
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: requests
|
|
@@ -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
|