local-deep-research 0.1.26__py3-none-any.whl → 0.2.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 +23 -22
- local_deep_research/__main__.py +16 -0
- local_deep_research/advanced_search_system/__init__.py +7 -0
- local_deep_research/advanced_search_system/filters/__init__.py +8 -0
- local_deep_research/advanced_search_system/filters/base_filter.py +38 -0
- local_deep_research/advanced_search_system/filters/cross_engine_filter.py +200 -0
- local_deep_research/advanced_search_system/findings/base_findings.py +81 -0
- local_deep_research/advanced_search_system/findings/repository.py +452 -0
- local_deep_research/advanced_search_system/knowledge/__init__.py +1 -0
- local_deep_research/advanced_search_system/knowledge/base_knowledge.py +151 -0
- local_deep_research/advanced_search_system/knowledge/standard_knowledge.py +159 -0
- local_deep_research/advanced_search_system/questions/__init__.py +1 -0
- local_deep_research/advanced_search_system/questions/base_question.py +64 -0
- local_deep_research/advanced_search_system/questions/decomposition_question.py +445 -0
- local_deep_research/advanced_search_system/questions/standard_question.py +119 -0
- local_deep_research/advanced_search_system/repositories/__init__.py +7 -0
- local_deep_research/advanced_search_system/strategies/__init__.py +1 -0
- local_deep_research/advanced_search_system/strategies/base_strategy.py +118 -0
- local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +450 -0
- local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +312 -0
- local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +270 -0
- local_deep_research/advanced_search_system/strategies/standard_strategy.py +300 -0
- local_deep_research/advanced_search_system/tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/base_tool.py +100 -0
- local_deep_research/advanced_search_system/tools/knowledge_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/question_tools/__init__.py +1 -0
- local_deep_research/advanced_search_system/tools/search_tools/__init__.py +1 -0
- local_deep_research/api/__init__.py +5 -5
- local_deep_research/api/research_functions.py +154 -160
- local_deep_research/app.py +8 -0
- local_deep_research/citation_handler.py +25 -16
- local_deep_research/{config.py → config/config_files.py} +102 -110
- local_deep_research/config/llm_config.py +472 -0
- local_deep_research/config/search_config.py +77 -0
- local_deep_research/defaults/__init__.py +10 -5
- local_deep_research/defaults/main.toml +2 -2
- local_deep_research/defaults/search_engines.toml +60 -34
- local_deep_research/main.py +121 -19
- local_deep_research/migrate_db.py +147 -0
- local_deep_research/report_generator.py +87 -45
- local_deep_research/search_system.py +153 -283
- local_deep_research/setup_data_dir.py +35 -0
- local_deep_research/test_migration.py +178 -0
- local_deep_research/utilities/__init__.py +0 -0
- local_deep_research/utilities/db_utils.py +49 -0
- local_deep_research/{utilties → utilities}/enums.py +2 -2
- local_deep_research/{utilties → utilities}/llm_utils.py +63 -29
- local_deep_research/utilities/search_utilities.py +242 -0
- local_deep_research/{utilties → utilities}/setup_utils.py +4 -2
- local_deep_research/web/__init__.py +0 -1
- local_deep_research/web/app.py +86 -1709
- local_deep_research/web/app_factory.py +289 -0
- local_deep_research/web/database/README.md +70 -0
- local_deep_research/web/database/migrate_to_ldr_db.py +289 -0
- local_deep_research/web/database/migrations.py +447 -0
- local_deep_research/web/database/models.py +117 -0
- local_deep_research/web/database/schema_upgrade.py +107 -0
- local_deep_research/web/models/database.py +294 -0
- local_deep_research/web/models/settings.py +94 -0
- local_deep_research/web/routes/api_routes.py +559 -0
- local_deep_research/web/routes/history_routes.py +354 -0
- local_deep_research/web/routes/research_routes.py +715 -0
- local_deep_research/web/routes/settings_routes.py +1583 -0
- local_deep_research/web/services/research_service.py +947 -0
- local_deep_research/web/services/resource_service.py +149 -0
- local_deep_research/web/services/settings_manager.py +669 -0
- local_deep_research/web/services/settings_service.py +187 -0
- local_deep_research/web/services/socket_service.py +210 -0
- local_deep_research/web/static/css/custom_dropdown.css +277 -0
- local_deep_research/web/static/css/settings.css +1223 -0
- local_deep_research/web/static/css/styles.css +525 -48
- local_deep_research/web/static/js/components/custom_dropdown.js +428 -0
- local_deep_research/web/static/js/components/detail.js +348 -0
- local_deep_research/web/static/js/components/fallback/formatting.js +122 -0
- local_deep_research/web/static/js/components/fallback/ui.js +215 -0
- local_deep_research/web/static/js/components/history.js +487 -0
- local_deep_research/web/static/js/components/logpanel.js +949 -0
- local_deep_research/web/static/js/components/progress.js +1107 -0
- local_deep_research/web/static/js/components/research.js +1865 -0
- local_deep_research/web/static/js/components/results.js +766 -0
- local_deep_research/web/static/js/components/settings.js +3981 -0
- local_deep_research/web/static/js/components/settings_sync.js +106 -0
- local_deep_research/web/static/js/main.js +226 -0
- local_deep_research/web/static/js/services/api.js +253 -0
- local_deep_research/web/static/js/services/audio.js +31 -0
- local_deep_research/web/static/js/services/formatting.js +119 -0
- local_deep_research/web/static/js/services/pdf.js +622 -0
- local_deep_research/web/static/js/services/socket.js +882 -0
- local_deep_research/web/static/js/services/ui.js +546 -0
- local_deep_research/web/templates/base.html +72 -0
- local_deep_research/web/templates/components/custom_dropdown.html +47 -0
- local_deep_research/web/templates/components/log_panel.html +32 -0
- local_deep_research/web/templates/components/mobile_nav.html +22 -0
- local_deep_research/web/templates/components/settings_form.html +299 -0
- local_deep_research/web/templates/components/sidebar.html +21 -0
- local_deep_research/web/templates/pages/details.html +73 -0
- local_deep_research/web/templates/pages/history.html +51 -0
- local_deep_research/web/templates/pages/progress.html +57 -0
- local_deep_research/web/templates/pages/research.html +139 -0
- local_deep_research/web/templates/pages/results.html +59 -0
- local_deep_research/web/templates/settings_dashboard.html +78 -192
- local_deep_research/web/utils/__init__.py +0 -0
- local_deep_research/web/utils/formatters.py +76 -0
- local_deep_research/web_search_engines/engines/full_search.py +18 -16
- local_deep_research/web_search_engines/engines/meta_search_engine.py +182 -131
- local_deep_research/web_search_engines/engines/search_engine_arxiv.py +224 -139
- local_deep_research/web_search_engines/engines/search_engine_brave.py +88 -71
- local_deep_research/web_search_engines/engines/search_engine_ddg.py +48 -39
- local_deep_research/web_search_engines/engines/search_engine_github.py +415 -204
- local_deep_research/web_search_engines/engines/search_engine_google_pse.py +123 -90
- local_deep_research/web_search_engines/engines/search_engine_guardian.py +210 -157
- local_deep_research/web_search_engines/engines/search_engine_local.py +532 -369
- local_deep_research/web_search_engines/engines/search_engine_local_all.py +42 -36
- local_deep_research/web_search_engines/engines/search_engine_pubmed.py +358 -266
- local_deep_research/web_search_engines/engines/search_engine_searxng.py +212 -160
- local_deep_research/web_search_engines/engines/search_engine_semantic_scholar.py +213 -170
- local_deep_research/web_search_engines/engines/search_engine_serpapi.py +84 -68
- local_deep_research/web_search_engines/engines/search_engine_wayback.py +186 -154
- local_deep_research/web_search_engines/engines/search_engine_wikipedia.py +115 -77
- local_deep_research/web_search_engines/search_engine_base.py +174 -99
- local_deep_research/web_search_engines/search_engine_factory.py +192 -102
- local_deep_research/web_search_engines/search_engines_config.py +22 -15
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/METADATA +177 -97
- local_deep_research-0.2.2.dist-info/RECORD +135 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/WHEEL +1 -2
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/entry_points.txt +3 -0
- local_deep_research/defaults/llm_config.py +0 -338
- local_deep_research/utilties/search_utilities.py +0 -114
- local_deep_research/web/static/js/app.js +0 -3763
- local_deep_research/web/templates/api_keys_config.html +0 -82
- local_deep_research/web/templates/collections_config.html +0 -90
- local_deep_research/web/templates/index.html +0 -348
- local_deep_research/web/templates/llm_config.html +0 -120
- local_deep_research/web/templates/main_config.html +0 -89
- local_deep_research/web/templates/search_engines_config.html +0 -154
- local_deep_research/web/templates/settings.html +0 -519
- local_deep_research-0.1.26.dist-info/RECORD +0 -61
- local_deep_research-0.1.26.dist-info/top_level.txt +0 -1
- /local_deep_research/{utilties → config}/__init__.py +0 -0
- {local_deep_research-0.1.26.dist-info → local_deep_research-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,472 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
from dynaconf.vendor.box.exceptions import BoxKeyError
|
6
|
+
from langchain_anthropic import ChatAnthropic
|
7
|
+
from langchain_community.llms import VLLM
|
8
|
+
from langchain_ollama import ChatOllama
|
9
|
+
from langchain_openai import ChatOpenAI
|
10
|
+
|
11
|
+
from ..utilities.db_utils import get_db_setting
|
12
|
+
from ..utilities.search_utilities import remove_think_tags
|
13
|
+
from .config_files import CONFIG_DIR, settings
|
14
|
+
|
15
|
+
# Setup logging
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
# Valid provider options
|
19
|
+
VALID_PROVIDERS = [
|
20
|
+
"ollama",
|
21
|
+
"openai",
|
22
|
+
"anthropic",
|
23
|
+
"vllm",
|
24
|
+
"openai_endpoint",
|
25
|
+
"lmstudio",
|
26
|
+
"llamacpp",
|
27
|
+
"none",
|
28
|
+
]
|
29
|
+
SECRETS_FILE = CONFIG_DIR / ".secrets.toml"
|
30
|
+
|
31
|
+
|
32
|
+
def get_llm(model_name=None, temperature=None, provider=None, openai_endpoint_url=None):
|
33
|
+
"""
|
34
|
+
Get LLM instance based on model name and provider.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
model_name: Name of the model to use (if None, uses database setting)
|
38
|
+
temperature: Model temperature (if None, uses database setting)
|
39
|
+
provider: Provider to use (if None, uses database setting)
|
40
|
+
openai_endpoint_url: Custom endpoint URL to use (if None, uses database
|
41
|
+
setting)
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
A LangChain LLM instance with automatic think-tag removal
|
45
|
+
"""
|
46
|
+
|
47
|
+
# Use database values for parameters if not provided
|
48
|
+
if model_name is None:
|
49
|
+
model_name = get_db_setting("llm.model", settings.llm.model)
|
50
|
+
if temperature is None:
|
51
|
+
temperature = get_db_setting("llm.temperature", settings.llm.temperature)
|
52
|
+
if provider is None:
|
53
|
+
provider = get_db_setting("llm.provider", settings.llm.provider)
|
54
|
+
|
55
|
+
# Clean model name: remove quotes and extra whitespace
|
56
|
+
if model_name:
|
57
|
+
model_name = model_name.strip().strip("\"'").strip()
|
58
|
+
|
59
|
+
# Clean provider: remove quotes and extra whitespace
|
60
|
+
if provider:
|
61
|
+
provider = provider.strip().strip("\"'").strip()
|
62
|
+
|
63
|
+
# Normalize provider: convert to lowercase
|
64
|
+
provider = provider.lower() if provider else None
|
65
|
+
|
66
|
+
# Validate provider
|
67
|
+
if provider not in VALID_PROVIDERS:
|
68
|
+
logger.error(f"Invalid provider in settings: {provider}")
|
69
|
+
raise ValueError(
|
70
|
+
f"Invalid provider: {provider}. Must be one of: {VALID_PROVIDERS}"
|
71
|
+
)
|
72
|
+
print(
|
73
|
+
f"Getting LLM with model: {model_name}, temperature: {temperature}, provider: {provider}"
|
74
|
+
)
|
75
|
+
|
76
|
+
# Common parameters for all models
|
77
|
+
common_params = {
|
78
|
+
"temperature": temperature,
|
79
|
+
}
|
80
|
+
try:
|
81
|
+
common_params["max_tokens"] = settings.llm.max_tokens
|
82
|
+
except BoxKeyError:
|
83
|
+
# Some providers don't support this parameter, in which case it can
|
84
|
+
# be omitted.
|
85
|
+
pass
|
86
|
+
|
87
|
+
# Handle different providers
|
88
|
+
if provider == "anthropic":
|
89
|
+
api_key_name = "ANTHROPIC_API_KEY"
|
90
|
+
api_key = settings.get(api_key_name, "")
|
91
|
+
if not api_key:
|
92
|
+
api_key = os.getenv(api_key_name)
|
93
|
+
if not api_key:
|
94
|
+
api_key = os.getenv("LDR_" + api_key_name)
|
95
|
+
if not api_key:
|
96
|
+
logger.warning(
|
97
|
+
"ANTHROPIC_API_KEY not found. Falling back to default model."
|
98
|
+
)
|
99
|
+
return get_fallback_model(temperature)
|
100
|
+
|
101
|
+
llm = ChatAnthropic(
|
102
|
+
model=model_name, anthropic_api_key=api_key, **common_params
|
103
|
+
)
|
104
|
+
return wrap_llm_without_think_tags(llm)
|
105
|
+
|
106
|
+
elif provider == "openai":
|
107
|
+
api_key_name = "OPENAI_API_KEY"
|
108
|
+
api_key = settings.get(api_key_name, "")
|
109
|
+
if not api_key:
|
110
|
+
api_key = os.getenv(api_key_name)
|
111
|
+
if not api_key:
|
112
|
+
api_key = os.getenv("LDR_" + api_key_name)
|
113
|
+
if not api_key:
|
114
|
+
logger.warning("OPENAI_API_KEY not found. Falling back to default model.")
|
115
|
+
return get_fallback_model(temperature)
|
116
|
+
|
117
|
+
llm = ChatOpenAI(model=model_name, api_key=api_key, **common_params)
|
118
|
+
return wrap_llm_without_think_tags(llm)
|
119
|
+
|
120
|
+
elif provider == "openai_endpoint":
|
121
|
+
api_key_name = "OPENAI_ENDPOINT_API_KEY"
|
122
|
+
api_key = settings.get(api_key_name, "")
|
123
|
+
if not api_key:
|
124
|
+
api_key = os.getenv(api_key_name)
|
125
|
+
if not api_key:
|
126
|
+
api_key = os.getenv("LDR_" + api_key_name)
|
127
|
+
if not api_key:
|
128
|
+
logger.warning(
|
129
|
+
"OPENAI_ENDPOINT_API_KEY not found. Falling back to default model."
|
130
|
+
)
|
131
|
+
return get_fallback_model(temperature)
|
132
|
+
|
133
|
+
# Get endpoint URL from settings
|
134
|
+
if openai_endpoint_url is not None:
|
135
|
+
openai_endpoint_url = get_db_setting(
|
136
|
+
"llm.openai_endpoint_url", settings.llm.openai_endpoint_url
|
137
|
+
)
|
138
|
+
|
139
|
+
llm = ChatOpenAI(
|
140
|
+
model=model_name,
|
141
|
+
api_key=api_key,
|
142
|
+
openai_api_base=openai_endpoint_url,
|
143
|
+
**common_params,
|
144
|
+
)
|
145
|
+
return wrap_llm_without_think_tags(llm)
|
146
|
+
|
147
|
+
elif provider == "vllm":
|
148
|
+
try:
|
149
|
+
llm = VLLM(
|
150
|
+
model=model_name,
|
151
|
+
trust_remote_code=True,
|
152
|
+
max_new_tokens=128,
|
153
|
+
top_k=10,
|
154
|
+
top_p=0.95,
|
155
|
+
temperature=temperature,
|
156
|
+
)
|
157
|
+
return wrap_llm_without_think_tags(llm)
|
158
|
+
except Exception as e:
|
159
|
+
logger.error(f"Error loading VLLM model: {e}")
|
160
|
+
logger.warning("Falling back.")
|
161
|
+
return get_fallback_model(temperature)
|
162
|
+
|
163
|
+
elif provider == "ollama":
|
164
|
+
try:
|
165
|
+
# Use the configurable Ollama base URL
|
166
|
+
base_url = os.getenv(
|
167
|
+
"OLLAMA_BASE_URL",
|
168
|
+
settings.llm.get("ollama_base_url", "http://localhost:11434"),
|
169
|
+
)
|
170
|
+
|
171
|
+
# Check if Ollama is available before trying to use it
|
172
|
+
if not is_ollama_available():
|
173
|
+
logger.error(
|
174
|
+
f"Ollama not available at {base_url}. Falling back to dummy model."
|
175
|
+
)
|
176
|
+
return get_fallback_model(temperature)
|
177
|
+
|
178
|
+
# Check if the requested model exists
|
179
|
+
import requests
|
180
|
+
|
181
|
+
try:
|
182
|
+
logger.info(f"Checking if model '{model_name}' exists in Ollama")
|
183
|
+
response = requests.get(f"{base_url}/api/tags", timeout=3.0)
|
184
|
+
if response.status_code == 200:
|
185
|
+
# Handle both newer and older Ollama API formats
|
186
|
+
data = response.json()
|
187
|
+
models = []
|
188
|
+
if "models" in data:
|
189
|
+
# Newer Ollama API
|
190
|
+
models = data.get("models", [])
|
191
|
+
else:
|
192
|
+
# Older Ollama API format
|
193
|
+
models = data
|
194
|
+
|
195
|
+
# Get list of model names
|
196
|
+
model_names = [m.get("name", "").lower() for m in models]
|
197
|
+
logger.info(
|
198
|
+
f"Available Ollama models: {', '.join(model_names[:5])}{' and more' if len(model_names) > 5 else ''}"
|
199
|
+
)
|
200
|
+
|
201
|
+
if model_name.lower() not in model_names:
|
202
|
+
logger.error(
|
203
|
+
f"Model '{model_name}' not found in Ollama. Available models: {', '.join(model_names[:5])}"
|
204
|
+
)
|
205
|
+
return get_fallback_model(temperature)
|
206
|
+
except Exception as model_check_error:
|
207
|
+
logger.error(
|
208
|
+
f"Error checking for model '{model_name}' in Ollama: {str(model_check_error)}"
|
209
|
+
)
|
210
|
+
# Continue anyway, let ChatOllama handle potential errors
|
211
|
+
|
212
|
+
logger.info(
|
213
|
+
f"Creating ChatOllama with model={model_name}, base_url={base_url}"
|
214
|
+
)
|
215
|
+
try:
|
216
|
+
llm = ChatOllama(model=model_name, base_url=base_url, **common_params)
|
217
|
+
# Test invoke to validate model works
|
218
|
+
logger.info("Testing Ollama model with simple invocation")
|
219
|
+
test_result = llm.invoke("Hello")
|
220
|
+
logger.info(
|
221
|
+
f"Ollama test successful. Response type: {type(test_result)}"
|
222
|
+
)
|
223
|
+
return wrap_llm_without_think_tags(llm)
|
224
|
+
except Exception as chat_error:
|
225
|
+
logger.error(f"Error creating or testing ChatOllama: {str(chat_error)}")
|
226
|
+
return get_fallback_model(temperature)
|
227
|
+
except Exception as e:
|
228
|
+
logger.error(f"Error in Ollama provider section: {str(e)}")
|
229
|
+
return get_fallback_model(temperature)
|
230
|
+
|
231
|
+
elif provider == "lmstudio":
|
232
|
+
# LM Studio supports OpenAI API format, so we can use ChatOpenAI directly
|
233
|
+
lmstudio_url = settings.llm.get("lmstudio_url", "http://localhost:1234")
|
234
|
+
lmstudio_url = get_db_setting("llm.lmstudio_url", lmstudio_url)
|
235
|
+
|
236
|
+
llm = ChatOpenAI(
|
237
|
+
model=model_name,
|
238
|
+
api_key="lm-studio", # LM Studio doesn't require a real API key
|
239
|
+
base_url=f"{lmstudio_url}/v1", # Use the configured URL with /v1 endpoint
|
240
|
+
temperature=temperature,
|
241
|
+
max_tokens=get_db_setting("llm.max_tokens", settings.llm.max_tokens),
|
242
|
+
)
|
243
|
+
return wrap_llm_without_think_tags(llm)
|
244
|
+
|
245
|
+
elif provider == "llamacpp":
|
246
|
+
# Import LlamaCpp
|
247
|
+
from langchain_community.llms import LlamaCpp
|
248
|
+
|
249
|
+
# Get LlamaCpp model path from settings
|
250
|
+
model_path = settings.llm.get("llamacpp_model_path", "")
|
251
|
+
model_path = get_db_setting("llm.llamacpp_model_path", model_path)
|
252
|
+
if not model_path:
|
253
|
+
logger.error("llamacpp_model_path not set in settings")
|
254
|
+
raise ValueError("llamacpp_model_path not set in settings")
|
255
|
+
|
256
|
+
# Get additional LlamaCpp parameters
|
257
|
+
n_gpu_layers = settings.llm.get("llamacpp_n_gpu_layers", 1)
|
258
|
+
n_gpu_layers = get_db_setting("llm.llamacpp_n_gpu_layers", n_gpu_layers)
|
259
|
+
n_batch = settings.llm.get("llamacpp_n_batch", 512)
|
260
|
+
n_batch = get_db_setting("llm.llamacpp_n_batch", n_batch)
|
261
|
+
f16_kv = settings.llm.get("llamacpp_f16_kv", True)
|
262
|
+
f16_kv = get_db_setting("llm.llamacpp_f16_kv", f16_kv)
|
263
|
+
|
264
|
+
# Create LlamaCpp instance
|
265
|
+
llm = LlamaCpp(
|
266
|
+
model_path=model_path,
|
267
|
+
temperature=temperature,
|
268
|
+
max_tokens=get_db_setting("llm.max_tokens", settings.llm.max_tokens),
|
269
|
+
n_gpu_layers=n_gpu_layers,
|
270
|
+
n_batch=n_batch,
|
271
|
+
f16_kv=f16_kv,
|
272
|
+
verbose=True,
|
273
|
+
)
|
274
|
+
return wrap_llm_without_think_tags(llm)
|
275
|
+
|
276
|
+
else:
|
277
|
+
return wrap_llm_without_think_tags(get_fallback_model(temperature))
|
278
|
+
|
279
|
+
|
280
|
+
def get_fallback_model(temperature=None):
|
281
|
+
"""Create a dummy model for when no providers are available"""
|
282
|
+
from langchain_community.llms.fake import FakeListLLM
|
283
|
+
|
284
|
+
return FakeListLLM(
|
285
|
+
responses=[
|
286
|
+
"No language models are available. Please install Ollama or set up API keys."
|
287
|
+
]
|
288
|
+
)
|
289
|
+
|
290
|
+
|
291
|
+
def wrap_llm_without_think_tags(llm):
|
292
|
+
"""Create a wrapper class that processes LLM outputs with remove_think_tags"""
|
293
|
+
|
294
|
+
class ProcessingLLMWrapper:
|
295
|
+
def __init__(self, base_llm):
|
296
|
+
self.base_llm = base_llm
|
297
|
+
|
298
|
+
def invoke(self, *args, **kwargs):
|
299
|
+
response = self.base_llm.invoke(*args, **kwargs)
|
300
|
+
|
301
|
+
# Process the response content if it has a content attribute
|
302
|
+
if hasattr(response, "content"):
|
303
|
+
response.content = remove_think_tags(response.content)
|
304
|
+
elif isinstance(response, str):
|
305
|
+
response = remove_think_tags(response)
|
306
|
+
|
307
|
+
return response
|
308
|
+
|
309
|
+
# Pass through any other attributes to the base LLM
|
310
|
+
def __getattr__(self, name):
|
311
|
+
return getattr(self.base_llm, name)
|
312
|
+
|
313
|
+
return ProcessingLLMWrapper(llm)
|
314
|
+
|
315
|
+
|
316
|
+
def get_available_provider_types():
|
317
|
+
"""Return available model providers"""
|
318
|
+
providers = {}
|
319
|
+
|
320
|
+
if is_ollama_available():
|
321
|
+
providers["ollama"] = "Ollama (local models)"
|
322
|
+
|
323
|
+
if is_openai_available():
|
324
|
+
providers["openai"] = "OpenAI API"
|
325
|
+
|
326
|
+
if is_anthropic_available():
|
327
|
+
providers["anthropic"] = "Anthropic API"
|
328
|
+
|
329
|
+
if is_openai_endpoint_available():
|
330
|
+
providers["openai_endpoint"] = "OpenAI-compatible Endpoint"
|
331
|
+
|
332
|
+
if is_lmstudio_available():
|
333
|
+
providers["lmstudio"] = "LM Studio (local models)"
|
334
|
+
|
335
|
+
if is_llamacpp_available():
|
336
|
+
providers["llamacpp"] = "LlamaCpp (local models)"
|
337
|
+
|
338
|
+
# Check for VLLM capability
|
339
|
+
try:
|
340
|
+
import torch # noqa: F401
|
341
|
+
import transformers # noqa: F401
|
342
|
+
|
343
|
+
providers["vllm"] = "VLLM (local models)"
|
344
|
+
except ImportError:
|
345
|
+
pass
|
346
|
+
|
347
|
+
# Default fallback
|
348
|
+
if not providers:
|
349
|
+
providers["none"] = "No model providers available"
|
350
|
+
|
351
|
+
return providers
|
352
|
+
|
353
|
+
|
354
|
+
def is_openai_available():
|
355
|
+
"""Check if OpenAI is available"""
|
356
|
+
try:
|
357
|
+
api_key = settings.get("OPENAI_API_KEY", "")
|
358
|
+
if not api_key:
|
359
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
360
|
+
return bool(api_key)
|
361
|
+
except Exception:
|
362
|
+
return False
|
363
|
+
|
364
|
+
|
365
|
+
def is_anthropic_available():
|
366
|
+
"""Check if Anthropic is available"""
|
367
|
+
try:
|
368
|
+
api_key = settings.get("ANTHROPIC_API_KEY", "")
|
369
|
+
if not api_key:
|
370
|
+
api_key = os.getenv("ANTHROPIC_API_KEY")
|
371
|
+
return bool(api_key)
|
372
|
+
except Exception:
|
373
|
+
return False
|
374
|
+
|
375
|
+
|
376
|
+
def is_openai_endpoint_available():
|
377
|
+
"""Check if OpenAI endpoint is available"""
|
378
|
+
try:
|
379
|
+
api_key = settings.get("OPENAI_ENDPOINT_API_KEY", "")
|
380
|
+
if not api_key:
|
381
|
+
api_key = os.getenv("OPENAI_ENDPOINT_API_KEY")
|
382
|
+
return bool(api_key)
|
383
|
+
except Exception:
|
384
|
+
return False
|
385
|
+
|
386
|
+
|
387
|
+
def is_ollama_available():
|
388
|
+
"""Check if Ollama is running"""
|
389
|
+
try:
|
390
|
+
import requests
|
391
|
+
|
392
|
+
base_url = os.getenv(
|
393
|
+
"OLLAMA_BASE_URL",
|
394
|
+
settings.llm.get("ollama_base_url", "http://localhost:11434"),
|
395
|
+
)
|
396
|
+
logger.info(f"Checking Ollama availability at {base_url}/api/tags")
|
397
|
+
|
398
|
+
try:
|
399
|
+
response = requests.get(f"{base_url}/api/tags", timeout=3.0)
|
400
|
+
if response.status_code == 200:
|
401
|
+
logger.info(f"Ollama is available. Status code: {response.status_code}")
|
402
|
+
# Log first 100 chars of response to debug
|
403
|
+
logger.info(f"Response preview: {str(response.text)[:100]}")
|
404
|
+
return True
|
405
|
+
else:
|
406
|
+
logger.warning(
|
407
|
+
f"Ollama API returned status code: {response.status_code}"
|
408
|
+
)
|
409
|
+
return False
|
410
|
+
except requests.exceptions.RequestException as req_error:
|
411
|
+
logger.error(f"Request error when checking Ollama: {str(req_error)}")
|
412
|
+
return False
|
413
|
+
except Exception as e:
|
414
|
+
logger.error(f"Unexpected error when checking Ollama: {str(e)}")
|
415
|
+
return False
|
416
|
+
except Exception as outer_e:
|
417
|
+
logger.error(f"Error in is_ollama_available: {str(outer_e)}")
|
418
|
+
return False
|
419
|
+
|
420
|
+
|
421
|
+
def is_vllm_available():
|
422
|
+
"""Check if VLLM capability is available"""
|
423
|
+
try:
|
424
|
+
import torch # noqa: F401
|
425
|
+
import transformers # noqa: F401
|
426
|
+
|
427
|
+
return True
|
428
|
+
except ImportError:
|
429
|
+
return False
|
430
|
+
|
431
|
+
|
432
|
+
def is_lmstudio_available():
|
433
|
+
"""Check if LM Studio is available"""
|
434
|
+
try:
|
435
|
+
import requests
|
436
|
+
|
437
|
+
lmstudio_url = settings.llm.get("lmstudio_url", "http://localhost:1234")
|
438
|
+
lmstudio_url = get_db_setting("llm.lmstudio_url", lmstudio_url)
|
439
|
+
# LM Studio typically uses OpenAI-compatible endpoints
|
440
|
+
response = requests.get(f"{lmstudio_url}/v1/models", timeout=1.0)
|
441
|
+
return response.status_code == 200
|
442
|
+
except Exception:
|
443
|
+
return False
|
444
|
+
|
445
|
+
|
446
|
+
def is_llamacpp_available():
|
447
|
+
"""Check if LlamaCpp is available and configured"""
|
448
|
+
try:
|
449
|
+
from langchain_community.llms import LlamaCpp # noqa: F401
|
450
|
+
|
451
|
+
model_path = settings.llm.get("llamacpp_model_path", "")
|
452
|
+
model_path = get_db_setting("llm.llamacpp_model_path", model_path)
|
453
|
+
return bool(model_path) and os.path.exists(model_path)
|
454
|
+
except Exception:
|
455
|
+
return False
|
456
|
+
|
457
|
+
|
458
|
+
def get_available_providers():
|
459
|
+
"""Get dictionary of available providers"""
|
460
|
+
return get_available_provider_types()
|
461
|
+
|
462
|
+
|
463
|
+
secrets_file = Path(SECRETS_FILE)
|
464
|
+
AVAILABLE_PROVIDERS = get_available_providers()
|
465
|
+
selected_provider = get_db_setting("llm.provider", settings.llm.provider).lower()
|
466
|
+
|
467
|
+
# Log which providers are available
|
468
|
+
logger.info(f"Available providers: {list(AVAILABLE_PROVIDERS.keys())}")
|
469
|
+
|
470
|
+
# Check if selected provider is available
|
471
|
+
if selected_provider not in AVAILABLE_PROVIDERS and selected_provider != "none":
|
472
|
+
logger.warning(f"Selected provider {selected_provider} is not available.")
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# local_deep_research/config.py
|
2
|
+
import logging
|
3
|
+
|
4
|
+
from ..utilities.db_utils import get_db_setting
|
5
|
+
from ..web_search_engines.search_engine_factory import get_search as factory_get_search
|
6
|
+
from .config_files import settings
|
7
|
+
from .llm_config import get_llm
|
8
|
+
|
9
|
+
# Setup logging
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
# Whether to check the quality search results using the LLM.
|
14
|
+
QUALITY_CHECK_DDG_URLS = True
|
15
|
+
# Whether to only retrieve snippets instead of full search results.
|
16
|
+
SEARCH_SNIPPETS_ONLY = get_db_setting(
|
17
|
+
"search.snippets_only", settings.search.snippets_only
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
# Expose get_search function
|
22
|
+
def get_search(search_tool=None, llm_instance=None):
|
23
|
+
"""
|
24
|
+
Helper function to get search engine
|
25
|
+
|
26
|
+
Args:
|
27
|
+
search_tool: Override the search tool setting (e.g. searxng, wikipedia)
|
28
|
+
llm_instance: Override the LLM instance
|
29
|
+
"""
|
30
|
+
|
31
|
+
# Use specified tool or default from settings
|
32
|
+
tool = search_tool or get_db_setting("search.tool", settings.search.tool)
|
33
|
+
logger.info(f"Creating search engine with tool: {tool}")
|
34
|
+
|
35
|
+
# Get LLM instance (use provided or get fresh one)
|
36
|
+
llm = llm_instance or get_llm()
|
37
|
+
|
38
|
+
# Get search parameters
|
39
|
+
params = {
|
40
|
+
"search_tool": tool,
|
41
|
+
"llm_instance": llm,
|
42
|
+
"max_results": get_db_setting(
|
43
|
+
"search.max_results", settings.search.max_results
|
44
|
+
),
|
45
|
+
"region": get_db_setting("search.region", settings.search.region),
|
46
|
+
"time_period": get_db_setting(
|
47
|
+
"search.time_period", settings.search.time_period
|
48
|
+
),
|
49
|
+
"safe_search": get_db_setting(
|
50
|
+
"search.safe_search", settings.search.safe_search
|
51
|
+
),
|
52
|
+
"search_snippets_only": SEARCH_SNIPPETS_ONLY,
|
53
|
+
"search_language": get_db_setting(
|
54
|
+
"search.search_language", settings.search.search_language
|
55
|
+
),
|
56
|
+
"max_filtered_results": get_db_setting(
|
57
|
+
"search.max_filtered_results", settings.search.max_filtered_results
|
58
|
+
),
|
59
|
+
}
|
60
|
+
|
61
|
+
# Log NULL parameters for debugging
|
62
|
+
logger.info(
|
63
|
+
f"Search config: tool={tool}, max_results={params['max_results']}, time_period={params['time_period']}"
|
64
|
+
)
|
65
|
+
|
66
|
+
# Create search engine
|
67
|
+
search_engine = factory_get_search(**params)
|
68
|
+
|
69
|
+
# Log the created engine type
|
70
|
+
if search_engine:
|
71
|
+
logger.info(
|
72
|
+
f"Successfully created search engine of type: {type(search_engine).__name__}"
|
73
|
+
)
|
74
|
+
else:
|
75
|
+
logger.error(f"Failed to create search engine for tool: {tool}")
|
76
|
+
|
77
|
+
return search_engine
|
@@ -4,8 +4,9 @@ Default configuration module for Local Deep Research.
|
|
4
4
|
This module is responsible for loading and initializing default
|
5
5
|
configuration files and resources used throughout the application.
|
6
6
|
"""
|
7
|
-
|
7
|
+
|
8
8
|
import logging
|
9
|
+
import os
|
9
10
|
from pathlib import Path
|
10
11
|
|
11
12
|
logger = logging.getLogger(__name__)
|
@@ -18,27 +19,31 @@ DEFAULT_FILES = {
|
|
18
19
|
"main.toml": DEFAULTS_DIR / "main.toml",
|
19
20
|
"local_collections.toml": DEFAULTS_DIR / "local_collections.toml",
|
20
21
|
"search_engines.toml": DEFAULTS_DIR / "search_engines.toml",
|
21
|
-
"llm_config.py": DEFAULTS_DIR / "llm_config.py"
|
22
22
|
}
|
23
23
|
|
24
|
+
|
24
25
|
def get_default_file_path(filename):
|
25
26
|
"""Get the path to a default configuration file."""
|
26
27
|
if filename in DEFAULT_FILES:
|
27
28
|
return DEFAULT_FILES[filename]
|
28
29
|
return None
|
29
30
|
|
31
|
+
|
30
32
|
def list_default_files():
|
31
33
|
"""List all available default configuration files."""
|
32
34
|
return list(DEFAULT_FILES.keys())
|
33
35
|
|
36
|
+
|
34
37
|
def ensure_defaults_exist():
|
35
38
|
"""Verify that all expected default files exist in the package."""
|
36
39
|
missing = []
|
37
40
|
for filename, filepath in DEFAULT_FILES.items():
|
38
41
|
if not filepath.exists():
|
39
42
|
missing.append(filename)
|
40
|
-
|
43
|
+
|
41
44
|
if missing:
|
42
|
-
logger.warning(
|
45
|
+
logger.warning(
|
46
|
+
f"The following default files are missing from the package: {', '.join(missing)}"
|
47
|
+
)
|
43
48
|
return False
|
44
|
-
return True
|
49
|
+
return True
|
@@ -38,7 +38,7 @@ enable_fact_checking = false
|
|
38
38
|
|
39
39
|
|
40
40
|
[search]
|
41
|
-
# Search tool to use (auto, wikipedia, arxiv, duckduckgo,
|
41
|
+
# Search tool to use (auto, wikipedia, arxiv, duckduckgo, serpapi, google_pse,etc.)
|
42
42
|
# "auto" intelligently selects based on query content (recommended)
|
43
43
|
# "local_all" searches only local document collections
|
44
44
|
tool = "auto"
|
@@ -71,7 +71,7 @@ safe_search = true
|
|
71
71
|
search_language = "English"
|
72
72
|
|
73
73
|
# Return only snippets, not full content (faster but less detailed)
|
74
|
-
snippets_only =
|
74
|
+
snippets_only = true
|
75
75
|
|
76
76
|
# Skip relevance filtering (return all results)
|
77
77
|
skip_relevance_filter = false
|