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
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-06 15:42 +0000
1"""
2Standard question generation implementation.
3"""
5from datetime import datetime, UTC
6from typing import List, Optional
8from loguru import logger
10from ...utilities.json_utils import get_llm_response_text
11from .base_question import BaseQuestionGenerator
14class StandardQuestionGenerator(BaseQuestionGenerator):
15 """Standard question generator."""
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 {}
29 logger.info("Generating follow-up questions...")
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"
42 response = self.model.invoke(prompt)
44 response_text = get_llm_response_text(response)
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]
52 logger.info(f"Generated {len(questions)} follow-up questions")
54 return questions
56 def generate_sub_questions(
57 self, query: str, context: str = ""
58 ) -> List[str]:
59 """
60 Generate sub-questions from a main query.
62 Args:
63 query: The main query to break down
64 context: Additional context for question generation
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.
71Original Question: {query}
73{context}
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
82Format your response as a numbered list with ONLY the sub-questions, one per line:
831. First sub-question
842. Second sub-question
85...
87Only provide the numbered sub-questions, nothing else."""
89 try:
90 response = self.model.invoke(prompt)
92 content = get_llm_response_text(response)
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)
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 []