unique_deep_research 2026.36.0.dev0__tar.gz → 2026.36.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.
Files changed (23) hide show
  1. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/PKG-INFO +3 -3
  2. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/pyproject.toml +3 -3
  3. unique_deep_research-2026.36.0.dev2/unique_deep_research/invocation_stats.py +5 -0
  4. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/service.py +9 -16
  5. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/unique_custom/utils.py +2 -2
  6. unique_deep_research-2026.36.0.dev0/unique_deep_research/invocation_stats.py +0 -84
  7. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/README.md +0 -0
  8. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/__init__.py +0 -0
  9. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/config.py +0 -0
  10. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/markdown_utils.py +0 -0
  11. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/templates/clarifying_agent.j2 +0 -0
  12. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/templates/openai/oai_research_system_message.j2 +0 -0
  13. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/templates/report_cleanup_prompt.j2 +0 -0
  14. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/templates/research_instructions_agent.j2 +0 -0
  15. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/templates/unique/compress_research_system.j2 +0 -0
  16. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/templates/unique/lead_agent_system.j2 +0 -0
  17. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/templates/unique/report_writer_system_open_deep_research.j2 +0 -0
  18. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/templates/unique/research_agent_system.j2 +0 -0
  19. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/unique_custom/__init__.py +0 -0
  20. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/unique_custom/agents.py +0 -0
  21. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/unique_custom/citation.py +0 -0
  22. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/unique_custom/state.py +0 -0
  23. {unique_deep_research-2026.36.0.dev0 → unique_deep_research-2026.36.0.dev2}/unique_deep_research/unique_custom/tools.py +0 -0
@@ -1,18 +1,18 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-deep-research
3
- Version: 2026.36.0.dev0
3
+ Version: 2026.36.0.dev2
4
4
  Summary: Deep Research Tool for complex research tasks
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.36.0.dev0,<2026.36.0rc0
10
+ Requires-Dist: unique-toolkit>=2026.36.0.dev6,<2026.36.0rc0
11
11
  Requires-Dist: jinja2>=3.1.2,<4
12
12
  Requires-Dist: openai>=1.99.0,<3
13
13
  Requires-Dist: langgraph>=1.0.0,<2
14
14
  Requires-Dist: langchain-core>=1.0.0,<2
15
- Requires-Dist: unique-web-search>=2026.36.0.dev0,<2026.36.0rc0
15
+ Requires-Dist: unique-web-search>=2026.36.0.dev3,<2026.36.0rc0
16
16
  Requires-Dist: httpx>=0.28.0,<0.29
17
17
  Requires-Dist: timeout-decorator>=0.5.0,<0.6
18
18
  Requires-Dist: beautifulsoup4>=4.12.0,<5
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_deep_research"
3
- version = "2026.36.0.dev0"
3
+ version = "2026.36.0.dev2"
4
4
  description = "Deep Research Tool for complex research tasks"
5
5
  readme = "README.md"
6
6
  license = { text = "Proprietary" }
@@ -14,12 +14,12 @@ 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.36.0.dev0,<2026.36.0rc0",
17
+ "unique-toolkit>=2026.36.0.dev6,<2026.36.0rc0",
18
18
  "jinja2>=3.1.2,<4",
19
19
  "openai>=1.99.0,<3",
20
20
  "langgraph>=1.0.0,<2",
21
21
  "langchain-core>=1.0.0,<2",
22
- "unique-web-search>=2026.36.0.dev0,<2026.36.0rc0",
22
+ "unique-web-search>=2026.36.0.dev3,<2026.36.0rc0",
23
23
  "httpx>=0.28.0,<0.29",
24
24
  "timeout-decorator>=0.5.0,<0.6",
25
25
  "beautifulsoup4>=4.12.0,<5",
@@ -0,0 +1,5 @@
1
+ """Run-scoped collection of Deep Research language-model usage."""
2
+
3
+ from unique_toolkit.language_model.invocation_stats import InvocationStatsCollector
4
+
5
+ collector = InvocationStatsCollector("deep_research")
@@ -50,9 +50,7 @@ from unique_toolkit.language_model.schemas import (
50
50
  LanguageModelMessageRole,
51
51
  )
52
52
  from unique_toolkit.short_term_memory.service import ShortTermMemoryService
53
- from unique_web_search.invocation_stats import (
54
- invocation_stats_scope as web_search_invocation_stats_scope,
55
- )
53
+ from unique_web_search.invocation_stats import collector as web_search_collector
56
54
 
57
55
  from unique_deep_research.unique_custom.tools import crawl_url, get_today_str
58
56
 
@@ -63,12 +61,7 @@ from .config import (
63
61
  DeepResearchToolConfig,
64
62
  UniqueEngine,
65
63
  )
66
- from .invocation_stats import (
67
- invocation_stats_scope,
68
- record_invocation_stats,
69
- record_language_model_response,
70
- record_token_usage,
71
- )
64
+ from .invocation_stats import collector
72
65
  from .markdown_utils import (
73
66
  export_report_to_docx,
74
67
  postprocess_research_result_with_chunks,
@@ -270,7 +263,7 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
270
263
  )
271
264
 
272
265
  async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
273
- with invocation_stats_scope() as invocation_stats:
266
+ with collector.scope() as invocation_stats:
274
267
  sub = self.chat_service.cancellation.on_cancellation.subscribe(
275
268
  self._on_cancellation
276
269
  )
@@ -555,7 +548,7 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
555
548
  },
556
549
  }
557
550
 
558
- with web_search_invocation_stats_scope() as web_search_invocation_stats:
551
+ with web_search_collector.scope() as web_search_invocation_stats:
559
552
  try:
560
553
  result = await self.chat_service.cancellation.run_with_cancellation(
561
554
  custom_agent.ainvoke(initial_state, config=config), # type: ignore[arg-type]
@@ -566,7 +559,7 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
566
559
  # calls happen during ainvoke(); merge it into the outer Deep Research
567
560
  # scope even if the graph raises, so tokens already spent before the
568
561
  # failure aren't dropped from the failure ToolCallResponse.
569
- record_invocation_stats(web_search_invocation_stats)
562
+ collector.record_invocation_stats(web_search_invocation_stats)
570
563
 
571
564
  research_result = result.get("final_report", "")
572
565
 
@@ -691,7 +684,7 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
691
684
  _LOGGER.info(
692
685
  f"OpenAI research token usage: {event.response.usage}"
693
686
  )
694
- record_token_usage(
687
+ collector.record_token_usage(
695
688
  model_name=self.config.engine.research_model.name,
696
689
  usage=event.response.usage,
697
690
  source="deep_research.openai_research",
@@ -872,7 +865,7 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
872
865
  temperature=0.1,
873
866
  max_tokens=5000,
874
867
  )
875
- record_language_model_response(
868
+ collector.record_language_model_response(
876
869
  model_name=self.config.engine.large_model.name,
877
870
  response=response,
878
871
  source="deep_research.report_cleanup",
@@ -937,7 +930,7 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
937
930
  model_name=self.config.engine.small_model.name,
938
931
  content_chunks=None,
939
932
  )
940
- record_language_model_response(
933
+ collector.record_language_model_response(
941
934
  model_name=self.config.engine.small_model.name,
942
935
  response=response,
943
936
  source="deep_research.clarification",
@@ -969,7 +962,7 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
969
962
  model=self.config.engine.large_model.name,
970
963
  messages=chat_messages,
971
964
  )
972
- record_language_model_response(
965
+ collector.record_language_model_response(
973
966
  model_name=self.config.engine.large_model.name,
974
967
  response=research_response,
975
968
  source="deep_research.research_brief",
@@ -29,7 +29,7 @@ from unique_toolkit.content.service import ContentService
29
29
  from unique_toolkit.language_model.infos import LanguageModelInfo
30
30
 
31
31
  from ..config import UniqueEngine
32
- from ..invocation_stats import record_language_model_response
32
+ from ..invocation_stats import collector
33
33
  from .citation import GlobalCitationManager
34
34
  from .state import AgentState, ResearcherState, SupervisorState
35
35
 
@@ -586,7 +586,7 @@ async def ainvoke_with_token_handling(
586
586
  )
587
587
  raise e
588
588
  else:
589
- record_language_model_response(
589
+ collector.record_language_model_response(
590
590
  model_name=model_info.name,
591
591
  response=response,
592
592
  source=source,
@@ -1,84 +0,0 @@
1
- """Run-scoped collection of Deep Research language-model usage."""
2
-
3
- import logging
4
- from collections.abc import Iterator
5
- from contextlib import contextmanager
6
- from contextvars import ContextVar, Token
7
- from typing import Any
8
-
9
- from unique_toolkit.language_model.invocation_stats import LanguageModelInvocationStats
10
- from unique_toolkit.language_model.schemas import LanguageModelTokenUsage
11
-
12
- _LOGGER = logging.getLogger(__name__)
13
-
14
- _CURRENT_INVOCATION_STATS: ContextVar[list[LanguageModelInvocationStats] | None] = (
15
- ContextVar("deep_research_invocation_stats", default=None)
16
- )
17
-
18
-
19
- @contextmanager
20
- def invocation_stats_scope() -> Iterator[list[LanguageModelInvocationStats]]:
21
- """Collect stats for one Deep Research run and isolate concurrent runs."""
22
- invocation_stats: list[LanguageModelInvocationStats] = []
23
- token: Token[list[LanguageModelInvocationStats] | None] = (
24
- _CURRENT_INVOCATION_STATS.set(invocation_stats)
25
- )
26
- try:
27
- yield invocation_stats
28
- finally:
29
- _CURRENT_INVOCATION_STATS.reset(token)
30
-
31
-
32
- def record_token_usage(
33
- *,
34
- model_name: str,
35
- usage: Any,
36
- source: str,
37
- ) -> None:
38
- """Record provider usage when both a run scope and usage are available."""
39
- invocation_stats = _CURRENT_INVOCATION_STATS.get()
40
- if invocation_stats is None or usage is None:
41
- return
42
-
43
- try:
44
- token_usage = LanguageModelTokenUsage.model_validate(usage)
45
- invocation_stats.append(
46
- LanguageModelInvocationStats.from_usage(
47
- model_name=model_name,
48
- token_usage=token_usage,
49
- source=source,
50
- )
51
- )
52
- except Exception:
53
- _LOGGER.warning(
54
- "Unable to parse Deep Research token usage for %s",
55
- source,
56
- exc_info=True,
57
- )
58
-
59
-
60
- def record_invocation_stats(
61
- invocations: list[LanguageModelInvocationStats],
62
- ) -> None:
63
- """Add invocation stats collected by a nested Deep Research dependency."""
64
- invocation_stats = _CURRENT_INVOCATION_STATS.get()
65
- if invocation_stats is not None:
66
- invocation_stats.extend(invocations)
67
-
68
-
69
- def record_language_model_response(
70
- *,
71
- model_name: str,
72
- response: Any,
73
- source: str,
74
- ) -> None:
75
- """Record usage from OpenAI SDK, toolkit, or LangChain responses."""
76
- usage = getattr(response, "usage", None)
77
- if usage is None:
78
- usage = getattr(response, "usage_metadata", None)
79
- if usage is None:
80
- response_metadata = getattr(response, "response_metadata", None)
81
- if isinstance(response_metadata, dict):
82
- usage = response_metadata.get("token_usage")
83
-
84
- record_token_usage(model_name=model_name, usage=usage, source=source)