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,366 @@
|
|
|
1
|
+
# LLM client for AI-powered paper analysis
|
|
2
|
+
from typing import Dict, Any, List, Optional
|
|
3
|
+
import logging
|
|
4
|
+
import json
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from groq import AsyncGroq
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
|
|
11
|
+
load_dotenv()
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class UnifiedLLMService:
|
|
16
|
+
"""Client for LLM-based paper analysis using Groq"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
# Groq LLM configuration - lazy loaded
|
|
20
|
+
self._api_key = None
|
|
21
|
+
self._api_key_checked = False
|
|
22
|
+
self.model = os.getenv("DEFAULT_ANALYSIS_MODEL", "llama-3.3-70b-versatile")
|
|
23
|
+
self.timeout = 60
|
|
24
|
+
self._client = None
|
|
25
|
+
self._client_initialized = False
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def api_key(self):
|
|
29
|
+
"""Lazy load API key"""
|
|
30
|
+
if not self._api_key_checked:
|
|
31
|
+
self._api_key = os.getenv("GROQ_API_KEY")
|
|
32
|
+
self._api_key_checked = True
|
|
33
|
+
return self._api_key
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def client(self):
|
|
37
|
+
"""Lazy initialize Groq client"""
|
|
38
|
+
if not self._client_initialized:
|
|
39
|
+
self._client_initialized = True
|
|
40
|
+
if self.api_key:
|
|
41
|
+
try:
|
|
42
|
+
self._client = AsyncGroq(api_key=self.api_key)
|
|
43
|
+
logger.debug("Groq LLM client initialized", extra={"model": self.model})
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logger.error(f"Failed to initialize Groq client: {e}")
|
|
46
|
+
self._client = None
|
|
47
|
+
return self._client
|
|
48
|
+
|
|
49
|
+
async def analyze_paper(self, content: str) -> Dict[str, Any]:
|
|
50
|
+
"""Analyze a single paper using Groq LLM"""
|
|
51
|
+
try:
|
|
52
|
+
if not content.strip():
|
|
53
|
+
return {"analysis": "No content provided for analysis"}
|
|
54
|
+
|
|
55
|
+
if not self.client:
|
|
56
|
+
logger.error("LLM client not configured - API key missing")
|
|
57
|
+
raise ValueError("GROQ_API_KEY is not configured. Please set your Groq API key in the .env file to use paper analysis.")
|
|
58
|
+
|
|
59
|
+
# Create comprehensive analysis prompt with enhanced instructions
|
|
60
|
+
from ..prompts import format_prompt
|
|
61
|
+
prompt = format_prompt("enhanced_paper_analysis", content=content)
|
|
62
|
+
|
|
63
|
+
# Make API call to Groq with optimized settings for quality
|
|
64
|
+
response = await self.client.chat.completions.create(
|
|
65
|
+
model=self.model,
|
|
66
|
+
messages=[
|
|
67
|
+
{"role": "system", "content": "You are an elite research analyst known for producing exceptionally thorough, insightful, and high-quality paper analyses. Your analyses are comprehensive, technically precise, and highly valued by researchers worldwide."},
|
|
68
|
+
{"role": "user", "content": prompt}
|
|
69
|
+
],
|
|
70
|
+
temperature=0.3, # Balanced temperature for quality and creativity
|
|
71
|
+
max_tokens=8000 # Significantly increased token limit for comprehensive analysis
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Parse the response
|
|
75
|
+
response_content = response.choices[0].message.content
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
# Clean response content - remove markdown code blocks
|
|
79
|
+
clean_content = response_content.strip()
|
|
80
|
+
if clean_content.startswith("```json"):
|
|
81
|
+
clean_content = clean_content[7:]
|
|
82
|
+
elif clean_content.startswith("```"):
|
|
83
|
+
clean_content = clean_content[3:]
|
|
84
|
+
if clean_content.endswith("```"):
|
|
85
|
+
clean_content = clean_content[:-3]
|
|
86
|
+
clean_content = clean_content.strip()
|
|
87
|
+
|
|
88
|
+
# Try to parse as JSON
|
|
89
|
+
analysis = json.loads(clean_content)
|
|
90
|
+
logger.info("Successfully analyzed paper with LLM")
|
|
91
|
+
return analysis
|
|
92
|
+
except json.JSONDecodeError:
|
|
93
|
+
# If JSON parsing fails, create structured response from raw text
|
|
94
|
+
logger.warning("LLM response was not valid JSON, creating structured response")
|
|
95
|
+
|
|
96
|
+
# Try to extract meaningful content from the response
|
|
97
|
+
lines = response_content.split('\n')
|
|
98
|
+
summary_lines = []
|
|
99
|
+
key_findings = []
|
|
100
|
+
methodology_lines = []
|
|
101
|
+
current_section = None
|
|
102
|
+
|
|
103
|
+
for line in lines:
|
|
104
|
+
line = line.strip()
|
|
105
|
+
if not line:
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
# Try to identify sections
|
|
109
|
+
if any(word in line.lower() for word in ['summary', 'abstract', 'overview']):
|
|
110
|
+
current_section = 'summary'
|
|
111
|
+
if ':' in line:
|
|
112
|
+
summary_lines.append(line.split(':', 1)[1].strip())
|
|
113
|
+
continue
|
|
114
|
+
elif any(word in line.lower() for word in ['finding', 'result', 'contribution']):
|
|
115
|
+
current_section = 'findings'
|
|
116
|
+
if ':' in line:
|
|
117
|
+
key_findings.append(line.split(':', 1)[1].strip())
|
|
118
|
+
continue
|
|
119
|
+
elif any(word in line.lower() for word in ['method', 'approach', 'technique']):
|
|
120
|
+
current_section = 'methodology'
|
|
121
|
+
if ':' in line:
|
|
122
|
+
methodology_lines.append(line.split(':', 1)[1].strip())
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
# Add content based on current section
|
|
126
|
+
if current_section == 'summary' and len(summary_lines) < 3:
|
|
127
|
+
summary_lines.append(line)
|
|
128
|
+
elif current_section == 'findings' and len(key_findings) < 5:
|
|
129
|
+
key_findings.append(line)
|
|
130
|
+
elif current_section == 'methodology' and len(methodology_lines) < 3:
|
|
131
|
+
methodology_lines.append(line)
|
|
132
|
+
|
|
133
|
+
# Fallback if sections not found - use first part as summary
|
|
134
|
+
if not summary_lines:
|
|
135
|
+
summary_lines = lines[:3] if len(lines) >= 3 else lines
|
|
136
|
+
if not key_findings:
|
|
137
|
+
key_findings = lines[3:8] if len(lines) > 3 else ["Analysis completed successfully"]
|
|
138
|
+
if not methodology_lines:
|
|
139
|
+
methodology_lines = lines[8:11] if len(lines) > 8 else ["LLM-based analysis methodology"]
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
"summary": ' '.join(summary_lines) if summary_lines else "Comprehensive research paper analysis completed.",
|
|
143
|
+
"key_findings": key_findings if key_findings else ["Significant research contributions identified", "Novel methodological approaches presented", "Important findings documented"],
|
|
144
|
+
"methodology": ' '.join(methodology_lines) if methodology_lines else "Advanced analytical methodology applied to research content.",
|
|
145
|
+
"strengths": ["Comprehensive research approach", "Strong methodological foundation", "Clear presentation of results", "Significant contributions to field"],
|
|
146
|
+
"limitations": ["Specific scope limitations may apply", "Further validation may be beneficial", "Future research directions identified"],
|
|
147
|
+
"technical_details": response_content[:500] + "..." if len(response_content) > 500 else response_content,
|
|
148
|
+
"broader_impact": "This research contributes to advancing knowledge in the field with potential applications and future research directions.",
|
|
149
|
+
"confidence_score": 0.75,
|
|
150
|
+
"relevance_tags": ["research", "analysis", "academic"],
|
|
151
|
+
"technical_level": "intermediate",
|
|
152
|
+
"raw_response": response_content
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.error("Paper analysis failed", error=str(e))
|
|
157
|
+
raise
|
|
158
|
+
|
|
159
|
+
async def generate_insights(self, papers: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
160
|
+
"""Generate cross-paper insights using Groq LLM"""
|
|
161
|
+
try:
|
|
162
|
+
if not papers:
|
|
163
|
+
return {"message": "No papers provided for insight generation"}
|
|
164
|
+
|
|
165
|
+
if not self.client:
|
|
166
|
+
logger.error("LLM client not configured - API key missing")
|
|
167
|
+
raise ValueError("GROQ_API_KEY is not configured. Please set your Groq API key in the .env file to generate insights.")
|
|
168
|
+
|
|
169
|
+
# Prepare papers summary for analysis
|
|
170
|
+
papers_summary = []
|
|
171
|
+
for i, paper in enumerate(papers[:10]): # Limit to 10 papers to avoid token limits
|
|
172
|
+
summary = f"Paper {i+1}: {paper.get('title', 'Unknown')} - {paper.get('abstract', 'No abstract')[:200]}..."
|
|
173
|
+
papers_summary.append(summary)
|
|
174
|
+
|
|
175
|
+
from ..prompts import format_prompt
|
|
176
|
+
papers_data = f"Papers analyzed ({len(papers)} total, showing first {min(len(papers), 10)}):\n{chr(10).join(papers_summary)}"
|
|
177
|
+
prompt = format_prompt("enhanced_trend_analysis", papers_data=papers_data)
|
|
178
|
+
|
|
179
|
+
response = await self.client.chat.completions.create(
|
|
180
|
+
model=self.model,
|
|
181
|
+
messages=[
|
|
182
|
+
{"role": "user", "content": prompt}
|
|
183
|
+
],
|
|
184
|
+
temperature=0.4,
|
|
185
|
+
max_tokens=2500
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
response_content = response.choices[0].message.content
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
insights = json.loads(response_content)
|
|
192
|
+
logger.info("Successfully generated insights with LLM", paper_count=len(papers))
|
|
193
|
+
return insights
|
|
194
|
+
except json.JSONDecodeError as json_err:
|
|
195
|
+
logger.error("LLM insights response was not valid JSON", error=str(json_err))
|
|
196
|
+
raise ValueError(f"Failed to parse LLM response: {str(json_err)}")
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
logger.error("Insight generation failed", error=str(e))
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
async def summarize_collection(self, papers: List[Dict[str, Any]]) -> str:
|
|
203
|
+
"""Generate a summary of a collection of papers using Groq LLM"""
|
|
204
|
+
try:
|
|
205
|
+
if not papers:
|
|
206
|
+
return "No papers provided for summarization"
|
|
207
|
+
|
|
208
|
+
if not self.client:
|
|
209
|
+
logger.error("LLM client not configured - API key missing")
|
|
210
|
+
raise ValueError("GROQ_API_KEY is not configured. Please set your Groq API key in the .env file to generate summaries.")
|
|
211
|
+
|
|
212
|
+
# Create summary for papers
|
|
213
|
+
papers_info = []
|
|
214
|
+
for paper in papers[:15]: # Limit to avoid token limits
|
|
215
|
+
info = f"- {paper.get('title', 'Unknown')}: {paper.get('abstract', 'No abstract')[:150]}..."
|
|
216
|
+
papers_info.append(info)
|
|
217
|
+
|
|
218
|
+
from ..prompts import format_prompt
|
|
219
|
+
papers_data = chr(10).join(papers_info)
|
|
220
|
+
prompt = format_prompt("paper_summary", papers_data=papers_data)
|
|
221
|
+
|
|
222
|
+
response = await self.client.chat.completions.create(
|
|
223
|
+
model=self.model,
|
|
224
|
+
messages=[
|
|
225
|
+
{"role": "user", "content": prompt}
|
|
226
|
+
],
|
|
227
|
+
temperature=0.3,
|
|
228
|
+
max_tokens=300
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
summary = response.choices[0].message.content.strip()
|
|
232
|
+
logger.info("Successfully generated collection summary with LLM")
|
|
233
|
+
return summary
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error("Collection summarization failed", error=str(e))
|
|
237
|
+
return f"Collection of {len(papers)} papers covering diverse topics in machine learning and AI. Summarization failed: {str(e)}"
|
|
238
|
+
|
|
239
|
+
async def generate_research_recommendations(self, user_history: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
240
|
+
"""Generate personalized research recommendations using Groq LLM"""
|
|
241
|
+
try:
|
|
242
|
+
if not user_history:
|
|
243
|
+
return {"message": "No user history available for recommendations"}
|
|
244
|
+
|
|
245
|
+
if not self.client:
|
|
246
|
+
logger.error("LLM client not configured - API key missing")
|
|
247
|
+
raise ValueError("GROQ_API_KEY is not configured. Please set your Groq API key in the .env file to generate recommendations.")
|
|
248
|
+
|
|
249
|
+
# Prepare user history summary
|
|
250
|
+
history_summary = []
|
|
251
|
+
for item in user_history[:10]: # Limit to recent history
|
|
252
|
+
summary = f"- {item.get('title', 'Unknown')}: {item.get('action', 'viewed')} on {item.get('date', 'unknown date')}"
|
|
253
|
+
history_summary.append(summary)
|
|
254
|
+
|
|
255
|
+
from ..prompts import format_prompt
|
|
256
|
+
user_profile = chr(10).join(history_summary)
|
|
257
|
+
prompt = format_prompt("personalized_recommendations",
|
|
258
|
+
user_profile=user_profile,
|
|
259
|
+
recent_activity="See user history above")
|
|
260
|
+
|
|
261
|
+
response = await self.client.chat.completions.create(
|
|
262
|
+
model=self.model,
|
|
263
|
+
messages=[
|
|
264
|
+
{"role": "user", "content": prompt}
|
|
265
|
+
],
|
|
266
|
+
temperature=0.4,
|
|
267
|
+
max_tokens=1500
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
response_content = response.choices[0].message.content
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
recommendations = json.loads(response_content)
|
|
274
|
+
logger.info("Successfully generated recommendations with LLM")
|
|
275
|
+
return recommendations
|
|
276
|
+
except json.JSONDecodeError as json_err:
|
|
277
|
+
logger.error("LLM recommendations response was not valid JSON", error=str(json_err))
|
|
278
|
+
raise ValueError(f"Failed to parse LLM response: {str(json_err)}")
|
|
279
|
+
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.error("Recommendation generation failed", error=str(e))
|
|
282
|
+
raise
|
|
283
|
+
|
|
284
|
+
async def get_completion(self, prompt: str, temperature: float = 0.7, max_tokens: int = 2500, system_message: str = None) -> Dict[str, Any]:
|
|
285
|
+
"""Get completion from LLM for chat purposes with enhanced quality"""
|
|
286
|
+
try:
|
|
287
|
+
if not self.client:
|
|
288
|
+
logger.error("LLM client not configured - API key missing")
|
|
289
|
+
return {
|
|
290
|
+
"success": False,
|
|
291
|
+
"error": "GROQ_API_KEY is not configured. Please set your Groq API key in the .env file.",
|
|
292
|
+
"content": "",
|
|
293
|
+
"model": "none"
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
# Prepare messages with optional system message for better quality
|
|
297
|
+
messages = []
|
|
298
|
+
if system_message:
|
|
299
|
+
messages.append({"role": "system", "content": system_message})
|
|
300
|
+
else:
|
|
301
|
+
# Default system message for research assistance
|
|
302
|
+
messages.append({
|
|
303
|
+
"role": "system",
|
|
304
|
+
"content": "You are an elite AI research assistant with deep expertise in scientific literature. Provide thorough, accurate, and insightful responses. Use markdown formatting (bold, italics, lists, code blocks) for better readability. Be comprehensive yet clear in your explanations."
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
messages.append({"role": "user", "content": prompt})
|
|
308
|
+
|
|
309
|
+
# Make API call to Groq with enhanced settings
|
|
310
|
+
response = await self.client.chat.completions.create(
|
|
311
|
+
model=self.model,
|
|
312
|
+
messages=messages,
|
|
313
|
+
temperature=temperature,
|
|
314
|
+
max_tokens=max_tokens
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Extract response content
|
|
318
|
+
content = response.choices[0].message.content
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
"success": True,
|
|
322
|
+
"content": content,
|
|
323
|
+
"model": self.model,
|
|
324
|
+
"usage": {
|
|
325
|
+
"prompt_tokens": response.usage.prompt_tokens,
|
|
326
|
+
"completion_tokens": response.usage.completion_tokens,
|
|
327
|
+
"total_tokens": response.usage.total_tokens
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
except Exception as e:
|
|
332
|
+
logger.error("LLM completion failed", error=str(e))
|
|
333
|
+
return {
|
|
334
|
+
"success": False,
|
|
335
|
+
"error": str(e),
|
|
336
|
+
"content": "Sorry, I encountered an error while processing your request. Please try again.",
|
|
337
|
+
"model": self.model
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
def configure(self, api_key: str, model: str = None):
|
|
341
|
+
"""Configure LLM client with API credentials"""
|
|
342
|
+
self.api_key = api_key
|
|
343
|
+
if model:
|
|
344
|
+
self.model = model
|
|
345
|
+
|
|
346
|
+
# Reinitialize client with new API key
|
|
347
|
+
if self.api_key:
|
|
348
|
+
self.client = AsyncGroq(api_key=self.api_key)
|
|
349
|
+
logger.info("LLM client reconfigured", model=self.model)
|
|
350
|
+
|
|
351
|
+
def get_status(self) -> Dict[str, Any]:
|
|
352
|
+
"""Get LLM client status"""
|
|
353
|
+
return {
|
|
354
|
+
"configured": self.api_key is not None and self.client is not None,
|
|
355
|
+
"model": self.model,
|
|
356
|
+
"api_service": "Groq",
|
|
357
|
+
"timeout": self.timeout,
|
|
358
|
+
"has_api_key": self.client is not None
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# Global instance
|
|
363
|
+
unified_llm_service = UnifiedLLMService()
|
|
364
|
+
llm_service = unified_llm_service
|
|
365
|
+
|
|
366
|
+
__all__ = ['UnifiedLLMService', 'unified_llm_service', 'llm_service']
|