local-deep-research 0.5.7__py3-none-any.whl → 0.6.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/__version__.py +1 -1
- local_deep_research/advanced_search_system/candidate_exploration/progressive_explorer.py +11 -1
- local_deep_research/advanced_search_system/questions/browsecomp_question.py +32 -6
- local_deep_research/advanced_search_system/strategies/focused_iteration_strategy.py +33 -8
- local_deep_research/advanced_search_system/strategies/source_based_strategy.py +2 -0
- local_deep_research/api/__init__.py +2 -0
- local_deep_research/api/research_functions.py +177 -3
- local_deep_research/benchmarks/graders.py +150 -5
- local_deep_research/benchmarks/models/__init__.py +19 -0
- local_deep_research/benchmarks/models/benchmark_models.py +283 -0
- local_deep_research/benchmarks/ui/__init__.py +1 -0
- local_deep_research/benchmarks/web_api/__init__.py +6 -0
- local_deep_research/benchmarks/web_api/benchmark_routes.py +862 -0
- local_deep_research/benchmarks/web_api/benchmark_service.py +920 -0
- local_deep_research/config/llm_config.py +106 -21
- local_deep_research/defaults/default_settings.json +448 -3
- local_deep_research/error_handling/report_generator.py +10 -0
- local_deep_research/llm/__init__.py +19 -0
- local_deep_research/llm/llm_registry.py +155 -0
- local_deep_research/metrics/db_models.py +3 -7
- local_deep_research/metrics/search_tracker.py +25 -11
- local_deep_research/report_generator.py +3 -2
- local_deep_research/search_system.py +12 -9
- local_deep_research/utilities/log_utils.py +23 -10
- local_deep_research/utilities/thread_context.py +99 -0
- local_deep_research/web/app_factory.py +32 -8
- local_deep_research/web/database/benchmark_schema.py +230 -0
- local_deep_research/web/database/convert_research_id_to_string.py +161 -0
- local_deep_research/web/database/models.py +55 -1
- local_deep_research/web/database/schema_upgrade.py +397 -2
- local_deep_research/web/database/uuid_migration.py +265 -0
- local_deep_research/web/routes/api_routes.py +62 -31
- local_deep_research/web/routes/history_routes.py +13 -6
- local_deep_research/web/routes/metrics_routes.py +264 -4
- local_deep_research/web/routes/research_routes.py +45 -18
- local_deep_research/web/routes/route_registry.py +352 -0
- local_deep_research/web/routes/settings_routes.py +382 -22
- local_deep_research/web/services/research_service.py +22 -29
- local_deep_research/web/services/settings_manager.py +53 -0
- local_deep_research/web/services/settings_service.py +2 -0
- local_deep_research/web/static/css/styles.css +8 -0
- local_deep_research/web/static/js/components/detail.js +7 -14
- local_deep_research/web/static/js/components/details.js +8 -10
- local_deep_research/web/static/js/components/fallback/ui.js +4 -4
- local_deep_research/web/static/js/components/history.js +6 -6
- local_deep_research/web/static/js/components/logpanel.js +14 -11
- local_deep_research/web/static/js/components/progress.js +51 -46
- local_deep_research/web/static/js/components/research.js +250 -89
- local_deep_research/web/static/js/components/results.js +5 -7
- local_deep_research/web/static/js/components/settings.js +32 -26
- local_deep_research/web/static/js/components/settings_sync.js +24 -23
- local_deep_research/web/static/js/config/urls.js +285 -0
- local_deep_research/web/static/js/main.js +8 -8
- local_deep_research/web/static/js/research_form.js +267 -12
- local_deep_research/web/static/js/services/api.js +18 -18
- local_deep_research/web/static/js/services/keyboard.js +8 -8
- local_deep_research/web/static/js/services/socket.js +53 -35
- local_deep_research/web/static/js/services/ui.js +1 -1
- local_deep_research/web/templates/base.html +4 -1
- local_deep_research/web/templates/components/custom_dropdown.html +5 -3
- local_deep_research/web/templates/components/mobile_nav.html +3 -3
- local_deep_research/web/templates/components/sidebar.html +9 -3
- local_deep_research/web/templates/pages/benchmark.html +2697 -0
- local_deep_research/web/templates/pages/benchmark_results.html +1136 -0
- local_deep_research/web/templates/pages/benchmark_simple.html +453 -0
- local_deep_research/web/templates/pages/cost_analytics.html +1 -1
- local_deep_research/web/templates/pages/metrics.html +212 -39
- local_deep_research/web/templates/pages/research.html +8 -6
- local_deep_research/web/templates/pages/star_reviews.html +1 -1
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +14 -1
- local_deep_research/web_search_engines/engines/search_engine_brave.py +15 -1
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +20 -1
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +26 -2
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +15 -1
- local_deep_research/web_search_engines/engines/search_engine_retriever.py +192 -0
- local_deep_research/web_search_engines/engines/search_engine_tavily.py +307 -0
- local_deep_research/web_search_engines/rate_limiting/__init__.py +14 -0
- local_deep_research/web_search_engines/rate_limiting/__main__.py +9 -0
- local_deep_research/web_search_engines/rate_limiting/cli.py +209 -0
- local_deep_research/web_search_engines/rate_limiting/exceptions.py +21 -0
- local_deep_research/web_search_engines/rate_limiting/tracker.py +506 -0
- local_deep_research/web_search_engines/retriever_registry.py +108 -0
- local_deep_research/web_search_engines/search_engine_base.py +161 -43
- local_deep_research/web_search_engines/search_engine_factory.py +14 -0
- local_deep_research/web_search_engines/search_engines_config.py +20 -0
- local_deep_research-0.6.0.dist-info/METADATA +374 -0
- {local_deep_research-0.5.7.dist-info → local_deep_research-0.6.0.dist-info}/RECORD +90 -65
- local_deep_research-0.5.7.dist-info/METADATA +0 -420
- {local_deep_research-0.5.7.dist-info → local_deep_research-0.6.0.dist-info}/WHEEL +0 -0
- {local_deep_research-0.5.7.dist-info → local_deep_research-0.6.0.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.5.7.dist-info → local_deep_research-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,6 +5,7 @@ import requests
|
|
5
5
|
from flask import Blueprint, current_app, jsonify, request
|
6
6
|
|
7
7
|
from ...utilities.url_utils import normalize_url
|
8
|
+
from ...utilities.db_utils import get_db_session
|
8
9
|
from ..models.database import get_db_connection
|
9
10
|
from ..routes.research_routes import active_research, termination_flags
|
10
11
|
from ..services.research_service import (
|
@@ -17,12 +18,50 @@ from ..services.resource_service import (
|
|
17
18
|
delete_resource,
|
18
19
|
get_resources_for_research,
|
19
20
|
)
|
21
|
+
from ..services.settings_manager import SettingsManager
|
20
22
|
|
21
23
|
# Create blueprint
|
22
24
|
api_bp = Blueprint("api", __name__)
|
23
25
|
logger = logging.getLogger(__name__)
|
24
26
|
|
25
27
|
|
28
|
+
@api_bp.route("/settings/current-config", methods=["GET"])
|
29
|
+
def get_current_config():
|
30
|
+
"""Get the current configuration from database settings."""
|
31
|
+
try:
|
32
|
+
session = get_db_session()
|
33
|
+
settings_manager = SettingsManager(db_session=session)
|
34
|
+
|
35
|
+
config = {
|
36
|
+
"provider": settings_manager.get_setting(
|
37
|
+
"llm.provider", "Not configured"
|
38
|
+
),
|
39
|
+
"model": settings_manager.get_setting(
|
40
|
+
"llm.model", "Not configured"
|
41
|
+
),
|
42
|
+
"search_tool": settings_manager.get_setting(
|
43
|
+
"search.tool", "searxng"
|
44
|
+
),
|
45
|
+
"iterations": settings_manager.get_setting("search.iterations", 8),
|
46
|
+
"questions_per_iteration": settings_manager.get_setting(
|
47
|
+
"search.questions_per_iteration", 5
|
48
|
+
),
|
49
|
+
"search_strategy": settings_manager.get_setting(
|
50
|
+
"search.search_strategy", "focused_iteration"
|
51
|
+
),
|
52
|
+
}
|
53
|
+
|
54
|
+
session.close()
|
55
|
+
|
56
|
+
return jsonify({"success": True, "config": config})
|
57
|
+
|
58
|
+
except Exception:
|
59
|
+
logger.exception("Error getting current config")
|
60
|
+
return jsonify(
|
61
|
+
{"success": False, "error": "An internal error occurred"}
|
62
|
+
), 500
|
63
|
+
|
64
|
+
|
26
65
|
# API Routes
|
27
66
|
@api_bp.route("/start", methods=["POST"])
|
28
67
|
def api_start_research():
|
@@ -53,7 +92,7 @@ def api_start_research():
|
|
53
92
|
}
|
54
93
|
|
55
94
|
cursor.execute(
|
56
|
-
"INSERT INTO research_history (query, mode, status, created_at, progress_log,
|
95
|
+
"INSERT INTO research_history (query, mode, status, created_at, progress_log, research_meta) VALUES (?, ?, ?, ?, ?, ?)",
|
57
96
|
(
|
58
97
|
query,
|
59
98
|
mode,
|
@@ -100,39 +139,31 @@ def api_research_status(research_id):
|
|
100
139
|
Get the status of a research process
|
101
140
|
"""
|
102
141
|
try:
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
(
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
if result is None:
|
113
|
-
return jsonify({"error": "Research not found"}), 404
|
142
|
+
from ..database.models import ResearchHistory
|
143
|
+
|
144
|
+
db_session = get_db_session()
|
145
|
+
with db_session:
|
146
|
+
research = (
|
147
|
+
db_session.query(ResearchHistory)
|
148
|
+
.filter_by(id=research_id)
|
149
|
+
.first()
|
150
|
+
)
|
114
151
|
|
115
|
-
|
152
|
+
if research is None:
|
153
|
+
return jsonify({"error": "Research not found"}), 404
|
116
154
|
|
117
|
-
|
118
|
-
|
119
|
-
if metadata_str:
|
120
|
-
try:
|
121
|
-
metadata = json.loads(metadata_str)
|
122
|
-
except json.JSONDecodeError:
|
123
|
-
logger.warning(
|
124
|
-
f"Invalid JSON in metadata for research {research_id}"
|
125
|
-
)
|
155
|
+
# Get metadata
|
156
|
+
metadata = research.research_meta or {}
|
126
157
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
158
|
+
return jsonify(
|
159
|
+
{
|
160
|
+
"status": research.status,
|
161
|
+
"progress": research.progress,
|
162
|
+
"completed_at": research.completed_at,
|
163
|
+
"report_path": research.report_path,
|
164
|
+
"metadata": metadata,
|
165
|
+
}
|
166
|
+
)
|
136
167
|
except Exception as e:
|
137
168
|
logger.error(f"Error getting research status: {str(e)}")
|
138
169
|
return jsonify({"status": "error", "message": str(e)}), 500
|
@@ -4,6 +4,7 @@ import traceback
|
|
4
4
|
from pathlib import Path
|
5
5
|
|
6
6
|
from flask import Blueprint, jsonify, make_response
|
7
|
+
from ..utils.templates import render_template_with_defaults
|
7
8
|
|
8
9
|
from ..models.database import (
|
9
10
|
get_db_connection,
|
@@ -17,7 +18,7 @@ from ..services.research_service import get_research_strategy
|
|
17
18
|
logger = logging.getLogger(__name__)
|
18
19
|
|
19
20
|
# Create a Blueprint for the history routes
|
20
|
-
history_bp = Blueprint("history", __name__)
|
21
|
+
history_bp = Blueprint("history", __name__, url_prefix="/history")
|
21
22
|
|
22
23
|
|
23
24
|
def resolve_report_path(report_path: str) -> Path:
|
@@ -34,9 +35,15 @@ def resolve_report_path(report_path: str) -> Path:
|
|
34
35
|
return project_root / path
|
35
36
|
|
36
37
|
|
37
|
-
@history_bp.route("/
|
38
|
+
@history_bp.route("/")
|
39
|
+
def history_page():
|
40
|
+
"""Render the history page"""
|
41
|
+
return render_template_with_defaults("pages/history.html")
|
42
|
+
|
43
|
+
|
44
|
+
@history_bp.route("/api", methods=["GET"])
|
38
45
|
def get_history():
|
39
|
-
"""Get the research history"""
|
46
|
+
"""Get the research history JSON data"""
|
40
47
|
try:
|
41
48
|
conn = get_db_connection()
|
42
49
|
conn.row_factory = lambda cursor, row: {
|
@@ -73,8 +80,8 @@ def get_history():
|
|
73
80
|
item["duration_seconds"] = None
|
74
81
|
if "report_path" not in item:
|
75
82
|
item["report_path"] = None
|
76
|
-
if "
|
77
|
-
item["
|
83
|
+
if "research_meta" not in item:
|
84
|
+
item["research_meta"] = "{}"
|
78
85
|
if "progress_log" not in item:
|
79
86
|
item["progress_log"] = "[]"
|
80
87
|
|
@@ -287,7 +294,7 @@ def get_report(research_id):
|
|
287
294
|
}
|
288
295
|
|
289
296
|
# Also include any stored metadata
|
290
|
-
stored_metadata = json.loads(result.get("
|
297
|
+
stored_metadata = json.loads(result.get("research_meta", "{}"))
|
291
298
|
if stored_metadata and isinstance(stored_metadata, dict):
|
292
299
|
enhanced_metadata.update(stored_metadata)
|
293
300
|
|
@@ -11,7 +11,13 @@ from ...metrics.db_models import ResearchRating, TokenUsage
|
|
11
11
|
from ...metrics.query_utils import get_time_filter_condition
|
12
12
|
from ...metrics.search_tracker import get_search_tracker
|
13
13
|
from ...utilities.db_utils import get_db_session
|
14
|
-
from
|
14
|
+
from ...web_search_engines.rate_limiting import get_tracker
|
15
|
+
from ..database.models import (
|
16
|
+
Research,
|
17
|
+
ResearchStrategy,
|
18
|
+
RateLimitAttempt,
|
19
|
+
RateLimitEstimate,
|
20
|
+
)
|
15
21
|
from ..utils.templates import render_template_with_defaults
|
16
22
|
|
17
23
|
# Create a Blueprint for metrics
|
@@ -296,6 +302,161 @@ def get_strategy_analytics(period="30d"):
|
|
296
302
|
}
|
297
303
|
|
298
304
|
|
305
|
+
def get_rate_limiting_analytics(period="30d"):
|
306
|
+
"""Get rate limiting analytics for the specified period."""
|
307
|
+
try:
|
308
|
+
session = get_db_session()
|
309
|
+
|
310
|
+
# Calculate date range
|
311
|
+
days_map = {"7d": 7, "30d": 30, "90d": 90, "365d": 365, "all": None}
|
312
|
+
days = days_map.get(period, 30)
|
313
|
+
|
314
|
+
try:
|
315
|
+
# Get current rate limit estimates
|
316
|
+
estimates = session.query(RateLimitEstimate).all()
|
317
|
+
|
318
|
+
# Get recent attempts for analytics
|
319
|
+
attempts_query = session.query(RateLimitAttempt)
|
320
|
+
if days:
|
321
|
+
cutoff_timestamp = (
|
322
|
+
datetime.now() - timedelta(days=days)
|
323
|
+
).timestamp()
|
324
|
+
attempts_query = attempts_query.filter(
|
325
|
+
RateLimitAttempt.timestamp >= cutoff_timestamp
|
326
|
+
)
|
327
|
+
|
328
|
+
attempts = attempts_query.all()
|
329
|
+
|
330
|
+
# Calculate analytics
|
331
|
+
total_attempts = len(attempts)
|
332
|
+
successful_attempts = len([a for a in attempts if a.success])
|
333
|
+
failed_attempts = total_attempts - successful_attempts
|
334
|
+
|
335
|
+
# Engine-specific analytics
|
336
|
+
engine_stats = {}
|
337
|
+
for estimate in estimates:
|
338
|
+
engine_attempts = [
|
339
|
+
a for a in attempts if a.engine_type == estimate.engine_type
|
340
|
+
]
|
341
|
+
engine_success = len([a for a in engine_attempts if a.success])
|
342
|
+
engine_total = len(engine_attempts)
|
343
|
+
|
344
|
+
engine_stats[estimate.engine_type] = {
|
345
|
+
"base_wait_seconds": round(estimate.base_wait_seconds, 2),
|
346
|
+
"min_wait_seconds": round(estimate.min_wait_seconds, 2),
|
347
|
+
"max_wait_seconds": round(estimate.max_wait_seconds, 2),
|
348
|
+
"success_rate": round(estimate.success_rate * 100, 1),
|
349
|
+
"total_attempts": estimate.total_attempts,
|
350
|
+
"recent_attempts": engine_total,
|
351
|
+
"recent_success_rate": round(
|
352
|
+
(engine_success / engine_total * 100)
|
353
|
+
if engine_total > 0
|
354
|
+
else 0,
|
355
|
+
1,
|
356
|
+
),
|
357
|
+
"last_updated": datetime.fromtimestamp(
|
358
|
+
estimate.last_updated
|
359
|
+
).strftime("%Y-%m-%d %H:%M:%S"),
|
360
|
+
"status": "healthy"
|
361
|
+
if estimate.success_rate > 0.8
|
362
|
+
else "degraded"
|
363
|
+
if estimate.success_rate > 0.5
|
364
|
+
else "poor",
|
365
|
+
}
|
366
|
+
|
367
|
+
# Overall success rate trend
|
368
|
+
success_rate = round(
|
369
|
+
(successful_attempts / total_attempts * 100)
|
370
|
+
if total_attempts > 0
|
371
|
+
else 0,
|
372
|
+
1,
|
373
|
+
)
|
374
|
+
|
375
|
+
# Rate limiting events (failures)
|
376
|
+
rate_limit_events = len(
|
377
|
+
[
|
378
|
+
a
|
379
|
+
for a in attempts
|
380
|
+
if not a.success and a.error_type == "RateLimitError"
|
381
|
+
]
|
382
|
+
)
|
383
|
+
|
384
|
+
# Calculate average wait times
|
385
|
+
if attempts:
|
386
|
+
avg_wait_time = sum(a.wait_time for a in attempts) / len(
|
387
|
+
attempts
|
388
|
+
)
|
389
|
+
successful_wait_times = [
|
390
|
+
a.wait_time for a in attempts if a.success
|
391
|
+
]
|
392
|
+
avg_successful_wait = (
|
393
|
+
sum(successful_wait_times) / len(successful_wait_times)
|
394
|
+
if successful_wait_times
|
395
|
+
else 0
|
396
|
+
)
|
397
|
+
else:
|
398
|
+
avg_wait_time = 0
|
399
|
+
avg_successful_wait = 0
|
400
|
+
|
401
|
+
return {
|
402
|
+
"rate_limiting_analytics": {
|
403
|
+
"total_attempts": total_attempts,
|
404
|
+
"successful_attempts": successful_attempts,
|
405
|
+
"failed_attempts": failed_attempts,
|
406
|
+
"success_rate": success_rate,
|
407
|
+
"rate_limit_events": rate_limit_events,
|
408
|
+
"avg_wait_time": round(avg_wait_time, 2),
|
409
|
+
"avg_successful_wait": round(avg_successful_wait, 2),
|
410
|
+
"engine_stats": engine_stats,
|
411
|
+
"total_engines_tracked": len(estimates),
|
412
|
+
"healthy_engines": len(
|
413
|
+
[
|
414
|
+
s
|
415
|
+
for s in engine_stats.values()
|
416
|
+
if s["status"] == "healthy"
|
417
|
+
]
|
418
|
+
),
|
419
|
+
"degraded_engines": len(
|
420
|
+
[
|
421
|
+
s
|
422
|
+
for s in engine_stats.values()
|
423
|
+
if s["status"] == "degraded"
|
424
|
+
]
|
425
|
+
),
|
426
|
+
"poor_engines": len(
|
427
|
+
[
|
428
|
+
s
|
429
|
+
for s in engine_stats.values()
|
430
|
+
if s["status"] == "poor"
|
431
|
+
]
|
432
|
+
),
|
433
|
+
}
|
434
|
+
}
|
435
|
+
|
436
|
+
finally:
|
437
|
+
session.close()
|
438
|
+
|
439
|
+
except Exception as e:
|
440
|
+
logger.exception(f"Error getting rate limiting analytics: {e}")
|
441
|
+
return {
|
442
|
+
"rate_limiting_analytics": {
|
443
|
+
"total_attempts": 0,
|
444
|
+
"successful_attempts": 0,
|
445
|
+
"failed_attempts": 0,
|
446
|
+
"success_rate": 0,
|
447
|
+
"rate_limit_events": 0,
|
448
|
+
"avg_wait_time": 0,
|
449
|
+
"avg_successful_wait": 0,
|
450
|
+
"engine_stats": {},
|
451
|
+
"total_engines_tracked": 0,
|
452
|
+
"healthy_engines": 0,
|
453
|
+
"degraded_engines": 0,
|
454
|
+
"poor_engines": 0,
|
455
|
+
"error": "An internal error occurred while processing the request.",
|
456
|
+
}
|
457
|
+
}
|
458
|
+
|
459
|
+
|
299
460
|
@metrics_bp.route("/")
|
300
461
|
def metrics_dashboard():
|
301
462
|
"""Render the metrics dashboard page."""
|
@@ -355,11 +516,15 @@ def api_metrics():
|
|
355
516
|
# Get strategy analytics
|
356
517
|
strategy_data = get_strategy_analytics(period)
|
357
518
|
|
519
|
+
# Get rate limiting analytics
|
520
|
+
rate_limiting_data = get_rate_limiting_analytics(period)
|
521
|
+
|
358
522
|
# Combine metrics
|
359
523
|
combined_metrics = {
|
360
524
|
**token_metrics,
|
361
525
|
**search_metrics,
|
362
526
|
**strategy_data,
|
527
|
+
**rate_limiting_data,
|
363
528
|
"user_satisfaction": user_satisfaction,
|
364
529
|
}
|
365
530
|
|
@@ -384,6 +549,80 @@ def api_metrics():
|
|
384
549
|
)
|
385
550
|
|
386
551
|
|
552
|
+
@metrics_bp.route("/api/rate-limiting")
|
553
|
+
def api_rate_limiting_metrics():
|
554
|
+
"""Get detailed rate limiting metrics."""
|
555
|
+
try:
|
556
|
+
period = request.args.get("period", "30d")
|
557
|
+
rate_limiting_data = get_rate_limiting_analytics(period)
|
558
|
+
|
559
|
+
return jsonify(
|
560
|
+
{"status": "success", "data": rate_limiting_data, "period": period}
|
561
|
+
)
|
562
|
+
except Exception as e:
|
563
|
+
logger.exception(f"Error getting rate limiting metrics: {e}")
|
564
|
+
return jsonify(
|
565
|
+
{
|
566
|
+
"status": "error",
|
567
|
+
"message": "Failed to retrieve rate limiting metrics",
|
568
|
+
}
|
569
|
+
), 500
|
570
|
+
|
571
|
+
|
572
|
+
@metrics_bp.route("/api/rate-limiting/current")
|
573
|
+
def api_current_rate_limits():
|
574
|
+
"""Get current rate limit estimates for all engines."""
|
575
|
+
try:
|
576
|
+
tracker = get_tracker()
|
577
|
+
stats = tracker.get_stats()
|
578
|
+
|
579
|
+
current_limits = []
|
580
|
+
for stat in stats:
|
581
|
+
(
|
582
|
+
engine_type,
|
583
|
+
base_wait,
|
584
|
+
min_wait,
|
585
|
+
max_wait,
|
586
|
+
last_updated,
|
587
|
+
total_attempts,
|
588
|
+
success_rate,
|
589
|
+
) = stat
|
590
|
+
current_limits.append(
|
591
|
+
{
|
592
|
+
"engine_type": engine_type,
|
593
|
+
"base_wait_seconds": round(base_wait, 2),
|
594
|
+
"min_wait_seconds": round(min_wait, 2),
|
595
|
+
"max_wait_seconds": round(max_wait, 2),
|
596
|
+
"success_rate": round(success_rate * 100, 1),
|
597
|
+
"total_attempts": total_attempts,
|
598
|
+
"last_updated": datetime.fromtimestamp(
|
599
|
+
last_updated
|
600
|
+
).strftime("%Y-%m-%d %H:%M:%S"),
|
601
|
+
"status": "healthy"
|
602
|
+
if success_rate > 0.8
|
603
|
+
else "degraded"
|
604
|
+
if success_rate > 0.5
|
605
|
+
else "poor",
|
606
|
+
}
|
607
|
+
)
|
608
|
+
|
609
|
+
return jsonify(
|
610
|
+
{
|
611
|
+
"status": "success",
|
612
|
+
"current_limits": current_limits,
|
613
|
+
"timestamp": datetime.now().isoformat(),
|
614
|
+
}
|
615
|
+
)
|
616
|
+
except Exception as e:
|
617
|
+
logger.exception(f"Error getting current rate limits: {e}")
|
618
|
+
return jsonify(
|
619
|
+
{
|
620
|
+
"status": "error",
|
621
|
+
"message": "Failed to retrieve current rate limits",
|
622
|
+
}
|
623
|
+
), 500
|
624
|
+
|
625
|
+
|
387
626
|
@metrics_bp.route("/api/metrics/research/<int:research_id>")
|
388
627
|
def api_research_metrics(research_id):
|
389
628
|
"""Get metrics for a specific research."""
|
@@ -1030,18 +1269,39 @@ def api_cost_analytics():
|
|
1030
1269
|
if time_condition is not None:
|
1031
1270
|
query = query.filter(time_condition)
|
1032
1271
|
|
1033
|
-
|
1272
|
+
# First check if we have any records to avoid expensive queries
|
1273
|
+
record_count = query.count()
|
1034
1274
|
|
1035
|
-
if
|
1275
|
+
if record_count == 0:
|
1036
1276
|
return jsonify(
|
1037
1277
|
{
|
1038
1278
|
"status": "success",
|
1039
1279
|
"period": period,
|
1040
|
-
"
|
1280
|
+
"overview": {
|
1281
|
+
"total_cost": 0.0,
|
1282
|
+
"total_tokens": 0,
|
1283
|
+
"prompt_tokens": 0,
|
1284
|
+
"completion_tokens": 0,
|
1285
|
+
},
|
1286
|
+
"top_expensive_research": [],
|
1287
|
+
"research_count": 0,
|
1041
1288
|
"message": "No token usage data found for this period",
|
1042
1289
|
}
|
1043
1290
|
)
|
1044
1291
|
|
1292
|
+
# If we have too many records, limit to recent ones to avoid timeout
|
1293
|
+
if record_count > 1000:
|
1294
|
+
logger.warning(
|
1295
|
+
f"Large dataset detected ({record_count} records), limiting to recent 1000 for performance"
|
1296
|
+
)
|
1297
|
+
usage_records = (
|
1298
|
+
query.order_by(TokenUsage.timestamp.desc())
|
1299
|
+
.limit(1000)
|
1300
|
+
.all()
|
1301
|
+
)
|
1302
|
+
else:
|
1303
|
+
usage_records = query.all()
|
1304
|
+
|
1045
1305
|
# Convert to dict format
|
1046
1306
|
usage_data = []
|
1047
1307
|
for record in usage_records:
|
@@ -29,18 +29,12 @@ from ..database.models import ResearchHistory, ResearchLog
|
|
29
29
|
from ...utilities.db_utils import get_db_session
|
30
30
|
|
31
31
|
# Create a Blueprint for the research application
|
32
|
-
research_bp = Blueprint("research", __name__
|
32
|
+
research_bp = Blueprint("research", __name__)
|
33
33
|
|
34
34
|
# Output directory for research results
|
35
35
|
OUTPUT_DIR = "research_outputs"
|
36
36
|
|
37
37
|
|
38
|
-
# Route for index page - redirection
|
39
|
-
@research_bp.route("/")
|
40
|
-
def index():
|
41
|
-
return render_template_with_defaults("pages/research.html")
|
42
|
-
|
43
|
-
|
44
38
|
# Add the missing static file serving route
|
45
39
|
@research_bp.route("/static/<path:path>")
|
46
40
|
def serve_static(path):
|
@@ -325,7 +319,7 @@ def terminate_research(research_id):
|
|
325
319
|
active_research[research_id]["log"].append(log_entry)
|
326
320
|
|
327
321
|
# Add to database log
|
328
|
-
logger.log("
|
322
|
+
logger.log("MILESTONE", f"Research ended: {termination_message}")
|
329
323
|
|
330
324
|
# Update the log in the database (old way for backward compatibility)
|
331
325
|
cursor.execute(
|
@@ -749,7 +743,7 @@ def get_research_status(research_id):
|
|
749
743
|
conn = get_db_connection()
|
750
744
|
cursor = conn.cursor()
|
751
745
|
cursor.execute(
|
752
|
-
"SELECT status, progress, completed_at, report_path,
|
746
|
+
"SELECT status, progress, completed_at, report_path, research_meta FROM research_history WHERE id = ?",
|
753
747
|
(research_id,),
|
754
748
|
)
|
755
749
|
result = cursor.fetchone()
|
@@ -837,13 +831,46 @@ def get_research_status(research_id):
|
|
837
831
|
if error_info:
|
838
832
|
metadata["error_info"] = error_info
|
839
833
|
|
834
|
+
# Get the latest milestone log for this research
|
835
|
+
latest_milestone = None
|
836
|
+
try:
|
837
|
+
db_session = get_db_session()
|
838
|
+
with db_session:
|
839
|
+
milestone_log = (
|
840
|
+
db_session.query(ResearchLog)
|
841
|
+
.filter_by(research_id=research_id, level="MILESTONE")
|
842
|
+
.order_by(ResearchLog.timestamp.desc())
|
843
|
+
.first()
|
844
|
+
)
|
845
|
+
if milestone_log:
|
846
|
+
latest_milestone = {
|
847
|
+
"message": milestone_log.message,
|
848
|
+
"time": milestone_log.timestamp.isoformat()
|
849
|
+
if milestone_log.timestamp
|
850
|
+
else None,
|
851
|
+
"type": "MILESTONE",
|
852
|
+
}
|
853
|
+
logger.debug(
|
854
|
+
f"Found latest milestone for research {research_id}: {milestone_log.message}"
|
855
|
+
)
|
856
|
+
else:
|
857
|
+
logger.debug(
|
858
|
+
f"No milestone logs found for research {research_id}"
|
859
|
+
)
|
860
|
+
except Exception as e:
|
861
|
+
logger.warning(f"Error fetching latest milestone: {str(e)}")
|
862
|
+
|
840
863
|
conn.close()
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
864
|
+
response_data = {
|
865
|
+
"status": status,
|
866
|
+
"progress": progress,
|
867
|
+
"completed_at": completed_at,
|
868
|
+
"report_path": report_path,
|
869
|
+
"metadata": metadata,
|
870
|
+
}
|
871
|
+
|
872
|
+
# Include latest milestone as a log_entry for frontend compatibility
|
873
|
+
if latest_milestone:
|
874
|
+
response_data["log_entry"] = latest_milestone
|
875
|
+
|
876
|
+
return jsonify(response_data)
|