local-deep-research 0.1.26__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- local_deep_research/__init__.py +23 -22
- local_deep_research/__main__.py +16 -0
- local_deep_research/advanced_search_system/__init__.py +7 -0
- local_deep_research/advanced_search_system/filters/__init__.py +8 -0
- local_deep_research/advanced_search_system/filters/base_filter.py +38 -0
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +200 -0
- local_deep_research/advanced_search_system/findings/base_findings.py +81 -0
- local_deep_research/advanced_search_system/findings/repository.py +452 -0
- local_deep_research/advanced_search_system/knowledge/__init__.py +1 -0
- local_deep_research/advanced_search_system/knowledge/base_knowledge.py +151 -0
- local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +159 -0
- local_deep_research/advanced_search_system/questions/__init__.py +1 -0
- local_deep_research/advanced_search_system/questions/base_question.py +64 -0
- local_deep_research/advanced_search_system/questions/decomposition_question.py +445 -0
- local_deep_research/advanced_search_system/questions/standard_question.py +119 -0
- local_deep_research/advanced_search_system/repositories/__init__.py +7 -0
- local_deep_research/advanced_search_system/strategies/__init__.py +1 -0
- local_deep_research/advanced_search_system/strategies/base_strategy.py +118 -0
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +450 -0
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +312 -0
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +270 -0
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +300 -0
- local_deep_research/advanced_search_system/tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/base_tool.py +100 -0
- local_deep_research/advanced_search_system/tools/knowledge_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/question_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/search_tools/__init__.py +1 -0
- local_deep_research/api/__init__.py +5 -5
- local_deep_research/api/research_functions.py +154 -160
- local_deep_research/app.py +8 -0
- local_deep_research/citation_handler.py +25 -16
- local_deep_research/{config.py → config/config_files.py} +102 -110
- local_deep_research/config/llm_config.py +472 -0
- local_deep_research/config/search_config.py +77 -0
- local_deep_research/defaults/__init__.py +10 -5
- local_deep_research/defaults/main.toml +2 -2
- local_deep_research/defaults/search_engines.toml +60 -34
- local_deep_research/main.py +121 -19
- local_deep_research/migrate_db.py +147 -0
- local_deep_research/report_generator.py +87 -45
- local_deep_research/search_system.py +153 -283
- local_deep_research/setup_data_dir.py +35 -0
- local_deep_research/test_migration.py +178 -0
- local_deep_research/utilities/__init__.py +0 -0
- local_deep_research/utilities/db_utils.py +49 -0
- local_deep_research/{utilties → utilities}/enums.py +2 -2
- local_deep_research/{utilties → utilities}/llm_utils.py +63 -29
- local_deep_research/utilities/search_utilities.py +242 -0
- local_deep_research/{utilties → utilities}/setup_utils.py +4 -2
- local_deep_research/web/__init__.py +0 -1
- local_deep_research/web/app.py +86 -1709
- local_deep_research/web/app_factory.py +289 -0
- local_deep_research/web/database/README.md +70 -0
- local_deep_research/web/database/migrate_to_ldr_db.py +289 -0
- local_deep_research/web/database/migrations.py +447 -0
- local_deep_research/web/database/models.py +117 -0
- local_deep_research/web/database/schema_upgrade.py +107 -0
- local_deep_research/web/models/database.py +294 -0
- local_deep_research/web/models/settings.py +94 -0
- local_deep_research/web/routes/api_routes.py +559 -0
- local_deep_research/web/routes/history_routes.py +354 -0
- local_deep_research/web/routes/research_routes.py +715 -0
- local_deep_research/web/routes/settings_routes.py +1583 -0
- local_deep_research/web/services/research_service.py +947 -0
- local_deep_research/web/services/resource_service.py +149 -0
- local_deep_research/web/services/settings_manager.py +669 -0
- local_deep_research/web/services/settings_service.py +187 -0
- local_deep_research/web/services/socket_service.py +210 -0
- local_deep_research/web/static/css/custom_dropdown.css +277 -0
- local_deep_research/web/static/css/settings.css +1223 -0
- local_deep_research/web/static/css/styles.css +525 -48
- local_deep_research/web/static/js/components/custom_dropdown.js +428 -0
- local_deep_research/web/static/js/components/detail.js +348 -0
- local_deep_research/web/static/js/components/fallback/formatting.js +122 -0
- local_deep_research/web/static/js/components/fallback/ui.js +215 -0
- local_deep_research/web/static/js/components/history.js +487 -0
- local_deep_research/web/static/js/components/logpanel.js +949 -0
- local_deep_research/web/static/js/components/progress.js +1107 -0
- local_deep_research/web/static/js/components/research.js +1865 -0
- local_deep_research/web/static/js/components/results.js +766 -0
- local_deep_research/web/static/js/components/settings.js +3981 -0
- local_deep_research/web/static/js/components/settings_sync.js +106 -0
- local_deep_research/web/static/js/main.js +226 -0
- local_deep_research/web/static/js/services/api.js +253 -0
- local_deep_research/web/static/js/services/audio.js +31 -0
- local_deep_research/web/static/js/services/formatting.js +119 -0
- local_deep_research/web/static/js/services/pdf.js +622 -0
- local_deep_research/web/static/js/services/socket.js +882 -0
- local_deep_research/web/static/js/services/ui.js +546 -0
- local_deep_research/web/templates/base.html +72 -0
- local_deep_research/web/templates/components/custom_dropdown.html +47 -0
- local_deep_research/web/templates/components/log_panel.html +32 -0
- local_deep_research/web/templates/components/mobile_nav.html +22 -0
- local_deep_research/web/templates/components/settings_form.html +299 -0
- local_deep_research/web/templates/components/sidebar.html +21 -0
- local_deep_research/web/templates/pages/details.html +73 -0
- local_deep_research/web/templates/pages/history.html +51 -0
- local_deep_research/web/templates/pages/progress.html +57 -0
- local_deep_research/web/templates/pages/research.html +139 -0
- local_deep_research/web/templates/pages/results.html +59 -0
- local_deep_research/web/templates/settings_dashboard.html +78 -192
- local_deep_research/web/utils/__init__.py +0 -0
- local_deep_research/web/utils/formatters.py +76 -0
- local_deep_research/web_search_engines/engines/full_search.py +18 -16
- local_deep_research/web_search_engines/engines/meta_search_engine.py +182 -131
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +224 -139
- local_deep_research/web_search_engines/engines/search_engine_brave.py +88 -71
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +48 -39
- local_deep_research/web_search_engines/engines/search_engine_github.py +415 -204
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +123 -90
- local_deep_research/web_search_engines/engines/search_engine_guardian.py +210 -157
- local_deep_research/web_search_engines/engines/search_engine_local.py +532 -369
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +42 -36
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +358 -266
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +212 -160
- local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +213 -170
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +84 -68
- local_deep_research/web_search_engines/engines/search_engine_wayback.py +186 -154
- local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +115 -77
- local_deep_research/web_search_engines/search_engine_base.py +174 -99
- local_deep_research/web_search_engines/search_engine_factory.py +192 -102
- local_deep_research/web_search_engines/search_engines_config.py +22 -15
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/METADATA +177 -97
- local_deep_research-0.2.2.dist-info/RECORD +135 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/WHEEL +1 -2
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/entry_points.txt +3 -0
- local_deep_research/defaults/llm_config.py +0 -338
- local_deep_research/utilties/search_utilities.py +0 -114
- local_deep_research/web/static/js/app.js +0 -3763
- local_deep_research/web/templates/api_keys_config.html +0 -82
- local_deep_research/web/templates/collections_config.html +0 -90
- local_deep_research/web/templates/index.html +0 -348
- local_deep_research/web/templates/llm_config.html +0 -120
- local_deep_research/web/templates/main_config.html +0 -89
- local_deep_research/web/templates/search_engines_config.html +0 -154
- local_deep_research/web/templates/settings.html +0 -519
- local_deep_research-0.1.26.dist-info/RECORD +0 -61
- local_deep_research-0.1.26.dist-info/top_level.txt +0 -1
- /local_deep_research/{utilties → config}/__init__.py +0 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,450 @@
|
|
1
|
+
"""
|
2
|
+
IterDRAG strategy implementation.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import json
|
6
|
+
import logging
|
7
|
+
from datetime import datetime
|
8
|
+
from typing import Dict, List
|
9
|
+
|
10
|
+
from langchain_core.language_models import BaseLLM
|
11
|
+
|
12
|
+
from ...citation_handler import CitationHandler
|
13
|
+
from ...config.config_files import settings
|
14
|
+
from ...config.llm_config import get_llm
|
15
|
+
from ...config.search_config import get_search
|
16
|
+
from ...utilities.db_utils import get_db_setting
|
17
|
+
from ...utilities.search_utilities import extract_links_from_search_results
|
18
|
+
from ..findings.repository import FindingsRepository
|
19
|
+
from ..knowledge.standard_knowledge import StandardKnowledge
|
20
|
+
from ..questions.decomposition_question import DecompositionQuestionGenerator
|
21
|
+
from .base_strategy import BaseSearchStrategy
|
22
|
+
|
23
|
+
logger = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
|
26
|
+
class IterDRAGStrategy(BaseSearchStrategy):
|
27
|
+
"""IterDRAG strategy that breaks queries into sub-queries."""
|
28
|
+
|
29
|
+
def __init__(
|
30
|
+
self, model: BaseLLM | None = None, search=None, citation_handler=None
|
31
|
+
):
|
32
|
+
"""Initialize the strategy with optional dependency injection for testing."""
|
33
|
+
super().__init__()
|
34
|
+
self.model = model or get_llm()
|
35
|
+
self.search = search or get_search()
|
36
|
+
self.progress_callback = None
|
37
|
+
self.all_links_of_system = list()
|
38
|
+
self.questions_by_iteration = {}
|
39
|
+
|
40
|
+
# Use provided citation_handler or create one
|
41
|
+
self.citation_handler = citation_handler or CitationHandler(self.model)
|
42
|
+
|
43
|
+
# Initialize components
|
44
|
+
self.question_generator = DecompositionQuestionGenerator(self.model)
|
45
|
+
self.knowledge_generator = StandardKnowledge(self.model)
|
46
|
+
self.findings_repository = FindingsRepository(self.model)
|
47
|
+
|
48
|
+
def _update_progress(
|
49
|
+
self, message: str, progress_percent: int = None, metadata: dict = None
|
50
|
+
) -> None:
|
51
|
+
"""Send a progress update via the callback if available."""
|
52
|
+
if self.progress_callback:
|
53
|
+
self.progress_callback(message, progress_percent, metadata or {})
|
54
|
+
|
55
|
+
def _generate_subqueries(
|
56
|
+
self, query: str, initial_results: List[Dict], current_knowledge: str
|
57
|
+
) -> List[str]:
|
58
|
+
"""Generate sub-queries based on initial search results and the main query."""
|
59
|
+
try:
|
60
|
+
# Format context for question generation
|
61
|
+
context = f"""Current Query: {query}
|
62
|
+
Current Date: {datetime.now().strftime('%Y-%m-%d')}
|
63
|
+
Past Questions: {self.questions_by_iteration}
|
64
|
+
Current Knowledge: {current_knowledge}
|
65
|
+
|
66
|
+
Initial Search Results:
|
67
|
+
{json.dumps(initial_results, indent=2)}"""
|
68
|
+
|
69
|
+
# Generate sub-queries using the question generator
|
70
|
+
return self.question_generator.generate_questions(
|
71
|
+
query, context, int(get_db_setting("search.questions_per_iteration"))
|
72
|
+
)
|
73
|
+
except Exception as e:
|
74
|
+
logger.error(f"Error generating sub-queries: {str(e)}")
|
75
|
+
return []
|
76
|
+
|
77
|
+
def analyze_topic(self, query: str) -> Dict:
|
78
|
+
"""IterDRAG implementation of the topic analysis process."""
|
79
|
+
findings = []
|
80
|
+
current_knowledge = ""
|
81
|
+
section_links = list()
|
82
|
+
|
83
|
+
self._update_progress(
|
84
|
+
"Initializing IterDRAG research system",
|
85
|
+
5,
|
86
|
+
{"phase": "init", "strategy": "iterdrag"},
|
87
|
+
)
|
88
|
+
|
89
|
+
# Check if search engine is available
|
90
|
+
if self.search is None:
|
91
|
+
error_msg = (
|
92
|
+
"Error: No search engine available. Please check your configuration."
|
93
|
+
)
|
94
|
+
self._update_progress(
|
95
|
+
error_msg,
|
96
|
+
100,
|
97
|
+
{
|
98
|
+
"phase": "error",
|
99
|
+
"error": "No search engine available",
|
100
|
+
"status": "failed",
|
101
|
+
},
|
102
|
+
)
|
103
|
+
return {
|
104
|
+
"findings": [],
|
105
|
+
"iterations": 0,
|
106
|
+
"questions": {},
|
107
|
+
"formatted_findings": "Error: Unable to conduct research without a search engine.",
|
108
|
+
"current_knowledge": "",
|
109
|
+
"error": error_msg,
|
110
|
+
}
|
111
|
+
|
112
|
+
# Initial search for the main query
|
113
|
+
self._update_progress(
|
114
|
+
"Performing initial search for main query",
|
115
|
+
10,
|
116
|
+
{"phase": "search", "iteration": 1},
|
117
|
+
)
|
118
|
+
|
119
|
+
initial_results = self.search.run(query)
|
120
|
+
if not initial_results:
|
121
|
+
self._update_progress(
|
122
|
+
"No initial results found",
|
123
|
+
15,
|
124
|
+
{"phase": "search_complete", "result_count": 0},
|
125
|
+
)
|
126
|
+
initial_results = []
|
127
|
+
else:
|
128
|
+
self._update_progress(
|
129
|
+
f"Found {len(initial_results)} initial results",
|
130
|
+
15,
|
131
|
+
{"phase": "search_complete", "result_count": len(initial_results)},
|
132
|
+
)
|
133
|
+
|
134
|
+
# Extract and save links
|
135
|
+
initial_links = extract_links_from_search_results(initial_results)
|
136
|
+
self.all_links_of_system.extend(initial_links)
|
137
|
+
section_links.extend(initial_links)
|
138
|
+
|
139
|
+
# Generate sub-queries
|
140
|
+
self._update_progress(
|
141
|
+
"Generating sub-queries for IterDRAG analysis",
|
142
|
+
20,
|
143
|
+
{"phase": "iterdrag_decomposition"},
|
144
|
+
)
|
145
|
+
|
146
|
+
sub_queries = self._generate_subqueries(
|
147
|
+
query, initial_results, current_knowledge
|
148
|
+
)
|
149
|
+
|
150
|
+
# Store questions in repository and in self.questions_by_iteration
|
151
|
+
self.questions_by_iteration = {0: sub_queries}
|
152
|
+
self.findings_repository.set_questions_by_iteration(self.questions_by_iteration)
|
153
|
+
|
154
|
+
if not sub_queries:
|
155
|
+
logger.error("No sub-queries were generated to analyze.")
|
156
|
+
finding = {
|
157
|
+
"phase": "Analysis Error",
|
158
|
+
"content": "No sub-queries could be generated for the main question.",
|
159
|
+
"question": query,
|
160
|
+
"search_results": [],
|
161
|
+
}
|
162
|
+
findings.append(finding)
|
163
|
+
else:
|
164
|
+
# Process each sub-query
|
165
|
+
total_subqueries = len(sub_queries)
|
166
|
+
|
167
|
+
for i, sub_query in enumerate(sub_queries, 1):
|
168
|
+
progress_base = 25 + (i / total_subqueries * 50)
|
169
|
+
self._update_progress(
|
170
|
+
f"Processing sub-query {i} of {total_subqueries}: {sub_query}",
|
171
|
+
int(progress_base),
|
172
|
+
{"phase": "subquery", "subquery_index": i},
|
173
|
+
)
|
174
|
+
|
175
|
+
# Search for the sub-query
|
176
|
+
try:
|
177
|
+
sub_results = self.search.run(sub_query)
|
178
|
+
if not sub_results:
|
179
|
+
self._update_progress(
|
180
|
+
f"No results for sub-query: {sub_query}",
|
181
|
+
int(progress_base + 2),
|
182
|
+
{"phase": "search_complete", "result_count": 0},
|
183
|
+
)
|
184
|
+
sub_results = []
|
185
|
+
else:
|
186
|
+
self._update_progress(
|
187
|
+
f"Found {len(sub_results)} results for sub-query: {sub_query}",
|
188
|
+
int(progress_base + 2),
|
189
|
+
{
|
190
|
+
"phase": "search_complete",
|
191
|
+
"result_count": len(sub_results),
|
192
|
+
},
|
193
|
+
)
|
194
|
+
except Exception as e:
|
195
|
+
logger.error(f"Error searching for sub-query: {str(e)}")
|
196
|
+
sub_results = []
|
197
|
+
|
198
|
+
try:
|
199
|
+
# Use previous knowledge to answer this sub-query
|
200
|
+
result = self.citation_handler.analyze_followup(
|
201
|
+
sub_query,
|
202
|
+
sub_results,
|
203
|
+
current_knowledge,
|
204
|
+
nr_of_links=len(self.all_links_of_system),
|
205
|
+
)
|
206
|
+
|
207
|
+
# Extract and save links AFTER citation handler processes results
|
208
|
+
sub_links = extract_links_from_search_results(sub_results)
|
209
|
+
self.all_links_of_system.extend(sub_links)
|
210
|
+
section_links.extend(sub_links)
|
211
|
+
|
212
|
+
if result is not None:
|
213
|
+
finding = {
|
214
|
+
"phase": f"Sub-query {i}",
|
215
|
+
"content": result["content"],
|
216
|
+
"question": sub_query,
|
217
|
+
"search_results": sub_results,
|
218
|
+
"documents": result["documents"],
|
219
|
+
}
|
220
|
+
findings.append(finding)
|
221
|
+
self.findings_repository.add_finding(sub_query, finding)
|
222
|
+
self.findings_repository.add_documents(result["documents"])
|
223
|
+
|
224
|
+
# Add to current knowledge with space around +
|
225
|
+
current_knowledge = (
|
226
|
+
current_knowledge + "\n\n\n New: \n" + result["content"]
|
227
|
+
)
|
228
|
+
except Exception as e:
|
229
|
+
logger.error(f"Error analyzing sub-query results: {str(e)}")
|
230
|
+
finding = {
|
231
|
+
"phase": f"Follow-up Iteration 0.{i + 1}",
|
232
|
+
"content": "Error analyzing sub-query results.",
|
233
|
+
"question": sub_query,
|
234
|
+
"search_results": [],
|
235
|
+
"documents": [],
|
236
|
+
}
|
237
|
+
findings.append(finding)
|
238
|
+
# Optionally update current_knowledge even if analysis failed?
|
239
|
+
# current_knowledge += f"\n\nError analyzing results for: {sub_query}"
|
240
|
+
|
241
|
+
# Final answer synthesis based on all sub-query findings
|
242
|
+
self._update_progress(
|
243
|
+
"Synthesizing final answer from sub-queries",
|
244
|
+
80,
|
245
|
+
{"phase": "final_synthesis"},
|
246
|
+
)
|
247
|
+
|
248
|
+
try:
|
249
|
+
# Extract finding contents for synthesis
|
250
|
+
finding_contents = [f["content"] for f in findings if "content" in f]
|
251
|
+
|
252
|
+
# Synthesize findings into a final answer
|
253
|
+
final_answer = self.findings_repository.synthesize_findings(
|
254
|
+
query=query,
|
255
|
+
sub_queries=sub_queries,
|
256
|
+
findings=finding_contents,
|
257
|
+
accumulated_knowledge=current_knowledge,
|
258
|
+
)
|
259
|
+
|
260
|
+
# Check if the synthesis failed with an error
|
261
|
+
if isinstance(final_answer, str) and final_answer.startswith("Error:"):
|
262
|
+
logger.error(f"Synthesis returned an error: {final_answer}")
|
263
|
+
|
264
|
+
# Extract error type for better handling
|
265
|
+
error_type = "unknown"
|
266
|
+
error_message = final_answer.lower()
|
267
|
+
|
268
|
+
if "timeout" in error_message:
|
269
|
+
error_type = "timeout"
|
270
|
+
elif "token limit" in error_message:
|
271
|
+
error_type = "token_limit"
|
272
|
+
elif "rate limit" in error_message:
|
273
|
+
error_type = "rate_limit"
|
274
|
+
elif "connection" in error_message:
|
275
|
+
error_type = "connection"
|
276
|
+
|
277
|
+
# Log with specific error type
|
278
|
+
logger.error(f"Synthesis failed with error type: {error_type}")
|
279
|
+
|
280
|
+
# Add error information to progress update
|
281
|
+
self._update_progress(
|
282
|
+
f"Synthesis failed: {error_type}. Attempting fallback...",
|
283
|
+
85,
|
284
|
+
{"phase": "synthesis_error", "error_type": error_type},
|
285
|
+
)
|
286
|
+
|
287
|
+
# Try fallback: use the best individual finding as the final answer
|
288
|
+
longest_finding = ""
|
289
|
+
for f in findings:
|
290
|
+
if isinstance(f, dict) and "content" in f:
|
291
|
+
if len(f["content"]) > len(longest_finding):
|
292
|
+
longest_finding = f["content"]
|
293
|
+
|
294
|
+
if longest_finding:
|
295
|
+
logger.info("Using longest finding as fallback synthesis")
|
296
|
+
final_answer = f"""
|
297
|
+
# Research Results (Fallback Mode)
|
298
|
+
|
299
|
+
{longest_finding}
|
300
|
+
|
301
|
+
## Note
|
302
|
+
The final synthesis could not be completed due to an error: {final_answer}
|
303
|
+
This is a fallback response using the most detailed individual finding.
|
304
|
+
"""
|
305
|
+
else:
|
306
|
+
# If we don't have any findings with content, use current_knowledge
|
307
|
+
logger.info("Using current knowledge as fallback synthesis")
|
308
|
+
final_answer = f"""
|
309
|
+
# Research Results (Fallback Mode)
|
310
|
+
|
311
|
+
{current_knowledge}
|
312
|
+
|
313
|
+
## Note
|
314
|
+
The final synthesis could not be completed due to an error: {final_answer}
|
315
|
+
This is a fallback response using the accumulated knowledge.
|
316
|
+
"""
|
317
|
+
|
318
|
+
# Create a synthesis finding
|
319
|
+
finding = {
|
320
|
+
"phase": "Final synthesis",
|
321
|
+
"content": final_answer,
|
322
|
+
"question": query,
|
323
|
+
"search_results": [],
|
324
|
+
"documents": [],
|
325
|
+
}
|
326
|
+
findings.append(finding)
|
327
|
+
|
328
|
+
# Store the synthesized content
|
329
|
+
self.findings_repository.add_finding(query + "_synthesis", final_answer)
|
330
|
+
|
331
|
+
# Update current knowledge with the synthesized version
|
332
|
+
current_knowledge = final_answer
|
333
|
+
except Exception as e:
|
334
|
+
logger.error(f"Error synthesizing final answer: {str(e)}")
|
335
|
+
import traceback
|
336
|
+
|
337
|
+
logger.error(traceback.format_exc())
|
338
|
+
|
339
|
+
# Create an error finding
|
340
|
+
error_finding = {
|
341
|
+
"phase": "Final synthesis error",
|
342
|
+
"content": f"Error during synthesis: {str(e)}",
|
343
|
+
"question": query,
|
344
|
+
"search_results": [],
|
345
|
+
"documents": [],
|
346
|
+
}
|
347
|
+
findings.append(error_finding)
|
348
|
+
|
349
|
+
# If synthesis completely fails, construct a fallback answer from the most relevant findings
|
350
|
+
self._update_progress(
|
351
|
+
"Synthesis failed. Creating fallback summary...",
|
352
|
+
85,
|
353
|
+
{"phase": "synthesis_fallback"},
|
354
|
+
)
|
355
|
+
|
356
|
+
try:
|
357
|
+
# Extract best content from findings
|
358
|
+
key_findings = []
|
359
|
+
for i, f in enumerate(findings):
|
360
|
+
if isinstance(f, dict) and "content" in f and f.get("content"):
|
361
|
+
# Only take the first 500 chars of each finding for the fallback
|
362
|
+
content_preview = f.get("content", "")[:500]
|
363
|
+
if content_preview:
|
364
|
+
key_findings.append(
|
365
|
+
f"### Finding {i + 1}\n\n{content_preview}..."
|
366
|
+
)
|
367
|
+
|
368
|
+
# Create fallback content
|
369
|
+
fallback_content = f"""
|
370
|
+
# Research Results (Error Recovery Mode)
|
371
|
+
|
372
|
+
## Original Query
|
373
|
+
{query}
|
374
|
+
|
375
|
+
## Key Findings
|
376
|
+
{chr(10).join(key_findings[:5]) if key_findings else "No valid findings were generated."}
|
377
|
+
|
378
|
+
## Error Information
|
379
|
+
The system encountered an error during final synthesis: {str(e)}
|
380
|
+
This is an automatically generated fallback response.
|
381
|
+
"""
|
382
|
+
|
383
|
+
final_answer = fallback_content
|
384
|
+
except Exception as fallback_error:
|
385
|
+
# Last resort fallback
|
386
|
+
logger.error(f"Even fallback creation failed: {fallback_error}")
|
387
|
+
final_answer = f"""
|
388
|
+
# Research Error
|
389
|
+
|
390
|
+
The system encountered multiple errors while processing your query: "{query}"
|
391
|
+
|
392
|
+
Primary error: {str(e)}
|
393
|
+
Fallback error: {str(fallback_error)}
|
394
|
+
|
395
|
+
Please try again with a different query or contact support.
|
396
|
+
"""
|
397
|
+
|
398
|
+
# Compress knowledge if needed
|
399
|
+
if (
|
400
|
+
get_db_setting(
|
401
|
+
"general.knowledge_accumulation",
|
402
|
+
settings.general.knowledge_accumulation,
|
403
|
+
)
|
404
|
+
== "ITERATION"
|
405
|
+
):
|
406
|
+
try:
|
407
|
+
self._update_progress(
|
408
|
+
"Compressing knowledge", 90, {"phase": "knowledge_compression"}
|
409
|
+
)
|
410
|
+
current_knowledge = self.knowledge_generator.compress_knowledge(
|
411
|
+
current_knowledge, query, section_links
|
412
|
+
)
|
413
|
+
except Exception as e:
|
414
|
+
logger.error(f"Error compressing knowledge: {str(e)}")
|
415
|
+
|
416
|
+
# Format and save findings
|
417
|
+
self._update_progress(
|
418
|
+
"Formatting and saving findings", 95, {"phase": "formatting"}
|
419
|
+
)
|
420
|
+
|
421
|
+
try:
|
422
|
+
# First, get just the synthesized content without formatting
|
423
|
+
if not isinstance(final_answer, str):
|
424
|
+
logger.error(
|
425
|
+
"final_answer is not a string, using current_knowledge as fallback"
|
426
|
+
)
|
427
|
+
final_answer = current_knowledge
|
428
|
+
|
429
|
+
# Ensure latest questions are in the repository
|
430
|
+
self.findings_repository.set_questions_by_iteration(
|
431
|
+
self.questions_by_iteration
|
432
|
+
)
|
433
|
+
|
434
|
+
# Now format the findings with search questions and sources
|
435
|
+
formatted_findings = self.findings_repository.format_findings_to_text(
|
436
|
+
findings, final_answer
|
437
|
+
)
|
438
|
+
except Exception as e:
|
439
|
+
logger.error(f"Error formatting final findings: {str(e)}")
|
440
|
+
formatted_findings = "Error: Could not format findings due to an error."
|
441
|
+
|
442
|
+
self._update_progress("Research complete", 100, {"phase": "complete"})
|
443
|
+
|
444
|
+
return {
|
445
|
+
"findings": findings, # Keep the detailed findings list
|
446
|
+
"iterations": 1,
|
447
|
+
"questions": self.questions_by_iteration, # Use the member variable instead of {"0": sub_queries}
|
448
|
+
"formatted_findings": formatted_findings, # This is the fully formatted string for UI
|
449
|
+
"current_knowledge": current_knowledge, # This is the synthesized content for knowledge base
|
450
|
+
}
|