exa-py 1.13.2__tar.gz → 1.14.0__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.
- {exa_py-1.13.2 → exa_py-1.14.0}/PKG-INFO +3 -2
- {exa_py-1.13.2 → exa_py-1.14.0}/README.md +2 -1
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/api.py +1 -1
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/research/__init__.py +2 -1
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/research/client.py +112 -10
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/research/models.py +22 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/pyproject.toml +2 -2
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/__init__.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/py.typed +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/utils.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/__init__.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/_generator/pydantic/BaseModel.jinja2 +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/client.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/core/__init__.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/core/base.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/enrichments/__init__.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/enrichments/client.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/items/__init__.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/items/client.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/searches/__init__.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/searches/client.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/streams/__init__.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/streams/client.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/streams/runs/__init__.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/streams/runs/client.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/types.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/exa_py/websets/webhooks/__init__.py +0 -0
- {exa_py-1.13.2 → exa_py-1.14.0}/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.
|
|
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
|
-
|
|
129
|
+
instructions=QUESTION,
|
|
130
|
+
model="exa-research",
|
|
130
131
|
output_schema=OUTPUT_SCHEMA,
|
|
131
132
|
)
|
|
132
133
|
```
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
"
|
|
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(
|
|
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
|
-
|
|
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
|
-
"
|
|
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)
|
|
@@ -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.
|
|
3
|
+
version = "1.14.0"
|
|
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.
|
|
35
|
+
version = "1.14.0"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|