unique_toolkit 2026.30.0.dev7__py3-none-any.whl → 2026.30.0.dev9__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/content/smart_rules.py +8 -1
- unique_toolkit/experimental/components/content_tree/service.py +51 -14
- unique_toolkit/services/knowledge_base.py +8 -8
- {unique_toolkit-2026.30.0.dev7.dist-info → unique_toolkit-2026.30.0.dev9.dist-info}/METADATA +3 -2
- {unique_toolkit-2026.30.0.dev7.dist-info → unique_toolkit-2026.30.0.dev9.dist-info}/RECORD +6 -6
- {unique_toolkit-2026.30.0.dev7.dist-info → unique_toolkit-2026.30.0.dev9.dist-info}/WHEEL +0 -0
|
@@ -19,6 +19,8 @@ class Operator(str, Enum):
|
|
|
19
19
|
LESS_THAN_OR_EQUAL = "lessThanOrEqual"
|
|
20
20
|
IN = "in"
|
|
21
21
|
NOT_IN = "notIn"
|
|
22
|
+
OVERLAPS = "overlaps"
|
|
23
|
+
NOT_OVERLAPS = "notOverlaps"
|
|
22
24
|
CONTAINS = "contains"
|
|
23
25
|
NOT_CONTAINS = "notContains"
|
|
24
26
|
IS_NULL = "isNull"
|
|
@@ -148,7 +150,12 @@ def eval_operator(
|
|
|
148
150
|
return empty_operator(query.operator, user_metadata, tool_parameters)
|
|
149
151
|
elif query.operator == Operator.NESTED:
|
|
150
152
|
return eval_nested_operator(query.value, user_metadata, tool_parameters)
|
|
151
|
-
elif query.operator in [
|
|
153
|
+
elif query.operator in [
|
|
154
|
+
Operator.IN,
|
|
155
|
+
Operator.NOT_IN,
|
|
156
|
+
Operator.OVERLAPS,
|
|
157
|
+
Operator.NOT_OVERLAPS,
|
|
158
|
+
]:
|
|
152
159
|
return array_operator(query.value, user_metadata, tool_parameters)
|
|
153
160
|
else:
|
|
154
161
|
raise ValueError(f"Operator {query.operator} not supported")
|
|
@@ -19,12 +19,14 @@ on its own.
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import asyncio
|
|
22
|
-
import difflib
|
|
23
22
|
import functools
|
|
24
23
|
import json
|
|
24
|
+
import re
|
|
25
25
|
from collections.abc import Callable
|
|
26
26
|
from typing import TYPE_CHECKING, Any, Protocol, Self, overload
|
|
27
27
|
|
|
28
|
+
from rapidfuzz import fuzz
|
|
29
|
+
|
|
28
30
|
from unique_toolkit._common.validate_required_values import validate_required_values
|
|
29
31
|
from unique_toolkit.app.unique_settings import UniqueSettings
|
|
30
32
|
from unique_toolkit.content.schemas import ContentInfo
|
|
@@ -44,6 +46,32 @@ if TYPE_CHECKING:
|
|
|
44
46
|
from unique_toolkit.app.unique_settings import UniqueContext
|
|
45
47
|
|
|
46
48
|
|
|
49
|
+
_NON_ALNUM_RE = re.compile(r"[^a-zA-Z0-9]+")
|
|
50
|
+
_TRAILING_EXTENSION_RE = re.compile(r"\.[A-Za-z0-9]{1,8}$")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _tokenize_for_fuzzy_scoring(
|
|
54
|
+
value: str, case_sensitive: bool, *, strip_extension: bool = False
|
|
55
|
+
) -> str:
|
|
56
|
+
"""Normalize ``value`` into whitespace-separated tokens for
|
|
57
|
+
:func:`rapidfuzz.fuzz.token_set_ratio`, which only tokenizes on
|
|
58
|
+
whitespace — filenames/paths use ``_``/``-``/``/`` (and other
|
|
59
|
+
punctuation) as their real word separators, so those need converting to
|
|
60
|
+
spaces first or every candidate is scored as a single opaque token (no
|
|
61
|
+
better than a plain char-ratio). Mirrors what
|
|
62
|
+
:func:`rapidfuzz.utils.default_process` does, minus the unconditional
|
|
63
|
+
lowercasing it bundles in — kept as a local regex rather than that
|
|
64
|
+
helper (via ``fuzz.token_set_ratio``'s ``processor=`` argument) so
|
|
65
|
+
``case_sensitive=True`` can still skip lowercasing while keeping
|
|
66
|
+
separator normalization.
|
|
67
|
+
"""
|
|
68
|
+
if strip_extension:
|
|
69
|
+
value = _TRAILING_EXTENSION_RE.sub("", value)
|
|
70
|
+
if not case_sensitive:
|
|
71
|
+
value = value.lower()
|
|
72
|
+
return _NON_ALNUM_RE.sub(" ", value).strip()
|
|
73
|
+
|
|
74
|
+
|
|
47
75
|
class _CachedResolveTaskFactory(Protocol):
|
|
48
76
|
"""Structural type for :func:`functools.cache`-wrapped resolve-task factory.
|
|
49
77
|
|
|
@@ -340,16 +368,24 @@ class ContentTree:
|
|
|
340
368
|
) -> list[FuzzyMatch]:
|
|
341
369
|
"""Fuzzy-match ``query`` against visible file names and/or paths.
|
|
342
370
|
|
|
343
|
-
Scoring uses :
|
|
344
|
-
|
|
345
|
-
|
|
371
|
+
Scoring uses :func:`rapidfuzz.fuzz.token_set_ratio` over each string's
|
|
372
|
+
tokens (split on whitespace, ``_``, ``-``, and ``/``, extension
|
|
373
|
+
stripped), scaled to ``[0.0, 1.0]``. Token-set scoring means a short
|
|
374
|
+
query that's a complete word inside a much longer filename scores
|
|
375
|
+
highly (ideally ``1.0`` for an exact token match) instead of being
|
|
376
|
+
penalized for the filename's unrelated length — a plain character-level
|
|
377
|
+
ratio (e.g. :class:`difflib.SequenceMatcher`, used here previously)
|
|
378
|
+
scores "alpensys" vs. "alpensys_budget_vs_actual_q3_2024.docx" at only
|
|
379
|
+
~0.35, indistinguishable from genuinely unrelated files, because it
|
|
380
|
+
penalizes every unmatched character in the (much longer) candidate.
|
|
381
|
+
Matching is case-insensitive by default since file names in a
|
|
382
|
+
knowledge base tend to be noisy.
|
|
346
383
|
|
|
347
384
|
Args:
|
|
348
385
|
query: The search string (typically a fragment of a filename or path).
|
|
349
386
|
limit: Maximum number of matches to return, after sorting by score
|
|
350
387
|
descending.
|
|
351
|
-
min_score: Drop matches scoring below this threshold.
|
|
352
|
-
:mod:`difflib`'s own rule of thumb for "reasonably close".
|
|
388
|
+
min_score: Drop matches scoring below this threshold.
|
|
353
389
|
match_on: Score against the basename (``"key"``), the joined folder
|
|
354
390
|
path (``"path"``), or take the max of both (``"both"``).
|
|
355
391
|
case_sensitive: If ``False`` (default) both sides are lowercased
|
|
@@ -369,25 +405,26 @@ class ContentTree:
|
|
|
369
405
|
metadata_filter=metadata_filter,
|
|
370
406
|
max_concurrent_scope_lookups=max_concurrent_scope_lookups,
|
|
371
407
|
)
|
|
372
|
-
normalized_query = query
|
|
408
|
+
normalized_query = _tokenize_for_fuzzy_scoring(query, case_sensitive)
|
|
373
409
|
|
|
374
410
|
matches: list[FuzzyMatch] = []
|
|
375
411
|
for content_info, segments in rows:
|
|
376
|
-
key_candidate =
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
412
|
+
key_candidate = _tokenize_for_fuzzy_scoring(
|
|
413
|
+
content_info.key, case_sensitive, strip_extension=True
|
|
414
|
+
)
|
|
415
|
+
path_candidate = _tokenize_for_fuzzy_scoring(
|
|
416
|
+
"/".join(segments), case_sensitive
|
|
417
|
+
)
|
|
381
418
|
|
|
382
419
|
score_key = match_on in ("key", "both")
|
|
383
420
|
score_path = match_on in ("path", "both")
|
|
384
421
|
key_score = (
|
|
385
|
-
|
|
422
|
+
fuzz.token_set_ratio(normalized_query, key_candidate) / 100.0
|
|
386
423
|
if score_key
|
|
387
424
|
else 0.0
|
|
388
425
|
)
|
|
389
426
|
path_score = (
|
|
390
|
-
|
|
427
|
+
fuzz.token_set_ratio(normalized_query, path_candidate) / 100.0
|
|
391
428
|
if score_path
|
|
392
429
|
else 0.0
|
|
393
430
|
)
|
|
@@ -230,7 +230,7 @@ class KnowledgeBaseService:
|
|
|
230
230
|
searches = search_content_chunks(
|
|
231
231
|
user_id=self._user_id,
|
|
232
232
|
company_id=self._company_id,
|
|
233
|
-
chat_id=
|
|
233
|
+
chat_id=None,
|
|
234
234
|
search_string=search_string,
|
|
235
235
|
search_type=search_type,
|
|
236
236
|
limit=limit,
|
|
@@ -327,7 +327,7 @@ class KnowledgeBaseService:
|
|
|
327
327
|
searches = await search_content_chunks_async(
|
|
328
328
|
user_id=self._user_id,
|
|
329
329
|
company_id=self._company_id,
|
|
330
|
-
chat_id=
|
|
330
|
+
chat_id=None,
|
|
331
331
|
search_string=search_string,
|
|
332
332
|
search_type=search_type,
|
|
333
333
|
limit=limit,
|
|
@@ -363,7 +363,7 @@ class KnowledgeBaseService:
|
|
|
363
363
|
return search_contents(
|
|
364
364
|
user_id=self._user_id,
|
|
365
365
|
company_id=self._company_id,
|
|
366
|
-
chat_id=
|
|
366
|
+
chat_id=None,
|
|
367
367
|
where=where,
|
|
368
368
|
include_failed_content=include_failed_content,
|
|
369
369
|
)
|
|
@@ -387,7 +387,7 @@ class KnowledgeBaseService:
|
|
|
387
387
|
return await search_contents_async(
|
|
388
388
|
user_id=self._user_id,
|
|
389
389
|
company_id=self._company_id,
|
|
390
|
-
chat_id=
|
|
390
|
+
chat_id=None,
|
|
391
391
|
where=where,
|
|
392
392
|
include_failed_content=include_failed_content,
|
|
393
393
|
)
|
|
@@ -429,7 +429,7 @@ class KnowledgeBaseService:
|
|
|
429
429
|
content_name=content_name,
|
|
430
430
|
mime_type=mime_type,
|
|
431
431
|
scope_id=scope_id,
|
|
432
|
-
chat_id=
|
|
432
|
+
chat_id=None,
|
|
433
433
|
skip_ingestion=skip_ingestion,
|
|
434
434
|
ingestion_config=ingestion_config,
|
|
435
435
|
metadata=metadata,
|
|
@@ -470,7 +470,7 @@ class KnowledgeBaseService:
|
|
|
470
470
|
content_name=content_name,
|
|
471
471
|
mime_type=mime_type,
|
|
472
472
|
scope_id=scope_id,
|
|
473
|
-
chat_id=
|
|
473
|
+
chat_id=None,
|
|
474
474
|
skip_ingestion=skip_ingestion,
|
|
475
475
|
ingestion_config=ingestion_config,
|
|
476
476
|
metadata=metadata,
|
|
@@ -511,7 +511,7 @@ class KnowledgeBaseService:
|
|
|
511
511
|
content_name=content_name,
|
|
512
512
|
mime_type=mime_type,
|
|
513
513
|
scope_id=scope_id,
|
|
514
|
-
chat_id=
|
|
514
|
+
chat_id=None,
|
|
515
515
|
skip_ingestion=skip_ingestion,
|
|
516
516
|
skip_excel_ingestion=skip_excel_ingestion,
|
|
517
517
|
ingestion_config=ingestion_config,
|
|
@@ -544,7 +544,7 @@ class KnowledgeBaseService:
|
|
|
544
544
|
user_id=self._user_id,
|
|
545
545
|
company_id=self._company_id,
|
|
546
546
|
content_id=content_id,
|
|
547
|
-
chat_id=
|
|
547
|
+
chat_id=None,
|
|
548
548
|
filename=output_filename,
|
|
549
549
|
tmp_dir_path=output_dir_path,
|
|
550
550
|
)
|
{unique_toolkit-2026.30.0.dev7.dist-info → unique_toolkit-2026.30.0.dev9.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-toolkit
|
|
3
|
-
Version: 2026.30.0.
|
|
3
|
+
Version: 2026.30.0.dev9
|
|
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>
|
|
@@ -16,7 +16,7 @@ Requires-Dist: sseclient>=0.0.27,<0.0.28
|
|
|
16
16
|
Requires-Dist: openai>=1.105.0,<3
|
|
17
17
|
Requires-Dist: platformdirs>=4.0.0,<5
|
|
18
18
|
Requires-Dist: pillow>=12.1.1
|
|
19
|
-
Requires-Dist: unique-sdk>=2026.30.0.
|
|
19
|
+
Requires-Dist: unique-sdk>=2026.30.0.dev2,<2026.30.0rc0
|
|
20
20
|
Requires-Dist: jambo>=0.1.2,<0.2
|
|
21
21
|
Requires-Dist: jinja2>=3.1.6,<4
|
|
22
22
|
Requires-Dist: docxtpl>=0.20.1,<0.21
|
|
@@ -32,6 +32,7 @@ Requires-Dist: pypandoc>=1.16.2,<2
|
|
|
32
32
|
Requires-Dist: tenacity>=9.1.4
|
|
33
33
|
Requires-Dist: pydantic-core>=2.41.5
|
|
34
34
|
Requires-Dist: cachetools>=5.0,<6
|
|
35
|
+
Requires-Dist: rapidfuzz>=3.0,<4
|
|
35
36
|
Requires-Dist: fastapi>=0.121.0,<0.122 ; extra == 'fastapi'
|
|
36
37
|
Requires-Dist: starlette>=0.49.1 ; extra == 'fastapi'
|
|
37
38
|
Requires-Dist: uvicorn>=0.37.0,<0.38 ; extra == 'fastapi'
|
|
@@ -260,7 +260,7 @@ unique_toolkit/content/constants.py,sha256=1iy4Y67xobl5VTnJB6SxSyuoBWbdLl9244xfV
|
|
|
260
260
|
unique_toolkit/content/functions.py,sha256=avnlEQ1FkVz2NTbXUkafU04jzygqOJtUMQL605rEL4c,34019
|
|
261
261
|
unique_toolkit/content/schemas.py,sha256=AmnlqMpA_JgbgsaZF6Xdy8kt9lcY0YCqlfqpG1tYWeE,11306
|
|
262
262
|
unique_toolkit/content/service.py,sha256=S_sFtG1hJd-zz7XyP-6Gu-B8_Xx-r5v46qZLWdRRyRs,29477
|
|
263
|
-
unique_toolkit/content/smart_rules.py,sha256
|
|
263
|
+
unique_toolkit/content/smart_rules.py,sha256=-bRlF_e66yKA8KsXTPYqfhHZu1jsXXl5akIZnSgu5X8,11621
|
|
264
264
|
unique_toolkit/content/utils.py,sha256=keDkqu_ZzQY0y_g7g_XLOza3p4XsvfqVVDI9ylM91KY,10115
|
|
265
265
|
unique_toolkit/data_extraction/README.md,sha256=5KxzqPHC29wxBiWJ-87wBQisSgRBkuSnPTb38pGH1wA,3138
|
|
266
266
|
unique_toolkit/data_extraction/__init__.py,sha256=y6uSp8PAqgPZMoBgXWp1Ep0gsk9MD-NV8qn2QpOY-xY,330
|
|
@@ -294,7 +294,7 @@ unique_toolkit/experimental/components/_base.py,sha256=XZHKffTIy331ZQnsywl0m1XkJ
|
|
|
294
294
|
unique_toolkit/experimental/components/content_tree/__init__.py,sha256=30eyVB7CzDC9WZTXj-FYQNY20o0m987SV3dn6WdFI-U,2103
|
|
295
295
|
unique_toolkit/experimental/components/content_tree/functions.py,sha256=xLBHNHBsoySIESU-jvmU-6_-5LrbiXkv7-btxbCf_38,8690
|
|
296
296
|
unique_toolkit/experimental/components/content_tree/schemas.py,sha256=qRF8UUxhB616lzFSi5FV6vIdLXDHry3fWWb3eSTMJAo,3942
|
|
297
|
-
unique_toolkit/experimental/components/content_tree/service.py,sha256=
|
|
297
|
+
unique_toolkit/experimental/components/content_tree/service.py,sha256=U04U5Gq1jElUlVbSjefHAIefBi8KRZhKUwmotADx2GU,18907
|
|
298
298
|
unique_toolkit/experimental/components/internal_search/__init__.py,sha256=Jj-ujG_SS5-uqCrrNidRn_H0sQAeSvyWtt9Tw0pkbzI,3091
|
|
299
299
|
unique_toolkit/experimental/components/internal_search/base/__init__.py,sha256=GB7vNBvbF6V1oLjKEf2KVvukr-BwgYpAkD5jKtGG9wU,929
|
|
300
300
|
unique_toolkit/experimental/components/internal_search/base/config.py,sha256=Bf77n0YSEFXRZKAqe4efAOITnyD3hH5Gp-gEVbMw1qE,2482
|
|
@@ -397,7 +397,7 @@ unique_toolkit/protocols/support.py,sha256=x5edzbBmKAUuvm3oLFh52gkgQsqR5LU7M0t8R
|
|
|
397
397
|
unique_toolkit/services/__init__.py,sha256=b9Wl7Wvz1JmZDbDexIZzBoXZ0wscXuczGAH8LoL-Qak,572
|
|
398
398
|
unique_toolkit/services/chat_service.py,sha256=MAueuSfATFJH8aSeNxbnBI_Ne1UgYUIk3i5F1f2ALFQ,97692
|
|
399
399
|
unique_toolkit/services/factory.py,sha256=7gNeanyHkgX23Iz7RMCQTCkE9gI5asCI11nXxM0jjx4,9832
|
|
400
|
-
unique_toolkit/services/knowledge_base.py,sha256=
|
|
400
|
+
unique_toolkit/services/knowledge_base.py,sha256=3pkYoHVtJL7ZtEq_H-p6_v4IpJ_KXkVLlapF0xkl1-U,43462
|
|
401
401
|
unique_toolkit/short_term_memory/__init__.py,sha256=2mI3AUrffgH7Yt-xS57EGqnHf7jnn6xquoKEhJqk3Wg,185
|
|
402
402
|
unique_toolkit/short_term_memory/constants.py,sha256=698CL6-wjup2MvU19RxSmQk3gX7aqW_OOpZB7sbz_Xg,34
|
|
403
403
|
unique_toolkit/short_term_memory/functions.py,sha256=mbEeWkJ5Jr0cvg1_m6l9aS1L6nWE5ZhviNzpKlCs-a8,7907
|
|
@@ -406,6 +406,6 @@ unique_toolkit/short_term_memory/service.py,sha256=2JMBEBn6GoyK-a0xvdcWpPft-3C74
|
|
|
406
406
|
unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
407
407
|
unique_toolkit/smart_rules/compile.py,sha256=vRe0aMJWuVIK-VsqomgxQImzke7MDmwgSxxJfYCa7zw,1386
|
|
408
408
|
unique_toolkit/test_utilities/events.py,sha256=FLrhMh0A_mQutAUjoD0FdMcVQ4Gq31r35OVGzRjvLro,6760
|
|
409
|
-
unique_toolkit-2026.30.0.
|
|
410
|
-
unique_toolkit-2026.30.0.
|
|
411
|
-
unique_toolkit-2026.30.0.
|
|
409
|
+
unique_toolkit-2026.30.0.dev9.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
410
|
+
unique_toolkit-2026.30.0.dev9.dist-info/METADATA,sha256=uiBB4yGaDGzeuETwGsKxFx1tSatqpygBsG1j8AABOsY,7459
|
|
411
|
+
unique_toolkit-2026.30.0.dev9.dist-info/RECORD,,
|
|
File without changes
|