exa-py 1.13.0__tar.gz → 1.13.2__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.

Potentially problematic release.


This version of exa-py might be problematic. Click here for more details.

Files changed (36) hide show
  1. {exa_py-1.13.0/exa_py.egg-info → exa_py-1.13.2}/PKG-INFO +54 -16
  2. exa_py-1.13.0/PKG-INFO → exa_py-1.13.2/README.md +37 -23
  3. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/api.py +47 -125
  4. exa_py-1.13.2/exa_py/research/__init__.py +9 -0
  5. exa_py-1.13.2/exa_py/research/client.py +232 -0
  6. exa_py-1.13.2/exa_py/research/models.py +98 -0
  7. exa_py-1.13.2/exa_py/websets/_generator/pydantic/BaseModel.jinja2 +42 -0
  8. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/client.py +2 -1
  9. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/core/base.py +6 -2
  10. exa_py-1.13.2/exa_py/websets/streams/__init__.py +4 -0
  11. exa_py-1.13.2/exa_py/websets/streams/client.py +96 -0
  12. exa_py-1.13.2/exa_py/websets/streams/runs/__init__.py +3 -0
  13. exa_py-1.13.2/exa_py/websets/streams/runs/client.py +38 -0
  14. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/types.py +302 -49
  15. {exa_py-1.13.0 → exa_py-1.13.2}/pyproject.toml +4 -6
  16. exa_py-1.13.0/README.md +0 -76
  17. exa_py-1.13.0/exa_py.egg-info/SOURCES.txt +0 -26
  18. exa_py-1.13.0/exa_py.egg-info/dependency_links.txt +0 -1
  19. exa_py-1.13.0/exa_py.egg-info/requires.txt +0 -6
  20. exa_py-1.13.0/exa_py.egg-info/top_level.txt +0 -1
  21. exa_py-1.13.0/setup.cfg +0 -4
  22. exa_py-1.13.0/setup.py +0 -26
  23. exa_py-1.13.0/tests/test_websets.py +0 -415
  24. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/__init__.py +0 -0
  25. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/py.typed +0 -0
  26. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/utils.py +0 -0
  27. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/__init__.py +0 -0
  28. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/core/__init__.py +0 -0
  29. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/enrichments/__init__.py +0 -0
  30. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/enrichments/client.py +0 -0
  31. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/items/__init__.py +0 -0
  32. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/items/client.py +0 -0
  33. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/searches/__init__.py +0 -0
  34. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/searches/client.py +0 -0
  35. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/webhooks/__init__.py +0 -0
  36. {exa_py-1.13.0 → exa_py-1.13.2}/exa_py/websets/webhooks/client.py +0 -0
@@ -1,21 +1,25 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.3
2
2
  Name: exa-py
3
- Version: 1.13.0
3
+ Version: 1.13.2
4
4
  Summary: Python SDK for Exa API.
5
- Home-page: https://github.com/exa-labs/exa-py
6
- Author: Exa
7
- Author-email: Exa AI <hello@exa.ai>
8
5
  License: MIT
6
+ Author: Exa AI
7
+ Author-email: hello@exa.ai
9
8
  Requires-Python: >=3.9
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Requires-Dist: httpx (>=0.28.1)
17
+ Requires-Dist: openai (>=1.48)
18
+ Requires-Dist: pydantic (>=2.10.6)
19
+ Requires-Dist: pytest-mock (>=3.14.0)
20
+ Requires-Dist: requests (>=2.32.3)
21
+ Requires-Dist: typing-extensions (>=4.12.2)
10
22
  Description-Content-Type: text/markdown
11
- Requires-Dist: requests>=2.32.3
12
- Requires-Dist: typing-extensions>=4.12.2
13
- Requires-Dist: openai>=1.48
14
- Requires-Dist: pydantic>=2.10.6
15
- Requires-Dist: pytest-mock>=3.14.0
16
- Requires-Dist: httpx>=0.28.1
17
- Dynamic: author
18
- Dynamic: home-page
19
23
 
20
24
  # Exa
21
25
 
@@ -41,6 +45,7 @@ exa = Exa(api_key="your-api-key")
41
45
  ```
42
46
 
43
47
  ## Common requests
48
+
44
49
  ```python
45
50
 
46
51
  # basic search
@@ -59,9 +64,9 @@ exa = Exa(api_key="your-api-key")
59
64
  results = exa.search_and_contents("This is a Exa query:")
60
65
 
61
66
  # search and get contents with contents options
62
- results = exa.search_and_contents("This is a Exa query:",
67
+ results = exa.search_and_contents("This is a Exa query:",
63
68
  text={"include_html_tags": True, "max_characters": 1000})
64
-
69
+
65
70
  # find similar documents
66
71
  results = exa.find_similar("https://example.com")
67
72
 
@@ -75,7 +80,7 @@ exa = Exa(api_key="your-api-key")
75
80
  results = exa.get_contents(["tesla.com"])
76
81
 
77
82
  # get contents with contents options
78
- results = exa.get_contents(["urls"],
83
+ results = exa.get_contents(["urls"],
79
84
  text={"include_html_tags": True, "max_characters": 1000})
80
85
 
81
86
  # basic answer
@@ -91,5 +96,38 @@ exa = Exa(api_key="your-api-key")
91
96
  for chunk in response:
92
97
  print(chunk, end='', flush=True)
93
98
 
99
+ # research task example – answer a question with citations
100
+ # Example prompt & schema inspired by the TypeScript example.
101
+ QUESTION = (
102
+ "Summarize the history of San Francisco highlighting one or two major events "
103
+ "for each decade from 1850 to 1950"
104
+ )
105
+ OUTPUT_SCHEMA: Dict[str, Any] = {
106
+ "type": "object",
107
+ "required": ["timeline"],
108
+ "properties": {
109
+ "timeline": {
110
+ "type": "array",
111
+ "items": {
112
+ "type": "object",
113
+ "required": ["decade", "notableEvents"],
114
+ "properties": {
115
+ "decade": {
116
+ "type": "string",
117
+ "description": 'Decade label e.g. "1850s"',
118
+ },
119
+ "notableEvents": {
120
+ "type": "string",
121
+ "description": "A summary of notable events.",
122
+ },
123
+ },
124
+ },
125
+ },
126
+ },
127
+ }
128
+ resp = exa.research.create_task(
129
+ input_instructions=QUESTION,
130
+ output_schema=OUTPUT_SCHEMA,
131
+ )
94
132
  ```
95
133
 
@@ -1,22 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: exa-py
3
- Version: 1.13.0
4
- Summary: Python SDK for Exa API.
5
- Home-page: https://github.com/exa-labs/exa-py
6
- Author: Exa
7
- Author-email: Exa AI <hello@exa.ai>
8
- License: MIT
9
- Requires-Python: >=3.9
10
- Description-Content-Type: text/markdown
11
- Requires-Dist: requests>=2.32.3
12
- Requires-Dist: typing-extensions>=4.12.2
13
- Requires-Dist: openai>=1.48
14
- Requires-Dist: pydantic>=2.10.6
15
- Requires-Dist: pytest-mock>=3.14.0
16
- Requires-Dist: httpx>=0.28.1
17
- Dynamic: author
18
- Dynamic: home-page
19
-
20
1
  # Exa
21
2
 
22
3
  Exa (formerly Metaphor) API in Python
@@ -41,6 +22,7 @@ exa = Exa(api_key="your-api-key")
41
22
  ```
42
23
 
43
24
  ## Common requests
25
+
44
26
  ```python
45
27
 
46
28
  # basic search
@@ -59,9 +41,9 @@ exa = Exa(api_key="your-api-key")
59
41
  results = exa.search_and_contents("This is a Exa query:")
60
42
 
61
43
  # search and get contents with contents options
62
- results = exa.search_and_contents("This is a Exa query:",
44
+ results = exa.search_and_contents("This is a Exa query:",
63
45
  text={"include_html_tags": True, "max_characters": 1000})
64
-
46
+
65
47
  # find similar documents
66
48
  results = exa.find_similar("https://example.com")
67
49
 
@@ -75,7 +57,7 @@ exa = Exa(api_key="your-api-key")
75
57
  results = exa.get_contents(["tesla.com"])
76
58
 
77
59
  # get contents with contents options
78
- results = exa.get_contents(["urls"],
60
+ results = exa.get_contents(["urls"],
79
61
  text={"include_html_tags": True, "max_characters": 1000})
80
62
 
81
63
  # basic answer
@@ -91,5 +73,37 @@ exa = Exa(api_key="your-api-key")
91
73
  for chunk in response:
92
74
  print(chunk, end='', flush=True)
93
75
 
76
+ # research task example – answer a question with citations
77
+ # Example prompt & schema inspired by the TypeScript example.
78
+ QUESTION = (
79
+ "Summarize the history of San Francisco highlighting one or two major events "
80
+ "for each decade from 1850 to 1950"
81
+ )
82
+ OUTPUT_SCHEMA: Dict[str, Any] = {
83
+ "type": "object",
84
+ "required": ["timeline"],
85
+ "properties": {
86
+ "timeline": {
87
+ "type": "array",
88
+ "items": {
89
+ "type": "object",
90
+ "required": ["decade", "notableEvents"],
91
+ "properties": {
92
+ "decade": {
93
+ "type": "string",
94
+ "description": 'Decade label e.g. "1850s"',
95
+ },
96
+ "notableEvents": {
97
+ "type": "string",
98
+ "description": "A summary of notable events.",
99
+ },
100
+ },
101
+ },
102
+ },
103
+ },
104
+ }
105
+ resp = exa.research.create_task(
106
+ input_instructions=QUESTION,
107
+ output_schema=OUTPUT_SCHEMA,
108
+ )
94
109
  ```
95
-
@@ -38,6 +38,7 @@ from exa_py.utils import (
38
38
  )
39
39
  from .websets import WebsetsClient
40
40
  from .websets.core.base import ExaJSONEncoder
41
+ from .research.client import ResearchClient, AsyncResearchClient
41
42
 
42
43
  is_beta = os.getenv("IS_BETA") == "True"
43
44
 
@@ -56,7 +57,7 @@ def snake_to_camel(snake_str: str) -> str:
56
57
  return "$schema"
57
58
  if snake_str == "not_":
58
59
  return "not"
59
-
60
+
60
61
  components = snake_str.split("_")
61
62
  return components[0] + "".join(x.title() for x in components[1:])
62
63
 
@@ -261,6 +262,7 @@ class JSONSchema(TypedDict, total=False):
261
262
  """Represents a JSON Schema definition used for structured summary output.
262
263
  To learn more visit https://json-schema.org/overview/what-is-jsonschema.
263
264
  """
265
+
264
266
  schema_: str # This will be converted to "$schema" in JSON
265
267
  title: str
266
268
  description: str
@@ -288,7 +290,7 @@ class SummaryContentsOptions(TypedDict, total=False):
288
290
 
289
291
  query: str
290
292
  schema: JSONSchema
291
-
293
+
292
294
 
293
295
  class ExtrasOptions(TypedDict, total=False):
294
296
  """A class representing additional extraction fields (e.g. links, images)"""
@@ -669,7 +671,7 @@ class AnswerResponse:
669
671
  citations (List[AnswerResult]): A list of citations used to generate the answer.
670
672
  """
671
673
 
672
- answer: str
674
+ answer: Union[str, dict[str, Any]]
673
675
  citations: List[AnswerResult]
674
676
 
675
677
  def __str__(self):
@@ -765,9 +767,9 @@ class AsyncStreamAnswerResponse:
765
767
  content = chunk["choices"][0]["delta"].get("content")
766
768
 
767
769
  if (
768
- "citations" in chunk
769
- and chunk["citations"]
770
- and chunk["citations"] != "null"
770
+ "citations" in chunk
771
+ and chunk["citations"]
772
+ and chunk["citations"] != "null"
771
773
  ):
772
774
  citations = [
773
775
  AnswerResult(**to_snake_case(s)) for s in chunk["citations"]
@@ -776,6 +778,7 @@ class AsyncStreamAnswerResponse:
776
778
  stream_chunk = StreamChunk(content=content, citations=citations)
777
779
  if stream_chunk.has_data():
778
780
  yield stream_chunk
781
+
779
782
  return generator()
780
783
 
781
784
  def close(self) -> None:
@@ -834,36 +837,6 @@ def nest_fields(original_dict: Dict, fields_to_nest: List[str], new_key: str):
834
837
 
835
838
  return original_dict
836
839
 
837
- @dataclass
838
- class ResearchTaskResponse:
839
- """A class representing the response for a research task.
840
-
841
- Attributes:
842
- id (str): The unique identifier for the research request.
843
- status (str): Status of the research request.
844
- output (Optional[Dict[str, Any]]): The answer structured as JSON, if available.
845
- citations (Optional[Dict[str, List[_Result]]]): List of citations used to generate the answer, grouped by root field in the output schema.
846
- """
847
-
848
- id: str
849
- status: str
850
- output: Optional[Dict[str, Any]]
851
- citations: Dict[str, List[_Result]]
852
-
853
- def __str__(self):
854
- output_repr = (
855
- json.dumps(self.output, indent=2, ensure_ascii=False)
856
- if self.output is not None
857
- else "None"
858
- )
859
- citations_str = "\n\n".join(str(src) for src in self.citations)
860
- return (
861
- f"ID: {self.id}\n"
862
- f"Status: {self.status}\n"
863
- f"Output: {output_repr}\n\n"
864
- f"Citations:\n{citations_str}"
865
- )
866
-
867
840
 
868
841
  class Exa:
869
842
  """A client for interacting with Exa API."""
@@ -872,7 +845,7 @@ class Exa:
872
845
  self,
873
846
  api_key: Optional[str],
874
847
  base_url: str = "https://api.exa.ai",
875
- user_agent: str = "exa-py 1.12.1",
848
+ user_agent: str = "exa-py 1.12.4",
876
849
  ):
877
850
  """Initialize the Exa client with the provided API key and optional base URL and user agent.
878
851
 
@@ -889,10 +862,22 @@ class Exa:
889
862
  "API key must be provided as an argument or in EXA_API_KEY environment variable"
890
863
  )
891
864
  self.base_url = base_url
892
- self.headers = {"x-api-key": api_key, "User-Agent": user_agent, "Content-Type": "application/json"}
865
+ self.headers = {
866
+ "x-api-key": api_key,
867
+ "User-Agent": user_agent,
868
+ "Content-Type": "application/json",
869
+ }
893
870
  self.websets = WebsetsClient(self)
871
+ # Research tasks client (new, mirrors Websets design)
872
+ self.research = ResearchClient(self)
894
873
 
895
- def request(self, endpoint: str, data: Optional[Union[Dict[str, Any], str]] = None, method: str = "POST", params: Optional[Dict[str, Any]] = None) -> Union[Dict[str, Any], requests.Response]:
874
+ def request(
875
+ self,
876
+ endpoint: str,
877
+ data: Optional[Union[Dict[str, Any], str]] = None,
878
+ method: str = "POST",
879
+ params: Optional[Dict[str, Any]] = None,
880
+ ) -> Union[Dict[str, Any], requests.Response]:
896
881
  """Send a request to the Exa API, optionally streaming if data['stream'] is True.
897
882
 
898
883
  Args:
@@ -915,13 +900,13 @@ class Exa:
915
900
  else:
916
901
  # Otherwise, serialize the dictionary to JSON if it exists
917
902
  json_data = json.dumps(data, cls=ExaJSONEncoder) if data else None
918
-
903
+
919
904
  if data and data.get("stream"):
920
905
  res = requests.post(
921
- self.base_url + endpoint,
906
+ self.base_url + endpoint,
922
907
  data=json_data,
923
- headers=self.headers,
924
- stream=True
908
+ headers=self.headers,
909
+ stream=True,
925
910
  )
926
911
  return res
927
912
 
@@ -931,20 +916,14 @@ class Exa:
931
916
  )
932
917
  elif method.upper() == "POST":
933
918
  res = requests.post(
934
- self.base_url + endpoint,
935
- data=json_data,
936
- headers=self.headers
919
+ self.base_url + endpoint, data=json_data, headers=self.headers
937
920
  )
938
921
  elif method.upper() == "PATCH":
939
922
  res = requests.patch(
940
- self.base_url + endpoint,
941
- data=json_data,
942
- headers=self.headers
923
+ self.base_url + endpoint, data=json_data, headers=self.headers
943
924
  )
944
925
  elif method.upper() == "DELETE":
945
- res = requests.delete(
946
- self.base_url + endpoint, headers=self.headers
947
- )
926
+ res = requests.delete(self.base_url + endpoint, headers=self.headers)
948
927
  else:
949
928
  raise ValueError(f"Unsupported HTTP method: {method}")
950
929
 
@@ -1875,6 +1854,7 @@ class Exa:
1875
1854
  text: Optional[bool] = False,
1876
1855
  system_prompt: Optional[str] = None,
1877
1856
  model: Optional[Literal["exa", "exa-pro"]] = None,
1857
+ output_schema: Optional[dict[str, Any]] = None,
1878
1858
  ) -> Union[AnswerResponse, StreamAnswerResponse]: ...
1879
1859
 
1880
1860
  def answer(
@@ -1885,6 +1865,7 @@ class Exa:
1885
1865
  text: Optional[bool] = False,
1886
1866
  system_prompt: Optional[str] = None,
1887
1867
  model: Optional[Literal["exa", "exa-pro"]] = None,
1868
+ output_schema: Optional[dict[str, Any]] = None,
1888
1869
  ) -> Union[AnswerResponse, StreamAnswerResponse]:
1889
1870
  """Generate an answer to a query using Exa's search and LLM capabilities.
1890
1871
 
@@ -1893,6 +1874,7 @@ class Exa:
1893
1874
  text (bool, optional): Whether to include full text in the results. Defaults to False.
1894
1875
  system_prompt (str, optional): A system prompt to guide the LLM's behavior when generating the answer.
1895
1876
  model (str, optional): The model to use for answering. Either "exa" or "exa-pro". Defaults to None.
1877
+ output_schema (dict[str, Any], optional): JSON schema describing the desired answer structure.
1896
1878
 
1897
1879
  Returns:
1898
1880
  AnswerResponse: An object containing the answer and citations.
@@ -1922,6 +1904,7 @@ class Exa:
1922
1904
  text: bool = False,
1923
1905
  system_prompt: Optional[str] = None,
1924
1906
  model: Optional[Literal["exa", "exa-pro"]] = None,
1907
+ output_schema: Optional[dict[str, Any]] = None,
1925
1908
  ) -> StreamAnswerResponse:
1926
1909
  """Generate a streaming answer response.
1927
1910
 
@@ -1930,7 +1913,7 @@ class Exa:
1930
1913
  text (bool): Whether to include full text in the results. Defaults to False.
1931
1914
  system_prompt (str, optional): A system prompt to guide the LLM's behavior when generating the answer.
1932
1915
  model (str, optional): The model to use for answering. Either "exa" or "exa-pro". Defaults to None.
1933
-
1916
+ output_schema (dict[str, Any], optional): JSON schema describing the desired answer structure.
1934
1917
  Returns:
1935
1918
  StreamAnswerResponse: An object that can be iterated over to retrieve (partial text, partial citations).
1936
1919
  Each iteration yields a tuple of (Optional[str], Optional[List[AnswerResult]]).
@@ -1941,40 +1924,12 @@ class Exa:
1941
1924
  raw_response = self.request("/answer", options)
1942
1925
  return StreamAnswerResponse(raw_response)
1943
1926
 
1944
- def researchTask(
1945
- self,
1946
- *,
1947
- input_instructions: str,
1948
- output_schema: Dict[str, Any],
1949
- ) -> ResearchTaskResponse:
1950
- """Submit a research request to Exa.
1951
-
1952
- Args:
1953
- input_instructions (str): The instructions for the research task.
1954
- output_schema (Dict[str, Any]): JSON schema describing the desired answer structure.
1955
- """
1956
- # Build the request payload expected by the Exa API
1957
- options = {
1958
- "input": {"instructions": input_instructions},
1959
- "output": {"schema": output_schema},
1960
- }
1961
-
1962
- response = self.request("/research/tasks", options)
1963
-
1964
- return ResearchTaskResponse(
1965
- id=response["id"],
1966
- status=response["status"],
1967
- output=response.get("output"),
1968
- citations={
1969
- key: [_Result(**to_snake_case(citation)) for citation in citations_list]
1970
- for key, citations_list in response.get("citations", {}).items()
1971
- },
1972
- )
1973
-
1974
1927
 
1975
1928
  class AsyncExa(Exa):
1976
1929
  def __init__(self, api_key: str, api_base: str = "https://api.exa.ai"):
1977
1930
  super().__init__(api_key, api_base)
1931
+ # Override the synchronous ResearchClient with its async counterpart.
1932
+ self.research = AsyncResearchClient(self)
1978
1933
  self._client = None
1979
1934
 
1980
1935
  @property
@@ -1982,9 +1937,7 @@ class AsyncExa(Exa):
1982
1937
  # this may only be a
1983
1938
  if self._client is None:
1984
1939
  self._client = httpx.AsyncClient(
1985
- base_url=self.base_url,
1986
- headers=self.headers,
1987
- timeout=60
1940
+ base_url=self.base_url, headers=self.headers, timeout=60
1988
1941
  )
1989
1942
  return self._client
1990
1943
 
@@ -2004,15 +1957,14 @@ class AsyncExa(Exa):
2004
1957
  """
2005
1958
  if data.get("stream"):
2006
1959
  request = httpx.Request(
2007
- 'POST',
2008
- self.base_url + endpoint,
2009
- json=data,
2010
- headers=self.headers
1960
+ "POST", self.base_url + endpoint, json=data, headers=self.headers
2011
1961
  )
2012
1962
  res = await self.client.send(request, stream=True)
2013
1963
  return res
2014
1964
 
2015
- res = await self.client.post(self.base_url + endpoint, json=data, headers=self.headers)
1965
+ res = await self.client.post(
1966
+ self.base_url + endpoint, json=data, headers=self.headers
1967
+ )
2016
1968
  if res.status_code != 200:
2017
1969
  raise ValueError(
2018
1970
  f"Request failed with status code {res.status_code}: {res.text}"
@@ -2250,6 +2202,7 @@ class AsyncExa(Exa):
2250
2202
  text: Optional[bool] = False,
2251
2203
  system_prompt: Optional[str] = None,
2252
2204
  model: Optional[Literal["exa", "exa-pro"]] = None,
2205
+ output_schema: Optional[dict[str, Any]] = None,
2253
2206
  ) -> Union[AnswerResponse, StreamAnswerResponse]:
2254
2207
  """Generate an answer to a query using Exa's search and LLM capabilities.
2255
2208
 
@@ -2258,6 +2211,7 @@ class AsyncExa(Exa):
2258
2211
  text (bool, optional): Whether to include full text in the results. Defaults to False.
2259
2212
  system_prompt (str, optional): A system prompt to guide the LLM's behavior when generating the answer.
2260
2213
  model (str, optional): The model to use for answering. Either "exa" or "exa-pro". Defaults to None.
2214
+ output_schema (dict[str, Any], optional): JSON schema describing the desired answer structure.
2261
2215
 
2262
2216
  Returns:
2263
2217
  AnswerResponse: An object containing the answer and citations.
@@ -2287,6 +2241,7 @@ class AsyncExa(Exa):
2287
2241
  text: bool = False,
2288
2242
  system_prompt: Optional[str] = None,
2289
2243
  model: Optional[Literal["exa", "exa-pro"]] = None,
2244
+ output_schema: Optional[dict[str, Any]] = None,
2290
2245
  ) -> AsyncStreamAnswerResponse:
2291
2246
  """Generate a streaming answer response.
2292
2247
 
@@ -2295,7 +2250,7 @@ class AsyncExa(Exa):
2295
2250
  text (bool): Whether to include full text in the results. Defaults to False.
2296
2251
  system_prompt (str, optional): A system prompt to guide the LLM's behavior when generating the answer.
2297
2252
  model (str, optional): The model to use for answering. Either "exa" or "exa-pro". Defaults to None.
2298
-
2253
+ output_schema (dict[str, Any], optional): JSON schema describing the desired answer structure.
2299
2254
  Returns:
2300
2255
  AsyncStreamAnswerResponse: An object that can be iterated over to retrieve (partial text, partial citations).
2301
2256
  Each iteration yields a tuple of (Optional[str], Optional[List[AnswerResult]]).
@@ -2305,36 +2260,3 @@ class AsyncExa(Exa):
2305
2260
  options["stream"] = True
2306
2261
  raw_response = await self.async_request("/answer", options)
2307
2262
  return AsyncStreamAnswerResponse(raw_response)
2308
-
2309
- async def researchTask(
2310
- self,
2311
- *,
2312
- input_instructions: str,
2313
- output_schema: Dict[str, Any],
2314
- ) -> ResearchTaskResponse:
2315
- """Asynchronously submit a research request to Exa.
2316
-
2317
- Args:
2318
- input_instructions (str): The instructions for the research task.
2319
- output_schema (Dict[str, Any]): JSON schema describing the desired answer structure.
2320
-
2321
- Returns:
2322
- ResearchTaskResponse: The parsed response from the Exa API.
2323
- """
2324
- # Build the request payload expected by the Exa API
2325
- options = {
2326
- "input": {"instructions": input_instructions},
2327
- "output": {"schema": output_schema},
2328
- }
2329
-
2330
- response = await self.async_request("/research/tasks", options)
2331
-
2332
- return ResearchTaskResponse(
2333
- id=response["id"],
2334
- status=response["status"],
2335
- output=response.get("output"),
2336
- citations={
2337
- key: [_Result(**to_snake_case(citation)) for citation in citations_list]
2338
- for key, citations_list in response.get("citations", {}).items()
2339
- },
2340
- )
@@ -0,0 +1,9 @@
1
+ from .client import ResearchClient, AsyncResearchClient
2
+ from .models import ResearchTask
3
+
4
+ __all__ = [
5
+ "ResearchClient",
6
+ "AsyncResearchClient",
7
+ "ResearchTaskId",
8
+ "ResearchTask",
9
+ ]