local-deep-research 0.3.12__py3-none-any.whl → 0.4.1__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.
Files changed (94) hide show
  1. local_deep_research/__init__.py +1 -0
  2. local_deep_research/__version__.py +1 -1
  3. local_deep_research/advanced_search_system/filters/base_filter.py +2 -3
  4. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +4 -5
  5. local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +298 -0
  6. local_deep_research/advanced_search_system/findings/repository.py +0 -3
  7. local_deep_research/advanced_search_system/strategies/base_strategy.py +1 -2
  8. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +14 -18
  9. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +4 -8
  10. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +5 -6
  11. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +2 -2
  12. local_deep_research/advanced_search_system/strategies/standard_strategy.py +9 -7
  13. local_deep_research/api/benchmark_functions.py +288 -0
  14. local_deep_research/api/research_functions.py +8 -4
  15. local_deep_research/benchmarks/README.md +162 -0
  16. local_deep_research/benchmarks/__init__.py +51 -0
  17. local_deep_research/benchmarks/benchmark_functions.py +353 -0
  18. local_deep_research/benchmarks/cli/__init__.py +16 -0
  19. local_deep_research/benchmarks/cli/benchmark_commands.py +338 -0
  20. local_deep_research/benchmarks/cli.py +347 -0
  21. local_deep_research/benchmarks/comparison/__init__.py +12 -0
  22. local_deep_research/benchmarks/comparison/evaluator.py +768 -0
  23. local_deep_research/benchmarks/datasets/__init__.py +53 -0
  24. local_deep_research/benchmarks/datasets/base.py +295 -0
  25. local_deep_research/benchmarks/datasets/browsecomp.py +116 -0
  26. local_deep_research/benchmarks/datasets/custom_dataset_template.py +98 -0
  27. local_deep_research/benchmarks/datasets/simpleqa.py +74 -0
  28. local_deep_research/benchmarks/datasets/utils.py +116 -0
  29. local_deep_research/benchmarks/datasets.py +31 -0
  30. local_deep_research/benchmarks/efficiency/__init__.py +14 -0
  31. local_deep_research/benchmarks/efficiency/resource_monitor.py +367 -0
  32. local_deep_research/benchmarks/efficiency/speed_profiler.py +214 -0
  33. local_deep_research/benchmarks/evaluators/__init__.py +18 -0
  34. local_deep_research/benchmarks/evaluators/base.py +74 -0
  35. local_deep_research/benchmarks/evaluators/browsecomp.py +83 -0
  36. local_deep_research/benchmarks/evaluators/composite.py +121 -0
  37. local_deep_research/benchmarks/evaluators/simpleqa.py +271 -0
  38. local_deep_research/benchmarks/graders.py +410 -0
  39. local_deep_research/benchmarks/metrics/README.md +80 -0
  40. local_deep_research/benchmarks/metrics/__init__.py +24 -0
  41. local_deep_research/benchmarks/metrics/calculation.py +385 -0
  42. local_deep_research/benchmarks/metrics/reporting.py +155 -0
  43. local_deep_research/benchmarks/metrics/visualization.py +205 -0
  44. local_deep_research/benchmarks/metrics.py +11 -0
  45. local_deep_research/benchmarks/optimization/__init__.py +32 -0
  46. local_deep_research/benchmarks/optimization/api.py +274 -0
  47. local_deep_research/benchmarks/optimization/metrics.py +20 -0
  48. local_deep_research/benchmarks/optimization/optuna_optimizer.py +1163 -0
  49. local_deep_research/benchmarks/runners.py +434 -0
  50. local_deep_research/benchmarks/templates.py +65 -0
  51. local_deep_research/config/llm_config.py +26 -23
  52. local_deep_research/config/search_config.py +1 -5
  53. local_deep_research/defaults/default_settings.json +108 -7
  54. local_deep_research/search_system.py +16 -8
  55. local_deep_research/utilities/db_utils.py +3 -6
  56. local_deep_research/utilities/es_utils.py +441 -0
  57. local_deep_research/utilities/log_utils.py +36 -0
  58. local_deep_research/utilities/search_utilities.py +8 -9
  59. local_deep_research/web/app.py +15 -10
  60. local_deep_research/web/app_factory.py +9 -12
  61. local_deep_research/web/database/migrations.py +8 -5
  62. local_deep_research/web/database/models.py +20 -0
  63. local_deep_research/web/database/schema_upgrade.py +5 -8
  64. local_deep_research/web/models/database.py +15 -18
  65. local_deep_research/web/routes/benchmark_routes.py +427 -0
  66. local_deep_research/web/routes/research_routes.py +13 -17
  67. local_deep_research/web/routes/settings_routes.py +264 -67
  68. local_deep_research/web/services/research_service.py +58 -73
  69. local_deep_research/web/services/settings_manager.py +1 -4
  70. local_deep_research/web/services/settings_service.py +4 -6
  71. local_deep_research/web/static/css/styles.css +12 -0
  72. local_deep_research/web/static/js/components/logpanel.js +164 -155
  73. local_deep_research/web/static/js/components/research.js +44 -3
  74. local_deep_research/web/static/js/components/settings.js +27 -0
  75. local_deep_research/web/static/js/services/socket.js +47 -0
  76. local_deep_research/web_search_engines/default_search_engines.py +38 -0
  77. local_deep_research/web_search_engines/engines/meta_search_engine.py +100 -33
  78. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +31 -17
  79. local_deep_research/web_search_engines/engines/search_engine_brave.py +8 -3
  80. local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +343 -0
  81. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +14 -6
  82. local_deep_research/web_search_engines/engines/search_engine_local.py +19 -23
  83. local_deep_research/web_search_engines/engines/search_engine_local_all.py +9 -12
  84. local_deep_research/web_search_engines/engines/search_engine_searxng.py +12 -17
  85. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +8 -4
  86. local_deep_research/web_search_engines/search_engine_base.py +22 -5
  87. local_deep_research/web_search_engines/search_engine_factory.py +30 -11
  88. local_deep_research/web_search_engines/search_engines_config.py +14 -1
  89. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/METADATA +10 -2
  90. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/RECORD +93 -51
  91. local_deep_research/app.py +0 -8
  92. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/WHEEL +0 -0
  93. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/entry_points.txt +0 -0
  94. {local_deep_research-0.3.12.dist-info → local_deep_research-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -9,6 +9,7 @@ from .__version__ import __version__
9
9
  from .config.llm_config import get_llm
10
10
  from .config.search_config import get_search
11
11
  from .report_generator import get_report_generator
12
+ from .web.app import main
12
13
 
13
14
 
14
15
  def get_advanced_search_system(strategy_name: str = "iterdrag"):
@@ -1 +1 @@
1
- __version__ = "0.3.12"
1
+ __version__ = "0.4.1"
@@ -3,17 +3,16 @@
3
3
  Base class for search result filters.
4
4
  """
5
5
 
6
- import logging
7
6
  from abc import ABC, abstractmethod
8
7
  from typing import Dict, List
9
8
 
10
- logger = logging.getLogger(__name__)
9
+ from langchain_core.language_models.chat_models import BaseChatModel
11
10
 
12
11
 
13
12
  class BaseFilter(ABC):
14
13
  """Abstract base class for all search result filters."""
15
14
 
16
- def __init__(self, model=None):
15
+ def __init__(self, model: BaseChatModel | None = None):
17
16
  """
18
17
  Initialize the filter.
19
18
 
@@ -3,15 +3,14 @@ Cross-engine search result filter implementation.
3
3
  """
4
4
 
5
5
  import json
6
- import logging
7
6
  from typing import Dict, List
8
7
 
8
+ from loguru import logger
9
+
9
10
  from ...utilities.db_utils import get_db_setting
10
11
  from ...utilities.search_utilities import remove_think_tags
11
12
  from .base_filter import BaseFilter
12
13
 
13
- logger = logging.getLogger(__name__)
14
-
15
14
 
16
15
  class CrossEngineFilter(BaseFilter):
17
16
  """Filter that ranks and filters results from multiple search engines."""
@@ -194,8 +193,8 @@ If no results seem relevant to the query, return an empty array: []"""
194
193
  result["index"] = str(i + start_index + 1)
195
194
  return top_results
196
195
 
197
- except Exception as e:
198
- logger.error(f"Cross-engine filtering error: {e}")
196
+ except Exception:
197
+ logger.exception("Cross-engine filtering error")
199
198
  top_results = results[: min(self.max_results, len(results))]
200
199
  # Update indices if requested
201
200
  if reindex:
@@ -0,0 +1,298 @@
1
+ import time
2
+ import traceback
3
+ from datetime import timedelta
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from langchain_core.language_models.chat_models import BaseChatModel
7
+ from loguru import logger
8
+ from methodtools import lru_cache
9
+
10
+ from ...config.llm_config import get_llm
11
+ from ...search_system import AdvancedSearchSystem
12
+ from ...utilities.db_utils import get_db_session, get_db_setting
13
+ from ...web.database.models import Journal
14
+ from ...web_search_engines.search_engine_factory import create_search_engine
15
+ from .base_filter import BaseFilter
16
+
17
+
18
+ class JournalFilterError(Exception):
19
+ """
20
+ Custom exception for errors related to journal filtering.
21
+ """
22
+
23
+
24
+ class JournalReputationFilter(BaseFilter):
25
+ """
26
+ A filter for academic results that considers the reputation of journals.
27
+
28
+ Note that this filter requires SearXNG to be available in order to work.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ model: BaseChatModel | None = None,
34
+ reliability_threshold: int | None = None,
35
+ max_context: int | None = None,
36
+ exclude_non_published: bool | None = None,
37
+ quality_reanalysis_period: timedelta | None = None,
38
+ ):
39
+ """
40
+ Args:
41
+ model: The LLM model to use for analysis.
42
+ reliability_threshold: The filter scores journal reliability on a
43
+ scale of 1-10. Results from any journal with a reliability
44
+ below this threshold will be culled. Will be read from the
45
+ settings if not specified.
46
+ max_context: The maximum number of characters to feed into the
47
+ LLM when assessing journal reliability.
48
+ exclude_non_published: If true, it will exclude any results that
49
+ don't have an associated journal publication.
50
+ quality_reanalysis_period: Period at which to update journal
51
+ quality assessments.
52
+
53
+ """
54
+ super().__init__(model)
55
+
56
+ if self.model is None:
57
+ self.model = get_llm()
58
+
59
+ self.__threshold = reliability_threshold
60
+ if self.__threshold is None:
61
+ self.__threshold = int(
62
+ get_db_setting("search.journal_reputation.threshold", 4)
63
+ )
64
+ self.__max_context = max_context
65
+ if self.__max_context is None:
66
+ self.__max_context = int(
67
+ get_db_setting("search.journal_reputation.max_context", 3000)
68
+ )
69
+ self.__exclude_non_published = exclude_non_published
70
+ if self.__exclude_non_published is None:
71
+ self.__exclude_non_published = bool(
72
+ get_db_setting("search.journal_reputation.exclude_non_published", False)
73
+ )
74
+ self.__quality_reanalysis_period = quality_reanalysis_period
75
+ if self.__quality_reanalysis_period is None:
76
+ self.__quality_reanalysis_period = timedelta(
77
+ days=int(
78
+ get_db_setting("search.journal_reputation.reanalysis_period", 365)
79
+ )
80
+ )
81
+
82
+ # SearXNG is required so we can search the open web for reputational
83
+ # information.
84
+ self.__engine = create_search_engine("searxng", llm=self.model)
85
+ if self.__engine is None:
86
+ raise JournalFilterError("SearXNG initialization failed.")
87
+
88
+ self.__db_session = get_db_session()
89
+
90
+ @classmethod
91
+ def create_default(
92
+ cls, model: BaseChatModel | None = None, *, engine_name: str
93
+ ) -> Optional["JournalReputationFilter"]:
94
+ """
95
+ Initializes a default configuration of the filter based on the settings.
96
+
97
+ Args:
98
+ model: Explicitly specify the LLM to use.
99
+ engine_name: The name of the search engine. Will be used to check
100
+ the enablement status for that engine.
101
+
102
+ Returns:
103
+ The filter that it created, or None if filtering is disabled in
104
+ the settings, or misconfigured.
105
+
106
+ """
107
+ if not bool(
108
+ get_db_setting(
109
+ f"search.engine.web.{engine_name}.journal_reputation.enabled",
110
+ True,
111
+ )
112
+ ):
113
+ return None
114
+
115
+ try:
116
+ # Initialize the filter with default settings.
117
+ return JournalReputationFilter(model=model)
118
+ except JournalFilterError:
119
+ logger.error(
120
+ "SearXNG is not configured, but is required for "
121
+ "journal reputation filtering. Disabling filtering."
122
+ )
123
+ return None
124
+
125
+ def __make_search_system(self) -> AdvancedSearchSystem:
126
+ """
127
+ Creates a new `AdvancedSearchSystem` instance.
128
+
129
+ Returns:
130
+ The system it created.
131
+
132
+ """
133
+ return AdvancedSearchSystem(
134
+ llm=self.model,
135
+ search=self.__engine,
136
+ # We clamp down on the default iterations and questions for speed.
137
+ max_iterations=2,
138
+ questions_per_iteration=3,
139
+ )
140
+
141
+ @lru_cache(maxsize=1024)
142
+ def __analyze_journal_reputation(self, journal_name: str) -> int:
143
+ """
144
+ Analyzes the reputation of a particular journal.
145
+
146
+ Args:
147
+ journal_name: The name of the journal.
148
+
149
+ Returns:
150
+ The reputation of the journal, on a scale from 1-10.
151
+
152
+ """
153
+ logger.info(f"Analyzing reputation of journal '{journal_name}'...")
154
+
155
+ # Perform a search for information about this journal.
156
+ journal_info = self.__make_search_system().analyze_topic(
157
+ f'Assess the reputability and reliability of the journal "'
158
+ f'{journal_name}", with a particular focus on its quartile '
159
+ f"ranking and peer review status. Be sure to specify the journal "
160
+ f"name in any generated questions."
161
+ )
162
+ journal_info = "\n".join([f["content"] for f in journal_info["findings"]])
163
+ logger.debug(f"Received raw info about journal: {journal_info}")
164
+
165
+ # Have the LLM assess the reliability based on this information.
166
+ prompt = f"""
167
+ You are a research assistant helping to assess the reliability and
168
+ reputability of scientific journals. A reputable journal should be
169
+ peer-reviewed, not predatory, and high-impact. Please review the
170
+ following information on the journal "{journal_name}" and output a
171
+ reputability score between 1 and 10, where 1-3 is not reputable and
172
+ probably predatory, 4-6 is reputable but low-impact (Q2 or Q3),
173
+ and 7-10 is reputable Q1 journals. Only output the number, do not
174
+ provide any explanation or other output.
175
+
176
+ JOURNAL INFORMATION:
177
+
178
+ {journal_info}
179
+ """
180
+ if len(prompt) > self.__max_context:
181
+ # If the prompt is too long, truncate it to fit within the max context size.
182
+ prompt = prompt[: self.__max_context] + "..."
183
+
184
+ # Generate a response from the LLM model.
185
+ response = self.model.invoke(prompt).text()
186
+ logger.debug(f"Got raw LLM response: {response}")
187
+
188
+ # Extract the score from the response.
189
+ try:
190
+ reputation_score = int(response.strip())
191
+ except ValueError:
192
+ logger.error("Failed to parse reputation score from LLM response.")
193
+ raise ValueError("Failed to parse reputation score from LLM response.")
194
+
195
+ return max(min(reputation_score, 10), 1)
196
+
197
+ def __add_journal_to_db(self, *, name: str, quality: int) -> None:
198
+ """
199
+ Saves the journal quality information to the database.
200
+
201
+ Args:
202
+ name: The name of the journal.
203
+ quality: The quality assessment for the journal.
204
+
205
+ """
206
+ journal = self.__db_session.query(Journal).filter_by(name=name).first()
207
+ if journal is not None:
208
+ journal.quality = quality
209
+ journal.quality_model = self.model.name
210
+ journal.quality_analysis_time = int(time.time())
211
+ else:
212
+ journal = Journal(
213
+ name=name,
214
+ quality=quality,
215
+ quality_model=self.model.name,
216
+ quality_analysis_time=int(time.time()),
217
+ )
218
+ self.__db_session.add(journal)
219
+
220
+ self.__db_session.commit()
221
+
222
+ def __clean_journal_name(self, journal_name: str) -> str:
223
+ """
224
+ Cleans up the name of a journal to remove any extraneous information.
225
+ This is mostly to make caching more effective.
226
+
227
+ Args:
228
+ journal_name: The raw name of the journal.
229
+
230
+ Returns:
231
+ The cleaned name.
232
+
233
+ """
234
+ logger.debug(f"Cleaning raw journal name: {journal_name}")
235
+
236
+ prompt = f"""
237
+ Clean up the following journal or conference name:
238
+
239
+ "{journal_name}"
240
+
241
+ Remove any references to volumes, pages, months, or years. Expand
242
+ abbreviations if possible. For conferences, remove locations. Only
243
+ output the clean name, do not provide any explanation or other output.
244
+ """
245
+
246
+ response = self.model.invoke(prompt).text()
247
+ return response.strip()
248
+
249
+ def __check_result(self, result: Dict[str, Any]) -> bool:
250
+ """
251
+ Performs a search to determine the reputability of a result journal..
252
+
253
+ Args:
254
+ result: The result to check.
255
+
256
+ Returns:
257
+ True if the journal is reputable or if it couldn't determine a
258
+ reputability score, false otherwise.
259
+
260
+ """
261
+ journal_name = result.get("journal_ref")
262
+ if journal_name is None:
263
+ logger.debug(
264
+ f"Result {result.get('title')} has no associated "
265
+ f"journal, not evaluating reputation."
266
+ )
267
+ return not self.__exclude_non_published
268
+ journal_name = self.__clean_journal_name(journal_name)
269
+
270
+ # Check the database first.
271
+ journal = self.__db_session.query(Journal).filter_by(name=journal_name).first()
272
+ if (
273
+ journal is not None
274
+ and (time.time() - journal.quality_analysis_time)
275
+ < self.__quality_reanalysis_period.total_seconds()
276
+ ):
277
+ logger.debug(f"Found existing reputation for {journal_name} in database.")
278
+ return journal.quality >= self.__threshold
279
+
280
+ # Evaluate reputation.
281
+ try:
282
+ quality = self.__analyze_journal_reputation(journal_name)
283
+ # Save to the database.
284
+ self.__add_journal_to_db(name=journal_name, quality=quality)
285
+ return quality >= self.__threshold
286
+ except ValueError:
287
+ # The LLM behaved weirdly. In this case, we will just assume it's
288
+ # okay.
289
+ return True
290
+
291
+ def filter_results(self, results: List[Dict], query: str, **kwargs) -> List[Dict]:
292
+ try:
293
+ return list(filter(self.__check_result, results))
294
+ except Exception as e:
295
+ logger.error(
296
+ f"Journal quality filtering failed: {e}, {traceback.format_exc()}"
297
+ )
298
+ return results
@@ -291,9 +291,6 @@ Use IEEE style citations [1], [2], etc. Never make up your own citations.
291
291
 
292
292
  # Check if we're on Windows
293
293
  if platform.system() == "Windows":
294
- # Windows-compatible timeout using threading
295
- class TimeoutError(Exception):
296
- pass
297
294
 
298
295
  def timeout_handler(timeout_seconds, callback, args):
299
296
  def handler():
@@ -3,11 +3,10 @@ Base class for all search strategies.
3
3
  Defines the common interface and shared functionality for different search approaches.
4
4
  """
5
5
 
6
- import logging
7
6
  from abc import ABC, abstractmethod
8
7
  from typing import Callable, Dict, List, Optional
9
8
 
10
- logger = logging.getLogger(__name__)
9
+ from loguru import logger
11
10
 
12
11
 
13
12
  class BaseSearchStrategy(ABC):
@@ -3,10 +3,11 @@ IterDRAG strategy implementation.
3
3
  """
4
4
 
5
5
  import json
6
- import logging
7
6
  from datetime import datetime
8
7
  from typing import Dict, List
9
8
 
9
+ from loguru import logger
10
+
10
11
  from ...citation_handler import CitationHandler
11
12
  from ...config.llm_config import get_llm
12
13
  from ...config.search_config import get_search
@@ -17,8 +18,6 @@ from ..knowledge.standard_knowledge import StandardKnowledge
17
18
  from ..questions.decomposition_question import DecompositionQuestionGenerator
18
19
  from .base_strategy import BaseSearchStrategy
19
20
 
20
- logger = logging.getLogger(__name__)
21
-
22
21
 
23
22
  class IterDRAGStrategy(BaseSearchStrategy):
24
23
  """IterDRAG strategy that breaks queries into sub-queries."""
@@ -83,8 +82,8 @@ Initial Search Results:
83
82
  return self.question_generator.generate_questions(
84
83
  query, context, int(get_db_setting("search.questions_per_iteration"))
85
84
  )
86
- except Exception as e:
87
- logger.error(f"Error generating sub-queries: {str(e)}")
85
+ except Exception:
86
+ logger.exception("Error generating sub-queries")
88
87
  return []
89
88
 
90
89
  def analyze_topic(self, query: str) -> Dict:
@@ -204,8 +203,8 @@ Initial Search Results:
204
203
  "result_count": len(sub_results),
205
204
  },
206
205
  )
207
- except Exception as e:
208
- logger.error(f"Error searching for sub-query: {str(e)}")
206
+ except Exception:
207
+ logger.exception("Error searching for sub-query")
209
208
  sub_results = []
210
209
 
211
210
  try:
@@ -238,8 +237,8 @@ Initial Search Results:
238
237
  current_knowledge = (
239
238
  current_knowledge + "\n\n\n New: \n" + result["content"]
240
239
  )
241
- except Exception as e:
242
- logger.error(f"Error analyzing sub-query results: {str(e)}")
240
+ except Exception:
241
+ logger.exception("Error analyzing sub-query results:")
243
242
  finding = {
244
243
  "phase": f"Follow-up Iteration 0.{i + 1}",
245
244
  "content": "Error analyzing sub-query results.",
@@ -344,10 +343,7 @@ This is a fallback response using the accumulated knowledge.
344
343
  # Update current knowledge with the synthesized version
345
344
  current_knowledge = final_answer
346
345
  except Exception as e:
347
- logger.error(f"Error synthesizing final answer: {str(e)}")
348
- import traceback
349
-
350
- logger.error(traceback.format_exc())
346
+ logger.exception("Error synthesizing final answer")
351
347
 
352
348
  # Create an error finding
353
349
  error_finding = {
@@ -396,7 +392,7 @@ This is an automatically generated fallback response.
396
392
  final_answer = fallback_content
397
393
  except Exception as fallback_error:
398
394
  # Last resort fallback
399
- logger.error(f"Even fallback creation failed: {fallback_error}")
395
+ logger.exception("Even fallback creation failed")
400
396
  final_answer = f"""
401
397
  # Research Error
402
398
 
@@ -417,8 +413,8 @@ Please try again with a different query or contact support.
417
413
  current_knowledge = self.knowledge_generator.compress_knowledge(
418
414
  current_knowledge, query, section_links
419
415
  )
420
- except Exception as e:
421
- logger.error(f"Error compressing knowledge: {str(e)}")
416
+ except Exception:
417
+ logger.exception("Error compressing knowledge")
422
418
 
423
419
  # Format and save findings
424
420
  self._update_progress(
@@ -442,8 +438,8 @@ Please try again with a different query or contact support.
442
438
  formatted_findings = self.findings_repository.format_findings_to_text(
443
439
  findings, final_answer
444
440
  )
445
- except Exception as e:
446
- logger.error(f"Error formatting final findings: {str(e)}")
441
+ except Exception:
442
+ logger.exception("Error formatting final findings")
447
443
  formatted_findings = "Error: Could not format findings due to an error."
448
444
 
449
445
  self._update_progress("Research complete", 100, {"phase": "complete"})
@@ -3,9 +3,10 @@ Parallel search strategy implementation for maximum search speed.
3
3
  """
4
4
 
5
5
  import concurrent.futures
6
- import logging
7
6
  from typing import Dict
8
7
 
8
+ from loguru import logger
9
+
9
10
  from ...citation_handler import CitationHandler
10
11
  from ...config.llm_config import get_llm
11
12
  from ...config.search_config import get_search
@@ -16,8 +17,6 @@ from ..findings.repository import FindingsRepository
16
17
  from ..questions.standard_question import StandardQuestionGenerator
17
18
  from .base_strategy import BaseSearchStrategy
18
19
 
19
- logger = logging.getLogger(__name__)
20
-
21
20
 
22
21
  class ParallelSearchStrategy(BaseSearchStrategy):
23
22
  """
@@ -212,7 +211,7 @@ class ParallelSearchStrategy(BaseSearchStrategy):
212
211
  result = self.search.run(q)
213
212
  return {"question": q, "results": result or []}
214
213
  except Exception as e:
215
- logger.error(f"Error searching for '{q}': {str(e)}")
214
+ logger.exception(f"Error searching for '{q}'")
216
215
  return {"question": q, "results": [], "error": str(e)}
217
216
 
218
217
  # Run searches in parallel
@@ -408,11 +407,8 @@ class ParallelSearchStrategy(BaseSearchStrategy):
408
407
  )
409
408
 
410
409
  except Exception as e:
411
- import traceback
412
-
413
410
  error_msg = f"Error in research process: {str(e)}"
414
- logger.error(error_msg)
415
- logger.error(traceback.format_exc())
411
+ logger.exception(error_msg)
416
412
  synthesized_content = f"Error: {str(e)}"
417
413
  formatted_findings = f"Error: {str(e)}"
418
414
  finding = {
@@ -2,9 +2,10 @@
2
2
  RapidSearch strategy implementation.
3
3
  """
4
4
 
5
- import logging
6
5
  from typing import Dict
7
6
 
7
+ from loguru import logger
8
+
8
9
  from ...citation_handler import CitationHandler
9
10
  from ...config.llm_config import get_llm
10
11
  from ...config.search_config import get_search
@@ -14,8 +15,6 @@ from ..knowledge.standard_knowledge import StandardKnowledge
14
15
  from ..questions.standard_question import StandardQuestionGenerator
15
16
  from .base_strategy import BaseSearchStrategy
16
17
 
17
- logger = logging.getLogger(__name__)
18
-
19
18
 
20
19
  class RapidSearchStrategy(BaseSearchStrategy):
21
20
  """
@@ -116,7 +115,7 @@ class RapidSearchStrategy(BaseSearchStrategy):
116
115
 
117
116
  except Exception as e:
118
117
  error_msg = f"Error during initial search: {str(e)}"
119
- logger.error(f"SEARCH ERROR: {error_msg}")
118
+ logger.exception(f"SEARCH ERROR: {error_msg}")
120
119
  self._update_progress(
121
120
  error_msg, 15, {"phase": "search_error", "error": str(e)}
122
121
  )
@@ -187,7 +186,7 @@ class RapidSearchStrategy(BaseSearchStrategy):
187
186
 
188
187
  except Exception as e:
189
188
  error_msg = f"Error during search: {str(e)}"
190
- logger.error(f"SEARCH ERROR: {error_msg}")
189
+ logger.exception(f"SEARCH ERROR: {error_msg}")
191
190
  self._update_progress(
192
191
  error_msg,
193
192
  int(question_progress + 2),
@@ -248,7 +247,7 @@ class RapidSearchStrategy(BaseSearchStrategy):
248
247
 
249
248
  except Exception as e:
250
249
  error_msg = f"Error synthesizing final answer: {str(e)}"
251
- logger.error(error_msg)
250
+ logger.exception(error_msg)
252
251
  synthesized_content = f"Error generating synthesis: {str(e)}"
253
252
  formatted_findings = f"Error: {str(e)}"
254
253
  finding = {
@@ -115,7 +115,7 @@ class SourceBasedSearchStrategy(BaseSearchStrategy):
115
115
  }
116
116
 
117
117
  # Determine number of iterations to run
118
- iterations_to_run = get_db_setting("search.iterations")
118
+ iterations_to_run = get_db_setting("search.iterations", 2)
119
119
  logger.debug("Selected amount of iterations: " + str(iterations_to_run))
120
120
  iterations_to_run = int(iterations_to_run)
121
121
  try:
@@ -177,7 +177,7 @@ class SourceBasedSearchStrategy(BaseSearchStrategy):
177
177
  current_knowledge=context,
178
178
  query=query,
179
179
  questions_per_iteration=int(
180
- get_db_setting("search.questions_per_iteration")
180
+ get_db_setting("search.questions_per_iteration", 2)
181
181
  ),
182
182
  questions_by_iteration=self.questions_by_iteration,
183
183
  )
@@ -1,7 +1,8 @@
1
1
  import json
2
- import logging
3
2
  from typing import Dict
4
3
 
4
+ from loguru import logger
5
+
5
6
  from ...citation_handler import CitationHandler
6
7
  from ...config.llm_config import get_llm
7
8
  from ...config.search_config import get_search
@@ -13,8 +14,6 @@ from ..knowledge.standard_knowledge import StandardKnowledge
13
14
  from ..questions.standard_question import StandardQuestionGenerator
14
15
  from .base_strategy import BaseSearchStrategy
15
16
 
16
- logger = logging.getLogger(__name__)
17
-
18
17
 
19
18
  class StandardSearchStrategy(BaseSearchStrategy):
20
19
  """Standard iterative search strategy that generates follow-up questions."""
@@ -112,7 +111,10 @@ Iteration: {iteration + 1} of {total_iterations}"""
112
111
 
113
112
  # Call question generator with updated interface
114
113
  questions = self.question_generator.generate_questions(
115
- query=query, context=context
114
+ query=query,
115
+ current_knowledge=context,
116
+ questions_per_iteration=self.questions_per_iteration,
117
+ questions_by_iteration=self.questions_by_iteration,
116
118
  )
117
119
 
118
120
  self.questions_by_iteration[iteration] = questions
@@ -153,7 +155,7 @@ Iteration: {iteration + 1} of {total_iterations}"""
153
155
  search_results = self.search.run(question)
154
156
  except Exception as e:
155
157
  error_msg = f"Error during search: {str(e)}"
156
- logger.error(f"SEARCH ERROR: {error_msg}")
158
+ logger.exception(f"SEARCH ERROR: {error_msg}")
157
159
  self._handle_search_error(error_msg, question_progress_base + 10)
158
160
  search_results = []
159
161
 
@@ -237,7 +239,7 @@ Iteration: {iteration + 1} of {total_iterations}"""
237
239
  )
238
240
  except Exception as e:
239
241
  error_msg = f"Error analyzing results: {str(e)}"
240
- logger.info(f"ANALYSIS ERROR: {error_msg}")
242
+ logger.exception(f"ANALYSIS ERROR: {error_msg}")
241
243
  self._handle_search_error(error_msg, question_progress_base + 10)
242
244
 
243
245
  iteration += 1
@@ -257,7 +259,7 @@ Iteration: {iteration + 1} of {total_iterations}"""
257
259
  logger.info("FINISHED ITERATION - Compressing Knowledge")
258
260
  except Exception as e:
259
261
  error_msg = f"Error compressing knowledge: {str(e)}"
260
- logger.info(f"COMPRESSION ERROR: {error_msg}")
262
+ logger.exception(f"COMPRESSION ERROR: {error_msg}")
261
263
  self._handle_search_error(
262
264
  error_msg, int((iteration / total_iterations) * 100 - 3)
263
265
  )