local-deep-research 0.5.2__py3-none-any.whl → 0.5.3__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/config/llm_config.py +61 -1
- local_deep_research/error_handling/__init__.py +13 -0
- local_deep_research/error_handling/error_reporter.py +236 -0
- local_deep_research/error_handling/report_generator.py +403 -0
- local_deep_research/web/routes/history_routes.py +1 -1
- local_deep_research/web/services/research_service.py +106 -2
- local_deep_research/web/static/js/components/progress.js +19 -13
- local_deep_research/web/static/js/components/results.js +1 -1
- local_deep_research/web/templates/pages/research.html +2 -2
- {local_deep_research-0.5.2.dist-info → local_deep_research-0.5.3.dist-info}/METADATA +1 -1
- {local_deep_research-0.5.2.dist-info → local_deep_research-0.5.3.dist-info}/RECORD +15 -13
- local_deep_research/test_migration.py +0 -188
- {local_deep_research-0.5.2.dist-info → local_deep_research-0.5.3.dist-info}/WHEEL +0 -0
- {local_deep_research-0.5.2.dist-info → local_deep_research-0.5.3.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.5.2.dist-info → local_deep_research-0.5.3.dist-info}/licenses/LICENSE +0 -0
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.5.
|
1
|
+
__version__ = "0.5.3"
|
@@ -397,6 +397,24 @@ def get_llm(
|
|
397
397
|
llm = ChatOllama(
|
398
398
|
model=model_name, base_url=base_url, **common_params
|
399
399
|
)
|
400
|
+
|
401
|
+
# Log the actual client configuration after creation
|
402
|
+
logger.debug(
|
403
|
+
f"ChatOllama created - base_url attribute: {getattr(llm, 'base_url', 'not found')}"
|
404
|
+
)
|
405
|
+
if hasattr(llm, "_client"):
|
406
|
+
client = llm._client
|
407
|
+
logger.debug(f"ChatOllama _client type: {type(client)}")
|
408
|
+
if hasattr(client, "_client"):
|
409
|
+
inner_client = client._client
|
410
|
+
logger.debug(
|
411
|
+
f"ChatOllama inner client type: {type(inner_client)}"
|
412
|
+
)
|
413
|
+
if hasattr(inner_client, "base_url"):
|
414
|
+
logger.debug(
|
415
|
+
f"ChatOllama inner client base_url: {inner_client.base_url}"
|
416
|
+
)
|
417
|
+
|
400
418
|
# Test invoke to validate model works
|
401
419
|
logger.info("Testing Ollama model with simple invocation")
|
402
420
|
test_result = llm.invoke("Hello")
|
@@ -545,7 +563,49 @@ def wrap_llm_without_think_tags(
|
|
545
563
|
self.base_llm = base_llm
|
546
564
|
|
547
565
|
def invoke(self, *args, **kwargs):
|
548
|
-
|
566
|
+
# Log detailed request information for Ollama models
|
567
|
+
if hasattr(self.base_llm, "base_url"):
|
568
|
+
logger.debug(
|
569
|
+
f"LLM Request - Base URL: {self.base_llm.base_url}"
|
570
|
+
)
|
571
|
+
logger.debug(
|
572
|
+
f"LLM Request - Model: {getattr(self.base_llm, 'model', 'unknown')}"
|
573
|
+
)
|
574
|
+
logger.debug(
|
575
|
+
f"LLM Request - Args count: {len(args)}, Kwargs: {list(kwargs.keys())}"
|
576
|
+
)
|
577
|
+
|
578
|
+
# Log the prompt if it's in args
|
579
|
+
if args and len(args) > 0:
|
580
|
+
prompt_text = (
|
581
|
+
str(args[0])[:200] + "..."
|
582
|
+
if len(str(args[0])) > 200
|
583
|
+
else str(args[0])
|
584
|
+
)
|
585
|
+
logger.debug(f"LLM Request - Prompt preview: {prompt_text}")
|
586
|
+
|
587
|
+
# Check if there's any client configuration
|
588
|
+
if hasattr(self.base_llm, "_client"):
|
589
|
+
client = self.base_llm._client
|
590
|
+
if hasattr(client, "_client") and hasattr(
|
591
|
+
client._client, "base_url"
|
592
|
+
):
|
593
|
+
logger.debug(
|
594
|
+
f"LLM Request - Client base URL: {client._client.base_url}"
|
595
|
+
)
|
596
|
+
|
597
|
+
try:
|
598
|
+
response = self.base_llm.invoke(*args, **kwargs)
|
599
|
+
logger.debug(f"LLM Response - Success, type: {type(response)}")
|
600
|
+
except Exception as e:
|
601
|
+
logger.error(f"LLM Request - Failed with error: {str(e)}")
|
602
|
+
# Log any URL information from the error
|
603
|
+
error_str = str(e)
|
604
|
+
if "http://" in error_str or "https://" in error_str:
|
605
|
+
logger.error(
|
606
|
+
f"LLM Request - Error contains URL info: {error_str}"
|
607
|
+
)
|
608
|
+
raise
|
549
609
|
|
550
610
|
# Process the response content if it has a content attribute
|
551
611
|
if hasattr(response, "content"):
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""
|
2
|
+
Error Handling Module for Local Deep Research
|
3
|
+
|
4
|
+
This module provides comprehensive error handling capabilities including:
|
5
|
+
- Error categorization and analysis
|
6
|
+
- User-friendly error report generation
|
7
|
+
- Integration with partial research results
|
8
|
+
"""
|
9
|
+
|
10
|
+
from .error_reporter import ErrorReporter
|
11
|
+
from .report_generator import ErrorReportGenerator
|
12
|
+
|
13
|
+
__all__ = ["ErrorReporter", "ErrorReportGenerator"]
|
@@ -0,0 +1,236 @@
|
|
1
|
+
"""
|
2
|
+
ErrorReporter - Main error categorization and handling logic
|
3
|
+
"""
|
4
|
+
|
5
|
+
import re
|
6
|
+
from enum import Enum
|
7
|
+
from typing import Dict, Optional, Any
|
8
|
+
from loguru import logger
|
9
|
+
|
10
|
+
|
11
|
+
class ErrorCategory(Enum):
|
12
|
+
"""Categories of errors that can occur during research"""
|
13
|
+
|
14
|
+
CONNECTION_ERROR = "connection_error"
|
15
|
+
MODEL_ERROR = "model_error"
|
16
|
+
SEARCH_ERROR = "search_error"
|
17
|
+
SYNTHESIS_ERROR = "synthesis_error"
|
18
|
+
FILE_ERROR = "file_error"
|
19
|
+
UNKNOWN_ERROR = "unknown_error"
|
20
|
+
|
21
|
+
|
22
|
+
class ErrorReporter:
|
23
|
+
"""
|
24
|
+
Analyzes and categorizes errors to provide better user feedback
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(self):
|
28
|
+
self.error_patterns = {
|
29
|
+
ErrorCategory.CONNECTION_ERROR: [
|
30
|
+
r"POST predict.*EOF",
|
31
|
+
r"Connection refused",
|
32
|
+
r"timeout",
|
33
|
+
r"Connection.*failed",
|
34
|
+
r"HTTP error \d+",
|
35
|
+
r"network.*error",
|
36
|
+
r"\[Errno 111\]",
|
37
|
+
r"host\.docker\.internal",
|
38
|
+
r"host.*localhost.*Docker",
|
39
|
+
r"127\.0\.0\.1.*Docker",
|
40
|
+
r"localhost.*1234.*Docker",
|
41
|
+
r"LM.*Studio.*Docker.*Mac",
|
42
|
+
],
|
43
|
+
ErrorCategory.MODEL_ERROR: [
|
44
|
+
r"Model.*not found",
|
45
|
+
r"Invalid.*model",
|
46
|
+
r"Ollama.*not available",
|
47
|
+
r"API key.*invalid",
|
48
|
+
r"Authentication.*error",
|
49
|
+
r"max_workers must be greater than 0",
|
50
|
+
r"TypeError.*Context.*Size",
|
51
|
+
r"'<' not supported between",
|
52
|
+
r"No auth credentials found",
|
53
|
+
r"401.*API key",
|
54
|
+
],
|
55
|
+
ErrorCategory.SEARCH_ERROR: [
|
56
|
+
r"Search.*failed",
|
57
|
+
r"No search results",
|
58
|
+
r"Search engine.*error",
|
59
|
+
r"Rate limit.*exceeded",
|
60
|
+
r"The search is longer than 256 characters",
|
61
|
+
r"Failed to create search engine",
|
62
|
+
r"could not be found",
|
63
|
+
r"GitHub API error",
|
64
|
+
r"database.*locked",
|
65
|
+
],
|
66
|
+
ErrorCategory.SYNTHESIS_ERROR: [
|
67
|
+
r"Error.*synthesis",
|
68
|
+
r"Failed.*generate",
|
69
|
+
r"Synthesis.*timeout",
|
70
|
+
r"detailed.*report.*stuck",
|
71
|
+
r"report.*taking.*long",
|
72
|
+
r"progress.*100.*stuck",
|
73
|
+
],
|
74
|
+
ErrorCategory.FILE_ERROR: [
|
75
|
+
r"Permission denied",
|
76
|
+
r"File.*not found",
|
77
|
+
r"Cannot write.*file",
|
78
|
+
r"Disk.*full",
|
79
|
+
r"No module named.*local_deep_research",
|
80
|
+
r"HTTP error 404.*research results",
|
81
|
+
r"Attempt to write readonly database",
|
82
|
+
],
|
83
|
+
}
|
84
|
+
|
85
|
+
def categorize_error(self, error_message: str) -> ErrorCategory:
|
86
|
+
"""
|
87
|
+
Categorize an error based on its message
|
88
|
+
|
89
|
+
Args:
|
90
|
+
error_message: The error message to categorize
|
91
|
+
|
92
|
+
Returns:
|
93
|
+
ErrorCategory: The categorized error type
|
94
|
+
"""
|
95
|
+
error_message = str(error_message).lower()
|
96
|
+
|
97
|
+
for category, patterns in self.error_patterns.items():
|
98
|
+
for pattern in patterns:
|
99
|
+
if re.search(pattern.lower(), error_message):
|
100
|
+
logger.debug(
|
101
|
+
f"Categorized error as {category.value}: {pattern}"
|
102
|
+
)
|
103
|
+
return category
|
104
|
+
|
105
|
+
return ErrorCategory.UNKNOWN_ERROR
|
106
|
+
|
107
|
+
def get_user_friendly_title(self, category: ErrorCategory) -> str:
|
108
|
+
"""
|
109
|
+
Get a user-friendly title for an error category
|
110
|
+
|
111
|
+
Args:
|
112
|
+
category: The error category
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
str: User-friendly title
|
116
|
+
"""
|
117
|
+
titles = {
|
118
|
+
ErrorCategory.CONNECTION_ERROR: "Connection Issue",
|
119
|
+
ErrorCategory.MODEL_ERROR: "LLM Service Error",
|
120
|
+
ErrorCategory.SEARCH_ERROR: "Search Service Error",
|
121
|
+
ErrorCategory.SYNTHESIS_ERROR: "Report Generation Error",
|
122
|
+
ErrorCategory.FILE_ERROR: "File System Error",
|
123
|
+
ErrorCategory.UNKNOWN_ERROR: "Unexpected Error",
|
124
|
+
}
|
125
|
+
return titles.get(category, "Error")
|
126
|
+
|
127
|
+
def get_suggested_actions(self, category: ErrorCategory) -> list:
|
128
|
+
"""
|
129
|
+
Get suggested actions for resolving an error
|
130
|
+
|
131
|
+
Args:
|
132
|
+
category: The error category
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
list: List of suggested actions
|
136
|
+
"""
|
137
|
+
suggestions = {
|
138
|
+
ErrorCategory.CONNECTION_ERROR: [
|
139
|
+
"Check if the LLM service (Ollama/LM Studio) is running",
|
140
|
+
"Verify network connectivity",
|
141
|
+
"Try switching to a different model provider",
|
142
|
+
"Check the service logs for more details",
|
143
|
+
],
|
144
|
+
ErrorCategory.MODEL_ERROR: [
|
145
|
+
"Verify the model name is correct",
|
146
|
+
"Check if the model is downloaded and available",
|
147
|
+
"Validate API keys if using external services",
|
148
|
+
"Try switching to a different model",
|
149
|
+
],
|
150
|
+
ErrorCategory.SEARCH_ERROR: [
|
151
|
+
"Check internet connectivity",
|
152
|
+
"Try reducing the number of search results",
|
153
|
+
"Wait a moment and try again",
|
154
|
+
"Check if search service is configured correctly",
|
155
|
+
"For local documents: ensure the path is absolute and folder exists",
|
156
|
+
"Try a different search engine if one is failing",
|
157
|
+
],
|
158
|
+
ErrorCategory.SYNTHESIS_ERROR: [
|
159
|
+
"The research data was collected successfully",
|
160
|
+
"Try switching to a different model for report generation",
|
161
|
+
"Check the partial results below",
|
162
|
+
"Review the detailed logs for more information",
|
163
|
+
],
|
164
|
+
ErrorCategory.FILE_ERROR: [
|
165
|
+
"Check disk space availability",
|
166
|
+
"Verify write permissions",
|
167
|
+
"Try changing the output directory",
|
168
|
+
"Restart the application",
|
169
|
+
],
|
170
|
+
ErrorCategory.UNKNOWN_ERROR: [
|
171
|
+
"Check the detailed logs below for more information",
|
172
|
+
"Try running the research again",
|
173
|
+
"Report this issue if it persists",
|
174
|
+
"Contact support with the error details",
|
175
|
+
],
|
176
|
+
}
|
177
|
+
return suggestions.get(category, ["Check the logs for more details"])
|
178
|
+
|
179
|
+
def analyze_error(
|
180
|
+
self, error_message: str, context: Optional[Dict[str, Any]] = None
|
181
|
+
) -> Dict[str, Any]:
|
182
|
+
"""
|
183
|
+
Perform comprehensive error analysis
|
184
|
+
|
185
|
+
Args:
|
186
|
+
error_message: The error message to analyze
|
187
|
+
context: Optional context information
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
dict: Comprehensive error analysis
|
191
|
+
"""
|
192
|
+
category = self.categorize_error(error_message)
|
193
|
+
|
194
|
+
analysis = {
|
195
|
+
"category": category,
|
196
|
+
"title": self.get_user_friendly_title(category),
|
197
|
+
"original_error": error_message,
|
198
|
+
"suggestions": self.get_suggested_actions(category),
|
199
|
+
"severity": self._determine_severity(category),
|
200
|
+
"recoverable": self._is_recoverable(category),
|
201
|
+
}
|
202
|
+
|
203
|
+
# Add context-specific information
|
204
|
+
if context:
|
205
|
+
analysis["context"] = context
|
206
|
+
analysis["has_partial_results"] = bool(
|
207
|
+
context.get("findings")
|
208
|
+
or context.get("current_knowledge")
|
209
|
+
or context.get("search_results")
|
210
|
+
)
|
211
|
+
|
212
|
+
return analysis
|
213
|
+
|
214
|
+
def _determine_severity(self, category: ErrorCategory) -> str:
|
215
|
+
"""Determine error severity level"""
|
216
|
+
severity_map = {
|
217
|
+
ErrorCategory.CONNECTION_ERROR: "high",
|
218
|
+
ErrorCategory.MODEL_ERROR: "high",
|
219
|
+
ErrorCategory.SEARCH_ERROR: "medium",
|
220
|
+
ErrorCategory.SYNTHESIS_ERROR: "low", # Can often show partial results
|
221
|
+
ErrorCategory.FILE_ERROR: "medium",
|
222
|
+
ErrorCategory.UNKNOWN_ERROR: "high",
|
223
|
+
}
|
224
|
+
return severity_map.get(category, "medium")
|
225
|
+
|
226
|
+
def _is_recoverable(self, category: ErrorCategory) -> bool:
|
227
|
+
"""Determine if error is recoverable with user action"""
|
228
|
+
recoverable = {
|
229
|
+
ErrorCategory.CONNECTION_ERROR: True,
|
230
|
+
ErrorCategory.MODEL_ERROR: True,
|
231
|
+
ErrorCategory.SEARCH_ERROR: True,
|
232
|
+
ErrorCategory.SYNTHESIS_ERROR: True,
|
233
|
+
ErrorCategory.FILE_ERROR: True,
|
234
|
+
ErrorCategory.UNKNOWN_ERROR: False,
|
235
|
+
}
|
236
|
+
return recoverable.get(category, False)
|
@@ -0,0 +1,403 @@
|
|
1
|
+
"""
|
2
|
+
ErrorReportGenerator - Create user-friendly error reports
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, Any, Optional
|
6
|
+
from loguru import logger
|
7
|
+
|
8
|
+
from .error_reporter import ErrorReporter
|
9
|
+
|
10
|
+
|
11
|
+
class ErrorReportGenerator:
|
12
|
+
"""
|
13
|
+
Generates comprehensive, user-friendly error reports
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(self, llm=None):
|
17
|
+
"""
|
18
|
+
Initialize error report generator
|
19
|
+
|
20
|
+
Args:
|
21
|
+
llm: Optional LLM instance (unused, kept for compatibility)
|
22
|
+
"""
|
23
|
+
self.error_reporter = ErrorReporter()
|
24
|
+
|
25
|
+
def generate_error_report(
|
26
|
+
self,
|
27
|
+
error_message: str,
|
28
|
+
query: str,
|
29
|
+
partial_results: Optional[Dict[str, Any]] = None,
|
30
|
+
search_iterations: int = 0,
|
31
|
+
research_id: Optional[int] = None,
|
32
|
+
) -> str:
|
33
|
+
"""
|
34
|
+
Generate a comprehensive error report
|
35
|
+
|
36
|
+
Args:
|
37
|
+
error_message: The error that occurred
|
38
|
+
query: The research query
|
39
|
+
partial_results: Any partial results that were collected
|
40
|
+
search_iterations: Number of search iterations completed
|
41
|
+
research_id: Research ID for reference
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
str: Formatted error report in Markdown
|
45
|
+
"""
|
46
|
+
try:
|
47
|
+
# Analyze the error
|
48
|
+
context = {
|
49
|
+
"query": query,
|
50
|
+
"search_iterations": search_iterations,
|
51
|
+
"research_id": research_id,
|
52
|
+
"partial_results": partial_results,
|
53
|
+
}
|
54
|
+
|
55
|
+
if partial_results:
|
56
|
+
context.update(partial_results)
|
57
|
+
|
58
|
+
error_analysis = self.error_reporter.analyze_error(
|
59
|
+
error_message, context
|
60
|
+
)
|
61
|
+
|
62
|
+
# Build the simplified report
|
63
|
+
report_parts = []
|
64
|
+
|
65
|
+
# Header with user-friendly error message and logs reference
|
66
|
+
user_friendly_message = self._make_error_user_friendly(
|
67
|
+
error_message
|
68
|
+
)
|
69
|
+
category_title = error_analysis.get("title", "Error")
|
70
|
+
|
71
|
+
report_parts.append("# ⚠️ Research Failed")
|
72
|
+
report_parts.append(f"\n**Error Type:** {category_title}")
|
73
|
+
report_parts.append(f"\n**What happened:** {user_friendly_message}")
|
74
|
+
report_parts.append(
|
75
|
+
'\n*For detailed error information, scroll down to the research logs and select "Errors" from the filter.*'
|
76
|
+
)
|
77
|
+
|
78
|
+
# Support links - moved up for better visibility
|
79
|
+
report_parts.append("\n## 💬 Get Help")
|
80
|
+
report_parts.append("We're here to help you get this working:")
|
81
|
+
report_parts.append(
|
82
|
+
"- 📖 **Documentation & guides:** [Wiki](https://github.com/LearningCircuit/local-deep-research/wiki)"
|
83
|
+
)
|
84
|
+
report_parts.append(
|
85
|
+
"- 💬 **Chat with the community:** [Discord #help-and-support](https://discord.gg/ttcqQeFcJ3)"
|
86
|
+
)
|
87
|
+
report_parts.append(
|
88
|
+
"- 🐛 **Report bugs or get help:** [GitHub Issues](https://github.com/LearningCircuit/local-deep-research/issues) *(don't hesitate to ask if you're stuck!)*"
|
89
|
+
)
|
90
|
+
report_parts.append(
|
91
|
+
"- 💭 **Join discussions:** [Reddit r/LocalDeepResearch](https://www.reddit.com/r/LocalDeepResearch/) *(checked less frequently)*"
|
92
|
+
)
|
93
|
+
|
94
|
+
# Show partial results if available (in expandable section)
|
95
|
+
if error_analysis.get("has_partial_results"):
|
96
|
+
partial_content = self._format_partial_results(partial_results)
|
97
|
+
if partial_content:
|
98
|
+
report_parts.append(
|
99
|
+
f"\n<details>\n<summary>📊 Partial Results Available</summary>\n\n{partial_content}\n</details>"
|
100
|
+
)
|
101
|
+
|
102
|
+
return "\n".join(report_parts)
|
103
|
+
|
104
|
+
except Exception as e:
|
105
|
+
# Fallback: always return something, even if error report generation fails
|
106
|
+
logger.exception(f"Failed to generate error report: {e}")
|
107
|
+
return f"""# ⚠️ Research Failed
|
108
|
+
|
109
|
+
**What happened:** {error_message}
|
110
|
+
|
111
|
+
## 💬 Get Help
|
112
|
+
We're here to help you get this working:
|
113
|
+
- 📖 **Documentation & guides:** [Wiki](https://github.com/LearningCircuit/local-deep-research/wiki)
|
114
|
+
- 💬 **Chat with the community:** [Discord #help-and-support](https://discord.gg/ttcqQeFcJ3)
|
115
|
+
- 🐛 **Report bugs or get help:** [GitHub Issues](https://github.com/LearningCircuit/local-deep-research/issues) *(don't hesitate to ask if you're stuck!)*
|
116
|
+
|
117
|
+
*Note: Error report generation failed - showing basic error information.*"""
|
118
|
+
|
119
|
+
def _format_partial_results(
|
120
|
+
self, partial_results: Optional[Dict[str, Any]]
|
121
|
+
) -> str:
|
122
|
+
"""
|
123
|
+
Format partial results for display
|
124
|
+
|
125
|
+
Args:
|
126
|
+
partial_results: Partial results data
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
str: Formatted partial results
|
130
|
+
"""
|
131
|
+
if not partial_results:
|
132
|
+
return ""
|
133
|
+
|
134
|
+
formatted_parts = []
|
135
|
+
|
136
|
+
# Current knowledge summary
|
137
|
+
if "current_knowledge" in partial_results:
|
138
|
+
knowledge = partial_results["current_knowledge"]
|
139
|
+
if knowledge and len(knowledge.strip()) > 50:
|
140
|
+
formatted_parts.append("### Research Summary\n")
|
141
|
+
formatted_parts.append(
|
142
|
+
knowledge[:1000] + "..."
|
143
|
+
if len(knowledge) > 1000
|
144
|
+
else knowledge
|
145
|
+
)
|
146
|
+
formatted_parts.append("")
|
147
|
+
|
148
|
+
# Search results
|
149
|
+
if "search_results" in partial_results:
|
150
|
+
results = partial_results["search_results"]
|
151
|
+
if results:
|
152
|
+
formatted_parts.append("### Search Results Found\n")
|
153
|
+
for i, result in enumerate(results[:5], 1): # Show top 5
|
154
|
+
title = result.get("title", "Untitled")
|
155
|
+
url = result.get("url", "")
|
156
|
+
formatted_parts.append(f"{i}. **{title}**")
|
157
|
+
if url:
|
158
|
+
formatted_parts.append(f" - URL: {url}")
|
159
|
+
formatted_parts.append("")
|
160
|
+
|
161
|
+
# Findings
|
162
|
+
if "findings" in partial_results:
|
163
|
+
findings = partial_results["findings"]
|
164
|
+
if findings:
|
165
|
+
formatted_parts.append("### Research Findings\n")
|
166
|
+
for i, finding in enumerate(findings[:3], 1): # Show top 3
|
167
|
+
content = finding.get("content", "")
|
168
|
+
if content and not content.startswith("Error:"):
|
169
|
+
phase = finding.get("phase", f"Finding {i}")
|
170
|
+
formatted_parts.append(f"**{phase}:**")
|
171
|
+
formatted_parts.append(
|
172
|
+
content[:500] + "..."
|
173
|
+
if len(content) > 500
|
174
|
+
else content
|
175
|
+
)
|
176
|
+
formatted_parts.append("")
|
177
|
+
|
178
|
+
if formatted_parts:
|
179
|
+
formatted_parts.append(
|
180
|
+
"*Note: The above results were successfully collected before the error occurred.*"
|
181
|
+
)
|
182
|
+
|
183
|
+
return "\n".join(formatted_parts) if formatted_parts else ""
|
184
|
+
|
185
|
+
def _get_technical_context(
|
186
|
+
self,
|
187
|
+
error_analysis: Dict[str, Any],
|
188
|
+
partial_results: Optional[Dict[str, Any]],
|
189
|
+
) -> str:
|
190
|
+
"""
|
191
|
+
Get additional technical context for the error
|
192
|
+
|
193
|
+
Args:
|
194
|
+
error_analysis: Error analysis results
|
195
|
+
partial_results: Partial results if available
|
196
|
+
|
197
|
+
Returns:
|
198
|
+
str: Technical context information
|
199
|
+
"""
|
200
|
+
context_parts = []
|
201
|
+
|
202
|
+
# Add timing information if available
|
203
|
+
if partial_results:
|
204
|
+
if "start_time" in partial_results:
|
205
|
+
context_parts.append(
|
206
|
+
f"- **Start Time:** {partial_results['start_time']}"
|
207
|
+
)
|
208
|
+
|
209
|
+
if "last_activity" in partial_results:
|
210
|
+
context_parts.append(
|
211
|
+
f"- **Last Activity:** {partial_results['last_activity']}"
|
212
|
+
)
|
213
|
+
|
214
|
+
# Add model information
|
215
|
+
if "model_config" in partial_results:
|
216
|
+
config = partial_results["model_config"]
|
217
|
+
context_parts.append(
|
218
|
+
f"- **Model:** {config.get('model_name', 'Unknown')}"
|
219
|
+
)
|
220
|
+
context_parts.append(
|
221
|
+
f"- **Provider:** {config.get('provider', 'Unknown')}"
|
222
|
+
)
|
223
|
+
|
224
|
+
# Add search information
|
225
|
+
if "search_config" in partial_results:
|
226
|
+
search_config = partial_results["search_config"]
|
227
|
+
context_parts.append(
|
228
|
+
f"- **Search Engine:** {search_config.get('engine', 'Unknown')}"
|
229
|
+
)
|
230
|
+
context_parts.append(
|
231
|
+
f"- **Max Results:** {search_config.get('max_results', 'Unknown')}"
|
232
|
+
)
|
233
|
+
|
234
|
+
# Add any error codes or HTTP status
|
235
|
+
if "status_code" in partial_results:
|
236
|
+
context_parts.append(
|
237
|
+
f"- **Status Code:** {partial_results['status_code']}"
|
238
|
+
)
|
239
|
+
|
240
|
+
if "error_code" in partial_results:
|
241
|
+
context_parts.append(
|
242
|
+
f"- **Error Code:** {partial_results['error_code']}"
|
243
|
+
)
|
244
|
+
|
245
|
+
# Add error-specific context based on category
|
246
|
+
category = error_analysis.get("category")
|
247
|
+
if category:
|
248
|
+
if "connection" in category.value.lower():
|
249
|
+
context_parts.append(
|
250
|
+
"- **Network Error:** Connection-related issue detected"
|
251
|
+
)
|
252
|
+
context_parts.append(
|
253
|
+
"- **Retry Recommended:** Check service status and try again"
|
254
|
+
)
|
255
|
+
elif "model" in category.value.lower():
|
256
|
+
context_parts.append(
|
257
|
+
"- **Model Error:** Issue with AI model or configuration"
|
258
|
+
)
|
259
|
+
context_parts.append(
|
260
|
+
"- **Check:** Model service availability and parameters"
|
261
|
+
)
|
262
|
+
|
263
|
+
return "\n".join(context_parts) if context_parts else ""
|
264
|
+
|
265
|
+
def generate_quick_error_summary(
|
266
|
+
self, error_message: str
|
267
|
+
) -> Dict[str, str]:
|
268
|
+
"""
|
269
|
+
Generate a quick error summary for API responses
|
270
|
+
|
271
|
+
Args:
|
272
|
+
error_message: The error message
|
273
|
+
|
274
|
+
Returns:
|
275
|
+
dict: Quick error summary
|
276
|
+
"""
|
277
|
+
error_analysis = self.error_reporter.analyze_error(error_message)
|
278
|
+
|
279
|
+
return {
|
280
|
+
"title": error_analysis["title"],
|
281
|
+
"category": error_analysis["category"].value,
|
282
|
+
"severity": error_analysis["severity"],
|
283
|
+
"recoverable": error_analysis["recoverable"],
|
284
|
+
}
|
285
|
+
|
286
|
+
def _make_error_user_friendly(self, error_message: str) -> str:
|
287
|
+
"""
|
288
|
+
Replace cryptic technical error messages with user-friendly versions
|
289
|
+
|
290
|
+
Args:
|
291
|
+
error_message: The original technical error message
|
292
|
+
|
293
|
+
Returns:
|
294
|
+
str: User-friendly error message, or original if no replacement found
|
295
|
+
"""
|
296
|
+
# Dictionary of technical errors to user-friendly messages
|
297
|
+
error_replacements = {
|
298
|
+
"max_workers must be greater than 0": (
|
299
|
+
"The LLM failed to generate search questions. This usually means the LLM service isn't responding properly.\n\n"
|
300
|
+
"**Try this:**\n"
|
301
|
+
"- Check if your LLM service (Ollama/LM Studio) is running\n"
|
302
|
+
"- Restart the LLM service\n"
|
303
|
+
"- Try a different model"
|
304
|
+
),
|
305
|
+
"POST predict.*EOF": (
|
306
|
+
"Lost connection to Ollama. This usually means Ollama stopped responding or there's a network issue.\n\n"
|
307
|
+
"**Try this:**\n"
|
308
|
+
"- Restart Ollama: `ollama serve`\n"
|
309
|
+
"- Check if Ollama is still running: `ps aux | grep ollama`\n"
|
310
|
+
"- Try a different port if 11434 is in use"
|
311
|
+
),
|
312
|
+
"HTTP error 404.*research results": (
|
313
|
+
"The research completed but the results can't be displayed. The files were likely generated successfully.\n\n"
|
314
|
+
"**Try this:**\n"
|
315
|
+
"- Check the `research_outputs` folder for your report\n"
|
316
|
+
"- Ensure the folder has proper read/write permissions\n"
|
317
|
+
"- Restart the LDR web interface"
|
318
|
+
),
|
319
|
+
"Connection refused|\\[Errno 111\\]": (
|
320
|
+
"Cannot connect to the LLM service. The service might not be running or is using a different address.\n\n"
|
321
|
+
"**Try this:**\n"
|
322
|
+
"- Start your LLM service (Ollama: `ollama serve`, LM Studio: launch the app)\n"
|
323
|
+
"- **Docker on Mac/Windows:** Change URL from `http://localhost:1234` to `http://host.docker.internal:1234`\n"
|
324
|
+
"- **Docker on Linux:** Use your host IP instead of localhost (find with `hostname -I`)\n"
|
325
|
+
"- Check the service URL in settings matches where your LLM is running\n"
|
326
|
+
"- Verify the port number is correct (Ollama: 11434, LM Studio: 1234)"
|
327
|
+
),
|
328
|
+
"The search is longer than 256 characters": (
|
329
|
+
"Your search query is too long for GitHub's API (max 256 characters).\n\n"
|
330
|
+
"**Try this:**\n"
|
331
|
+
"- Shorten your research query\n"
|
332
|
+
"- Use a different search engine (DuckDuckGo, Searx, etc.)\n"
|
333
|
+
"- Break your research into smaller, focused queries"
|
334
|
+
),
|
335
|
+
"No module named.*local_deep_research": (
|
336
|
+
"Installation issue detected. The package isn't properly installed.\n\n"
|
337
|
+
"**Try this:**\n"
|
338
|
+
"- Reinstall: `pip install -e .` from the project directory\n"
|
339
|
+
"- Check you're using the right Python environment\n"
|
340
|
+
"- For Docker users: rebuild the container"
|
341
|
+
),
|
342
|
+
"Failed to create search engine|could not be found": (
|
343
|
+
"Search engine configuration problem.\n\n"
|
344
|
+
"**Try this:**\n"
|
345
|
+
"- Use the default search engine (auto)\n"
|
346
|
+
"- Check search engine settings in Advanced Options\n"
|
347
|
+
"- Ensure required API keys are set for external search engines"
|
348
|
+
),
|
349
|
+
"TypeError.*Context.*Size|'<' not supported between": (
|
350
|
+
"Model configuration issue. The context size setting might not be compatible with your model.\n\n"
|
351
|
+
"**Try this:**\n"
|
352
|
+
"- Check your model's maximum context size\n"
|
353
|
+
"- Leave context size settings at default\n"
|
354
|
+
"- Try a different model"
|
355
|
+
),
|
356
|
+
"Model.*not found in Ollama": (
|
357
|
+
"The specified model isn't available in Ollama.\n\n"
|
358
|
+
"**Try this:**\n"
|
359
|
+
"- Check available models: `ollama list`\n"
|
360
|
+
"- Pull the model: `ollama pull <model-name>`\n"
|
361
|
+
"- Use the exact model name shown in `ollama list` (e.g., 'gemma2:9b' not 'gemma:latest')"
|
362
|
+
),
|
363
|
+
"No auth credentials found|401.*API key": (
|
364
|
+
"API key is missing or incorrectly configured.\n\n"
|
365
|
+
"**Try this:**\n"
|
366
|
+
"- Set API key in the web UI settings (not in .env files)\n"
|
367
|
+
"- Go to Settings → Advanced → enter your API key\n"
|
368
|
+
"- For custom endpoints, ensure the key format matches what your provider expects"
|
369
|
+
),
|
370
|
+
"Attempt to write readonly database": (
|
371
|
+
"Permission issue with the database file.\n\n"
|
372
|
+
"**Try this:**\n"
|
373
|
+
"- On Windows: Run as Administrator\n"
|
374
|
+
"- On Linux/Mac: Check folder permissions\n"
|
375
|
+
"- Delete and recreate the database file if corrupted"
|
376
|
+
),
|
377
|
+
"Invalid value.*SearXNG|database.*locked": (
|
378
|
+
"SearXNG configuration or rate limiting issue.\n\n"
|
379
|
+
"**Try this:**\n"
|
380
|
+
"- Keep 'Search snippets only' enabled (don't turn it off)\n"
|
381
|
+
"- Restart SearXNG: `docker restart searxng`\n"
|
382
|
+
"- If rate limited, wait a few minutes or use a VPN"
|
383
|
+
),
|
384
|
+
"host.*localhost.*Docker|127\\.0\\.0\\.1.*Docker|localhost.*1234.*Docker|LM.*Studio.*Docker.*Mac": (
|
385
|
+
"Docker networking issue - can't connect to services on host.\n\n"
|
386
|
+
"**Try this:**\n"
|
387
|
+
"- **On Mac/Windows Docker:** Replace 'localhost' or '127.0.0.1' with 'host.docker.internal'\n"
|
388
|
+
"- **On Linux Docker:** Use your host's actual IP address (find with `hostname -I`)\n"
|
389
|
+
"- **Example:** Change `http://localhost:1234` to `http://host.docker.internal:1234`\n"
|
390
|
+
"- Ensure the service port isn't blocked by firewall\n"
|
391
|
+
"- Alternative: Use host networking mode (see wiki for setup)"
|
392
|
+
),
|
393
|
+
}
|
394
|
+
|
395
|
+
# Check each pattern and replace if found
|
396
|
+
for pattern, replacement in error_replacements.items():
|
397
|
+
import re
|
398
|
+
|
399
|
+
if re.search(pattern, error_message, re.IGNORECASE):
|
400
|
+
return f"{replacement}\n\nTechnical error: {error_message}"
|
401
|
+
|
402
|
+
# If no specific replacement found, return original message
|
403
|
+
return error_message
|
@@ -254,7 +254,7 @@ def get_research_details(research_id):
|
|
254
254
|
)
|
255
255
|
|
256
256
|
|
257
|
-
@history_bp.route("/report/<int:research_id>")
|
257
|
+
@history_bp.route("/history/report/<int:research_id>")
|
258
258
|
def get_report(research_id):
|
259
259
|
conn = get_db_connection()
|
260
260
|
conn.row_factory = lambda cursor, row: {
|
@@ -17,6 +17,7 @@ from ...utilities.threading_utils import thread_context, thread_with_app_context
|
|
17
17
|
from ..database.models import ResearchStrategy, ResearchHistory
|
18
18
|
from ..models.database import calculate_duration
|
19
19
|
from .socket_service import SocketIOService
|
20
|
+
from ...error_handling.report_generator import ErrorReportGenerator
|
20
21
|
|
21
22
|
# Output directory for research results
|
22
23
|
_PROJECT_ROOT = Path(__file__).parents[4]
|
@@ -657,8 +658,40 @@ def run_research_process(
|
|
657
658
|
)
|
658
659
|
|
659
660
|
try:
|
660
|
-
#
|
661
|
-
|
661
|
+
# Check if we have an error in the findings and use enhanced error handling
|
662
|
+
if isinstance(
|
663
|
+
raw_formatted_findings, str
|
664
|
+
) and raw_formatted_findings.startswith("Error:"):
|
665
|
+
logger.info(
|
666
|
+
"Generating enhanced error report using ErrorReportGenerator"
|
667
|
+
)
|
668
|
+
|
669
|
+
# Get LLM for error explanation if available
|
670
|
+
try:
|
671
|
+
llm = get_llm(research_id=research_id)
|
672
|
+
except Exception:
|
673
|
+
llm = None
|
674
|
+
logger.warning(
|
675
|
+
"Could not get LLM for error explanation"
|
676
|
+
)
|
677
|
+
|
678
|
+
# Generate comprehensive error report
|
679
|
+
error_generator = ErrorReportGenerator(llm)
|
680
|
+
clean_markdown = error_generator.generate_error_report(
|
681
|
+
error_message=raw_formatted_findings,
|
682
|
+
query=query,
|
683
|
+
partial_results=results,
|
684
|
+
search_iterations=results.get("iterations", 0),
|
685
|
+
research_id=research_id,
|
686
|
+
)
|
687
|
+
|
688
|
+
logger.info(
|
689
|
+
"Generated enhanced error report with %d characters",
|
690
|
+
len(clean_markdown),
|
691
|
+
)
|
692
|
+
else:
|
693
|
+
# Get the synthesized content from the LLM directly
|
694
|
+
clean_markdown = raw_formatted_findings
|
662
695
|
|
663
696
|
# Extract all sources from findings to add them to the summary
|
664
697
|
all_links = []
|
@@ -878,10 +911,77 @@ def run_research_process(
|
|
878
911
|
"solution": "Check API configuration and credentials."
|
879
912
|
}
|
880
913
|
|
914
|
+
# Generate enhanced error report for failed research
|
915
|
+
enhanced_report_content = None
|
916
|
+
try:
|
917
|
+
# Get LLM for error explanation if available
|
918
|
+
try:
|
919
|
+
llm = get_llm(research_id=research_id)
|
920
|
+
except Exception:
|
921
|
+
llm = None
|
922
|
+
logger.warning(
|
923
|
+
"Could not get LLM for error explanation in failure handler"
|
924
|
+
)
|
925
|
+
|
926
|
+
# Get partial results if they exist
|
927
|
+
partial_results = results if "results" in locals() else None
|
928
|
+
search_iterations = (
|
929
|
+
results.get("iterations", 0) if partial_results else 0
|
930
|
+
)
|
931
|
+
|
932
|
+
# Generate comprehensive error report
|
933
|
+
error_generator = ErrorReportGenerator(llm)
|
934
|
+
enhanced_report_content = error_generator.generate_error_report(
|
935
|
+
error_message=f"Research failed: {str(e)}",
|
936
|
+
query=query,
|
937
|
+
partial_results=partial_results,
|
938
|
+
search_iterations=search_iterations,
|
939
|
+
research_id=research_id,
|
940
|
+
)
|
941
|
+
|
942
|
+
logger.info(
|
943
|
+
"Generated enhanced error report for failed research (length: %d)",
|
944
|
+
len(enhanced_report_content),
|
945
|
+
)
|
946
|
+
|
947
|
+
# Save enhanced error report as the actual report file
|
948
|
+
try:
|
949
|
+
reports_folder = OUTPUT_DIR
|
950
|
+
report_filename = f"research_{research_id}_error_report.md"
|
951
|
+
report_path = reports_folder / report_filename
|
952
|
+
|
953
|
+
with open(report_path, "w", encoding="utf-8") as f:
|
954
|
+
f.write(enhanced_report_content)
|
955
|
+
|
956
|
+
logger.info(
|
957
|
+
"Saved enhanced error report to: %s", report_path
|
958
|
+
)
|
959
|
+
|
960
|
+
# Store the report path so it can be retrieved later
|
961
|
+
report_path_to_save = str(
|
962
|
+
report_path.relative_to(reports_folder.parent)
|
963
|
+
)
|
964
|
+
|
965
|
+
except Exception as report_error:
|
966
|
+
logger.exception(
|
967
|
+
"Failed to save enhanced error report: %s", report_error
|
968
|
+
)
|
969
|
+
report_path_to_save = None
|
970
|
+
|
971
|
+
except Exception as error_gen_error:
|
972
|
+
logger.exception(
|
973
|
+
"Failed to generate enhanced error report: %s",
|
974
|
+
error_gen_error,
|
975
|
+
)
|
976
|
+
enhanced_report_content = None
|
977
|
+
report_path_to_save = None
|
978
|
+
|
881
979
|
# Update metadata with more context about the error
|
882
980
|
metadata = {"phase": "error", "error": user_friendly_error}
|
883
981
|
if error_context:
|
884
982
|
metadata.update(error_context)
|
983
|
+
if enhanced_report_content:
|
984
|
+
metadata["has_enhanced_report"] = True
|
885
985
|
|
886
986
|
# If we still have an active research record, update its log
|
887
987
|
if research_id in active_research:
|
@@ -934,6 +1034,10 @@ def run_research_process(
|
|
934
1034
|
research.duration_seconds = duration_seconds
|
935
1035
|
research.metadata = metadata
|
936
1036
|
|
1037
|
+
# Add error report path if available
|
1038
|
+
if "report_path_to_save" in locals() and report_path_to_save:
|
1039
|
+
research.report_path = report_path_to_save
|
1040
|
+
|
937
1041
|
db_session.commit()
|
938
1042
|
|
939
1043
|
try:
|
@@ -630,18 +630,20 @@
|
|
630
630
|
cancelButton.style.display = 'none';
|
631
631
|
}
|
632
632
|
} else if (data.status === 'failed' || data.status === 'cancelled') {
|
633
|
-
//
|
634
|
-
if (
|
635
|
-
|
633
|
+
// For failed research, try to show the error report if available
|
634
|
+
if (data.status === 'failed') {
|
635
|
+
if (viewResultsButton) {
|
636
|
+
viewResultsButton.textContent = 'View Error Report';
|
637
|
+
viewResultsButton.href = `/research/results/${currentResearchId}`;
|
638
|
+
viewResultsButton.style.display = 'inline-block';
|
639
|
+
}
|
636
640
|
} else {
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
viewResultsButton.href = '/';
|
644
|
-
viewResultsButton.style.display = 'inline-block';
|
641
|
+
// For cancelled research, go back to home
|
642
|
+
if (viewResultsButton) {
|
643
|
+
viewResultsButton.textContent = 'Start New Research';
|
644
|
+
viewResultsButton.href = '/';
|
645
|
+
viewResultsButton.style.display = 'inline-block';
|
646
|
+
}
|
645
647
|
}
|
646
648
|
|
647
649
|
// Hide cancel button
|
@@ -917,8 +919,12 @@
|
|
917
919
|
cancelButton.style.display = 'none';
|
918
920
|
}
|
919
921
|
|
920
|
-
// Show
|
921
|
-
|
922
|
+
// Show error report button
|
923
|
+
if (viewResultsButton) {
|
924
|
+
viewResultsButton.textContent = 'View Error Report';
|
925
|
+
viewResultsButton.href = `/research/results/${currentResearchId}`;
|
926
|
+
viewResultsButton.style.display = 'inline-block';
|
927
|
+
}
|
922
928
|
|
923
929
|
// Show notification if enabled
|
924
930
|
showNotification('Research Error', `There was an error with your research: ${data.error}`);
|
@@ -98,7 +98,7 @@
|
|
98
98
|
resultsContainer.innerHTML = '<div class="text-center my-5"><i class="fas fa-spinner fa-pulse"></i><p class="mt-3">Loading research results...</p></div>';
|
99
99
|
|
100
100
|
// Fetch result from API
|
101
|
-
const response = await fetch(`/research/api/report/${researchId}`);
|
101
|
+
const response = await fetch(`/research/api/history/report/${researchId}`);
|
102
102
|
|
103
103
|
if (!response.ok) {
|
104
104
|
throw new Error(`HTTP error ${response.status}`);
|
@@ -48,7 +48,7 @@
|
|
48
48
|
<label for="mode-quick" class="mode-option active" data-mode="quick" role="radio" aria-checked="true" tabindex="0">
|
49
49
|
<div class="mode-icon"><i class="fas fa-bolt" aria-hidden="true"></i></div>
|
50
50
|
<div class="mode-info">
|
51
|
-
<
|
51
|
+
<h2>Quick Summary</h2>
|
52
52
|
<p>Generated in a few minutes</p>
|
53
53
|
</div>
|
54
54
|
</label>
|
@@ -57,7 +57,7 @@
|
|
57
57
|
<label for="mode-detailed" class="mode-option" data-mode="detailed" role="radio" aria-checked="false" tabindex="-1">
|
58
58
|
<div class="mode-icon"><i class="fas fa-microscope" aria-hidden="true"></i></div>
|
59
59
|
<div class="mode-info">
|
60
|
-
<
|
60
|
+
<h2>Detailed Report</h2>
|
61
61
|
<p>In-depth analysis (takes longer)</p>
|
62
62
|
</div>
|
63
63
|
</label>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: local-deep-research
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.3
|
4
4
|
Summary: AI-powered research assistant with deep, iterative analysis using LLMs and web searches
|
5
5
|
Author-Email: LearningCircuit <185559241+LearningCircuit@users.noreply.github.com>, HashedViking <6432677+HashedViking@users.noreply.github.com>
|
6
6
|
License: MIT License
|
@@ -1,9 +1,9 @@
|
|
1
|
-
local_deep_research-0.5.
|
2
|
-
local_deep_research-0.5.
|
3
|
-
local_deep_research-0.5.
|
4
|
-
local_deep_research-0.5.
|
1
|
+
local_deep_research-0.5.3.dist-info/METADATA,sha256=sJGfKxEAI9Nn8wafKK3qnTOaj8sJzq2mqNvl130ep0s,17676
|
2
|
+
local_deep_research-0.5.3.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
|
3
|
+
local_deep_research-0.5.3.dist-info/entry_points.txt,sha256=GcXS501Rjh-P80S8db7hnrQ23mS_Jg27PwpVQVO77as,113
|
4
|
+
local_deep_research-0.5.3.dist-info/licenses/LICENSE,sha256=Qg2CaTdu6SWnSqk1_JtgBPp_Da-LdqJDhT1Vt1MUc5s,1072
|
5
5
|
local_deep_research/__init__.py,sha256=j1ktf_e9HeXPe86NHibY5aINtZfTSGRTvLNtz9BJZa4,1071
|
6
|
-
local_deep_research/__version__.py,sha256=
|
6
|
+
local_deep_research/__version__.py,sha256=tgzuqHKcEdKBaP57F5oXxq4XlW2n9J4Fj8ZGu7nGOZg,22
|
7
7
|
local_deep_research/advanced_search_system/__init__.py,sha256=sGusMj4eFIrhXR6QbOM16UDKB6aI-iS4IFivKWpMlh0,234
|
8
8
|
local_deep_research/advanced_search_system/answer_decoding/__init__.py,sha256=BmmbIPQnouYyboFD61CDq71fW5On555w7dbt42s9gV4,148
|
9
9
|
local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py,sha256=4FDMP4n_z5DOzVIisH3_kexRqNm1AO3MDe-Md3WtgE0,12856
|
@@ -135,11 +135,14 @@ local_deep_research/citation_handlers/forced_answer_citation_handler.py,sha256=o
|
|
135
135
|
local_deep_research/citation_handlers/precision_extraction_handler.py,sha256=wJkoWzu4ODAZRJoCH5WPZVD96UnsrfBDvW8cE4FCm_Q,19835
|
136
136
|
local_deep_research/citation_handlers/standard_citation_handler.py,sha256=PaJDvkwWOW6J4ZtA1VYSE78F4p3xiMtH1ZvdIBceyDQ,2987
|
137
137
|
local_deep_research/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
138
|
-
local_deep_research/config/llm_config.py,sha256=
|
138
|
+
local_deep_research/config/llm_config.py,sha256=_IIYxtdn9S3ry2OVrBX9ztLNg3frSp2vNqDcqhie-aY,21512
|
139
139
|
local_deep_research/config/search_config.py,sha256=DP7SbdcqGPu1yxv3fxtqd1vda5pVY3fH66DyDKuzJio,2209
|
140
140
|
local_deep_research/defaults/.env.template,sha256=_eVCy4d_XwpGXy8n50CG3wH9xx2oqJCFKS7IbqgInDk,491
|
141
141
|
local_deep_research/defaults/__init__.py,sha256=EUnb-wRT2mtv6lqM0hB16KfHJkR7d8JBNCIbEKwu48k,1342
|
142
142
|
local_deep_research/defaults/default_settings.json,sha256=0dfTFkplu7wnLJQYLBf4wKER55EmYbp_rMAJ5kgmUXc,124250
|
143
|
+
local_deep_research/error_handling/__init__.py,sha256=3kiEhchFP-gDeD_qM3lhtQqeVx7enqIwm_4hBKtohLg,396
|
144
|
+
local_deep_research/error_handling/error_reporter.py,sha256=tPBWUftpw6NWIcoRJJbaGaLApx7CQdcz3AppGDWKmnM,8583
|
145
|
+
local_deep_research/error_handling/report_generator.py,sha256=EQnKC2uENEWNAhOA2swJTxEG5CEwZDTHY4dlM1s4kM0,17303
|
143
146
|
local_deep_research/metrics/__init__.py,sha256=u1yUJndwNie8Pq3HGx2NxmFCVwIl0EvkxSxiMFpzSyM,331
|
144
147
|
local_deep_research/metrics/database.py,sha256=Nd6jTN__ZLM2lHld9sHT_tiVe3XNFgNeanFhIVuAVhw,1686
|
145
148
|
local_deep_research/metrics/db_models.py,sha256=X3ow1uXjjmZhdARE7MV37pQhr3UQknB1X5iWdTiJtSs,3464
|
@@ -159,7 +162,6 @@ local_deep_research/migrate_db.py,sha256=-Ql7bC2VmXlWtDOXj7CGVBvBlSk--wHyZCniWJn
|
|
159
162
|
local_deep_research/report_generator.py,sha256=J7Y7_soTAPkIajzbkIAPeq1ziTsXPMMz4Fwl6C-n6Zw,9282
|
160
163
|
local_deep_research/search_system.py,sha256=BpHhWXj82iVAJajpwyHaYyeBcNKbYY5DG2oxoNDERlo,27194
|
161
164
|
local_deep_research/setup_data_dir.py,sha256=7MJa2MMdDUnktJVHwMpyNL2079-qylpIyyLpVbF5AUY,1134
|
162
|
-
local_deep_research/test_migration.py,sha256=vzgGkY2EkmvQYJtUGD7Vt14i8jf04oCvNkzMnwuMBzY,6683
|
163
165
|
local_deep_research/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
164
166
|
local_deep_research/utilities/db_utils.py,sha256=BE3oP7WRbtlaqOxMrVFoAo11NpTk3RlC9w8tZUC_INU,1681
|
165
167
|
local_deep_research/utilities/enums.py,sha256=yFwmodt93uETdQd7qyW4vOUhiAzZF-BHBbVYHKN7scU,223
|
@@ -185,11 +187,11 @@ local_deep_research/web/models/settings.py,sha256=rXBI9vY5k3ndR8dPd3fZJy-6HwYltQ
|
|
185
187
|
local_deep_research/web/routes/api_routes.py,sha256=7hOFqF7Y3Qc5PwugXRoSi1CEVIcXUeW6nSwWayLB0P4,20052
|
186
188
|
local_deep_research/web/routes/benchmark_routes.py,sha256=bXiURrmWoaGtbOQ3jhgXgxjs3Qa440Jqk1CPZ1QMyO0,15725
|
187
189
|
local_deep_research/web/routes/globals.py,sha256=cCCSW0MPxHKNXDy0kGG5HqOz197i1iyqJdWRI72DCWw,454
|
188
|
-
local_deep_research/web/routes/history_routes.py,sha256=
|
190
|
+
local_deep_research/web/routes/history_routes.py,sha256=fFdZbMBWAQmtgTipWD1zNeRDhXRGAYiJhCmWrlWYaaY,12847
|
189
191
|
local_deep_research/web/routes/metrics_routes.py,sha256=b0fYdhH_OvyvMRjdswA41qPNV4DIWrZjoOnlysEBcAE,41619
|
190
192
|
local_deep_research/web/routes/research_routes.py,sha256=fVMV9WDO2T6XVVH1oU_bCXR1ekg6XNbnvkc61NB-DQs,27964
|
191
193
|
local_deep_research/web/routes/settings_routes.py,sha256=0VZPWSZ9-yQNFNdid_rC4jzqKI2sE0AjucuIstZx548,60600
|
192
|
-
local_deep_research/web/services/research_service.py,sha256=
|
194
|
+
local_deep_research/web/services/research_service.py,sha256=UXzq9Kx5t3fOv4IHKk2fP413XvEEwDzUapF5ehZzAIM,48027
|
193
195
|
local_deep_research/web/services/resource_service.py,sha256=aU7SDADxcIAAuIymL_TOxUV_HvEMAfL8gZSB5gVSzFM,4674
|
194
196
|
local_deep_research/web/services/settings_manager.py,sha256=EsAqZ5LTFFJcrWdCEheQYUqMWCwh7AUGe6Jefi8-Df4,19744
|
195
197
|
local_deep_research/web/services/settings_service.py,sha256=W3TdSb1_WTBdKVQEvi4swXBP99dMm2IT7H-5Y1weEH4,3535
|
@@ -204,9 +206,9 @@ local_deep_research/web/static/js/components/fallback/formatting.js,sha256=OoP38
|
|
204
206
|
local_deep_research/web/static/js/components/fallback/ui.js,sha256=O4fMeHXodvpFTEgSYR2TmcbL5Kyt2Hq1Tv2O7B3fDpk,7210
|
205
207
|
local_deep_research/web/static/js/components/history.js,sha256=TiE1R1U81L0bOuKhBgmB-9gS-66YNgmi_yIy-rG_5uY,15507
|
206
208
|
local_deep_research/web/static/js/components/logpanel.js,sha256=IiTpKPSEn7KtKT4xh9X6FcMZWfloj5eTDa8Oday2lhs,42808
|
207
|
-
local_deep_research/web/static/js/components/progress.js,sha256=
|
209
|
+
local_deep_research/web/static/js/components/progress.js,sha256=8cydtosQZpdX2MTpYEq9GPARCWV5azKMaRHxYiDvJoM,42542
|
208
210
|
local_deep_research/web/static/js/components/research.js,sha256=EGWDdpswJnvH3GNlmp5albNzMv6-_20uF2D0qB73-zo,86577
|
209
|
-
local_deep_research/web/static/js/components/results.js,sha256=
|
211
|
+
local_deep_research/web/static/js/components/results.js,sha256=3kDk0kodglBqIODh_ve0I9ERIZkITz2yB5isUUda-bc,35763
|
210
212
|
local_deep_research/web/static/js/components/settings.js,sha256=qnRxnx9LdyOpjIEKfb4mJdES9sLkNKvyzxy4OGUqmGQ,169998
|
211
213
|
local_deep_research/web/static/js/components/settings_sync.js,sha256=LWDZ2EE8ChCxI5TPmPm9F4rOiYIEzEJxSCE1GLXk-2w,3925
|
212
214
|
local_deep_research/web/static/js/main.js,sha256=z7gJvwL6A7xqAYloen8uoshdOLeRWohM57xsfrCDUuI,8143
|
@@ -232,7 +234,7 @@ local_deep_research/web/templates/pages/details.html,sha256=Led51_cv97e_Z057_7QV
|
|
232
234
|
local_deep_research/web/templates/pages/history.html,sha256=dGeXmpp1WTsd3Em_8k1cscwOJEPO_7puu04KC966QhA,1982
|
233
235
|
local_deep_research/web/templates/pages/metrics.html,sha256=IYG08UNnWAATqCDh0fRskJONKjHTLGgLWcrztiAm0Yg,81507
|
234
236
|
local_deep_research/web/templates/pages/progress.html,sha256=iRKGkgyo9wBKrYAiwk61MbnaDUxeQf8SGP3wGoHWteQ,2546
|
235
|
-
local_deep_research/web/templates/pages/research.html,sha256=
|
237
|
+
local_deep_research/web/templates/pages/research.html,sha256=Nn_LatiSZfKxa-Oq7z1WaV65GfPHu35YvYJDlHiTDs0,9634
|
236
238
|
local_deep_research/web/templates/pages/results.html,sha256=n5tHESU5iRcjwjga4y589r41B9NEqHIvzpMsXlRL0vw,3591
|
237
239
|
local_deep_research/web/templates/pages/star_reviews.html,sha256=Bg3cGwRjNBBSvC9ZoqZqjY-6_0-cM4VZHRGlTai_Wng,24361
|
238
240
|
local_deep_research/web/templates/settings_dashboard.html,sha256=A1fBe1D29v8G3ys94_rIiBP6Ky-rtYL6TRbDluoBMPI,4342
|
@@ -262,4 +264,4 @@ local_deep_research/web_search_engines/engines/search_engine_wikipedia.py,sha256
|
|
262
264
|
local_deep_research/web_search_engines/search_engine_base.py,sha256=sRgtszDM9RqNw_oVdmGk8CmKS_9EJYR-LyE1as53cp8,12401
|
263
265
|
local_deep_research/web_search_engines/search_engine_factory.py,sha256=eMaFup2p4u1nP4fTmjzfLUAl_mUZkoE1mUABBIvNzDM,12095
|
264
266
|
local_deep_research/web_search_engines/search_engines_config.py,sha256=oJ5GL9BhFvWFgmFtvwJ7AZ9o-uPLEfTNhJJouHF40iA,5296
|
265
|
-
local_deep_research-0.5.
|
267
|
+
local_deep_research-0.5.3.dist-info/RECORD,,
|
@@ -1,188 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python
|
2
|
-
"""
|
3
|
-
Migration test script for Local Deep Research.
|
4
|
-
This script checks the contents of both the legacy and new databases to diagnose migration issues.
|
5
|
-
"""
|
6
|
-
|
7
|
-
import os
|
8
|
-
import sqlite3
|
9
|
-
import sys
|
10
|
-
import time
|
11
|
-
|
12
|
-
|
13
|
-
def check_db_content(db_path, description):
|
14
|
-
"""Check what tables and how many rows are in a database."""
|
15
|
-
if not os.path.exists(db_path):
|
16
|
-
print(f"❌ {description} database not found at: {db_path}")
|
17
|
-
return False
|
18
|
-
|
19
|
-
print(f"📊 Examining {description} database at: {db_path}")
|
20
|
-
try:
|
21
|
-
conn = sqlite3.connect(db_path)
|
22
|
-
cursor = conn.cursor()
|
23
|
-
|
24
|
-
# Get list of tables
|
25
|
-
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
26
|
-
tables = [
|
27
|
-
row[0]
|
28
|
-
for row in cursor.fetchall()
|
29
|
-
if not row[0].startswith("sqlite_")
|
30
|
-
]
|
31
|
-
|
32
|
-
if not tables:
|
33
|
-
print(" ℹ️ No user tables found in database")
|
34
|
-
conn.close()
|
35
|
-
return False
|
36
|
-
|
37
|
-
print(f" 📋 Tables found: {', '.join(tables)}")
|
38
|
-
|
39
|
-
# For each table, count rows
|
40
|
-
for table in tables:
|
41
|
-
cursor.execute(f"SELECT COUNT(*) FROM {table}")
|
42
|
-
count = cursor.fetchone()[0]
|
43
|
-
print(f" 📝 Table '{table}' has {count} rows")
|
44
|
-
|
45
|
-
# If table has rows, show sample
|
46
|
-
if count > 0:
|
47
|
-
cursor.execute(f"SELECT * FROM {table} LIMIT 1")
|
48
|
-
columns = [description[0] for description in cursor.description]
|
49
|
-
print(f" Columns: {', '.join(columns)}")
|
50
|
-
|
51
|
-
# For specific tables, get key columns
|
52
|
-
if table in [
|
53
|
-
"research_history",
|
54
|
-
"research_logs",
|
55
|
-
"research",
|
56
|
-
"settings",
|
57
|
-
]:
|
58
|
-
key_cols = (
|
59
|
-
"id, query, status"
|
60
|
-
if table == "research_history"
|
61
|
-
else "id, key, value"
|
62
|
-
if table == "settings"
|
63
|
-
else "id, message"
|
64
|
-
)
|
65
|
-
cursor.execute(f"SELECT {key_cols} FROM {table} LIMIT 3")
|
66
|
-
sample = cursor.fetchall()
|
67
|
-
for row in sample:
|
68
|
-
print(f" Sample data: {row}")
|
69
|
-
|
70
|
-
conn.close()
|
71
|
-
return True
|
72
|
-
except Exception as e:
|
73
|
-
print(f"❌ Error examining database: {e}")
|
74
|
-
return False
|
75
|
-
|
76
|
-
|
77
|
-
def main():
|
78
|
-
"""Main function to test the migration."""
|
79
|
-
# Import necessary constants
|
80
|
-
try:
|
81
|
-
# Set up paths
|
82
|
-
current_dir = os.path.dirname(os.path.abspath(__file__))
|
83
|
-
project_root = os.path.abspath(os.path.join(current_dir, "..", ".."))
|
84
|
-
|
85
|
-
# Determine paths
|
86
|
-
data_dir = os.path.join(project_root, "data")
|
87
|
-
new_db_path = os.path.join(data_dir, "ldr.db")
|
88
|
-
|
89
|
-
legacy_research_history_db = os.path.join(
|
90
|
-
project_root, "src", "local_deep_research", "research_history.db"
|
91
|
-
)
|
92
|
-
legacy_deep_research_db = os.path.join(data_dir, "deep_research.db")
|
93
|
-
|
94
|
-
# Print paths for verification
|
95
|
-
print("=" * 60)
|
96
|
-
print("DATABASE PATHS")
|
97
|
-
print("=" * 60)
|
98
|
-
print(f"New database path: {new_db_path}")
|
99
|
-
print(f"Legacy research history DB: {legacy_research_history_db}")
|
100
|
-
print(f"Legacy deep research DB: {legacy_deep_research_db}")
|
101
|
-
print("=" * 60)
|
102
|
-
|
103
|
-
# Check all databases
|
104
|
-
check_db_content(legacy_research_history_db, "Legacy research_history")
|
105
|
-
check_db_content(legacy_deep_research_db, "Legacy deep_research")
|
106
|
-
|
107
|
-
# Now check for the new database or create it if needed
|
108
|
-
if os.path.exists(new_db_path):
|
109
|
-
check_db_content(new_db_path, "New ldr")
|
110
|
-
else:
|
111
|
-
print(f"ℹ️ New database doesn't exist yet at: {new_db_path}")
|
112
|
-
print("Would you like to run a test migration? (y/n)")
|
113
|
-
choice = input("> ").lower()
|
114
|
-
if choice == "y":
|
115
|
-
# Run the migration script directly
|
116
|
-
try:
|
117
|
-
from src.local_deep_research.setup_data_dir import (
|
118
|
-
setup_data_dir,
|
119
|
-
)
|
120
|
-
except ImportError:
|
121
|
-
# If that fails, try with the direct import
|
122
|
-
sys.path.append(
|
123
|
-
os.path.dirname(
|
124
|
-
os.path.dirname(os.path.abspath(__file__))
|
125
|
-
)
|
126
|
-
)
|
127
|
-
from local_deep_research.setup_data_dir import (
|
128
|
-
setup_data_dir,
|
129
|
-
)
|
130
|
-
|
131
|
-
setup_data_dir()
|
132
|
-
|
133
|
-
# Import migration function
|
134
|
-
try:
|
135
|
-
from src.local_deep_research.web.database.migrate_to_ldr_db import (
|
136
|
-
migrate_to_ldr_db,
|
137
|
-
)
|
138
|
-
except ImportError:
|
139
|
-
# If that fails, try with the direct import
|
140
|
-
from local_deep_research.web.database.migrate_to_ldr_db import (
|
141
|
-
migrate_to_ldr_db,
|
142
|
-
)
|
143
|
-
|
144
|
-
print("Running migration...")
|
145
|
-
success = migrate_to_ldr_db()
|
146
|
-
|
147
|
-
# Wait briefly to ensure file system has time to update
|
148
|
-
time.sleep(1)
|
149
|
-
|
150
|
-
if success:
|
151
|
-
print("\n✅ Migration completed. Checking new database:")
|
152
|
-
check_db_content(new_db_path, "New ldr")
|
153
|
-
else:
|
154
|
-
print("❌ Migration failed")
|
155
|
-
|
156
|
-
# Get the paths from the migration script to verify
|
157
|
-
try:
|
158
|
-
try:
|
159
|
-
from src.local_deep_research.web.models.database import (
|
160
|
-
DB_PATH,
|
161
|
-
LEGACY_DEEP_RESEARCH_DB,
|
162
|
-
LEGACY_RESEARCH_HISTORY_DB,
|
163
|
-
)
|
164
|
-
except ImportError:
|
165
|
-
from local_deep_research.web.models.database import (
|
166
|
-
DB_PATH,
|
167
|
-
LEGACY_DEEP_RESEARCH_DB,
|
168
|
-
LEGACY_RESEARCH_HISTORY_DB,
|
169
|
-
)
|
170
|
-
|
171
|
-
print("\n" + "=" * 60)
|
172
|
-
print("PATHS FROM DATABASE MODULE")
|
173
|
-
print("=" * 60)
|
174
|
-
print(f"DB_PATH: {DB_PATH}")
|
175
|
-
print(f"LEGACY_RESEARCH_HISTORY_DB: {LEGACY_RESEARCH_HISTORY_DB}")
|
176
|
-
print(f"LEGACY_DEEP_RESEARCH_DB: {LEGACY_DEEP_RESEARCH_DB}")
|
177
|
-
except ImportError as e:
|
178
|
-
print(f"Could not import paths from database module: {e}")
|
179
|
-
|
180
|
-
except Exception as e:
|
181
|
-
print(f"Error in test script: {e}")
|
182
|
-
return 1
|
183
|
-
|
184
|
-
return 0
|
185
|
-
|
186
|
-
|
187
|
-
if __name__ == "__main__":
|
188
|
-
sys.exit(main())
|
File without changes
|
{local_deep_research-0.5.2.dist-info → local_deep_research-0.5.3.dist-info}/entry_points.txt
RENAMED
File without changes
|
{local_deep_research-0.5.2.dist-info → local_deep_research-0.5.3.dist-info}/licenses/LICENSE
RENAMED
File without changes
|