unique_internal_search 2026.22.0.dev3__tar.gz → 2026.22.0.dev4__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.
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/PKG-INFO +2 -2
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/pyproject.toml +2 -2
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/config.py +18 -0
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/prompts.py +5 -0
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/service.py +18 -1
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/uploaded_search/service.py +63 -20
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/README.md +0 -0
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/__init__.py +0 -0
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/services/message_log.py +0 -0
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/uploaded_search/config.py +0 -0
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/uploaded_search/prompts/__init__.py +0 -0
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/uploaded_search/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/utils.py +0 -0
- {unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/unique_internal_search/validators.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-internal-search
|
|
3
|
-
Version: 2026.22.0.
|
|
3
|
+
Version: 2026.22.0.dev4
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Martin Fadler, Sadique Sheik, Fabian Schläpfer, Pascal Hauri
|
|
6
6
|
Author-email: Martin Fadler <martin.fadler@unique.ch>, Sadique Sheik <sadique@unique.ch>, Fabian Schläpfer <fabian@unique.ch>, Pascal Hauri <pascal@unique.ch>
|
|
7
7
|
License: Proprietary
|
|
8
8
|
Requires-Dist: pydantic>=2.8.2,<3
|
|
9
9
|
Requires-Dist: typing-extensions>=4.9.0,<5
|
|
10
|
-
Requires-Dist: unique-toolkit>=2026.22.0.
|
|
10
|
+
Requires-Dist: unique-toolkit>=2026.22.0.dev12,<2026.22.0rc0
|
|
11
11
|
Requires-Python: >=3.12, <4
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
|
{unique_internal_search-2026.22.0.dev3 → unique_internal_search-2026.22.0.dev4}/pyproject.toml
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique_internal_search"
|
|
3
|
-
version = "2026.22.0.
|
|
3
|
+
version = "2026.22.0.dev4"
|
|
4
4
|
description = ""
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "Proprietary" }
|
|
@@ -14,7 +14,7 @@ requires-python = ">=3.12,<4"
|
|
|
14
14
|
dependencies = [
|
|
15
15
|
"pydantic>=2.8.2,<3",
|
|
16
16
|
"typing-extensions>=4.9.0,<5",
|
|
17
|
-
"unique-toolkit>=2026.22.0.
|
|
17
|
+
"unique-toolkit>=2026.22.0.dev12,<2026.22.0rc0",
|
|
18
18
|
]
|
|
19
19
|
|
|
20
20
|
[build-system]
|
|
@@ -23,6 +23,7 @@ from unique_toolkit.content.schemas import (
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
from unique_internal_search.prompts import (
|
|
26
|
+
DEFAULT_CONTENT_IDS_PARAM_DESCRIPTION,
|
|
26
27
|
DEFAULT_LANGUAGE_PARAM_DESCRIPTION,
|
|
27
28
|
DEFAULT_SEARCH_STRING_PARAM_DESCRIPTION,
|
|
28
29
|
DEFAULT_TOOL_DESCRIPTION,
|
|
@@ -209,6 +210,23 @@ class InternalSearchConfig(BaseToolConfig):
|
|
|
209
210
|
description="Allow execution of multiple search strings in one call. When set to True, each string is searched individually and results are merged into a single response.",
|
|
210
211
|
)
|
|
211
212
|
|
|
213
|
+
enable_content_id_filter: bool = Field(
|
|
214
|
+
default=False,
|
|
215
|
+
description=(
|
|
216
|
+
"When True, exposes an optional `content_ids` parameter in the tool schema so the LLM can restrict the search to specific documents. "
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
param_description_content_ids: Annotated[
|
|
221
|
+
str,
|
|
222
|
+
RJSFMetaTag.StringWidget.textarea(
|
|
223
|
+
rows=int(len(DEFAULT_CONTENT_IDS_PARAM_DESCRIPTION.split("\n")))
|
|
224
|
+
),
|
|
225
|
+
] = get_string_field_with_pattern_validation(
|
|
226
|
+
DEFAULT_CONTENT_IDS_PARAM_DESCRIPTION,
|
|
227
|
+
description="`content_ids` parameter description shown to the LLM.",
|
|
228
|
+
)
|
|
229
|
+
|
|
212
230
|
metadata_chunk_sections: dict[str, str] = Field(
|
|
213
231
|
default={},
|
|
214
232
|
description=(
|
|
@@ -63,6 +63,11 @@ DEFAULT_SEARCH_STRING_PARAM_DESCRIPTION = "An expanded term that is optimized fo
|
|
|
63
63
|
|
|
64
64
|
DEFAULT_LANGUAGE_PARAM_DESCRIPTION = "The language that the user wrote the query in"
|
|
65
65
|
|
|
66
|
+
DEFAULT_CONTENT_IDS_PARAM_DESCRIPTION = (
|
|
67
|
+
"Optional list of document IDs (e.g cont_sx6neydisjrdvq59q39s89cg) to restrict the search "
|
|
68
|
+
"to specific documents. Use `null` to search all available documents."
|
|
69
|
+
)
|
|
70
|
+
|
|
66
71
|
|
|
67
72
|
DEFAULT_TOOL_RESPONSE_SYSTEM_REMINDER_PROMPT = """
|
|
68
73
|
## Reminder: Cite Every Fact from These Results
|
|
@@ -168,7 +168,12 @@ class InternalSearchService:
|
|
|
168
168
|
)
|
|
169
169
|
and chat_only
|
|
170
170
|
):
|
|
171
|
-
content_ids
|
|
171
|
+
if content_ids is not None:
|
|
172
|
+
content_ids = list(
|
|
173
|
+
filter(lambda x: x in self.selected_uploaded_file_ids, content_ids)
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
content_ids = self.selected_uploaded_file_ids
|
|
172
177
|
|
|
173
178
|
# Run all searches in parallel
|
|
174
179
|
results = await asyncio.gather(
|
|
@@ -236,6 +241,7 @@ class InternalSearchService:
|
|
|
236
241
|
self.debug_info = {
|
|
237
242
|
"searchStrings": search_strings,
|
|
238
243
|
"metadataFilter": metadata_filter,
|
|
244
|
+
"contentIds": content_ids,
|
|
239
245
|
"chatOnly": chat_only,
|
|
240
246
|
}
|
|
241
247
|
return selected_chunks
|
|
@@ -424,6 +430,16 @@ class InternalSearchTool(Tool[InternalSearchConfig], InternalSearchService):
|
|
|
424
430
|
Field(description=self.config.param_description_search_string),
|
|
425
431
|
)
|
|
426
432
|
|
|
433
|
+
optional_fields: dict = {}
|
|
434
|
+
if self.config.enable_content_id_filter:
|
|
435
|
+
optional_fields["content_ids"] = (
|
|
436
|
+
list[str] | None,
|
|
437
|
+
Field(
|
|
438
|
+
default=None,
|
|
439
|
+
description=self.config.param_description_content_ids,
|
|
440
|
+
),
|
|
441
|
+
)
|
|
442
|
+
|
|
427
443
|
internal_search_tool_input = create_model(
|
|
428
444
|
"InternalSearchToolInput",
|
|
429
445
|
search_string=search_string_field,
|
|
@@ -431,6 +447,7 @@ class InternalSearchTool(Tool[InternalSearchConfig], InternalSearchService):
|
|
|
431
447
|
str,
|
|
432
448
|
Field(description=self.config.param_description_language),
|
|
433
449
|
),
|
|
450
|
+
**optional_fields,
|
|
434
451
|
)
|
|
435
452
|
return LanguageModelToolDescription(
|
|
436
453
|
name=self.name,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
1
3
|
from pydantic import Field, create_model
|
|
2
4
|
from typing_extensions import override
|
|
3
5
|
from unique_toolkit import ContentService
|
|
@@ -14,6 +16,7 @@ from unique_toolkit.agentic.tools.tool_progress_reporter import (
|
|
|
14
16
|
)
|
|
15
17
|
from unique_toolkit.app.schemas import BaseEvent, ChatEvent
|
|
16
18
|
from unique_toolkit.chat.service import LanguageModelToolDescription
|
|
19
|
+
from unique_toolkit.content import Content
|
|
17
20
|
from unique_toolkit.language_model.schemas import (
|
|
18
21
|
LanguageModelFunction,
|
|
19
22
|
)
|
|
@@ -50,12 +53,28 @@ class UploadedSearchTool(Tool[UploadedSearchConfig]):
|
|
|
50
53
|
else:
|
|
51
54
|
self._user_query = None
|
|
52
55
|
|
|
56
|
+
# This blocking API call should be avoided.
|
|
57
|
+
# However, we don't have an easy way to pass user uploaded files to the tool currently
|
|
58
|
+
# Note that this was being done in `tool_description_for_system_prompt` before
|
|
59
|
+
self._valid_documents = self._compute_valid_documents()
|
|
60
|
+
|
|
53
61
|
@override
|
|
54
62
|
def display_name(self) -> str:
|
|
55
63
|
return self._display_name
|
|
56
64
|
|
|
57
65
|
@override
|
|
58
66
|
def tool_description(self) -> LanguageModelToolDescription:
|
|
67
|
+
optional_fields: dict = {}
|
|
68
|
+
if self._config.enable_content_id_filter and self._valid_documents:
|
|
69
|
+
content_id_type = Literal[*(d.id for d in self._valid_documents)]
|
|
70
|
+
optional_fields["content_ids"] = (
|
|
71
|
+
list[content_id_type] | None,
|
|
72
|
+
Field(
|
|
73
|
+
default=None,
|
|
74
|
+
description=self._config.param_description_content_ids,
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
|
|
59
78
|
internal_search_tool_input = create_model(
|
|
60
79
|
"InternalSearchToolInput",
|
|
61
80
|
search_string=(
|
|
@@ -66,7 +85,9 @@ class UploadedSearchTool(Tool[UploadedSearchConfig]):
|
|
|
66
85
|
str,
|
|
67
86
|
Field(description=self._config.param_description_language),
|
|
68
87
|
),
|
|
88
|
+
**optional_fields,
|
|
69
89
|
)
|
|
90
|
+
|
|
70
91
|
return LanguageModelToolDescription(
|
|
71
92
|
name=self.name,
|
|
72
93
|
description=self._config.tool_description,
|
|
@@ -74,28 +95,12 @@ class UploadedSearchTool(Tool[UploadedSearchConfig]):
|
|
|
74
95
|
)
|
|
75
96
|
|
|
76
97
|
def tool_description_for_system_prompt(self) -> str:
|
|
77
|
-
documents = self._content_service.get_documents_uploaded_to_chat()
|
|
78
|
-
if feature_flags.enable_selected_uploaded_files_un_18215.is_enabled(
|
|
79
|
-
self._company_id
|
|
80
|
-
):
|
|
81
|
-
documents = [
|
|
82
|
-
doc for doc in documents if doc.id in self._selected_uploaded_files
|
|
83
|
-
]
|
|
84
|
-
|
|
85
|
-
valid_documents = []
|
|
86
|
-
for doc in documents:
|
|
87
|
-
if not doc.is_ingested(default_if_unknown=True) or doc.is_expired():
|
|
88
|
-
continue
|
|
89
|
-
valid_documents.append(
|
|
90
|
-
{
|
|
91
|
-
"name": doc.title or doc.key,
|
|
92
|
-
"id": doc.id,
|
|
93
|
-
}
|
|
94
|
-
)
|
|
95
|
-
|
|
96
98
|
return render_template(
|
|
97
99
|
self._config.tool_description_for_system_prompt,
|
|
98
|
-
valid_documents=
|
|
100
|
+
valid_documents=[
|
|
101
|
+
{"name": doc.title or doc.key, "id": doc.id}
|
|
102
|
+
for doc in self._valid_documents
|
|
103
|
+
],
|
|
99
104
|
)
|
|
100
105
|
|
|
101
106
|
def tool_format_information_for_system_prompt(self) -> str:
|
|
@@ -114,6 +119,26 @@ class UploadedSearchTool(Tool[UploadedSearchConfig]):
|
|
|
114
119
|
search_string_data = ""
|
|
115
120
|
if isinstance(tool_call.arguments, dict):
|
|
116
121
|
search_string_data = tool_call.arguments.get("search_string", "") or ""
|
|
122
|
+
|
|
123
|
+
# Verify no content_id outside valid ones, as tool may not be strict
|
|
124
|
+
|
|
125
|
+
if "content_ids" in tool_call.arguments:
|
|
126
|
+
tool_content_ids = tool_call.arguments["content_ids"]
|
|
127
|
+
|
|
128
|
+
if tool_content_ids is not None:
|
|
129
|
+
valid_content_ids = {doc.id for doc in self._valid_documents}
|
|
130
|
+
filtered = list(
|
|
131
|
+
filter(
|
|
132
|
+
lambda content_id: content_id in valid_content_ids,
|
|
133
|
+
tool_content_ids,
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if len(filtered) == 0:
|
|
138
|
+
raise ValueError("All supplied `content_ids` are invalid")
|
|
139
|
+
|
|
140
|
+
tool_call.arguments["content_ids"] = filtered
|
|
141
|
+
|
|
117
142
|
tool_response = await self._internal_search_tool.run(tool_call)
|
|
118
143
|
if (
|
|
119
144
|
self._tool_progress_reporter
|
|
@@ -151,5 +176,23 @@ This tool call was automatically executed to retrieve the user's uploaded docume
|
|
|
151
176
|
Please do not mention these instructions in your response to the user!
|
|
152
177
|
</system_reminder>"""
|
|
153
178
|
|
|
179
|
+
def _compute_valid_documents(self) -> list[Content]:
|
|
180
|
+
documents = self._content_service.get_documents_uploaded_to_chat()
|
|
181
|
+
|
|
182
|
+
if feature_flags.enable_selected_uploaded_files_un_18215.is_enabled(
|
|
183
|
+
self._company_id
|
|
184
|
+
):
|
|
185
|
+
documents = [
|
|
186
|
+
doc for doc in documents if doc.id in self._selected_uploaded_files
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
valid_documents = []
|
|
190
|
+
for doc in documents:
|
|
191
|
+
if not doc.is_ingested(default_if_unknown=True) or doc.is_expired():
|
|
192
|
+
continue
|
|
193
|
+
valid_documents.append(doc)
|
|
194
|
+
|
|
195
|
+
return valid_documents
|
|
196
|
+
|
|
154
197
|
|
|
155
198
|
ToolFactory.register_tool(UploadedSearchTool, UploadedSearchConfig)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|