tavily-python 0.7.11__tar.gz → 0.7.13__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.11 → tavily_python-0.7.13}/PKG-INFO +2 -2
- {tavily_python-0.7.11 → tavily_python-0.7.13}/setup.py +2 -2
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily/async_tavily.py +13 -29
- tavily_python-0.7.13/tavily/config.py +2 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily/errors.py +1 -1
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily/tavily.py +15 -33
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily_python.egg-info/PKG-INFO +2 -2
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily_python.egg-info/SOURCES.txt +1 -3
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tests/test_crawl.py +0 -3
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tests/test_map.py +1 -4
- tavily_python-0.7.11/tavily/config.py +0 -12
- tavily_python-0.7.11/tests/test_async_search.py +0 -219
- tavily_python-0.7.11/tests/test_sync_search.py +0 -219
- {tavily_python-0.7.11 → tavily_python-0.7.13}/LICENSE +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/README.md +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/setup.cfg +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily/__init__.py +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily/hybrid_rag/__init__.py +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily/hybrid_rag/hybrid_rag.py +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily/utils.py +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily_python.egg-info/dependency_links.txt +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily_python.egg-info/requires.txt +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tavily_python.egg-info/top_level.txt +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/tests/test_errors.py +0 -0
- {tavily_python-0.7.11 → tavily_python-0.7.13}/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.13
|
|
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
|
|
@@ -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.13',
|
|
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
|
)
|
|
@@ -7,7 +7,6 @@ import httpx
|
|
|
7
7
|
|
|
8
8
|
from .utils import get_max_items_from_list
|
|
9
9
|
from .errors import UsageLimitExceededError, InvalidAPIKeyError, MissingAPIKeyError, BadRequestError, ForbiddenError, TimeoutError
|
|
10
|
-
from .config import AllowedCategory
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class AsyncTavilyClient:
|
|
@@ -67,7 +66,7 @@ class AsyncTavilyClient:
|
|
|
67
66
|
include_answer: Union[bool, Literal["basic", "advanced"]] = None,
|
|
68
67
|
include_raw_content: Union[bool, Literal["markdown", "text"]] = None,
|
|
69
68
|
include_images: bool = None,
|
|
70
|
-
timeout:
|
|
69
|
+
timeout: float = 60,
|
|
71
70
|
country: str = None,
|
|
72
71
|
auto_parameters: bool = None,
|
|
73
72
|
include_favicon: bool = None,
|
|
@@ -142,7 +141,7 @@ class AsyncTavilyClient:
|
|
|
142
141
|
include_answer: Union[bool, Literal["basic", "advanced"]] = None,
|
|
143
142
|
include_raw_content: Union[bool, Literal["markdown", "text"]] = None,
|
|
144
143
|
include_images: bool = None,
|
|
145
|
-
timeout:
|
|
144
|
+
timeout: float = 60,
|
|
146
145
|
country: str = None,
|
|
147
146
|
auto_parameters: bool = None,
|
|
148
147
|
include_favicon: bool = None,
|
|
@@ -184,7 +183,7 @@ class AsyncTavilyClient:
|
|
|
184
183
|
include_images: bool = None,
|
|
185
184
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
186
185
|
format: Literal["markdown", "text"] = None,
|
|
187
|
-
timeout:
|
|
186
|
+
timeout: float = 30,
|
|
188
187
|
include_favicon: bool = None,
|
|
189
188
|
**kwargs
|
|
190
189
|
) -> dict:
|
|
@@ -206,8 +205,6 @@ class AsyncTavilyClient:
|
|
|
206
205
|
if kwargs:
|
|
207
206
|
data.update(kwargs)
|
|
208
207
|
|
|
209
|
-
timeout = min(timeout, 120)
|
|
210
|
-
|
|
211
208
|
async with self._client_creator() as client:
|
|
212
209
|
try:
|
|
213
210
|
response = await client.post("/extract", content=json.dumps(data), timeout=timeout)
|
|
@@ -240,7 +237,7 @@ class AsyncTavilyClient:
|
|
|
240
237
|
include_images: bool = None,
|
|
241
238
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
242
239
|
format: Literal["markdown", "text"] = None,
|
|
243
|
-
timeout:
|
|
240
|
+
timeout: float = 30,
|
|
244
241
|
include_favicon: bool = None,
|
|
245
242
|
**kwargs, # Accept custom arguments
|
|
246
243
|
) -> dict:
|
|
@@ -248,7 +245,6 @@ class AsyncTavilyClient:
|
|
|
248
245
|
Combined extract method.
|
|
249
246
|
include_favicon: If True, include the favicon in the extraction results.
|
|
250
247
|
"""
|
|
251
|
-
timeout = min(timeout, 120)
|
|
252
248
|
response_dict = await self._extract(urls,
|
|
253
249
|
include_images,
|
|
254
250
|
extract_depth,
|
|
@@ -278,10 +274,9 @@ class AsyncTavilyClient:
|
|
|
278
274
|
exclude_domains: Sequence[str] = None,
|
|
279
275
|
allow_external: bool = None,
|
|
280
276
|
include_images: bool = None,
|
|
281
|
-
categories: Sequence[AllowedCategory] = None,
|
|
282
277
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
283
278
|
format: Literal["markdown", "text"] = None,
|
|
284
|
-
timeout:
|
|
279
|
+
timeout: float = 150,
|
|
285
280
|
include_favicon: bool = None,
|
|
286
281
|
**kwargs
|
|
287
282
|
) -> dict:
|
|
@@ -299,10 +294,10 @@ class AsyncTavilyClient:
|
|
|
299
294
|
"exclude_paths": exclude_paths,
|
|
300
295
|
"exclude_domains": exclude_domains,
|
|
301
296
|
"allow_external": allow_external,
|
|
302
|
-
"categories": categories,
|
|
303
297
|
"include_images": include_images,
|
|
304
298
|
"extract_depth": extract_depth,
|
|
305
299
|
"format": format,
|
|
300
|
+
"timeout": timeout,
|
|
306
301
|
"include_favicon": include_favicon,
|
|
307
302
|
}
|
|
308
303
|
|
|
@@ -311,8 +306,6 @@ class AsyncTavilyClient:
|
|
|
311
306
|
|
|
312
307
|
data = {k: v for k, v in data.items() if v is not None}
|
|
313
308
|
|
|
314
|
-
timeout = min(timeout, 120)
|
|
315
|
-
|
|
316
309
|
async with self._client_creator() as client:
|
|
317
310
|
try:
|
|
318
311
|
response = await client.post("/crawl", content=json.dumps(data), timeout=timeout)
|
|
@@ -350,11 +343,10 @@ class AsyncTavilyClient:
|
|
|
350
343
|
exclude_paths: Sequence[str] = None,
|
|
351
344
|
exclude_domains: Sequence[str] = None,
|
|
352
345
|
allow_external: bool = None,
|
|
353
|
-
categories: Sequence[AllowedCategory] = None,
|
|
354
346
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
355
347
|
include_images: bool = None,
|
|
356
348
|
format: Literal["markdown", "text"] = None,
|
|
357
|
-
timeout:
|
|
349
|
+
timeout: float = 150,
|
|
358
350
|
include_favicon: bool = None,
|
|
359
351
|
**kwargs
|
|
360
352
|
) -> dict:
|
|
@@ -362,7 +354,6 @@ class AsyncTavilyClient:
|
|
|
362
354
|
Combined crawl method.
|
|
363
355
|
|
|
364
356
|
"""
|
|
365
|
-
timeout = min(timeout, 120)
|
|
366
357
|
response_dict = await self._crawl(url,
|
|
367
358
|
max_depth=max_depth,
|
|
368
359
|
max_breadth=max_breadth,
|
|
@@ -373,7 +364,6 @@ class AsyncTavilyClient:
|
|
|
373
364
|
exclude_paths=exclude_paths,
|
|
374
365
|
exclude_domains=exclude_domains,
|
|
375
366
|
allow_external=allow_external,
|
|
376
|
-
categories=categories,
|
|
377
367
|
extract_depth=extract_depth,
|
|
378
368
|
include_images=include_images,
|
|
379
369
|
format=format,
|
|
@@ -395,8 +385,7 @@ class AsyncTavilyClient:
|
|
|
395
385
|
exclude_domains: Sequence[str] = None,
|
|
396
386
|
allow_external: bool = None,
|
|
397
387
|
include_images: bool = None,
|
|
398
|
-
|
|
399
|
-
timeout: int = 60,
|
|
388
|
+
timeout: float = 150,
|
|
400
389
|
**kwargs
|
|
401
390
|
) -> dict:
|
|
402
391
|
"""
|
|
@@ -414,7 +403,7 @@ class AsyncTavilyClient:
|
|
|
414
403
|
"exclude_domains": exclude_domains,
|
|
415
404
|
"allow_external": allow_external,
|
|
416
405
|
"include_images": include_images,
|
|
417
|
-
"
|
|
406
|
+
"timeout": timeout,
|
|
418
407
|
}
|
|
419
408
|
|
|
420
409
|
if kwargs:
|
|
@@ -422,8 +411,6 @@ class AsyncTavilyClient:
|
|
|
422
411
|
|
|
423
412
|
data = {k: v for k, v in data.items() if v is not None}
|
|
424
413
|
|
|
425
|
-
timeout = min(timeout, 120)
|
|
426
|
-
|
|
427
414
|
async with self._client_creator() as client:
|
|
428
415
|
try:
|
|
429
416
|
response = await client.post("/map", content=json.dumps(data), timeout=timeout)
|
|
@@ -462,15 +449,13 @@ class AsyncTavilyClient:
|
|
|
462
449
|
exclude_domains: Sequence[str] = None,
|
|
463
450
|
allow_external: bool = None,
|
|
464
451
|
include_images: bool = None,
|
|
465
|
-
|
|
466
|
-
timeout: int = 60,
|
|
452
|
+
timeout: float = 150,
|
|
467
453
|
**kwargs
|
|
468
454
|
) -> dict:
|
|
469
455
|
"""
|
|
470
456
|
Combined map method.
|
|
471
457
|
|
|
472
458
|
"""
|
|
473
|
-
timeout = min(timeout, 120)
|
|
474
459
|
response_dict = await self._map(url,
|
|
475
460
|
max_depth=max_depth,
|
|
476
461
|
max_breadth=max_breadth,
|
|
@@ -482,7 +467,6 @@ class AsyncTavilyClient:
|
|
|
482
467
|
exclude_domains=exclude_domains,
|
|
483
468
|
allow_external=allow_external,
|
|
484
469
|
include_images=include_images,
|
|
485
|
-
categories=categories,
|
|
486
470
|
timeout=timeout,
|
|
487
471
|
**kwargs)
|
|
488
472
|
|
|
@@ -497,7 +481,7 @@ class AsyncTavilyClient:
|
|
|
497
481
|
include_domains: Sequence[str] = None,
|
|
498
482
|
exclude_domains: Sequence[str] = None,
|
|
499
483
|
max_tokens: int = 4000,
|
|
500
|
-
timeout:
|
|
484
|
+
timeout: float = 60,
|
|
501
485
|
country: str = None,
|
|
502
486
|
include_favicon: bool = None,
|
|
503
487
|
**kwargs, # Accept custom arguments
|
|
@@ -538,7 +522,7 @@ class AsyncTavilyClient:
|
|
|
538
522
|
max_results: int = 5,
|
|
539
523
|
include_domains: Sequence[str] = None,
|
|
540
524
|
exclude_domains: Sequence[str] = None,
|
|
541
|
-
timeout:
|
|
525
|
+
timeout: float = 60,
|
|
542
526
|
country: str = None,
|
|
543
527
|
include_favicon: bool = None,
|
|
544
528
|
**kwargs, # Accept custom arguments
|
|
@@ -568,7 +552,7 @@ class AsyncTavilyClient:
|
|
|
568
552
|
query: str,
|
|
569
553
|
search_depth: Literal["basic", "advanced"] = "advanced",
|
|
570
554
|
max_results: int = 5,
|
|
571
|
-
timeout:
|
|
555
|
+
timeout: float = 60,
|
|
572
556
|
country: str = None,
|
|
573
557
|
) -> Sequence[dict]:
|
|
574
558
|
""" Company information search method. Search depth is advanced by default to get the best answer. """
|
|
@@ -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):
|
|
@@ -6,7 +6,6 @@ from typing import Literal, Sequence, Optional, List, Union
|
|
|
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
|
|
9
|
-
from .config import AllowedCategory
|
|
10
9
|
|
|
11
10
|
class TavilyClient:
|
|
12
11
|
"""
|
|
@@ -50,7 +49,7 @@ class TavilyClient:
|
|
|
50
49
|
include_answer: Union[bool, Literal["basic", "advanced"]] = None,
|
|
51
50
|
include_raw_content: Union[bool, Literal["markdown", "text"]] = None,
|
|
52
51
|
include_images: bool = None,
|
|
53
|
-
timeout:
|
|
52
|
+
timeout: float = 60,
|
|
54
53
|
country: str = None,
|
|
55
54
|
auto_parameters: bool = None,
|
|
56
55
|
include_favicon: bool = None,
|
|
@@ -127,7 +126,7 @@ class TavilyClient:
|
|
|
127
126
|
include_answer: Union[bool, Literal["basic", "advanced"]] = None,
|
|
128
127
|
include_raw_content: Union[bool, Literal["markdown", "text"]] = None,
|
|
129
128
|
include_images: bool = None,
|
|
130
|
-
timeout:
|
|
129
|
+
timeout: float = 60,
|
|
131
130
|
country: str = None,
|
|
132
131
|
auto_parameters: bool = None,
|
|
133
132
|
include_favicon: bool = None,
|
|
@@ -136,7 +135,7 @@ class TavilyClient:
|
|
|
136
135
|
"""
|
|
137
136
|
Combined search method.
|
|
138
137
|
"""
|
|
139
|
-
|
|
138
|
+
|
|
140
139
|
response_dict = self._search(query,
|
|
141
140
|
search_depth=search_depth,
|
|
142
141
|
topic=topic,
|
|
@@ -168,7 +167,7 @@ class TavilyClient:
|
|
|
168
167
|
include_images: bool = None,
|
|
169
168
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
170
169
|
format: Literal["markdown", "text"] = None,
|
|
171
|
-
timeout:
|
|
170
|
+
timeout: float = 30,
|
|
172
171
|
include_favicon: bool = None,
|
|
173
172
|
**kwargs
|
|
174
173
|
) -> dict:
|
|
@@ -189,8 +188,6 @@ class TavilyClient:
|
|
|
189
188
|
if kwargs:
|
|
190
189
|
data.update(kwargs)
|
|
191
190
|
|
|
192
|
-
timeout = min(timeout, 120)
|
|
193
|
-
|
|
194
191
|
try:
|
|
195
192
|
response = requests.post(self.base_url + "/extract", data=json.dumps(data), headers=self.headers, timeout=timeout, proxies=self.proxies)
|
|
196
193
|
except requests.exceptions.Timeout:
|
|
@@ -221,14 +218,13 @@ class TavilyClient:
|
|
|
221
218
|
include_images: bool = None,
|
|
222
219
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
223
220
|
format: Literal["markdown", "text"] = None,
|
|
224
|
-
timeout:
|
|
221
|
+
timeout: float = 30,
|
|
225
222
|
include_favicon: bool = None,
|
|
226
223
|
**kwargs, # Accept custom arguments
|
|
227
224
|
) -> dict:
|
|
228
225
|
"""
|
|
229
226
|
Combined extract method.
|
|
230
227
|
"""
|
|
231
|
-
timeout = min(timeout, 120)
|
|
232
228
|
response_dict = self._extract(urls,
|
|
233
229
|
include_images,
|
|
234
230
|
extract_depth,
|
|
@@ -257,10 +253,9 @@ class TavilyClient:
|
|
|
257
253
|
exclude_domains: Sequence[str] = None,
|
|
258
254
|
allow_external: bool = None,
|
|
259
255
|
include_images: bool = None,
|
|
260
|
-
categories: Sequence[AllowedCategory] = None,
|
|
261
256
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
262
257
|
format: Literal["markdown", "text"] = None,
|
|
263
|
-
timeout:
|
|
258
|
+
timeout: float = 150,
|
|
264
259
|
include_favicon: bool = None,
|
|
265
260
|
**kwargs
|
|
266
261
|
) -> dict:
|
|
@@ -280,9 +275,9 @@ class TavilyClient:
|
|
|
280
275
|
"exclude_domains": exclude_domains,
|
|
281
276
|
"allow_external": allow_external,
|
|
282
277
|
"include_images": include_images,
|
|
283
|
-
"categories": categories,
|
|
284
278
|
"extract_depth": extract_depth,
|
|
285
279
|
"format": format,
|
|
280
|
+
"timeout": timeout,
|
|
286
281
|
"include_favicon": include_favicon,
|
|
287
282
|
}
|
|
288
283
|
|
|
@@ -290,8 +285,6 @@ class TavilyClient:
|
|
|
290
285
|
data.update(kwargs)
|
|
291
286
|
|
|
292
287
|
data = {k: v for k, v in data.items() if v is not None}
|
|
293
|
-
|
|
294
|
-
timeout = min(timeout, 120)
|
|
295
288
|
|
|
296
289
|
try:
|
|
297
290
|
response = requests.post(
|
|
@@ -331,10 +324,9 @@ class TavilyClient:
|
|
|
331
324
|
exclude_domains: Sequence[str] = None,
|
|
332
325
|
allow_external: bool = None,
|
|
333
326
|
include_images: bool = None,
|
|
334
|
-
categories: Sequence[AllowedCategory] = None,
|
|
335
327
|
extract_depth: Literal["basic", "advanced"] = None,
|
|
336
328
|
format: Literal["markdown", "text"] = None,
|
|
337
|
-
timeout:
|
|
329
|
+
timeout: float = 150,
|
|
338
330
|
include_favicon: bool = None,
|
|
339
331
|
**kwargs
|
|
340
332
|
) -> dict:
|
|
@@ -342,7 +334,6 @@ class TavilyClient:
|
|
|
342
334
|
Combined crawl method.
|
|
343
335
|
include_favicon: If True, include the favicon in the crawl results.
|
|
344
336
|
"""
|
|
345
|
-
timeout = min(timeout, 120)
|
|
346
337
|
response_dict = self._crawl(url,
|
|
347
338
|
max_depth=max_depth,
|
|
348
339
|
max_breadth=max_breadth,
|
|
@@ -354,7 +345,6 @@ class TavilyClient:
|
|
|
354
345
|
exclude_domains=exclude_domains,
|
|
355
346
|
allow_external=allow_external,
|
|
356
347
|
include_images=include_images,
|
|
357
|
-
categories=categories,
|
|
358
348
|
extract_depth=extract_depth,
|
|
359
349
|
format=format,
|
|
360
350
|
timeout=timeout,
|
|
@@ -375,8 +365,7 @@ class TavilyClient:
|
|
|
375
365
|
exclude_domains: Sequence[str] = None,
|
|
376
366
|
allow_external: bool = None,
|
|
377
367
|
include_images: bool = None,
|
|
378
|
-
|
|
379
|
-
timeout: int = 60,
|
|
368
|
+
timeout: float = 150,
|
|
380
369
|
**kwargs
|
|
381
370
|
) -> dict:
|
|
382
371
|
"""
|
|
@@ -394,7 +383,7 @@ class TavilyClient:
|
|
|
394
383
|
"exclude_domains": exclude_domains,
|
|
395
384
|
"allow_external": allow_external,
|
|
396
385
|
"include_images": include_images,
|
|
397
|
-
"
|
|
386
|
+
"timeout": timeout,
|
|
398
387
|
}
|
|
399
388
|
|
|
400
389
|
if kwargs:
|
|
@@ -402,8 +391,6 @@ class TavilyClient:
|
|
|
402
391
|
|
|
403
392
|
data = {k: v for k, v in data.items() if v is not None}
|
|
404
393
|
|
|
405
|
-
timeout = min(timeout, 120)
|
|
406
|
-
|
|
407
394
|
try:
|
|
408
395
|
response = requests.post(
|
|
409
396
|
self.base_url + "/map", data=json.dumps(data), headers=self.headers, timeout=timeout, proxies=self.proxies)
|
|
@@ -442,15 +429,13 @@ class TavilyClient:
|
|
|
442
429
|
exclude_domains: Sequence[str] = None,
|
|
443
430
|
allow_external: bool = None,
|
|
444
431
|
include_images: bool = None,
|
|
445
|
-
|
|
446
|
-
timeout: int = 60,
|
|
432
|
+
timeout: float = 150,
|
|
447
433
|
**kwargs
|
|
448
434
|
) -> dict:
|
|
449
435
|
"""
|
|
450
436
|
Combined map method.
|
|
451
437
|
|
|
452
438
|
"""
|
|
453
|
-
timeout = min(timeout, 120)
|
|
454
439
|
response_dict = self._map(url,
|
|
455
440
|
max_depth=max_depth,
|
|
456
441
|
max_breadth=max_breadth,
|
|
@@ -462,7 +447,6 @@ class TavilyClient:
|
|
|
462
447
|
exclude_domains=exclude_domains,
|
|
463
448
|
allow_external=allow_external,
|
|
464
449
|
include_images=include_images,
|
|
465
|
-
categories=categories,
|
|
466
450
|
timeout=timeout,
|
|
467
451
|
**kwargs)
|
|
468
452
|
|
|
@@ -477,7 +461,7 @@ class TavilyClient:
|
|
|
477
461
|
include_domains: Sequence[str] = None,
|
|
478
462
|
exclude_domains: Sequence[str] = None,
|
|
479
463
|
max_tokens: int = 4000,
|
|
480
|
-
timeout:
|
|
464
|
+
timeout: float = 60,
|
|
481
465
|
country: str = None,
|
|
482
466
|
include_favicon: bool = None,
|
|
483
467
|
**kwargs, # Accept custom arguments
|
|
@@ -490,7 +474,7 @@ class TavilyClient:
|
|
|
490
474
|
|
|
491
475
|
Returns a string of JSON containing the search context up to context limit.
|
|
492
476
|
"""
|
|
493
|
-
|
|
477
|
+
|
|
494
478
|
response_dict = self._search(query,
|
|
495
479
|
search_depth=search_depth,
|
|
496
480
|
topic=topic,
|
|
@@ -519,7 +503,7 @@ class TavilyClient:
|
|
|
519
503
|
max_results: int = 5,
|
|
520
504
|
include_domains: Sequence[str] = None,
|
|
521
505
|
exclude_domains: Sequence[str] = None,
|
|
522
|
-
timeout:
|
|
506
|
+
timeout: float = 60,
|
|
523
507
|
country: str = None,
|
|
524
508
|
include_favicon: bool = None,
|
|
525
509
|
**kwargs, # Accept custom arguments
|
|
@@ -527,7 +511,6 @@ class TavilyClient:
|
|
|
527
511
|
"""
|
|
528
512
|
Q&A search method. Search depth is advanced by default to get the best answer.
|
|
529
513
|
"""
|
|
530
|
-
timeout = min(timeout, 120)
|
|
531
514
|
response_dict = self._search(query,
|
|
532
515
|
search_depth=search_depth,
|
|
533
516
|
topic=topic,
|
|
@@ -550,11 +533,10 @@ class TavilyClient:
|
|
|
550
533
|
search_depth: Literal["basic",
|
|
551
534
|
"advanced"] = "advanced",
|
|
552
535
|
max_results: int = 5,
|
|
553
|
-
timeout:
|
|
536
|
+
timeout: float = 60,
|
|
554
537
|
country: str = None,
|
|
555
538
|
) -> Sequence[dict]:
|
|
556
539
|
""" Company information search method. Search depth is advanced by default to get the best answer. """
|
|
557
|
-
timeout = min(timeout, 120)
|
|
558
540
|
def _perform_search(topic):
|
|
559
541
|
return self._search(query,
|
|
560
542
|
search_depth=search_depth,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tavily-python
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.13
|
|
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
|
|
@@ -14,9 +14,7 @@ tavily_python.egg-info/SOURCES.txt
|
|
|
14
14
|
tavily_python.egg-info/dependency_links.txt
|
|
15
15
|
tavily_python.egg-info/requires.txt
|
|
16
16
|
tavily_python.egg-info/top_level.txt
|
|
17
|
-
tests/test_async_search.py
|
|
18
17
|
tests/test_crawl.py
|
|
19
18
|
tests/test_errors.py
|
|
20
19
|
tests/test_map.py
|
|
21
|
-
tests/test_search.py
|
|
22
|
-
tests/test_sync_search.py
|
|
20
|
+
tests/test_search.py
|
|
@@ -40,7 +40,6 @@ def validate_specific(request, response):
|
|
|
40
40
|
"exclude_domains": ["example.com"],
|
|
41
41
|
"allow_external": False,
|
|
42
42
|
"include_images": True,
|
|
43
|
-
"categories": ["pricing", "documentation"],
|
|
44
43
|
"extract_depth": "advanced"
|
|
45
44
|
}.items():
|
|
46
45
|
assert request_json.get(key) == value
|
|
@@ -67,7 +66,6 @@ def test_sync_crawl_specific(sync_interceptor, sync_client):
|
|
|
67
66
|
exclude_domains=["example.com"],
|
|
68
67
|
allow_external=False,
|
|
69
68
|
include_images=True,
|
|
70
|
-
categories=["pricing", "documentation"],
|
|
71
69
|
extract_depth="advanced",
|
|
72
70
|
timeout=10
|
|
73
71
|
)
|
|
@@ -95,7 +93,6 @@ def test_async_crawl_specific(async_interceptor, async_client):
|
|
|
95
93
|
exclude_domains=["example.com"],
|
|
96
94
|
allow_external=False,
|
|
97
95
|
include_images=True,
|
|
98
|
-
categories=["pricing", "documentation"],
|
|
99
96
|
extract_depth="advanced",
|
|
100
97
|
timeout=10
|
|
101
98
|
))
|
|
@@ -38,8 +38,7 @@ def validate_specific(request, response):
|
|
|
38
38
|
"exclude_paths": ["/blog"],
|
|
39
39
|
"exclude_domains": ["example.com"],
|
|
40
40
|
"allow_external": False,
|
|
41
|
-
"include_images": True
|
|
42
|
-
"categories": ["pricing", "documentation"]
|
|
41
|
+
"include_images": True
|
|
43
42
|
}.items():
|
|
44
43
|
assert request_json.get(key) == value
|
|
45
44
|
|
|
@@ -65,7 +64,6 @@ def test_sync_map_specific(sync_interceptor, sync_client):
|
|
|
65
64
|
exclude_domains=["example.com"],
|
|
66
65
|
allow_external=False,
|
|
67
66
|
include_images=True,
|
|
68
|
-
categories=["pricing", "documentation"],
|
|
69
67
|
timeout=10
|
|
70
68
|
)
|
|
71
69
|
|
|
@@ -92,7 +90,6 @@ def test_async_map_specific(async_interceptor, async_client):
|
|
|
92
90
|
exclude_domains=["example.com"],
|
|
93
91
|
allow_external=False,
|
|
94
92
|
include_images=True,
|
|
95
|
-
categories=["pricing", "documentation"],
|
|
96
93
|
timeout=10
|
|
97
94
|
))
|
|
98
95
|
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
from typing import Literal
|
|
2
|
-
|
|
3
|
-
DEFAULT_MODEL_ENCODING = "gpt-3.5-turbo"
|
|
4
|
-
DEFAULT_MAX_TOKENS = 4000
|
|
5
|
-
|
|
6
|
-
# Create a type that represents all allowed categories
|
|
7
|
-
AllowedCategory = Literal[
|
|
8
|
-
"Documentation", "Blog", "Blogs", "Community", "About", "Contact",
|
|
9
|
-
"Privacy", "Terms", "Status", "Pricing", "Enterprise", "Careers",
|
|
10
|
-
"E-Commerce", "Authentication", "Developer", "Developers", "Solutions",
|
|
11
|
-
"Partners", "Downloads", "Media", "Events", "People"
|
|
12
|
-
]
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
import os
|
|
3
|
-
from tavily import AsyncTavilyClient, MissingAPIKeyError, InvalidAPIKeyError
|
|
4
|
-
from urllib.parse import urlparse
|
|
5
|
-
import asyncio
|
|
6
|
-
|
|
7
|
-
from unit_tests import cases
|
|
8
|
-
class SearchTest(unittest.TestCase):
|
|
9
|
-
|
|
10
|
-
def setUp(self) -> None:
|
|
11
|
-
self.tavily_client = AsyncTavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
|
|
12
|
-
return super().setUp()
|
|
13
|
-
|
|
14
|
-
def tearDown(self) -> None:
|
|
15
|
-
return super().tearDown()
|
|
16
|
-
|
|
17
|
-
# Every single search result should have these properties
|
|
18
|
-
def common_search_result_properties(self, result) -> None:
|
|
19
|
-
self.assertIsInstance(result, dict)
|
|
20
|
-
self.assertIn("title", result)
|
|
21
|
-
self.assertIn("url", result)
|
|
22
|
-
self.assertIn("content", result)
|
|
23
|
-
self.assertIn("score", result)
|
|
24
|
-
|
|
25
|
-
# General search results should have these properties
|
|
26
|
-
def general_search_result_properties(self, result) -> None:
|
|
27
|
-
self.common_search_result_properties(result)
|
|
28
|
-
self.assertIn("raw_content", result)
|
|
29
|
-
|
|
30
|
-
# News search results should have these properties
|
|
31
|
-
def news_search_result_properties(self, result) -> None:
|
|
32
|
-
self.common_search_result_properties(result)
|
|
33
|
-
self.assertIn("published_date", result)
|
|
34
|
-
|
|
35
|
-
# Topic-specific properties
|
|
36
|
-
def topic_specific_properties(self, result, **params) -> None:
|
|
37
|
-
if params.get("topic", "general") == "general":
|
|
38
|
-
self.assertIn("raw_content", result)
|
|
39
|
-
elif params.get("topic", "general") == "news":
|
|
40
|
-
self.assertIn("published_date", result)
|
|
41
|
-
|
|
42
|
-
# Domain inclusion/exclusion-dependent properties
|
|
43
|
-
def domain_dependent_properties(self, response, **params) -> None:
|
|
44
|
-
if params.get("topic", "general") != "general":
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
if params.get("include_domains", False) and len(params["include_domains"]) > 0:
|
|
48
|
-
for result in response["results"]:
|
|
49
|
-
self.assertTrue(any(domain in urlparse(result["url"]).netloc for domain in params["include_domains"]))
|
|
50
|
-
|
|
51
|
-
if params.get("exclude_domains", False) and len(params["exclude_domains"]) > 0:
|
|
52
|
-
for result in response["results"]:
|
|
53
|
-
self.assertFalse(any(domain in urlparse(result["url"]).netloc for domain in params["exclude_domains"]))
|
|
54
|
-
|
|
55
|
-
# Image-dependent properties
|
|
56
|
-
def image_properties(self, response, **params) -> None:
|
|
57
|
-
if params.get("topic", "general") == "general" and params.get("include_images", False):
|
|
58
|
-
self.assertIsNotNone(response["images"])
|
|
59
|
-
self.assertIsInstance(response["images"], list)
|
|
60
|
-
for image in response["images"]:
|
|
61
|
-
self.assertIsInstance(image, str)
|
|
62
|
-
|
|
63
|
-
# Answer-dependent properties
|
|
64
|
-
def answer_properties(self, response, **params) -> None:
|
|
65
|
-
if params.get("include_answer", False):
|
|
66
|
-
self.assertIn("answer", response)
|
|
67
|
-
self.assertIsInstance(response["answer"], str)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# Every single search response should have these properties
|
|
71
|
-
def common_response_properties(self, response) -> None:
|
|
72
|
-
self.assertIsNotNone(response)
|
|
73
|
-
self.assertIsInstance(response, dict)
|
|
74
|
-
self.assertIn("answer", response)
|
|
75
|
-
self.assertIn("query", response)
|
|
76
|
-
self.assertIn("results", response)
|
|
77
|
-
self.assertIn("images", response)
|
|
78
|
-
self.assertIn("response_time", response)
|
|
79
|
-
self.assertIn("follow_up_questions", response)
|
|
80
|
-
|
|
81
|
-
self.assertIsNotNone(response["query"])
|
|
82
|
-
self.assertIsNotNone(response["results"])
|
|
83
|
-
self.assertIsNotNone(response["response_time"])
|
|
84
|
-
self.assertIsNotNone(response["images"])
|
|
85
|
-
|
|
86
|
-
self.assertIsInstance(response["query"], str)
|
|
87
|
-
self.assertIsInstance(response["results"], list)
|
|
88
|
-
self.assertIsInstance(response["response_time"], float)
|
|
89
|
-
self.assertIsInstance(response["images"], list)
|
|
90
|
-
|
|
91
|
-
# Search responses also have properties that depend on the request params
|
|
92
|
-
def custom_response_properties(self, response, **params) -> None:
|
|
93
|
-
self.domain_dependent_properties(response, **params)
|
|
94
|
-
self.image_properties(response, **params)
|
|
95
|
-
self.answer_properties(response, **params)
|
|
96
|
-
for result in response["results"]:
|
|
97
|
-
self.topic_specific_properties(result, **params)
|
|
98
|
-
|
|
99
|
-
def test_internal_search(self) -> None:
|
|
100
|
-
for test_case in cases:
|
|
101
|
-
with self.subTest(msg=test_case["name"]):
|
|
102
|
-
response = asyncio.run(self.tavily_client._search(**test_case["params"]))
|
|
103
|
-
self.common_response_properties(response)
|
|
104
|
-
if test_case["params"].get("topic", "general") == "general":
|
|
105
|
-
for search_result in response["results"]:
|
|
106
|
-
self.general_search_result_properties(search_result)
|
|
107
|
-
elif test_case["params"].get("topic", "general") == "news":
|
|
108
|
-
for search_result in response["results"]:
|
|
109
|
-
self.news_search_result_properties(search_result)
|
|
110
|
-
self.custom_response_properties(response, **test_case["params"])
|
|
111
|
-
|
|
112
|
-
def test_external_search(self) -> None:
|
|
113
|
-
for test_case in cases:
|
|
114
|
-
with self.subTest(msg=test_case["name"]):
|
|
115
|
-
response = asyncio.run(self.tavily_client.search(**test_case["params"]))
|
|
116
|
-
self.common_response_properties(response)
|
|
117
|
-
if test_case["params"].get("topic", "general") == "general":
|
|
118
|
-
for search_result in response["results"]:
|
|
119
|
-
self.general_search_result_properties(search_result)
|
|
120
|
-
elif test_case["params"].get("topic", "general") == "news":
|
|
121
|
-
for search_result in response["results"]:
|
|
122
|
-
self.news_search_result_properties(search_result)
|
|
123
|
-
self.custom_response_properties(response, **test_case["params"])
|
|
124
|
-
|
|
125
|
-
class QNASearchTest(unittest.TestCase):
|
|
126
|
-
|
|
127
|
-
def setUp(self) -> None:
|
|
128
|
-
self.tavily_client = AsyncTavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
|
|
129
|
-
return super().setUp()
|
|
130
|
-
|
|
131
|
-
def tearDown(self) -> None:
|
|
132
|
-
return super().tearDown()
|
|
133
|
-
|
|
134
|
-
def test_qna_search(self) -> None:
|
|
135
|
-
for test_case in cases:
|
|
136
|
-
if "include_answer" in test_case["params"]:
|
|
137
|
-
del test_case["params"]["include_answer"]
|
|
138
|
-
if "include_raw_content" in test_case["params"]:
|
|
139
|
-
del test_case["params"]["include_raw_content"]
|
|
140
|
-
if "include_images" in test_case["params"]:
|
|
141
|
-
del test_case["params"]["include_images"]
|
|
142
|
-
with self.subTest(msg=test_case["name"]):
|
|
143
|
-
response = asyncio.run(self.tavily_client.qna_search(**test_case["params"]))
|
|
144
|
-
self.assertIsNotNone(response)
|
|
145
|
-
self.assertIsInstance(response, str)
|
|
146
|
-
self.assertTrue(len(response) > 0)
|
|
147
|
-
|
|
148
|
-
class CompanyInfoSearchTest(unittest.TestCase):
|
|
149
|
-
|
|
150
|
-
def setUp(self) -> None:
|
|
151
|
-
self.tavily_client = AsyncTavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
|
|
152
|
-
return super().setUp()
|
|
153
|
-
|
|
154
|
-
def tearDown(self) -> None:
|
|
155
|
-
return super().tearDown()
|
|
156
|
-
|
|
157
|
-
# Every single search result should have these properties
|
|
158
|
-
def common_search_result_properties(self, result) -> None:
|
|
159
|
-
self.assertIsInstance(result, dict)
|
|
160
|
-
self.assertIn("title", result)
|
|
161
|
-
self.assertIn("url", result)
|
|
162
|
-
self.assertIn("content", result)
|
|
163
|
-
self.assertIn("score", result)
|
|
164
|
-
|
|
165
|
-
def test_company_info_search(self) -> None:
|
|
166
|
-
for test_case in cases:
|
|
167
|
-
if "topic" in test_case["params"]:
|
|
168
|
-
del test_case["params"]["topic"]
|
|
169
|
-
if "include_domains" in test_case["params"]:
|
|
170
|
-
del test_case["params"]["include_domains"]
|
|
171
|
-
if "exclude_domains" in test_case["params"]:
|
|
172
|
-
del test_case["params"]["exclude_domains"]
|
|
173
|
-
if "include_raw_content" in test_case["params"]:
|
|
174
|
-
del test_case["params"]["include_raw_content"]
|
|
175
|
-
if "include_images" in test_case["params"]:
|
|
176
|
-
del test_case["params"]["include_images"]
|
|
177
|
-
if "include_answer" in test_case["params"]:
|
|
178
|
-
del test_case["params"]["include_answer"]
|
|
179
|
-
if "use_cache" in test_case["params"]:
|
|
180
|
-
del test_case["params"]["use_cache"]
|
|
181
|
-
with self.subTest(msg=test_case["name"]):
|
|
182
|
-
response = asyncio.run(self.tavily_client.get_company_info(**test_case["params"]))
|
|
183
|
-
self.assertIsNotNone(response)
|
|
184
|
-
self.assertIsInstance(response, list)
|
|
185
|
-
self.assertTrue(len(response) > 0)
|
|
186
|
-
for search_result in response:
|
|
187
|
-
self.common_search_result_properties(search_result)
|
|
188
|
-
|
|
189
|
-
class ErrorTest(unittest.TestCase):
|
|
190
|
-
|
|
191
|
-
def setUp(self) -> None:
|
|
192
|
-
return super().setUp()
|
|
193
|
-
|
|
194
|
-
def tearDown(self) -> None:
|
|
195
|
-
return super().tearDown()
|
|
196
|
-
|
|
197
|
-
# This test is here to ensure that no MissingAPIKeyError is raised when the API key is in the environment
|
|
198
|
-
def test_load_key_from_env(self) -> None:
|
|
199
|
-
self.assertIn('results', asyncio.run(AsyncTavilyClient().search("Why is Tavily the best search API?")))
|
|
200
|
-
|
|
201
|
-
def test_missing_api_key(self) -> None:
|
|
202
|
-
with self.assertRaises(MissingAPIKeyError):
|
|
203
|
-
AsyncTavilyClient(api_key='')
|
|
204
|
-
|
|
205
|
-
old_key = os.getenv("TAVILY_API_KEY")
|
|
206
|
-
del os.environ["TAVILY_API_KEY"]
|
|
207
|
-
with self.assertRaises(MissingAPIKeyError):
|
|
208
|
-
AsyncTavilyClient()
|
|
209
|
-
|
|
210
|
-
os.environ["TAVILY_API_KEY"] = old_key
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def test_invalid_api_key(self) -> None:
|
|
214
|
-
with self.assertRaises(InvalidAPIKeyError):
|
|
215
|
-
asyncio.run(AsyncTavilyClient(api_key="invalid_api_key").search("Why is Tavily the best search API?"))
|
|
216
|
-
|
|
217
|
-
if __name__ == "__main__":
|
|
218
|
-
|
|
219
|
-
unittest.main()
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
import os
|
|
3
|
-
from tavily import TavilyClient, InvalidAPIKeyError, MissingAPIKeyError
|
|
4
|
-
from urllib.parse import urlparse
|
|
5
|
-
|
|
6
|
-
from unit_tests import cases
|
|
7
|
-
|
|
8
|
-
class SearchTest(unittest.TestCase):
|
|
9
|
-
|
|
10
|
-
def setUp(self) -> None:
|
|
11
|
-
self.tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
|
|
12
|
-
return super().setUp()
|
|
13
|
-
|
|
14
|
-
def tearDown(self) -> None:
|
|
15
|
-
return super().tearDown()
|
|
16
|
-
|
|
17
|
-
# Every single search result should have these properties
|
|
18
|
-
def common_search_result_properties(self, result) -> None:
|
|
19
|
-
self.assertIsInstance(result, dict)
|
|
20
|
-
self.assertIn("title", result)
|
|
21
|
-
self.assertIn("url", result)
|
|
22
|
-
self.assertIn("content", result)
|
|
23
|
-
self.assertIn("score", result)
|
|
24
|
-
|
|
25
|
-
# General search results should have these properties
|
|
26
|
-
def general_search_result_properties(self, result) -> None:
|
|
27
|
-
self.common_search_result_properties(result)
|
|
28
|
-
self.assertIn("raw_content", result)
|
|
29
|
-
|
|
30
|
-
# News search results should have these properties
|
|
31
|
-
def news_search_result_properties(self, result) -> None:
|
|
32
|
-
self.common_search_result_properties(result)
|
|
33
|
-
self.assertIn("published_date", result)
|
|
34
|
-
|
|
35
|
-
# Topic-specific properties
|
|
36
|
-
def topic_specific_properties(self, result, **params) -> None:
|
|
37
|
-
if params.get("topic", "general") == "general":
|
|
38
|
-
self.assertIn("raw_content", result)
|
|
39
|
-
elif params.get("topic", "general") == "news":
|
|
40
|
-
self.assertIn("published_date", result)
|
|
41
|
-
|
|
42
|
-
# Domain inclusion/exclusion-dependent properties
|
|
43
|
-
def domain_dependent_properties(self, response, **params) -> None:
|
|
44
|
-
if params.get("topic", "general") != "general":
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
if params.get("include_domains", False) and len(params["include_domains"]) > 0:
|
|
48
|
-
for result in response["results"]:
|
|
49
|
-
self.assertTrue(any(domain in urlparse(result["url"]).netloc for domain in params["include_domains"]))
|
|
50
|
-
|
|
51
|
-
if params.get("exclude_domains", False) and len(params["exclude_domains"]) > 0:
|
|
52
|
-
for result in response["results"]:
|
|
53
|
-
self.assertFalse(any(domain in urlparse(result["url"]).netloc for domain in params["exclude_domains"]))
|
|
54
|
-
|
|
55
|
-
# Image-dependent properties
|
|
56
|
-
def image_properties(self, response, **params) -> None:
|
|
57
|
-
if params.get("topic", "general") == "general" and params.get("include_images", False):
|
|
58
|
-
self.assertIsNotNone(response["images"])
|
|
59
|
-
self.assertIsInstance(response["images"], list)
|
|
60
|
-
for image in response["images"]:
|
|
61
|
-
self.assertIsInstance(image, str)
|
|
62
|
-
|
|
63
|
-
# Answer-dependent properties
|
|
64
|
-
def answer_properties(self, response, **params) -> None:
|
|
65
|
-
if params.get("include_answer", False):
|
|
66
|
-
self.assertIn("answer", response)
|
|
67
|
-
self.assertIsInstance(response["answer"], str)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# Every single search response should have these properties
|
|
71
|
-
def common_response_properties(self, response) -> None:
|
|
72
|
-
self.assertIsNotNone(response)
|
|
73
|
-
self.assertIsInstance(response, dict)
|
|
74
|
-
self.assertIn("answer", response)
|
|
75
|
-
self.assertIn("query", response)
|
|
76
|
-
self.assertIn("results", response)
|
|
77
|
-
self.assertIn("images", response)
|
|
78
|
-
self.assertIn("response_time", response)
|
|
79
|
-
self.assertIn("follow_up_questions", response)
|
|
80
|
-
|
|
81
|
-
self.assertIsNotNone(response["query"])
|
|
82
|
-
self.assertIsNotNone(response["results"])
|
|
83
|
-
self.assertIsNotNone(response["response_time"])
|
|
84
|
-
self.assertIsNotNone(response["images"])
|
|
85
|
-
|
|
86
|
-
self.assertIsInstance(response["query"], str)
|
|
87
|
-
self.assertIsInstance(response["results"], list)
|
|
88
|
-
self.assertIsInstance(response["response_time"], float)
|
|
89
|
-
self.assertIsInstance(response["images"], list)
|
|
90
|
-
|
|
91
|
-
# Search responses also have properties that depend on the request params
|
|
92
|
-
def custom_response_properties(self, response, **params) -> None:
|
|
93
|
-
self.domain_dependent_properties(response, **params)
|
|
94
|
-
self.image_properties(response, **params)
|
|
95
|
-
self.answer_properties(response, **params)
|
|
96
|
-
for result in response["results"]:
|
|
97
|
-
self.topic_specific_properties(result, **params)
|
|
98
|
-
|
|
99
|
-
def test_internal_search(self) -> None:
|
|
100
|
-
for test_case in cases:
|
|
101
|
-
with self.subTest(msg=test_case["name"]):
|
|
102
|
-
result = self.tavily_client._search(**test_case["params"])
|
|
103
|
-
self.common_response_properties(result)
|
|
104
|
-
if test_case["params"].get("topic", "general") == "general":
|
|
105
|
-
for search_result in result["results"]:
|
|
106
|
-
self.general_search_result_properties(search_result)
|
|
107
|
-
elif test_case["params"].get("topic", "general") == "news":
|
|
108
|
-
for search_result in result["results"]:
|
|
109
|
-
self.news_search_result_properties(search_result)
|
|
110
|
-
self.custom_response_properties(result, **test_case["params"])
|
|
111
|
-
|
|
112
|
-
def test_external_search(self) -> None:
|
|
113
|
-
for test_case in cases:
|
|
114
|
-
with self.subTest(msg=test_case["name"]):
|
|
115
|
-
response = self.tavily_client._search(**test_case["params"])
|
|
116
|
-
self.common_response_properties(response)
|
|
117
|
-
if test_case["params"].get("topic", "general") == "general":
|
|
118
|
-
for search_result in response["results"]:
|
|
119
|
-
self.general_search_result_properties(search_result)
|
|
120
|
-
elif test_case["params"].get("topic", "general") == "news":
|
|
121
|
-
for search_result in response["results"]:
|
|
122
|
-
self.news_search_result_properties(search_result)
|
|
123
|
-
self.custom_response_properties(response, **test_case["params"])
|
|
124
|
-
|
|
125
|
-
class QNASearchTest(unittest.TestCase):
|
|
126
|
-
|
|
127
|
-
def setUp(self) -> None:
|
|
128
|
-
self.tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
|
|
129
|
-
return super().setUp()
|
|
130
|
-
|
|
131
|
-
def tearDown(self) -> None:
|
|
132
|
-
return super().tearDown()
|
|
133
|
-
|
|
134
|
-
def test_qna_search(self) -> None:
|
|
135
|
-
for test_case in cases:
|
|
136
|
-
if "include_answer" in test_case["params"]:
|
|
137
|
-
del test_case["params"]["include_answer"]
|
|
138
|
-
if "include_raw_content" in test_case["params"]:
|
|
139
|
-
del test_case["params"]["include_raw_content"]
|
|
140
|
-
if "include_images" in test_case["params"]:
|
|
141
|
-
del test_case["params"]["include_images"]
|
|
142
|
-
with self.subTest(msg=test_case["name"]):
|
|
143
|
-
response = self.tavily_client.qna_search(**test_case["params"])
|
|
144
|
-
self.assertIsNotNone(response)
|
|
145
|
-
self.assertIsInstance(response, str)
|
|
146
|
-
self.assertTrue(len(response) > 0)
|
|
147
|
-
|
|
148
|
-
class CompanyInfoSearchTest(unittest.TestCase):
|
|
149
|
-
|
|
150
|
-
def setUp(self) -> None:
|
|
151
|
-
self.tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
|
|
152
|
-
return super().setUp()
|
|
153
|
-
|
|
154
|
-
def tearDown(self) -> None:
|
|
155
|
-
return super().tearDown()
|
|
156
|
-
|
|
157
|
-
# Every single search result should have these properties
|
|
158
|
-
def common_search_result_properties(self, result) -> None:
|
|
159
|
-
self.assertIsInstance(result, dict)
|
|
160
|
-
self.assertIn("title", result)
|
|
161
|
-
self.assertIn("url", result)
|
|
162
|
-
self.assertIn("content", result)
|
|
163
|
-
self.assertIn("score", result)
|
|
164
|
-
|
|
165
|
-
def test_company_info_search(self) -> None:
|
|
166
|
-
for test_case in cases:
|
|
167
|
-
if "topic" in test_case["params"]:
|
|
168
|
-
del test_case["params"]["topic"]
|
|
169
|
-
if "include_domains" in test_case["params"]:
|
|
170
|
-
del test_case["params"]["include_domains"]
|
|
171
|
-
if "exclude_domains" in test_case["params"]:
|
|
172
|
-
del test_case["params"]["exclude_domains"]
|
|
173
|
-
if "include_raw_content" in test_case["params"]:
|
|
174
|
-
del test_case["params"]["include_raw_content"]
|
|
175
|
-
if "include_images" in test_case["params"]:
|
|
176
|
-
del test_case["params"]["include_images"]
|
|
177
|
-
if "include_answer" in test_case["params"]:
|
|
178
|
-
del test_case["params"]["include_answer"]
|
|
179
|
-
if "use_cache" in test_case["params"]:
|
|
180
|
-
del test_case["params"]["use_cache"]
|
|
181
|
-
with self.subTest(msg=test_case["name"]):
|
|
182
|
-
response = self.tavily_client.get_company_info(**test_case["params"])
|
|
183
|
-
self.assertIsNotNone(response)
|
|
184
|
-
self.assertIsInstance(response, list)
|
|
185
|
-
self.assertTrue(len(response) > 0)
|
|
186
|
-
for search_result in response:
|
|
187
|
-
self.common_search_result_properties(search_result)
|
|
188
|
-
|
|
189
|
-
class ErrorTest(unittest.TestCase):
|
|
190
|
-
|
|
191
|
-
def setUp(self) -> None:
|
|
192
|
-
return super().setUp()
|
|
193
|
-
|
|
194
|
-
def tearDown(self) -> None:
|
|
195
|
-
return super().tearDown()
|
|
196
|
-
|
|
197
|
-
# This test is here to ensure that no MissingAPIKeyError is raised when the API key is in the environment
|
|
198
|
-
def test_load_key_from_env(self) -> None:
|
|
199
|
-
self.assertIn('results', TavilyClient().search("Why is Tavily the best search API?"))
|
|
200
|
-
|
|
201
|
-
def test_missing_api_key(self) -> None:
|
|
202
|
-
with self.assertRaises(MissingAPIKeyError):
|
|
203
|
-
TavilyClient(api_key='')
|
|
204
|
-
|
|
205
|
-
old_key = os.getenv("TAVILY_API_KEY")
|
|
206
|
-
del os.environ["TAVILY_API_KEY"]
|
|
207
|
-
with self.assertRaises(MissingAPIKeyError):
|
|
208
|
-
TavilyClient()
|
|
209
|
-
|
|
210
|
-
os.environ["TAVILY_API_KEY"] = old_key
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def test_invalid_api_key(self) -> None:
|
|
214
|
-
with self.assertRaises(InvalidAPIKeyError):
|
|
215
|
-
TavilyClient(api_key="invalid_api_key").search("Why is Tavily the best search API?")
|
|
216
|
-
|
|
217
|
-
if __name__ == "__main__":
|
|
218
|
-
|
|
219
|
-
unittest.main()
|
|
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
|