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,223 @@
1
+ """
2
+ Main constraint checker that orchestrates constraint validation.
3
+
4
+ This module provides the primary interface for checking candidates against constraints.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Dict, List, Optional
9
+
10
+ from langchain_core.language_models import BaseChatModel
11
+ from loguru import logger
12
+
13
+ from ..candidates.base_candidate import Candidate
14
+ from ..constraints.base_constraint import Constraint
15
+ from .evidence_analyzer import EvidenceAnalyzer
16
+ from .rejection_engine import RejectionEngine, RejectionResult
17
+
18
+
19
+ @dataclass
20
+ class ConstraintCheckResult:
21
+ """Result of checking a candidate against all constraints."""
22
+
23
+ candidate: Candidate
24
+ total_score: float
25
+ constraint_scores: Dict[str, Dict]
26
+ rejection_result: Optional[RejectionResult]
27
+ detailed_results: List[Dict]
28
+
29
+
30
+ class ConstraintChecker:
31
+ """
32
+ Main constraint checker that validates candidates against constraints.
33
+
34
+ This checker:
35
+ 1. Gathers evidence for each constraint
36
+ 2. Analyzes evidence using dual confidence scoring
37
+ 3. Makes rejection decisions based on evidence
38
+ 4. Provides detailed scoring breakdown
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ model: BaseChatModel,
44
+ evidence_gatherer=None, # Will be passed in from strategy
45
+ negative_threshold: float = 0.25,
46
+ positive_threshold: float = 0.4,
47
+ uncertainty_penalty: float = 0.2,
48
+ negative_weight: float = 0.5,
49
+ ):
50
+ """
51
+ Initialize the constraint checker.
52
+
53
+ Args:
54
+ model: Language model for evidence analysis
55
+ evidence_gatherer: Function to gather evidence (from strategy)
56
+ negative_threshold: Rejection threshold for negative evidence
57
+ positive_threshold: Minimum positive evidence required
58
+ uncertainty_penalty: Penalty for uncertain evidence
59
+ negative_weight: Weight for negative evidence in scoring
60
+ """
61
+ self.model = model
62
+ self.evidence_gatherer = evidence_gatherer
63
+
64
+ # Initialize components
65
+ self.evidence_analyzer = EvidenceAnalyzer(model)
66
+ self.rejection_engine = RejectionEngine(
67
+ negative_threshold, positive_threshold
68
+ )
69
+
70
+ # Scoring parameters
71
+ self.uncertainty_penalty = uncertainty_penalty
72
+ self.negative_weight = negative_weight
73
+
74
+ def check_candidate(
75
+ self, candidate: Candidate, constraints: List[Constraint]
76
+ ) -> ConstraintCheckResult:
77
+ """
78
+ Check a candidate against all constraints.
79
+
80
+ Args:
81
+ candidate: The candidate to check
82
+ constraints: List of constraints to check against
83
+
84
+ Returns:
85
+ ConstraintCheckResult: Complete evaluation result
86
+ """
87
+ logger.info(f"Checking candidate: {candidate.name}")
88
+
89
+ constraint_results = {}
90
+ constraint_scores = {}
91
+ detailed_results = []
92
+ total_score = 0.0
93
+
94
+ for constraint in constraints:
95
+ # Gather evidence for this constraint
96
+ evidence_list = self._gather_evidence_for_constraint(
97
+ candidate, constraint
98
+ )
99
+
100
+ if evidence_list:
101
+ # Analyze evidence with dual confidence
102
+ dual_evidence = [
103
+ self.evidence_analyzer.analyze_evidence_dual_confidence(
104
+ e, constraint
105
+ )
106
+ for e in evidence_list
107
+ ]
108
+
109
+ constraint_results[constraint] = dual_evidence
110
+
111
+ # Calculate average scores for this constraint
112
+ avg_positive = sum(
113
+ e.positive_confidence for e in dual_evidence
114
+ ) / len(dual_evidence)
115
+ avg_negative = sum(
116
+ e.negative_confidence for e in dual_evidence
117
+ ) / len(dual_evidence)
118
+ avg_uncertainty = sum(
119
+ e.uncertainty for e in dual_evidence
120
+ ) / len(dual_evidence)
121
+
122
+ # Calculate constraint score
123
+ score = self.evidence_analyzer.evaluate_evidence_list(
124
+ evidence_list,
125
+ constraint,
126
+ self.uncertainty_penalty,
127
+ self.negative_weight,
128
+ )
129
+
130
+ # Store results
131
+ constraint_scores[constraint.value] = {
132
+ "total": score,
133
+ "positive": avg_positive,
134
+ "negative": avg_negative,
135
+ "uncertainty": avg_uncertainty,
136
+ "weight": constraint.weight,
137
+ }
138
+
139
+ detailed_results.append(
140
+ {
141
+ "constraint": constraint.value,
142
+ "score": score,
143
+ "positive": avg_positive,
144
+ "negative": avg_negative,
145
+ "uncertainty": avg_uncertainty,
146
+ "weight": constraint.weight,
147
+ "type": constraint.type.value,
148
+ }
149
+ )
150
+
151
+ # Log result
152
+ symbol = "✓" if score >= 0.8 else "○" if score >= 0.5 else "✗"
153
+ logger.info(
154
+ f"{symbol} {candidate.name} | {constraint.value}: {int(score * 100)}% "
155
+ f"(+{int(avg_positive * 100)}% -{int(avg_negative * 100)}% ?{int(avg_uncertainty * 100)}%)"
156
+ )
157
+
158
+ else:
159
+ # No evidence found
160
+ score = 0.5 - self.uncertainty_penalty
161
+
162
+ constraint_scores[constraint.value] = {
163
+ "total": score,
164
+ "positive": 0.0,
165
+ "negative": 0.0,
166
+ "uncertainty": 1.0,
167
+ "weight": constraint.weight,
168
+ }
169
+
170
+ detailed_results.append(
171
+ {
172
+ "constraint": constraint.value,
173
+ "score": score,
174
+ "positive": 0.0,
175
+ "negative": 0.0,
176
+ "uncertainty": 1.0,
177
+ "weight": constraint.weight,
178
+ "type": constraint.type.value,
179
+ }
180
+ )
181
+
182
+ logger.info(
183
+ f"? {candidate.name} | {constraint.value}: No evidence found"
184
+ )
185
+
186
+ # Check for rejection
187
+ rejection_result = self.rejection_engine.check_all_constraints(
188
+ candidate, constraint_results
189
+ )
190
+
191
+ if rejection_result and rejection_result.should_reject:
192
+ # Candidate should be rejected
193
+ total_score = 0.0
194
+ else:
195
+ # Calculate weighted average score
196
+ if detailed_results:
197
+ weights = [r["weight"] for r in detailed_results]
198
+ scores = [r["score"] for r in detailed_results]
199
+ total_score = sum(s * w for s, w in zip(scores, weights)) / sum(
200
+ weights
201
+ )
202
+
203
+ logger.info(f"Final score for {candidate.name}: {total_score:.2%}")
204
+
205
+ return ConstraintCheckResult(
206
+ candidate=candidate,
207
+ total_score=total_score,
208
+ constraint_scores=constraint_scores,
209
+ rejection_result=rejection_result,
210
+ detailed_results=detailed_results,
211
+ )
212
+
213
+ def _gather_evidence_for_constraint(
214
+ self, candidate: Candidate, constraint: Constraint
215
+ ) -> List[Dict]:
216
+ """Gather evidence for a constraint using the provided evidence gatherer."""
217
+ if self.evidence_gatherer:
218
+ return self.evidence_gatherer(candidate, constraint)
219
+ else:
220
+ logger.warning(
221
+ "No evidence gatherer provided - cannot gather evidence"
222
+ )
223
+ return []
@@ -0,0 +1,387 @@
1
+ """
2
+ Multi-Stage Constraint Verification with Intelligent Weighting
3
+
4
+ This module implements the constraint satisfaction tracking system outlined in
5
+ BROWSECOMP_IMPROVEMENT_STRATEGY.md to improve BrowseComp performance.
6
+
7
+ Key features:
8
+ 1. Intelligent constraint weighting based on reliability
9
+ 2. Progressive constraint verification
10
+ 3. Partial satisfaction scoring
11
+ 4. Constraint difficulty analysis
12
+ """
13
+
14
+ import logging
15
+ from dataclasses import dataclass
16
+ from typing import Dict, List
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class ConstraintVerificationResult:
23
+ """Result of constraint verification process."""
24
+
25
+ total_score: float
26
+ should_accept: bool
27
+ constraint_details: Dict[str, Dict]
28
+ weighted_score: float
29
+ reliability_adjusted_score: float
30
+
31
+
32
+ class ConstraintSatisfactionTracker:
33
+ """
34
+ Multi-stage constraint verification with intelligent weighting.
35
+
36
+ Implements the strategy from BROWSECOMP_IMPROVEMENT_STRATEGY.md to:
37
+ - Weight constraints by reliability and importance
38
+ - Allow partial satisfaction instead of requiring perfect matches
39
+ - Prioritize reliable constraints over problematic ones
40
+ """
41
+
42
+ def __init__(self, satisfaction_threshold: float = 0.7):
43
+ self.satisfaction_threshold = satisfaction_threshold
44
+
45
+ # Constraint type weights based on reliability and importance
46
+ self.constraint_weights = {
47
+ "PROPERTY": 1.0, # Basic properties, moderately reliable
48
+ "NAME_PATTERN": 1.5, # Names are highly identifying
49
+ "STATISTIC": 0.8, # Often imprecise/outdated
50
+ "EVENT": 1.2, # Events are good identifiers
51
+ "TEMPORAL": 1.1, # Dates are useful but sometimes fuzzy
52
+ "LOCATION": 1.3, # Geographic info is reliable
53
+ "COMPARISON": 0.6, # Relative comparisons often problematic
54
+ "EXISTENCE": 1.0, # Basic existence checks
55
+ "RELATIONSHIP": 0.9, # Relationships can be complex
56
+ }
57
+
58
+ # Difficulty multipliers (easier constraints get higher weight)
59
+ self.difficulty_multipliers = {
60
+ "EASY": 1.2, # Simple property checks
61
+ "MEDIUM": 1.0, # Numerical comparisons
62
+ "HARD": 0.7, # Complex relationships, ratios
63
+ }
64
+
65
+ def verify_constraints_progressively(
66
+ self, candidate: object, constraints: List[object]
67
+ ) -> ConstraintVerificationResult:
68
+ """
69
+ Score each constraint independently with intelligent weighting.
70
+
71
+ Args:
72
+ candidate: The candidate entity to verify
73
+ constraints: List of constraints to check
74
+
75
+ Returns:
76
+ ConstraintVerificationResult with scoring details
77
+ """
78
+ scores = {}
79
+ total_weight = 0
80
+
81
+ for constraint in constraints:
82
+ # Analyze constraint difficulty
83
+ difficulty = self.analyze_constraint_difficulty(constraint)
84
+
85
+ # Calculate constraint weight
86
+ weight = self.calculate_constraint_weight(constraint, difficulty)
87
+
88
+ # Verify constraint against candidate
89
+ satisfaction = self.verify_single_constraint(candidate, constraint)
90
+
91
+ # Store detailed results
92
+ scores[
93
+ constraint.id if hasattr(constraint, "id") else str(constraint)
94
+ ] = {
95
+ "satisfaction": satisfaction,
96
+ "weight": weight,
97
+ "difficulty": difficulty,
98
+ "constraint_type": self._get_constraint_type(constraint),
99
+ "raw_score": satisfaction,
100
+ "weighted_score": satisfaction * weight,
101
+ }
102
+
103
+ total_weight += weight
104
+
105
+ # Calculate weighted satisfaction score
106
+ if total_weight > 0:
107
+ weighted_score = (
108
+ sum(
109
+ score["satisfaction"] * score["weight"]
110
+ for score in scores.values()
111
+ )
112
+ / total_weight
113
+ )
114
+ else:
115
+ weighted_score = 0.0
116
+
117
+ # Apply reliability adjustment
118
+ reliability_adjusted_score = self._apply_reliability_adjustment(
119
+ weighted_score, scores
120
+ )
121
+
122
+ # Determine if candidate should be accepted
123
+ should_accept = (
124
+ reliability_adjusted_score >= self.satisfaction_threshold
125
+ )
126
+
127
+ return ConstraintVerificationResult(
128
+ total_score=reliability_adjusted_score,
129
+ should_accept=should_accept,
130
+ constraint_details=scores,
131
+ weighted_score=weighted_score,
132
+ reliability_adjusted_score=reliability_adjusted_score,
133
+ )
134
+
135
+ def analyze_constraint_difficulty(self, constraint: object) -> str:
136
+ """
137
+ Classify constraints by difficulty level.
138
+
139
+ Returns:
140
+ 'EASY', 'MEDIUM', or 'HARD'
141
+ """
142
+ constraint_text = str(constraint).lower()
143
+
144
+ # HARD: Complex relationships and ratios
145
+ hard_indicators = [
146
+ "times more",
147
+ "times larger",
148
+ "times bigger",
149
+ "ratio",
150
+ "proportion",
151
+ "percentage of",
152
+ "compared to",
153
+ "relative to",
154
+ "in relation to",
155
+ "between",
156
+ "among",
157
+ "relationship",
158
+ ]
159
+
160
+ if any(indicator in constraint_text for indicator in hard_indicators):
161
+ return "HARD"
162
+
163
+ # MEDIUM: Numerical comparisons and ranges
164
+ medium_indicators = [
165
+ "number",
166
+ "count",
167
+ "amount",
168
+ "quantity",
169
+ "more than",
170
+ "less than",
171
+ "at least",
172
+ "at most",
173
+ "approximately",
174
+ "around",
175
+ "about",
176
+ "million",
177
+ "billion",
178
+ "thousand",
179
+ ]
180
+
181
+ if any(indicator in constraint_text for indicator in medium_indicators):
182
+ return "MEDIUM"
183
+
184
+ # EASY: Simple property checks
185
+ return "EASY"
186
+
187
+ def calculate_constraint_weight(
188
+ self, constraint: object, difficulty: str
189
+ ) -> float:
190
+ """
191
+ Calculate the weight for a constraint based on type and difficulty.
192
+
193
+ Args:
194
+ constraint: The constraint object
195
+ difficulty: Difficulty level ('EASY', 'MEDIUM', 'HARD')
196
+
197
+ Returns:
198
+ Float weight value
199
+ """
200
+ # Get base weight from constraint type
201
+ constraint_type = self._get_constraint_type(constraint)
202
+ base_weight = self.constraint_weights.get(constraint_type, 1.0)
203
+
204
+ # Apply difficulty multiplier
205
+ difficulty_multiplier = self.difficulty_multipliers.get(difficulty, 1.0)
206
+
207
+ # Calculate final weight
208
+ final_weight = base_weight * difficulty_multiplier
209
+
210
+ logger.debug(
211
+ f"Constraint weight: {constraint_type} × {difficulty} = "
212
+ f"{base_weight} × {difficulty_multiplier} = {final_weight}"
213
+ )
214
+
215
+ return final_weight
216
+
217
+ def verify_single_constraint(
218
+ self, candidate: object, constraint: object
219
+ ) -> float:
220
+ """
221
+ Verify a single constraint against a candidate.
222
+
223
+ This is a placeholder - implement actual verification logic
224
+ based on your constraint checking system.
225
+
226
+ Returns:
227
+ Float score from 0.0 to 1.0
228
+ """
229
+ # TODO: Implement actual constraint verification
230
+ # This should call your existing constraint checker
231
+
232
+ # For now, return a placeholder score
233
+ # In practice, this would call something like:
234
+ # return self.constraint_checker.verify(candidate, constraint)
235
+
236
+ return 0.5 # Placeholder
237
+
238
+ def _get_constraint_type(self, constraint: object) -> str:
239
+ """Extract constraint type from constraint object."""
240
+ if hasattr(constraint, "type"):
241
+ if hasattr(constraint.type, "value"):
242
+ return constraint.type.value
243
+ else:
244
+ return str(constraint.type)
245
+ elif hasattr(constraint, "constraint_type"):
246
+ return constraint.constraint_type
247
+ else:
248
+ # Try to infer from constraint text
249
+ constraint_text = str(constraint).lower()
250
+
251
+ if any(
252
+ word in constraint_text
253
+ for word in ["name", "called", "known as"]
254
+ ):
255
+ return "NAME_PATTERN"
256
+ elif any(
257
+ word in constraint_text
258
+ for word in ["location", "country", "city"]
259
+ ):
260
+ return "LOCATION"
261
+ elif any(
262
+ word in constraint_text
263
+ for word in ["year", "date", "when", "time"]
264
+ ):
265
+ return "TEMPORAL"
266
+ elif any(
267
+ word in constraint_text
268
+ for word in ["number", "count", "amount"]
269
+ ):
270
+ return "STATISTIC"
271
+ elif any(
272
+ word in constraint_text
273
+ for word in ["event", "happened", "occurred"]
274
+ ):
275
+ return "EVENT"
276
+ elif any(
277
+ word in constraint_text
278
+ for word in ["than", "more", "less", "compared"]
279
+ ):
280
+ return "COMPARISON"
281
+ else:
282
+ return "PROPERTY"
283
+
284
+ def _apply_reliability_adjustment(
285
+ self, weighted_score: float, constraint_scores: Dict[str, Dict]
286
+ ) -> float:
287
+ """
288
+ Apply reliability adjustment based on constraint mix.
289
+
290
+ Boosts score if high-reliability constraints are satisfied,
291
+ reduces score if only low-reliability constraints match.
292
+ """
293
+ # Count high vs low reliability constraints
294
+ high_reliability_count = 0
295
+ high_reliability_satisfied = 0
296
+ low_reliability_count = 0
297
+ low_reliability_satisfied = 0
298
+
299
+ high_reliability_types = {
300
+ "NAME_PATTERN",
301
+ "LOCATION",
302
+ "EVENT",
303
+ "TEMPORAL",
304
+ }
305
+ low_reliability_types = {"COMPARISON", "STATISTIC"}
306
+
307
+ for constraint_id, score_data in constraint_scores.items():
308
+ constraint_type = score_data.get("constraint_type", "PROPERTY")
309
+ satisfaction = score_data.get("satisfaction", 0.0)
310
+
311
+ if constraint_type in high_reliability_types:
312
+ high_reliability_count += 1
313
+ if satisfaction > 0.5:
314
+ high_reliability_satisfied += 1
315
+ elif constraint_type in low_reliability_types:
316
+ low_reliability_count += 1
317
+ if satisfaction > 0.5:
318
+ low_reliability_satisfied += 1
319
+
320
+ # Calculate reliability bonus/penalty
321
+ reliability_adjustment = 0.0
322
+
323
+ # Bonus for satisfying high-reliability constraints
324
+ if high_reliability_count > 0:
325
+ high_reliability_ratio = (
326
+ high_reliability_satisfied / high_reliability_count
327
+ )
328
+ reliability_adjustment += high_reliability_ratio * 0.1
329
+
330
+ # Small penalty if only low-reliability constraints are satisfied
331
+ if low_reliability_count > 0 and high_reliability_satisfied == 0:
332
+ low_reliability_ratio = (
333
+ low_reliability_satisfied / low_reliability_count
334
+ )
335
+ if low_reliability_ratio > 0.7: # Many low-reliability matches
336
+ reliability_adjustment -= 0.05
337
+
338
+ # Apply adjustment
339
+ adjusted_score = weighted_score + reliability_adjustment
340
+
341
+ # Ensure score stays in valid range
342
+ return max(0.0, min(1.0, adjusted_score))
343
+
344
+ def get_constraint_priorities(self) -> Dict[str, int]:
345
+ """
346
+ Get constraint priorities for relaxation strategies.
347
+
348
+ Returns priorities from 1 (relax first) to 10 (never relax).
349
+ """
350
+ return {
351
+ "COMPARISON": 1, # Frequently relax
352
+ "STATISTIC": 3, # Often relax
353
+ "PROPERTY": 6,
354
+ "EVENT": 5,
355
+ "TEMPORAL": 7,
356
+ "LOCATION": 8,
357
+ "EXISTENCE": 9, # Rarely relax
358
+ "NAME_PATTERN": 10, # Never relax
359
+ }
360
+
361
+ def suggest_constraint_relaxation(
362
+ self, constraints: List[object], current_results: List[object]
363
+ ) -> List[List[object]]:
364
+ """
365
+ Suggest progressive constraint relaxation based on results.
366
+
367
+ Returns list of relaxed constraint sets to try.
368
+ """
369
+ if len(current_results) > 5:
370
+ return [constraints] # Enough results, no relaxation needed
371
+
372
+ priorities = self.get_constraint_priorities()
373
+
374
+ # Sort constraints by relaxation priority (lowest first)
375
+ relaxable_constraints = sorted(
376
+ constraints,
377
+ key=lambda c: priorities.get(self._get_constraint_type(c), 5),
378
+ )
379
+
380
+ # Generate progressive relaxation sets
381
+ relaxed_sets = []
382
+ for i in range(1, min(len(constraints), 4)): # Max 3 relaxation levels
383
+ relaxed_set = relaxable_constraints[:-i] # Remove i lowest priority
384
+ if len(relaxed_set) >= 2: # Keep at least 2 constraints
385
+ relaxed_sets.append(relaxed_set)
386
+
387
+ return relaxed_sets