unique_toolkit 2026.38.0.dev2__py3-none-any.whl → 2026.38.0.dev4__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.
- unique_toolkit/experimental/components/content_tree/functions.py +162 -8
- unique_toolkit/experimental/components/internal_search/base/service.py +18 -4
- unique_toolkit/language_model/infos.py +28 -0
- {unique_toolkit-2026.38.0.dev2.dist-info → unique_toolkit-2026.38.0.dev4.dist-info}/METADATA +1 -1
- {unique_toolkit-2026.38.0.dev2.dist-info → unique_toolkit-2026.38.0.dev4.dist-info}/RECORD +6 -6
- {unique_toolkit-2026.38.0.dev2.dist-info → unique_toolkit-2026.38.0.dev4.dist-info}/WHEEL +0 -0
|
@@ -19,14 +19,21 @@ from __future__ import annotations
|
|
|
19
19
|
import asyncio
|
|
20
20
|
import json
|
|
21
21
|
import logging
|
|
22
|
-
from collections.abc import Awaitable, Callable
|
|
22
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
23
23
|
from pathlib import PurePosixPath
|
|
24
|
-
from typing import Any
|
|
24
|
+
from typing import Any, cast
|
|
25
25
|
|
|
26
26
|
import unique_sdk
|
|
27
27
|
from typing_extensions import deprecated
|
|
28
28
|
|
|
29
29
|
from unique_toolkit.content.schemas import BaseFolderInfo, ContentInfo
|
|
30
|
+
from unique_toolkit.content.smart_rules import (
|
|
31
|
+
AndStatement,
|
|
32
|
+
Operator,
|
|
33
|
+
OrStatement,
|
|
34
|
+
UniqueQL,
|
|
35
|
+
parse_uniqueql,
|
|
36
|
+
)
|
|
30
37
|
from unique_toolkit.experimental.components.content_tree.schemas import (
|
|
31
38
|
FolderWalkSnapshot,
|
|
32
39
|
PathTrieNode,
|
|
@@ -69,6 +76,137 @@ def _parse_content_infos_payload(payload: Any) -> tuple[list[ContentInfo], int]:
|
|
|
69
76
|
return files, total
|
|
70
77
|
|
|
71
78
|
|
|
79
|
+
_NULLISH_OPERATORS = {
|
|
80
|
+
Operator.IS_NULL.value,
|
|
81
|
+
Operator.IS_NOT_NULL.value,
|
|
82
|
+
Operator.IS_EMPTY.value,
|
|
83
|
+
Operator.IS_NOT_EMPTY.value,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _content_uniqueql_record(info: ContentInfo) -> dict[str, Any]:
|
|
88
|
+
dumped = info.model_dump(by_alias=True, mode="json")
|
|
89
|
+
metadata = dumped.get("metadata") or {}
|
|
90
|
+
if not isinstance(metadata, dict):
|
|
91
|
+
metadata = {}
|
|
92
|
+
record = {
|
|
93
|
+
key: value
|
|
94
|
+
for key, value in dumped.items()
|
|
95
|
+
if key != "metadata" and value is not None
|
|
96
|
+
}
|
|
97
|
+
record["metadata"] = metadata
|
|
98
|
+
for key, value in metadata.items():
|
|
99
|
+
record.setdefault(key, value)
|
|
100
|
+
return record
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _uniqueql_fill_nullish_values(node: Any) -> Any:
|
|
104
|
+
"""Statement.value cannot be None; documented isNull/isEmpty UniqueQL uses null."""
|
|
105
|
+
if not isinstance(node, dict):
|
|
106
|
+
return node
|
|
107
|
+
if "and" in node:
|
|
108
|
+
return {
|
|
109
|
+
**node,
|
|
110
|
+
"and": [_uniqueql_fill_nullish_values(item) for item in node["and"]],
|
|
111
|
+
}
|
|
112
|
+
if "or" in node:
|
|
113
|
+
return {
|
|
114
|
+
**node,
|
|
115
|
+
"or": [_uniqueql_fill_nullish_values(item) for item in node["or"]],
|
|
116
|
+
}
|
|
117
|
+
filled = dict(node)
|
|
118
|
+
if filled.get("operator") in _NULLISH_OPERATORS and filled.get("value") is None:
|
|
119
|
+
filled["value"] = True
|
|
120
|
+
if isinstance(filled.get("value"), dict):
|
|
121
|
+
filled["value"] = _uniqueql_fill_nullish_values(filled["value"])
|
|
122
|
+
return filled
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _uniqueql_matches(record: Mapping[str, Any], query: UniqueQL) -> bool:
|
|
126
|
+
if isinstance(query, AndStatement):
|
|
127
|
+
return bool(query.and_list) and all(
|
|
128
|
+
_uniqueql_matches(record, child) for child in query.and_list
|
|
129
|
+
)
|
|
130
|
+
if isinstance(query, OrStatement):
|
|
131
|
+
return bool(query.or_list) and any(
|
|
132
|
+
_uniqueql_matches(record, child) for child in query.or_list
|
|
133
|
+
)
|
|
134
|
+
actual: Any = record
|
|
135
|
+
for key in query.path:
|
|
136
|
+
if not isinstance(actual, Mapping):
|
|
137
|
+
actual = None
|
|
138
|
+
break
|
|
139
|
+
actual = actual.get(key)
|
|
140
|
+
expected = query.value
|
|
141
|
+
operator = query.operator
|
|
142
|
+
is_empty = actual is None or actual in ("", [], {})
|
|
143
|
+
match operator:
|
|
144
|
+
case Operator.EQUALS:
|
|
145
|
+
return actual == expected
|
|
146
|
+
case Operator.NOT_EQUALS:
|
|
147
|
+
return actual != expected
|
|
148
|
+
case Operator.CONTAINS:
|
|
149
|
+
try:
|
|
150
|
+
return expected in cast(Any, actual)
|
|
151
|
+
except TypeError:
|
|
152
|
+
return False
|
|
153
|
+
case Operator.NOT_CONTAINS:
|
|
154
|
+
try:
|
|
155
|
+
return expected not in cast(Any, actual)
|
|
156
|
+
except TypeError:
|
|
157
|
+
return True
|
|
158
|
+
case Operator.IN:
|
|
159
|
+
return isinstance(expected, list) and actual in expected
|
|
160
|
+
case Operator.NOT_IN:
|
|
161
|
+
return isinstance(expected, list) and actual not in expected
|
|
162
|
+
case Operator.OVERLAPS:
|
|
163
|
+
return (
|
|
164
|
+
isinstance(actual, list)
|
|
165
|
+
and isinstance(expected, list)
|
|
166
|
+
and any(item in actual for item in expected)
|
|
167
|
+
)
|
|
168
|
+
case Operator.NOT_OVERLAPS:
|
|
169
|
+
return not (
|
|
170
|
+
isinstance(actual, list)
|
|
171
|
+
and isinstance(expected, list)
|
|
172
|
+
and any(item in actual for item in expected)
|
|
173
|
+
)
|
|
174
|
+
case Operator.GREATER_THAN:
|
|
175
|
+
try:
|
|
176
|
+
return cast(Any, actual) > expected
|
|
177
|
+
except TypeError:
|
|
178
|
+
return False
|
|
179
|
+
case Operator.GREATER_THAN_OR_EQUAL:
|
|
180
|
+
try:
|
|
181
|
+
return cast(Any, actual) >= expected
|
|
182
|
+
except TypeError:
|
|
183
|
+
return False
|
|
184
|
+
case Operator.LESS_THAN:
|
|
185
|
+
try:
|
|
186
|
+
return cast(Any, actual) < expected
|
|
187
|
+
except TypeError:
|
|
188
|
+
return False
|
|
189
|
+
case Operator.LESS_THAN_OR_EQUAL:
|
|
190
|
+
try:
|
|
191
|
+
return cast(Any, actual) <= expected
|
|
192
|
+
except TypeError:
|
|
193
|
+
return False
|
|
194
|
+
case Operator.IS_NULL:
|
|
195
|
+
return actual is None
|
|
196
|
+
case Operator.IS_NOT_NULL:
|
|
197
|
+
return actual is not None
|
|
198
|
+
case Operator.IS_EMPTY:
|
|
199
|
+
return is_empty
|
|
200
|
+
case Operator.IS_NOT_EMPTY:
|
|
201
|
+
return not is_empty
|
|
202
|
+
case Operator.NESTED:
|
|
203
|
+
return (
|
|
204
|
+
isinstance(expected, (AndStatement, OrStatement))
|
|
205
|
+
and isinstance(actual, Mapping)
|
|
206
|
+
and _uniqueql_matches(actual, expected)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
72
210
|
def _propagate_wait_interrupt(result: object) -> None:
|
|
73
211
|
"""Re-raise timeout/cancellation so callers can return a partial snapshot."""
|
|
74
212
|
if isinstance(result, (asyncio.CancelledError, TimeoutError)):
|
|
@@ -139,7 +277,8 @@ async def _list_direct_children_async(
|
|
|
139
277
|
|
|
140
278
|
``scope_id=None`` is the knowledge-base root. ``Content.get_infos`` without
|
|
141
279
|
``parentId`` lists the **entire** catalog, so root only fetches folders.
|
|
142
|
-
Files are listed per folder with ``parentId``.
|
|
280
|
+
Files are listed per folder with ``parentId``. Unique rejects ``parentId``
|
|
281
|
+
with ``metadataFilter``, so UniqueQL is applied to the returned files.
|
|
143
282
|
"""
|
|
144
283
|
parent_params: dict[str, Any] = {}
|
|
145
284
|
if scope_id:
|
|
@@ -164,23 +303,38 @@ async def _list_direct_children_async(
|
|
|
164
303
|
if not scope_id:
|
|
165
304
|
return await folders_task
|
|
166
305
|
|
|
306
|
+
parsed_filter = (
|
|
307
|
+
parse_uniqueql(_uniqueql_fill_nullish_values(metadata_filter))
|
|
308
|
+
if metadata_filter
|
|
309
|
+
else None
|
|
310
|
+
)
|
|
311
|
+
|
|
167
312
|
async def _content_page(skip: int) -> Any:
|
|
168
|
-
params = dict(parent_params)
|
|
169
|
-
if metadata_filter:
|
|
170
|
-
params["metadataFilter"] = metadata_filter
|
|
171
313
|
return await unique_sdk.Content.get_infos_async(
|
|
172
314
|
user_id=user_id,
|
|
173
315
|
company_id=company_id,
|
|
174
316
|
skip=skip,
|
|
175
317
|
take=step_size,
|
|
176
|
-
**
|
|
318
|
+
**parent_params,
|
|
177
319
|
)
|
|
178
320
|
|
|
321
|
+
def _parse_content_page(payload: Any) -> tuple[list[ContentInfo], int]:
|
|
322
|
+
files, total = _parse_content_infos_payload(payload)
|
|
323
|
+
# Keep parentId listing + local UniqueQL until UniqueQL/folderId metadata
|
|
324
|
+
# listing is equivalent (soft-delete/versioning); then drop this record filter.
|
|
325
|
+
if parsed_filter is not None:
|
|
326
|
+
files = [
|
|
327
|
+
info
|
|
328
|
+
for info in files
|
|
329
|
+
if _uniqueql_matches(_content_uniqueql_record(info), parsed_filter)
|
|
330
|
+
]
|
|
331
|
+
return files, total
|
|
332
|
+
|
|
179
333
|
folder_result, file_result = await asyncio.gather(
|
|
180
334
|
folders_task,
|
|
181
335
|
_paginate_parent_listing(
|
|
182
336
|
_content_page,
|
|
183
|
-
|
|
337
|
+
_parse_content_page,
|
|
184
338
|
step_size=step_size,
|
|
185
339
|
max_concurrent_requests=max_concurrent_page_fetches,
|
|
186
340
|
on_page=on_files,
|
|
@@ -75,12 +75,26 @@ class InternalSearchBaseService( # pyright: ignore[reportImplicitAbstractClass]
|
|
|
75
75
|
)
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
# TaskGroup awaits cancelled children so httpcore can release the connection.
|
|
79
|
+
results: list[SearchStringResult | BaseException | None] = [None] * len(
|
|
80
|
+
search_queries
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
async def _run(i: int, query: str) -> None:
|
|
84
|
+
try:
|
|
85
|
+
results[i] = await self._search_single_query(query=query)
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
# not BaseException: CancelledError must reach the group
|
|
88
|
+
results[i] = exc
|
|
89
|
+
|
|
90
|
+
async with asyncio.TaskGroup() as tg:
|
|
91
|
+
for i, query in enumerate(search_queries):
|
|
92
|
+
tg.create_task(_run(i, query))
|
|
93
|
+
|
|
94
|
+
found = self._collect_results(
|
|
95
|
+
[r for r in results if r is not None],
|
|
96
|
+
search_queries,
|
|
97
|
+
)
|
|
84
98
|
return await self._finalize_run(
|
|
85
99
|
search_queries,
|
|
86
100
|
found,
|
|
@@ -110,6 +110,7 @@ class LanguageModelName(StrEnum):
|
|
|
110
110
|
GROK_4_1_FAST_NON_REASONING = "litellm:grok-4-1-fast-non-reasoning"
|
|
111
111
|
GROK_4_1_FAST_REASONING = "litellm:grok-4-1-fast-reasoning"
|
|
112
112
|
GROK_4_5 = "litellm:grok-4-5"
|
|
113
|
+
GROK_4_6 = "litellm:grok-4-6"
|
|
113
114
|
LITELLM_OPENAI_GPT_5 = "litellm:openai-gpt-5"
|
|
114
115
|
LITELLM_OPENAI_GPT_5_MINI = "litellm:openai-gpt-5-mini"
|
|
115
116
|
LITELLM_OPENAI_GPT_5_NANO = "litellm:openai-gpt-5-nano"
|
|
@@ -2195,6 +2196,33 @@ class LanguageModelInfo(BaseModel):
|
|
|
2195
2196
|
published_at=date(2026, 7, 16),
|
|
2196
2197
|
supported_reasoning_efforts=["low", "medium", "high"],
|
|
2197
2198
|
)
|
|
2199
|
+
case LanguageModelName.GROK_4_6:
|
|
2200
|
+
# Specs from https://docs.x.ai/developers/grok-4-6
|
|
2201
|
+
# Announcement date from https://x.ai/news/grok-4-6
|
|
2202
|
+
return cls(
|
|
2203
|
+
name=model_name,
|
|
2204
|
+
capabilities=[
|
|
2205
|
+
ModelCapabilities.FUNCTION_CALLING,
|
|
2206
|
+
ModelCapabilities.REASONING,
|
|
2207
|
+
ModelCapabilities.STREAMING,
|
|
2208
|
+
ModelCapabilities.STRUCTURED_OUTPUT,
|
|
2209
|
+
ModelCapabilities.VISION,
|
|
2210
|
+
],
|
|
2211
|
+
provider=LanguageModelProvider.LITELLM,
|
|
2212
|
+
family=ModelFamily.XAI,
|
|
2213
|
+
version="grok-4-6",
|
|
2214
|
+
encoder_name=EncoderName.O200K_BASE, # TODO: Update encoder with grok tokenizer
|
|
2215
|
+
token_limits=LanguageModelTokenLimits(
|
|
2216
|
+
# Context window is 500_000, we leave 20_000 tokens as buffer due to tokenizer mismatch
|
|
2217
|
+
# Assign 90% for input and 10% for output
|
|
2218
|
+
token_limit_input=int(480_000 * 0.9),
|
|
2219
|
+
token_limit_output=int(480_000 * 0.1),
|
|
2220
|
+
),
|
|
2221
|
+
info_cutoff_at=date(2026, 1, day=1),
|
|
2222
|
+
published_at=date(2026, 8, 12),
|
|
2223
|
+
supported_reasoning_efforts=["low", "medium", "high", "xhigh"],
|
|
2224
|
+
default_options={"reasoning_effort": "high"},
|
|
2225
|
+
)
|
|
2198
2226
|
case LanguageModelName.LITELLM_OPENAI_GPT_5:
|
|
2199
2227
|
return cls(
|
|
2200
2228
|
name=model_name,
|
{unique_toolkit-2026.38.0.dev2.dist-info → unique_toolkit-2026.38.0.dev4.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-toolkit
|
|
3
|
-
Version: 2026.38.0.
|
|
3
|
+
Version: 2026.38.0.dev4
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Cedric Klinkert, Martin Fadler, Sadique Sheik, Fabian Schläpfer, Pascal Hauri, Ejiroghene Oki-Peter, Rami Azouz, Ricardo Visini, Sanzio Monti
|
|
6
6
|
Author-email: Cedric Klinkert <cedric.klinkert@unique.ch>, Martin Fadler <martin.fadler@unique.ch>, Sadique Sheik <sadique@unique.ch>, Fabian Schläpfer <fabian@unique.ch>, Pascal Hauri <pascal@unique.ch>, Ejiroghene Oki-Peter <oki-peter.ejiroghene@unique.ai>, Rami Azouz <rami.ext@unique.ch>, Ricardo Visini <ricardo.visini@unique.ch>, Sanzio Monti <sanzio.monti@unique.ai>
|
|
@@ -304,14 +304,14 @@ unique_toolkit/experimental/_internal/streaming/pattern_replacer.py,sha256=RLLQK
|
|
|
304
304
|
unique_toolkit/experimental/components/__init__.py,sha256=rl-s8AnHZoLEOB8ei9jQgiRIndfMWrzWJm8gEMzNcS4,2029
|
|
305
305
|
unique_toolkit/experimental/components/_base.py,sha256=XZHKffTIy331ZQnsywl0m1XkJ4DCjHV2on5EBkaG-ik,4153
|
|
306
306
|
unique_toolkit/experimental/components/content_tree/__init__.py,sha256=zAMzctv__M5e4OppVD_neO471V4zKBeHcbVrixZciaM,1946
|
|
307
|
-
unique_toolkit/experimental/components/content_tree/functions.py,sha256=
|
|
307
|
+
unique_toolkit/experimental/components/content_tree/functions.py,sha256=SwWxgwjXEhZcUh7bZJOFpJ_CwldEzLnMzMi69EEZeAQ,19465
|
|
308
308
|
unique_toolkit/experimental/components/content_tree/schemas.py,sha256=8MphCYk8VCnNk5v274voeKd9HLvsOHjpk7G8D4YPuWU,9719
|
|
309
309
|
unique_toolkit/experimental/components/content_tree/service.py,sha256=OKkPz-prJAr_8VzK0PLYTDZutV8LqlOaXnev57YxID0,25415
|
|
310
310
|
unique_toolkit/experimental/components/internal_search/__init__.py,sha256=Jj-ujG_SS5-uqCrrNidRn_H0sQAeSvyWtt9Tw0pkbzI,3091
|
|
311
311
|
unique_toolkit/experimental/components/internal_search/base/__init__.py,sha256=GB7vNBvbF6V1oLjKEf2KVvukr-BwgYpAkD5jKtGG9wU,929
|
|
312
312
|
unique_toolkit/experimental/components/internal_search/base/config.py,sha256=Bf77n0YSEFXRZKAqe4efAOITnyD3hH5Gp-gEVbMw1qE,2482
|
|
313
313
|
unique_toolkit/experimental/components/internal_search/base/schemas.py,sha256=N9UkSNDuKidNpec8Wimfow2-EhIWqK5G7j7c_Mn28Jw,1431
|
|
314
|
-
unique_toolkit/experimental/components/internal_search/base/service.py,sha256=
|
|
314
|
+
unique_toolkit/experimental/components/internal_search/base/service.py,sha256=uM6PFixs0yEkpnjVfQOwXowR-aAy1bV0dGsRGcC03Vs,7478
|
|
315
315
|
unique_toolkit/experimental/components/internal_search/base/utils.py,sha256=sjePAtSs5-GXIbWh0sDwC8wXbX_XWiaTx21AZ7i7sto,6431
|
|
316
316
|
unique_toolkit/experimental/components/internal_search/chat/__init__.py,sha256=mnlw9kKlpHWwiYhQeECXOd0-54muym2TZj0QhAsFSa0,246
|
|
317
317
|
unique_toolkit/experimental/components/internal_search/chat/config.py,sha256=2DQhY5TIMIXAqpezORDeRf3AV5_2jV1LnTKBqzrSLbk,879
|
|
@@ -394,7 +394,7 @@ unique_toolkit/language_model/builder.py,sha256=KwBzsD6-fvQnB4p2I7M-5Tk6j4tmQ973
|
|
|
394
394
|
unique_toolkit/language_model/constants.py,sha256=B-topqW0r83dkC_25DeQfnPk3n53qzIHUCBS7YJ0-1U,119
|
|
395
395
|
unique_toolkit/language_model/default_language_model.py,sha256=mcyuFs_V6tp52C8xJSHiEHO4kFGj6xed1gvozvCRD7k,1181
|
|
396
396
|
unique_toolkit/language_model/functions.py,sha256=W6JC4LUh-LZ9waDgph--xVmKeQ7Ls6NRAqDx9DwQdeY,26691
|
|
397
|
-
unique_toolkit/language_model/infos.py,sha256=
|
|
397
|
+
unique_toolkit/language_model/infos.py,sha256=NWVMUxcL4lOHmgNmEXZyj7qTq-mt_gfYYWGMweCRtlE,151046
|
|
398
398
|
unique_toolkit/language_model/invocation_stats.py,sha256=CQbpUz394IsVd8eNTB1gPSMt5f0UXmkN1amNsqtAW6U,5857
|
|
399
399
|
unique_toolkit/language_model/model_costs.py,sha256=SMYhikdN4h_qMUKKrpHYyYO5o4MW_QlZGGEr643Hw0M,4354
|
|
400
400
|
unique_toolkit/language_model/prompt.py,sha256=JSawaLjQg3VR-E2fK8engFyJnNdk21zaO8pPIodzN4Q,3991
|
|
@@ -422,6 +422,6 @@ unique_toolkit/short_term_memory/service.py,sha256=2JMBEBn6GoyK-a0xvdcWpPft-3C74
|
|
|
422
422
|
unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
423
423
|
unique_toolkit/smart_rules/compile.py,sha256=vRe0aMJWuVIK-VsqomgxQImzke7MDmwgSxxJfYCa7zw,1386
|
|
424
424
|
unique_toolkit/test_utilities/events.py,sha256=FLrhMh0A_mQutAUjoD0FdMcVQ4Gq31r35OVGzRjvLro,6760
|
|
425
|
-
unique_toolkit-2026.38.0.
|
|
426
|
-
unique_toolkit-2026.38.0.
|
|
427
|
-
unique_toolkit-2026.38.0.
|
|
425
|
+
unique_toolkit-2026.38.0.dev4.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
426
|
+
unique_toolkit-2026.38.0.dev4.dist-info/METADATA,sha256=bLPgw9um9D6gC7lAdRZksRhdY2iSUB7paSAIS8f2ccI,7821
|
|
427
|
+
unique_toolkit-2026.38.0.dev4.dist-info/RECORD,,
|
|
File without changes
|