behaviorgpt 0.2.2__tar.gz → 0.4.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.
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/PKG-INFO +20 -4
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/README.md +19 -3
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/pyproject.toml +1 -1
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/pyproject.toml.orig +1 -1
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/__init__.py +2 -1
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/_client.py +52 -2
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/resources/catalogs.py +100 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/resources/requests.py +2 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/types/__init__.py +2 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/types/http.py +15 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/LICENSE +0 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/_exceptions.py +0 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/resources/__init__.py +0 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/resources/_shared.py +0 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/resources/recommendations.py +0 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/resources/search.py +0 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/types/domains.py +0 -0
- {behaviorgpt-0.2.2 → behaviorgpt-0.4.0}/src/behaviorgpt/types/events.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: behaviorgpt
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python client for BehaviorGPT, UnboxAI's Large Behavioral Model for search, recommendations and personalization
|
|
5
5
|
Keywords: recommendations,personalization,search,behavioral model,unboxai
|
|
6
6
|
Author: Unbox AI
|
|
@@ -123,11 +123,14 @@ The model can only return items it knows about. Out of the box the client uses `
|
|
|
123
123
|
|
|
124
124
|
- [`docs/catalog-format.md`](https://github.com/Unbox-AI/behaviorgpt/blob/main/docs/catalog-format.md) lists the required columns and types.
|
|
125
125
|
- A catalog can hold at most 100,000 items.
|
|
126
|
-
- Each API key can hold 2 catalogs. The file name is the catalog's name: uploading a file with the same name again refreshes that catalog and keeps its `catalog_id`, and a new name takes one of the 2 slots. A failed upload does not use up a slot. Once both slots are in use, an upload under a new name is refused
|
|
127
|
-
- [`examples/sample_catalog.csv`](https://github.com/Unbox-AI/behaviorgpt/blob/main/examples/sample_catalog.csv) is an example only, showing what each column should contain. Your own file will look different depending on your catalog
|
|
126
|
+
- Each API key can hold 2 catalogs. The file name is the catalog's name: uploading a file with the same name again refreshes that catalog and keeps its `catalog_id`, and a new name takes one of the 2 slots. A failed upload does not use up a slot. Once both slots are in use, an upload under a new name is refused until you delete one of them (see below); to raise the limit, contact alexander@unboxai.com.
|
|
127
|
+
- [`examples/sample_catalog.csv`](https://github.com/Unbox-AI/behaviorgpt/blob/main/examples/sample_catalog.csv) is an example only, showing what each column should contain. Your own file will look different depending on your catalog. A CSV with the documented column names converts to the parquet structure with `catalog_from_csv`.
|
|
128
128
|
|
|
129
129
|
```python
|
|
130
|
-
|
|
130
|
+
from behaviorgpt import catalog_from_csv
|
|
131
|
+
|
|
132
|
+
path = catalog_from_csv("my_catalog.csv") # writes my_catalog.parquet
|
|
133
|
+
job = client.embed(path, wait=True, timeout=1800.0)
|
|
131
134
|
client.complete([Search("...")], catalog_id=job.catalog_id)
|
|
132
135
|
```
|
|
133
136
|
|
|
@@ -146,6 +149,19 @@ client.add_items("new_items.parquet", catalog_id=job.catalog_id, wait=True)
|
|
|
146
149
|
- Uploading the catalog's file again with `embed` replaces the whole catalog, added items included. Put them in that file too to keep them.
|
|
147
150
|
- A catalog embedded before `add_items` existed needs one more full upload before items can be added to it.
|
|
148
151
|
|
|
152
|
+
### Listing and deleting catalogs
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
for c in client.list_catalogs():
|
|
156
|
+
print(c.catalog_id, c.catalog_name, c.status, c.owned)
|
|
157
|
+
|
|
158
|
+
client.delete_catalog("cat_...")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- `list_catalogs()` returns your own catalogs, in any status, and the shared ones that are ready. `owned` marks your own.
|
|
162
|
+
- `delete_catalog()` removes one of your catalogs with its items and frees its slot. It cannot be undone.
|
|
163
|
+
- Deleting another key's catalog fails with status 404, the same as an unknown id. While an `embed` or `add_items` job runs for the catalog, deleting fails with status 409; try again once the job is ready or failed.
|
|
164
|
+
|
|
149
165
|
If mapping your data to the format is hard, open a coding agent in this directory and ask it to embed your catalog. The repo ships a skill at [`.claude/skills/embed-catalog/SKILL.md`](https://github.com/Unbox-AI/behaviorgpt/blob/main/.claude/skills/embed-catalog/SKILL.md) that walks the agent through mapping your columns, building and validating the parquet, checking image URLs, and uploading. Claude Code picks it up automatically; for other agents, point them at that file.
|
|
150
166
|
|
|
151
167
|
### Events
|
|
@@ -106,11 +106,14 @@ The model can only return items it knows about. Out of the box the client uses `
|
|
|
106
106
|
|
|
107
107
|
- [`docs/catalog-format.md`](https://github.com/Unbox-AI/behaviorgpt/blob/main/docs/catalog-format.md) lists the required columns and types.
|
|
108
108
|
- A catalog can hold at most 100,000 items.
|
|
109
|
-
- Each API key can hold 2 catalogs. The file name is the catalog's name: uploading a file with the same name again refreshes that catalog and keeps its `catalog_id`, and a new name takes one of the 2 slots. A failed upload does not use up a slot. Once both slots are in use, an upload under a new name is refused
|
|
110
|
-
- [`examples/sample_catalog.csv`](https://github.com/Unbox-AI/behaviorgpt/blob/main/examples/sample_catalog.csv) is an example only, showing what each column should contain. Your own file will look different depending on your catalog
|
|
109
|
+
- Each API key can hold 2 catalogs. The file name is the catalog's name: uploading a file with the same name again refreshes that catalog and keeps its `catalog_id`, and a new name takes one of the 2 slots. A failed upload does not use up a slot. Once both slots are in use, an upload under a new name is refused until you delete one of them (see below); to raise the limit, contact alexander@unboxai.com.
|
|
110
|
+
- [`examples/sample_catalog.csv`](https://github.com/Unbox-AI/behaviorgpt/blob/main/examples/sample_catalog.csv) is an example only, showing what each column should contain. Your own file will look different depending on your catalog. A CSV with the documented column names converts to the parquet structure with `catalog_from_csv`.
|
|
111
111
|
|
|
112
112
|
```python
|
|
113
|
-
|
|
113
|
+
from behaviorgpt import catalog_from_csv
|
|
114
|
+
|
|
115
|
+
path = catalog_from_csv("my_catalog.csv") # writes my_catalog.parquet
|
|
116
|
+
job = client.embed(path, wait=True, timeout=1800.0)
|
|
114
117
|
client.complete([Search("...")], catalog_id=job.catalog_id)
|
|
115
118
|
```
|
|
116
119
|
|
|
@@ -129,6 +132,19 @@ client.add_items("new_items.parquet", catalog_id=job.catalog_id, wait=True)
|
|
|
129
132
|
- Uploading the catalog's file again with `embed` replaces the whole catalog, added items included. Put them in that file too to keep them.
|
|
130
133
|
- A catalog embedded before `add_items` existed needs one more full upload before items can be added to it.
|
|
131
134
|
|
|
135
|
+
### Listing and deleting catalogs
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
for c in client.list_catalogs():
|
|
139
|
+
print(c.catalog_id, c.catalog_name, c.status, c.owned)
|
|
140
|
+
|
|
141
|
+
client.delete_catalog("cat_...")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- `list_catalogs()` returns your own catalogs, in any status, and the shared ones that are ready. `owned` marks your own.
|
|
145
|
+
- `delete_catalog()` removes one of your catalogs with its items and frees its slot. It cannot be undone.
|
|
146
|
+
- Deleting another key's catalog fails with status 404, the same as an unknown id. While an `embed` or `add_items` job runs for the catalog, deleting fails with status 409; try again once the job is ready or failed.
|
|
147
|
+
|
|
132
148
|
If mapping your data to the format is hard, open a coding agent in this directory and ask it to embed your catalog. The repo ships a skill at [`.claude/skills/embed-catalog/SKILL.md`](https://github.com/Unbox-AI/behaviorgpt/blob/main/.claude/skills/embed-catalog/SKILL.md) that walks the agent through mapping your columns, building and validating the parquet, checking image URLs, and uploading. Claude Code picks it up automatically; for other agents, point them at that file.
|
|
133
149
|
|
|
134
150
|
### Events
|
|
@@ -5,7 +5,7 @@ from behaviorgpt._exceptions import (
|
|
|
5
5
|
RateLimitError,
|
|
6
6
|
UnboxAIError,
|
|
7
7
|
)
|
|
8
|
-
from behaviorgpt.resources.catalogs import ProgressPrinter
|
|
8
|
+
from behaviorgpt.resources.catalogs import ProgressPrinter, catalog_from_csv
|
|
9
9
|
from behaviorgpt.types.events import (
|
|
10
10
|
AddToCart,
|
|
11
11
|
Order,
|
|
@@ -23,6 +23,7 @@ __all__ = [
|
|
|
23
23
|
"UnboxAIClient",
|
|
24
24
|
"AsyncUnboxAIClient",
|
|
25
25
|
"ProgressPrinter",
|
|
26
|
+
"catalog_from_csv",
|
|
26
27
|
"UnboxAIError",
|
|
27
28
|
"AuthenticationError",
|
|
28
29
|
"BadRequestError",
|
|
@@ -18,6 +18,7 @@ from behaviorgpt.types import (
|
|
|
18
18
|
AddItemsJobDetails,
|
|
19
19
|
AddToCart,
|
|
20
20
|
CartItem,
|
|
21
|
+
CatalogSummary,
|
|
21
22
|
Domains,
|
|
22
23
|
EmbedJobDetails,
|
|
23
24
|
Item,
|
|
@@ -35,6 +36,7 @@ from behaviorgpt.types import (
|
|
|
35
36
|
# file is stored and queued: httpx's 5 s default is too short for both.
|
|
36
37
|
DEFAULT_HTTP_TIMEOUT = httpx.Timeout(120.0, connect=10.0, write=300.0)
|
|
37
38
|
DEFAULT_BASE_URL = "https://behaviorgpt-northeurope.api.unboxai.com/v1"
|
|
39
|
+
DEFAULT_TRAILING_SEARCHES = 3
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
class _BaseClient:
|
|
@@ -74,6 +76,7 @@ class _BaseClient:
|
|
|
74
76
|
catalog_id: Optional[str],
|
|
75
77
|
filters: Optional[dict[str, Any]],
|
|
76
78
|
register_event: bool,
|
|
79
|
+
max_trailing_searches: int = DEFAULT_TRAILING_SEARCHES,
|
|
77
80
|
) -> tuple[bool, UnboxAIRequest, dict]:
|
|
78
81
|
"""The request `complete` sends, whether it goes to search, and its headers."""
|
|
79
82
|
active_catalog, request_headers = self._catalog_headers(catalog_id)
|
|
@@ -90,6 +93,7 @@ class _BaseClient:
|
|
|
90
93
|
offset=offset,
|
|
91
94
|
filters=filters,
|
|
92
95
|
register_event=register_event,
|
|
96
|
+
max_trailing_searches=max_trailing_searches,
|
|
93
97
|
)
|
|
94
98
|
return True, req, request_headers
|
|
95
99
|
|
|
@@ -216,9 +220,22 @@ class UnboxAIClient(_BaseClient):
|
|
|
216
220
|
catalog_id: Optional[str] = None,
|
|
217
221
|
filters: Optional[dict[str, Any]] = None,
|
|
218
222
|
register_event: bool = False,
|
|
223
|
+
max_trailing_searches: int = DEFAULT_TRAILING_SEARCHES,
|
|
219
224
|
) -> UnboxAIResponse:
|
|
225
|
+
"""Rank the items most likely to come after `history`.
|
|
226
|
+
|
|
227
|
+
When the history ends in searches, up to `max_trailing_searches` of
|
|
228
|
+
them in a row, the query included, reach the model, so `nike` then
|
|
229
|
+
`shoes` reads as one intent.
|
|
230
|
+
"""
|
|
220
231
|
is_search, req, headers = self._complete_request(
|
|
221
|
-
history,
|
|
232
|
+
history,
|
|
233
|
+
limit,
|
|
234
|
+
offset,
|
|
235
|
+
catalog_id,
|
|
236
|
+
filters,
|
|
237
|
+
register_event,
|
|
238
|
+
max_trailing_searches,
|
|
222
239
|
)
|
|
223
240
|
endpoint = self.search if is_search else self.recommendations
|
|
224
241
|
return endpoint.create(payload=req, headers=headers)
|
|
@@ -290,6 +307,22 @@ class UnboxAIClient(_BaseClient):
|
|
|
290
307
|
def job_status(self, job_id: str) -> JobStatus:
|
|
291
308
|
return self.catalogs.get_job_status(job_id)
|
|
292
309
|
|
|
310
|
+
def list_catalogs(self) -> list[CatalogSummary]:
|
|
311
|
+
"""The catalogs this key can query: its own, in any status, and the
|
|
312
|
+
shared ones that are ready. `owned` marks the key's own."""
|
|
313
|
+
return self.catalogs.list_catalogs()
|
|
314
|
+
|
|
315
|
+
def delete_catalog(self, catalog_id: str) -> None:
|
|
316
|
+
"""Delete one of this key's catalogs, its items and its index, and
|
|
317
|
+
free its slot. This cannot be undone.
|
|
318
|
+
|
|
319
|
+
A catalog that is not this key's raises `UnboxAIError` with status
|
|
320
|
+
404, the same as an unknown one. While an embed or `add_items` job runs
|
|
321
|
+
for the catalog, it raises with status 409; delete once the job is
|
|
322
|
+
ready or failed.
|
|
323
|
+
"""
|
|
324
|
+
self.catalogs.delete_catalog(catalog_id)
|
|
325
|
+
|
|
293
326
|
def similar_items(
|
|
294
327
|
self,
|
|
295
328
|
item_id: str,
|
|
@@ -397,9 +430,18 @@ class AsyncUnboxAIClient(_BaseClient):
|
|
|
397
430
|
catalog_id: Optional[str] = None,
|
|
398
431
|
filters: Optional[dict[str, Any]] = None,
|
|
399
432
|
register_event: bool = False,
|
|
433
|
+
max_trailing_searches: int = DEFAULT_TRAILING_SEARCHES,
|
|
400
434
|
) -> UnboxAIResponse:
|
|
435
|
+
"""Rank the items most likely to come after `history`; see
|
|
436
|
+
`UnboxAIClient.complete`."""
|
|
401
437
|
is_search, req, headers = self._complete_request(
|
|
402
|
-
history,
|
|
438
|
+
history,
|
|
439
|
+
limit,
|
|
440
|
+
offset,
|
|
441
|
+
catalog_id,
|
|
442
|
+
filters,
|
|
443
|
+
register_event,
|
|
444
|
+
max_trailing_searches,
|
|
403
445
|
)
|
|
404
446
|
endpoint = self.search if is_search else self.recommendations
|
|
405
447
|
return await endpoint.create(payload=req, headers=headers)
|
|
@@ -454,6 +496,14 @@ class AsyncUnboxAIClient(_BaseClient):
|
|
|
454
496
|
async def job_status(self, job_id: str) -> JobStatus:
|
|
455
497
|
return await self.catalogs.get_job_status(job_id)
|
|
456
498
|
|
|
499
|
+
async def list_catalogs(self) -> list[CatalogSummary]:
|
|
500
|
+
"""See `UnboxAIClient.list_catalogs`."""
|
|
501
|
+
return await self.catalogs.list_catalogs()
|
|
502
|
+
|
|
503
|
+
async def delete_catalog(self, catalog_id: str) -> None:
|
|
504
|
+
"""See `UnboxAIClient.delete_catalog`."""
|
|
505
|
+
await self.catalogs.delete_catalog(catalog_id)
|
|
506
|
+
|
|
457
507
|
async def similar_items(
|
|
458
508
|
self,
|
|
459
509
|
item_id: str,
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
+
import ast
|
|
1
2
|
import asyncio
|
|
2
3
|
import time
|
|
3
4
|
import warnings
|
|
4
5
|
from datetime import UTC, datetime
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any, Callable, Optional
|
|
8
|
+
from urllib.parse import quote
|
|
7
9
|
|
|
8
10
|
import httpx
|
|
9
11
|
import pyarrow as pa
|
|
12
|
+
import pyarrow.compute as pc
|
|
13
|
+
import pyarrow.csv as pa_csv
|
|
10
14
|
import pyarrow.parquet as pq
|
|
11
15
|
|
|
12
16
|
from behaviorgpt._exceptions import UnboxAIError
|
|
13
17
|
from behaviorgpt.resources._shared import handle_response
|
|
14
18
|
from behaviorgpt.types import (
|
|
15
19
|
AddItemsJobDetails,
|
|
20
|
+
CatalogSummary,
|
|
16
21
|
EmbedJobDetails,
|
|
17
22
|
JobStatus,
|
|
18
23
|
SimilarItemsRequest,
|
|
@@ -93,6 +98,79 @@ def check_catalog_schema(path: Path) -> None:
|
|
|
93
98
|
)
|
|
94
99
|
|
|
95
100
|
|
|
101
|
+
LIST_COLUMNS: dict[str, type] = {
|
|
102
|
+
"sales_since": int,
|
|
103
|
+
"search_keywords": str,
|
|
104
|
+
"keywords": str,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _parse_list(column: str, cell: Optional[str]) -> Optional[list]:
|
|
109
|
+
"""`[1, 2]` or `a, b` as a list of the column's item type; empty is null."""
|
|
110
|
+
if cell is None or not cell.strip():
|
|
111
|
+
return None
|
|
112
|
+
cast = LIST_COLUMNS[column]
|
|
113
|
+
try:
|
|
114
|
+
items = (
|
|
115
|
+
ast.literal_eval(cell) if cell.strip().startswith("[") else cell.split(",")
|
|
116
|
+
)
|
|
117
|
+
return [cast(str(item).strip()) for item in items if str(item).strip()]
|
|
118
|
+
except (ValueError, SyntaxError) as exc:
|
|
119
|
+
raise UnboxAIError(f"{column}: cannot read {cell!r} as a list") from exc
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def catalog_from_csv(
|
|
123
|
+
csv_path: Path | str, parquet_path: Optional[Path | str] = None
|
|
124
|
+
) -> Path:
|
|
125
|
+
"""Convert a CSV with the columns of docs/catalog-format.md to a catalog
|
|
126
|
+
parquet, check it and return its path.
|
|
127
|
+
|
|
128
|
+
Every cell is read as text, so prices keep their formatting. List columns
|
|
129
|
+
take `[1, 2]` or `a, b`. Empty cells, and columns the CSV lacks, become
|
|
130
|
+
null; other columns are left out. Duplicate ids keep their first row. The
|
|
131
|
+
parquet goes next to the CSV unless `parquet_path` is given; its file name
|
|
132
|
+
becomes the catalog's name.
|
|
133
|
+
"""
|
|
134
|
+
csv_path = Path(csv_path)
|
|
135
|
+
out = Path(parquet_path) if parquet_path else csv_path.with_suffix(".parquet")
|
|
136
|
+
try:
|
|
137
|
+
table = pa_csv.read_csv(
|
|
138
|
+
csv_path,
|
|
139
|
+
convert_options=pa_csv.ConvertOptions(
|
|
140
|
+
column_types={column: pa.string() for column in CATALOG_SCHEMA},
|
|
141
|
+
include_columns=list(CATALOG_SCHEMA),
|
|
142
|
+
include_missing_columns=True,
|
|
143
|
+
strings_can_be_null=True,
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
except pa.ArrowInvalid as exc:
|
|
147
|
+
raise UnboxAIError(f"{csv_path} is not a readable CSV file: {exc}") from exc
|
|
148
|
+
|
|
149
|
+
first_rows = {}
|
|
150
|
+
for row, item_id in enumerate(table.column("id").to_pylist()):
|
|
151
|
+
first_rows.setdefault(item_id, row)
|
|
152
|
+
table = table.take(sorted(first_rows.values()))
|
|
153
|
+
|
|
154
|
+
columns = {}
|
|
155
|
+
for column, expected in CATALOG_SCHEMA.items():
|
|
156
|
+
values = table.column(column)
|
|
157
|
+
if column in LIST_COLUMNS:
|
|
158
|
+
values = pa.array(
|
|
159
|
+
[_parse_list(column, cell) for cell in values.to_pylist()],
|
|
160
|
+
type=expected,
|
|
161
|
+
)
|
|
162
|
+
elif expected != pa.string():
|
|
163
|
+
try:
|
|
164
|
+
values = pc.cast(values, expected)
|
|
165
|
+
except pa.ArrowInvalid as exc:
|
|
166
|
+
raise UnboxAIError(f"{column}: expected {expected}: {exc}") from exc
|
|
167
|
+
columns[column] = values
|
|
168
|
+
|
|
169
|
+
pq.write_table(pa.table(columns, schema=pa.schema(CATALOG_SCHEMA)), out)
|
|
170
|
+
check_catalog_schema(out)
|
|
171
|
+
return out
|
|
172
|
+
|
|
173
|
+
|
|
96
174
|
def format_reference_time(reference_time: datetime | str) -> str:
|
|
97
175
|
"""Format `reference_time` for the upload; a `datetime` must be timezone-aware."""
|
|
98
176
|
if isinstance(reference_time, str):
|
|
@@ -133,6 +211,16 @@ def items_path(catalog_id: str) -> str:
|
|
|
133
211
|
return f"/catalogs/{catalog_id}/items"
|
|
134
212
|
|
|
135
213
|
|
|
214
|
+
def catalog_path(catalog_id: str) -> str:
|
|
215
|
+
if not catalog_id:
|
|
216
|
+
raise ValueError("catalog_id is required to delete a catalog")
|
|
217
|
+
return f"/catalogs/{quote(catalog_id, safe='')}"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def catalog_summaries(response: httpx.Response) -> list[CatalogSummary]:
|
|
221
|
+
return [CatalogSummary(**c) for c in handle_response(response)["catalogs"]]
|
|
222
|
+
|
|
223
|
+
|
|
136
224
|
def similar_items_body(
|
|
137
225
|
item_id: str, catalog_id: str, limit: int, offset: int, filters: Optional[dict]
|
|
138
226
|
) -> dict:
|
|
@@ -405,6 +493,12 @@ class Catalogs:
|
|
|
405
493
|
def get_umap(self, *, catalog_id: str):
|
|
406
494
|
return umap_page(self._client.get(f"/{catalog_id}/umap"))
|
|
407
495
|
|
|
496
|
+
def list_catalogs(self) -> list[CatalogSummary]:
|
|
497
|
+
return catalog_summaries(self._client.get("/catalogs"))
|
|
498
|
+
|
|
499
|
+
def delete_catalog(self, catalog_id: str) -> None:
|
|
500
|
+
handle_response(self._client.delete(catalog_path(catalog_id)))
|
|
501
|
+
|
|
408
502
|
|
|
409
503
|
class AsyncCatalogs:
|
|
410
504
|
"""The async counterpart of `Catalogs`; see it for what each method does."""
|
|
@@ -520,3 +614,9 @@ class AsyncCatalogs:
|
|
|
520
614
|
|
|
521
615
|
async def get_umap(self, *, catalog_id: str) -> str:
|
|
522
616
|
return umap_page(await self._client.get(f"/{catalog_id}/umap"))
|
|
617
|
+
|
|
618
|
+
async def list_catalogs(self) -> list[CatalogSummary]:
|
|
619
|
+
return catalog_summaries(await self._client.get("/catalogs"))
|
|
620
|
+
|
|
621
|
+
async def delete_catalog(self, catalog_id: str) -> None:
|
|
622
|
+
handle_response(await self._client.delete(catalog_path(catalog_id)))
|
|
@@ -25,6 +25,7 @@ class UnboxAIRequester:
|
|
|
25
25
|
register_event: bool = False,
|
|
26
26
|
timestamp: Optional[datetime] = None,
|
|
27
27
|
num_candidates: Optional[int] = None,
|
|
28
|
+
max_trailing_searches: Optional[int] = None,
|
|
28
29
|
) -> UnboxAIRequest:
|
|
29
30
|
return UnboxAIRequest(
|
|
30
31
|
query=query,
|
|
@@ -38,4 +39,5 @@ class UnboxAIRequester:
|
|
|
38
39
|
register_event=register_event,
|
|
39
40
|
timestamp=timestamp,
|
|
40
41
|
num_candidates=num_candidates,
|
|
42
|
+
max_trailing_searches=max_trailing_searches,
|
|
41
43
|
)
|
|
@@ -16,6 +16,7 @@ from behaviorgpt.types.events import (
|
|
|
16
16
|
)
|
|
17
17
|
from behaviorgpt.types.http import (
|
|
18
18
|
AddItemsJobDetails,
|
|
19
|
+
CatalogSummary,
|
|
19
20
|
EmbedJobDetails,
|
|
20
21
|
Item,
|
|
21
22
|
ItemsPage,
|
|
@@ -42,6 +43,7 @@ __all__ = [
|
|
|
42
43
|
"SimilarItemsRequest",
|
|
43
44
|
"EmbedJobDetails",
|
|
44
45
|
"AddItemsJobDetails",
|
|
46
|
+
"CatalogSummary",
|
|
45
47
|
"JobStatus",
|
|
46
48
|
"ItemsRejected",
|
|
47
49
|
"RejectedItem",
|
|
@@ -56,6 +56,10 @@ class UnboxAIRequest(BaseModel):
|
|
|
56
56
|
timestamp: Optional[datetime] = None
|
|
57
57
|
# How many nearest-neighbour candidates the server explores.
|
|
58
58
|
num_candidates: Optional[int] = Field(default=None, gt=0, le=5_000)
|
|
59
|
+
# Search only: how many searches in a row at the end of the history, the
|
|
60
|
+
# query included, reach the model. Unset, the deployment's history settings
|
|
61
|
+
# apply.
|
|
62
|
+
max_trailing_searches: Optional[int] = Field(default=None, ge=1, le=10)
|
|
59
63
|
|
|
60
64
|
@field_validator("timestamp")
|
|
61
65
|
@classmethod
|
|
@@ -148,6 +152,17 @@ class AddItemsJobDetails(EmbedJobDetails):
|
|
|
148
152
|
products: int
|
|
149
153
|
|
|
150
154
|
|
|
155
|
+
class CatalogSummary(BaseModel):
|
|
156
|
+
catalog_id: str
|
|
157
|
+
# display label derived from the uploaded file name, server-side
|
|
158
|
+
catalog_name: str
|
|
159
|
+
# 'pending', 'ready' or 'failed'; shared catalogs are listed only when ready
|
|
160
|
+
status: str
|
|
161
|
+
# uploaded with this API key, so it counts against the key's catalog slots
|
|
162
|
+
# and this key can delete it
|
|
163
|
+
owned: bool
|
|
164
|
+
|
|
165
|
+
|
|
151
166
|
class SimilarItemsRequest(BaseModel):
|
|
152
167
|
model_config = ConfigDict(serialize_by_alias=True)
|
|
153
168
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|