arionxiv 1.0.32__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.
- arionxiv/__init__.py +40 -0
- arionxiv/__main__.py +10 -0
- arionxiv/arxiv_operations/__init__.py +0 -0
- arionxiv/arxiv_operations/client.py +225 -0
- arionxiv/arxiv_operations/fetcher.py +173 -0
- arionxiv/arxiv_operations/searcher.py +122 -0
- arionxiv/arxiv_operations/utils.py +293 -0
- arionxiv/cli/__init__.py +4 -0
- arionxiv/cli/commands/__init__.py +1 -0
- arionxiv/cli/commands/analyze.py +587 -0
- arionxiv/cli/commands/auth.py +365 -0
- arionxiv/cli/commands/chat.py +714 -0
- arionxiv/cli/commands/daily.py +482 -0
- arionxiv/cli/commands/fetch.py +217 -0
- arionxiv/cli/commands/library.py +295 -0
- arionxiv/cli/commands/preferences.py +426 -0
- arionxiv/cli/commands/search.py +254 -0
- arionxiv/cli/commands/settings_unified.py +1407 -0
- arionxiv/cli/commands/trending.py +41 -0
- arionxiv/cli/commands/welcome.py +168 -0
- arionxiv/cli/main.py +407 -0
- arionxiv/cli/ui/__init__.py +1 -0
- arionxiv/cli/ui/global_theme_manager.py +173 -0
- arionxiv/cli/ui/logo.py +127 -0
- arionxiv/cli/ui/splash.py +89 -0
- arionxiv/cli/ui/theme.py +32 -0
- arionxiv/cli/ui/theme_system.py +391 -0
- arionxiv/cli/utils/__init__.py +54 -0
- arionxiv/cli/utils/animations.py +522 -0
- arionxiv/cli/utils/api_client.py +583 -0
- arionxiv/cli/utils/api_config.py +505 -0
- arionxiv/cli/utils/command_suggestions.py +147 -0
- arionxiv/cli/utils/db_config_manager.py +254 -0
- arionxiv/github_actions_runner.py +206 -0
- arionxiv/main.py +23 -0
- arionxiv/prompts/__init__.py +9 -0
- arionxiv/prompts/prompts.py +247 -0
- arionxiv/rag_techniques/__init__.py +8 -0
- arionxiv/rag_techniques/basic_rag.py +1531 -0
- arionxiv/scheduler_daemon.py +139 -0
- arionxiv/server.py +1000 -0
- arionxiv/server_main.py +24 -0
- arionxiv/services/__init__.py +73 -0
- arionxiv/services/llm_client.py +30 -0
- arionxiv/services/llm_inference/__init__.py +58 -0
- arionxiv/services/llm_inference/groq_client.py +469 -0
- arionxiv/services/llm_inference/llm_utils.py +250 -0
- arionxiv/services/llm_inference/openrouter_client.py +564 -0
- arionxiv/services/unified_analysis_service.py +872 -0
- arionxiv/services/unified_auth_service.py +457 -0
- arionxiv/services/unified_config_service.py +456 -0
- arionxiv/services/unified_daily_dose_service.py +823 -0
- arionxiv/services/unified_database_service.py +1633 -0
- arionxiv/services/unified_llm_service.py +366 -0
- arionxiv/services/unified_paper_service.py +604 -0
- arionxiv/services/unified_pdf_service.py +522 -0
- arionxiv/services/unified_prompt_service.py +344 -0
- arionxiv/services/unified_scheduler_service.py +589 -0
- arionxiv/services/unified_user_service.py +954 -0
- arionxiv/utils/__init__.py +51 -0
- arionxiv/utils/api_helpers.py +200 -0
- arionxiv/utils/file_cleanup.py +150 -0
- arionxiv/utils/ip_helper.py +96 -0
- arionxiv-1.0.32.dist-info/METADATA +336 -0
- arionxiv-1.0.32.dist-info/RECORD +69 -0
- arionxiv-1.0.32.dist-info/WHEEL +5 -0
- arionxiv-1.0.32.dist-info/entry_points.txt +4 -0
- arionxiv-1.0.32.dist-info/licenses/LICENSE +21 -0
- arionxiv-1.0.32.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified Prompt Service for ArionXiv
|
|
3
|
+
Manages LLM prompts from MongoDB with on-demand loading and TTL caching
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, Any, Optional, List, Tuple
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from .unified_database_service import unified_database_service
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UnifiedPromptService:
|
|
16
|
+
"""
|
|
17
|
+
Service for managing LLM prompts stored in MongoDB.
|
|
18
|
+
Prompts are loaded on-demand and cached with TTL.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
# Admin username for prompt management
|
|
23
|
+
self.admin_user_name = os.getenv("ADMIN_USER_NAME", "ariondas")
|
|
24
|
+
|
|
25
|
+
# Cache for prompts with TTL - stores {prompt_name: (template, expires_at)}
|
|
26
|
+
self._prompt_cache: Dict[str, Tuple[str, datetime]] = {}
|
|
27
|
+
self._cache_ttl_seconds = 300 # 5 minutes TTL
|
|
28
|
+
|
|
29
|
+
def _is_admin(self, user_name: str) -> bool:
|
|
30
|
+
"""Check if user is admin"""
|
|
31
|
+
if not user_name:
|
|
32
|
+
return False
|
|
33
|
+
return user_name.lower() == self.admin_user_name.lower()
|
|
34
|
+
|
|
35
|
+
def _is_cached(self, prompt_name: str) -> bool:
|
|
36
|
+
"""Check if prompt is cached and not expired"""
|
|
37
|
+
if prompt_name not in self._prompt_cache:
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
_, expires_at = self._prompt_cache[prompt_name]
|
|
41
|
+
return datetime.utcnow() < expires_at
|
|
42
|
+
|
|
43
|
+
def _get_from_cache(self, prompt_name: str) -> Optional[str]:
|
|
44
|
+
"""Get prompt from cache if valid"""
|
|
45
|
+
if self._is_cached(prompt_name):
|
|
46
|
+
template, _ = self._prompt_cache[prompt_name]
|
|
47
|
+
return template
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def _add_to_cache(self, prompt_name: str, template: str):
|
|
51
|
+
"""Add prompt to cache with TTL"""
|
|
52
|
+
expires_at = datetime.utcnow() + timedelta(seconds=self._cache_ttl_seconds)
|
|
53
|
+
self._prompt_cache[prompt_name] = (template, expires_at)
|
|
54
|
+
|
|
55
|
+
async def save_prompt(
|
|
56
|
+
self,
|
|
57
|
+
prompt_name: str,
|
|
58
|
+
template: str,
|
|
59
|
+
user_name: str = None
|
|
60
|
+
) -> Dict[str, Any]:
|
|
61
|
+
"""
|
|
62
|
+
Save or update a prompt in database (admin only)
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
prompt_name: Unique key for the prompt
|
|
66
|
+
template: The prompt template string
|
|
67
|
+
user_name: Username of user (must be admin)
|
|
68
|
+
"""
|
|
69
|
+
if not user_name or not self._is_admin(user_name):
|
|
70
|
+
return {
|
|
71
|
+
"success": False,
|
|
72
|
+
"error": "Only admin can save prompts"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
prompt_doc = {
|
|
77
|
+
"prompt_name": prompt_name,
|
|
78
|
+
"template": template,
|
|
79
|
+
"updated_at": datetime.utcnow(),
|
|
80
|
+
"updated_by": user_name
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Upsert - create or update
|
|
84
|
+
result = await unified_database_service.db.prompts.update_one(
|
|
85
|
+
{"prompt_name": prompt_name},
|
|
86
|
+
{"$set": prompt_doc, "$setOnInsert": {"created_at": datetime.utcnow()}},
|
|
87
|
+
upsert=True
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Invalidate cache for this prompt
|
|
91
|
+
if prompt_name in self._prompt_cache:
|
|
92
|
+
del self._prompt_cache[prompt_name]
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
"success": True,
|
|
96
|
+
"modified": result.modified_count > 0,
|
|
97
|
+
"created": result.upserted_id is not None
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.error(f"Failed to save prompt {prompt_name}: {str(e)}")
|
|
102
|
+
return {
|
|
103
|
+
"success": False,
|
|
104
|
+
"error": str(e)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async def get_prompts_batch(self, prompt_names: List[str]) -> Dict[str, str]:
|
|
108
|
+
"""
|
|
109
|
+
Get multiple prompts in a single query with caching.
|
|
110
|
+
Falls back to DEFAULT_PROMPTS if database is unavailable.
|
|
111
|
+
Returns dict of {prompt_name: template}
|
|
112
|
+
"""
|
|
113
|
+
from ..prompts.prompts import DEFAULT_PROMPTS
|
|
114
|
+
|
|
115
|
+
result = {}
|
|
116
|
+
to_fetch = []
|
|
117
|
+
|
|
118
|
+
# Check cache first
|
|
119
|
+
for name in prompt_names:
|
|
120
|
+
cached = self._get_from_cache(name)
|
|
121
|
+
if cached:
|
|
122
|
+
result[name] = cached
|
|
123
|
+
else:
|
|
124
|
+
to_fetch.append(name)
|
|
125
|
+
|
|
126
|
+
# If no database connection, use fallback prompts
|
|
127
|
+
if unified_database_service.db is None:
|
|
128
|
+
for name in to_fetch:
|
|
129
|
+
if name in DEFAULT_PROMPTS:
|
|
130
|
+
result[name] = DEFAULT_PROMPTS[name]
|
|
131
|
+
self._add_to_cache(name, DEFAULT_PROMPTS[name])
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
# Fetch uncached prompts from database
|
|
135
|
+
if to_fetch:
|
|
136
|
+
try:
|
|
137
|
+
cursor = unified_database_service.db.prompts.find(
|
|
138
|
+
{"prompt_name": {"$in": to_fetch}}
|
|
139
|
+
)
|
|
140
|
+
prompts = await cursor.to_list(length=None)
|
|
141
|
+
|
|
142
|
+
for prompt in prompts:
|
|
143
|
+
name = prompt["prompt_name"]
|
|
144
|
+
template = prompt["template"]
|
|
145
|
+
result[name] = template
|
|
146
|
+
self._add_to_cache(name, template)
|
|
147
|
+
|
|
148
|
+
# For any prompts not found in DB, use fallback
|
|
149
|
+
for name in to_fetch:
|
|
150
|
+
if name not in result and name in DEFAULT_PROMPTS:
|
|
151
|
+
result[name] = DEFAULT_PROMPTS[name]
|
|
152
|
+
self._add_to_cache(name, DEFAULT_PROMPTS[name])
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.error(f"Failed to fetch prompts batch: {str(e)}")
|
|
156
|
+
# Fall back to DEFAULT_PROMPTS on error
|
|
157
|
+
for name in to_fetch:
|
|
158
|
+
if name in DEFAULT_PROMPTS:
|
|
159
|
+
result[name] = DEFAULT_PROMPTS[name]
|
|
160
|
+
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
async def get_prompt(self, name: str) -> Dict[str, Any]:
|
|
164
|
+
"""
|
|
165
|
+
Get a single prompt by name with TTL caching.
|
|
166
|
+
Falls back to DEFAULT_PROMPTS if database is unavailable.
|
|
167
|
+
"""
|
|
168
|
+
from ..prompts.prompts import DEFAULT_PROMPTS
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
# Check cache first
|
|
172
|
+
cached_template = self._get_from_cache(name)
|
|
173
|
+
if cached_template:
|
|
174
|
+
return {
|
|
175
|
+
"success": True,
|
|
176
|
+
"template": cached_template,
|
|
177
|
+
"cached": True
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# If no database connection, use fallback prompts
|
|
181
|
+
if unified_database_service.db is None:
|
|
182
|
+
if name in DEFAULT_PROMPTS:
|
|
183
|
+
template = DEFAULT_PROMPTS[name]
|
|
184
|
+
self._add_to_cache(name, template)
|
|
185
|
+
return {
|
|
186
|
+
"success": True,
|
|
187
|
+
"template": template,
|
|
188
|
+
"cached": False,
|
|
189
|
+
"fallback": True
|
|
190
|
+
}
|
|
191
|
+
else:
|
|
192
|
+
return {
|
|
193
|
+
"success": False,
|
|
194
|
+
"error": f"Prompt '{name}' not found in defaults"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# Query database
|
|
198
|
+
prompt = await unified_database_service.find_one(
|
|
199
|
+
"prompts",
|
|
200
|
+
{"prompt_name": name}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if prompt:
|
|
204
|
+
template = prompt["template"]
|
|
205
|
+
self._add_to_cache(name, template)
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
"success": True,
|
|
209
|
+
"template": template,
|
|
210
|
+
"cached": False
|
|
211
|
+
}
|
|
212
|
+
else:
|
|
213
|
+
# Fall back to DEFAULT_PROMPTS if not in database
|
|
214
|
+
if name in DEFAULT_PROMPTS:
|
|
215
|
+
template = DEFAULT_PROMPTS[name]
|
|
216
|
+
self._add_to_cache(name, template)
|
|
217
|
+
return {
|
|
218
|
+
"success": True,
|
|
219
|
+
"template": template,
|
|
220
|
+
"cached": False,
|
|
221
|
+
"fallback": True
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
"success": False,
|
|
225
|
+
"error": f"Prompt '{name}' not found"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
logger.error(f"Failed to get prompt {name}: {str(e)}")
|
|
230
|
+
# Fall back to DEFAULT_PROMPTS on error
|
|
231
|
+
if name in DEFAULT_PROMPTS:
|
|
232
|
+
template = DEFAULT_PROMPTS[name]
|
|
233
|
+
self._add_to_cache(name, template)
|
|
234
|
+
return {
|
|
235
|
+
"success": True,
|
|
236
|
+
"template": template,
|
|
237
|
+
"cached": False,
|
|
238
|
+
"fallback": True
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
"success": False,
|
|
242
|
+
"error": f"Failed to get prompt: {str(e)}"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async def list_all_prompts(self, user_name: str = None) -> Dict[str, Any]:
|
|
246
|
+
"""
|
|
247
|
+
List all available prompts (admin only for viewing all)
|
|
248
|
+
"""
|
|
249
|
+
if not user_name or not self._is_admin(user_name):
|
|
250
|
+
return {
|
|
251
|
+
"success": False,
|
|
252
|
+
"error": "Admin only"
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
cursor = unified_database_service.db.prompts.find({}).sort("prompt_name", 1)
|
|
257
|
+
prompts = await cursor.to_list(length=None)
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
"success": True,
|
|
261
|
+
"prompts": [
|
|
262
|
+
{
|
|
263
|
+
"prompt_name": p["prompt_name"],
|
|
264
|
+
"updated_at": p.get("updated_at"),
|
|
265
|
+
"template_length": len(p.get("template", ""))
|
|
266
|
+
}
|
|
267
|
+
for p in prompts
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.error(f"Failed to list prompts: {str(e)}")
|
|
273
|
+
return {
|
|
274
|
+
"success": False,
|
|
275
|
+
"error": str(e)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async def delete_prompt(self, prompt_name: str, user_name: str = None) -> Dict[str, Any]:
|
|
279
|
+
"""
|
|
280
|
+
Delete a prompt (admin only)
|
|
281
|
+
"""
|
|
282
|
+
if not user_name or not self._is_admin(user_name):
|
|
283
|
+
return {
|
|
284
|
+
"success": False,
|
|
285
|
+
"error": "Admin only"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
result = await unified_database_service.db.prompts.delete_one(
|
|
290
|
+
{"prompt_name": prompt_name}
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
# Remove from cache
|
|
294
|
+
if prompt_name in self._prompt_cache:
|
|
295
|
+
del self._prompt_cache[prompt_name]
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
"success": result.deleted_count > 0,
|
|
299
|
+
"deleted": result.deleted_count
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
except Exception as e:
|
|
303
|
+
logger.error(f"Failed to delete prompt {prompt_name}: {str(e)}")
|
|
304
|
+
return {
|
|
305
|
+
"success": False,
|
|
306
|
+
"error": str(e)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async def format_prompt(self, prompt_name: str, **kwargs) -> str:
|
|
310
|
+
"""
|
|
311
|
+
Get and format a prompt with variables
|
|
312
|
+
"""
|
|
313
|
+
result = await self.get_prompt(prompt_name)
|
|
314
|
+
|
|
315
|
+
if not result["success"]:
|
|
316
|
+
raise ValueError(f"Prompt not found: {prompt_name}")
|
|
317
|
+
|
|
318
|
+
template = result["template"]
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
return template.format(**kwargs)
|
|
322
|
+
except KeyError as e:
|
|
323
|
+
raise ValueError(f"Missing variable for prompt '{prompt_name}': {e}")
|
|
324
|
+
|
|
325
|
+
def clear_cache(self, prompt_name: str = None):
|
|
326
|
+
"""Clear cache for specific prompt or all prompts"""
|
|
327
|
+
if prompt_name:
|
|
328
|
+
if prompt_name in self._prompt_cache:
|
|
329
|
+
del self._prompt_cache[prompt_name]
|
|
330
|
+
else:
|
|
331
|
+
self._prompt_cache = {}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# Global instance
|
|
335
|
+
unified_prompt_service = UnifiedPromptService()
|
|
336
|
+
|
|
337
|
+
# Export for convenience
|
|
338
|
+
prompt_service = unified_prompt_service
|
|
339
|
+
|
|
340
|
+
__all__ = [
|
|
341
|
+
'UnifiedPromptService',
|
|
342
|
+
'unified_prompt_service',
|
|
343
|
+
'prompt_service'
|
|
344
|
+
]
|