Coverage for src/local_deep_research/news/utils/headline_generator.py: 100%

29 statements  

« prev     ^ index     » next       coverage.py v7.15.1, created at 2026-07-20 01:24 +0000

1""" 

2Headline generation utilities for news items. 

3Uses LLM to generate concise, meaningful headlines from long queries and findings. 

4""" 

5 

6from typing import Optional 

7from loguru import logger 

8 

9 

10def generate_headline( 

11 query: str, 

12 findings: str = "", 

13 max_length: int = 100, 

14 settings_snapshot: Optional[dict] = None, 

15) -> str: 

16 """ 

17 Generate a concise headline from a query and optional findings. 

18 

19 Args: 

20 query: The search query or research question 

21 findings: Optional findings/content to help generate better headline 

22 max_length: Maximum length for the headline 

23 settings_snapshot: Optional settings snapshot so the LLM call 

24 picks up the active egress policy. Background callers 

25 should pass this through; without it, the LLM PEP's 

26 ``settings_snapshot is not None`` guard skips and a cloud 

27 LLM can fire even under require_local_endpoint. 

28 

29 Returns: 

30 A concise headline string 

31 """ 

32 # Always try LLM generation first for dynamic headlines based on actual content 

33 llm_headline = _generate_with_llm( 

34 query, findings, max_length, settings_snapshot 

35 ) 

36 if llm_headline: 

37 return llm_headline 

38 

39 # No fallback - if LLM fails, indicate failure 

40 return "[Headline generation failed]" 

41 

42 

43def _generate_with_llm( 

44 query: str, 

45 findings: str, 

46 max_length: int, 

47 settings_snapshot: Optional[dict] = None, 

48) -> Optional[str]: 

49 """Generate headline using LLM.""" 

50 try: 

51 from ...config.llm_config import get_llm 

52 

53 # Use the configured model for headline generation 

54 llm = get_llm(temperature=0.3, settings_snapshot=settings_snapshot) 

55 

56 try: 

57 # Focus only on the findings/report content, not the query 

58 if not findings: 

59 logger.debug("No findings provided for headline generation") 

60 return None 

61 

62 # Use the COMPLETE findings - no character limit 

63 findings_preview = findings 

64 logger.debug( 

65 f"Generating headline with {len(findings)} chars of findings" 

66 ) 

67 

68 prompt = f"""Generate a comprehensive news headline that captures the key events from the research report below. 

69 

70Research Findings: 

71{findings_preview} 

72 

73Requirements: 

74- Include MULTIPLE major events if several important things happened (e.g., "Earthquake Strikes California While Wildfires Rage; Global Markets Tumble Amid Political Tensions") 

75- Capture as much important information as possible in the headline 

76- Be specific about locations, impacts, and key details 

77- Professional news headline style but can be longer to include more information 

78- Focus on the most impactful findings from the report 

79- Use semicolons or commas to separate multiple major events 

80- No quotes or punctuation at start/end 

81- Base the headline ONLY on the actual findings in the report 

82 

83Generate only the headline text, nothing else.""" 

84 

85 response = llm.invoke(prompt) 

86 headline: str = str(response.content).strip() 

87 

88 # Clean up the generated headline 

89 headline = headline.strip("\"'.,!?") 

90 

91 # Validate the headline 

92 if headline: 

93 logger.debug(f"Generated headline: {headline}") 

94 return headline 

95 finally: 

96 from ...utilities.resource_utils import safe_close 

97 

98 safe_close(llm, "headline LLM") 

99 

100 except Exception as e: 

101 logger.debug(f"LLM headline generation failed: {e}") 

102 

103 return None