unique_internal_search 2026.32.0.dev0__tar.gz → 2026.32.0.dev1__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.
Files changed (15) hide show
  1. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/PKG-INFO +2 -2
  2. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/pyproject.toml +2 -2
  3. unique_internal_search-2026.32.0.dev1/unique_internal_search/invocation_stats.py +33 -0
  4. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/unique_internal_search/service.py +34 -0
  5. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/README.md +0 -0
  6. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/unique_internal_search/__init__.py +0 -0
  7. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/unique_internal_search/config.py +0 -0
  8. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/unique_internal_search/prompts.py +0 -0
  9. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/unique_internal_search/services/message_log.py +0 -0
  10. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/unique_internal_search/uploaded_search/config.py +0 -0
  11. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/unique_internal_search/uploaded_search/prompts/__init__.py +0 -0
  12. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/unique_internal_search/uploaded_search/prompts/tool_description_for_system_prompt.j2 +0 -0
  13. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/unique_internal_search/uploaded_search/service.py +0 -0
  14. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/unique_internal_search/utils.py +0 -0
  15. {unique_internal_search-2026.32.0.dev0 → unique_internal_search-2026.32.0.dev1}/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.dev0
3
+ Version: 2026.32.0.dev1
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.dev0,<2026.32.0rc0
10
+ Requires-Dist: unique-toolkit>=2026.32.0.dev4,<2026.32.0rc0
11
11
  Requires-Python: >=3.12, <4
12
12
  Description-Content-Type: text/markdown
13
13
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_internal_search"
3
- version = "2026.32.0.dev0"
3
+ version = "2026.32.0.dev1"
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.dev0,<2026.32.0rc0",
17
+ "unique-toolkit>=2026.32.0.dev4,<2026.32.0rc0",
18
18
  ]
19
19
 
20
20
  [build-system]
@@ -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,
@@ -311,6 +315,13 @@ class InternalSearchService:
311
315
  chunks=found_chunks,
312
316
  config=self.config.chunk_relevancy_sort_config,
313
317
  )
318
+ if isinstance(chunk_relevancy_sorter_result.relevancies, list):
319
+ record_invocation_stats(
320
+ invocation
321
+ for relevancy in chunk_relevancy_sorter_result.relevancies
322
+ if relevancy.relevancy is not None
323
+ for invocation in relevancy.relevancy.invocation_stats
324
+ )
314
325
  found_chunks = chunk_relevancy_sorter_result.content_chunks
315
326
  except ChunkRelevancySorterException as e:
316
327
  self.logger.warning(f"Error while sorting chunks: {e.error_message}")
@@ -474,6 +485,29 @@ class InternalSearchTool(Tool[InternalSearchConfig], InternalSearchService):
474
485
  # TODO: find a solution for tracking
475
486
  # @track(name="internal_search_tool_run")
476
487
  async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
488
+ with invocation_stats_scope() as invocation_stats:
489
+ try:
490
+ response = await self._run(tool_call)
491
+ except Exception as e:
492
+ # _run() has no top-level try/except of its own, so any error
493
+ # after self.search() (which may already have spent relevancy-
494
+ # sorting tokens) would otherwise escape straight to
495
+ # SafeTaskExecutor, which builds a fresh, stats-less error
496
+ # response one level up -- silently dropping tokens already
497
+ # spent before the failure.
498
+ # logger.exception() already attaches the active exception's
499
+ # type and traceback; don't stringify it into the message.
500
+ self.logger.exception("InternalSearch tool run failed")
501
+ return ToolCallResponse(
502
+ id=tool_call.id, # type: ignore
503
+ name=self.name,
504
+ error_message=str(e),
505
+ invocation_stats=invocation_stats,
506
+ )
507
+ response.invocation_stats.extend(invocation_stats)
508
+ return response
509
+
510
+ async def _run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
477
511
  """
478
512
  Perform a search in the Vector DB based on the user's message and generate a response.
479
513
  """