local-deep-research 0.4.4__py3-none-any.whl → 0.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- local_deep_research/__init__.py +7 -0
- local_deep_research/__version__.py +1 -1
- local_deep_research/advanced_search_system/answer_decoding/__init__.py +5 -0
- local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py +421 -0
- local_deep_research/advanced_search_system/candidate_exploration/README.md +219 -0
- local_deep_research/advanced_search_system/candidate_exploration/__init__.py +25 -0
- local_deep_research/advanced_search_system/candidate_exploration/adaptive_explorer.py +329 -0
- local_deep_research/advanced_search_system/candidate_exploration/base_explorer.py +341 -0
- local_deep_research/advanced_search_system/candidate_exploration/constraint_guided_explorer.py +436 -0
- local_deep_research/advanced_search_system/candidate_exploration/diversity_explorer.py +457 -0
- local_deep_research/advanced_search_system/candidate_exploration/parallel_explorer.py +250 -0
- local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +255 -0
- local_deep_research/advanced_search_system/candidates/__init__.py +5 -0
- local_deep_research/advanced_search_system/candidates/base_candidate.py +59 -0
- local_deep_research/advanced_search_system/constraint_checking/README.md +150 -0
- local_deep_research/advanced_search_system/constraint_checking/__init__.py +35 -0
- local_deep_research/advanced_search_system/constraint_checking/base_constraint_checker.py +122 -0
- local_deep_research/advanced_search_system/constraint_checking/constraint_checker.py +223 -0
- local_deep_research/advanced_search_system/constraint_checking/constraint_satisfaction_tracker.py +387 -0
- local_deep_research/advanced_search_system/constraint_checking/dual_confidence_checker.py +424 -0
- local_deep_research/advanced_search_system/constraint_checking/evidence_analyzer.py +174 -0
- local_deep_research/advanced_search_system/constraint_checking/intelligent_constraint_relaxer.py +503 -0
- local_deep_research/advanced_search_system/constraint_checking/rejection_engine.py +143 -0
- local_deep_research/advanced_search_system/constraint_checking/strict_checker.py +259 -0
- local_deep_research/advanced_search_system/constraint_checking/threshold_checker.py +213 -0
- local_deep_research/advanced_search_system/constraints/__init__.py +6 -0
- local_deep_research/advanced_search_system/constraints/base_constraint.py +58 -0
- local_deep_research/advanced_search_system/constraints/constraint_analyzer.py +143 -0
- local_deep_research/advanced_search_system/evidence/__init__.py +12 -0
- local_deep_research/advanced_search_system/evidence/base_evidence.py +57 -0
- local_deep_research/advanced_search_system/evidence/evaluator.py +159 -0
- local_deep_research/advanced_search_system/evidence/requirements.py +122 -0
- local_deep_research/advanced_search_system/filters/base_filter.py +3 -1
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +8 -2
- local_deep_research/advanced_search_system/filters/journal_reputation_filter.py +43 -29
- local_deep_research/advanced_search_system/findings/repository.py +54 -17
- local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +3 -1
- local_deep_research/advanced_search_system/query_generation/adaptive_query_generator.py +405 -0
- local_deep_research/advanced_search_system/questions/__init__.py +16 -0
- local_deep_research/advanced_search_system/questions/atomic_fact_question.py +171 -0
- local_deep_research/advanced_search_system/questions/browsecomp_question.py +287 -0
- local_deep_research/advanced_search_system/questions/decomposition_question.py +13 -4
- local_deep_research/advanced_search_system/questions/entity_aware_question.py +184 -0
- local_deep_research/advanced_search_system/questions/standard_question.py +9 -3
- local_deep_research/advanced_search_system/search_optimization/cross_constraint_manager.py +624 -0
- local_deep_research/advanced_search_system/source_management/diversity_manager.py +613 -0
- local_deep_research/advanced_search_system/strategies/__init__.py +42 -0
- local_deep_research/advanced_search_system/strategies/adaptive_decomposition_strategy.py +564 -0
- local_deep_research/advanced_search_system/strategies/base_strategy.py +4 -4
- local_deep_research/advanced_search_system/strategies/browsecomp_entity_strategy.py +1031 -0
- local_deep_research/advanced_search_system/strategies/browsecomp_optimized_strategy.py +778 -0
- local_deep_research/advanced_search_system/strategies/concurrent_dual_confidence_strategy.py +446 -0
- local_deep_research/advanced_search_system/strategies/constrained_search_strategy.py +1348 -0
- local_deep_research/advanced_search_system/strategies/constraint_parallel_strategy.py +522 -0
- local_deep_research/advanced_search_system/strategies/direct_search_strategy.py +217 -0
- local_deep_research/advanced_search_system/strategies/dual_confidence_strategy.py +320 -0
- local_deep_research/advanced_search_system/strategies/dual_confidence_with_rejection.py +219 -0
- local_deep_research/advanced_search_system/strategies/early_stop_constrained_strategy.py +369 -0
- local_deep_research/advanced_search_system/strategies/entity_aware_source_strategy.py +140 -0
- local_deep_research/advanced_search_system/strategies/evidence_based_strategy.py +1248 -0
- local_deep_research/advanced_search_system/strategies/evidence_based_strategy_v2.py +1337 -0
- local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +537 -0
- local_deep_research/advanced_search_system/strategies/improved_evidence_based_strategy.py +782 -0
- local_deep_research/advanced_search_system/strategies/iterative_reasoning_strategy.py +760 -0
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +55 -21
- local_deep_research/advanced_search_system/strategies/llm_driven_modular_strategy.py +865 -0
- local_deep_research/advanced_search_system/strategies/modular_strategy.py +1142 -0
- local_deep_research/advanced_search_system/strategies/parallel_constrained_strategy.py +506 -0
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +34 -16
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +29 -9
- local_deep_research/advanced_search_system/strategies/recursive_decomposition_strategy.py +492 -0
- local_deep_research/advanced_search_system/strategies/smart_decomposition_strategy.py +284 -0
- local_deep_research/advanced_search_system/strategies/smart_query_strategy.py +515 -0
- local_deep_research/advanced_search_system/strategies/source_based_strategy.py +48 -24
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +34 -14
- local_deep_research/advanced_search_system/tools/base_tool.py +7 -2
- local_deep_research/api/benchmark_functions.py +6 -2
- local_deep_research/api/research_functions.py +10 -4
- local_deep_research/benchmarks/__init__.py +9 -7
- local_deep_research/benchmarks/benchmark_functions.py +6 -2
- local_deep_research/benchmarks/cli/benchmark_commands.py +27 -10
- local_deep_research/benchmarks/cli.py +38 -13
- local_deep_research/benchmarks/comparison/__init__.py +4 -2
- local_deep_research/benchmarks/comparison/evaluator.py +316 -239
- local_deep_research/benchmarks/datasets/__init__.py +1 -1
- local_deep_research/benchmarks/datasets/base.py +91 -72
- local_deep_research/benchmarks/datasets/browsecomp.py +54 -33
- local_deep_research/benchmarks/datasets/custom_dataset_template.py +19 -19
- local_deep_research/benchmarks/datasets/simpleqa.py +14 -14
- local_deep_research/benchmarks/datasets/utils.py +48 -29
- local_deep_research/benchmarks/datasets.py +4 -11
- local_deep_research/benchmarks/efficiency/__init__.py +8 -4
- local_deep_research/benchmarks/efficiency/resource_monitor.py +223 -171
- local_deep_research/benchmarks/efficiency/speed_profiler.py +62 -48
- local_deep_research/benchmarks/evaluators/browsecomp.py +3 -1
- local_deep_research/benchmarks/evaluators/composite.py +6 -2
- local_deep_research/benchmarks/evaluators/simpleqa.py +36 -13
- local_deep_research/benchmarks/graders.py +32 -10
- local_deep_research/benchmarks/metrics/README.md +1 -1
- local_deep_research/benchmarks/metrics/calculation.py +25 -10
- local_deep_research/benchmarks/metrics/reporting.py +7 -3
- local_deep_research/benchmarks/metrics/visualization.py +42 -23
- local_deep_research/benchmarks/metrics.py +1 -1
- local_deep_research/benchmarks/optimization/__init__.py +3 -1
- local_deep_research/benchmarks/optimization/api.py +7 -1
- local_deep_research/benchmarks/optimization/optuna_optimizer.py +75 -26
- local_deep_research/benchmarks/runners.py +48 -15
- local_deep_research/citation_handler.py +65 -92
- local_deep_research/citation_handlers/__init__.py +15 -0
- local_deep_research/citation_handlers/base_citation_handler.py +70 -0
- local_deep_research/citation_handlers/forced_answer_citation_handler.py +179 -0
- local_deep_research/citation_handlers/precision_extraction_handler.py +550 -0
- local_deep_research/citation_handlers/standard_citation_handler.py +80 -0
- local_deep_research/config/llm_config.py +271 -169
- local_deep_research/config/search_config.py +14 -5
- local_deep_research/defaults/__init__.py +0 -1
- local_deep_research/metrics/__init__.py +13 -0
- local_deep_research/metrics/database.py +58 -0
- local_deep_research/metrics/db_models.py +115 -0
- local_deep_research/metrics/migrate_add_provider_to_token_usage.py +148 -0
- local_deep_research/metrics/migrate_call_stack_tracking.py +105 -0
- local_deep_research/metrics/migrate_enhanced_tracking.py +75 -0
- local_deep_research/metrics/migrate_research_ratings.py +31 -0
- local_deep_research/metrics/models.py +61 -0
- local_deep_research/metrics/pricing/__init__.py +12 -0
- local_deep_research/metrics/pricing/cost_calculator.py +237 -0
- local_deep_research/metrics/pricing/pricing_cache.py +143 -0
- local_deep_research/metrics/pricing/pricing_fetcher.py +240 -0
- local_deep_research/metrics/query_utils.py +51 -0
- local_deep_research/metrics/search_tracker.py +380 -0
- local_deep_research/metrics/token_counter.py +1078 -0
- local_deep_research/migrate_db.py +3 -1
- local_deep_research/report_generator.py +22 -8
- local_deep_research/search_system.py +390 -9
- local_deep_research/test_migration.py +15 -5
- local_deep_research/utilities/db_utils.py +7 -4
- local_deep_research/utilities/es_utils.py +115 -104
- local_deep_research/utilities/llm_utils.py +15 -5
- local_deep_research/utilities/log_utils.py +151 -0
- local_deep_research/utilities/search_cache.py +387 -0
- local_deep_research/utilities/search_utilities.py +14 -6
- local_deep_research/utilities/threading_utils.py +92 -0
- local_deep_research/utilities/url_utils.py +6 -0
- local_deep_research/web/api.py +347 -0
- local_deep_research/web/app.py +13 -17
- local_deep_research/web/app_factory.py +71 -66
- local_deep_research/web/database/migrate_to_ldr_db.py +12 -4
- local_deep_research/web/database/migrations.py +20 -3
- local_deep_research/web/database/models.py +74 -25
- local_deep_research/web/database/schema_upgrade.py +49 -29
- local_deep_research/web/models/database.py +63 -83
- local_deep_research/web/routes/api_routes.py +56 -22
- local_deep_research/web/routes/benchmark_routes.py +4 -1
- local_deep_research/web/routes/globals.py +22 -0
- local_deep_research/web/routes/history_routes.py +71 -46
- local_deep_research/web/routes/metrics_routes.py +1155 -0
- local_deep_research/web/routes/research_routes.py +192 -54
- local_deep_research/web/routes/settings_routes.py +156 -55
- local_deep_research/web/services/research_service.py +412 -251
- local_deep_research/web/services/resource_service.py +36 -11
- local_deep_research/web/services/settings_manager.py +55 -17
- local_deep_research/web/services/settings_service.py +12 -4
- local_deep_research/web/services/socket_service.py +295 -188
- local_deep_research/web/static/css/custom_dropdown.css +180 -0
- local_deep_research/web/static/css/styles.css +39 -1
- local_deep_research/web/static/js/components/detail.js +633 -267
- local_deep_research/web/static/js/components/details.js +751 -0
- local_deep_research/web/static/js/components/fallback/formatting.js +11 -11
- local_deep_research/web/static/js/components/fallback/ui.js +23 -23
- local_deep_research/web/static/js/components/history.js +76 -76
- local_deep_research/web/static/js/components/logpanel.js +61 -13
- local_deep_research/web/static/js/components/progress.js +13 -2
- local_deep_research/web/static/js/components/research.js +99 -12
- local_deep_research/web/static/js/components/results.js +239 -106
- local_deep_research/web/static/js/main.js +40 -40
- local_deep_research/web/static/js/services/audio.js +1 -1
- local_deep_research/web/static/js/services/formatting.js +11 -11
- local_deep_research/web/static/js/services/keyboard.js +157 -0
- local_deep_research/web/static/js/services/pdf.js +80 -80
- local_deep_research/web/static/sounds/README.md +1 -1
- local_deep_research/web/templates/base.html +1 -0
- local_deep_research/web/templates/components/log_panel.html +7 -1
- local_deep_research/web/templates/components/mobile_nav.html +1 -1
- local_deep_research/web/templates/components/sidebar.html +3 -0
- local_deep_research/web/templates/pages/cost_analytics.html +1245 -0
- local_deep_research/web/templates/pages/details.html +325 -24
- local_deep_research/web/templates/pages/history.html +1 -1
- local_deep_research/web/templates/pages/metrics.html +1929 -0
- local_deep_research/web/templates/pages/progress.html +2 -2
- local_deep_research/web/templates/pages/research.html +53 -17
- local_deep_research/web/templates/pages/results.html +12 -1
- local_deep_research/web/templates/pages/star_reviews.html +803 -0
- local_deep_research/web/utils/formatters.py +9 -3
- local_deep_research/web_search_engines/default_search_engines.py +5 -3
- local_deep_research/web_search_engines/engines/full_search.py +8 -2
- local_deep_research/web_search_engines/engines/meta_search_engine.py +59 -20
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +19 -6
- local_deep_research/web_search_engines/engines/search_engine_brave.py +6 -2
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +3 -1
- local_deep_research/web_search_engines/engines/search_engine_elasticsearch.py +81 -58
- local_deep_research/web_search_engines/engines/search_engine_github.py +46 -15
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +16 -6
- local_deep_research/web_search_engines/engines/search_engine_guardian.py +39 -15
- local_deep_research/web_search_engines/engines/search_engine_local.py +58 -25
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +15 -5
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +63 -21
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +37 -11
- local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +27 -9
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +12 -4
- local_deep_research/web_search_engines/engines/search_engine_wayback.py +31 -10
- local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +12 -3
- local_deep_research/web_search_engines/search_engine_base.py +83 -35
- local_deep_research/web_search_engines/search_engine_factory.py +25 -8
- local_deep_research/web_search_engines/search_engines_config.py +9 -3
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/METADATA +7 -1
- local_deep_research-0.5.2.dist-info/RECORD +265 -0
- local_deep_research-0.4.4.dist-info/RECORD +0 -177
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/WHEEL +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.2.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 []
|
local_deep_research/advanced_search_system/constraint_checking/constraint_satisfaction_tracker.py
ADDED
@@ -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
|