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
@@ -49,6 +49,12 @@ def normalize_url(raw_url: str) -> str:
|
|
49
49
|
# At this point, we should have hostname:port or just hostname
|
50
50
|
# Determine if this is localhost or an external host
|
51
51
|
hostname = raw_url.split(":")[0].split("/")[0]
|
52
|
+
|
53
|
+
# Handle IPv6 addresses in brackets
|
54
|
+
if hostname.startswith("[") and "]" in raw_url:
|
55
|
+
# Extract the IPv6 address including brackets
|
56
|
+
hostname = raw_url.split("]")[0] + "]"
|
57
|
+
|
52
58
|
is_localhost = hostname in ("localhost", "127.0.0.1", "[::1]", "0.0.0.0")
|
53
59
|
|
54
60
|
# Use http for localhost, https for external hosts
|
@@ -0,0 +1,347 @@
|
|
1
|
+
"""
|
2
|
+
REST API for Local Deep Research.
|
3
|
+
Provides HTTP access to programmatic search and research capabilities.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import logging
|
7
|
+
import time
|
8
|
+
from functools import wraps
|
9
|
+
|
10
|
+
from flask import Blueprint, jsonify, request
|
11
|
+
|
12
|
+
from ..api.research_functions import analyze_documents
|
13
|
+
from .services.settings_service import get_setting
|
14
|
+
|
15
|
+
# Create a blueprint for the API
|
16
|
+
api_blueprint = Blueprint("api_v1", __name__, url_prefix="/api/v1")
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
# Rate limiting data store: {ip_address: [timestamp1, timestamp2, ...]}
|
20
|
+
rate_limit_data = {}
|
21
|
+
|
22
|
+
|
23
|
+
def api_access_control(f):
|
24
|
+
"""
|
25
|
+
Decorator to enforce API access control:
|
26
|
+
- Check if API is enabled
|
27
|
+
- Enforce rate limiting
|
28
|
+
"""
|
29
|
+
|
30
|
+
@wraps(f)
|
31
|
+
def decorated_function(*args, **kwargs):
|
32
|
+
# Check if API is enabled
|
33
|
+
api_enabled = get_setting("app.enable_api", True) # Default to enabled
|
34
|
+
if not api_enabled:
|
35
|
+
return jsonify({"error": "API access is disabled"}), 403
|
36
|
+
|
37
|
+
# Implement rate limiting
|
38
|
+
rate_limit = get_setting(
|
39
|
+
"app.api_rate_limit", 60
|
40
|
+
) # Default 60 requests per minute
|
41
|
+
if rate_limit:
|
42
|
+
client_ip = request.remote_addr
|
43
|
+
current_time = time.time()
|
44
|
+
|
45
|
+
# Initialize or clean up old requests for this IP
|
46
|
+
if client_ip not in rate_limit_data:
|
47
|
+
rate_limit_data[client_ip] = []
|
48
|
+
|
49
|
+
# Remove timestamps older than 1 minute
|
50
|
+
rate_limit_data[client_ip] = [
|
51
|
+
ts
|
52
|
+
for ts in rate_limit_data[client_ip]
|
53
|
+
if current_time - ts < 60
|
54
|
+
]
|
55
|
+
|
56
|
+
# Check if rate limit is exceeded
|
57
|
+
if len(rate_limit_data[client_ip]) >= rate_limit:
|
58
|
+
return (
|
59
|
+
jsonify(
|
60
|
+
{
|
61
|
+
"error": f"Rate limit exceeded. Maximum {rate_limit} requests per minute allowed."
|
62
|
+
}
|
63
|
+
),
|
64
|
+
429,
|
65
|
+
)
|
66
|
+
|
67
|
+
# Add current timestamp to the list
|
68
|
+
rate_limit_data[client_ip].append(current_time)
|
69
|
+
|
70
|
+
return f(*args, **kwargs)
|
71
|
+
|
72
|
+
return decorated_function
|
73
|
+
|
74
|
+
|
75
|
+
@api_blueprint.route("/", methods=["GET"])
|
76
|
+
@api_access_control
|
77
|
+
def api_documentation():
|
78
|
+
"""
|
79
|
+
Provide documentation on the available API endpoints.
|
80
|
+
"""
|
81
|
+
api_docs = {
|
82
|
+
"api_version": "v1",
|
83
|
+
"description": "REST API for Local Deep Research",
|
84
|
+
"endpoints": [
|
85
|
+
{
|
86
|
+
"path": "/api/v1/quick_summary",
|
87
|
+
"method": "POST",
|
88
|
+
"description": "Generate a quick research summary",
|
89
|
+
"parameters": {
|
90
|
+
"query": "Research query (required)",
|
91
|
+
"search_tool": "Search engine to use (optional)",
|
92
|
+
"iterations": "Number of search iterations (optional)",
|
93
|
+
"temperature": "LLM temperature (optional)",
|
94
|
+
},
|
95
|
+
},
|
96
|
+
{
|
97
|
+
"path": "/api/v1/generate_report",
|
98
|
+
"method": "POST",
|
99
|
+
"description": "Generate a comprehensive research report",
|
100
|
+
"parameters": {
|
101
|
+
"query": "Research query (required)",
|
102
|
+
"output_file": "Path to save report (optional)",
|
103
|
+
"searches_per_section": "Searches per report section (optional)",
|
104
|
+
"model_name": "LLM model to use (optional)",
|
105
|
+
"temperature": "LLM temperature (optional)",
|
106
|
+
},
|
107
|
+
},
|
108
|
+
{
|
109
|
+
"path": "/api/v1/analyze_documents",
|
110
|
+
"method": "POST",
|
111
|
+
"description": "Search and analyze documents in a local collection",
|
112
|
+
"parameters": {
|
113
|
+
"query": "Search query (required)",
|
114
|
+
"collection_name": "Local collection name (required)",
|
115
|
+
"max_results": "Maximum results to return (optional)",
|
116
|
+
"temperature": "LLM temperature (optional)",
|
117
|
+
"force_reindex": "Force collection reindexing (optional)",
|
118
|
+
},
|
119
|
+
},
|
120
|
+
],
|
121
|
+
}
|
122
|
+
|
123
|
+
return jsonify(api_docs)
|
124
|
+
|
125
|
+
|
126
|
+
@api_blueprint.route("/health", methods=["GET"])
|
127
|
+
def health_check():
|
128
|
+
"""Simple health check endpoint."""
|
129
|
+
return jsonify(
|
130
|
+
{"status": "ok", "message": "API is running", "timestamp": time.time()}
|
131
|
+
)
|
132
|
+
|
133
|
+
|
134
|
+
@api_blueprint.route("/quick_summary_test", methods=["POST"])
|
135
|
+
@api_access_control
|
136
|
+
def api_quick_summary_test():
|
137
|
+
"""Test endpoint using programmatic access with minimal parameters for fast testing."""
|
138
|
+
data = request.json
|
139
|
+
if not data or "query" not in data:
|
140
|
+
return jsonify({"error": "Query parameter is required"}), 400
|
141
|
+
|
142
|
+
query = data.get("query")
|
143
|
+
|
144
|
+
try:
|
145
|
+
# Import here to avoid circular imports
|
146
|
+
from ..api.research_functions import quick_summary
|
147
|
+
|
148
|
+
logger.info(f"Processing quick_summary_test request: query='{query}'")
|
149
|
+
|
150
|
+
# Use minimal parameters for faster testing
|
151
|
+
result = quick_summary(
|
152
|
+
query=query,
|
153
|
+
search_tool="wikipedia", # Use fast Wikipedia search for testing
|
154
|
+
iterations=1, # Single iteration for speed
|
155
|
+
temperature=0.7,
|
156
|
+
)
|
157
|
+
|
158
|
+
return jsonify(result)
|
159
|
+
except Exception as e:
|
160
|
+
logger.error(
|
161
|
+
f"Error in quick_summary_test API: {str(e)}", exc_info=True
|
162
|
+
)
|
163
|
+
return (
|
164
|
+
jsonify(
|
165
|
+
{
|
166
|
+
"error": "An internal error has occurred. Please try again later."
|
167
|
+
}
|
168
|
+
),
|
169
|
+
500,
|
170
|
+
)
|
171
|
+
|
172
|
+
|
173
|
+
@api_blueprint.route("/quick_summary", methods=["POST"])
|
174
|
+
@api_access_control
|
175
|
+
def api_quick_summary():
|
176
|
+
"""
|
177
|
+
Generate a quick research summary via REST API.
|
178
|
+
|
179
|
+
POST /api/v1/quick_summary
|
180
|
+
{
|
181
|
+
"query": "Advances in fusion energy research",
|
182
|
+
"search_tool": "auto", # Optional: search engine to use
|
183
|
+
"iterations": 2, # Optional: number of search iterations
|
184
|
+
"temperature": 0.7 # Optional: LLM temperature
|
185
|
+
}
|
186
|
+
"""
|
187
|
+
data = request.json
|
188
|
+
if not data or "query" not in data:
|
189
|
+
return jsonify({"error": "Query parameter is required"}), 400
|
190
|
+
|
191
|
+
# Extract query and optional parameters
|
192
|
+
query = data.get("query")
|
193
|
+
params = {k: v for k, v in data.items() if k != "query"}
|
194
|
+
|
195
|
+
try:
|
196
|
+
# Import here to avoid circular imports
|
197
|
+
from ..api.research_functions import quick_summary
|
198
|
+
|
199
|
+
logger.info(f"Processing quick_summary request: query='{query}'")
|
200
|
+
|
201
|
+
# Set reasonable defaults for API use
|
202
|
+
params.setdefault("temperature", 0.7)
|
203
|
+
params.setdefault("search_tool", "auto")
|
204
|
+
params.setdefault("iterations", 1)
|
205
|
+
|
206
|
+
# Call the actual research function
|
207
|
+
result = quick_summary(query, **params)
|
208
|
+
|
209
|
+
return jsonify(result)
|
210
|
+
except TimeoutError:
|
211
|
+
logger.error("Request timed out")
|
212
|
+
return (
|
213
|
+
jsonify(
|
214
|
+
{
|
215
|
+
"error": "Request timed out. Please try with a simpler query or fewer iterations."
|
216
|
+
}
|
217
|
+
),
|
218
|
+
504,
|
219
|
+
)
|
220
|
+
except Exception as e:
|
221
|
+
logger.error(f"Error in quick_summary API: {str(e)}", exc_info=True)
|
222
|
+
return (
|
223
|
+
jsonify(
|
224
|
+
{
|
225
|
+
"error": "An internal error has occurred. Please try again later."
|
226
|
+
}
|
227
|
+
),
|
228
|
+
500,
|
229
|
+
)
|
230
|
+
|
231
|
+
|
232
|
+
@api_blueprint.route("/generate_report", methods=["POST"])
|
233
|
+
@api_access_control
|
234
|
+
def api_generate_report():
|
235
|
+
"""
|
236
|
+
Generate a comprehensive research report via REST API.
|
237
|
+
|
238
|
+
POST /api/v1/generate_report
|
239
|
+
{
|
240
|
+
"query": "Impact of climate change on agriculture",
|
241
|
+
"output_file": "/path/to/save/report.md", # Optional
|
242
|
+
"searches_per_section": 2, # Optional
|
243
|
+
"model_name": "gpt-4", # Optional
|
244
|
+
"temperature": 0.5 # Optional
|
245
|
+
}
|
246
|
+
"""
|
247
|
+
data = request.json
|
248
|
+
if not data or "query" not in data:
|
249
|
+
return jsonify({"error": "Query parameter is required"}), 400
|
250
|
+
|
251
|
+
query = data.get("query")
|
252
|
+
params = {k: v for k, v in data.items() if k != "query"}
|
253
|
+
|
254
|
+
try:
|
255
|
+
# Import here to avoid circular imports
|
256
|
+
from ..api.research_functions import generate_report
|
257
|
+
|
258
|
+
# Set reasonable defaults for API use
|
259
|
+
params.setdefault("searches_per_section", 1)
|
260
|
+
params.setdefault("temperature", 0.7)
|
261
|
+
|
262
|
+
logger.info(
|
263
|
+
f"Processing generate_report request: query='{query}', params={params}"
|
264
|
+
)
|
265
|
+
|
266
|
+
result = generate_report(query, **params)
|
267
|
+
|
268
|
+
# Don't return the full content for large reports
|
269
|
+
if (
|
270
|
+
result
|
271
|
+
and "content" in result
|
272
|
+
and isinstance(result["content"], str)
|
273
|
+
and len(result["content"]) > 10000
|
274
|
+
):
|
275
|
+
# Include a summary of the report content
|
276
|
+
content_preview = (
|
277
|
+
result["content"][:2000] + "... [Content truncated]"
|
278
|
+
)
|
279
|
+
result["content"] = content_preview
|
280
|
+
result["content_truncated"] = True
|
281
|
+
|
282
|
+
return jsonify(result)
|
283
|
+
except TimeoutError:
|
284
|
+
logger.error("Request timed out")
|
285
|
+
return (
|
286
|
+
jsonify(
|
287
|
+
{"error": "Request timed out. Please try with a simpler query."}
|
288
|
+
),
|
289
|
+
504,
|
290
|
+
)
|
291
|
+
except Exception as e:
|
292
|
+
logger.error(f"Error in generate_report API: {str(e)}", exc_info=True)
|
293
|
+
return (
|
294
|
+
jsonify(
|
295
|
+
{
|
296
|
+
"error": "An internal error has occurred. Please try again later."
|
297
|
+
}
|
298
|
+
),
|
299
|
+
500,
|
300
|
+
)
|
301
|
+
|
302
|
+
|
303
|
+
@api_blueprint.route("/analyze_documents", methods=["POST"])
|
304
|
+
@api_access_control
|
305
|
+
def api_analyze_documents():
|
306
|
+
"""
|
307
|
+
Search and analyze documents in a local collection via REST API.
|
308
|
+
|
309
|
+
POST /api/v1/analyze_documents
|
310
|
+
{
|
311
|
+
"query": "neural networks in medicine",
|
312
|
+
"collection_name": "research_papers", # Required: local collection name
|
313
|
+
"max_results": 20, # Optional: max results to return
|
314
|
+
"temperature": 0.7, # Optional: LLM temperature
|
315
|
+
"force_reindex": false # Optional: force reindexing
|
316
|
+
}
|
317
|
+
"""
|
318
|
+
data = request.json
|
319
|
+
if not data or "query" not in data or "collection_name" not in data:
|
320
|
+
return (
|
321
|
+
jsonify(
|
322
|
+
{
|
323
|
+
"error": "Both query and collection_name parameters are required"
|
324
|
+
}
|
325
|
+
),
|
326
|
+
400,
|
327
|
+
)
|
328
|
+
|
329
|
+
query = data.get("query")
|
330
|
+
collection_name = data.get("collection_name")
|
331
|
+
params = {
|
332
|
+
k: v for k, v in data.items() if k not in ["query", "collection_name"]
|
333
|
+
}
|
334
|
+
|
335
|
+
try:
|
336
|
+
result = analyze_documents(query, collection_name, **params)
|
337
|
+
return jsonify(result)
|
338
|
+
except Exception as e:
|
339
|
+
logger.error(f"Error in analyze_documents API: {str(e)}", exc_info=True)
|
340
|
+
return (
|
341
|
+
jsonify(
|
342
|
+
{
|
343
|
+
"error": "An internal error has occurred. Please try again later."
|
344
|
+
}
|
345
|
+
),
|
346
|
+
500,
|
347
|
+
)
|
local_deep_research/web/app.py
CHANGED
@@ -5,6 +5,7 @@ from loguru import logger
|
|
5
5
|
|
6
6
|
from ..setup_data_dir import setup_data_dir
|
7
7
|
from ..utilities.db_utils import get_db_setting
|
8
|
+
from ..utilities.log_utils import config_logger
|
8
9
|
from .app_factory import create_app
|
9
10
|
from .models.database import (
|
10
11
|
DB_PATH,
|
@@ -15,6 +16,9 @@ from .models.database import (
|
|
15
16
|
# Ensure data directory exists
|
16
17
|
setup_data_dir()
|
17
18
|
|
19
|
+
# Configure logging with milestone level
|
20
|
+
config_logger("ldr_web")
|
21
|
+
|
18
22
|
# Run schema upgrades if database exists
|
19
23
|
if os.path.exists(DB_PATH):
|
20
24
|
try:
|
@@ -32,9 +36,9 @@ def check_migration_needed():
|
|
32
36
|
"""Check if database migration is needed, based on presence of legacy files and absence of new DB"""
|
33
37
|
if not os.path.exists(DB_PATH):
|
34
38
|
# The new database doesn't exist, check if legacy databases exist
|
35
|
-
legacy_files_exist = os.path.exists(
|
36
|
-
|
37
|
-
)
|
39
|
+
legacy_files_exist = os.path.exists(
|
40
|
+
LEGACY_DEEP_RESEARCH_DB
|
41
|
+
) or os.path.exists(LEGACY_RESEARCH_HISTORY_DB)
|
38
42
|
|
39
43
|
if legacy_files_exist:
|
40
44
|
logger.info(
|
@@ -45,16 +49,15 @@ def check_migration_needed():
|
|
45
49
|
return False
|
46
50
|
|
47
51
|
|
48
|
-
|
49
|
-
app, socketio = create_app()
|
50
|
-
|
51
|
-
|
52
|
-
@logger.catch()
|
52
|
+
@logger.catch
|
53
53
|
def main():
|
54
54
|
"""
|
55
55
|
Entry point for the web application when run as a command.
|
56
56
|
This function is needed for the package's entry point to work properly.
|
57
57
|
"""
|
58
|
+
# Create the Flask app and SocketIO instance
|
59
|
+
app, socketio = create_app()
|
60
|
+
|
58
61
|
# Check if migration is needed
|
59
62
|
if check_migration_needed():
|
60
63
|
logger.info(
|
@@ -93,15 +96,8 @@ def main():
|
|
93
96
|
host = get_db_setting("web.host", "0.0.0.0")
|
94
97
|
debug = get_db_setting("web.debug", True)
|
95
98
|
|
96
|
-
|
97
|
-
|
98
|
-
app,
|
99
|
-
debug=debug,
|
100
|
-
host=host,
|
101
|
-
port=port,
|
102
|
-
allow_unsafe_werkzeug=True,
|
103
|
-
use_reloader=False,
|
104
|
-
)
|
99
|
+
with app.app_context():
|
100
|
+
socketio.run(host, port, debug=debug)
|
105
101
|
|
106
102
|
|
107
103
|
if __name__ == "__main__":
|
@@ -6,17 +6,15 @@ from flask import (
|
|
6
6
|
Flask,
|
7
7
|
jsonify,
|
8
8
|
make_response,
|
9
|
-
redirect,
|
10
9
|
request,
|
11
10
|
send_from_directory,
|
12
|
-
url_for,
|
13
11
|
)
|
14
|
-
from flask_socketio import SocketIO
|
15
12
|
from flask_wtf.csrf import CSRFProtect
|
16
13
|
from loguru import logger
|
17
14
|
|
18
15
|
from ..utilities.log_utils import InterceptHandler
|
19
16
|
from .models.database import DB_PATH, init_db
|
17
|
+
from .services.socket_service import SocketIOService
|
20
18
|
|
21
19
|
|
22
20
|
def create_app():
|
@@ -38,7 +36,9 @@ def create_app():
|
|
38
36
|
TEMPLATE_DIR = (package_dir / "templates").as_posix()
|
39
37
|
|
40
38
|
# Initialize Flask app with package directories
|
41
|
-
app = Flask(
|
39
|
+
app = Flask(
|
40
|
+
__name__, static_folder=STATIC_DIR, template_folder=TEMPLATE_DIR
|
41
|
+
)
|
42
42
|
logger.debug(f"Using package static path: {STATIC_DIR}")
|
43
43
|
logger.debug(f"Using package template path: {TEMPLATE_DIR}")
|
44
44
|
except Exception:
|
@@ -58,6 +58,14 @@ def create_app():
|
|
58
58
|
# Exempt Socket.IO from CSRF protection
|
59
59
|
csrf.exempt("research.socket_io")
|
60
60
|
|
61
|
+
# Disable CSRF for API routes
|
62
|
+
@app.before_request
|
63
|
+
def disable_csrf_for_api():
|
64
|
+
if request.path.startswith("/api/v1/") or request.path.startswith(
|
65
|
+
"/research/api/"
|
66
|
+
):
|
67
|
+
csrf.protect = lambda: None
|
68
|
+
|
61
69
|
# Database configuration - Use unified ldr.db from the database module
|
62
70
|
db_path = DB_PATH
|
63
71
|
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}"
|
@@ -65,26 +73,12 @@ def create_app():
|
|
65
73
|
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
66
74
|
app.config["SQLALCHEMY_ECHO"] = False
|
67
75
|
|
68
|
-
# Initialize extensions
|
69
|
-
socketio = SocketIO(
|
70
|
-
app,
|
71
|
-
cors_allowed_origins="*",
|
72
|
-
async_mode="threading",
|
73
|
-
path="/research/socket.io",
|
74
|
-
logger=False,
|
75
|
-
engineio_logger=False,
|
76
|
-
ping_timeout=20,
|
77
|
-
ping_interval=5,
|
78
|
-
)
|
79
|
-
|
80
76
|
# Initialize the database
|
81
77
|
create_database(app)
|
82
78
|
init_db()
|
83
79
|
|
84
80
|
# Register socket service
|
85
|
-
|
86
|
-
|
87
|
-
set_socketio(socketio)
|
81
|
+
socket_service = SocketIOService(app=app)
|
88
82
|
|
89
83
|
# Apply middleware
|
90
84
|
apply_middleware(app)
|
@@ -95,10 +89,7 @@ def create_app():
|
|
95
89
|
# Register error handlers
|
96
90
|
register_error_handlers(app)
|
97
91
|
|
98
|
-
|
99
|
-
register_socket_events(socketio)
|
100
|
-
|
101
|
-
return app, socketio
|
92
|
+
return app, socket_service
|
102
93
|
|
103
94
|
|
104
95
|
def apply_middleware(app):
|
@@ -123,12 +114,18 @@ def apply_middleware(app):
|
|
123
114
|
response.headers["X-Content-Security-Policy"] = csp
|
124
115
|
|
125
116
|
# Add CORS headers for API requests
|
126
|
-
if request.path.startswith("/api/")
|
117
|
+
if request.path.startswith("/api/") or request.path.startswith(
|
118
|
+
"/research/api/"
|
119
|
+
):
|
127
120
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
128
121
|
response.headers["Access-Control-Allow-Methods"] = (
|
129
|
-
"GET, POST, DELETE, OPTIONS"
|
122
|
+
"GET, POST, PUT, DELETE, OPTIONS"
|
123
|
+
)
|
124
|
+
response.headers["Access-Control-Allow-Headers"] = (
|
125
|
+
"Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override"
|
130
126
|
)
|
131
|
-
response.headers["Access-Control-Allow-
|
127
|
+
response.headers["Access-Control-Allow-Credentials"] = "true"
|
128
|
+
response.headers["Access-Control-Max-Age"] = "3600"
|
132
129
|
|
133
130
|
return response
|
134
131
|
|
@@ -144,28 +141,69 @@ def apply_middleware(app):
|
|
144
141
|
# Return empty response to prevent further processing
|
145
142
|
return "", 200
|
146
143
|
|
144
|
+
# Handle CORS preflight requests
|
145
|
+
@app.before_request
|
146
|
+
def handle_preflight():
|
147
|
+
if request.method == "OPTIONS":
|
148
|
+
if request.path.startswith("/api/") or request.path.startswith(
|
149
|
+
"/research/api/"
|
150
|
+
):
|
151
|
+
response = app.make_default_options_response()
|
152
|
+
response.headers["Access-Control-Allow-Origin"] = "*"
|
153
|
+
response.headers["Access-Control-Allow-Methods"] = (
|
154
|
+
"GET, POST, PUT, DELETE, OPTIONS"
|
155
|
+
)
|
156
|
+
response.headers["Access-Control-Allow-Headers"] = (
|
157
|
+
"Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override"
|
158
|
+
)
|
159
|
+
response.headers["Access-Control-Allow-Credentials"] = "true"
|
160
|
+
response.headers["Access-Control-Max-Age"] = "3600"
|
161
|
+
return response
|
162
|
+
|
147
163
|
|
148
164
|
def register_blueprints(app):
|
149
165
|
"""Register blueprints with the Flask app."""
|
150
166
|
|
151
167
|
# Import blueprints
|
168
|
+
from .api import api_blueprint # Import the API blueprint
|
152
169
|
from .routes.api_routes import api_bp # Import the API blueprint
|
153
170
|
from .routes.history_routes import history_bp
|
171
|
+
from .routes.metrics_routes import metrics_bp
|
154
172
|
from .routes.research_routes import research_bp
|
155
173
|
from .routes.settings_routes import settings_bp
|
156
174
|
|
175
|
+
# Add root route
|
176
|
+
@app.route("/")
|
177
|
+
def index():
|
178
|
+
"""Root route - redirect to research page"""
|
179
|
+
from flask import redirect, url_for
|
180
|
+
|
181
|
+
return redirect(url_for("research.index"))
|
182
|
+
|
157
183
|
# Register blueprints
|
158
184
|
app.register_blueprint(research_bp)
|
159
185
|
app.register_blueprint(history_bp, url_prefix="/research/api")
|
186
|
+
app.register_blueprint(metrics_bp)
|
160
187
|
app.register_blueprint(settings_bp)
|
161
188
|
app.register_blueprint(
|
162
189
|
api_bp, url_prefix="/research/api"
|
163
190
|
) # Register API blueprint with prefix
|
164
191
|
|
165
|
-
#
|
166
|
-
|
167
|
-
|
168
|
-
|
192
|
+
# Register API v1 blueprint
|
193
|
+
app.register_blueprint(api_blueprint) # Already has url_prefix='/api/v1'
|
194
|
+
|
195
|
+
# After registration, update CSRF exemptions
|
196
|
+
if hasattr(app, "extensions") and "csrf" in app.extensions:
|
197
|
+
csrf = app.extensions["csrf"]
|
198
|
+
# Exempt the API blueprint routes by actual endpoints
|
199
|
+
csrf.exempt("api_v1")
|
200
|
+
csrf.exempt("api")
|
201
|
+
for rule in app.url_map.iter_rules():
|
202
|
+
if rule.endpoint and (
|
203
|
+
rule.endpoint.startswith("api_v1.")
|
204
|
+
or rule.endpoint.startswith("api.")
|
205
|
+
):
|
206
|
+
csrf.exempt(rule.endpoint)
|
169
207
|
|
170
208
|
# Add favicon route
|
171
209
|
@app.route("/favicon.ico")
|
@@ -192,41 +230,6 @@ def register_error_handlers(app):
|
|
192
230
|
return make_response(jsonify({"error": "Server error"}), 500)
|
193
231
|
|
194
232
|
|
195
|
-
def register_socket_events(socketio):
|
196
|
-
"""Register Socket.IO event handlers."""
|
197
|
-
|
198
|
-
from .routes.research_routes import get_globals
|
199
|
-
from .services.socket_service import (
|
200
|
-
handle_connect,
|
201
|
-
handle_default_error,
|
202
|
-
handle_disconnect,
|
203
|
-
handle_socket_error,
|
204
|
-
handle_subscribe,
|
205
|
-
)
|
206
|
-
|
207
|
-
@socketio.on("connect")
|
208
|
-
def on_connect():
|
209
|
-
handle_connect(request)
|
210
|
-
|
211
|
-
@socketio.on("disconnect")
|
212
|
-
def on_disconnect():
|
213
|
-
handle_disconnect(request)
|
214
|
-
|
215
|
-
@socketio.on("subscribe_to_research")
|
216
|
-
def on_subscribe(data):
|
217
|
-
globals_dict = get_globals()
|
218
|
-
active_research = globals_dict.get("active_research", {})
|
219
|
-
handle_subscribe(data, request, active_research)
|
220
|
-
|
221
|
-
@socketio.on_error
|
222
|
-
def on_error(e):
|
223
|
-
return handle_socket_error(e)
|
224
|
-
|
225
|
-
@socketio.on_error_default
|
226
|
-
def on_default_error(e):
|
227
|
-
return handle_default_error(e)
|
228
|
-
|
229
|
-
|
230
233
|
def create_database(app):
|
231
234
|
"""
|
232
235
|
Create the database and tables for the application.
|
@@ -250,7 +253,9 @@ def create_database(app):
|
|
250
253
|
Base.metadata.create_all(engine)
|
251
254
|
|
252
255
|
# Configure session factory
|
253
|
-
session_factory = sessionmaker(
|
256
|
+
session_factory = sessionmaker(
|
257
|
+
bind=engine, autocommit=False, autoflush=False
|
258
|
+
)
|
254
259
|
app.db_session = scoped_session(session_factory)
|
255
260
|
|
256
261
|
# Run migrations and setup predefined settings
|