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.

Files changed (37) hide show
  1. cite_agent/__version__.py +1 -1
  2. cite_agent/cli.py +22 -2
  3. cite_agent/enhanced_ai_agent.py +407 -82
  4. cite_agent/project_detector.py +148 -0
  5. {cite_agent-1.3.5.dist-info → cite_agent-1.3.7.dist-info}/METADATA +1 -1
  6. cite_agent-1.3.7.dist-info/RECORD +31 -0
  7. {cite_agent-1.3.5.dist-info → cite_agent-1.3.7.dist-info}/top_level.txt +0 -1
  8. cite_agent-1.3.5.dist-info/RECORD +0 -56
  9. src/__init__.py +0 -1
  10. src/services/__init__.py +0 -132
  11. src/services/auth_service/__init__.py +0 -3
  12. src/services/auth_service/auth_manager.py +0 -33
  13. src/services/graph/__init__.py +0 -1
  14. src/services/graph/knowledge_graph.py +0 -194
  15. src/services/llm_service/__init__.py +0 -5
  16. src/services/llm_service/llm_manager.py +0 -495
  17. src/services/paper_service/__init__.py +0 -5
  18. src/services/paper_service/openalex.py +0 -231
  19. src/services/performance_service/__init__.py +0 -1
  20. src/services/performance_service/rust_performance.py +0 -395
  21. src/services/research_service/__init__.py +0 -23
  22. src/services/research_service/chatbot.py +0 -2056
  23. src/services/research_service/citation_manager.py +0 -436
  24. src/services/research_service/context_manager.py +0 -1441
  25. src/services/research_service/conversation_manager.py +0 -597
  26. src/services/research_service/critical_paper_detector.py +0 -577
  27. src/services/research_service/enhanced_research.py +0 -121
  28. src/services/research_service/enhanced_synthesizer.py +0 -375
  29. src/services/research_service/query_generator.py +0 -777
  30. src/services/research_service/synthesizer.py +0 -1273
  31. src/services/search_service/__init__.py +0 -5
  32. src/services/search_service/indexer.py +0 -186
  33. src/services/search_service/search_engine.py +0 -342
  34. src/services/simple_enhanced_main.py +0 -287
  35. {cite_agent-1.3.5.dist-info → cite_agent-1.3.7.dist-info}/WHEEL +0 -0
  36. {cite_agent-1.3.5.dist-info → cite_agent-1.3.7.dist-info}/entry_points.txt +0 -0
  37. {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
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cite-agent
3
- Version: 1.3.5
3
+ Version: 1.3.7
4
4
  Summary: Terminal AI assistant for academic research with citation verification
5
5
  Home-page: https://github.com/Spectating101/cite-agent
6
6
  Author: Cite-Agent Team
@@ -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,3 +0,0 @@
1
- """
2
- Authentication service package
3
- """
@@ -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()
@@ -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()}"
@@ -1,5 +0,0 @@
1
- """LLM service package exposing the unified LLMManager."""
2
-
3
- from .llm_manager import LLMManager
4
-
5
- __all__ = ["LLMManager"]