exa-py 1.13.2__py3-none-any.whl → 1.14.0__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
@@ -1965,7 +1965,7 @@ class AsyncExa(Exa):
1965
1965
  res = await self.client.post(
1966
1966
  self.base_url + endpoint, json=data, headers=self.headers
1967
1967
  )
1968
- if res.status_code != 200:
1968
+ if res.status_code != 200 and res.status_code != 201:
1969
1969
  raise ValueError(
1970
1970
  f"Request failed with status code {res.status_code}: {res.text}"
1971
1971
  )
@@ -1,9 +1,10 @@
1
1
  from .client import ResearchClient, AsyncResearchClient
2
- from .models import ResearchTask
2
+ from .models import ResearchTask, ListResearchTasksResponse, ResearchTaskId
3
3
 
4
4
  __all__ = [
5
5
  "ResearchClient",
6
6
  "AsyncResearchClient",
7
7
  "ResearchTaskId",
8
8
  "ResearchTask",
9
+ "ListResearchTasksResponse",
9
10
  ]
exa_py/research/client.py CHANGED
@@ -9,14 +9,18 @@ block, but at runtime we only pay the cost if/when a helper is actually used.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- from typing import TYPE_CHECKING, Any, Dict
12
+ from typing import TYPE_CHECKING, Any, Dict, Optional
13
13
 
14
14
  if TYPE_CHECKING: # pragma: no cover – only for static analysers
15
15
  # Import with full type info when static type-checking. `_Result` still
16
16
  # lives in ``exa_py.api`` but the response model moved to
17
17
  # ``exa_py.research.models``.
18
18
  from ..api import _Result # noqa: F401
19
- from .models import ResearchTask, ResearchTaskId # noqa: F401
19
+ from .models import (
20
+ ResearchTask,
21
+ ResearchTaskId,
22
+ ListResearchTasksResponse,
23
+ ) # noqa: F401
20
24
 
21
25
  # ---------------------------------------------------------------------------
22
26
  # Public, user-facing clients
@@ -34,16 +38,20 @@ class ResearchClient:
34
38
  def create_task(
35
39
  self,
36
40
  *,
37
- input_instructions: str,
41
+ instructions: str,
42
+ model: str = "exa-research",
38
43
  output_schema: Dict[str, Any],
39
44
  ) -> "ResearchTaskId":
40
45
  """Submit a research request and return the *task identifier*."""
41
46
  payload = {
42
- "input": {"instructions": input_instructions},
47
+ "instructions": instructions,
48
+ "model": model,
43
49
  "output": {"schema": output_schema},
44
50
  }
45
51
 
46
- raw_response: Dict[str, Any] = self._client.request("/research/tasks", payload)
52
+ raw_response: Dict[str, Any] = self._client.request(
53
+ "/research/v0/tasks", payload
54
+ )
47
55
 
48
56
  # Defensive checks so that we fail loudly if the contract changes.
49
57
  if not isinstance(raw_response, dict) or "id" not in raw_response:
@@ -60,7 +68,7 @@ class ResearchClient:
60
68
  self, id: str
61
69
  ) -> "ResearchTask": # noqa: D401 – imperative mood is fine
62
70
  """Fetch the current status / result for a research task."""
63
- endpoint = f"/research/tasks/{id}"
71
+ endpoint = f"/research/v0/tasks/{id}"
64
72
 
65
73
  # The new endpoint is a simple GET.
66
74
  raw_response: Dict[str, Any] = self._client.request(endpoint, method="GET")
@@ -108,6 +116,54 @@ class ResearchClient:
108
116
 
109
117
  time.sleep(poll_interval)
110
118
 
119
+ # ------------------------------------------------------------------
120
+ # Listing helpers
121
+ # ------------------------------------------------------------------
122
+
123
+ def list(
124
+ self,
125
+ *,
126
+ cursor: Optional[str] = None,
127
+ limit: Optional[int] = None,
128
+ ) -> "ListResearchTasksResponse":
129
+ """List research tasks with optional pagination.
130
+
131
+ Parameters
132
+ ----------
133
+ cursor:
134
+ Pagination cursor returned by a previous call (optional).
135
+ limit:
136
+ Maximum number of tasks to return (optional).
137
+ """
138
+
139
+ params = {
140
+ k: v for k, v in {"cursor": cursor, "limit": limit}.items() if v is not None
141
+ }
142
+
143
+ raw_response: Dict[str, Any] = self._client.request(
144
+ "/research/v0/tasks",
145
+ data=None,
146
+ method="GET",
147
+ params=params or None,
148
+ )
149
+
150
+ # Defensive checks so that we fail loudly if the contract changes.
151
+ if not isinstance(raw_response, dict) or "data" not in raw_response:
152
+ raise RuntimeError(
153
+ f"Unexpected response while listing research tasks: {raw_response}"
154
+ )
155
+
156
+ tasks = [_build_research_task(t) for t in raw_response.get("data", [])]
157
+
158
+ # Lazy import to avoid cycles.
159
+ from .models import ListResearchTasksResponse # noqa: WPS433 – runtime import
160
+
161
+ return ListResearchTasksResponse(
162
+ data=tasks,
163
+ has_more=raw_response.get("hasMore", False),
164
+ next_cursor=raw_response.get("nextCursor"),
165
+ )
166
+
111
167
 
112
168
  class AsyncResearchClient:
113
169
  """Async counterpart used via :pyattr:`AsyncExa.research`."""
@@ -118,18 +174,20 @@ class AsyncResearchClient:
118
174
  async def create_task(
119
175
  self,
120
176
  *,
121
- input_instructions: str,
177
+ instructions: str,
178
+ model: str = "exa-research",
122
179
  output_schema: Dict[str, Any],
123
180
  ) -> "ResearchTaskId":
124
181
  """Submit a research request and return the *task identifier* (async)."""
125
182
 
126
183
  payload = {
127
- "input": {"instructions": input_instructions},
184
+ "instructions": instructions,
185
+ "model": model,
128
186
  "output": {"schema": output_schema},
129
187
  }
130
188
 
131
189
  raw_response: Dict[str, Any] = await self._client.async_request(
132
- "/research/tasks", payload
190
+ "/research/v0/tasks", payload
133
191
  )
134
192
 
135
193
  # Defensive checks so that we fail loudly if the contract changes.
@@ -146,7 +204,7 @@ class AsyncResearchClient:
146
204
  async def get_task(self, id: str) -> "ResearchTask": # noqa: D401
147
205
  """Fetch the current status / result for a research task (async)."""
148
206
 
149
- endpoint = f"/research/tasks/{id}"
207
+ endpoint = f"/research/v0/tasks/{id}"
150
208
 
151
209
  # Perform GET using the underlying HTTP client because `async_request`
152
210
  # only supports POST semantics.
@@ -199,6 +257,50 @@ class AsyncResearchClient:
199
257
 
200
258
  await asyncio.sleep(poll_interval)
201
259
 
260
+ # ------------------------------------------------------------------
261
+ # Listing helpers
262
+ # ------------------------------------------------------------------
263
+
264
+ async def list(
265
+ self,
266
+ *,
267
+ cursor: Optional[str] = None,
268
+ limit: Optional[int] = None,
269
+ ) -> "ListResearchTasksResponse":
270
+ """Async list of research tasks with optional pagination."""
271
+
272
+ params = {
273
+ k: v for k, v in {"cursor": cursor, "limit": limit}.items() if v is not None
274
+ }
275
+
276
+ resp = await self._client.client.get(
277
+ self._client.base_url + "/research/v0/tasks",
278
+ headers=self._client.headers,
279
+ params=params or None,
280
+ )
281
+
282
+ if resp.status_code >= 400:
283
+ raise RuntimeError(
284
+ f"Request failed with status code {resp.status_code}: {resp.text}"
285
+ )
286
+
287
+ raw_response: Dict[str, Any] = resp.json()
288
+
289
+ if not isinstance(raw_response, dict) or "data" not in raw_response:
290
+ raise RuntimeError(
291
+ f"Unexpected response while listing research tasks: {raw_response}"
292
+ )
293
+
294
+ tasks = [_build_research_task(t) for t in raw_response.get("data", [])]
295
+
296
+ from .models import ListResearchTasksResponse # noqa: WPS433 – runtime import
297
+
298
+ return ListResearchTasksResponse(
299
+ data=tasks,
300
+ has_more=raw_response.get("hasMore", False),
301
+ next_cursor=raw_response.get("nextCursor"),
302
+ )
303
+
202
304
 
203
305
  # ---------------------------------------------------------------------------
204
306
  # Internal helpers (lazy imports to avoid cycles)
exa_py/research/models.py CHANGED
@@ -92,7 +92,29 @@ class ResearchTask:
92
92
  )
93
93
 
94
94
 
95
+ @dataclass
96
+ class ListResearchTasksResponse:
97
+ """Paginated list of research tasks."""
98
+
99
+ data: List[ResearchTask]
100
+ has_more: bool
101
+ next_cursor: Optional[str]
102
+
103
+ # -----------------------------------------------------------------
104
+ # Pretty representation helpers
105
+ # -----------------------------------------------------------------
106
+ def __str__(self) -> str: # pragma: no cover – convenience only
107
+ tasks_repr = "\n\n".join(str(task) for task in self.data)
108
+ cursor_repr = self.next_cursor or "None"
109
+ return (
110
+ f"Tasks:\n{tasks_repr}\n\n"
111
+ f"Has more: {self.has_more}\n"
112
+ f"Next cursor: {cursor_repr}"
113
+ )
114
+
115
+
95
116
  __all__ = [
96
117
  "ResearchTaskId",
97
118
  "ResearchTask",
119
+ "ListResearchTasksResponse",
98
120
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: exa-py
3
- Version: 1.13.2
3
+ Version: 1.14.0
4
4
  Summary: Python SDK for Exa API.
5
5
  License: MIT
6
6
  Author: Exa AI
@@ -126,7 +126,8 @@ exa = Exa(api_key="your-api-key")
126
126
  },
127
127
  }
128
128
  resp = exa.research.create_task(
129
- input_instructions=QUESTION,
129
+ instructions=QUESTION,
130
+ model="exa-research",
130
131
  output_schema=OUTPUT_SCHEMA,
131
132
  )
132
133
  ```
@@ -1,9 +1,9 @@
1
1
  exa_py/__init__.py,sha256=M2GC9oSdoV6m2msboW0vMWWl8wrth4o6gmEV4MYLGG8,66
2
- exa_py/api.py,sha256=Bn7h_eRvXmwBUmJi2B2JpHAQPrHfbwKf0A-XVXLjqa0,84876
2
+ exa_py/api.py,sha256=zwTEiIkWa6Gynx5NW9VMMY4Nws63gVDRSmzKdT4v2aY,84903
3
3
  exa_py/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- exa_py/research/__init__.py,sha256=D1xgm4VlbWtRb1cMgshcW4dIyR7IXhOW2s7ihxcE1Jc,195
5
- exa_py/research/client.py,sha256=Zno5xblfwhX8gWgc4OvI24a-ZS7_g1b32_tr6j7C7Jg,8217
6
- exa_py/research/models.py,sha256=WXTnALhM9FcVQ95Tzzc5EDKU48hyPhu8RSMmipqCjOk,2982
4
+ exa_py/research/__init__.py,sha256=QeY-j6bP4QP5tF9ytX0IeQhJvd0Wn4cJCD69U8pP7kA,271
5
+ exa_py/research/client.py,sha256=oFQpczMcfA20Sn_yq36As8GlFo2u8B77IqVMMRqt3qE,11481
6
+ exa_py/research/models.py,sha256=j7YgRoMRp2MLgnaij7775x_hJEeV5gksKpfLwmawqxY,3704
7
7
  exa_py/utils.py,sha256=Rc1FJjoR9LQ7L_OJM91Sd1GNkbHjcLyEvJENhRix6gc,2405
8
8
  exa_py/websets/__init__.py,sha256=uOBAb9VrIHrPKoddGOp2ai2KgWlyUVCLMZqfbGOlboA,70
9
9
  exa_py/websets/_generator/pydantic/BaseModel.jinja2,sha256=RUDCmPZVamoVx1WudylscYFfDhGoNNtRYlpTvKjAiuA,1276
@@ -23,6 +23,6 @@ exa_py/websets/streams/runs/client.py,sha256=ja63-G0Y74YDgsWq30aoa0llIx8I9mIwbCb
23
23
  exa_py/websets/types.py,sha256=-KmC6N9LMh5fPhhXZslmUyQcSmBqIdPF-qQ27qS4GEw,34756
24
24
  exa_py/websets/webhooks/__init__.py,sha256=iTPBCxFd73z4RifLQMX6iRECx_6pwlI5qscLNjMOUHE,77
25
25
  exa_py/websets/webhooks/client.py,sha256=zsIRMTeJU65yj-zo7Zz-gG02Prtzgcx6utGFSoY4HQQ,4222
26
- exa_py-1.13.2.dist-info/METADATA,sha256=kvXQv5_-4BJ0uIYLfZ-c9aJOfqE_0STUZ3oM-GX2Jfs,4098
27
- exa_py-1.13.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
28
- exa_py-1.13.2.dist-info/RECORD,,
26
+ exa_py-1.14.0.dist-info/METADATA,sha256=jOIKH214iEOdk7HE46-tyhST5d9iGvgCaaff12tPxiY,4120
27
+ exa_py-1.14.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
28
+ exa_py-1.14.0.dist-info/RECORD,,