exa-py 1.14.13__py3-none-any.whl → 1.14.15__py3-none-any.whl
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.
Potentially problematic release.
This version of exa-py might be problematic. Click here for more details.
- exa_py/api.py +572 -244
- exa_py/research/client.py +24 -17
- exa_py/utils.py +139 -23
- exa_py/websets/core/base.py +3 -3
- exa_py/websets/types.py +123 -126
- {exa_py-1.14.13.dist-info → exa_py-1.14.15.dist-info}/METADATA +1 -1
- {exa_py-1.14.13.dist-info → exa_py-1.14.15.dist-info}/RECORD +8 -8
- {exa_py-1.14.13.dist-info → exa_py-1.14.15.dist-info}/WHEEL +0 -0
exa_py/api.py
CHANGED
|
@@ -32,14 +32,18 @@ from typing_extensions import TypedDict
|
|
|
32
32
|
|
|
33
33
|
from exa_py.utils import (
|
|
34
34
|
ExaOpenAICompletion,
|
|
35
|
+
_convert_schema_input,
|
|
36
|
+
_get_package_version,
|
|
35
37
|
add_message_to_messages,
|
|
36
38
|
format_exa_result,
|
|
37
39
|
maybe_get_query,
|
|
40
|
+
JSONSchemaInput,
|
|
38
41
|
)
|
|
39
42
|
from .websets import WebsetsClient
|
|
40
43
|
from .websets.core.base import ExaJSONEncoder
|
|
41
44
|
from .research.client import ResearchClient, AsyncResearchClient
|
|
42
45
|
|
|
46
|
+
|
|
43
47
|
is_beta = os.getenv("IS_BETA") == "True"
|
|
44
48
|
|
|
45
49
|
|
|
@@ -62,7 +66,7 @@ def snake_to_camel(snake_str: str) -> str:
|
|
|
62
66
|
return components[0] + "".join(x.title() for x in components[1:])
|
|
63
67
|
|
|
64
68
|
|
|
65
|
-
def to_camel_case(data: dict) -> dict:
|
|
69
|
+
def to_camel_case(data: dict, skip_keys: list[str] = []) -> dict:
|
|
66
70
|
"""
|
|
67
71
|
Convert keys in a dictionary from snake_case to camelCase recursively.
|
|
68
72
|
|
|
@@ -74,7 +78,9 @@ def to_camel_case(data: dict) -> dict:
|
|
|
74
78
|
"""
|
|
75
79
|
if isinstance(data, dict):
|
|
76
80
|
return {
|
|
77
|
-
snake_to_camel(k): to_camel_case(v
|
|
81
|
+
snake_to_camel(k): to_camel_case(v, skip_keys)
|
|
82
|
+
if isinstance(v, dict) and k not in skip_keys
|
|
83
|
+
else v
|
|
78
84
|
for k, v in data.items()
|
|
79
85
|
if v is not None
|
|
80
86
|
}
|
|
@@ -261,6 +267,11 @@ class HighlightsContentsOptions(TypedDict, total=False):
|
|
|
261
267
|
|
|
262
268
|
class JSONSchema(TypedDict, total=False):
|
|
263
269
|
"""Represents a JSON Schema definition used for structured summary output.
|
|
270
|
+
|
|
271
|
+
.. deprecated:: 1.15.0
|
|
272
|
+
Use Pydantic models or dict[str, Any] directly instead.
|
|
273
|
+
This will be removed in a future version.
|
|
274
|
+
|
|
264
275
|
To learn more visit https://json-schema.org/overview/what-is-jsonschema.
|
|
265
276
|
"""
|
|
266
277
|
|
|
@@ -286,11 +297,12 @@ class SummaryContentsOptions(TypedDict, total=False):
|
|
|
286
297
|
|
|
287
298
|
Attributes:
|
|
288
299
|
query (str): The query string for the summary. Summary will bias towards answering the query.
|
|
289
|
-
schema (
|
|
300
|
+
schema (Union[BaseModel, dict[str, Any]]): JSON schema for structured output from summary.
|
|
301
|
+
Can be a Pydantic model (automatically converted) or a dict containing JSON Schema.
|
|
290
302
|
"""
|
|
291
303
|
|
|
292
304
|
query: str
|
|
293
|
-
schema:
|
|
305
|
+
schema: JSONSchemaInput
|
|
294
306
|
|
|
295
307
|
|
|
296
308
|
class ContextContentsOptions(TypedDict, total=False):
|
|
@@ -362,7 +374,19 @@ class _Result:
|
|
|
362
374
|
subpages: Optional[List[_Result]] = None
|
|
363
375
|
extras: Optional[Dict] = None
|
|
364
376
|
|
|
365
|
-
def __init__(
|
|
377
|
+
def __init__(
|
|
378
|
+
self,
|
|
379
|
+
url,
|
|
380
|
+
id,
|
|
381
|
+
title=None,
|
|
382
|
+
score=None,
|
|
383
|
+
published_date=None,
|
|
384
|
+
author=None,
|
|
385
|
+
image=None,
|
|
386
|
+
favicon=None,
|
|
387
|
+
subpages=None,
|
|
388
|
+
extras=None,
|
|
389
|
+
):
|
|
366
390
|
self.url = url
|
|
367
391
|
self.id = id
|
|
368
392
|
self.title = title
|
|
@@ -406,8 +430,35 @@ class Result(_Result):
|
|
|
406
430
|
highlight_scores: Optional[List[float]] = None
|
|
407
431
|
summary: Optional[str] = None
|
|
408
432
|
|
|
409
|
-
def __init__(
|
|
410
|
-
|
|
433
|
+
def __init__(
|
|
434
|
+
self,
|
|
435
|
+
url,
|
|
436
|
+
id,
|
|
437
|
+
title=None,
|
|
438
|
+
score=None,
|
|
439
|
+
published_date=None,
|
|
440
|
+
author=None,
|
|
441
|
+
image=None,
|
|
442
|
+
favicon=None,
|
|
443
|
+
subpages=None,
|
|
444
|
+
extras=None,
|
|
445
|
+
text=None,
|
|
446
|
+
highlights=None,
|
|
447
|
+
highlight_scores=None,
|
|
448
|
+
summary=None,
|
|
449
|
+
):
|
|
450
|
+
super().__init__(
|
|
451
|
+
url,
|
|
452
|
+
id,
|
|
453
|
+
title,
|
|
454
|
+
score,
|
|
455
|
+
published_date,
|
|
456
|
+
author,
|
|
457
|
+
image,
|
|
458
|
+
favicon,
|
|
459
|
+
subpages,
|
|
460
|
+
extras,
|
|
461
|
+
)
|
|
411
462
|
self.text = text
|
|
412
463
|
self.highlights = highlights
|
|
413
464
|
self.highlight_scores = highlight_scores
|
|
@@ -434,8 +485,32 @@ class ResultWithText(_Result):
|
|
|
434
485
|
|
|
435
486
|
text: str = dataclasses.field(default_factory=str)
|
|
436
487
|
|
|
437
|
-
def __init__(
|
|
438
|
-
|
|
488
|
+
def __init__(
|
|
489
|
+
self,
|
|
490
|
+
url,
|
|
491
|
+
id,
|
|
492
|
+
title=None,
|
|
493
|
+
score=None,
|
|
494
|
+
published_date=None,
|
|
495
|
+
author=None,
|
|
496
|
+
image=None,
|
|
497
|
+
favicon=None,
|
|
498
|
+
subpages=None,
|
|
499
|
+
extras=None,
|
|
500
|
+
text="",
|
|
501
|
+
):
|
|
502
|
+
super().__init__(
|
|
503
|
+
url,
|
|
504
|
+
id,
|
|
505
|
+
title,
|
|
506
|
+
score,
|
|
507
|
+
published_date,
|
|
508
|
+
author,
|
|
509
|
+
image,
|
|
510
|
+
favicon,
|
|
511
|
+
subpages,
|
|
512
|
+
extras,
|
|
513
|
+
)
|
|
439
514
|
self.text = text
|
|
440
515
|
|
|
441
516
|
def __str__(self):
|
|
@@ -456,8 +531,33 @@ class ResultWithHighlights(_Result):
|
|
|
456
531
|
highlights: List[str] = dataclasses.field(default_factory=list)
|
|
457
532
|
highlight_scores: List[float] = dataclasses.field(default_factory=list)
|
|
458
533
|
|
|
459
|
-
def __init__(
|
|
460
|
-
|
|
534
|
+
def __init__(
|
|
535
|
+
self,
|
|
536
|
+
url,
|
|
537
|
+
id,
|
|
538
|
+
title=None,
|
|
539
|
+
score=None,
|
|
540
|
+
published_date=None,
|
|
541
|
+
author=None,
|
|
542
|
+
image=None,
|
|
543
|
+
favicon=None,
|
|
544
|
+
subpages=None,
|
|
545
|
+
extras=None,
|
|
546
|
+
highlights=None,
|
|
547
|
+
highlight_scores=None,
|
|
548
|
+
):
|
|
549
|
+
super().__init__(
|
|
550
|
+
url,
|
|
551
|
+
id,
|
|
552
|
+
title,
|
|
553
|
+
score,
|
|
554
|
+
published_date,
|
|
555
|
+
author,
|
|
556
|
+
image,
|
|
557
|
+
favicon,
|
|
558
|
+
subpages,
|
|
559
|
+
extras,
|
|
560
|
+
)
|
|
461
561
|
self.highlights = highlights if highlights is not None else []
|
|
462
562
|
self.highlight_scores = highlight_scores if highlight_scores is not None else []
|
|
463
563
|
|
|
@@ -484,8 +584,34 @@ class ResultWithTextAndHighlights(_Result):
|
|
|
484
584
|
highlights: List[str] = dataclasses.field(default_factory=list)
|
|
485
585
|
highlight_scores: List[float] = dataclasses.field(default_factory=list)
|
|
486
586
|
|
|
487
|
-
def __init__(
|
|
488
|
-
|
|
587
|
+
def __init__(
|
|
588
|
+
self,
|
|
589
|
+
url,
|
|
590
|
+
id,
|
|
591
|
+
title=None,
|
|
592
|
+
score=None,
|
|
593
|
+
published_date=None,
|
|
594
|
+
author=None,
|
|
595
|
+
image=None,
|
|
596
|
+
favicon=None,
|
|
597
|
+
subpages=None,
|
|
598
|
+
extras=None,
|
|
599
|
+
text="",
|
|
600
|
+
highlights=None,
|
|
601
|
+
highlight_scores=None,
|
|
602
|
+
):
|
|
603
|
+
super().__init__(
|
|
604
|
+
url,
|
|
605
|
+
id,
|
|
606
|
+
title,
|
|
607
|
+
score,
|
|
608
|
+
published_date,
|
|
609
|
+
author,
|
|
610
|
+
image,
|
|
611
|
+
favicon,
|
|
612
|
+
subpages,
|
|
613
|
+
extras,
|
|
614
|
+
)
|
|
489
615
|
self.text = text
|
|
490
616
|
self.highlights = highlights if highlights is not None else []
|
|
491
617
|
self.highlight_scores = highlight_scores if highlight_scores is not None else []
|
|
@@ -510,8 +636,32 @@ class ResultWithSummary(_Result):
|
|
|
510
636
|
|
|
511
637
|
summary: str = dataclasses.field(default_factory=str)
|
|
512
638
|
|
|
513
|
-
def __init__(
|
|
514
|
-
|
|
639
|
+
def __init__(
|
|
640
|
+
self,
|
|
641
|
+
url,
|
|
642
|
+
id,
|
|
643
|
+
title=None,
|
|
644
|
+
score=None,
|
|
645
|
+
published_date=None,
|
|
646
|
+
author=None,
|
|
647
|
+
image=None,
|
|
648
|
+
favicon=None,
|
|
649
|
+
subpages=None,
|
|
650
|
+
extras=None,
|
|
651
|
+
summary="",
|
|
652
|
+
):
|
|
653
|
+
super().__init__(
|
|
654
|
+
url,
|
|
655
|
+
id,
|
|
656
|
+
title,
|
|
657
|
+
score,
|
|
658
|
+
published_date,
|
|
659
|
+
author,
|
|
660
|
+
image,
|
|
661
|
+
favicon,
|
|
662
|
+
subpages,
|
|
663
|
+
extras,
|
|
664
|
+
)
|
|
515
665
|
self.summary = summary
|
|
516
666
|
|
|
517
667
|
def __str__(self):
|
|
@@ -532,8 +682,33 @@ class ResultWithTextAndSummary(_Result):
|
|
|
532
682
|
text: str = dataclasses.field(default_factory=str)
|
|
533
683
|
summary: str = dataclasses.field(default_factory=str)
|
|
534
684
|
|
|
535
|
-
def __init__(
|
|
536
|
-
|
|
685
|
+
def __init__(
|
|
686
|
+
self,
|
|
687
|
+
url,
|
|
688
|
+
id,
|
|
689
|
+
title=None,
|
|
690
|
+
score=None,
|
|
691
|
+
published_date=None,
|
|
692
|
+
author=None,
|
|
693
|
+
image=None,
|
|
694
|
+
favicon=None,
|
|
695
|
+
subpages=None,
|
|
696
|
+
extras=None,
|
|
697
|
+
text="",
|
|
698
|
+
summary="",
|
|
699
|
+
):
|
|
700
|
+
super().__init__(
|
|
701
|
+
url,
|
|
702
|
+
id,
|
|
703
|
+
title,
|
|
704
|
+
score,
|
|
705
|
+
published_date,
|
|
706
|
+
author,
|
|
707
|
+
image,
|
|
708
|
+
favicon,
|
|
709
|
+
subpages,
|
|
710
|
+
extras,
|
|
711
|
+
)
|
|
537
712
|
self.text = text
|
|
538
713
|
self.summary = summary
|
|
539
714
|
|
|
@@ -557,8 +732,34 @@ class ResultWithHighlightsAndSummary(_Result):
|
|
|
557
732
|
highlight_scores: List[float] = dataclasses.field(default_factory=list)
|
|
558
733
|
summary: str = dataclasses.field(default_factory=str)
|
|
559
734
|
|
|
560
|
-
def __init__(
|
|
561
|
-
|
|
735
|
+
def __init__(
|
|
736
|
+
self,
|
|
737
|
+
url,
|
|
738
|
+
id,
|
|
739
|
+
title=None,
|
|
740
|
+
score=None,
|
|
741
|
+
published_date=None,
|
|
742
|
+
author=None,
|
|
743
|
+
image=None,
|
|
744
|
+
favicon=None,
|
|
745
|
+
subpages=None,
|
|
746
|
+
extras=None,
|
|
747
|
+
highlights=None,
|
|
748
|
+
highlight_scores=None,
|
|
749
|
+
summary="",
|
|
750
|
+
):
|
|
751
|
+
super().__init__(
|
|
752
|
+
url,
|
|
753
|
+
id,
|
|
754
|
+
title,
|
|
755
|
+
score,
|
|
756
|
+
published_date,
|
|
757
|
+
author,
|
|
758
|
+
image,
|
|
759
|
+
favicon,
|
|
760
|
+
subpages,
|
|
761
|
+
extras,
|
|
762
|
+
)
|
|
562
763
|
self.highlights = highlights if highlights is not None else []
|
|
563
764
|
self.highlight_scores = highlight_scores if highlight_scores is not None else []
|
|
564
765
|
self.summary = summary
|
|
@@ -589,8 +790,35 @@ class ResultWithTextAndHighlightsAndSummary(_Result):
|
|
|
589
790
|
highlight_scores: List[float] = dataclasses.field(default_factory=list)
|
|
590
791
|
summary: str = dataclasses.field(default_factory=str)
|
|
591
792
|
|
|
592
|
-
def __init__(
|
|
593
|
-
|
|
793
|
+
def __init__(
|
|
794
|
+
self,
|
|
795
|
+
url,
|
|
796
|
+
id,
|
|
797
|
+
title=None,
|
|
798
|
+
score=None,
|
|
799
|
+
published_date=None,
|
|
800
|
+
author=None,
|
|
801
|
+
image=None,
|
|
802
|
+
favicon=None,
|
|
803
|
+
subpages=None,
|
|
804
|
+
extras=None,
|
|
805
|
+
text="",
|
|
806
|
+
highlights=None,
|
|
807
|
+
highlight_scores=None,
|
|
808
|
+
summary="",
|
|
809
|
+
):
|
|
810
|
+
super().__init__(
|
|
811
|
+
url,
|
|
812
|
+
id,
|
|
813
|
+
title,
|
|
814
|
+
score,
|
|
815
|
+
published_date,
|
|
816
|
+
author,
|
|
817
|
+
image,
|
|
818
|
+
favicon,
|
|
819
|
+
subpages,
|
|
820
|
+
extras,
|
|
821
|
+
)
|
|
594
822
|
self.text = text
|
|
595
823
|
self.highlights = highlights if highlights is not None else []
|
|
596
824
|
self.highlight_scores = highlight_scores if highlight_scores is not None else []
|
|
@@ -626,7 +854,9 @@ class AnswerResult:
|
|
|
626
854
|
author: Optional[str] = None
|
|
627
855
|
text: Optional[str] = None
|
|
628
856
|
|
|
629
|
-
def __init__(
|
|
857
|
+
def __init__(
|
|
858
|
+
self, id, url, title=None, published_date=None, author=None, text=None
|
|
859
|
+
):
|
|
630
860
|
self.id = id
|
|
631
861
|
self.url = url
|
|
632
862
|
self.title = title
|
|
@@ -736,14 +966,16 @@ class StreamAnswerResponse:
|
|
|
736
966
|
citations = []
|
|
737
967
|
for s in chunk["citations"]:
|
|
738
968
|
snake_s = to_snake_case(s)
|
|
739
|
-
citations.append(
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
969
|
+
citations.append(
|
|
970
|
+
AnswerResult(
|
|
971
|
+
id=snake_s.get("id"),
|
|
972
|
+
url=snake_s.get("url"),
|
|
973
|
+
title=snake_s.get("title"),
|
|
974
|
+
published_date=snake_s.get("published_date"),
|
|
975
|
+
author=snake_s.get("author"),
|
|
976
|
+
text=snake_s.get("text"),
|
|
977
|
+
)
|
|
978
|
+
)
|
|
747
979
|
|
|
748
980
|
stream_chunk = StreamChunk(content=content, citations=citations)
|
|
749
981
|
if stream_chunk.has_data():
|
|
@@ -793,14 +1025,16 @@ class AsyncStreamAnswerResponse:
|
|
|
793
1025
|
citations = []
|
|
794
1026
|
for s in chunk["citations"]:
|
|
795
1027
|
snake_s = to_snake_case(s)
|
|
796
|
-
citations.append(
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1028
|
+
citations.append(
|
|
1029
|
+
AnswerResult(
|
|
1030
|
+
id=snake_s.get("id"),
|
|
1031
|
+
url=snake_s.get("url"),
|
|
1032
|
+
title=snake_s.get("title"),
|
|
1033
|
+
published_date=snake_s.get("published_date"),
|
|
1034
|
+
author=snake_s.get("author"),
|
|
1035
|
+
text=snake_s.get("text"),
|
|
1036
|
+
)
|
|
1037
|
+
)
|
|
804
1038
|
|
|
805
1039
|
stream_chunk = StreamChunk(content=content, citations=citations)
|
|
806
1040
|
if stream_chunk.has_data():
|
|
@@ -815,6 +1049,7 @@ class AsyncStreamAnswerResponse:
|
|
|
815
1049
|
|
|
816
1050
|
T = TypeVar("T")
|
|
817
1051
|
|
|
1052
|
+
|
|
818
1053
|
@dataclass
|
|
819
1054
|
class ContentStatus:
|
|
820
1055
|
"""A class representing the status of a content retrieval operation."""
|
|
@@ -824,7 +1059,6 @@ class ContentStatus:
|
|
|
824
1059
|
source: str
|
|
825
1060
|
|
|
826
1061
|
|
|
827
|
-
|
|
828
1062
|
@dataclass
|
|
829
1063
|
class SearchResponse(Generic[T]):
|
|
830
1064
|
"""A class representing the response for a search operation.
|
|
@@ -890,13 +1124,14 @@ class Exa:
|
|
|
890
1124
|
self,
|
|
891
1125
|
api_key: Optional[str],
|
|
892
1126
|
base_url: str = "https://api.exa.ai",
|
|
893
|
-
user_agent: str =
|
|
1127
|
+
user_agent: Optional[str] = None,
|
|
894
1128
|
):
|
|
895
1129
|
"""Initialize the Exa client with the provided API key and optional base URL and user agent.
|
|
896
1130
|
|
|
897
1131
|
Args:
|
|
898
1132
|
api_key (str): The API key for authenticating with the Exa API.
|
|
899
1133
|
base_url (str, optional): The base URL for the Exa API. Defaults to "https://api.exa.ai".
|
|
1134
|
+
user_agent (str, optional): Custom user agent. Defaults to "exa-py {version}".
|
|
900
1135
|
"""
|
|
901
1136
|
if api_key is None:
|
|
902
1137
|
import os
|
|
@@ -906,6 +1141,11 @@ class Exa:
|
|
|
906
1141
|
raise ValueError(
|
|
907
1142
|
"API key must be provided as an argument or in EXA_API_KEY environment variable"
|
|
908
1143
|
)
|
|
1144
|
+
|
|
1145
|
+
# Set default user agent with dynamic version if not provided
|
|
1146
|
+
if user_agent is None:
|
|
1147
|
+
user_agent = f"exa-py {_get_package_version()}"
|
|
1148
|
+
|
|
909
1149
|
self.base_url = base_url
|
|
910
1150
|
self.headers = {
|
|
911
1151
|
"x-api-key": api_key,
|
|
@@ -1027,22 +1267,24 @@ class Exa:
|
|
|
1027
1267
|
results = []
|
|
1028
1268
|
for result in data["results"]:
|
|
1029
1269
|
snake_result = to_snake_case(result)
|
|
1030
|
-
results.append(
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1270
|
+
results.append(
|
|
1271
|
+
Result(
|
|
1272
|
+
url=snake_result.get("url"),
|
|
1273
|
+
id=snake_result.get("id"),
|
|
1274
|
+
title=snake_result.get("title"),
|
|
1275
|
+
score=snake_result.get("score"),
|
|
1276
|
+
published_date=snake_result.get("published_date"),
|
|
1277
|
+
author=snake_result.get("author"),
|
|
1278
|
+
image=snake_result.get("image"),
|
|
1279
|
+
favicon=snake_result.get("favicon"),
|
|
1280
|
+
subpages=snake_result.get("subpages"),
|
|
1281
|
+
extras=snake_result.get("extras"),
|
|
1282
|
+
text=snake_result.get("text"),
|
|
1283
|
+
highlights=snake_result.get("highlights"),
|
|
1284
|
+
highlight_scores=snake_result.get("highlight_scores"),
|
|
1285
|
+
summary=snake_result.get("summary"),
|
|
1286
|
+
)
|
|
1287
|
+
)
|
|
1046
1288
|
return SearchResponse(
|
|
1047
1289
|
results,
|
|
1048
1290
|
data["autopromptString"] if "autopromptString" in data else None,
|
|
@@ -1299,6 +1541,12 @@ class Exa:
|
|
|
1299
1541
|
merged_options.update(CONTENTS_ENDPOINT_OPTIONS_TYPES)
|
|
1300
1542
|
validate_search_options(options, merged_options)
|
|
1301
1543
|
|
|
1544
|
+
# Convert schema if present in summary options
|
|
1545
|
+
if "summary" in options and isinstance(options["summary"], dict):
|
|
1546
|
+
summary_opts = options["summary"]
|
|
1547
|
+
if "schema" in summary_opts:
|
|
1548
|
+
summary_opts["schema"] = _convert_schema_input(summary_opts["schema"])
|
|
1549
|
+
|
|
1302
1550
|
# Nest the appropriate fields under "contents"
|
|
1303
1551
|
options = nest_fields(
|
|
1304
1552
|
options,
|
|
@@ -1315,28 +1563,30 @@ class Exa:
|
|
|
1315
1563
|
],
|
|
1316
1564
|
"contents",
|
|
1317
1565
|
)
|
|
1318
|
-
options = to_camel_case(options)
|
|
1566
|
+
options = to_camel_case(options, skip_keys=["schema"])
|
|
1319
1567
|
data = self.request("/search", options)
|
|
1320
1568
|
cost_dollars = parse_cost_dollars(data.get("costDollars"))
|
|
1321
1569
|
results = []
|
|
1322
1570
|
for result in data["results"]:
|
|
1323
1571
|
snake_result = to_snake_case(result)
|
|
1324
|
-
results.append(
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1572
|
+
results.append(
|
|
1573
|
+
Result(
|
|
1574
|
+
url=snake_result.get("url"),
|
|
1575
|
+
id=snake_result.get("id"),
|
|
1576
|
+
title=snake_result.get("title"),
|
|
1577
|
+
score=snake_result.get("score"),
|
|
1578
|
+
published_date=snake_result.get("published_date"),
|
|
1579
|
+
author=snake_result.get("author"),
|
|
1580
|
+
image=snake_result.get("image"),
|
|
1581
|
+
favicon=snake_result.get("favicon"),
|
|
1582
|
+
subpages=snake_result.get("subpages"),
|
|
1583
|
+
extras=snake_result.get("extras"),
|
|
1584
|
+
text=snake_result.get("text"),
|
|
1585
|
+
highlights=snake_result.get("highlights"),
|
|
1586
|
+
highlight_scores=snake_result.get("highlight_scores"),
|
|
1587
|
+
summary=snake_result.get("summary"),
|
|
1588
|
+
)
|
|
1589
|
+
)
|
|
1340
1590
|
return SearchResponse(
|
|
1341
1591
|
results,
|
|
1342
1592
|
data["autopromptString"] if "autopromptString" in data else None,
|
|
@@ -1474,7 +1724,7 @@ class Exa:
|
|
|
1474
1724
|
for k, v in kwargs.items():
|
|
1475
1725
|
if k != "self" and v is not None:
|
|
1476
1726
|
options[k] = v
|
|
1477
|
-
|
|
1727
|
+
|
|
1478
1728
|
if (
|
|
1479
1729
|
"text" not in options
|
|
1480
1730
|
and "highlights" not in options
|
|
@@ -1487,35 +1737,46 @@ class Exa:
|
|
|
1487
1737
|
merged_options.update(CONTENTS_OPTIONS_TYPES)
|
|
1488
1738
|
merged_options.update(CONTENTS_ENDPOINT_OPTIONS_TYPES)
|
|
1489
1739
|
validate_search_options(options, merged_options)
|
|
1490
|
-
|
|
1740
|
+
|
|
1741
|
+
# Convert schema if present in summary options
|
|
1742
|
+
if "summary" in options and isinstance(options["summary"], dict):
|
|
1743
|
+
summary_opts = options["summary"]
|
|
1744
|
+
if "schema" in summary_opts:
|
|
1745
|
+
summary_opts["schema"] = _convert_schema_input(summary_opts["schema"])
|
|
1746
|
+
|
|
1747
|
+
options = to_camel_case(options, ["schema"])
|
|
1491
1748
|
data = self.request("/contents", options)
|
|
1492
1749
|
cost_dollars = parse_cost_dollars(data.get("costDollars"))
|
|
1493
1750
|
statuses = []
|
|
1494
1751
|
for status in data.get("statuses", []):
|
|
1495
|
-
statuses.append(
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1752
|
+
statuses.append(
|
|
1753
|
+
ContentStatus(
|
|
1754
|
+
id=status.get("id"),
|
|
1755
|
+
status=status.get("status"),
|
|
1756
|
+
source=status.get("source"),
|
|
1757
|
+
)
|
|
1758
|
+
)
|
|
1500
1759
|
results = []
|
|
1501
1760
|
for result in data["results"]:
|
|
1502
1761
|
snake_result = to_snake_case(result)
|
|
1503
|
-
results.append(
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1762
|
+
results.append(
|
|
1763
|
+
Result(
|
|
1764
|
+
url=snake_result.get("url"),
|
|
1765
|
+
id=snake_result.get("id"),
|
|
1766
|
+
title=snake_result.get("title"),
|
|
1767
|
+
score=snake_result.get("score"),
|
|
1768
|
+
published_date=snake_result.get("published_date"),
|
|
1769
|
+
author=snake_result.get("author"),
|
|
1770
|
+
image=snake_result.get("image"),
|
|
1771
|
+
favicon=snake_result.get("favicon"),
|
|
1772
|
+
subpages=snake_result.get("subpages"),
|
|
1773
|
+
extras=snake_result.get("extras"),
|
|
1774
|
+
text=snake_result.get("text"),
|
|
1775
|
+
highlights=snake_result.get("highlights"),
|
|
1776
|
+
highlight_scores=snake_result.get("highlight_scores"),
|
|
1777
|
+
summary=snake_result.get("summary"),
|
|
1778
|
+
)
|
|
1779
|
+
)
|
|
1519
1780
|
return SearchResponse(
|
|
1520
1781
|
results,
|
|
1521
1782
|
data.get("autopromptString"),
|
|
@@ -1571,22 +1832,24 @@ class Exa:
|
|
|
1571
1832
|
results = []
|
|
1572
1833
|
for result in data["results"]:
|
|
1573
1834
|
snake_result = to_snake_case(result)
|
|
1574
|
-
results.append(
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1835
|
+
results.append(
|
|
1836
|
+
Result(
|
|
1837
|
+
url=snake_result.get("url"),
|
|
1838
|
+
id=snake_result.get("id"),
|
|
1839
|
+
title=snake_result.get("title"),
|
|
1840
|
+
score=snake_result.get("score"),
|
|
1841
|
+
published_date=snake_result.get("published_date"),
|
|
1842
|
+
author=snake_result.get("author"),
|
|
1843
|
+
image=snake_result.get("image"),
|
|
1844
|
+
favicon=snake_result.get("favicon"),
|
|
1845
|
+
subpages=snake_result.get("subpages"),
|
|
1846
|
+
extras=snake_result.get("extras"),
|
|
1847
|
+
text=snake_result.get("text"),
|
|
1848
|
+
highlights=snake_result.get("highlights"),
|
|
1849
|
+
highlight_scores=snake_result.get("highlight_scores"),
|
|
1850
|
+
summary=snake_result.get("summary"),
|
|
1851
|
+
)
|
|
1852
|
+
)
|
|
1590
1853
|
return SearchResponse(
|
|
1591
1854
|
results,
|
|
1592
1855
|
data.get("autopromptString"),
|
|
@@ -1825,6 +2088,13 @@ class Exa:
|
|
|
1825
2088
|
merged_options.update(CONTENTS_OPTIONS_TYPES)
|
|
1826
2089
|
merged_options.update(CONTENTS_ENDPOINT_OPTIONS_TYPES)
|
|
1827
2090
|
validate_search_options(options, merged_options)
|
|
2091
|
+
|
|
2092
|
+
# Convert schema if present in summary options
|
|
2093
|
+
if "summary" in options and isinstance(options["summary"], dict):
|
|
2094
|
+
summary_opts = options["summary"]
|
|
2095
|
+
if "schema" in summary_opts:
|
|
2096
|
+
summary_opts["schema"] = _convert_schema_input(summary_opts["schema"])
|
|
2097
|
+
|
|
1828
2098
|
# We nest the content fields
|
|
1829
2099
|
options = nest_fields(
|
|
1830
2100
|
options,
|
|
@@ -1841,28 +2111,30 @@ class Exa:
|
|
|
1841
2111
|
],
|
|
1842
2112
|
"contents",
|
|
1843
2113
|
)
|
|
1844
|
-
options = to_camel_case(options)
|
|
2114
|
+
options = to_camel_case(options, skip_keys=["schema"])
|
|
1845
2115
|
data = self.request("/findSimilar", options)
|
|
1846
2116
|
cost_dollars = parse_cost_dollars(data.get("costDollars"))
|
|
1847
2117
|
results = []
|
|
1848
2118
|
for result in data["results"]:
|
|
1849
2119
|
snake_result = to_snake_case(result)
|
|
1850
|
-
results.append(
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2120
|
+
results.append(
|
|
2121
|
+
Result(
|
|
2122
|
+
url=snake_result.get("url"),
|
|
2123
|
+
id=snake_result.get("id"),
|
|
2124
|
+
title=snake_result.get("title"),
|
|
2125
|
+
score=snake_result.get("score"),
|
|
2126
|
+
published_date=snake_result.get("published_date"),
|
|
2127
|
+
author=snake_result.get("author"),
|
|
2128
|
+
image=snake_result.get("image"),
|
|
2129
|
+
favicon=snake_result.get("favicon"),
|
|
2130
|
+
subpages=snake_result.get("subpages"),
|
|
2131
|
+
extras=snake_result.get("extras"),
|
|
2132
|
+
text=snake_result.get("text"),
|
|
2133
|
+
highlights=snake_result.get("highlights"),
|
|
2134
|
+
highlight_scores=snake_result.get("highlight_scores"),
|
|
2135
|
+
summary=snake_result.get("summary"),
|
|
2136
|
+
)
|
|
2137
|
+
)
|
|
1866
2138
|
return SearchResponse(
|
|
1867
2139
|
results,
|
|
1868
2140
|
data.get("autopromptString"),
|
|
@@ -2001,7 +2273,7 @@ class Exa:
|
|
|
2001
2273
|
use_autoprompt=exa_kwargs.get("use_autoprompt"),
|
|
2002
2274
|
type=exa_kwargs.get("type"),
|
|
2003
2275
|
category=exa_kwargs.get("category"),
|
|
2004
|
-
flags=exa_kwargs.get("flags")
|
|
2276
|
+
flags=exa_kwargs.get("flags"),
|
|
2005
2277
|
)
|
|
2006
2278
|
exa_str = format_exa_result(exa_result, max_len=max_len)
|
|
2007
2279
|
new_messages = add_message_to_messages(completion, messages, exa_str)
|
|
@@ -2021,7 +2293,7 @@ class Exa:
|
|
|
2021
2293
|
text: Optional[bool] = False,
|
|
2022
2294
|
system_prompt: Optional[str] = None,
|
|
2023
2295
|
model: Optional[Literal["exa", "exa-pro"]] = None,
|
|
2024
|
-
output_schema: Optional[
|
|
2296
|
+
output_schema: Optional[JSONSchemaInput] = None,
|
|
2025
2297
|
) -> Union[AnswerResponse, StreamAnswerResponse]: ...
|
|
2026
2298
|
|
|
2027
2299
|
def answer(
|
|
@@ -2032,7 +2304,7 @@ class Exa:
|
|
|
2032
2304
|
text: Optional[bool] = False,
|
|
2033
2305
|
system_prompt: Optional[str] = None,
|
|
2034
2306
|
model: Optional[Literal["exa", "exa-pro"]] = None,
|
|
2035
|
-
output_schema: Optional[
|
|
2307
|
+
output_schema: Optional[JSONSchemaInput] = None,
|
|
2036
2308
|
) -> Union[AnswerResponse, StreamAnswerResponse]:
|
|
2037
2309
|
"""Generate an answer to a query using Exa's search and LLM capabilities.
|
|
2038
2310
|
|
|
@@ -2056,20 +2328,27 @@ class Exa:
|
|
|
2056
2328
|
)
|
|
2057
2329
|
|
|
2058
2330
|
options = {k: v for k, v in locals().items() if k != "self" and v is not None}
|
|
2059
|
-
|
|
2331
|
+
|
|
2332
|
+
# Convert output_schema if present
|
|
2333
|
+
if "output_schema" in options and options["output_schema"] is not None:
|
|
2334
|
+
options["output_schema"] = _convert_schema_input(options["output_schema"])
|
|
2335
|
+
|
|
2336
|
+
options = to_camel_case(options, ["output_schema"])
|
|
2060
2337
|
response = self.request("/answer", options)
|
|
2061
2338
|
|
|
2062
2339
|
citations = []
|
|
2063
2340
|
for result in response["citations"]:
|
|
2064
2341
|
snake_result = to_snake_case(result)
|
|
2065
|
-
citations.append(
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2342
|
+
citations.append(
|
|
2343
|
+
AnswerResult(
|
|
2344
|
+
id=snake_result.get("id"),
|
|
2345
|
+
url=snake_result.get("url"),
|
|
2346
|
+
title=snake_result.get("title"),
|
|
2347
|
+
published_date=snake_result.get("published_date"),
|
|
2348
|
+
author=snake_result.get("author"),
|
|
2349
|
+
text=snake_result.get("text"),
|
|
2350
|
+
)
|
|
2351
|
+
)
|
|
2073
2352
|
return AnswerResponse(response["answer"], citations)
|
|
2074
2353
|
|
|
2075
2354
|
def stream_answer(
|
|
@@ -2079,7 +2358,7 @@ class Exa:
|
|
|
2079
2358
|
text: bool = False,
|
|
2080
2359
|
system_prompt: Optional[str] = None,
|
|
2081
2360
|
model: Optional[Literal["exa", "exa-pro"]] = None,
|
|
2082
|
-
output_schema: Optional[
|
|
2361
|
+
output_schema: Optional[JSONSchemaInput] = None,
|
|
2083
2362
|
) -> StreamAnswerResponse:
|
|
2084
2363
|
"""Generate a streaming answer response.
|
|
2085
2364
|
|
|
@@ -2094,7 +2373,12 @@ class Exa:
|
|
|
2094
2373
|
Each iteration yields a tuple of (Optional[str], Optional[List[AnswerResult]]).
|
|
2095
2374
|
"""
|
|
2096
2375
|
options = {k: v for k, v in locals().items() if k != "self" and v is not None}
|
|
2097
|
-
|
|
2376
|
+
|
|
2377
|
+
# Convert output_schema if present
|
|
2378
|
+
if "output_schema" in options and options["output_schema"] is not None:
|
|
2379
|
+
options["output_schema"] = _convert_schema_input(options["output_schema"])
|
|
2380
|
+
|
|
2381
|
+
options = to_camel_case(options, skip_keys=["output_schema"])
|
|
2098
2382
|
options["stream"] = True
|
|
2099
2383
|
raw_response = self.request("/answer", options)
|
|
2100
2384
|
return StreamAnswerResponse(raw_response)
|
|
@@ -2195,22 +2479,24 @@ class AsyncExa(Exa):
|
|
|
2195
2479
|
results = []
|
|
2196
2480
|
for result in data["results"]:
|
|
2197
2481
|
snake_result = to_snake_case(result)
|
|
2198
|
-
results.append(
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2482
|
+
results.append(
|
|
2483
|
+
Result(
|
|
2484
|
+
url=snake_result.get("url"),
|
|
2485
|
+
id=snake_result.get("id"),
|
|
2486
|
+
title=snake_result.get("title"),
|
|
2487
|
+
score=snake_result.get("score"),
|
|
2488
|
+
published_date=snake_result.get("published_date"),
|
|
2489
|
+
author=snake_result.get("author"),
|
|
2490
|
+
image=snake_result.get("image"),
|
|
2491
|
+
favicon=snake_result.get("favicon"),
|
|
2492
|
+
subpages=snake_result.get("subpages"),
|
|
2493
|
+
extras=snake_result.get("extras"),
|
|
2494
|
+
text=snake_result.get("text"),
|
|
2495
|
+
highlights=snake_result.get("highlights"),
|
|
2496
|
+
highlight_scores=snake_result.get("highlight_scores"),
|
|
2497
|
+
summary=snake_result.get("summary"),
|
|
2498
|
+
)
|
|
2499
|
+
)
|
|
2214
2500
|
return SearchResponse(
|
|
2215
2501
|
results,
|
|
2216
2502
|
data["autopromptString"] if "autopromptString" in data else None,
|
|
@@ -2239,6 +2525,12 @@ class AsyncExa(Exa):
|
|
|
2239
2525
|
merged_options.update(CONTENTS_ENDPOINT_OPTIONS_TYPES)
|
|
2240
2526
|
validate_search_options(options, merged_options)
|
|
2241
2527
|
|
|
2528
|
+
# Convert schema if present in summary options
|
|
2529
|
+
if "summary" in options and isinstance(options["summary"], dict):
|
|
2530
|
+
summary_opts = options["summary"]
|
|
2531
|
+
if "schema" in summary_opts:
|
|
2532
|
+
summary_opts["schema"] = _convert_schema_input(summary_opts["schema"])
|
|
2533
|
+
|
|
2242
2534
|
# Nest the appropriate fields under "contents"
|
|
2243
2535
|
options = nest_fields(
|
|
2244
2536
|
options,
|
|
@@ -2255,28 +2547,30 @@ class AsyncExa(Exa):
|
|
|
2255
2547
|
],
|
|
2256
2548
|
"contents",
|
|
2257
2549
|
)
|
|
2258
|
-
options = to_camel_case(options)
|
|
2550
|
+
options = to_camel_case(options, skip_keys=["schema"])
|
|
2259
2551
|
data = await self.async_request("/search", options)
|
|
2260
2552
|
cost_dollars = parse_cost_dollars(data.get("costDollars"))
|
|
2261
2553
|
results = []
|
|
2262
2554
|
for result in data["results"]:
|
|
2263
2555
|
snake_result = to_snake_case(result)
|
|
2264
|
-
results.append(
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2556
|
+
results.append(
|
|
2557
|
+
Result(
|
|
2558
|
+
url=snake_result.get("url"),
|
|
2559
|
+
id=snake_result.get("id"),
|
|
2560
|
+
title=snake_result.get("title"),
|
|
2561
|
+
score=snake_result.get("score"),
|
|
2562
|
+
published_date=snake_result.get("published_date"),
|
|
2563
|
+
author=snake_result.get("author"),
|
|
2564
|
+
image=snake_result.get("image"),
|
|
2565
|
+
favicon=snake_result.get("favicon"),
|
|
2566
|
+
subpages=snake_result.get("subpages"),
|
|
2567
|
+
extras=snake_result.get("extras"),
|
|
2568
|
+
text=snake_result.get("text"),
|
|
2569
|
+
highlights=snake_result.get("highlights"),
|
|
2570
|
+
highlight_scores=snake_result.get("highlight_scores"),
|
|
2571
|
+
summary=snake_result.get("summary"),
|
|
2572
|
+
)
|
|
2573
|
+
)
|
|
2280
2574
|
return SearchResponse(
|
|
2281
2575
|
results,
|
|
2282
2576
|
data["autopromptString"] if "autopromptString" in data else None,
|
|
@@ -2291,7 +2585,7 @@ class AsyncExa(Exa):
|
|
|
2291
2585
|
for k, v in kwargs.items():
|
|
2292
2586
|
if k != "self" and v is not None:
|
|
2293
2587
|
options[k] = v
|
|
2294
|
-
|
|
2588
|
+
|
|
2295
2589
|
if (
|
|
2296
2590
|
"text" not in options
|
|
2297
2591
|
and "highlights" not in options
|
|
@@ -2304,35 +2598,46 @@ class AsyncExa(Exa):
|
|
|
2304
2598
|
merged_options.update(CONTENTS_OPTIONS_TYPES)
|
|
2305
2599
|
merged_options.update(CONTENTS_ENDPOINT_OPTIONS_TYPES)
|
|
2306
2600
|
validate_search_options(options, merged_options)
|
|
2307
|
-
|
|
2601
|
+
|
|
2602
|
+
# Convert schema if present in summary options
|
|
2603
|
+
if "summary" in options and isinstance(options["summary"], dict):
|
|
2604
|
+
summary_opts = options["summary"]
|
|
2605
|
+
if "schema" in summary_opts:
|
|
2606
|
+
summary_opts["schema"] = _convert_schema_input(summary_opts["schema"])
|
|
2607
|
+
|
|
2608
|
+
options = to_camel_case(options, ["schema"])
|
|
2308
2609
|
data = await self.async_request("/contents", options)
|
|
2309
2610
|
cost_dollars = parse_cost_dollars(data.get("costDollars"))
|
|
2310
2611
|
statuses = []
|
|
2311
2612
|
for status in data.get("statuses", []):
|
|
2312
|
-
statuses.append(
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2613
|
+
statuses.append(
|
|
2614
|
+
ContentStatus(
|
|
2615
|
+
id=status.get("id"),
|
|
2616
|
+
status=status.get("status"),
|
|
2617
|
+
source=status.get("source"),
|
|
2618
|
+
)
|
|
2619
|
+
)
|
|
2317
2620
|
results = []
|
|
2318
2621
|
for result in data["results"]:
|
|
2319
2622
|
snake_result = to_snake_case(result)
|
|
2320
|
-
results.append(
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2623
|
+
results.append(
|
|
2624
|
+
Result(
|
|
2625
|
+
url=snake_result.get("url"),
|
|
2626
|
+
id=snake_result.get("id"),
|
|
2627
|
+
title=snake_result.get("title"),
|
|
2628
|
+
score=snake_result.get("score"),
|
|
2629
|
+
published_date=snake_result.get("published_date"),
|
|
2630
|
+
author=snake_result.get("author"),
|
|
2631
|
+
image=snake_result.get("image"),
|
|
2632
|
+
favicon=snake_result.get("favicon"),
|
|
2633
|
+
subpages=snake_result.get("subpages"),
|
|
2634
|
+
extras=snake_result.get("extras"),
|
|
2635
|
+
text=snake_result.get("text"),
|
|
2636
|
+
highlights=snake_result.get("highlights"),
|
|
2637
|
+
highlight_scores=snake_result.get("highlight_scores"),
|
|
2638
|
+
summary=snake_result.get("summary"),
|
|
2639
|
+
)
|
|
2640
|
+
)
|
|
2336
2641
|
return SearchResponse(
|
|
2337
2642
|
results,
|
|
2338
2643
|
data.get("autopromptString"),
|
|
@@ -2388,22 +2693,24 @@ class AsyncExa(Exa):
|
|
|
2388
2693
|
results = []
|
|
2389
2694
|
for result in data["results"]:
|
|
2390
2695
|
snake_result = to_snake_case(result)
|
|
2391
|
-
results.append(
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2696
|
+
results.append(
|
|
2697
|
+
Result(
|
|
2698
|
+
url=snake_result.get("url"),
|
|
2699
|
+
id=snake_result.get("id"),
|
|
2700
|
+
title=snake_result.get("title"),
|
|
2701
|
+
score=snake_result.get("score"),
|
|
2702
|
+
published_date=snake_result.get("published_date"),
|
|
2703
|
+
author=snake_result.get("author"),
|
|
2704
|
+
image=snake_result.get("image"),
|
|
2705
|
+
favicon=snake_result.get("favicon"),
|
|
2706
|
+
subpages=snake_result.get("subpages"),
|
|
2707
|
+
extras=snake_result.get("extras"),
|
|
2708
|
+
text=snake_result.get("text"),
|
|
2709
|
+
highlights=snake_result.get("highlights"),
|
|
2710
|
+
highlight_scores=snake_result.get("highlight_scores"),
|
|
2711
|
+
summary=snake_result.get("summary"),
|
|
2712
|
+
)
|
|
2713
|
+
)
|
|
2407
2714
|
return SearchResponse(
|
|
2408
2715
|
results,
|
|
2409
2716
|
data.get("autopromptString"),
|
|
@@ -2430,6 +2737,13 @@ class AsyncExa(Exa):
|
|
|
2430
2737
|
merged_options.update(CONTENTS_OPTIONS_TYPES)
|
|
2431
2738
|
merged_options.update(CONTENTS_ENDPOINT_OPTIONS_TYPES)
|
|
2432
2739
|
validate_search_options(options, merged_options)
|
|
2740
|
+
|
|
2741
|
+
# Convert schema if present in summary options
|
|
2742
|
+
if "summary" in options and isinstance(options["summary"], dict):
|
|
2743
|
+
summary_opts = options["summary"]
|
|
2744
|
+
if "schema" in summary_opts:
|
|
2745
|
+
summary_opts["schema"] = _convert_schema_input(summary_opts["schema"])
|
|
2746
|
+
|
|
2433
2747
|
# We nest the content fields
|
|
2434
2748
|
options = nest_fields(
|
|
2435
2749
|
options,
|
|
@@ -2446,28 +2760,30 @@ class AsyncExa(Exa):
|
|
|
2446
2760
|
],
|
|
2447
2761
|
"contents",
|
|
2448
2762
|
)
|
|
2449
|
-
options = to_camel_case(options)
|
|
2763
|
+
options = to_camel_case(options, skip_keys=["schema"])
|
|
2450
2764
|
data = await self.async_request("/findSimilar", options)
|
|
2451
2765
|
cost_dollars = parse_cost_dollars(data.get("costDollars"))
|
|
2452
2766
|
results = []
|
|
2453
2767
|
for result in data["results"]:
|
|
2454
2768
|
snake_result = to_snake_case(result)
|
|
2455
|
-
results.append(
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2769
|
+
results.append(
|
|
2770
|
+
Result(
|
|
2771
|
+
url=snake_result.get("url"),
|
|
2772
|
+
id=snake_result.get("id"),
|
|
2773
|
+
title=snake_result.get("title"),
|
|
2774
|
+
score=snake_result.get("score"),
|
|
2775
|
+
published_date=snake_result.get("published_date"),
|
|
2776
|
+
author=snake_result.get("author"),
|
|
2777
|
+
image=snake_result.get("image"),
|
|
2778
|
+
favicon=snake_result.get("favicon"),
|
|
2779
|
+
subpages=snake_result.get("subpages"),
|
|
2780
|
+
extras=snake_result.get("extras"),
|
|
2781
|
+
text=snake_result.get("text"),
|
|
2782
|
+
highlights=snake_result.get("highlights"),
|
|
2783
|
+
highlight_scores=snake_result.get("highlight_scores"),
|
|
2784
|
+
summary=snake_result.get("summary"),
|
|
2785
|
+
)
|
|
2786
|
+
)
|
|
2471
2787
|
return SearchResponse(
|
|
2472
2788
|
results,
|
|
2473
2789
|
data.get("autopromptString"),
|
|
@@ -2485,7 +2801,7 @@ class AsyncExa(Exa):
|
|
|
2485
2801
|
text: Optional[bool] = False,
|
|
2486
2802
|
system_prompt: Optional[str] = None,
|
|
2487
2803
|
model: Optional[Literal["exa", "exa-pro"]] = None,
|
|
2488
|
-
output_schema: Optional[
|
|
2804
|
+
output_schema: Optional[JSONSchemaInput] = None,
|
|
2489
2805
|
) -> Union[AnswerResponse, StreamAnswerResponse]:
|
|
2490
2806
|
"""Generate an answer to a query using Exa's search and LLM capabilities.
|
|
2491
2807
|
|
|
@@ -2509,20 +2825,27 @@ class AsyncExa(Exa):
|
|
|
2509
2825
|
)
|
|
2510
2826
|
|
|
2511
2827
|
options = {k: v for k, v in locals().items() if k != "self" and v is not None}
|
|
2512
|
-
|
|
2828
|
+
|
|
2829
|
+
# Convert output_schema if present
|
|
2830
|
+
if "output_schema" in options and options["output_schema"] is not None:
|
|
2831
|
+
options["output_schema"] = _convert_schema_input(options["output_schema"])
|
|
2832
|
+
|
|
2833
|
+
options = to_camel_case(options, skip_keys=["output_schema"])
|
|
2513
2834
|
response = await self.async_request("/answer", options)
|
|
2514
2835
|
|
|
2515
2836
|
citations = []
|
|
2516
2837
|
for result in response["citations"]:
|
|
2517
2838
|
snake_result = to_snake_case(result)
|
|
2518
|
-
citations.append(
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2839
|
+
citations.append(
|
|
2840
|
+
AnswerResult(
|
|
2841
|
+
id=snake_result.get("id"),
|
|
2842
|
+
url=snake_result.get("url"),
|
|
2843
|
+
title=snake_result.get("title"),
|
|
2844
|
+
published_date=snake_result.get("published_date"),
|
|
2845
|
+
author=snake_result.get("author"),
|
|
2846
|
+
text=snake_result.get("text"),
|
|
2847
|
+
)
|
|
2848
|
+
)
|
|
2526
2849
|
return AnswerResponse(response["answer"], citations)
|
|
2527
2850
|
|
|
2528
2851
|
async def stream_answer(
|
|
@@ -2532,7 +2855,7 @@ class AsyncExa(Exa):
|
|
|
2532
2855
|
text: bool = False,
|
|
2533
2856
|
system_prompt: Optional[str] = None,
|
|
2534
2857
|
model: Optional[Literal["exa", "exa-pro"]] = None,
|
|
2535
|
-
output_schema: Optional[
|
|
2858
|
+
output_schema: Optional[JSONSchemaInput] = None,
|
|
2536
2859
|
) -> AsyncStreamAnswerResponse:
|
|
2537
2860
|
"""Generate a streaming answer response.
|
|
2538
2861
|
|
|
@@ -2547,7 +2870,12 @@ class AsyncExa(Exa):
|
|
|
2547
2870
|
Each iteration yields a tuple of (Optional[str], Optional[List[AnswerResult]]).
|
|
2548
2871
|
"""
|
|
2549
2872
|
options = {k: v for k, v in locals().items() if k != "self" and v is not None}
|
|
2550
|
-
|
|
2873
|
+
|
|
2874
|
+
# Convert output_schema if present
|
|
2875
|
+
if "output_schema" in options and options["output_schema"] is not None:
|
|
2876
|
+
options["output_schema"] = _convert_schema_input(options["output_schema"])
|
|
2877
|
+
|
|
2878
|
+
options = to_camel_case(options, skip_keys=["output_schema"])
|
|
2551
2879
|
options["stream"] = True
|
|
2552
2880
|
raw_response = await self.async_request("/answer", options)
|
|
2553
2881
|
return AsyncStreamAnswerResponse(raw_response)
|