exa-py 1.14.14__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 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) if isinstance(v, dict) else 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 (JSONSchema): JSON schema for structured output from summary.
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: JSONSchema
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__(self, url, id, title=None, score=None, published_date=None, author=None, image=None, favicon=None, subpages=None, extras=None):
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__(self, url, id, title=None, score=None, published_date=None, author=None, image=None, favicon=None, subpages=None, extras=None, text=None, highlights=None, highlight_scores=None, summary=None):
410
- super().__init__(url, id, title, score, published_date, author, image, favicon, subpages, extras)
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__(self, url, id, title=None, score=None, published_date=None, author=None, image=None, favicon=None, subpages=None, extras=None, text=""):
438
- super().__init__(url, id, title, score, published_date, author, image, favicon, subpages, extras)
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__(self, url, id, title=None, score=None, published_date=None, author=None, image=None, favicon=None, subpages=None, extras=None, highlights=None, highlight_scores=None):
460
- super().__init__(url, id, title, score, published_date, author, image, favicon, subpages, extras)
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__(self, url, id, title=None, score=None, published_date=None, author=None, image=None, favicon=None, subpages=None, extras=None, text="", highlights=None, highlight_scores=None):
488
- super().__init__(url, id, title, score, published_date, author, image, favicon, subpages, extras)
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__(self, url, id, title=None, score=None, published_date=None, author=None, image=None, favicon=None, subpages=None, extras=None, summary=""):
514
- super().__init__(url, id, title, score, published_date, author, image, favicon, subpages, extras)
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__(self, url, id, title=None, score=None, published_date=None, author=None, image=None, favicon=None, subpages=None, extras=None, text="", summary=""):
536
- super().__init__(url, id, title, score, published_date, author, image, favicon, subpages, extras)
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__(self, url, id, title=None, score=None, published_date=None, author=None, image=None, favicon=None, subpages=None, extras=None, highlights=None, highlight_scores=None, summary=""):
561
- super().__init__(url, id, title, score, published_date, author, image, favicon, subpages, extras)
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__(self, url, id, title=None, score=None, published_date=None, author=None, image=None, favicon=None, subpages=None, extras=None, text="", highlights=None, highlight_scores=None, summary=""):
593
- super().__init__(url, id, title, score, published_date, author, image, favicon, subpages, extras)
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__(self, id, url, title=None, published_date=None, author=None, text=None):
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(AnswerResult(
740
- id=snake_s.get("id"),
741
- url=snake_s.get("url"),
742
- title=snake_s.get("title"),
743
- published_date=snake_s.get("published_date"),
744
- author=snake_s.get("author"),
745
- text=snake_s.get("text")
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(AnswerResult(
797
- id=snake_s.get("id"),
798
- url=snake_s.get("url"),
799
- title=snake_s.get("title"),
800
- published_date=snake_s.get("published_date"),
801
- author=snake_s.get("author"),
802
- text=snake_s.get("text")
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 = "exa-py 1.14.9",
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(Result(
1031
- url=snake_result.get("url"),
1032
- id=snake_result.get("id"),
1033
- title=snake_result.get("title"),
1034
- score=snake_result.get("score"),
1035
- published_date=snake_result.get("published_date"),
1036
- author=snake_result.get("author"),
1037
- image=snake_result.get("image"),
1038
- favicon=snake_result.get("favicon"),
1039
- subpages=snake_result.get("subpages"),
1040
- extras=snake_result.get("extras"),
1041
- text=snake_result.get("text"),
1042
- highlights=snake_result.get("highlights"),
1043
- highlight_scores=snake_result.get("highlight_scores"),
1044
- summary=snake_result.get("summary")
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(Result(
1325
- url=snake_result.get("url"),
1326
- id=snake_result.get("id"),
1327
- title=snake_result.get("title"),
1328
- score=snake_result.get("score"),
1329
- published_date=snake_result.get("published_date"),
1330
- author=snake_result.get("author"),
1331
- image=snake_result.get("image"),
1332
- favicon=snake_result.get("favicon"),
1333
- subpages=snake_result.get("subpages"),
1334
- extras=snake_result.get("extras"),
1335
- text=snake_result.get("text"),
1336
- highlights=snake_result.get("highlights"),
1337
- highlight_scores=snake_result.get("highlight_scores"),
1338
- summary=snake_result.get("summary")
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
- options = to_camel_case(options)
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(ContentStatus(
1496
- id=status.get("id"),
1497
- status=status.get("status"),
1498
- source=status.get("source")
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(Result(
1504
- url=snake_result.get("url"),
1505
- id=snake_result.get("id"),
1506
- title=snake_result.get("title"),
1507
- score=snake_result.get("score"),
1508
- published_date=snake_result.get("published_date"),
1509
- author=snake_result.get("author"),
1510
- image=snake_result.get("image"),
1511
- favicon=snake_result.get("favicon"),
1512
- subpages=snake_result.get("subpages"),
1513
- extras=snake_result.get("extras"),
1514
- text=snake_result.get("text"),
1515
- highlights=snake_result.get("highlights"),
1516
- highlight_scores=snake_result.get("highlight_scores"),
1517
- summary=snake_result.get("summary")
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(Result(
1575
- url=snake_result.get("url"),
1576
- id=snake_result.get("id"),
1577
- title=snake_result.get("title"),
1578
- score=snake_result.get("score"),
1579
- published_date=snake_result.get("published_date"),
1580
- author=snake_result.get("author"),
1581
- image=snake_result.get("image"),
1582
- favicon=snake_result.get("favicon"),
1583
- subpages=snake_result.get("subpages"),
1584
- extras=snake_result.get("extras"),
1585
- text=snake_result.get("text"),
1586
- highlights=snake_result.get("highlights"),
1587
- highlight_scores=snake_result.get("highlight_scores"),
1588
- summary=snake_result.get("summary")
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(Result(
1851
- url=snake_result.get("url"),
1852
- id=snake_result.get("id"),
1853
- title=snake_result.get("title"),
1854
- score=snake_result.get("score"),
1855
- published_date=snake_result.get("published_date"),
1856
- author=snake_result.get("author"),
1857
- image=snake_result.get("image"),
1858
- favicon=snake_result.get("favicon"),
1859
- subpages=snake_result.get("subpages"),
1860
- extras=snake_result.get("extras"),
1861
- text=snake_result.get("text"),
1862
- highlights=snake_result.get("highlights"),
1863
- highlight_scores=snake_result.get("highlight_scores"),
1864
- summary=snake_result.get("summary")
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[dict[str, Any]] = None,
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[dict[str, Any]] = None,
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
- options = to_camel_case(options)
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(AnswerResult(
2066
- id=snake_result.get("id"),
2067
- url=snake_result.get("url"),
2068
- title=snake_result.get("title"),
2069
- published_date=snake_result.get("published_date"),
2070
- author=snake_result.get("author"),
2071
- text=snake_result.get("text")
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[dict[str, Any]] = None,
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
- options = to_camel_case(options)
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(Result(
2199
- url=snake_result.get("url"),
2200
- id=snake_result.get("id"),
2201
- title=snake_result.get("title"),
2202
- score=snake_result.get("score"),
2203
- published_date=snake_result.get("published_date"),
2204
- author=snake_result.get("author"),
2205
- image=snake_result.get("image"),
2206
- favicon=snake_result.get("favicon"),
2207
- subpages=snake_result.get("subpages"),
2208
- extras=snake_result.get("extras"),
2209
- text=snake_result.get("text"),
2210
- highlights=snake_result.get("highlights"),
2211
- highlight_scores=snake_result.get("highlight_scores"),
2212
- summary=snake_result.get("summary")
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(Result(
2265
- url=snake_result.get("url"),
2266
- id=snake_result.get("id"),
2267
- title=snake_result.get("title"),
2268
- score=snake_result.get("score"),
2269
- published_date=snake_result.get("published_date"),
2270
- author=snake_result.get("author"),
2271
- image=snake_result.get("image"),
2272
- favicon=snake_result.get("favicon"),
2273
- subpages=snake_result.get("subpages"),
2274
- extras=snake_result.get("extras"),
2275
- text=snake_result.get("text"),
2276
- highlights=snake_result.get("highlights"),
2277
- highlight_scores=snake_result.get("highlight_scores"),
2278
- summary=snake_result.get("summary")
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
- options = to_camel_case(options)
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(ContentStatus(
2313
- id=status.get("id"),
2314
- status=status.get("status"),
2315
- source=status.get("source")
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(Result(
2321
- url=snake_result.get("url"),
2322
- id=snake_result.get("id"),
2323
- title=snake_result.get("title"),
2324
- score=snake_result.get("score"),
2325
- published_date=snake_result.get("published_date"),
2326
- author=snake_result.get("author"),
2327
- image=snake_result.get("image"),
2328
- favicon=snake_result.get("favicon"),
2329
- subpages=snake_result.get("subpages"),
2330
- extras=snake_result.get("extras"),
2331
- text=snake_result.get("text"),
2332
- highlights=snake_result.get("highlights"),
2333
- highlight_scores=snake_result.get("highlight_scores"),
2334
- summary=snake_result.get("summary")
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(Result(
2392
- url=snake_result.get("url"),
2393
- id=snake_result.get("id"),
2394
- title=snake_result.get("title"),
2395
- score=snake_result.get("score"),
2396
- published_date=snake_result.get("published_date"),
2397
- author=snake_result.get("author"),
2398
- image=snake_result.get("image"),
2399
- favicon=snake_result.get("favicon"),
2400
- subpages=snake_result.get("subpages"),
2401
- extras=snake_result.get("extras"),
2402
- text=snake_result.get("text"),
2403
- highlights=snake_result.get("highlights"),
2404
- highlight_scores=snake_result.get("highlight_scores"),
2405
- summary=snake_result.get("summary")
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(Result(
2456
- url=snake_result.get("url"),
2457
- id=snake_result.get("id"),
2458
- title=snake_result.get("title"),
2459
- score=snake_result.get("score"),
2460
- published_date=snake_result.get("published_date"),
2461
- author=snake_result.get("author"),
2462
- image=snake_result.get("image"),
2463
- favicon=snake_result.get("favicon"),
2464
- subpages=snake_result.get("subpages"),
2465
- extras=snake_result.get("extras"),
2466
- text=snake_result.get("text"),
2467
- highlights=snake_result.get("highlights"),
2468
- highlight_scores=snake_result.get("highlight_scores"),
2469
- summary=snake_result.get("summary")
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[dict[str, Any]] = None,
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
- options = to_camel_case(options)
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(AnswerResult(
2519
- id=snake_result.get("id"),
2520
- url=snake_result.get("url"),
2521
- title=snake_result.get("title"),
2522
- published_date=snake_result.get("published_date"),
2523
- author=snake_result.get("author"),
2524
- text=snake_result.get("text")
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[dict[str, Any]] = None,
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
- options = to_camel_case(options)
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)