cite-agent 1.3.5__py3-none-any.whl → 1.3.7__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.
Potentially problematic release.
This version of cite-agent might be problematic. Click here for more details.
- cite_agent/__version__.py +1 -1
- cite_agent/cli.py +22 -2
- cite_agent/enhanced_ai_agent.py +407 -82
- cite_agent/project_detector.py +148 -0
- {cite_agent-1.3.5.dist-info → cite_agent-1.3.7.dist-info}/METADATA +1 -1
- cite_agent-1.3.7.dist-info/RECORD +31 -0
- {cite_agent-1.3.5.dist-info → cite_agent-1.3.7.dist-info}/top_level.txt +0 -1
- cite_agent-1.3.5.dist-info/RECORD +0 -56
- src/__init__.py +0 -1
- src/services/__init__.py +0 -132
- src/services/auth_service/__init__.py +0 -3
- src/services/auth_service/auth_manager.py +0 -33
- src/services/graph/__init__.py +0 -1
- src/services/graph/knowledge_graph.py +0 -194
- src/services/llm_service/__init__.py +0 -5
- src/services/llm_service/llm_manager.py +0 -495
- src/services/paper_service/__init__.py +0 -5
- src/services/paper_service/openalex.py +0 -231
- src/services/performance_service/__init__.py +0 -1
- src/services/performance_service/rust_performance.py +0 -395
- src/services/research_service/__init__.py +0 -23
- src/services/research_service/chatbot.py +0 -2056
- src/services/research_service/citation_manager.py +0 -436
- src/services/research_service/context_manager.py +0 -1441
- src/services/research_service/conversation_manager.py +0 -597
- src/services/research_service/critical_paper_detector.py +0 -577
- src/services/research_service/enhanced_research.py +0 -121
- src/services/research_service/enhanced_synthesizer.py +0 -375
- src/services/research_service/query_generator.py +0 -777
- src/services/research_service/synthesizer.py +0 -1273
- src/services/search_service/__init__.py +0 -5
- src/services/search_service/indexer.py +0 -186
- src/services/search_service/search_engine.py +0 -342
- src/services/simple_enhanced_main.py +0 -287
- {cite_agent-1.3.5.dist-info → cite_agent-1.3.7.dist-info}/WHEEL +0 -0
- {cite_agent-1.3.5.dist-info → cite_agent-1.3.7.dist-info}/entry_points.txt +0 -0
- {cite_agent-1.3.5.dist-info → cite_agent-1.3.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generic project detection - works with ANY IDE/project type
|
|
4
|
+
Not RStudio-specific - detects R, Python, Node, Jupyter, etc.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, List, Optional, Any
|
|
10
|
+
import glob
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ProjectDetector:
|
|
14
|
+
"""Detects project type and provides context"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, working_dir: Optional[str] = None):
|
|
17
|
+
self.working_dir = Path(working_dir or os.getcwd())
|
|
18
|
+
|
|
19
|
+
def detect_project(self) -> Optional[Dict[str, Any]]:
|
|
20
|
+
"""
|
|
21
|
+
Detect what kind of project user is working in
|
|
22
|
+
Returns project info or None if not in a project
|
|
23
|
+
"""
|
|
24
|
+
project_info = {
|
|
25
|
+
"type": None,
|
|
26
|
+
"name": None,
|
|
27
|
+
"recent_files": [],
|
|
28
|
+
"description": None
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Check for R project (.Rproj file OR 2+ .R files)
|
|
32
|
+
rproj_files = list(self.working_dir.glob("*.Rproj"))
|
|
33
|
+
r_files = list(self.working_dir.glob("*.R")) + list(self.working_dir.glob("*.Rmd"))
|
|
34
|
+
|
|
35
|
+
if rproj_files:
|
|
36
|
+
project_info["type"] = "R"
|
|
37
|
+
project_info["name"] = rproj_files[0].stem
|
|
38
|
+
project_info["recent_files"] = self._get_recent_files([".R", ".Rmd", ".qmd"])
|
|
39
|
+
project_info["description"] = f"R/RStudio project: {project_info['name']}"
|
|
40
|
+
return project_info
|
|
41
|
+
elif len(r_files) >= 2:
|
|
42
|
+
# R project without .Rproj file
|
|
43
|
+
project_info["type"] = "R"
|
|
44
|
+
project_info["name"] = self.working_dir.name
|
|
45
|
+
project_info["recent_files"] = self._get_recent_files([".R", ".Rmd", ".qmd"])
|
|
46
|
+
project_info["description"] = f"R project: {project_info['name']}"
|
|
47
|
+
return project_info
|
|
48
|
+
|
|
49
|
+
# Check for Python project
|
|
50
|
+
if (self.working_dir / "pyproject.toml").exists() or \
|
|
51
|
+
(self.working_dir / "setup.py").exists() or \
|
|
52
|
+
(self.working_dir / "requirements.txt").exists():
|
|
53
|
+
project_info["type"] = "Python"
|
|
54
|
+
project_info["name"] = self.working_dir.name
|
|
55
|
+
project_info["recent_files"] = self._get_recent_files([".py", ".ipynb"])
|
|
56
|
+
project_info["description"] = f"Python project: {project_info['name']}"
|
|
57
|
+
return project_info
|
|
58
|
+
|
|
59
|
+
# Check for Node.js project
|
|
60
|
+
if (self.working_dir / "package.json").exists():
|
|
61
|
+
project_info["type"] = "Node"
|
|
62
|
+
project_info["name"] = self.working_dir.name
|
|
63
|
+
project_info["recent_files"] = self._get_recent_files([".js", ".ts", ".jsx", ".tsx"])
|
|
64
|
+
project_info["description"] = f"Node.js project: {project_info['name']}"
|
|
65
|
+
return project_info
|
|
66
|
+
|
|
67
|
+
# Check for Jupyter/Data Science directory
|
|
68
|
+
ipynb_files = list(self.working_dir.glob("*.ipynb"))
|
|
69
|
+
if len(ipynb_files) >= 2: # 2+ notebooks = likely data science project
|
|
70
|
+
project_info["type"] = "Jupyter"
|
|
71
|
+
project_info["name"] = self.working_dir.name
|
|
72
|
+
project_info["recent_files"] = self._get_recent_files([".ipynb", ".py", ".csv"])
|
|
73
|
+
project_info["description"] = f"Jupyter/Data Science project: {project_info['name']}"
|
|
74
|
+
return project_info
|
|
75
|
+
|
|
76
|
+
# Check for Git repository
|
|
77
|
+
if (self.working_dir / ".git").exists():
|
|
78
|
+
project_info["type"] = "Git"
|
|
79
|
+
project_info["name"] = self.working_dir.name
|
|
80
|
+
# Get recent files of any code type
|
|
81
|
+
project_info["recent_files"] = self._get_recent_files([".py", ".js", ".R", ".java", ".cpp", ".rs"])
|
|
82
|
+
project_info["description"] = f"Git repository: {project_info['name']}"
|
|
83
|
+
return project_info
|
|
84
|
+
|
|
85
|
+
# Not in a recognized project
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def _get_recent_files(self, extensions: List[str], limit: int = 5) -> List[Dict[str, Any]]:
|
|
89
|
+
"""Get recently modified files with given extensions"""
|
|
90
|
+
files = []
|
|
91
|
+
|
|
92
|
+
for ext in extensions:
|
|
93
|
+
for filepath in self.working_dir.glob(f"**/*{ext}"):
|
|
94
|
+
if filepath.is_file() and not any(part.startswith('.') for part in filepath.parts):
|
|
95
|
+
try:
|
|
96
|
+
mtime = filepath.stat().st_mtime
|
|
97
|
+
files.append({
|
|
98
|
+
"name": filepath.name,
|
|
99
|
+
"path": str(filepath),
|
|
100
|
+
"relative_path": str(filepath.relative_to(self.working_dir)),
|
|
101
|
+
"modified": mtime,
|
|
102
|
+
"extension": ext
|
|
103
|
+
})
|
|
104
|
+
except:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
# Sort by modification time, newest first
|
|
108
|
+
files.sort(key=lambda f: f["modified"], reverse=True)
|
|
109
|
+
return files[:limit]
|
|
110
|
+
|
|
111
|
+
def format_project_banner(self, project_info: Dict[str, Any]) -> str:
|
|
112
|
+
"""Format project info as a nice banner"""
|
|
113
|
+
if not project_info or not project_info["type"]:
|
|
114
|
+
return ""
|
|
115
|
+
|
|
116
|
+
icon_map = {
|
|
117
|
+
"R": "📊",
|
|
118
|
+
"Python": "🐍",
|
|
119
|
+
"Node": "📦",
|
|
120
|
+
"Jupyter": "📓",
|
|
121
|
+
"Git": "📂"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
icon = icon_map.get(project_info["type"], "📁")
|
|
125
|
+
|
|
126
|
+
banner = f"\n{icon} {project_info['description']}\n"
|
|
127
|
+
|
|
128
|
+
if project_info["recent_files"]:
|
|
129
|
+
banner += "📄 Recent files:\n"
|
|
130
|
+
for f in project_info["recent_files"][:3]:
|
|
131
|
+
banner += f" • {f['relative_path']}\n"
|
|
132
|
+
|
|
133
|
+
return banner
|
|
134
|
+
|
|
135
|
+
def get_project_context_for_llm(self, project_info: Dict[str, Any]) -> str:
|
|
136
|
+
"""Get project context to add to LLM prompts"""
|
|
137
|
+
if not project_info or not project_info["type"]:
|
|
138
|
+
return ""
|
|
139
|
+
|
|
140
|
+
context = f"User is working in a {project_info['type']} project: {project_info['name']}\n"
|
|
141
|
+
|
|
142
|
+
if project_info["recent_files"]:
|
|
143
|
+
context += "Recent files:\n"
|
|
144
|
+
for f in project_info["recent_files"][:5]:
|
|
145
|
+
context += f"- {f['relative_path']}\n"
|
|
146
|
+
|
|
147
|
+
return context
|
|
148
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
cite_agent/__init__.py,sha256=wAXV2v8nNOmIAd0rh8196ItBl9hHWBVOBl5Re4VB77I,1645
|
|
2
|
+
cite_agent/__main__.py,sha256=6x3lltwG-iZHeQbN12rwvdkPDfd2Rmdk71tOOaC89Mw,179
|
|
3
|
+
cite_agent/__version__.py,sha256=9peaXOar2qezOPJEKG6cD_A0aaXrzdVN8h-v6fBoBEk,22
|
|
4
|
+
cite_agent/account_client.py,sha256=yLuzhIJoIZuXHXGbaVMzDxRATQwcy-wiaLnUrDuwUhI,5725
|
|
5
|
+
cite_agent/agent_backend_only.py,sha256=H4DH4hmKhT0T3rQLAb2xnnJVjxl3pOZaljL9r6JndFY,6314
|
|
6
|
+
cite_agent/ascii_plotting.py,sha256=lk8BaECs6fmjtp4iH12G09-frlRehAN7HLhHt2crers,8570
|
|
7
|
+
cite_agent/auth.py,sha256=YtoGXKwcLkZQbop37iYYL9BzRWBRPlt_D9p71VGViS4,9833
|
|
8
|
+
cite_agent/backend_only_client.py,sha256=WqLF8x7aXTro2Q3ehqKMsdCg53s6fNk9Hy86bGxqmmw,2561
|
|
9
|
+
cite_agent/cli.py,sha256=QO4hmHOeiW_8gxCjos1zk7NV4-joQiLc9LNsv7zCr70,35931
|
|
10
|
+
cite_agent/cli_conversational.py,sha256=RAmgRNRyB8gQ8QLvWU-Tt23j2lmA34rQNT5F3_7SOq0,11141
|
|
11
|
+
cite_agent/cli_enhanced.py,sha256=EAaSw9qtiYRWUXF6_05T19GCXlz9cCSz6n41ASnXIPc,7407
|
|
12
|
+
cite_agent/cli_workflow.py,sha256=4oS_jW9D8ylovXbEFdsyLQONt4o0xxR4Xatfcc4tnBs,11641
|
|
13
|
+
cite_agent/dashboard.py,sha256=VGV5XQU1PnqvTsxfKMcue3j2ri_nvm9Be6O5aVays_w,10502
|
|
14
|
+
cite_agent/enhanced_ai_agent.py,sha256=wyuQu50mZo6jMYZqFD8Bqjk55dFFOBBR28AixchftXY,187083
|
|
15
|
+
cite_agent/project_detector.py,sha256=fPl5cLTy_oyufqrQ7RJ5IRVdofZoPqDRaQXW6tRtBJc,6086
|
|
16
|
+
cite_agent/rate_limiter.py,sha256=-0fXx8Tl4zVB4O28n9ojU2weRo-FBF1cJo9Z5jC2LxQ,10908
|
|
17
|
+
cite_agent/session_manager.py,sha256=B0MXSOsXdhO3DlvTG7S8x6pmGlYEDvIZ-o8TZM23niQ,9444
|
|
18
|
+
cite_agent/setup_config.py,sha256=3m2e3gw0srEWA0OygdRo64r-8HK5ohyXfct0c__CF3s,16817
|
|
19
|
+
cite_agent/streaming_ui.py,sha256=N6TWOo7GVQ_Ynfw73JCfrdGcLIU-PwbS3GbsHQHegmg,7810
|
|
20
|
+
cite_agent/telemetry.py,sha256=55kXdHvI24ZsEkbFtihcjIfJt2oiSXcEpLzTxQ3KCdQ,2916
|
|
21
|
+
cite_agent/ui.py,sha256=r1OAeY3NSeqhAjJYmEBH9CaennBuibFAz1Mur6YF80E,6134
|
|
22
|
+
cite_agent/updater.py,sha256=udoAAN4gBKAvKDV7JTh2FJO_jIhNk9bby4x6n188MEY,8458
|
|
23
|
+
cite_agent/web_search.py,sha256=FZCuNO7MAITiOIbpPbJyt2bzbXPzQla-9amJpnMpW_4,6520
|
|
24
|
+
cite_agent/workflow.py,sha256=a0YC0Mzz4or1C5t2gZcuJBQ0uMOZrooaI8eLu2kkI0k,15086
|
|
25
|
+
cite_agent/workflow_integration.py,sha256=A9ua0DN5pRtuU0cAwrUTGvqt2SXKhEHQbrHx16EGnDM,10910
|
|
26
|
+
cite_agent-1.3.7.dist-info/licenses/LICENSE,sha256=XJkyO4IymhSUniN1ENY6lLrL2729gn_rbRlFK6_Hi9M,1074
|
|
27
|
+
cite_agent-1.3.7.dist-info/METADATA,sha256=YRov18tZiDcm3tnPS7nIPoD8Ruq1wFghllHcqy6mTKc,12231
|
|
28
|
+
cite_agent-1.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
cite_agent-1.3.7.dist-info/entry_points.txt,sha256=bJ0u28nFIxQKH1PWQ2ak4PV-FAjhoxTC7YADEdDenFw,83
|
|
30
|
+
cite_agent-1.3.7.dist-info/top_level.txt,sha256=NNfD8pxDZzBK8tjDIpCs2BW9Va-OQ5qUFbEx0SgmyIE,11
|
|
31
|
+
cite_agent-1.3.7.dist-info/RECORD,,
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
cite_agent/__init__.py,sha256=wAXV2v8nNOmIAd0rh8196ItBl9hHWBVOBl5Re4VB77I,1645
|
|
2
|
-
cite_agent/__main__.py,sha256=6x3lltwG-iZHeQbN12rwvdkPDfd2Rmdk71tOOaC89Mw,179
|
|
3
|
-
cite_agent/__version__.py,sha256=tdqvkGH0OryRjjXzO3HS5DyYol-VTO9fC8m43nB2PgI,22
|
|
4
|
-
cite_agent/account_client.py,sha256=yLuzhIJoIZuXHXGbaVMzDxRATQwcy-wiaLnUrDuwUhI,5725
|
|
5
|
-
cite_agent/agent_backend_only.py,sha256=H4DH4hmKhT0T3rQLAb2xnnJVjxl3pOZaljL9r6JndFY,6314
|
|
6
|
-
cite_agent/ascii_plotting.py,sha256=lk8BaECs6fmjtp4iH12G09-frlRehAN7HLhHt2crers,8570
|
|
7
|
-
cite_agent/auth.py,sha256=YtoGXKwcLkZQbop37iYYL9BzRWBRPlt_D9p71VGViS4,9833
|
|
8
|
-
cite_agent/backend_only_client.py,sha256=WqLF8x7aXTro2Q3ehqKMsdCg53s6fNk9Hy86bGxqmmw,2561
|
|
9
|
-
cite_agent/cli.py,sha256=f0x19N4MB85XgfNtOPEtovLQO_qzTHsli5IpG8wra30,35029
|
|
10
|
-
cite_agent/cli_conversational.py,sha256=RAmgRNRyB8gQ8QLvWU-Tt23j2lmA34rQNT5F3_7SOq0,11141
|
|
11
|
-
cite_agent/cli_enhanced.py,sha256=EAaSw9qtiYRWUXF6_05T19GCXlz9cCSz6n41ASnXIPc,7407
|
|
12
|
-
cite_agent/cli_workflow.py,sha256=4oS_jW9D8ylovXbEFdsyLQONt4o0xxR4Xatfcc4tnBs,11641
|
|
13
|
-
cite_agent/dashboard.py,sha256=VGV5XQU1PnqvTsxfKMcue3j2ri_nvm9Be6O5aVays_w,10502
|
|
14
|
-
cite_agent/enhanced_ai_agent.py,sha256=ha7cps216cNV59yLnQe2-K8Af9wXmSL7TokT3TX-JuY,167857
|
|
15
|
-
cite_agent/rate_limiter.py,sha256=-0fXx8Tl4zVB4O28n9ojU2weRo-FBF1cJo9Z5jC2LxQ,10908
|
|
16
|
-
cite_agent/session_manager.py,sha256=B0MXSOsXdhO3DlvTG7S8x6pmGlYEDvIZ-o8TZM23niQ,9444
|
|
17
|
-
cite_agent/setup_config.py,sha256=3m2e3gw0srEWA0OygdRo64r-8HK5ohyXfct0c__CF3s,16817
|
|
18
|
-
cite_agent/streaming_ui.py,sha256=N6TWOo7GVQ_Ynfw73JCfrdGcLIU-PwbS3GbsHQHegmg,7810
|
|
19
|
-
cite_agent/telemetry.py,sha256=55kXdHvI24ZsEkbFtihcjIfJt2oiSXcEpLzTxQ3KCdQ,2916
|
|
20
|
-
cite_agent/ui.py,sha256=r1OAeY3NSeqhAjJYmEBH9CaennBuibFAz1Mur6YF80E,6134
|
|
21
|
-
cite_agent/updater.py,sha256=udoAAN4gBKAvKDV7JTh2FJO_jIhNk9bby4x6n188MEY,8458
|
|
22
|
-
cite_agent/web_search.py,sha256=FZCuNO7MAITiOIbpPbJyt2bzbXPzQla-9amJpnMpW_4,6520
|
|
23
|
-
cite_agent/workflow.py,sha256=a0YC0Mzz4or1C5t2gZcuJBQ0uMOZrooaI8eLu2kkI0k,15086
|
|
24
|
-
cite_agent/workflow_integration.py,sha256=A9ua0DN5pRtuU0cAwrUTGvqt2SXKhEHQbrHx16EGnDM,10910
|
|
25
|
-
cite_agent-1.3.5.dist-info/licenses/LICENSE,sha256=XJkyO4IymhSUniN1ENY6lLrL2729gn_rbRlFK6_Hi9M,1074
|
|
26
|
-
src/__init__.py,sha256=0eEpjRfjRjOTilP66y-AbGNslBsVYr_clE-bZUzsX7s,40
|
|
27
|
-
src/services/__init__.py,sha256=pTGLCH_84mz4nGtYMwQES5w-LzoSulUtx_uuNM6r-LA,4257
|
|
28
|
-
src/services/simple_enhanced_main.py,sha256=IJoOplCqcVUg3GvN_BRyAhpGrLm_WEPy2jmHcNCY6R0,9257
|
|
29
|
-
src/services/auth_service/__init__.py,sha256=VVFfBUr_GMJuxVH_553D2PZmZ9vhHeab9_qiJEf-g6Q,38
|
|
30
|
-
src/services/auth_service/auth_manager.py,sha256=MJdWFE36R_htoyBbjgGSTSx2Py61sTM3lhBjXBZ4Bog,873
|
|
31
|
-
src/services/graph/__init__.py,sha256=jheRQ-x652RZ68fKyUqUNGXmTAJsp5URVMhlOauFRO0,29
|
|
32
|
-
src/services/graph/knowledge_graph.py,sha256=ips2IpVpxDFkdPku4XKgZNRnoR2NjZqZk3xbIArJaaM,7348
|
|
33
|
-
src/services/llm_service/__init__.py,sha256=eNAsQpJtVXpJENb-gHtpKzWpncnHHAMB05EI48wrugQ,122
|
|
34
|
-
src/services/llm_service/llm_manager.py,sha256=6o5KN-3wJ0hT8PS9hPMpTGS6G9SlleSzYsXZQRjj_vI,21027
|
|
35
|
-
src/services/paper_service/__init__.py,sha256=0ONhTf_3H81l5y6EqHMRZd5dCXLAXDa-gbYwge84zKA,142
|
|
36
|
-
src/services/paper_service/openalex.py,sha256=pPhPcHMK2gQJCUVPB4ujE8xya0UqUvfcN95cy5ooP68,8801
|
|
37
|
-
src/services/performance_service/__init__.py,sha256=48bYfW4pzf-FG9644kTnNwGyD1tJJ7tVn3cD3r_ZAbk,65
|
|
38
|
-
src/services/performance_service/rust_performance.py,sha256=n-FzJ98XslmpUAkmmuaunYDTPz-9ZY-qL4oWAoBAaoA,15558
|
|
39
|
-
src/services/research_service/__init__.py,sha256=ZCBzSUdstHqwMmJ1x0kJK4PkRlv9OrSOEFeQFoVM-7M,813
|
|
40
|
-
src/services/research_service/chatbot.py,sha256=12pVAoe_fd2RXi6_cP-fxfRnWyJStsyn8znVu5cy9qo,91153
|
|
41
|
-
src/services/research_service/citation_manager.py,sha256=vzyVivBS0_9IiFE-wOH9hiLiC-fpHmiaZpR1084DenE,16586
|
|
42
|
-
src/services/research_service/context_manager.py,sha256=FGbeylLWKvgoA5fElyiqg5IhnMBIZ-t3w0oDHN4Zy1E,61332
|
|
43
|
-
src/services/research_service/conversation_manager.py,sha256=-rdzURzu-SiqozyeQLid5a5lS-KzIqGDozdE8BG-DTs,22854
|
|
44
|
-
src/services/research_service/critical_paper_detector.py,sha256=gc3oZHB8RqDhxFqJx21NoKLcHmmqHXRo0eXY-AL5KSc,21941
|
|
45
|
-
src/services/research_service/enhanced_research.py,sha256=5B8zZjJ2iSLEgnjfyDKow5x_MLRANLJdMbLmmPR5Lc0,4268
|
|
46
|
-
src/services/research_service/enhanced_synthesizer.py,sha256=puJg2C10KXryCMPkec-chC4rxbIJdFFswo7w4rbaXkc,16603
|
|
47
|
-
src/services/research_service/query_generator.py,sha256=LcFTGsewE6l2LRgUI2E6fXAcpy4vaYaUFFfZhI_WlYU,30707
|
|
48
|
-
src/services/research_service/synthesizer.py,sha256=lCcu37PWhWVNphHKaJJDIC-JQ5OINAN7OJ7iV9BWAvM,52557
|
|
49
|
-
src/services/search_service/__init__.py,sha256=UZFXdd7r6wietQ2kESXEyGffdfBbpghquecQde7auF4,137
|
|
50
|
-
src/services/search_service/indexer.py,sha256=u3-uwdAfmahWWsdebDF9i8XIyp7YtUMIHzlmBLBnPPM,7252
|
|
51
|
-
src/services/search_service/search_engine.py,sha256=S9HqQ_mk-8W4d4MUOgBbEGQGV29-eSuceSFvVb4Xk-k,12500
|
|
52
|
-
cite_agent-1.3.5.dist-info/METADATA,sha256=qCT90yGGiD7au0geLi4DZYVpOH2i7bWWNJb1tLZHryw,12231
|
|
53
|
-
cite_agent-1.3.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
54
|
-
cite_agent-1.3.5.dist-info/entry_points.txt,sha256=bJ0u28nFIxQKH1PWQ2ak4PV-FAjhoxTC7YADEdDenFw,83
|
|
55
|
-
cite_agent-1.3.5.dist-info/top_level.txt,sha256=TgOFqJTIy8vDZuOoYA2QgagkqZtfhM5Acvt_IsWzAKo,15
|
|
56
|
-
cite_agent-1.3.5.dist-info/RECORD,,
|
src/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Project root package initializer."""
|
src/services/__init__.py
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Main services package for AI services layer
|
|
3
|
-
Provides unified access to all service components
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
# Import all major service classes for easy access
|
|
7
|
-
from .llm_service.llm_manager import LLMManager
|
|
8
|
-
from .research_service.enhanced_research import EnhancedResearchService
|
|
9
|
-
from .context_manager.advanced_context import AdvancedContextManager
|
|
10
|
-
from .tool_framework.tool_manager import ToolManager
|
|
11
|
-
from .auth_service.auth_manager import auth_manager
|
|
12
|
-
|
|
13
|
-
# Service registry for dependency injection
|
|
14
|
-
SERVICE_REGISTRY = {}
|
|
15
|
-
|
|
16
|
-
def register_service(name: str, service_instance):
|
|
17
|
-
"""Register a service instance in the global registry"""
|
|
18
|
-
SERVICE_REGISTRY[name] = service_instance
|
|
19
|
-
|
|
20
|
-
def get_service(name: str):
|
|
21
|
-
"""Get a service instance from the registry"""
|
|
22
|
-
return SERVICE_REGISTRY.get(name)
|
|
23
|
-
|
|
24
|
-
def initialize_services(config: dict = None):
|
|
25
|
-
"""Initialize all core services with configuration"""
|
|
26
|
-
config = config or {}
|
|
27
|
-
|
|
28
|
-
# Initialize services (with minimal config for testing)
|
|
29
|
-
services = {}
|
|
30
|
-
|
|
31
|
-
try:
|
|
32
|
-
# LLM Manager (needs redis_url but we'll handle gracefully)
|
|
33
|
-
redis_url = config.get('redis_url', 'redis://localhost:6379')
|
|
34
|
-
llm_manager = LLMManager(redis_url=redis_url)
|
|
35
|
-
services['llm_manager'] = llm_manager
|
|
36
|
-
register_service('llm_manager', llm_manager)
|
|
37
|
-
except Exception as e:
|
|
38
|
-
# Graceful fallback for testing
|
|
39
|
-
print(f"LLM Manager initialization skipped: {e}")
|
|
40
|
-
|
|
41
|
-
try:
|
|
42
|
-
# Research Service
|
|
43
|
-
research_service = EnhancedResearchService()
|
|
44
|
-
services['research_service'] = research_service
|
|
45
|
-
register_service('research_service', research_service)
|
|
46
|
-
except Exception as e:
|
|
47
|
-
print(f"Research Service initialization skipped: {e}")
|
|
48
|
-
|
|
49
|
-
try:
|
|
50
|
-
# Context Manager
|
|
51
|
-
context_manager = AdvancedContextManager()
|
|
52
|
-
services['context_manager'] = context_manager
|
|
53
|
-
register_service('context_manager', context_manager)
|
|
54
|
-
except Exception as e:
|
|
55
|
-
print(f"Context Manager initialization skipped: {e}")
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
# Tool Manager
|
|
59
|
-
tool_manager = ToolManager()
|
|
60
|
-
services['tool_manager'] = tool_manager
|
|
61
|
-
register_service('tool_manager', tool_manager)
|
|
62
|
-
except Exception as e:
|
|
63
|
-
print(f"Tool Manager initialization skipped: {e}")
|
|
64
|
-
|
|
65
|
-
return services
|
|
66
|
-
|
|
67
|
-
class ServiceLayer:
|
|
68
|
-
"""Unified service layer for easy access to all AI services"""
|
|
69
|
-
|
|
70
|
-
def __init__(self, config: dict = None):
|
|
71
|
-
self.config = config or {}
|
|
72
|
-
self.services = {}
|
|
73
|
-
self._initialized = False
|
|
74
|
-
|
|
75
|
-
def initialize(self):
|
|
76
|
-
"""Initialize all services"""
|
|
77
|
-
if self._initialized:
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
self.services = initialize_services(self.config)
|
|
81
|
-
self._initialized = True
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def llm_manager(self) -> LLMManager:
|
|
85
|
-
"""Get LLM Manager service"""
|
|
86
|
-
return self.services.get('llm_manager')
|
|
87
|
-
|
|
88
|
-
@property
|
|
89
|
-
def research_service(self) -> EnhancedResearchService:
|
|
90
|
-
"""Get Research service"""
|
|
91
|
-
return self.services.get('research_service')
|
|
92
|
-
|
|
93
|
-
@property
|
|
94
|
-
def context_manager(self) -> AdvancedContextManager:
|
|
95
|
-
"""Get Context Manager service"""
|
|
96
|
-
return self.services.get('context_manager')
|
|
97
|
-
|
|
98
|
-
@property
|
|
99
|
-
def tool_manager(self) -> ToolManager:
|
|
100
|
-
"""Get Tool Manager service"""
|
|
101
|
-
return self.services.get('tool_manager')
|
|
102
|
-
|
|
103
|
-
def get_health_status(self) -> dict:
|
|
104
|
-
"""Get health status of all services"""
|
|
105
|
-
status = {
|
|
106
|
-
"services_initialized": self._initialized,
|
|
107
|
-
"total_services": len(self.services),
|
|
108
|
-
"available_services": list(self.services.keys())
|
|
109
|
-
}
|
|
110
|
-
return status
|
|
111
|
-
|
|
112
|
-
# Global service layer instance
|
|
113
|
-
_service_layer = None
|
|
114
|
-
|
|
115
|
-
def get_service_layer(config: dict = None) -> ServiceLayer:
|
|
116
|
-
"""Get the global service layer instance"""
|
|
117
|
-
global _service_layer
|
|
118
|
-
if _service_layer is None:
|
|
119
|
-
_service_layer = ServiceLayer(config)
|
|
120
|
-
return _service_layer
|
|
121
|
-
|
|
122
|
-
# Export key classes and functions
|
|
123
|
-
__all__ = [
|
|
124
|
-
'LLMManager',
|
|
125
|
-
'EnhancedResearchService',
|
|
126
|
-
'AdvancedContextManager',
|
|
127
|
-
'ToolManager',
|
|
128
|
-
'ServiceLayer',
|
|
129
|
-
'get_service_layer',
|
|
130
|
-
'initialize_services',
|
|
131
|
-
'auth_manager'
|
|
132
|
-
]
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Basic authentication manager for testing
|
|
3
|
-
"""
|
|
4
|
-
from typing import Dict, Any, Optional
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class AuthManager:
|
|
8
|
-
"""Basic auth manager for testing purposes"""
|
|
9
|
-
|
|
10
|
-
def __init__(self):
|
|
11
|
-
self.test_user = {
|
|
12
|
-
"id": "test_user_123",
|
|
13
|
-
"username": "test_user",
|
|
14
|
-
"email": "test@example.com"
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async def get_current_user(self) -> Dict[str, Any]:
|
|
18
|
-
"""Return test user for testing"""
|
|
19
|
-
return self.test_user
|
|
20
|
-
|
|
21
|
-
def verify_token(self, token: str) -> Optional[Dict[str, Any]]:
|
|
22
|
-
"""Verify token (test implementation)"""
|
|
23
|
-
if token == "test_token":
|
|
24
|
-
return self.test_user
|
|
25
|
-
return None
|
|
26
|
-
|
|
27
|
-
def create_token(self, user_data: Dict[str, Any]) -> str:
|
|
28
|
-
"""Create token (test implementation)"""
|
|
29
|
-
return "test_token"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# Global instance
|
|
33
|
-
auth_manager = AuthManager()
|
src/services/graph/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Graph service package."""
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
"""Lightweight async knowledge graph implementation used by the research synthesizer.
|
|
2
|
-
|
|
3
|
-
The production design originally assumed an external graph database, but the launch-ready
|
|
4
|
-
runtime needs a dependable in-process implementation that works without external services.
|
|
5
|
-
This module provides a minimal yet functional directed multigraph using in-memory storage.
|
|
6
|
-
|
|
7
|
-
The implementation focuses on the operations exercised by ``ResearchSynthesizer``:
|
|
8
|
-
|
|
9
|
-
* ``upsert_entity`` – register/update an entity node with typed metadata
|
|
10
|
-
* ``upsert_relationship`` – connect two entities with rich relationship properties
|
|
11
|
-
* ``get_entity`` / ``get_relationships`` – helper APIs for diagnostics and future features
|
|
12
|
-
|
|
13
|
-
Data is persisted in memory and optionally mirrored to a JSON file on disk so the graph can
|
|
14
|
-
survive multiple sessions during local development. All public methods are ``async`` to keep
|
|
15
|
-
parity with the historical interface and to allow easy replacement with an external graph
|
|
16
|
-
backend in the future.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
from __future__ import annotations
|
|
20
|
-
|
|
21
|
-
import asyncio
|
|
22
|
-
import json
|
|
23
|
-
from dataclasses import dataclass, field
|
|
24
|
-
from pathlib import Path
|
|
25
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
26
|
-
|
|
27
|
-
__all__ = ["KnowledgeGraph", "GraphEntity", "GraphRelationship"]
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@dataclass
|
|
31
|
-
class GraphEntity:
|
|
32
|
-
"""Represents a node in the knowledge graph."""
|
|
33
|
-
|
|
34
|
-
entity_id: str
|
|
35
|
-
entity_type: str
|
|
36
|
-
properties: Dict[str, Any] = field(default_factory=dict)
|
|
37
|
-
|
|
38
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
39
|
-
return {
|
|
40
|
-
"id": self.entity_id,
|
|
41
|
-
"type": self.entity_type,
|
|
42
|
-
"properties": self.properties,
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@dataclass
|
|
47
|
-
class GraphRelationship:
|
|
48
|
-
"""Represents a directed, typed relationship between two entities."""
|
|
49
|
-
|
|
50
|
-
rel_type: str
|
|
51
|
-
source_id: str
|
|
52
|
-
target_id: str
|
|
53
|
-
properties: Dict[str, Any] = field(default_factory=dict)
|
|
54
|
-
|
|
55
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
56
|
-
return {
|
|
57
|
-
"type": self.rel_type,
|
|
58
|
-
"source": self.source_id,
|
|
59
|
-
"target": self.target_id,
|
|
60
|
-
"properties": self.properties,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class KnowledgeGraph:
|
|
65
|
-
"""A simple async-safe in-memory knowledge graph."""
|
|
66
|
-
|
|
67
|
-
def __init__(self, *, persistence_path: Optional[Path] = None) -> None:
|
|
68
|
-
self._entities: Dict[str, GraphEntity] = {}
|
|
69
|
-
# Adjacency list keyed by (source_id, rel_type) -> list[target_id, props]
|
|
70
|
-
self._relationships: List[GraphRelationship] = []
|
|
71
|
-
self._lock = asyncio.Lock()
|
|
72
|
-
self._persistence_path = persistence_path
|
|
73
|
-
if self._persistence_path:
|
|
74
|
-
self._load_from_disk()
|
|
75
|
-
|
|
76
|
-
# ------------------------------------------------------------------
|
|
77
|
-
# Persistence helpers
|
|
78
|
-
# ------------------------------------------------------------------
|
|
79
|
-
def _load_from_disk(self) -> None:
|
|
80
|
-
if not self._persistence_path or not self._persistence_path.exists():
|
|
81
|
-
return
|
|
82
|
-
try:
|
|
83
|
-
payload = json.loads(self._persistence_path.read_text())
|
|
84
|
-
except Exception:
|
|
85
|
-
return
|
|
86
|
-
|
|
87
|
-
for entity in payload.get("entities", []):
|
|
88
|
-
graph_entity = GraphEntity(
|
|
89
|
-
entity_id=entity["id"],
|
|
90
|
-
entity_type=entity.get("type", "Unknown"),
|
|
91
|
-
properties=entity.get("properties", {}),
|
|
92
|
-
)
|
|
93
|
-
self._entities[graph_entity.entity_id] = graph_entity
|
|
94
|
-
|
|
95
|
-
for rel in payload.get("relationships", []):
|
|
96
|
-
graph_rel = GraphRelationship(
|
|
97
|
-
rel_type=rel.get("type", "related_to"),
|
|
98
|
-
source_id=rel.get("source"),
|
|
99
|
-
target_id=rel.get("target"),
|
|
100
|
-
properties=rel.get("properties", {}),
|
|
101
|
-
)
|
|
102
|
-
self._relationships.append(graph_rel)
|
|
103
|
-
|
|
104
|
-
def _persist(self) -> None:
|
|
105
|
-
if not self._persistence_path:
|
|
106
|
-
return
|
|
107
|
-
data = {
|
|
108
|
-
"entities": [entity.to_dict() for entity in self._entities.values()],
|
|
109
|
-
"relationships": [rel.to_dict() for rel in self._relationships],
|
|
110
|
-
}
|
|
111
|
-
try:
|
|
112
|
-
self._persistence_path.parent.mkdir(parents=True, exist_ok=True)
|
|
113
|
-
self._persistence_path.write_text(json.dumps(data, indent=2, sort_keys=True))
|
|
114
|
-
except Exception:
|
|
115
|
-
# Persistence failures should never stop the conversation flow
|
|
116
|
-
pass
|
|
117
|
-
|
|
118
|
-
# ------------------------------------------------------------------
|
|
119
|
-
# Public API
|
|
120
|
-
# ------------------------------------------------------------------
|
|
121
|
-
async def upsert_entity(self, entity_type: str, properties: Dict[str, Any]) -> str:
|
|
122
|
-
"""Create or update an entity.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
entity_type: Semantic type (e.g., "Paper", "Author").
|
|
126
|
-
properties: Arbitrary metadata. ``properties['id']`` is optional; when missing
|
|
127
|
-
a deterministic identifier is derived from ``properties['external_id']`` or
|
|
128
|
-
a hash of the payload.
|
|
129
|
-
Returns:
|
|
130
|
-
The entity identifier stored in the graph.
|
|
131
|
-
"""
|
|
132
|
-
|
|
133
|
-
async with self._lock:
|
|
134
|
-
entity_id = _determine_entity_id(entity_type, properties)
|
|
135
|
-
entity = self._entities.get(entity_id)
|
|
136
|
-
if entity:
|
|
137
|
-
entity.properties.update(properties)
|
|
138
|
-
else:
|
|
139
|
-
entity = GraphEntity(entity_id=entity_id, entity_type=entity_type, properties=properties)
|
|
140
|
-
self._entities[entity_id] = entity
|
|
141
|
-
self._persist()
|
|
142
|
-
return entity_id
|
|
143
|
-
|
|
144
|
-
async def upsert_relationship(
|
|
145
|
-
self,
|
|
146
|
-
rel_type: str,
|
|
147
|
-
source_id: str,
|
|
148
|
-
target_id: str,
|
|
149
|
-
properties: Optional[Dict[str, Any]] = None,
|
|
150
|
-
) -> Tuple[str, str, str]:
|
|
151
|
-
"""Create or update a directed relationship between two entities."""
|
|
152
|
-
|
|
153
|
-
properties = properties or {}
|
|
154
|
-
async with self._lock:
|
|
155
|
-
relationship = GraphRelationship(
|
|
156
|
-
rel_type=rel_type,
|
|
157
|
-
source_id=source_id,
|
|
158
|
-
target_id=target_id,
|
|
159
|
-
properties=properties,
|
|
160
|
-
)
|
|
161
|
-
self._relationships.append(relationship)
|
|
162
|
-
self._persist()
|
|
163
|
-
return (relationship.rel_type, relationship.source_id, relationship.target_id)
|
|
164
|
-
|
|
165
|
-
async def get_entity(self, entity_id: str) -> Optional[GraphEntity]:
|
|
166
|
-
async with self._lock:
|
|
167
|
-
return self._entities.get(entity_id)
|
|
168
|
-
|
|
169
|
-
async def get_relationships(self, entity_id: str) -> List[GraphRelationship]:
|
|
170
|
-
async with self._lock:
|
|
171
|
-
return [rel for rel in self._relationships if rel.source_id == entity_id or rel.target_id == entity_id]
|
|
172
|
-
|
|
173
|
-
async def stats(self) -> Dict[str, Any]:
|
|
174
|
-
async with self._lock:
|
|
175
|
-
return {
|
|
176
|
-
"entities": len(self._entities),
|
|
177
|
-
"relationships": len(self._relationships),
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def _determine_entity_id(entity_type: str, properties: Dict[str, Any]) -> str:
|
|
182
|
-
"""Best-effort deterministic identifier for an entity."""
|
|
183
|
-
|
|
184
|
-
# Preferred explicit IDs
|
|
185
|
-
for key in ("id", "external_id", "paper_id", "author_id", "identifier"):
|
|
186
|
-
value = properties.get(key)
|
|
187
|
-
if value:
|
|
188
|
-
return str(value)
|
|
189
|
-
|
|
190
|
-
# Fall back to hashed representation (order-stable via JSON dumps)
|
|
191
|
-
import hashlib
|
|
192
|
-
|
|
193
|
-
payload = json.dumps({"type": entity_type, "properties": properties}, sort_keys=True)
|
|
194
|
-
return f"{entity_type}:{hashlib.md5(payload.encode('utf-8')).hexdigest()}"
|