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.
Files changed (140) hide show
  1. local_deep_research/__init__.py +23 -22
  2. local_deep_research/__main__.py +16 -0
  3. local_deep_research/advanced_search_system/__init__.py +7 -0
  4. local_deep_research/advanced_search_system/filters/__init__.py +8 -0
  5. local_deep_research/advanced_search_system/filters/base_filter.py +38 -0
  6. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +200 -0
  7. local_deep_research/advanced_search_system/findings/base_findings.py +81 -0
  8. local_deep_research/advanced_search_system/findings/repository.py +452 -0
  9. local_deep_research/advanced_search_system/knowledge/__init__.py +1 -0
  10. local_deep_research/advanced_search_system/knowledge/base_knowledge.py +151 -0
  11. local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +159 -0
  12. local_deep_research/advanced_search_system/questions/__init__.py +1 -0
  13. local_deep_research/advanced_search_system/questions/base_question.py +64 -0
  14. local_deep_research/advanced_search_system/questions/decomposition_question.py +445 -0
  15. local_deep_research/advanced_search_system/questions/standard_question.py +119 -0
  16. local_deep_research/advanced_search_system/repositories/__init__.py +7 -0
  17. local_deep_research/advanced_search_system/strategies/__init__.py +1 -0
  18. local_deep_research/advanced_search_system/strategies/base_strategy.py +118 -0
  19. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +450 -0
  20. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +312 -0
  21. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +270 -0
  22. local_deep_research/advanced_search_system/strategies/standard_strategy.py +300 -0
  23. local_deep_research/advanced_search_system/tools/__init__.py +1 -0
  24. local_deep_research/advanced_search_system/tools/base_tool.py +100 -0
  25. local_deep_research/advanced_search_system/tools/knowledge_tools/__init__.py +1 -0
  26. local_deep_research/advanced_search_system/tools/question_tools/__init__.py +1 -0
  27. local_deep_research/advanced_search_system/tools/search_tools/__init__.py +1 -0
  28. local_deep_research/api/__init__.py +5 -5
  29. local_deep_research/api/research_functions.py +154 -160
  30. local_deep_research/app.py +8 -0
  31. local_deep_research/citation_handler.py +25 -16
  32. local_deep_research/{config.py → config/config_files.py} +102 -110
  33. local_deep_research/config/llm_config.py +472 -0
  34. local_deep_research/config/search_config.py +77 -0
  35. local_deep_research/defaults/__init__.py +10 -5
  36. local_deep_research/defaults/main.toml +2 -2
  37. local_deep_research/defaults/search_engines.toml +60 -34
  38. local_deep_research/main.py +121 -19
  39. local_deep_research/migrate_db.py +147 -0
  40. local_deep_research/report_generator.py +87 -45
  41. local_deep_research/search_system.py +153 -283
  42. local_deep_research/setup_data_dir.py +35 -0
  43. local_deep_research/test_migration.py +178 -0
  44. local_deep_research/utilities/__init__.py +0 -0
  45. local_deep_research/utilities/db_utils.py +49 -0
  46. local_deep_research/{utilties → utilities}/enums.py +2 -2
  47. local_deep_research/{utilties → utilities}/llm_utils.py +63 -29
  48. local_deep_research/utilities/search_utilities.py +242 -0
  49. local_deep_research/{utilties → utilities}/setup_utils.py +4 -2
  50. local_deep_research/web/__init__.py +0 -1
  51. local_deep_research/web/app.py +86 -1709
  52. local_deep_research/web/app_factory.py +289 -0
  53. local_deep_research/web/database/README.md +70 -0
  54. local_deep_research/web/database/migrate_to_ldr_db.py +289 -0
  55. local_deep_research/web/database/migrations.py +447 -0
  56. local_deep_research/web/database/models.py +117 -0
  57. local_deep_research/web/database/schema_upgrade.py +107 -0
  58. local_deep_research/web/models/database.py +294 -0
  59. local_deep_research/web/models/settings.py +94 -0
  60. local_deep_research/web/routes/api_routes.py +559 -0
  61. local_deep_research/web/routes/history_routes.py +354 -0
  62. local_deep_research/web/routes/research_routes.py +715 -0
  63. local_deep_research/web/routes/settings_routes.py +1583 -0
  64. local_deep_research/web/services/research_service.py +947 -0
  65. local_deep_research/web/services/resource_service.py +149 -0
  66. local_deep_research/web/services/settings_manager.py +669 -0
  67. local_deep_research/web/services/settings_service.py +187 -0
  68. local_deep_research/web/services/socket_service.py +210 -0
  69. local_deep_research/web/static/css/custom_dropdown.css +277 -0
  70. local_deep_research/web/static/css/settings.css +1223 -0
  71. local_deep_research/web/static/css/styles.css +525 -48
  72. local_deep_research/web/static/js/components/custom_dropdown.js +428 -0
  73. local_deep_research/web/static/js/components/detail.js +348 -0
  74. local_deep_research/web/static/js/components/fallback/formatting.js +122 -0
  75. local_deep_research/web/static/js/components/fallback/ui.js +215 -0
  76. local_deep_research/web/static/js/components/history.js +487 -0
  77. local_deep_research/web/static/js/components/logpanel.js +949 -0
  78. local_deep_research/web/static/js/components/progress.js +1107 -0
  79. local_deep_research/web/static/js/components/research.js +1865 -0
  80. local_deep_research/web/static/js/components/results.js +766 -0
  81. local_deep_research/web/static/js/components/settings.js +3981 -0
  82. local_deep_research/web/static/js/components/settings_sync.js +106 -0
  83. local_deep_research/web/static/js/main.js +226 -0
  84. local_deep_research/web/static/js/services/api.js +253 -0
  85. local_deep_research/web/static/js/services/audio.js +31 -0
  86. local_deep_research/web/static/js/services/formatting.js +119 -0
  87. local_deep_research/web/static/js/services/pdf.js +622 -0
  88. local_deep_research/web/static/js/services/socket.js +882 -0
  89. local_deep_research/web/static/js/services/ui.js +546 -0
  90. local_deep_research/web/templates/base.html +72 -0
  91. local_deep_research/web/templates/components/custom_dropdown.html +47 -0
  92. local_deep_research/web/templates/components/log_panel.html +32 -0
  93. local_deep_research/web/templates/components/mobile_nav.html +22 -0
  94. local_deep_research/web/templates/components/settings_form.html +299 -0
  95. local_deep_research/web/templates/components/sidebar.html +21 -0
  96. local_deep_research/web/templates/pages/details.html +73 -0
  97. local_deep_research/web/templates/pages/history.html +51 -0
  98. local_deep_research/web/templates/pages/progress.html +57 -0
  99. local_deep_research/web/templates/pages/research.html +139 -0
  100. local_deep_research/web/templates/pages/results.html +59 -0
  101. local_deep_research/web/templates/settings_dashboard.html +78 -192
  102. local_deep_research/web/utils/__init__.py +0 -0
  103. local_deep_research/web/utils/formatters.py +76 -0
  104. local_deep_research/web_search_engines/engines/full_search.py +18 -16
  105. local_deep_research/web_search_engines/engines/meta_search_engine.py +182 -131
  106. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +224 -139
  107. local_deep_research/web_search_engines/engines/search_engine_brave.py +88 -71
  108. local_deep_research/web_search_engines/engines/search_engine_ddg.py +48 -39
  109. local_deep_research/web_search_engines/engines/search_engine_github.py +415 -204
  110. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +123 -90
  111. local_deep_research/web_search_engines/engines/search_engine_guardian.py +210 -157
  112. local_deep_research/web_search_engines/engines/search_engine_local.py +532 -369
  113. local_deep_research/web_search_engines/engines/search_engine_local_all.py +42 -36
  114. local_deep_research/web_search_engines/engines/search_engine_pubmed.py +358 -266
  115. local_deep_research/web_search_engines/engines/search_engine_searxng.py +212 -160
  116. local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +213 -170
  117. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +84 -68
  118. local_deep_research/web_search_engines/engines/search_engine_wayback.py +186 -154
  119. local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +115 -77
  120. local_deep_research/web_search_engines/search_engine_base.py +174 -99
  121. local_deep_research/web_search_engines/search_engine_factory.py +192 -102
  122. local_deep_research/web_search_engines/search_engines_config.py +22 -15
  123. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/METADATA +177 -97
  124. local_deep_research-0.2.2.dist-info/RECORD +135 -0
  125. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/WHEEL +1 -2
  126. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/entry_points.txt +3 -0
  127. local_deep_research/defaults/llm_config.py +0 -338
  128. local_deep_research/utilties/search_utilities.py +0 -114
  129. local_deep_research/web/static/js/app.js +0 -3763
  130. local_deep_research/web/templates/api_keys_config.html +0 -82
  131. local_deep_research/web/templates/collections_config.html +0 -90
  132. local_deep_research/web/templates/index.html +0 -348
  133. local_deep_research/web/templates/llm_config.html +0 -120
  134. local_deep_research/web/templates/main_config.html +0 -89
  135. local_deep_research/web/templates/search_engines_config.html +0 -154
  136. local_deep_research/web/templates/settings.html +0 -519
  137. local_deep_research-0.1.26.dist-info/RECORD +0 -61
  138. local_deep_research-0.1.26.dist-info/top_level.txt +0 -1
  139. /local_deep_research/{utilties → config}/__init__.py +0 -0
  140. {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,300 @@
1
+ import json
2
+ import logging
3
+ from typing import Dict
4
+
5
+ from ...citation_handler import CitationHandler
6
+ from ...config.config_files import settings
7
+ from ...config.llm_config import get_llm
8
+ from ...config.search_config import get_search
9
+ from ...utilities.db_utils import get_db_setting
10
+ from ...utilities.enums import KnowledgeAccumulationApproach
11
+ from ...utilities.search_utilities import extract_links_from_search_results
12
+ from ..findings.repository import FindingsRepository
13
+ from ..knowledge.standard_knowledge import StandardKnowledge
14
+ from ..questions.standard_question import StandardQuestionGenerator
15
+ from .base_strategy import BaseSearchStrategy
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class StandardSearchStrategy(BaseSearchStrategy):
21
+ """Standard iterative search strategy that generates follow-up questions."""
22
+
23
+ def __init__(self, search=None, model=None, citation_handler=None):
24
+ """Initialize with optional dependency injection for testing."""
25
+ self.search = search or get_search()
26
+ self.model = model or get_llm()
27
+ self.max_iterations = int(get_db_setting("search.iterations"))
28
+ self.questions_per_iteration = int(
29
+ get_db_setting("search.questions_per_iteration")
30
+ )
31
+ self.context_limit = int(
32
+ get_db_setting("general.knowledge_accumulation_context_limit")
33
+ )
34
+ self.questions_by_iteration = {}
35
+
36
+ # Use provided citation_handler or create one
37
+ self.citation_handler = citation_handler or CitationHandler(self.model)
38
+
39
+ # Initialize specialized components
40
+ self.question_generator = StandardQuestionGenerator(self.model)
41
+ self.knowledge_generator = StandardKnowledge(self.model)
42
+ self.findings_repository = FindingsRepository(self.model)
43
+
44
+ # Initialize other attributes
45
+ self.progress_callback = None
46
+ self.all_links_of_system = list()
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 analyze_topic(self, query: str) -> Dict:
56
+ """Standard implementation of the topic analysis process."""
57
+ logger.info(f"Starting research on topic: {query}")
58
+
59
+ findings = []
60
+ current_knowledge = ""
61
+ iteration = 0
62
+ total_iterations = self.max_iterations
63
+ section_links = list()
64
+
65
+ self._update_progress(
66
+ "Initializing research system",
67
+ 5,
68
+ {"phase": "init", "iterations_planned": total_iterations},
69
+ )
70
+
71
+ # Check if search engine is available
72
+ if self.search is None:
73
+ error_msg = (
74
+ "Error: No search engine available. Please check your configuration."
75
+ )
76
+ self._update_progress(
77
+ error_msg,
78
+ 100,
79
+ {
80
+ "phase": "error",
81
+ "error": "No search engine available",
82
+ "status": "failed",
83
+ },
84
+ )
85
+ return {
86
+ "findings": [],
87
+ "iterations": 0,
88
+ "questions": {},
89
+ "formatted_findings": "Error: Unable to conduct research without a search engine.",
90
+ "current_knowledge": "",
91
+ "error": error_msg,
92
+ }
93
+
94
+ while iteration < self.max_iterations:
95
+ iteration_progress_base = (iteration / total_iterations) * 100
96
+ self._update_progress(
97
+ f"Starting iteration {iteration + 1} of {total_iterations}",
98
+ int(iteration_progress_base),
99
+ {"phase": "iteration_start", "iteration": iteration + 1},
100
+ )
101
+
102
+ # Generate questions for this iteration using the question generator
103
+ # Prepare context for question generation
104
+ context = f"""Current Query: {query}
105
+ Current Knowledge: {current_knowledge}
106
+ Previous Questions: {json.dumps(self.questions_by_iteration, indent=2)}
107
+ Iteration: {iteration + 1} of {total_iterations}"""
108
+
109
+ # Call question generator with updated interface
110
+ questions = self.question_generator.generate_questions(
111
+ query=query, context=context
112
+ )
113
+
114
+ self.questions_by_iteration[iteration] = questions
115
+ logger.info(f"Generated questions: {questions}")
116
+
117
+ question_count = len(questions)
118
+ knowledge_accumulation = get_db_setting(
119
+ "general.knowledge_accumulation",
120
+ settings.general.knowledge_accumulation,
121
+ )
122
+ for q_idx, question in enumerate(questions):
123
+ question_progress_base = iteration_progress_base + (
124
+ ((q_idx + 1) / question_count) * (100 / total_iterations) * 0.5
125
+ )
126
+
127
+ self._update_progress(
128
+ f"Searching for: {question}",
129
+ int(question_progress_base),
130
+ {
131
+ "phase": "search",
132
+ "iteration": iteration + 1,
133
+ "question_index": q_idx + 1,
134
+ },
135
+ )
136
+
137
+ try:
138
+ if self.search is None:
139
+ self._update_progress(
140
+ f"Search engine unavailable, skipping search for: {question}",
141
+ int(question_progress_base + 2),
142
+ {
143
+ "phase": "search_error",
144
+ "error": "No search engine available",
145
+ },
146
+ )
147
+ search_results = []
148
+ else:
149
+ search_results = self.search.run(question)
150
+ except Exception as e:
151
+ error_msg = f"Error during search: {str(e)}"
152
+ logger.error(f"SEARCH ERROR: {error_msg}")
153
+ self._handle_search_error(error_msg, question_progress_base + 10)
154
+ search_results = []
155
+
156
+ if search_results is None:
157
+ self._update_progress(
158
+ f"No search results found for question: {question}",
159
+ int(question_progress_base + 2),
160
+ {"phase": "search_complete", "result_count": 0},
161
+ )
162
+ search_results = [] # Initialize to empty list instead of None
163
+ continue
164
+
165
+ self._update_progress(
166
+ f"Found {len(search_results)} results for question: {question}",
167
+ int(question_progress_base + 2),
168
+ {"phase": "search_complete", "result_count": len(search_results)},
169
+ )
170
+
171
+ logger.info(f"len search: {len(search_results)}")
172
+
173
+ if len(search_results) == 0:
174
+ continue
175
+
176
+ self._update_progress(
177
+ f"Analyzing results for: {question}",
178
+ int(question_progress_base + 5),
179
+ {"phase": "analysis"},
180
+ )
181
+
182
+ try:
183
+ result = self.citation_handler.analyze_followup(
184
+ question,
185
+ search_results,
186
+ current_knowledge,
187
+ nr_of_links=len(self.all_links_of_system),
188
+ )
189
+ links = extract_links_from_search_results(search_results)
190
+ self.all_links_of_system.extend(links)
191
+ section_links.extend(links)
192
+
193
+ if result is not None:
194
+ results_with_links = str(result["content"])
195
+ findings.append(
196
+ {
197
+ "phase": f"Follow-up {iteration}.{questions.index(question) + 1}",
198
+ "content": results_with_links,
199
+ "question": question,
200
+ "search_results": search_results,
201
+ "documents": result["documents"],
202
+ }
203
+ )
204
+
205
+ if knowledge_accumulation != str(
206
+ KnowledgeAccumulationApproach.NO_KNOWLEDGE.value
207
+ ):
208
+ current_knowledge = (
209
+ current_knowledge
210
+ + "\n\n\n New: \n"
211
+ + results_with_links
212
+ )
213
+
214
+ if knowledge_accumulation == str(
215
+ KnowledgeAccumulationApproach.QUESTION.value
216
+ ):
217
+ logger.info("Compressing knowledge")
218
+ self._update_progress(
219
+ f"Compress Knowledge for: {question}",
220
+ int(question_progress_base + 0),
221
+ {"phase": "analysis"},
222
+ )
223
+ current_knowledge = (
224
+ self.knowledge_generator.compress_knowledge(
225
+ current_knowledge, query, section_links
226
+ )
227
+ )
228
+
229
+ self._update_progress(
230
+ f"Analysis complete for question: {question}",
231
+ int(question_progress_base + 10),
232
+ {"phase": "analysis_complete"},
233
+ )
234
+ except Exception as e:
235
+ error_msg = f"Error analyzing results: {str(e)}"
236
+ logger.info(f"ANALYSIS ERROR: {error_msg}")
237
+ self._handle_search_error(error_msg, question_progress_base + 10)
238
+
239
+ iteration += 1
240
+
241
+ self._update_progress(
242
+ f"Compressing knowledge after iteration {iteration}",
243
+ int((iteration / total_iterations) * 100 - 5),
244
+ {"phase": "knowledge_compression"},
245
+ )
246
+
247
+ if knowledge_accumulation == KnowledgeAccumulationApproach.ITERATION.value:
248
+ try:
249
+ logger.info("ITERATION - Compressing Knowledge")
250
+ current_knowledge = self.knowledge_generator.compress_knowledge(
251
+ current_knowledge, query, section_links
252
+ )
253
+ logger.info("FINISHED ITERATION - Compressing Knowledge")
254
+ except Exception as e:
255
+ error_msg = f"Error compressing knowledge: {str(e)}"
256
+ logger.info(f"COMPRESSION ERROR: {error_msg}")
257
+ self._handle_search_error(
258
+ error_msg, int((iteration / total_iterations) * 100 - 3)
259
+ )
260
+
261
+ self._update_progress(
262
+ f"Iteration {iteration} complete",
263
+ int((iteration / total_iterations) * 100),
264
+ {"phase": "iteration_complete", "iteration": iteration},
265
+ )
266
+
267
+ # Extract content from findings for synthesis
268
+ finding_contents = [f["content"] for f in findings if "content" in f]
269
+
270
+ # First synthesize findings to get coherent content
271
+ synthesized_content = self.findings_repository.synthesize_findings(
272
+ query,
273
+ finding_contents,
274
+ findings, # Pass the full findings list with search results
275
+ accumulated_knowledge=current_knowledge,
276
+ old_formatting=False, # Don't format here, just synthesize content
277
+ )
278
+
279
+ # Transfer questions to the repository
280
+ self.findings_repository.set_questions_by_iteration(
281
+ self.questions_by_iteration
282
+ )
283
+
284
+ # Now format the findings with search questions and sources
285
+ formatted_findings = self.findings_repository.format_findings_to_text(
286
+ findings, synthesized_content
287
+ )
288
+
289
+ # Add the synthesized content to the repository
290
+ self.findings_repository.add_finding(query, synthesized_content)
291
+
292
+ self._update_progress("Research complete", 95, {"phase": "complete"})
293
+
294
+ return {
295
+ "findings": findings,
296
+ "iterations": iteration,
297
+ "questions": self.questions_by_iteration,
298
+ "formatted_findings": formatted_findings,
299
+ "current_knowledge": current_knowledge,
300
+ }
@@ -0,0 +1 @@
1
+ # Search System Tools Package
@@ -0,0 +1,100 @@
1
+ """
2
+ Base class for all agent-compatible tools.
3
+ Defines the common interface and shared functionality for different tools.
4
+ """
5
+
6
+ import logging
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any, Dict
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class BaseTool(ABC):
14
+ """Abstract base class for all agent-compatible tools."""
15
+
16
+ def __init__(self, name: str, description: str):
17
+ """
18
+ Initialize the tool.
19
+
20
+ Args:
21
+ name: The name of the tool
22
+ description: A description of what the tool does
23
+ """
24
+ self.name = name
25
+ self.description = description
26
+ self.parameters: Dict[str, Dict[str, Any]] = {}
27
+
28
+ @abstractmethod
29
+ def execute(self, **kwargs) -> Any:
30
+ """
31
+ Execute the tool with the given parameters.
32
+
33
+ Args:
34
+ **kwargs: Tool-specific parameters
35
+
36
+ Returns:
37
+ Any: The result of the tool execution
38
+ """
39
+ pass
40
+
41
+ def get_schema(self) -> Dict[str, Any]:
42
+ """
43
+ Get the JSON schema for the tool's parameters.
44
+
45
+ Returns:
46
+ Dict[str, Any]: The JSON schema
47
+ """
48
+ return {
49
+ "name": self.name,
50
+ "description": self.description,
51
+ "parameters": self.parameters,
52
+ }
53
+
54
+ def validate_parameters(self, **kwargs) -> bool:
55
+ """
56
+ Validate the provided parameters against the tool's schema.
57
+
58
+ Args:
59
+ **kwargs: Parameters to validate
60
+
61
+ Returns:
62
+ bool: True if parameters are valid, False otherwise
63
+ """
64
+ for param_name, param_schema in self.parameters.items():
65
+ if param_name not in kwargs:
66
+ if param_schema.get("required", False):
67
+ logger.error(f"Missing required parameter: {param_name}")
68
+ return False
69
+ continue
70
+
71
+ param_value = kwargs[param_name]
72
+ param_type = param_schema.get("type")
73
+
74
+ if param_type and not isinstance(param_value, eval(param_type)):
75
+ logger.error(f"Invalid type for parameter {param_name}")
76
+ return False
77
+
78
+ if "enum" in param_schema and param_value not in param_schema["enum"]:
79
+ logger.error(f"Invalid value for parameter {param_name}")
80
+ return False
81
+
82
+ return True
83
+
84
+ def _log_execution(self, **kwargs) -> None:
85
+ """
86
+ Log tool execution details.
87
+
88
+ Args:
89
+ **kwargs: Parameters used in execution
90
+ """
91
+ logger.info(f"Executing tool {self.name} with parameters: {kwargs}")
92
+
93
+ def _log_result(self, result: Any) -> None:
94
+ """
95
+ Log tool execution result.
96
+
97
+ Args:
98
+ result: The result of the tool execution
99
+ """
100
+ logger.info(f"Tool {self.name} execution completed with result: {result}")
@@ -0,0 +1 @@
1
+ # Search System Tools Package
@@ -0,0 +1 @@
1
+ # Search System Tools Package
@@ -0,0 +1 @@
1
+ # Search System Tools Package
@@ -4,11 +4,11 @@ API module for programmatic access to Local Deep Research functionality.
4
4
  """
5
5
 
6
6
  from .research_functions import (
7
- quick_summary,
8
- generate_report,
9
7
  analyze_documents,
8
+ generate_report,
9
+ get_available_collections,
10
10
  get_available_search_engines,
11
- get_available_collections
11
+ quick_summary,
12
12
  )
13
13
 
14
14
  __all__ = [
@@ -16,5 +16,5 @@ __all__ = [
16
16
  "generate_report",
17
17
  "analyze_documents",
18
18
  "get_available_search_engines",
19
- "get_available_collections"
20
- ]
19
+ "get_available_collections",
20
+ ]