local-deep-research 0.4.4__py3-none-any.whl → 0.5.0__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 (220) hide show
  1. local_deep_research/__init__.py +7 -0
  2. local_deep_research/__version__.py +1 -1
  3. local_deep_research/advanced_search_system/answer_decoding/__init__.py +5 -0
  4. local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py +421 -0
  5. local_deep_research/advanced_search_system/candidate_exploration/README.md +219 -0
  6. local_deep_research/advanced_search_system/candidate_exploration/__init__.py +25 -0
  7. local_deep_research/advanced_search_system/candidate_exploration/adaptive_explorer.py +329 -0
  8. local_deep_research/advanced_search_system/candidate_exploration/base_explorer.py +341 -0
  9. local_deep_research/advanced_search_system/candidate_exploration/constraint_guided_explorer.py +436 -0
  10. local_deep_research/advanced_search_system/candidate_exploration/diversity_explorer.py +457 -0
  11. local_deep_research/advanced_search_system/candidate_exploration/parallel_explorer.py +250 -0
  12. local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +255 -0
  13. local_deep_research/advanced_search_system/candidates/__init__.py +5 -0
  14. local_deep_research/advanced_search_system/candidates/base_candidate.py +59 -0
  15. local_deep_research/advanced_search_system/constraint_checking/README.md +150 -0
  16. local_deep_research/advanced_search_system/constraint_checking/__init__.py +35 -0
  17. local_deep_research/advanced_search_system/constraint_checking/base_constraint_checker.py +122 -0
  18. local_deep_research/advanced_search_system/constraint_checking/constraint_checker.py +223 -0
  19. local_deep_research/advanced_search_system/constraint_checking/constraint_satisfaction_tracker.py +387 -0
  20. local_deep_research/advanced_search_system/constraint_checking/dual_confidence_checker.py +424 -0
  21. local_deep_research/advanced_search_system/constraint_checking/evidence_analyzer.py +174 -0
  22. local_deep_research/advanced_search_system/constraint_checking/intelligent_constraint_relaxer.py +503 -0
  23. local_deep_research/advanced_search_system/constraint_checking/rejection_engine.py +143 -0
  24. local_deep_research/advanced_search_system/constraint_checking/strict_checker.py +259 -0
  25. local_deep_research/advanced_search_system/constraint_checking/threshold_checker.py +213 -0
  26. local_deep_research/advanced_search_system/constraints/__init__.py +6 -0
  27. local_deep_research/advanced_search_system/constraints/base_constraint.py +58 -0
  28. local_deep_research/advanced_search_system/constraints/constraint_analyzer.py +143 -0
  29. local_deep_research/advanced_search_system/evidence/__init__.py +12 -0
  30. local_deep_research/advanced_search_system/evidence/base_evidence.py +57 -0
  31. local_deep_research/advanced_search_system/evidence/evaluator.py +159 -0
  32. local_deep_research/advanced_search_system/evidence/requirements.py +122 -0
  33. local_deep_research/advanced_search_system/filters/base_filter.py +3 -1
  34. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +8 -2
  35. local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +43 -29
  36. local_deep_research/advanced_search_system/findings/repository.py +54 -17
  37. local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +3 -1
  38. local_deep_research/advanced_search_system/query_generation/adaptive_query_generator.py +405 -0
  39. local_deep_research/advanced_search_system/questions/__init__.py +16 -0
  40. local_deep_research/advanced_search_system/questions/atomic_fact_question.py +171 -0
  41. local_deep_research/advanced_search_system/questions/browsecomp_question.py +287 -0
  42. local_deep_research/advanced_search_system/questions/decomposition_question.py +13 -4
  43. local_deep_research/advanced_search_system/questions/entity_aware_question.py +184 -0
  44. local_deep_research/advanced_search_system/questions/standard_question.py +9 -3
  45. local_deep_research/advanced_search_system/search_optimization/cross_constraint_manager.py +624 -0
  46. local_deep_research/advanced_search_system/source_management/diversity_manager.py +613 -0
  47. local_deep_research/advanced_search_system/strategies/__init__.py +42 -0
  48. local_deep_research/advanced_search_system/strategies/adaptive_decomposition_strategy.py +564 -0
  49. local_deep_research/advanced_search_system/strategies/base_strategy.py +4 -4
  50. local_deep_research/advanced_search_system/strategies/browsecomp_entity_strategy.py +1031 -0
  51. local_deep_research/advanced_search_system/strategies/browsecomp_optimized_strategy.py +778 -0
  52. local_deep_research/advanced_search_system/strategies/concurrent_dual_confidence_strategy.py +446 -0
  53. local_deep_research/advanced_search_system/strategies/constrained_search_strategy.py +1348 -0
  54. local_deep_research/advanced_search_system/strategies/constraint_parallel_strategy.py +522 -0
  55. local_deep_research/advanced_search_system/strategies/direct_search_strategy.py +217 -0
  56. local_deep_research/advanced_search_system/strategies/dual_confidence_strategy.py +320 -0
  57. local_deep_research/advanced_search_system/strategies/dual_confidence_with_rejection.py +219 -0
  58. local_deep_research/advanced_search_system/strategies/early_stop_constrained_strategy.py +369 -0
  59. local_deep_research/advanced_search_system/strategies/entity_aware_source_strategy.py +140 -0
  60. local_deep_research/advanced_search_system/strategies/evidence_based_strategy.py +1248 -0
  61. local_deep_research/advanced_search_system/strategies/evidence_based_strategy_v2.py +1337 -0
  62. local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +537 -0
  63. local_deep_research/advanced_search_system/strategies/improved_evidence_based_strategy.py +782 -0
  64. local_deep_research/advanced_search_system/strategies/iterative_reasoning_strategy.py +760 -0
  65. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +55 -21
  66. local_deep_research/advanced_search_system/strategies/llm_driven_modular_strategy.py +865 -0
  67. local_deep_research/advanced_search_system/strategies/modular_strategy.py +1142 -0
  68. local_deep_research/advanced_search_system/strategies/parallel_constrained_strategy.py +506 -0
  69. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +34 -16
  70. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +29 -9
  71. local_deep_research/advanced_search_system/strategies/recursive_decomposition_strategy.py +492 -0
  72. local_deep_research/advanced_search_system/strategies/smart_decomposition_strategy.py +284 -0
  73. local_deep_research/advanced_search_system/strategies/smart_query_strategy.py +515 -0
  74. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +48 -24
  75. local_deep_research/advanced_search_system/strategies/standard_strategy.py +34 -14
  76. local_deep_research/advanced_search_system/tools/base_tool.py +7 -2
  77. local_deep_research/api/benchmark_functions.py +6 -2
  78. local_deep_research/api/research_functions.py +10 -4
  79. local_deep_research/benchmarks/__init__.py +9 -7
  80. local_deep_research/benchmarks/benchmark_functions.py +6 -2
  81. local_deep_research/benchmarks/cli/benchmark_commands.py +27 -10
  82. local_deep_research/benchmarks/cli.py +38 -13
  83. local_deep_research/benchmarks/comparison/__init__.py +4 -2
  84. local_deep_research/benchmarks/comparison/evaluator.py +316 -239
  85. local_deep_research/benchmarks/datasets/__init__.py +1 -1
  86. local_deep_research/benchmarks/datasets/base.py +91 -72
  87. local_deep_research/benchmarks/datasets/browsecomp.py +54 -33
  88. local_deep_research/benchmarks/datasets/custom_dataset_template.py +19 -19
  89. local_deep_research/benchmarks/datasets/simpleqa.py +14 -14
  90. local_deep_research/benchmarks/datasets/utils.py +48 -29
  91. local_deep_research/benchmarks/datasets.py +4 -11
  92. local_deep_research/benchmarks/efficiency/__init__.py +8 -4
  93. local_deep_research/benchmarks/efficiency/resource_monitor.py +223 -171
  94. local_deep_research/benchmarks/efficiency/speed_profiler.py +62 -48
  95. local_deep_research/benchmarks/evaluators/browsecomp.py +3 -1
  96. local_deep_research/benchmarks/evaluators/composite.py +6 -2
  97. local_deep_research/benchmarks/evaluators/simpleqa.py +36 -13
  98. local_deep_research/benchmarks/graders.py +32 -10
  99. local_deep_research/benchmarks/metrics/README.md +1 -1
  100. local_deep_research/benchmarks/metrics/calculation.py +25 -10
  101. local_deep_research/benchmarks/metrics/reporting.py +7 -3
  102. local_deep_research/benchmarks/metrics/visualization.py +42 -23
  103. local_deep_research/benchmarks/metrics.py +1 -1
  104. local_deep_research/benchmarks/optimization/__init__.py +3 -1
  105. local_deep_research/benchmarks/optimization/api.py +7 -1
  106. local_deep_research/benchmarks/optimization/optuna_optimizer.py +75 -26
  107. local_deep_research/benchmarks/runners.py +48 -15
  108. local_deep_research/citation_handler.py +65 -92
  109. local_deep_research/citation_handlers/__init__.py +15 -0
  110. local_deep_research/citation_handlers/base_citation_handler.py +70 -0
  111. local_deep_research/citation_handlers/forced_answer_citation_handler.py +179 -0
  112. local_deep_research/citation_handlers/precision_extraction_handler.py +550 -0
  113. local_deep_research/citation_handlers/standard_citation_handler.py +80 -0
  114. local_deep_research/config/llm_config.py +271 -169
  115. local_deep_research/config/search_config.py +14 -5
  116. local_deep_research/defaults/__init__.py +0 -1
  117. local_deep_research/metrics/__init__.py +13 -0
  118. local_deep_research/metrics/database.py +58 -0
  119. local_deep_research/metrics/db_models.py +115 -0
  120. local_deep_research/metrics/migrate_add_provider_to_token_usage.py +148 -0
  121. local_deep_research/metrics/migrate_call_stack_tracking.py +105 -0
  122. local_deep_research/metrics/migrate_enhanced_tracking.py +75 -0
  123. local_deep_research/metrics/migrate_research_ratings.py +31 -0
  124. local_deep_research/metrics/models.py +61 -0
  125. local_deep_research/metrics/pricing/__init__.py +12 -0
  126. local_deep_research/metrics/pricing/cost_calculator.py +237 -0
  127. local_deep_research/metrics/pricing/pricing_cache.py +143 -0
  128. local_deep_research/metrics/pricing/pricing_fetcher.py +240 -0
  129. local_deep_research/metrics/query_utils.py +51 -0
  130. local_deep_research/metrics/search_tracker.py +380 -0
  131. local_deep_research/metrics/token_counter.py +1078 -0
  132. local_deep_research/migrate_db.py +3 -1
  133. local_deep_research/report_generator.py +22 -8
  134. local_deep_research/search_system.py +390 -9
  135. local_deep_research/test_migration.py +15 -5
  136. local_deep_research/utilities/db_utils.py +7 -4
  137. local_deep_research/utilities/es_utils.py +115 -104
  138. local_deep_research/utilities/llm_utils.py +15 -5
  139. local_deep_research/utilities/log_utils.py +151 -0
  140. local_deep_research/utilities/search_cache.py +387 -0
  141. local_deep_research/utilities/search_utilities.py +14 -6
  142. local_deep_research/utilities/threading_utils.py +92 -0
  143. local_deep_research/utilities/url_utils.py +6 -0
  144. local_deep_research/web/api.py +347 -0
  145. local_deep_research/web/app.py +13 -17
  146. local_deep_research/web/app_factory.py +71 -66
  147. local_deep_research/web/database/migrate_to_ldr_db.py +12 -4
  148. local_deep_research/web/database/migrations.py +5 -3
  149. local_deep_research/web/database/models.py +51 -2
  150. local_deep_research/web/database/schema_upgrade.py +49 -29
  151. local_deep_research/web/models/database.py +51 -61
  152. local_deep_research/web/routes/api_routes.py +56 -22
  153. local_deep_research/web/routes/benchmark_routes.py +4 -1
  154. local_deep_research/web/routes/globals.py +22 -0
  155. local_deep_research/web/routes/history_routes.py +71 -46
  156. local_deep_research/web/routes/metrics_routes.py +1155 -0
  157. local_deep_research/web/routes/research_routes.py +227 -41
  158. local_deep_research/web/routes/settings_routes.py +156 -55
  159. local_deep_research/web/services/research_service.py +310 -103
  160. local_deep_research/web/services/resource_service.py +36 -11
  161. local_deep_research/web/services/settings_manager.py +55 -17
  162. local_deep_research/web/services/settings_service.py +12 -4
  163. local_deep_research/web/services/socket_service.py +295 -188
  164. local_deep_research/web/static/css/custom_dropdown.css +180 -0
  165. local_deep_research/web/static/css/styles.css +39 -1
  166. local_deep_research/web/static/js/components/detail.js +633 -267
  167. local_deep_research/web/static/js/components/details.js +751 -0
  168. local_deep_research/web/static/js/components/fallback/formatting.js +11 -11
  169. local_deep_research/web/static/js/components/fallback/ui.js +23 -23
  170. local_deep_research/web/static/js/components/history.js +76 -76
  171. local_deep_research/web/static/js/components/logpanel.js +61 -13
  172. local_deep_research/web/static/js/components/progress.js +13 -2
  173. local_deep_research/web/static/js/components/research.js +99 -12
  174. local_deep_research/web/static/js/components/results.js +239 -106
  175. local_deep_research/web/static/js/main.js +40 -40
  176. local_deep_research/web/static/js/services/audio.js +1 -1
  177. local_deep_research/web/static/js/services/formatting.js +11 -11
  178. local_deep_research/web/static/js/services/keyboard.js +157 -0
  179. local_deep_research/web/static/js/services/pdf.js +80 -80
  180. local_deep_research/web/static/sounds/README.md +1 -1
  181. local_deep_research/web/templates/base.html +1 -0
  182. local_deep_research/web/templates/components/log_panel.html +7 -1
  183. local_deep_research/web/templates/components/mobile_nav.html +1 -1
  184. local_deep_research/web/templates/components/sidebar.html +3 -0
  185. local_deep_research/web/templates/pages/cost_analytics.html +1245 -0
  186. local_deep_research/web/templates/pages/details.html +325 -24
  187. local_deep_research/web/templates/pages/history.html +1 -1
  188. local_deep_research/web/templates/pages/metrics.html +1929 -0
  189. local_deep_research/web/templates/pages/progress.html +2 -2
  190. local_deep_research/web/templates/pages/research.html +53 -17
  191. local_deep_research/web/templates/pages/results.html +12 -1
  192. local_deep_research/web/templates/pages/star_reviews.html +803 -0
  193. local_deep_research/web/utils/formatters.py +9 -3
  194. local_deep_research/web_search_engines/default_search_engines.py +5 -3
  195. local_deep_research/web_search_engines/engines/full_search.py +8 -2
  196. local_deep_research/web_search_engines/engines/meta_search_engine.py +59 -20
  197. local_deep_research/web_search_engines/engines/search_engine_arxiv.py +19 -6
  198. local_deep_research/web_search_engines/engines/search_engine_brave.py +6 -2
  199. local_deep_research/web_search_engines/engines/search_engine_ddg.py +3 -1
  200. local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +81 -58
  201. local_deep_research/web_search_engines/engines/search_engine_github.py +46 -15
  202. local_deep_research/web_search_engines/engines/search_engine_google_pse.py +16 -6
  203. local_deep_research/web_search_engines/engines/search_engine_guardian.py +39 -15
  204. local_deep_research/web_search_engines/engines/search_engine_local.py +58 -25
  205. local_deep_research/web_search_engines/engines/search_engine_local_all.py +15 -5
  206. local_deep_research/web_search_engines/engines/search_engine_pubmed.py +63 -21
  207. local_deep_research/web_search_engines/engines/search_engine_searxng.py +37 -11
  208. local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +27 -9
  209. local_deep_research/web_search_engines/engines/search_engine_serpapi.py +12 -4
  210. local_deep_research/web_search_engines/engines/search_engine_wayback.py +31 -10
  211. local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +12 -3
  212. local_deep_research/web_search_engines/search_engine_base.py +83 -35
  213. local_deep_research/web_search_engines/search_engine_factory.py +25 -8
  214. local_deep_research/web_search_engines/search_engines_config.py +9 -3
  215. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/METADATA +7 -1
  216. local_deep_research-0.5.0.dist-info/RECORD +265 -0
  217. local_deep_research-0.4.4.dist-info/RECORD +0 -177
  218. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/WHEEL +0 -0
  219. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/entry_points.txt +0 -0
  220. {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,564 @@
1
+ """
2
+ Adaptive Decomposition Strategy for step-by-step query analysis.
3
+
4
+ This strategy dynamically adapts its approach based on intermediate findings,
5
+ making decisions at each step rather than decomposing everything upfront.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from enum import Enum
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from langchain_core.language_models import BaseChatModel
13
+ from loguru import logger
14
+
15
+ from ...utilities.search_utilities import format_findings, remove_think_tags
16
+ from ..findings.repository import FindingsRepository
17
+ from ..questions.standard_question import StandardQuestionGenerator
18
+ from .base_strategy import BaseSearchStrategy
19
+ from .source_based_strategy import SourceBasedSearchStrategy
20
+
21
+
22
+ class StepType(Enum):
23
+ """Types of steps in the adaptive process."""
24
+
25
+ CONSTRAINT_EXTRACTION = "constraint_extraction"
26
+ INITIAL_SEARCH = "initial_search"
27
+ VERIFICATION = "verification"
28
+ REFINEMENT = "refinement"
29
+ SYNTHESIS = "synthesis"
30
+
31
+
32
+ @dataclass
33
+ class StepResult:
34
+ """Result from a single adaptive step."""
35
+
36
+ step_type: StepType
37
+ description: str
38
+ findings: Dict
39
+ next_action: Optional[str] = None
40
+ confidence: float = 0.0
41
+
42
+
43
+ class AdaptiveDecompositionStrategy(BaseSearchStrategy):
44
+ """
45
+ A strategy that adapts its decomposition based on intermediate findings.
46
+
47
+ Instead of decomposing everything upfront, it takes a step-by-step approach,
48
+ using each step's findings to inform the next action.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ model: BaseChatModel,
54
+ search: Any,
55
+ all_links_of_system: List[str],
56
+ max_steps: int = 15,
57
+ min_confidence: float = 0.8,
58
+ source_search_iterations: int = 2,
59
+ source_questions_per_iteration: int = 20,
60
+ ):
61
+ """Initialize the adaptive decomposition strategy.
62
+
63
+ Args:
64
+ model: The language model to use
65
+ search: The search engine instance
66
+ all_links_of_system: List to store all encountered links
67
+ max_steps: Maximum steps to prevent infinite loops
68
+ min_confidence: Minimum confidence to consider answer complete
69
+ source_search_iterations: Iterations for source-based searches
70
+ source_questions_per_iteration: Questions per iteration for source-based searches
71
+ """
72
+ super().__init__(all_links_of_system)
73
+ self.model = model
74
+ self.search = search
75
+ self.max_steps = max_steps
76
+ self.min_confidence = min_confidence
77
+ self.source_search_iterations = source_search_iterations
78
+ self.source_questions_per_iteration = source_questions_per_iteration
79
+ self.question_generator = StandardQuestionGenerator(model)
80
+ self.findings_repository = FindingsRepository(model)
81
+ self.step_results: List[StepResult] = []
82
+ self.working_knowledge = {}
83
+
84
+ def analyze_topic(self, query: str) -> Dict:
85
+ """Analyze a topic using adaptive decomposition.
86
+
87
+ Args:
88
+ query: The research query to analyze
89
+
90
+ Returns:
91
+ Dictionary containing analysis results
92
+ """
93
+ self.all_links_of_system.clear()
94
+ self.questions_by_iteration = []
95
+ self.findings = []
96
+ self.step_results = []
97
+ self.working_knowledge = {
98
+ "original_query": query,
99
+ "constraints": [],
100
+ "candidates": [],
101
+ "verified_facts": [],
102
+ "uncertainties": [],
103
+ }
104
+
105
+ # Progress callback for UI
106
+ if self.progress_callback:
107
+ self.progress_callback(
108
+ "Starting adaptive analysis",
109
+ 1,
110
+ {"phase": "init", "strategy": "adaptive_decomposition"},
111
+ )
112
+
113
+ logger.info(f"Starting adaptive analysis for: {query}")
114
+
115
+ # Start with constraint extraction
116
+ current_step = 0
117
+ while current_step < self.max_steps:
118
+ # Decide next step based on current knowledge
119
+ next_step = self._decide_next_step(query, current_step)
120
+
121
+ if next_step is None:
122
+ break
123
+
124
+ logger.info(f"Step {current_step + 1}: {next_step.step_type.value}")
125
+
126
+ # Update progress
127
+ if self.progress_callback:
128
+ self.progress_callback(
129
+ f"Step {current_step + 1}: {next_step.step_type.value}",
130
+ int((current_step / self.max_steps) * 80) + 10,
131
+ {
132
+ "phase": "adaptive_step",
133
+ "step": current_step + 1,
134
+ "step_type": next_step.step_type.value,
135
+ },
136
+ )
137
+
138
+ # Execute the step
139
+ step_result = self._execute_step(next_step, query)
140
+ self.step_results.append(step_result)
141
+
142
+ # Check if we have a confident answer
143
+ if step_result.confidence >= self.min_confidence:
144
+ logger.info(
145
+ f"Confident answer found (confidence: {step_result.confidence})"
146
+ )
147
+ break
148
+
149
+ current_step += 1
150
+
151
+ # Final synthesis of all findings
152
+ final_result = self._synthesize_findings(query)
153
+
154
+ # Update progress
155
+ if self.progress_callback:
156
+ self.progress_callback(
157
+ "Analysis complete",
158
+ 100,
159
+ {"phase": "complete", "strategy": "adaptive_decomposition"},
160
+ )
161
+
162
+ return final_result
163
+
164
+ def _decide_next_step(
165
+ self, query: str, step_count: int
166
+ ) -> Optional[StepResult]:
167
+ """Decide the next step based on current knowledge.
168
+
169
+ Args:
170
+ query: The original query
171
+ step_count: Current step number
172
+
173
+ Returns:
174
+ Next step to execute, or None if complete
175
+ """
176
+ # Format current knowledge for analysis
177
+ knowledge_summary = f"""
178
+ Original Query: {query}
179
+
180
+ Current Knowledge:
181
+ - Extracted Constraints: {self.working_knowledge.get("constraints", [])}
182
+ - Candidate Locations: {self.working_knowledge.get("candidates", [])}
183
+ - Verified Facts: {self.working_knowledge.get("verified_facts", [])}
184
+ - Uncertainties: {self.working_knowledge.get("uncertainties", [])}
185
+
186
+ Previous Steps: {[s.step_type.value for s in self.step_results]}
187
+ """
188
+
189
+ prompt = f"""Based on the current knowledge, decide the next best action.
190
+
191
+ {knowledge_summary}
192
+
193
+ What should be the next step? Options:
194
+ 1. CONSTRAINT_EXTRACTION - Extract specific constraints from the query
195
+ 2. INITIAL_SEARCH - Perform broad search for candidates
196
+ 3. VERIFICATION - Verify specific facts about candidates
197
+ 4. REFINEMENT - Refine search based on new information
198
+ 5. SYNTHESIS - We have enough information to answer
199
+
200
+ Respond with:
201
+ NEXT_STEP: [step type]
202
+ DESCRIPTION: [what specifically to do]
203
+ REASONING: [why this is the best next step]
204
+ CONFIDENCE: [0-1 score of how confident we are in the current answer]
205
+ """
206
+
207
+ response = self.model.invoke(prompt)
208
+ content = remove_think_tags(response.content)
209
+
210
+ # Parse response
211
+ lines = content.strip().split("\n")
212
+ next_step_type = None
213
+ description = ""
214
+ confidence = 0.0
215
+
216
+ for line in lines:
217
+ if line.startswith("NEXT_STEP:"):
218
+ step_str = line.split(":", 1)[1].strip()
219
+ try:
220
+ next_step_type = StepType[step_str]
221
+ except KeyError:
222
+ # Try to match partial
223
+ for step_type in StepType:
224
+ if step_type.value in step_str.lower():
225
+ next_step_type = step_type
226
+ break
227
+ elif line.startswith("DESCRIPTION:"):
228
+ description = line.split(":", 1)[1].strip()
229
+ elif line.startswith("CONFIDENCE:"):
230
+ try:
231
+ confidence = float(line.split(":", 1)[1].strip())
232
+ except ValueError:
233
+ confidence = 0.0
234
+
235
+ if next_step_type is None:
236
+ return None
237
+
238
+ return StepResult(
239
+ step_type=next_step_type,
240
+ description=description,
241
+ findings={},
242
+ confidence=confidence,
243
+ )
244
+
245
+ def _execute_step(self, step: StepResult, query: str) -> StepResult:
246
+ """Execute a specific step in the adaptive process.
247
+
248
+ Args:
249
+ step: The step to execute
250
+ query: The original query
251
+
252
+ Returns:
253
+ Updated step result with findings
254
+ """
255
+ if step.step_type == StepType.CONSTRAINT_EXTRACTION:
256
+ return self._extract_constraints(query)
257
+ elif step.step_type == StepType.INITIAL_SEARCH:
258
+ return self._perform_initial_search(step.description)
259
+ elif step.step_type == StepType.VERIFICATION:
260
+ return self._verify_candidates(step.description)
261
+ elif step.step_type == StepType.REFINEMENT:
262
+ return self._refine_search(step.description)
263
+ elif step.step_type == StepType.SYNTHESIS:
264
+ return self._synthesize_current_knowledge(query)
265
+ else:
266
+ return step
267
+
268
+ def _extract_constraints(self, query: str) -> StepResult:
269
+ """Extract specific constraints and clues from the query.
270
+
271
+ Args:
272
+ query: The original query
273
+
274
+ Returns:
275
+ Step result with extracted constraints
276
+ """
277
+ prompt = f"""Extract all specific constraints and clues from this query:
278
+
279
+ Query: {query}
280
+
281
+ List each constraint/clue separately and explain why it's important:
282
+ """
283
+
284
+ response = self.model.invoke(prompt)
285
+ content = remove_think_tags(response.content)
286
+
287
+ # Parse constraints
288
+ constraints = []
289
+ lines = content.strip().split("\n")
290
+ for line in lines:
291
+ if line.strip() and (line[0].isdigit() or line.startswith("-")):
292
+ constraints.append(line.strip())
293
+
294
+ self.working_knowledge["constraints"] = constraints
295
+
296
+ return StepResult(
297
+ step_type=StepType.CONSTRAINT_EXTRACTION,
298
+ description="Extract constraints from query",
299
+ findings={"constraints": constraints},
300
+ confidence=0.0,
301
+ )
302
+
303
+ def _perform_initial_search(self, description: str) -> StepResult:
304
+ """Perform initial search based on extracted constraints.
305
+
306
+ Args:
307
+ description: Search description
308
+
309
+ Returns:
310
+ Step result with search findings
311
+ """
312
+ # Use source-based strategy for the search
313
+ source_strategy = SourceBasedSearchStrategy(
314
+ model=self.model,
315
+ search=self.search,
316
+ all_links_of_system=self.all_links_of_system,
317
+ include_text_content=True,
318
+ use_cross_engine_filter=True,
319
+ use_atomic_facts=True,
320
+ )
321
+
322
+ source_strategy.max_iterations = 1 # Quick initial search
323
+ source_strategy.questions_per_iteration = 10
324
+
325
+ if self.progress_callback:
326
+ source_strategy.set_progress_callback(self.progress_callback)
327
+
328
+ results = source_strategy.analyze_topic(description)
329
+
330
+ # Extract candidate locations from results
331
+ candidates = self._extract_candidates_from_results(results)
332
+ self.working_knowledge["candidates"] = candidates
333
+
334
+ return StepResult(
335
+ step_type=StepType.INITIAL_SEARCH,
336
+ description=description,
337
+ findings={"candidates": candidates, "raw_results": results},
338
+ confidence=0.2,
339
+ )
340
+
341
+ def _verify_candidates(self, description: str) -> StepResult:
342
+ """Verify specific facts about candidate locations.
343
+
344
+ Args:
345
+ description: Verification description
346
+
347
+ Returns:
348
+ Step result with verification findings
349
+ """
350
+ # Perform targeted search for verification
351
+ source_strategy = SourceBasedSearchStrategy(
352
+ model=self.model,
353
+ search=self.search,
354
+ all_links_of_system=self.all_links_of_system,
355
+ include_text_content=True,
356
+ use_cross_engine_filter=True,
357
+ use_atomic_facts=True,
358
+ )
359
+
360
+ source_strategy.max_iterations = 1
361
+ source_strategy.questions_per_iteration = 15
362
+
363
+ if self.progress_callback:
364
+ source_strategy.set_progress_callback(self.progress_callback)
365
+
366
+ results = source_strategy.analyze_topic(description)
367
+
368
+ # Update verified facts
369
+ verified_facts = self._extract_verified_facts(results)
370
+ self.working_knowledge["verified_facts"].extend(verified_facts)
371
+
372
+ # Calculate confidence based on verification
373
+ confidence = self._calculate_confidence()
374
+
375
+ return StepResult(
376
+ step_type=StepType.VERIFICATION,
377
+ description=description,
378
+ findings={"verified_facts": verified_facts},
379
+ confidence=confidence,
380
+ )
381
+
382
+ def _refine_search(self, description: str) -> StepResult:
383
+ """Refine search based on accumulated knowledge.
384
+
385
+ Args:
386
+ description: Refinement description
387
+
388
+ Returns:
389
+ Step result with refined findings
390
+ """
391
+ # Similar to verification but with more targeted approach
392
+ return self._verify_candidates(description)
393
+
394
+ def _synthesize_current_knowledge(self, query: str) -> StepResult:
395
+ """Synthesize current knowledge into an answer.
396
+
397
+ Args:
398
+ query: The original query
399
+
400
+ Returns:
401
+ Step result with synthesized answer
402
+ """
403
+ knowledge_summary = f"""
404
+ Original Query: {query}
405
+
406
+ Constraints: {self.working_knowledge["constraints"]}
407
+ Candidates: {self.working_knowledge["candidates"]}
408
+ Verified Facts: {self.working_knowledge["verified_facts"]}
409
+ """
410
+
411
+ prompt = f"""Based on all the information gathered, provide the answer:
412
+
413
+ {knowledge_summary}
414
+
415
+ Provide:
416
+ 1. The specific answer to the query
417
+ 2. Supporting evidence for the answer
418
+ 3. Confidence level (0-1)
419
+ """
420
+
421
+ response = self.model.invoke(prompt)
422
+ content = remove_think_tags(response.content)
423
+
424
+ return StepResult(
425
+ step_type=StepType.SYNTHESIS,
426
+ description="Final synthesis",
427
+ findings={"answer": content},
428
+ confidence=0.9,
429
+ )
430
+
431
+ def _synthesize_findings(self, query: str) -> Dict:
432
+ """Synthesize all findings into final result.
433
+
434
+ Args:
435
+ query: The original query
436
+
437
+ Returns:
438
+ Final results dictionary
439
+ """
440
+ # Compile all findings
441
+ all_findings = []
442
+ all_links = []
443
+ all_questions = []
444
+
445
+ for step in self.step_results:
446
+ finding = {
447
+ "phase": f"{step.step_type.value} (Step {self.step_results.index(step) + 1})",
448
+ "content": step.description,
449
+ "findings": step.findings,
450
+ "confidence": step.confidence,
451
+ "timestamp": self._get_timestamp(),
452
+ }
453
+ all_findings.append(finding)
454
+
455
+ # Extract links and questions from raw results if available
456
+ if "raw_results" in step.findings:
457
+ raw = step.findings["raw_results"]
458
+ if "all_links_of_system" in raw:
459
+ all_links.extend(raw["all_links_of_system"])
460
+ if "questions_by_iteration" in raw:
461
+ all_questions.extend(
462
+ raw.get("questions_by_iteration", {}).values()
463
+ )
464
+
465
+ # Get final answer
466
+ final_answer = "No confident answer found."
467
+ for step in reversed(self.step_results):
468
+ if (
469
+ step.step_type == StepType.SYNTHESIS
470
+ and "answer" in step.findings
471
+ ):
472
+ final_answer = step.findings["answer"]
473
+ break
474
+
475
+ # Format questions dictionary
476
+ questions_dict = {}
477
+ for i, questions in enumerate(all_questions):
478
+ if isinstance(questions, list):
479
+ questions_dict[i + 1] = questions
480
+
481
+ formatted_findings = format_findings(
482
+ all_findings, final_answer, questions_dict
483
+ )
484
+
485
+ return {
486
+ "current_knowledge": final_answer,
487
+ "formatted_findings": formatted_findings,
488
+ "findings": all_findings,
489
+ "iterations": len(self.step_results),
490
+ "questions_by_iteration": questions_dict,
491
+ "all_links_of_system": all_links,
492
+ "sources": all_links,
493
+ "step_results": [step.__dict__ for step in self.step_results],
494
+ "strategy": "adaptive_decomposition",
495
+ "working_knowledge": self.working_knowledge,
496
+ "questions": {
497
+ "total": sum(len(q) for q in questions_dict.values()),
498
+ "by_iteration": questions_dict,
499
+ },
500
+ }
501
+
502
+ def _extract_candidates_from_results(self, results: Dict) -> List[str]:
503
+ """Extract candidate locations from search results."""
504
+ candidates = []
505
+ if "current_knowledge" in results:
506
+ # Use LLM to extract location names
507
+ prompt = f"""Extract any location names mentioned in this text:
508
+
509
+ {results["current_knowledge"][:1000]}
510
+
511
+ List only location names, one per line:"""
512
+
513
+ response = self.model.invoke(prompt)
514
+ content = remove_think_tags(response.content)
515
+
516
+ for line in content.strip().split("\n"):
517
+ if line.strip():
518
+ candidates.append(line.strip())
519
+
520
+ return candidates
521
+
522
+ def _extract_verified_facts(self, results: Dict) -> List[str]:
523
+ """Extract verified facts from search results."""
524
+ facts = []
525
+ if "current_knowledge" in results:
526
+ # Use LLM to extract verified facts
527
+ prompt = f"""Extract specific verified facts from this text:
528
+
529
+ {results["current_knowledge"][:1000]}
530
+
531
+ List only confirmed facts, one per line:"""
532
+
533
+ response = self.model.invoke(prompt)
534
+ content = remove_think_tags(response.content)
535
+
536
+ for line in content.strip().split("\n"):
537
+ if line.strip():
538
+ facts.append(line.strip())
539
+
540
+ return facts
541
+
542
+ def _calculate_confidence(self) -> float:
543
+ """Calculate confidence based on verified facts vs constraints."""
544
+ if not self.working_knowledge["constraints"]:
545
+ return 0.0
546
+
547
+ verified_count = len(self.working_knowledge["verified_facts"])
548
+ constraint_count = len(self.working_knowledge["constraints"])
549
+
550
+ # Simple ratio with some adjustments
551
+ base_confidence = verified_count / constraint_count
552
+
553
+ # Boost if we have specific candidates
554
+ if self.working_knowledge["candidates"]:
555
+ base_confidence += 0.1
556
+
557
+ # Cap at 0.95 to leave room for synthesis
558
+ return min(base_confidence, 0.95)
559
+
560
+ def _get_timestamp(self) -> str:
561
+ """Get current timestamp for findings."""
562
+ from datetime import datetime
563
+
564
+ return datetime.utcnow().isoformat()
@@ -21,7 +21,9 @@ class BaseSearchStrategy(ABC):
21
21
  all_links_of_system if all_links_of_system is not None else []
22
22
  )
23
23
 
24
- def set_progress_callback(self, callback: Callable[[str, int, dict], None]) -> None:
24
+ def set_progress_callback(
25
+ self, callback: Callable[[str, int, dict], None]
26
+ ) -> None:
25
27
  """Set a callback function to receive progress updates."""
26
28
  self.progress_callback = callback
27
29
 
@@ -62,9 +64,7 @@ class BaseSearchStrategy(ABC):
62
64
  bool: True if search engine is available, False otherwise
63
65
  """
64
66
  if not hasattr(self, "search") or self.search is None:
65
- error_msg = (
66
- "Error: No search engine available. Please check your configuration."
67
- )
67
+ error_msg = "Error: No search engine available. Please check your configuration."
68
68
  self._update_progress(
69
69
  error_msg,
70
70
  100,