Coverage for src / local_deep_research / followup_research / service.py: 52%
50 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
1"""
2Service layer for follow-up research functionality.
4This service handles the business logic for follow-up research,
5including loading parent research context and orchestrating the search.
6"""
8from typing import Dict, Any
9from loguru import logger
11from ..database.models import ResearchHistory
12from ..database.session_context import get_user_db_session
13from ..web.services.research_sources_service import ResearchSourcesService
14from .models import FollowUpRequest
17class FollowUpResearchService:
18 """Service for handling follow-up research operations."""
20 def __init__(self, username: str = None):
21 """
22 Initialize the follow-up research service.
24 Args:
25 username: Username for database access
26 """
27 self.username = username
29 def load_parent_research(self, parent_research_id: str) -> Dict[str, Any]:
30 """
31 Load parent research data from the database.
33 Args:
34 parent_research_id: ID of the parent research
36 Returns:
37 Dictionary containing parent research data including:
38 - report_content: The generated report
39 - resources: List of research resources/links
40 - query: Original research query
41 - strategy: Strategy used
42 """
43 try:
44 with get_user_db_session(self.username) as session:
45 # Load research history
46 research = (
47 session.query(ResearchHistory)
48 .filter_by(id=parent_research_id)
49 .first()
50 )
52 if not research: 52 ↛ 53line 52 didn't jump to line 53 because the condition on line 52 was never true
53 logger.warning(
54 f"Parent research not found: {parent_research_id}"
55 )
56 return {}
58 logger.info(
59 f"Found research: {research.id}, has meta: {research.research_meta is not None}"
60 )
62 # Use the ResearchSourcesService to get sources properly from database
63 sources_service = ResearchSourcesService()
64 resource_list = sources_service.get_research_sources(
65 parent_research_id, username=self.username
66 )
68 logger.info(
69 f"Found {len(resource_list)} sources from ResearchResource table"
70 )
72 # If no sources in database, try to get from research_meta as fallback
73 if not resource_list and research.research_meta: 73 ↛ 74line 73 didn't jump to line 74 because the condition on line 73 was never true
74 logger.info(
75 "No sources in database, checking research_meta"
76 )
77 logger.info(
78 f"Research meta keys: {list(research.research_meta.keys()) if isinstance(research.research_meta, dict) else 'Not a dict'}"
79 )
81 # Try different possible locations for sources in research_meta
82 meta_sources = (
83 research.research_meta.get("all_links_of_system", [])
84 or research.research_meta.get("sources", [])
85 or research.research_meta.get("links", [])
86 or []
87 )
89 if meta_sources:
90 logger.info(
91 f"Found {len(meta_sources)} sources in research_meta, saving to database"
92 )
93 # Save them to the database for future use
94 saved = sources_service.save_research_sources(
95 parent_research_id,
96 meta_sources,
97 username=self.username,
98 )
99 logger.info(f"Saved {saved} sources to database")
101 # Now retrieve them properly formatted
102 resource_list = sources_service.get_research_sources(
103 parent_research_id, username=self.username
104 )
106 # Convert to dictionary format
107 parent_data = {
108 "research_id": research.id,
109 "query": research.query,
110 "report_content": research.report_content,
111 "formatted_findings": research.research_meta.get(
112 "formatted_findings", ""
113 )
114 if research.research_meta
115 else "",
116 "strategy": research.research_meta.get("strategy_name", "")
117 if research.research_meta
118 else "",
119 "resources": resource_list,
120 "all_links_of_system": resource_list,
121 }
123 logger.info(
124 f"Loaded parent research {parent_research_id} with "
125 f"{len(resource_list)} sources"
126 )
128 return parent_data
130 except Exception:
131 logger.exception("Error loading parent research")
132 return {}
134 def prepare_research_context(
135 self, parent_research_id: str
136 ) -> Dict[str, Any]:
137 """
138 Prepare the research context for the contextual follow-up strategy.
140 Args:
141 parent_research_id: ID of the parent research
143 Returns:
144 Research context dictionary for the strategy
145 """
146 parent_data = self.load_parent_research(parent_research_id)
148 if not parent_data: 148 ↛ 149line 148 didn't jump to line 149 because the condition on line 148 was never true
149 logger.warning("No parent data found, returning empty context")
150 return {}
152 # Format context for the strategy
153 research_context = {
154 "parent_research_id": parent_research_id,
155 "past_links": parent_data.get("all_links_of_system", []),
156 "past_findings": parent_data.get("formatted_findings", ""),
157 "report_content": parent_data.get("report_content", ""),
158 "resources": parent_data.get("resources", []),
159 "all_links_of_system": parent_data.get("all_links_of_system", []),
160 "original_query": parent_data.get("query", ""),
161 }
163 return research_context
165 def perform_followup(self, request: FollowUpRequest) -> Dict[str, Any]:
166 """
167 Perform a follow-up research based on parent research.
169 This method prepares the context and parameters for the research system
170 to use the contextual follow-up strategy.
172 Args:
173 request: FollowUpRequest with question and parent research ID
175 Returns:
176 Dictionary with research parameters for the research system
177 """
178 # Prepare the research context from parent
179 research_context = self.prepare_research_context(
180 request.parent_research_id
181 )
183 if not research_context:
184 logger.warning(
185 f"Parent research not found: {request.parent_research_id}, using empty context"
186 )
187 # Use empty context to allow follow-up without parent
188 research_context = {
189 "parent_research_id": request.parent_research_id,
190 "past_links": [],
191 "past_findings": "",
192 "report_content": "",
193 "resources": [],
194 "all_links_of_system": [],
195 "original_query": "",
196 }
198 # Prepare parameters for the research system
199 research_params = {
200 "query": request.question,
201 "strategy": "contextual-followup",
202 "delegate_strategy": request.strategy,
203 "max_iterations": request.max_iterations,
204 "questions_per_iteration": request.questions_per_iteration,
205 "research_context": research_context,
206 "parent_research_id": request.parent_research_id,
207 }
209 logger.info(
210 f"Prepared follow-up research for question: '{request.question}' "
211 f"based on parent: {request.parent_research_id}"
212 )
214 return research_params