unique_toolkit 2026.24.0.dev3__py3-none-any.whl → 2026.24.0.dev5__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.
@@ -8,6 +8,8 @@ from typing import Any
8
8
  from unique_toolkit.content.smart_rules import (
9
9
  Operator,
10
10
  Statement,
11
+ UniqueQL,
12
+ uniqueql_to_dict,
11
13
  )
12
14
 
13
15
  _UQL_AND = "and"
@@ -29,16 +31,17 @@ def build_folder_id_in_clause(scope_ids: list[str]) -> dict[str, Any]:
29
31
 
30
32
  def merge_scope_clause_into_metadata_filter(
31
33
  scope_clause: Mapping[str, Any],
32
- metadata_filter: dict[str, Any] | None,
34
+ metadata_filter: UniqueQL | Mapping[str, Any] | None,
33
35
  ) -> dict[str, Any]:
34
36
  """AND-merge *scope_clause* with *metadata_filter*, flattening a top-level ``and``."""
35
37
  scope_dict = dict(scope_clause)
36
- if not metadata_filter:
38
+ metadata_dict = uniqueql_to_dict(metadata_filter)
39
+ if not metadata_dict:
37
40
  return scope_dict
38
- inner_and = metadata_filter.get(_UQL_AND)
41
+ inner_and = metadata_dict.get(_UQL_AND)
39
42
  if isinstance(inner_and, list):
40
43
  return {_UQL_AND: [scope_dict, *inner_and]}
41
- return {_UQL_AND: [scope_dict, dict(metadata_filter)]}
44
+ return {_UQL_AND: [scope_dict, metadata_dict]}
42
45
 
43
46
 
44
47
  __all__ = [
@@ -974,7 +974,12 @@ def ui_schema_for_model(
974
974
  node["items"] = item_node
975
975
 
976
976
  # Dict -> additionalProperties (value side)
977
- elif origin is dict:
977
+ # Skip when the node already carries a ui:widget (fully-specified
978
+ # widget) or an anyOf (fully-specified type branching): walking the
979
+ # dict value type in those cases adds a spurious "additionalProperties"
980
+ # node that confuses RJSF renderers. Presentation-only meta such as
981
+ # ui:expandable or ui:title does NOT prevent the walk.
982
+ elif origin is dict and "ui:widget" not in node and "anyOf" not in node:
978
983
  _key_t, val_t = get_args(base) or (Any, Any)
979
984
  val_base, val_meta = _walk_annotated_chain(val_t)
980
985
  val_base = _unwrap_optional(val_base)
@@ -1,10 +1,13 @@
1
+ import json
1
2
  import re
3
+ from collections.abc import Mapping
2
4
  from datetime import datetime, timedelta, timezone
3
5
  from enum import Enum
4
- from typing import Any, Dict, List, Mapping, Self, Union
6
+ from typing import Annotated, Any, Dict, List, Self, Union
5
7
 
6
- from pydantic import AliasChoices, BaseModel, Field
8
+ from pydantic import AliasChoices, BaseModel, BeforeValidator, Field, PlainSerializer
7
9
  from pydantic.config import ConfigDict
10
+ from pydantic.json_schema import WithJsonSchema
8
11
 
9
12
 
10
13
  class Operator(str, Enum):
@@ -54,6 +57,10 @@ class BaseStatement(BaseModel):
54
57
  ) -> Self:
55
58
  return self.model_copy()
56
59
 
60
+ def to_dict(self) -> dict[str, Any]:
61
+ """Convert to wire-format JSON dict (uses serialization aliases)."""
62
+ return self.model_dump(mode="json")
63
+
57
64
 
58
65
  class Statement(BaseStatement):
59
66
  operator: Operator
@@ -285,7 +292,6 @@ def get_fallback_values(
285
292
  return values
286
293
 
287
294
 
288
- # Example usage:
289
295
  def parse_uniqueql(json_data: Dict[str, Any]) -> UniqueQL:
290
296
  if "operator" in json_data:
291
297
  return Statement.model_validate(json_data)
@@ -299,3 +305,55 @@ def parse_uniqueql(json_data: Dict[str, Any]) -> UniqueQL:
299
305
  )
300
306
  else:
301
307
  raise ValueError("Invalid UniqueQL format")
308
+
309
+
310
+ def parse_uniqueql_input(v: Any) -> UniqueQL | None:
311
+ """Parse config/API input (JSON string, dict, or model) into a UniqueQL model."""
312
+ if v is None:
313
+ return None
314
+ if isinstance(v, (Statement, AndStatement, OrStatement)):
315
+ return v
316
+ if isinstance(v, str):
317
+ if v.strip() == "":
318
+ return None
319
+ try:
320
+ v = json.loads(v)
321
+ except json.JSONDecodeError as e:
322
+ raise ValueError(f"Invalid JSON: {e}") from e
323
+ if isinstance(v, dict):
324
+ return parse_uniqueql(v)
325
+ raise ValueError(f"Expected JSON string or dict, got {type(v).__name__}")
326
+
327
+
328
+ def uniqueql_to_dict(
329
+ value: UniqueQL | Mapping[str, Any] | None,
330
+ ) -> dict[str, Any] | None:
331
+ """Convert a UniqueQL model (or wire dict) to a JSON-serializable dict for APIs."""
332
+ if value is None:
333
+ return None
334
+ if isinstance(value, BaseStatement):
335
+ return value.to_dict()
336
+ return dict(value)
337
+
338
+
339
+ def _serialize_uniqueql_field(value: UniqueQL | None) -> dict[str, Any] | None:
340
+ return None if value is None else value.to_dict()
341
+
342
+
343
+ _UNIQUEQL_JSON_SCHEMA = {
344
+ "anyOf": [
345
+ {"type": "string", "title": "UniqueQL (JSON)"},
346
+ {"type": "null", "title": "Deactivated", "description": "None"},
347
+ ]
348
+ }
349
+
350
+ UniqueQLField = Annotated[
351
+ UniqueQL | None,
352
+ BeforeValidator(parse_uniqueql_input),
353
+ WithJsonSchema(_UNIQUEQL_JSON_SCHEMA),
354
+ PlainSerializer(
355
+ _serialize_uniqueql_field,
356
+ when_used="json",
357
+ return_type=dict[str, Any] | None,
358
+ ),
359
+ ]
@@ -6,8 +6,10 @@ from typing import Annotated, Self
6
6
  from pydantic import Field, model_validator
7
7
 
8
8
  from unique_toolkit._common.config_checker import register_config
9
+ from unique_toolkit._common.pydantic.rjsf_tags import RJSFMetaTag
9
10
  from unique_toolkit._common.pydantic_helpers import DeactivatedNone
10
11
  from unique_toolkit.content.schemas import ContentRerankerConfig
12
+ from unique_toolkit.content.smart_rules import UniqueQLField
11
13
  from unique_toolkit.experimental.components.internal_search.base.config import (
12
14
  InternalSearchConfig,
13
15
  )
@@ -15,9 +17,11 @@ from unique_toolkit.experimental.components.internal_search.base.config import (
15
17
 
16
18
  @register_config()
17
19
  class KnowledgeBaseInternalSearchConfig(InternalSearchConfig):
18
- scope_ids: list[str] | DeactivatedNone = Field(
20
+ scope_ids: Annotated[
21
+ Annotated[list[str], Field(title="Scope IDs")] | DeactivatedNone,
22
+ RJSFMetaTag({"anyOf": [{"items": {"ui:title": "Scope ID"}}, {}]}),
23
+ ] = Field(
19
24
  default=None,
20
- title="Active",
21
25
  deprecated=True,
22
26
  description=(
23
27
  "Not accepted for new configs. Use ``metadata_filter`` with "
@@ -26,10 +30,29 @@ class KnowledgeBaseInternalSearchConfig(InternalSearchConfig):
26
30
  "a ``folderId in [scope_ids]`` metadata filter at search time."
27
31
  ),
28
32
  )
29
- metadata_filter: dict[str, object] | None = Field(
33
+ metadata_filter: Annotated[
34
+ UniqueQLField,
35
+ # Put textarea attrs on the string branch (index 0) only, not at field level.
36
+ # If set at field level, RJSF's MultiSchemaField applies ui:widget to every
37
+ # anyOf branch during option iteration, which crashes on non-string branches.
38
+ RJSFMetaTag(
39
+ {
40
+ "ui:options": {"customValidation": "uniqueql"},
41
+ "anyOf": [
42
+ {
43
+ "ui:widget": "textarea",
44
+ "ui:placeholder": '{"operator": "equals", "value": "...", "path": ["fieldName"]}',
45
+ "ui:emptyValue": "",
46
+ },
47
+ {},
48
+ ],
49
+ }
50
+ ),
51
+ ] = Field(
30
52
  default=None,
31
53
  description=(
32
- "Static metadata filter applied to every KB search. "
54
+ "Static UniqueQL metadata filter applied to every KB search. "
55
+ "Pass as a JSON string or a plain dict. "
33
56
  "Overridden by chat context filter or per-invocation state override."
34
57
  ),
35
58
  )
@@ -105,7 +105,11 @@ class KnowledgeBaseInternalSearchService(
105
105
  return cast("dict[str, Any] | None", self._state.metadata_filter_override)
106
106
  if self._context.chat is not None:
107
107
  return self._context.chat.metadata_filter
108
- return self._config.metadata_filter
108
+ return (
109
+ self._config.metadata_filter.to_dict()
110
+ if self._config.metadata_filter
111
+ else None
112
+ )
109
113
 
110
114
  @property
111
115
  def _effective_scope_ids(self) -> list[str] | None:
@@ -7,6 +7,7 @@ from unique_toolkit.content.smart_rules import (
7
7
  OrStatement,
8
8
  Statement,
9
9
  UniqueQL,
10
+ UniqueQLField,
10
11
  array_operator,
11
12
  binary_operator,
12
13
  calculate_current_date,
@@ -19,9 +20,11 @@ from unique_toolkit.content.smart_rules import (
19
20
  is_array_of_strings,
20
21
  null_operator,
21
22
  parse_uniqueql,
23
+ parse_uniqueql_input,
22
24
  replace_tool_parameters_patterns,
23
25
  replace_user_metadata_patterns,
24
26
  replace_variables,
27
+ uniqueql_to_dict,
25
28
  )
26
29
 
27
30
  warnings.warn(
@@ -38,6 +41,7 @@ __all__ = [
38
41
  "OrStatement",
39
42
  "Statement",
40
43
  "UniqueQL",
44
+ "UniqueQLField",
41
45
  "array_operator",
42
46
  "binary_operator",
43
47
  "calculate_current_date",
@@ -50,7 +54,9 @@ __all__ = [
50
54
  "is_array_of_strings",
51
55
  "null_operator",
52
56
  "parse_uniqueql",
57
+ "parse_uniqueql_input",
53
58
  "replace_tool_parameters_patterns",
54
59
  "replace_user_metadata_patterns",
55
60
  "replace_variables",
61
+ "uniqueql_to_dict",
56
62
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-toolkit
3
- Version: 2026.24.0.dev3
3
+ Version: 2026.24.0.dev5
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>
@@ -58,8 +58,8 @@ unique_toolkit/_common/experimental/write_up_agent/services/template_handler/ser
58
58
  unique_toolkit/_common/experimental/write_up_agent/services/template_handler/utils.py,sha256=FeJhTAU7AUlSFCfFwmEzzI22PtAdFJluzsGeiEPK49Q,6393
59
59
  unique_toolkit/_common/experimental/write_up_agent/utils.py,sha256=I_OCDXCxY6uZk_fNQg_bIXHwjnN7ND_ngcisThnGGIc,739
60
60
  unique_toolkit/_common/feature_flags/schema.py,sha256=X32VqH4VMK7bhEfSd8Wbddl8FVs7Gh7ucuIEbmqc4Kw,268
61
- unique_toolkit/_common/metadata_filter_scope.py,sha256=kXjUdCQVjBbWj2TfTyrV5P7Y6etgoQEpAhhIJQ7RXAs,1340
62
- unique_toolkit/_common/pydantic/rjsf_tags.py,sha256=ZmjqpCa1rCjE7Got1BPK9l3zDWWbkpkxedbWQpq5iSY,39533
61
+ unique_toolkit/_common/metadata_filter_scope.py,sha256=Lj7oYrqHUoiqSBn6PA1FmeqtZONhSdQ8u3CR5YolxcM,1432
62
+ unique_toolkit/_common/pydantic/rjsf_tags.py,sha256=B4iMCvKlzP-xE8pp56uJ4xYNB89Vz9u3z5MXXx68CPs,39955
63
63
  unique_toolkit/_common/pydantic_helpers.py,sha256=LTI21litYJdzRe7cbgO1ntKf06yb5V8v7hASMM3Wwa8,5839
64
64
  unique_toolkit/_common/referencing.py,sha256=UY7-9nJLcRqlHUNytfJZPVkdpjvy5wIps_7P33_qgA0,1496
65
65
  unique_toolkit/_common/string_utilities.py,sha256=HOAOZbH3vRmTDwUCIF0YTE04IWcVHZyq3uOmevxWXn0,4433
@@ -253,7 +253,7 @@ unique_toolkit/content/constants.py,sha256=1iy4Y67xobl5VTnJB6SxSyuoBWbdLl9244xfV
253
253
  unique_toolkit/content/functions.py,sha256=Rx3N9x23JjVt7mmUlFhOhjAjkWQCzzodtpDgoIsE2xg,33932
254
254
  unique_toolkit/content/schemas.py,sha256=z3xlTkOh9MTwHGLwcc5vfT5OmDRCiWE0F0MaiPMV4dg,10281
255
255
  unique_toolkit/content/service.py,sha256=fR93_76aPczx2voxR7cvYDs_-pxD-Mc9WP6-5IqA8ko,28301
256
- unique_toolkit/content/smart_rules.py,sha256=WEAN1AoRfSaCvg7keLXQg24CRmvyu7vHQgeNuH-WqPQ,9674
256
+ unique_toolkit/content/smart_rules.py,sha256=9GlHXm63POTi4jy5hW0qUjeqy-JSeL1wIbURM1GKhc4,11481
257
257
  unique_toolkit/content/utils.py,sha256=keDkqu_ZzQY0y_g7g_XLOza3p4XsvfqVVDI9ylM91KY,10115
258
258
  unique_toolkit/data_extraction/README.md,sha256=5KxzqPHC29wxBiWJ-87wBQisSgRBkuSnPTb38pGH1wA,3138
259
259
  unique_toolkit/data_extraction/__init__.py,sha256=y6uSp8PAqgPZMoBgXWp1Ep0gsk9MD-NV8qn2QpOY-xY,330
@@ -299,9 +299,9 @@ unique_toolkit/experimental/components/internal_search/chat/config.py,sha256=2DQ
299
299
  unique_toolkit/experimental/components/internal_search/chat/schemas.py,sha256=vWPulL7QVo9y8vPZTKAeHVYOCHG1DmKIh6W75S0jEnA,299
300
300
  unique_toolkit/experimental/components/internal_search/chat/service.py,sha256=SO60zGtzJIp5kGLEXCzHwcKgdN4MGeiv5kjGA3ki9fc,2355
301
301
  unique_toolkit/experimental/components/internal_search/knowledge_base/__init__.py,sha256=eAp_8qw5XP2z6GFbxvVCmwgirXxMvJuEDZGx-PsFi54,411
302
- unique_toolkit/experimental/components/internal_search/knowledge_base/config.py,sha256=MIvZ_HhJq85yDfoO586BN-XYRYHr3dX2wL0jT0w8D3Q,2217
302
+ unique_toolkit/experimental/components/internal_search/knowledge_base/config.py,sha256=QyewUg9tH5mmfL56N3mpFUZ3_2qSthitd8JCV1oX3oU,3232
303
303
  unique_toolkit/experimental/components/internal_search/knowledge_base/schemas.py,sha256=q5tk4IcpyypzqMb_36nEpO_eZgE9PqQsFrlpXJIwVWk,912
304
- unique_toolkit/experimental/components/internal_search/knowledge_base/service.py,sha256=o579j69_25cPYPzrwOE16DmTojzRUl-b3pEYUS7n_XQ,4756
304
+ unique_toolkit/experimental/components/internal_search/knowledge_base/service.py,sha256=LBp6SPJ8mO5x9rAbey-mejHZFK8om9EviyRUNGm5W7s,4856
305
305
  unique_toolkit/experimental/components/internal_search/post_processing/__init__.py,sha256=ndNXfBOya9AEzhg8inbJVRpOtG809cw9vURK1yXnlAY,1896
306
306
  unique_toolkit/experimental/components/internal_search/post_processing/config.py,sha256=Rkfm1PZhotJ8yyOK_SufIlUNcknyNzqMZWl4wYHnZMM,2385
307
307
  unique_toolkit/experimental/components/internal_search/post_processing/service.py,sha256=5GL0bm4a24saPLUamP59CPPjxGlpEgBHBDCn7OqWGEI,7499
@@ -389,8 +389,8 @@ unique_toolkit/short_term_memory/functions.py,sha256=RQpdcpAIKQ8rRDhP8bHqekIKttQ
389
389
  unique_toolkit/short_term_memory/schemas.py,sha256=C6gopsnuIgWJBG7XF87gxMwzLJtqbSGmsKXUf2h1sn4,1666
390
390
  unique_toolkit/short_term_memory/service.py,sha256=Ncw0_0CNM6kmzJSLVJGG5GcKpBduIBebYZlzZ7N4G8Q,8900
391
391
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
392
- unique_toolkit/smart_rules/compile.py,sha256=Ozhh70qCn2yOzRWr9d8WmJeTo7AQurwd3tStgBMPFLA,1246
392
+ unique_toolkit/smart_rules/compile.py,sha256=vRe0aMJWuVIK-VsqomgxQImzke7MDmwgSxxJfYCa7zw,1386
393
393
  unique_toolkit/test_utilities/events.py,sha256=FLrhMh0A_mQutAUjoD0FdMcVQ4Gq31r35OVGzRjvLro,6760
394
- unique_toolkit-2026.24.0.dev3.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
395
- unique_toolkit-2026.24.0.dev3.dist-info/METADATA,sha256=ZaoaKI5FW8PBEO4jZTDu8Rh1POBUC5M6p1z1px7d6aM,7392
396
- unique_toolkit-2026.24.0.dev3.dist-info/RECORD,,
394
+ unique_toolkit-2026.24.0.dev5.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
395
+ unique_toolkit-2026.24.0.dev5.dist-info/METADATA,sha256=c9BRIxMsywMfMEN91oVAv0NOEOi2KlnDULi-ohknj_c,7392
396
+ unique_toolkit-2026.24.0.dev5.dist-info/RECORD,,