exa-py 1.13.2__tar.gz → 1.14.1__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 (28) hide show
  1. {exa_py-1.13.2 → exa_py-1.14.1}/PKG-INFO +3 -2
  2. {exa_py-1.13.2 → exa_py-1.14.1}/README.md +2 -1
  3. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/api.py +1 -1
  4. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/research/__init__.py +2 -1
  5. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/research/client.py +123 -18
  6. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/research/models.py +22 -0
  7. {exa_py-1.13.2 → exa_py-1.14.1}/pyproject.toml +2 -2
  8. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/__init__.py +0 -0
  9. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/py.typed +0 -0
  10. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/utils.py +0 -0
  11. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/__init__.py +0 -0
  12. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/_generator/pydantic/BaseModel.jinja2 +0 -0
  13. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/client.py +0 -0
  14. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/core/__init__.py +0 -0
  15. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/core/base.py +0 -0
  16. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/enrichments/__init__.py +0 -0
  17. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/enrichments/client.py +0 -0
  18. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/items/__init__.py +0 -0
  19. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/items/client.py +0 -0
  20. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/searches/__init__.py +0 -0
  21. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/searches/client.py +0 -0
  22. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/streams/__init__.py +0 -0
  23. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/streams/client.py +0 -0
  24. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/streams/runs/__init__.py +0 -0
  25. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/streams/runs/client.py +0 -0
  26. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/types.py +0 -0
  27. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/webhooks/__init__.py +0 -0
  28. {exa_py-1.13.2 → exa_py-1.14.1}/exa_py/websets/webhooks/client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: exa-py
3
- Version: 1.13.2
3
+ Version: 1.14.1
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
  ```
@@ -103,7 +103,8 @@ exa = Exa(api_key="your-api-key")
103
103
  },
104
104
  }
105
105
  resp = exa.research.create_task(
106
- input_instructions=QUESTION,
106
+ instructions=QUESTION,
107
+ model="exa-research",
107
108
  output_schema=OUTPUT_SCHEMA,
108
109
  )
109
110
  ```
@@ -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
  ]
@@ -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, Literal
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,25 @@ class ResearchClient:
34
38
  def create_task(
35
39
  self,
36
40
  *,
37
- input_instructions: str,
38
- output_schema: Dict[str, Any],
41
+ instructions: str,
42
+ model: Literal["exa-research", "exa-research-pro"] = "exa-research",
43
+ output_infer_schema: bool = None,
44
+ output_schema: Dict[str, Any] = None,
39
45
  ) -> "ResearchTaskId":
40
46
  """Submit a research request and return the *task identifier*."""
41
- payload = {
42
- "input": {"instructions": input_instructions},
43
- "output": {"schema": output_schema},
44
- }
45
-
46
- raw_response: Dict[str, Any] = self._client.request("/research/tasks", payload)
47
+ payload = {"instructions": instructions}
48
+ if model is not None:
49
+ payload["model"] = model
50
+ if output_schema is not None or output_infer_schema is not None:
51
+ payload["output"] = {}
52
+ if output_schema is not None:
53
+ payload["output"]["schema"] = output_schema
54
+ if output_infer_schema is not None:
55
+ payload["output"]["inferSchema"] = output_infer_schema
56
+
57
+ raw_response: Dict[str, Any] = self._client.request(
58
+ "/research/v0/tasks", payload
59
+ )
47
60
 
48
61
  # Defensive checks so that we fail loudly if the contract changes.
49
62
  if not isinstance(raw_response, dict) or "id" not in raw_response:
@@ -56,11 +69,9 @@ class ResearchClient:
56
69
 
57
70
  return ResearchTaskId(id=raw_response["id"])
58
71
 
59
- def get_task(
60
- self, id: str
61
- ) -> "ResearchTask": # noqa: D401 – imperative mood is fine
72
+ def get_task(self, id: str) -> "ResearchTask": # noqa: D401 – imperative mood is fine
62
73
  """Fetch the current status / result for a research task."""
63
- endpoint = f"/research/tasks/{id}"
74
+ endpoint = f"/research/v0/tasks/{id}"
64
75
 
65
76
  # The new endpoint is a simple GET.
66
77
  raw_response: Dict[str, Any] = self._client.request(endpoint, method="GET")
@@ -108,6 +119,54 @@ class ResearchClient:
108
119
 
109
120
  time.sleep(poll_interval)
110
121
 
122
+ # ------------------------------------------------------------------
123
+ # Listing helpers
124
+ # ------------------------------------------------------------------
125
+
126
+ def list(
127
+ self,
128
+ *,
129
+ cursor: Optional[str] = None,
130
+ limit: Optional[int] = None,
131
+ ) -> "ListResearchTasksResponse":
132
+ """List research tasks with optional pagination.
133
+
134
+ Parameters
135
+ ----------
136
+ cursor:
137
+ Pagination cursor returned by a previous call (optional).
138
+ limit:
139
+ Maximum number of tasks to return (optional).
140
+ """
141
+
142
+ params = {
143
+ k: v for k, v in {"cursor": cursor, "limit": limit}.items() if v is not None
144
+ }
145
+
146
+ raw_response: Dict[str, Any] = self._client.request(
147
+ "/research/v0/tasks",
148
+ data=None,
149
+ method="GET",
150
+ params=params or None,
151
+ )
152
+
153
+ # Defensive checks so that we fail loudly if the contract changes.
154
+ if not isinstance(raw_response, dict) or "data" not in raw_response:
155
+ raise RuntimeError(
156
+ f"Unexpected response while listing research tasks: {raw_response}"
157
+ )
158
+
159
+ tasks = [_build_research_task(t) for t in raw_response.get("data", [])]
160
+
161
+ # Lazy import to avoid cycles.
162
+ from .models import ListResearchTasksResponse # noqa: WPS433 – runtime import
163
+
164
+ return ListResearchTasksResponse(
165
+ data=tasks,
166
+ has_more=raw_response.get("hasMore", False),
167
+ next_cursor=raw_response.get("nextCursor"),
168
+ )
169
+
111
170
 
112
171
  class AsyncResearchClient:
113
172
  """Async counterpart used via :pyattr:`AsyncExa.research`."""
@@ -118,18 +177,20 @@ class AsyncResearchClient:
118
177
  async def create_task(
119
178
  self,
120
179
  *,
121
- input_instructions: str,
180
+ instructions: str,
181
+ model: Literal["exa-research", "exa-research-pro"] = "exa-research",
122
182
  output_schema: Dict[str, Any],
123
183
  ) -> "ResearchTaskId":
124
184
  """Submit a research request and return the *task identifier* (async)."""
125
185
 
126
186
  payload = {
127
- "input": {"instructions": input_instructions},
187
+ "instructions": instructions,
188
+ "model": model,
128
189
  "output": {"schema": output_schema},
129
190
  }
130
191
 
131
192
  raw_response: Dict[str, Any] = await self._client.async_request(
132
- "/research/tasks", payload
193
+ "/research/v0/tasks", payload
133
194
  )
134
195
 
135
196
  # Defensive checks so that we fail loudly if the contract changes.
@@ -146,7 +207,7 @@ class AsyncResearchClient:
146
207
  async def get_task(self, id: str) -> "ResearchTask": # noqa: D401
147
208
  """Fetch the current status / result for a research task (async)."""
148
209
 
149
- endpoint = f"/research/tasks/{id}"
210
+ endpoint = f"/research/v0/tasks/{id}"
150
211
 
151
212
  # Perform GET using the underlying HTTP client because `async_request`
152
213
  # only supports POST semantics.
@@ -199,6 +260,50 @@ class AsyncResearchClient:
199
260
 
200
261
  await asyncio.sleep(poll_interval)
201
262
 
263
+ # ------------------------------------------------------------------
264
+ # Listing helpers
265
+ # ------------------------------------------------------------------
266
+
267
+ async def list(
268
+ self,
269
+ *,
270
+ cursor: Optional[str] = None,
271
+ limit: Optional[int] = None,
272
+ ) -> "ListResearchTasksResponse":
273
+ """Async list of research tasks with optional pagination."""
274
+
275
+ params = {
276
+ k: v for k, v in {"cursor": cursor, "limit": limit}.items() if v is not None
277
+ }
278
+
279
+ resp = await self._client.client.get(
280
+ self._client.base_url + "/research/v0/tasks",
281
+ headers=self._client.headers,
282
+ params=params or None,
283
+ )
284
+
285
+ if resp.status_code >= 400:
286
+ raise RuntimeError(
287
+ f"Request failed with status code {resp.status_code}: {resp.text}"
288
+ )
289
+
290
+ raw_response: Dict[str, Any] = resp.json()
291
+
292
+ if not isinstance(raw_response, dict) or "data" not in raw_response:
293
+ raise RuntimeError(
294
+ f"Unexpected response while listing research tasks: {raw_response}"
295
+ )
296
+
297
+ tasks = [_build_research_task(t) for t in raw_response.get("data", [])]
298
+
299
+ from .models import ListResearchTasksResponse # noqa: WPS433 – runtime import
300
+
301
+ return ListResearchTasksResponse(
302
+ data=tasks,
303
+ has_more=raw_response.get("hasMore", False),
304
+ next_cursor=raw_response.get("nextCursor"),
305
+ )
306
+
202
307
 
203
308
  # ---------------------------------------------------------------------------
204
309
  # Internal helpers (lazy imports to avoid cycles)
@@ -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
  [tool.poetry]
2
2
  name = "exa-py"
3
- version = "1.13.2"
3
+ version = "1.14.1"
4
4
  description = "Python SDK for Exa API."
5
5
  authors = ["Exa AI <hello@exa.ai>"]
6
6
  readme = "README.md"
@@ -32,7 +32,7 @@ in-project = true
32
32
 
33
33
  [project]
34
34
  name = "exa-py"
35
- version = "1.13.2"
35
+ version = "1.14.1"
36
36
  description = "Python SDK for Exa API."
37
37
  readme = "README.md"
38
38
  requires-python = ">=3.9"
File without changes
File without changes
File without changes
File without changes