Coverage for src/local_deep_research/advanced_search_system/questions/standard_question.py: 98%

36 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-06 15:42 +0000

1""" 

2Standard question generation implementation. 

3""" 

4 

5from datetime import datetime, UTC 

6from typing import List, Optional 

7 

8from loguru import logger 

9 

10from ...utilities.json_utils import get_llm_response_text 

11from .base_question import BaseQuestionGenerator 

12 

13 

14class StandardQuestionGenerator(BaseQuestionGenerator): 

15 """Standard question generator.""" 

16 

17 def generate_questions( 

18 self, 

19 current_knowledge: str, 

20 query: str, 

21 questions_per_iteration: int = 2, 

22 questions_by_iteration: Optional[dict] = None, 

23 ) -> List[str]: 

24 """Generate follow-up questions based on current knowledge.""" 

25 now = datetime.now(UTC) 

26 current_time = now.strftime("%Y-%m-%d") 

27 questions_by_iteration = questions_by_iteration or {} 

28 

29 logger.info("Generating follow-up questions...") 

30 

31 if questions_by_iteration: 

32 prompt = f"""Critically reflect current knowledge (e.g., timeliness), what {questions_per_iteration} high-quality internet search questions remain unanswered to exactly answer the query? 

33 Query: {query} 

34 Today: {current_time} 

35 Past questions: {questions_by_iteration!s} 

36 Knowledge: {current_knowledge} 

37 Include questions that critically reflect current knowledge. 

38 \n\n\nFormat: One question per line, e.g. \n Q: question1 \n Q: question2\n\n""" 

39 else: 

40 prompt = f" You will have follow up questions. First, identify if your knowledge is outdated (high chance). Today: {current_time}. Generate {questions_per_iteration} high-quality internet search questions to exactly answer: {query}\n\n\nFormat: One question per line, e.g. \n Q: question1 \n Q: question2\n\n" 

41 

42 response = self.model.invoke(prompt) 

43 

44 response_text = get_llm_response_text(response) 

45 

46 questions = [ 

47 q.replace("Q:", "").strip() 

48 for q in response_text.split("\n") 

49 if q.strip().startswith("Q:") 

50 ][:questions_per_iteration] 

51 

52 logger.info(f"Generated {len(questions)} follow-up questions") 

53 

54 return questions 

55 

56 def generate_sub_questions( 

57 self, query: str, context: str = "" 

58 ) -> List[str]: 

59 """ 

60 Generate sub-questions from a main query. 

61 

62 Args: 

63 query: The main query to break down 

64 context: Additional context for question generation 

65 

66 Returns: 

67 List[str]: List of generated sub-questions 

68 """ 

69 prompt = f"""You are an expert at breaking down complex questions into simpler sub-questions. 

70 

71Original Question: {query} 

72 

73{context} 

74 

75Break down the original question into 2-5 simpler sub-questions that would help answer the original question when answered in sequence. 

76Follow these guidelines: 

771. Each sub-question should be specific and answerable on its own 

782. Sub-questions should build towards answering the original question 

793. For multi-hop or complex queries, identify the individual facts or entities needed 

804. Ensure the sub-questions can be answered with separate searches 

81 

82Format your response as a numbered list with ONLY the sub-questions, one per line: 

831. First sub-question 

842. Second sub-question 

85... 

86 

87Only provide the numbered sub-questions, nothing else.""" 

88 

89 try: 

90 response = self.model.invoke(prompt) 

91 

92 content = get_llm_response_text(response) 

93 

94 # Parse sub-questions from the response 

95 sub_questions = [] 

96 for line in content.strip().split("\n"): 

97 line = line.strip() 

98 if line and (line[0].isdigit() or line.startswith("-")): 

99 # Extract sub-question from numbered or bulleted list 

100 parts = ( 

101 line.split(".", 1) 

102 if "." in line 

103 else line.split(" ", 1) 

104 ) 

105 if len(parts) > 1: 105 ↛ 96line 105 didn't jump to line 96 because the condition on line 105 was always true

106 sub_question = parts[1].strip() 

107 sub_questions.append(sub_question) 

108 

109 # Limit to at most 5 sub-questions 

110 return sub_questions[:5] 

111 except Exception: 

112 logger.exception("Error generating sub-questions") 

113 return []