Coverage for src/local_deep_research/advanced_search_system/summarization/base.py: 100%

27 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-03 23:15 +0000

1"""Abstract base for LLM-driven summarizers.""" 

2 

3from abc import ABC, abstractmethod 

4 

5from langchain_core.language_models.chat_models import BaseChatModel 

6from loguru import logger 

7 

8from ...utilities.search_utilities import remove_think_tags 

9 

10 

11class BaseSummarizer(ABC): 

12 """Common machinery for invoking an LLM to summarize text. 

13 

14 Subclasses provide the prompt via :meth:`_build_prompt`. The base class 

15 handles the model invocation, think-tag stripping, and length truncation. 

16 

17 On LLM-invocation failure (network error, rate limit, malformed response) 

18 :meth:`summarize` returns an empty string. This is a deliberate choice for 

19 callers that aggregate the summary into a larger context update — losing 

20 one summary turn should not abort the rest of the update. 

21 """ 

22 

23 INPUT_TRUNCATE_CHARS = 8000 

24 

25 def __init__( 

26 self, 

27 model: BaseChatModel, 

28 max_sentences: int = 3, 

29 max_chars: int = 300, 

30 ): 

31 self.model = model 

32 self.max_sentences = max_sentences 

33 self.max_chars = max_chars 

34 

35 def summarize(self, content: str) -> str: 

36 if not content: 

37 return "" 

38 

39 prompt = self._build_prompt(content[: self.INPUT_TRUNCATE_CHARS]) 

40 

41 try: 

42 response = self.model.invoke(prompt) 

43 except Exception: 

44 logger.opt(exception=True).debug("LLM summarization failed") 

45 return "" 

46 

47 summary = remove_think_tags(str(response.content)).strip() 

48 return self._truncate(summary) 

49 

50 @abstractmethod 

51 def _build_prompt(self, content: str) -> str: 

52 """Return the prompt sent to the LLM for ``content``.""" 

53 

54 def _truncate(self, summary: str) -> str: 

55 if len(summary) > self.max_chars: 

56 return summary[: self.max_chars].rstrip() + "..." 

57 return summary