unique_internal_search 2026.32.0.dev0__tar.gz → 2026.32.0.dev2__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.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/PKG-INFO +2 -2
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/pyproject.toml +2 -2
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/unique_internal_search/config.py +3 -12
- unique_internal_search-2026.32.0.dev2/unique_internal_search/invocation_stats.py +33 -0
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/unique_internal_search/service.py +46 -4
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/README.md +0 -0
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/unique_internal_search/__init__.py +0 -0
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/unique_internal_search/prompts.py +0 -0
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/unique_internal_search/services/message_log.py +0 -0
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/unique_internal_search/uploaded_search/config.py +0 -0
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/unique_internal_search/uploaded_search/prompts/__init__.py +0 -0
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/unique_internal_search/uploaded_search/prompts/tool_description_for_system_prompt.j2 +0 -0
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/unique_internal_search/uploaded_search/service.py +0 -0
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/unique_internal_search/utils.py +0 -0
- {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/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.32.0.
|
|
3
|
+
Version: 2026.32.0.dev2
|
|
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.32.0.
|
|
10
|
+
Requires-Dist: unique-toolkit>=2026.32.0.dev8,<2026.32.0rc0
|
|
11
11
|
Requires-Python: >=3.12, <4
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
|
{unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev2}/pyproject.toml
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique_internal_search"
|
|
3
|
-
version = "2026.32.0.
|
|
3
|
+
version = "2026.32.0.dev2"
|
|
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.32.0.
|
|
17
|
+
"unique-toolkit>=2026.32.0.dev8,<2026.32.0rc0",
|
|
18
18
|
]
|
|
19
19
|
|
|
20
20
|
[build-system]
|
|
@@ -33,8 +33,7 @@ from unique_internal_search.prompts import (
|
|
|
33
33
|
)
|
|
34
34
|
from unique_internal_search.validators import get_string_field_with_pattern_validation
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
DEFAULT_LIMIT_CHUNK_RELEVANCY_SORT_DISABLED = 1000
|
|
36
|
+
DEFAULT_SEARCH_LIMIT = 200
|
|
38
37
|
|
|
39
38
|
|
|
40
39
|
class ToolResponseSystemReminderConfig(BaseModel):
|
|
@@ -72,14 +71,6 @@ class ExperimentalFeatures(FeatureExtendedSourceSerialization):
|
|
|
72
71
|
)
|
|
73
72
|
|
|
74
73
|
|
|
75
|
-
def _search_limit_factory(data: dict[str, Any]) -> int:
|
|
76
|
-
return (
|
|
77
|
-
DEFAULT_LIMIT_CHUNK_RELEVANCY_SORT_ENABLED
|
|
78
|
-
if data["chunk_relevancy_sort_config"].enabled
|
|
79
|
-
else DEFAULT_LIMIT_CHUNK_RELEVANCY_SORT_DISABLED
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
74
|
_FIELD_ALIASES: dict[str, str] = {
|
|
84
75
|
"ftsSearchLanguage": "searchLanguage",
|
|
85
76
|
}
|
|
@@ -145,8 +136,8 @@ class InternalSearchConfig(BaseToolConfig):
|
|
|
145
136
|
description="The chunk relevancy sort config to use for the search.",
|
|
146
137
|
)
|
|
147
138
|
limit: int = Field(
|
|
148
|
-
|
|
149
|
-
description="
|
|
139
|
+
default=DEFAULT_SEARCH_LIMIT,
|
|
140
|
+
description="Maximum number of chunks returned in total across all search strings, capped by the token budget. Also used as the per-search-string fetch ceiling.",
|
|
150
141
|
)
|
|
151
142
|
chat_only: bool = Field(
|
|
152
143
|
default=False,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Run-scoped collection of language-model invocation usage."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Generator, Iterable
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from contextvars import ContextVar, Token
|
|
6
|
+
|
|
7
|
+
from unique_toolkit.language_model.invocation_stats import LanguageModelInvocationStats
|
|
8
|
+
|
|
9
|
+
_CURRENT_INVOCATION_STATS: ContextVar[list[LanguageModelInvocationStats] | None] = (
|
|
10
|
+
ContextVar("internal_search_invocation_stats", default=None)
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@contextmanager
|
|
15
|
+
def invocation_stats_scope() -> Generator[list[LanguageModelInvocationStats]]:
|
|
16
|
+
"""Collect stats for one internal-search run and isolate concurrent runs."""
|
|
17
|
+
invocation_stats: list[LanguageModelInvocationStats] = []
|
|
18
|
+
token: Token[list[LanguageModelInvocationStats] | None] = (
|
|
19
|
+
_CURRENT_INVOCATION_STATS.set(invocation_stats)
|
|
20
|
+
)
|
|
21
|
+
try:
|
|
22
|
+
yield invocation_stats
|
|
23
|
+
finally:
|
|
24
|
+
_CURRENT_INVOCATION_STATS.reset(token)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def record_invocation_stats(
|
|
28
|
+
invocation_stats: Iterable[LanguageModelInvocationStats],
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Record usage when an internal-search run scope is active."""
|
|
31
|
+
current_invocation_stats = _CURRENT_INVOCATION_STATS.get()
|
|
32
|
+
if current_invocation_stats is not None:
|
|
33
|
+
current_invocation_stats.extend(invocation_stats)
|
|
@@ -37,6 +37,10 @@ from unique_toolkit.language_model.schemas import (
|
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
from unique_internal_search.config import InternalSearchConfig
|
|
40
|
+
from unique_internal_search.invocation_stats import (
|
|
41
|
+
invocation_stats_scope,
|
|
42
|
+
record_invocation_stats,
|
|
43
|
+
)
|
|
40
44
|
from unique_internal_search.services.message_log import (
|
|
41
45
|
InternalSearchMessageLogger,
|
|
42
46
|
InternalSearchMessageLoggerNoop,
|
|
@@ -224,6 +228,13 @@ class InternalSearchService:
|
|
|
224
228
|
for result in found_chunks_per_search_string
|
|
225
229
|
for chunk in result.chunks
|
|
226
230
|
]
|
|
231
|
+
if len(found_chunks) > self.config.limit:
|
|
232
|
+
self.logger.info(
|
|
233
|
+
"Truncating %s chunks to the configured total limit of %s",
|
|
234
|
+
len(found_chunks),
|
|
235
|
+
self.config.limit,
|
|
236
|
+
)
|
|
237
|
+
found_chunks = found_chunks[: self.config.limit]
|
|
227
238
|
selected_chunks = pick_content_chunks_for_token_window(
|
|
228
239
|
found_chunks,
|
|
229
240
|
self._get_max_tokens(),
|
|
@@ -255,13 +266,12 @@ class InternalSearchService:
|
|
|
255
266
|
content_ids: list[str] | None = None,
|
|
256
267
|
) -> SearchStringResult:
|
|
257
268
|
try:
|
|
258
|
-
capped_limit = self._cap_limit_to_token_budget()
|
|
259
269
|
found_chunks: list[
|
|
260
270
|
ContentChunk
|
|
261
271
|
] = await self.content_service.search_content_chunks_async(
|
|
262
272
|
search_string=search_string, # type: ignore
|
|
263
273
|
search_type=self.config.search_type,
|
|
264
|
-
limit=
|
|
274
|
+
limit=self._cap_limit_to_token_budget(),
|
|
265
275
|
reranker_config=self.config.reranker_config,
|
|
266
276
|
search_language=self.config.search_language,
|
|
267
277
|
scope_ids=self.config.scope_ids,
|
|
@@ -311,6 +321,13 @@ class InternalSearchService:
|
|
|
311
321
|
chunks=found_chunks,
|
|
312
322
|
config=self.config.chunk_relevancy_sort_config,
|
|
313
323
|
)
|
|
324
|
+
if isinstance(chunk_relevancy_sorter_result.relevancies, list):
|
|
325
|
+
record_invocation_stats(
|
|
326
|
+
invocation
|
|
327
|
+
for relevancy in chunk_relevancy_sorter_result.relevancies
|
|
328
|
+
if relevancy.relevancy is not None
|
|
329
|
+
for invocation in relevancy.relevancy.invocation_stats
|
|
330
|
+
)
|
|
314
331
|
found_chunks = chunk_relevancy_sorter_result.content_chunks
|
|
315
332
|
except ChunkRelevancySorterException as e:
|
|
316
333
|
self.logger.warning(f"Error while sorting chunks: {e.error_message}")
|
|
@@ -348,10 +365,12 @@ class InternalSearchService:
|
|
|
348
365
|
capped_limit = min(self.config.limit, token_based_limit)
|
|
349
366
|
if capped_limit < self.config.limit:
|
|
350
367
|
self.logger.info(
|
|
351
|
-
|
|
368
|
+
"Search limit capped from %s to %s (token budget)",
|
|
369
|
+
self.config.limit,
|
|
370
|
+
capped_limit,
|
|
352
371
|
)
|
|
353
372
|
else:
|
|
354
|
-
self.logger.info(
|
|
373
|
+
self.logger.info("Search limit: %s (within token budget)", capped_limit)
|
|
355
374
|
return capped_limit
|
|
356
375
|
|
|
357
376
|
|
|
@@ -474,6 +493,29 @@ class InternalSearchTool(Tool[InternalSearchConfig], InternalSearchService):
|
|
|
474
493
|
# TODO: find a solution for tracking
|
|
475
494
|
# @track(name="internal_search_tool_run")
|
|
476
495
|
async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
|
|
496
|
+
with invocation_stats_scope() as invocation_stats:
|
|
497
|
+
try:
|
|
498
|
+
response = await self._run(tool_call)
|
|
499
|
+
except Exception as e:
|
|
500
|
+
# _run() has no top-level try/except of its own, so any error
|
|
501
|
+
# after self.search() (which may already have spent relevancy-
|
|
502
|
+
# sorting tokens) would otherwise escape straight to
|
|
503
|
+
# SafeTaskExecutor, which builds a fresh, stats-less error
|
|
504
|
+
# response one level up -- silently dropping tokens already
|
|
505
|
+
# spent before the failure.
|
|
506
|
+
# logger.exception() already attaches the active exception's
|
|
507
|
+
# type and traceback; don't stringify it into the message.
|
|
508
|
+
self.logger.exception("InternalSearch tool run failed")
|
|
509
|
+
return ToolCallResponse(
|
|
510
|
+
id=tool_call.id, # type: ignore
|
|
511
|
+
name=self.name,
|
|
512
|
+
error_message=str(e),
|
|
513
|
+
invocation_stats=invocation_stats,
|
|
514
|
+
)
|
|
515
|
+
response.invocation_stats.extend(invocation_stats)
|
|
516
|
+
return response
|
|
517
|
+
|
|
518
|
+
async def _run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
|
|
477
519
|
"""
|
|
478
520
|
Perform a search in the Vector DB based on the user's message and generate a response.
|
|
479
521
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|