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.
- 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 +5 -3
- local_deep_research/web/database/models.py +51 -2
- local_deep_research/web/database/schema_upgrade.py +49 -29
- local_deep_research/web/models/database.py +51 -61
- 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 +227 -41
- local_deep_research/web/routes/settings_routes.py +156 -55
- local_deep_research/web/services/research_service.py +310 -103
- 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.0.dist-info}/METADATA +7 -1
- local_deep_research-0.5.0.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.0.dist-info}/WHEEL +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.4.4.dist-info → local_deep_research-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,380 @@
|
|
1
|
+
"""
|
2
|
+
Search call tracking system for metrics collection.
|
3
|
+
Similar to token_counter.py but tracks search engine usage.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from typing import Any, Dict, List, Optional
|
7
|
+
|
8
|
+
from loguru import logger
|
9
|
+
from sqlalchemy import case, func
|
10
|
+
|
11
|
+
from .database import MetricsDatabase
|
12
|
+
from .db_models import SearchCall
|
13
|
+
from .query_utils import get_research_mode_condition, get_time_filter_condition
|
14
|
+
|
15
|
+
|
16
|
+
class SearchTracker:
|
17
|
+
"""Track search engine calls and performance metrics."""
|
18
|
+
|
19
|
+
def __init__(self, db: Optional[MetricsDatabase] = None):
|
20
|
+
"""Initialize the search tracker."""
|
21
|
+
self.db = db or MetricsDatabase()
|
22
|
+
self.research_context = {}
|
23
|
+
|
24
|
+
def set_research_context(self, context: Dict[str, Any]) -> None:
|
25
|
+
"""Set the current research context for search tracking."""
|
26
|
+
self.research_context = context or {}
|
27
|
+
logger.debug(f"Search tracker context updated: {self.research_context}")
|
28
|
+
|
29
|
+
def record_search(
|
30
|
+
self,
|
31
|
+
engine_name: str,
|
32
|
+
query: str,
|
33
|
+
results_count: int = 0,
|
34
|
+
response_time_ms: int = 0,
|
35
|
+
success: bool = True,
|
36
|
+
error_message: Optional[str] = None,
|
37
|
+
) -> None:
|
38
|
+
"""Record a completed search operation directly to database."""
|
39
|
+
|
40
|
+
# Extract research context
|
41
|
+
research_id = self.research_context.get("research_id")
|
42
|
+
research_query = self.research_context.get("research_query")
|
43
|
+
research_mode = self.research_context.get("research_mode", "unknown")
|
44
|
+
research_phase = self.research_context.get("research_phase", "search")
|
45
|
+
search_iteration = self.research_context.get("search_iteration", 0)
|
46
|
+
|
47
|
+
# Determine success status
|
48
|
+
success_status = "success" if success else "error"
|
49
|
+
error_type = None
|
50
|
+
if error_message:
|
51
|
+
error_type = (
|
52
|
+
type(error_message).__name__
|
53
|
+
if isinstance(error_message, Exception)
|
54
|
+
else "unknown_error"
|
55
|
+
)
|
56
|
+
|
57
|
+
# Record search call in database
|
58
|
+
try:
|
59
|
+
with self.db.get_session() as session:
|
60
|
+
# Create search call record
|
61
|
+
search_call = SearchCall(
|
62
|
+
research_id=research_id,
|
63
|
+
research_query=research_query,
|
64
|
+
research_mode=research_mode,
|
65
|
+
research_phase=research_phase,
|
66
|
+
search_iteration=search_iteration,
|
67
|
+
search_engine=engine_name,
|
68
|
+
query=query,
|
69
|
+
results_count=results_count,
|
70
|
+
response_time_ms=response_time_ms,
|
71
|
+
success_status=success_status,
|
72
|
+
error_type=error_type,
|
73
|
+
error_message=str(error_message) if error_message else None,
|
74
|
+
)
|
75
|
+
session.add(search_call)
|
76
|
+
session.commit()
|
77
|
+
|
78
|
+
logger.debug(
|
79
|
+
f"Search call recorded: {engine_name} - "
|
80
|
+
f"{results_count} results in {response_time_ms}ms"
|
81
|
+
)
|
82
|
+
|
83
|
+
except Exception as e:
|
84
|
+
logger.error(f"Failed to record search call: {e}")
|
85
|
+
|
86
|
+
def get_search_metrics(
|
87
|
+
self, period: str = "30d", research_mode: str = "all"
|
88
|
+
) -> Dict[str, Any]:
|
89
|
+
"""Get search engine usage metrics."""
|
90
|
+
with self.db.get_session() as session:
|
91
|
+
try:
|
92
|
+
# Build base query with filters
|
93
|
+
query = session.query(SearchCall).filter(
|
94
|
+
SearchCall.search_engine.isnot(None)
|
95
|
+
)
|
96
|
+
|
97
|
+
# Apply time filter
|
98
|
+
time_condition = get_time_filter_condition(
|
99
|
+
period, SearchCall.timestamp
|
100
|
+
)
|
101
|
+
if time_condition is not None:
|
102
|
+
query = query.filter(time_condition)
|
103
|
+
|
104
|
+
# Apply research mode filter
|
105
|
+
mode_condition = get_research_mode_condition(
|
106
|
+
research_mode, SearchCall.research_mode
|
107
|
+
)
|
108
|
+
if mode_condition is not None:
|
109
|
+
query = query.filter(mode_condition)
|
110
|
+
|
111
|
+
# Get search engine statistics using ORM aggregation
|
112
|
+
search_stats = session.query(
|
113
|
+
SearchCall.search_engine,
|
114
|
+
func.count().label("call_count"),
|
115
|
+
func.avg(SearchCall.response_time_ms).label(
|
116
|
+
"avg_response_time"
|
117
|
+
),
|
118
|
+
func.sum(SearchCall.results_count).label("total_results"),
|
119
|
+
func.avg(SearchCall.results_count).label(
|
120
|
+
"avg_results_per_call"
|
121
|
+
),
|
122
|
+
func.sum(
|
123
|
+
case(
|
124
|
+
(SearchCall.success_status == "success", 1), else_=0
|
125
|
+
)
|
126
|
+
).label("success_count"),
|
127
|
+
func.sum(
|
128
|
+
case((SearchCall.success_status == "error", 1), else_=0)
|
129
|
+
).label("error_count"),
|
130
|
+
).filter(SearchCall.search_engine.isnot(None))
|
131
|
+
|
132
|
+
# Apply same filters to stats query
|
133
|
+
if time_condition is not None:
|
134
|
+
search_stats = search_stats.filter(time_condition)
|
135
|
+
if mode_condition is not None:
|
136
|
+
search_stats = search_stats.filter(mode_condition)
|
137
|
+
|
138
|
+
search_stats = (
|
139
|
+
search_stats.group_by(SearchCall.search_engine)
|
140
|
+
.order_by(func.count().desc())
|
141
|
+
.all()
|
142
|
+
)
|
143
|
+
|
144
|
+
# Get recent search calls
|
145
|
+
recent_calls_query = session.query(SearchCall)
|
146
|
+
if time_condition is not None:
|
147
|
+
recent_calls_query = recent_calls_query.filter(
|
148
|
+
time_condition
|
149
|
+
)
|
150
|
+
if mode_condition is not None:
|
151
|
+
recent_calls_query = recent_calls_query.filter(
|
152
|
+
mode_condition
|
153
|
+
)
|
154
|
+
|
155
|
+
recent_calls = (
|
156
|
+
recent_calls_query.order_by(SearchCall.timestamp.desc())
|
157
|
+
.limit(20)
|
158
|
+
.all()
|
159
|
+
)
|
160
|
+
|
161
|
+
return {
|
162
|
+
"search_engine_stats": [
|
163
|
+
{
|
164
|
+
"engine": stat.search_engine,
|
165
|
+
"call_count": stat.call_count,
|
166
|
+
"avg_response_time": stat.avg_response_time or 0,
|
167
|
+
"total_results": stat.total_results or 0,
|
168
|
+
"avg_results_per_call": stat.avg_results_per_call
|
169
|
+
or 0,
|
170
|
+
"success_rate": (
|
171
|
+
(stat.success_count / stat.call_count * 100)
|
172
|
+
if stat.call_count > 0
|
173
|
+
else 0
|
174
|
+
),
|
175
|
+
"error_count": stat.error_count or 0,
|
176
|
+
}
|
177
|
+
for stat in search_stats
|
178
|
+
],
|
179
|
+
"recent_calls": [
|
180
|
+
{
|
181
|
+
"engine": call.search_engine,
|
182
|
+
"query": (
|
183
|
+
call.query[:100] + "..."
|
184
|
+
if len(call.query or "") > 100
|
185
|
+
else call.query
|
186
|
+
),
|
187
|
+
"results_count": call.results_count,
|
188
|
+
"response_time_ms": call.response_time_ms,
|
189
|
+
"success_status": call.success_status,
|
190
|
+
"timestamp": str(call.timestamp),
|
191
|
+
}
|
192
|
+
for call in recent_calls
|
193
|
+
],
|
194
|
+
}
|
195
|
+
|
196
|
+
except Exception as e:
|
197
|
+
logger.exception(f"Error getting search metrics: {e}")
|
198
|
+
return {"search_engine_stats": [], "recent_calls": []}
|
199
|
+
|
200
|
+
def get_research_search_metrics(self, research_id: int) -> Dict[str, Any]:
|
201
|
+
"""Get search metrics for a specific research session."""
|
202
|
+
with self.db.get_session() as session:
|
203
|
+
try:
|
204
|
+
# Get all search calls for this research
|
205
|
+
search_calls = (
|
206
|
+
session.query(SearchCall)
|
207
|
+
.filter(SearchCall.research_id == research_id)
|
208
|
+
.order_by(SearchCall.timestamp.asc())
|
209
|
+
.all()
|
210
|
+
)
|
211
|
+
|
212
|
+
# Get search engine stats for this research
|
213
|
+
engine_stats = (
|
214
|
+
session.query(
|
215
|
+
SearchCall.search_engine,
|
216
|
+
func.count().label("call_count"),
|
217
|
+
func.avg(SearchCall.response_time_ms).label(
|
218
|
+
"avg_response_time"
|
219
|
+
),
|
220
|
+
func.sum(SearchCall.results_count).label(
|
221
|
+
"total_results"
|
222
|
+
),
|
223
|
+
func.sum(
|
224
|
+
case(
|
225
|
+
(SearchCall.success_status == "success", 1),
|
226
|
+
else_=0,
|
227
|
+
)
|
228
|
+
).label("success_count"),
|
229
|
+
)
|
230
|
+
.filter(SearchCall.research_id == research_id)
|
231
|
+
.group_by(SearchCall.search_engine)
|
232
|
+
.order_by(func.count().desc())
|
233
|
+
.all()
|
234
|
+
)
|
235
|
+
|
236
|
+
# Calculate totals
|
237
|
+
total_searches = len(search_calls)
|
238
|
+
total_results = sum(
|
239
|
+
call.results_count or 0 for call in search_calls
|
240
|
+
)
|
241
|
+
avg_response_time = (
|
242
|
+
sum(call.response_time_ms or 0 for call in search_calls)
|
243
|
+
/ total_searches
|
244
|
+
if total_searches > 0
|
245
|
+
else 0
|
246
|
+
)
|
247
|
+
successful_searches = sum(
|
248
|
+
1
|
249
|
+
for call in search_calls
|
250
|
+
if call.success_status == "success"
|
251
|
+
)
|
252
|
+
success_rate = (
|
253
|
+
(successful_searches / total_searches * 100)
|
254
|
+
if total_searches > 0
|
255
|
+
else 0
|
256
|
+
)
|
257
|
+
|
258
|
+
return {
|
259
|
+
"total_searches": total_searches,
|
260
|
+
"total_results": total_results,
|
261
|
+
"avg_response_time": round(avg_response_time),
|
262
|
+
"success_rate": round(success_rate, 1),
|
263
|
+
"search_calls": [
|
264
|
+
{
|
265
|
+
"engine": call.search_engine,
|
266
|
+
"query": call.query,
|
267
|
+
"results_count": call.results_count,
|
268
|
+
"response_time_ms": call.response_time_ms,
|
269
|
+
"success_status": call.success_status,
|
270
|
+
"timestamp": str(call.timestamp),
|
271
|
+
}
|
272
|
+
for call in search_calls
|
273
|
+
],
|
274
|
+
"engine_stats": [
|
275
|
+
{
|
276
|
+
"engine": stat.search_engine,
|
277
|
+
"call_count": stat.call_count,
|
278
|
+
"avg_response_time": stat.avg_response_time or 0,
|
279
|
+
"total_results": stat.total_results or 0,
|
280
|
+
"success_rate": (
|
281
|
+
(stat.success_count / stat.call_count * 100)
|
282
|
+
if stat.call_count > 0
|
283
|
+
else 0
|
284
|
+
),
|
285
|
+
}
|
286
|
+
for stat in engine_stats
|
287
|
+
],
|
288
|
+
}
|
289
|
+
|
290
|
+
except Exception as e:
|
291
|
+
logger.exception(f"Error getting research search metrics: {e}")
|
292
|
+
return {
|
293
|
+
"total_searches": 0,
|
294
|
+
"total_results": 0,
|
295
|
+
"avg_response_time": 0,
|
296
|
+
"success_rate": 0,
|
297
|
+
"search_calls": [],
|
298
|
+
"engine_stats": [],
|
299
|
+
}
|
300
|
+
|
301
|
+
def get_search_time_series(
|
302
|
+
self, period: str = "30d", research_mode: str = "all"
|
303
|
+
) -> List[Dict[str, Any]]:
|
304
|
+
"""Get search activity time series data for charting.
|
305
|
+
|
306
|
+
Args:
|
307
|
+
period: Time period to filter by ('7d', '30d', '3m', '1y', 'all')
|
308
|
+
research_mode: Research mode to filter by ('quick', 'detailed', 'all')
|
309
|
+
|
310
|
+
Returns:
|
311
|
+
List of time series data points with search engine activity
|
312
|
+
"""
|
313
|
+
with self.db.get_session() as session:
|
314
|
+
try:
|
315
|
+
# Build base query
|
316
|
+
query = session.query(SearchCall).filter(
|
317
|
+
SearchCall.search_engine.isnot(None),
|
318
|
+
SearchCall.timestamp.isnot(None),
|
319
|
+
)
|
320
|
+
|
321
|
+
# Apply time filter
|
322
|
+
time_condition = get_time_filter_condition(
|
323
|
+
period, SearchCall.timestamp
|
324
|
+
)
|
325
|
+
if time_condition is not None:
|
326
|
+
query = query.filter(time_condition)
|
327
|
+
|
328
|
+
# Apply research mode filter
|
329
|
+
mode_condition = get_research_mode_condition(
|
330
|
+
research_mode, SearchCall.research_mode
|
331
|
+
)
|
332
|
+
if mode_condition is not None:
|
333
|
+
query = query.filter(mode_condition)
|
334
|
+
|
335
|
+
# Get all search calls ordered by time
|
336
|
+
search_calls = query.order_by(SearchCall.timestamp.asc()).all()
|
337
|
+
|
338
|
+
# Create time series data
|
339
|
+
time_series = []
|
340
|
+
for call in search_calls:
|
341
|
+
time_series.append(
|
342
|
+
{
|
343
|
+
"timestamp": (
|
344
|
+
str(call.timestamp) if call.timestamp else None
|
345
|
+
),
|
346
|
+
"search_engine": call.search_engine,
|
347
|
+
"results_count": call.results_count or 0,
|
348
|
+
"response_time_ms": call.response_time_ms or 0,
|
349
|
+
"success_status": call.success_status,
|
350
|
+
"query": (
|
351
|
+
call.query[:50] + "..."
|
352
|
+
if call.query and len(call.query) > 50
|
353
|
+
else call.query
|
354
|
+
),
|
355
|
+
}
|
356
|
+
)
|
357
|
+
|
358
|
+
return time_series
|
359
|
+
|
360
|
+
except Exception as e:
|
361
|
+
logger.exception(f"Error getting search time series: {e}")
|
362
|
+
return []
|
363
|
+
|
364
|
+
|
365
|
+
# Global search tracker instance
|
366
|
+
_search_tracker = None
|
367
|
+
|
368
|
+
|
369
|
+
def get_search_tracker() -> SearchTracker:
|
370
|
+
"""Get the global search tracker instance."""
|
371
|
+
global _search_tracker
|
372
|
+
if _search_tracker is None:
|
373
|
+
_search_tracker = SearchTracker()
|
374
|
+
return _search_tracker
|
375
|
+
|
376
|
+
|
377
|
+
def set_search_context(context: Dict[str, Any]) -> None:
|
378
|
+
"""Set search context for the global tracker."""
|
379
|
+
tracker = get_search_tracker()
|
380
|
+
tracker.set_research_context(context)
|