unique_deep_research 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.
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/PKG-INFO +3 -3
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/pyproject.toml +3 -3
- unique_deep_research-2026.32.0.dev1/unique_deep_research/invocation_stats.py +84 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/service.py +115 -38
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/unique_custom/agents.py +20 -5
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/unique_custom/utils.py +12 -1
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/README.md +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/__init__.py +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/config.py +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/markdown_utils.py +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/templates/clarifying_agent.j2 +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/templates/openai/oai_research_system_message.j2 +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/templates/report_cleanup_prompt.j2 +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/templates/research_instructions_agent.j2 +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/templates/unique/compress_research_system.j2 +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/templates/unique/lead_agent_system.j2 +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/templates/unique/report_writer_system_open_deep_research.j2 +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/templates/unique/research_agent_system.j2 +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/unique_custom/__init__.py +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/unique_custom/citation.py +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/unique_deep_research/unique_custom/state.py +0 -0
- {unique_deep_research-2026.32.0.dev0 → unique_deep_research-2026.32.0.dev1}/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.32.0.
|
|
3
|
+
Version: 2026.32.0.dev1
|
|
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.32.0.
|
|
10
|
+
Requires-Dist: unique-toolkit>=2026.32.0.dev4,<2026.32.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.32.0.
|
|
15
|
+
Requires-Dist: unique-web-search>=2026.32.0.dev1,<2026.32.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.32.0.
|
|
3
|
+
version = "2026.32.0.dev1"
|
|
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.32.0.
|
|
17
|
+
"unique-toolkit>=2026.32.0.dev4,<2026.32.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.32.0.
|
|
22
|
+
"unique-web-search>=2026.32.0.dev1,<2026.32.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,84 @@
|
|
|
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)
|
|
@@ -50,6 +50,9 @@ 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
56
|
|
|
54
57
|
from unique_deep_research.unique_custom.tools import crawl_url, get_today_str
|
|
55
58
|
|
|
@@ -60,6 +63,12 @@ from .config import (
|
|
|
60
63
|
DeepResearchToolConfig,
|
|
61
64
|
UniqueEngine,
|
|
62
65
|
)
|
|
66
|
+
from .invocation_stats import (
|
|
67
|
+
invocation_stats_scope,
|
|
68
|
+
record_invocation_stats,
|
|
69
|
+
record_language_model_response,
|
|
70
|
+
record_token_usage,
|
|
71
|
+
)
|
|
63
72
|
from .markdown_utils import (
|
|
64
73
|
export_report_to_docx,
|
|
65
74
|
postprocess_research_result_with_chunks,
|
|
@@ -261,39 +270,65 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
|
|
|
261
270
|
)
|
|
262
271
|
|
|
263
272
|
async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
|
|
264
|
-
|
|
265
|
-
self.
|
|
266
|
-
|
|
267
|
-
try:
|
|
268
|
-
return await self._run(tool_call)
|
|
269
|
-
except Exception as e:
|
|
270
|
-
self.write_message_log_text_message(
|
|
271
|
-
"**Research failed for an unknown reason**"
|
|
273
|
+
with invocation_stats_scope() as invocation_stats:
|
|
274
|
+
sub = self.chat_service.cancellation.on_cancellation.subscribe(
|
|
275
|
+
self._on_cancellation
|
|
272
276
|
)
|
|
273
|
-
|
|
274
|
-
await self.
|
|
277
|
+
try:
|
|
278
|
+
response = await self._run(tool_call)
|
|
279
|
+
response.invocation_stats.extend(invocation_stats)
|
|
280
|
+
return response
|
|
281
|
+
except Exception as e:
|
|
282
|
+
_LOGGER.exception("Deep Research tool run failed")
|
|
283
|
+
|
|
284
|
+
# The failure-handling below issues several awaits (status
|
|
285
|
+
# update, debugInfo read/write, message edit); if any of them
|
|
286
|
+
# raises it must not escape and take the tokens already spent
|
|
287
|
+
# this run down with it -- the finally block still returns a
|
|
288
|
+
# stats-bearing error response so analytics stay accurate.
|
|
289
|
+
try:
|
|
290
|
+
self.write_message_log_text_message(
|
|
291
|
+
"**Research failed for an unknown reason**"
|
|
292
|
+
)
|
|
293
|
+
if self.is_message_execution():
|
|
294
|
+
await self._update_execution_status(
|
|
295
|
+
MessageExecutionUpdateStatus.FAILED
|
|
296
|
+
)
|
|
275
297
|
|
|
276
|
-
|
|
298
|
+
# update_debug_info_async replaces the message's debugInfo
|
|
299
|
+
# wholesale (see ChatService.update_debug_info_async /
|
|
300
|
+
# replace_debug_info), so the existing debugInfo must be read
|
|
301
|
+
# and merged in first -- otherwise this write wipes out
|
|
302
|
+
# `llm_invocations` persisted from an earlier turn of this same
|
|
303
|
+
# research (e.g. the clarification round), and UniqueAI's later
|
|
304
|
+
# merge (unique_ai.py) reads back an empty prior list.
|
|
305
|
+
existing_debug_info = await self.chat_service.get_debug_info_async()
|
|
306
|
+
debug_info = {
|
|
307
|
+
**existing_debug_info,
|
|
308
|
+
**self._get_tool_debug_info(),
|
|
309
|
+
"error": str(e),
|
|
310
|
+
"traceback": traceback.format_exc(),
|
|
311
|
+
}
|
|
312
|
+
await self.chat_service.update_debug_info_async(debug_info)
|
|
313
|
+
await self.chat_service.modify_assistant_message_async(
|
|
314
|
+
content="Deep Research failed to complete for an unknown reason",
|
|
315
|
+
set_completed_at=True,
|
|
316
|
+
)
|
|
317
|
+
except Exception:
|
|
318
|
+
_LOGGER.exception(
|
|
319
|
+
"Failed to persist Deep Research failure state; "
|
|
320
|
+
"returning collected invocation stats anyway"
|
|
321
|
+
)
|
|
322
|
+
finally:
|
|
323
|
+
sub.cancel()
|
|
277
324
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
"
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
await self.chat_service.modify_assistant_message_async(
|
|
285
|
-
content="Deep Research failed to complete for an unknown reason",
|
|
286
|
-
set_completed_at=True,
|
|
325
|
+
return ToolCallResponse(
|
|
326
|
+
id=tool_call.id or "",
|
|
327
|
+
name=self.name,
|
|
328
|
+
content="Failed to complete research",
|
|
329
|
+
error_message="Research process failed or returned empty results",
|
|
330
|
+
invocation_stats=invocation_stats,
|
|
287
331
|
)
|
|
288
|
-
finally:
|
|
289
|
-
sub.cancel()
|
|
290
|
-
|
|
291
|
-
return ToolCallResponse(
|
|
292
|
-
id=tool_call.id or "",
|
|
293
|
-
name=self.name,
|
|
294
|
-
content="Failed to complete research",
|
|
295
|
-
error_message="Research process failed or returned empty results",
|
|
296
|
-
)
|
|
297
332
|
|
|
298
333
|
async def _run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
|
|
299
334
|
_LOGGER.info("Starting Deep Research tool run")
|
|
@@ -337,11 +372,25 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
|
|
|
337
372
|
if processed_result == "":
|
|
338
373
|
raise ValueError("Research returned empty result")
|
|
339
374
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
|
|
375
|
+
# The real report is already persisted by run_research() above; these
|
|
376
|
+
# two calls are trailing bookkeeping (completed-flag, execution status).
|
|
377
|
+
# If either raises, run()'s except handler would otherwise treat the
|
|
378
|
+
# whole turn as failed and overwrite the already-correct report with a
|
|
379
|
+
# generic failure message -- swallow here instead so a bookkeeping
|
|
380
|
+
# hiccup can't destroy a successful research result.
|
|
381
|
+
try:
|
|
382
|
+
await self.chat_service.modify_assistant_message_async(
|
|
383
|
+
set_completed_at=True,
|
|
384
|
+
)
|
|
385
|
+
await self._update_execution_status(
|
|
386
|
+
MessageExecutionUpdateStatus.COMPLETED
|
|
387
|
+
)
|
|
388
|
+
except Exception:
|
|
389
|
+
_LOGGER.warning(
|
|
390
|
+
"Failed to finalize Deep Research completion bookkeeping "
|
|
391
|
+
"after a successful research result",
|
|
392
|
+
exc_info=True,
|
|
393
|
+
)
|
|
345
394
|
|
|
346
395
|
return ToolCallResponse(
|
|
347
396
|
id=tool_call.id or "",
|
|
@@ -506,10 +555,18 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
|
|
|
506
555
|
},
|
|
507
556
|
}
|
|
508
557
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
558
|
+
with web_search_invocation_stats_scope() as web_search_invocation_stats:
|
|
559
|
+
try:
|
|
560
|
+
result = await self.chat_service.cancellation.run_with_cancellation(
|
|
561
|
+
custom_agent.ainvoke(initial_state, config=config), # type: ignore[arg-type]
|
|
562
|
+
cancel_result={"final_report": ""},
|
|
563
|
+
)
|
|
564
|
+
finally:
|
|
565
|
+
# web_search_invocation_stats is populated live as nested web-search
|
|
566
|
+
# calls happen during ainvoke(); merge it into the outer Deep Research
|
|
567
|
+
# scope even if the graph raises, so tokens already spent before the
|
|
568
|
+
# failure aren't dropped from the failure ToolCallResponse.
|
|
569
|
+
record_invocation_stats(web_search_invocation_stats)
|
|
513
570
|
|
|
514
571
|
research_result = result.get("final_report", "")
|
|
515
572
|
|
|
@@ -634,6 +691,11 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
|
|
|
634
691
|
_LOGGER.info(
|
|
635
692
|
f"OpenAI research token usage: {event.response.usage}"
|
|
636
693
|
)
|
|
694
|
+
record_token_usage(
|
|
695
|
+
model_name=self.config.engine.research_model.name,
|
|
696
|
+
usage=event.response.usage,
|
|
697
|
+
source="deep_research.openai_research",
|
|
698
|
+
)
|
|
637
699
|
# Extract the final output with annotations
|
|
638
700
|
if event.response.output and len(event.response.output) > 0:
|
|
639
701
|
final_output = event.response.output[-1]
|
|
@@ -810,6 +872,11 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
|
|
|
810
872
|
temperature=0.1,
|
|
811
873
|
max_tokens=5000,
|
|
812
874
|
)
|
|
875
|
+
record_language_model_response(
|
|
876
|
+
model_name=self.config.engine.large_model.name,
|
|
877
|
+
response=response,
|
|
878
|
+
source="deep_research.report_cleanup",
|
|
879
|
+
)
|
|
813
880
|
|
|
814
881
|
formatted_result = response.choices[0].message.content
|
|
815
882
|
if formatted_result:
|
|
@@ -870,6 +937,11 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
|
|
|
870
937
|
model_name=self.config.engine.small_model.name,
|
|
871
938
|
content_chunks=None,
|
|
872
939
|
)
|
|
940
|
+
record_language_model_response(
|
|
941
|
+
model_name=self.config.engine.small_model.name,
|
|
942
|
+
response=response,
|
|
943
|
+
source="deep_research.clarification",
|
|
944
|
+
)
|
|
873
945
|
assert isinstance(response.choices[0].message.content, str), (
|
|
874
946
|
"No clarifying questions generated"
|
|
875
947
|
)
|
|
@@ -897,6 +969,11 @@ class DeepResearchTool(Tool[DeepResearchToolConfig]):
|
|
|
897
969
|
model=self.config.engine.large_model.name,
|
|
898
970
|
messages=chat_messages,
|
|
899
971
|
)
|
|
972
|
+
record_language_model_response(
|
|
973
|
+
model_name=self.config.engine.large_model.name,
|
|
974
|
+
response=research_response,
|
|
975
|
+
source="deep_research.research_brief",
|
|
976
|
+
)
|
|
900
977
|
|
|
901
978
|
research_instructions = research_response.choices[0].message.content
|
|
902
979
|
assert research_instructions, "No research instructions generated"
|
|
@@ -198,7 +198,10 @@ async def research_supervisor(
|
|
|
198
198
|
supervisor_messages = state.get("supervisor_messages", [])
|
|
199
199
|
|
|
200
200
|
response = await ainvoke_with_token_handling(
|
|
201
|
-
research_model,
|
|
201
|
+
research_model,
|
|
202
|
+
supervisor_messages,
|
|
203
|
+
model_info=engine_config.research_model,
|
|
204
|
+
source="deep_research.supervisor",
|
|
202
205
|
)
|
|
203
206
|
if should_force_complete and not response.tool_calls:
|
|
204
207
|
_LOGGER.error("Failed to force research_complete tool call")
|
|
@@ -347,7 +350,10 @@ async def researcher(
|
|
|
347
350
|
researcher_messages = state.get("researcher_messages", [])
|
|
348
351
|
messages = [SystemMessage(content=researcher_prompt)] + researcher_messages
|
|
349
352
|
response = await ainvoke_with_token_handling(
|
|
350
|
-
research_model,
|
|
353
|
+
research_model,
|
|
354
|
+
messages,
|
|
355
|
+
model_info=engine_config.research_model,
|
|
356
|
+
source="deep_research.researcher",
|
|
351
357
|
)
|
|
352
358
|
|
|
353
359
|
return Command(
|
|
@@ -496,7 +502,10 @@ async def compress_research(
|
|
|
496
502
|
|
|
497
503
|
# Use proactive token handling instead of retry logic
|
|
498
504
|
response = await ainvoke_with_token_handling(
|
|
499
|
-
compression_model,
|
|
505
|
+
compression_model,
|
|
506
|
+
compression_messages,
|
|
507
|
+
model_info=custom_config.large_model,
|
|
508
|
+
source="deep_research.research_compression",
|
|
500
509
|
)
|
|
501
510
|
|
|
502
511
|
compressed_research = (
|
|
@@ -549,7 +558,10 @@ async def final_report_generation(
|
|
|
549
558
|
HumanMessage(content=message),
|
|
550
559
|
]
|
|
551
560
|
raw_report = await ainvoke_with_token_handling(
|
|
552
|
-
llm,
|
|
561
|
+
llm,
|
|
562
|
+
messages,
|
|
563
|
+
model_info=custom_config.large_model,
|
|
564
|
+
source="deep_research.final_report",
|
|
553
565
|
)
|
|
554
566
|
|
|
555
567
|
refinement_prompt = TEMPLATE_ENV.get_template("report_cleanup_prompt.j2").render(
|
|
@@ -560,7 +572,10 @@ async def final_report_generation(
|
|
|
560
572
|
HumanMessage(content=raw_report.content),
|
|
561
573
|
]
|
|
562
574
|
refined_report = await ainvoke_with_token_handling(
|
|
563
|
-
llm,
|
|
575
|
+
llm,
|
|
576
|
+
messages,
|
|
577
|
+
model_info=custom_config.large_model,
|
|
578
|
+
source="deep_research.final_report_refinement",
|
|
564
579
|
)
|
|
565
580
|
|
|
566
581
|
# Return successful report generation
|
|
@@ -29,6 +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
33
|
from .citation import GlobalCitationManager
|
|
33
34
|
from .state import AgentState, ResearcherState, SupervisorState
|
|
34
35
|
|
|
@@ -533,6 +534,8 @@ async def ainvoke_with_token_handling(
|
|
|
533
534
|
model: Runnable[List[BaseMessage], Any],
|
|
534
535
|
messages: List[BaseMessage],
|
|
535
536
|
model_info: LanguageModelInfo,
|
|
537
|
+
*,
|
|
538
|
+
source: str,
|
|
536
539
|
) -> Any:
|
|
537
540
|
"""
|
|
538
541
|
Invoke model with proactive token filtering and retry logic with exponential backoff.
|
|
@@ -543,6 +546,7 @@ async def ainvoke_with_token_handling(
|
|
|
543
546
|
model: The model to invoke
|
|
544
547
|
messages: List of messages to send
|
|
545
548
|
model_info: Model info for token limits
|
|
549
|
+
source: Stable identifier for the LLM call site
|
|
546
550
|
|
|
547
551
|
Returns:
|
|
548
552
|
Model response
|
|
@@ -556,7 +560,7 @@ async def ainvoke_with_token_handling(
|
|
|
556
560
|
|
|
557
561
|
for attempt in range(max_retries + 1):
|
|
558
562
|
try:
|
|
559
|
-
|
|
563
|
+
response = await model.ainvoke(prepared_messages)
|
|
560
564
|
except Exception as e:
|
|
561
565
|
_LOGGER.exception(f"Error invoking model: {e}")
|
|
562
566
|
# Handle token errors by truncating history and retrying
|
|
@@ -581,3 +585,10 @@ async def ainvoke_with_token_handling(
|
|
|
581
585
|
f"Model invocation failed after {max_retries + 1} attempts: {str(e)}"
|
|
582
586
|
)
|
|
583
587
|
raise e
|
|
588
|
+
else:
|
|
589
|
+
record_language_model_response(
|
|
590
|
+
model_name=model_info.name,
|
|
591
|
+
response=response,
|
|
592
|
+
source=source,
|
|
593
|
+
)
|
|
594
|
+
return response
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|