cite-agent 1.3.6__py3-none-any.whl → 1.3.8__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 (36) hide show
  1. cite_agent/__version__.py +1 -1
  2. cite_agent/cli.py +9 -2
  3. cite_agent/enhanced_ai_agent.py +1100 -77
  4. {cite_agent-1.3.6.dist-info → cite_agent-1.3.8.dist-info}/METADATA +1 -1
  5. cite_agent-1.3.8.dist-info/RECORD +31 -0
  6. {cite_agent-1.3.6.dist-info → cite_agent-1.3.8.dist-info}/top_level.txt +0 -1
  7. cite_agent-1.3.6.dist-info/RECORD +0 -57
  8. src/__init__.py +0 -1
  9. src/services/__init__.py +0 -132
  10. src/services/auth_service/__init__.py +0 -3
  11. src/services/auth_service/auth_manager.py +0 -33
  12. src/services/graph/__init__.py +0 -1
  13. src/services/graph/knowledge_graph.py +0 -194
  14. src/services/llm_service/__init__.py +0 -5
  15. src/services/llm_service/llm_manager.py +0 -495
  16. src/services/paper_service/__init__.py +0 -5
  17. src/services/paper_service/openalex.py +0 -231
  18. src/services/performance_service/__init__.py +0 -1
  19. src/services/performance_service/rust_performance.py +0 -395
  20. src/services/research_service/__init__.py +0 -23
  21. src/services/research_service/chatbot.py +0 -2056
  22. src/services/research_service/citation_manager.py +0 -436
  23. src/services/research_service/context_manager.py +0 -1441
  24. src/services/research_service/conversation_manager.py +0 -597
  25. src/services/research_service/critical_paper_detector.py +0 -577
  26. src/services/research_service/enhanced_research.py +0 -121
  27. src/services/research_service/enhanced_synthesizer.py +0 -375
  28. src/services/research_service/query_generator.py +0 -777
  29. src/services/research_service/synthesizer.py +0 -1273
  30. src/services/search_service/__init__.py +0 -5
  31. src/services/search_service/indexer.py +0 -186
  32. src/services/search_service/search_engine.py +0 -342
  33. src/services/simple_enhanced_main.py +0 -287
  34. {cite_agent-1.3.6.dist-info → cite_agent-1.3.8.dist-info}/WHEEL +0 -0
  35. {cite_agent-1.3.6.dist-info → cite_agent-1.3.8.dist-info}/entry_points.txt +0 -0
  36. {cite_agent-1.3.6.dist-info → cite_agent-1.3.8.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cite-agent
3
- Version: 1.3.6
3
+ Version: 1.3.8
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=47xEhOdVR5Y8-pZH8aVP6Z2UhhY8jGWTQ-rJHt5fIeU,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=X-MMgAAezDjIyb3Ro29BeO9zE5-iz-hqjTVhZIBp_GQ,225001
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.8.dist-info/licenses/LICENSE,sha256=XJkyO4IymhSUniN1ENY6lLrL2729gn_rbRlFK6_Hi9M,1074
27
+ cite_agent-1.3.8.dist-info/METADATA,sha256=VIfBiOvuEVFBm81jIoNOUsWbOfGq7Rk2jNAPDbzqGaY,12231
28
+ cite_agent-1.3.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ cite_agent-1.3.8.dist-info/entry_points.txt,sha256=bJ0u28nFIxQKH1PWQ2ak4PV-FAjhoxTC7YADEdDenFw,83
30
+ cite_agent-1.3.8.dist-info/top_level.txt,sha256=NNfD8pxDZzBK8tjDIpCs2BW9Va-OQ5qUFbEx0SgmyIE,11
31
+ cite_agent-1.3.8.dist-info/RECORD,,
@@ -1,57 +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=5ZbAQtod5QalTI1C2N07edlxplzG_Q2XvGOSyOok4uA,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=Qq0Gt7sNVR4R2ue10KFZElOPYrujBG9xTOqy2qetxL4,35562
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=hOL17pDKQdD1MJZRXkjEBlqyNmTdA_pcxIkQEojysFM,172282
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.6.dist-info/licenses/LICENSE,sha256=XJkyO4IymhSUniN1ENY6lLrL2729gn_rbRlFK6_Hi9M,1074
27
- src/__init__.py,sha256=0eEpjRfjRjOTilP66y-AbGNslBsVYr_clE-bZUzsX7s,40
28
- src/services/__init__.py,sha256=pTGLCH_84mz4nGtYMwQES5w-LzoSulUtx_uuNM6r-LA,4257
29
- src/services/simple_enhanced_main.py,sha256=IJoOplCqcVUg3GvN_BRyAhpGrLm_WEPy2jmHcNCY6R0,9257
30
- src/services/auth_service/__init__.py,sha256=VVFfBUr_GMJuxVH_553D2PZmZ9vhHeab9_qiJEf-g6Q,38
31
- src/services/auth_service/auth_manager.py,sha256=MJdWFE36R_htoyBbjgGSTSx2Py61sTM3lhBjXBZ4Bog,873
32
- src/services/graph/__init__.py,sha256=jheRQ-x652RZ68fKyUqUNGXmTAJsp5URVMhlOauFRO0,29
33
- src/services/graph/knowledge_graph.py,sha256=ips2IpVpxDFkdPku4XKgZNRnoR2NjZqZk3xbIArJaaM,7348
34
- src/services/llm_service/__init__.py,sha256=eNAsQpJtVXpJENb-gHtpKzWpncnHHAMB05EI48wrugQ,122
35
- src/services/llm_service/llm_manager.py,sha256=6o5KN-3wJ0hT8PS9hPMpTGS6G9SlleSzYsXZQRjj_vI,21027
36
- src/services/paper_service/__init__.py,sha256=0ONhTf_3H81l5y6EqHMRZd5dCXLAXDa-gbYwge84zKA,142
37
- src/services/paper_service/openalex.py,sha256=pPhPcHMK2gQJCUVPB4ujE8xya0UqUvfcN95cy5ooP68,8801
38
- src/services/performance_service/__init__.py,sha256=48bYfW4pzf-FG9644kTnNwGyD1tJJ7tVn3cD3r_ZAbk,65
39
- src/services/performance_service/rust_performance.py,sha256=n-FzJ98XslmpUAkmmuaunYDTPz-9ZY-qL4oWAoBAaoA,15558
40
- src/services/research_service/__init__.py,sha256=ZCBzSUdstHqwMmJ1x0kJK4PkRlv9OrSOEFeQFoVM-7M,813
41
- src/services/research_service/chatbot.py,sha256=12pVAoe_fd2RXi6_cP-fxfRnWyJStsyn8znVu5cy9qo,91153
42
- src/services/research_service/citation_manager.py,sha256=vzyVivBS0_9IiFE-wOH9hiLiC-fpHmiaZpR1084DenE,16586
43
- src/services/research_service/context_manager.py,sha256=FGbeylLWKvgoA5fElyiqg5IhnMBIZ-t3w0oDHN4Zy1E,61332
44
- src/services/research_service/conversation_manager.py,sha256=-rdzURzu-SiqozyeQLid5a5lS-KzIqGDozdE8BG-DTs,22854
45
- src/services/research_service/critical_paper_detector.py,sha256=gc3oZHB8RqDhxFqJx21NoKLcHmmqHXRo0eXY-AL5KSc,21941
46
- src/services/research_service/enhanced_research.py,sha256=5B8zZjJ2iSLEgnjfyDKow5x_MLRANLJdMbLmmPR5Lc0,4268
47
- src/services/research_service/enhanced_synthesizer.py,sha256=puJg2C10KXryCMPkec-chC4rxbIJdFFswo7w4rbaXkc,16603
48
- src/services/research_service/query_generator.py,sha256=LcFTGsewE6l2LRgUI2E6fXAcpy4vaYaUFFfZhI_WlYU,30707
49
- src/services/research_service/synthesizer.py,sha256=lCcu37PWhWVNphHKaJJDIC-JQ5OINAN7OJ7iV9BWAvM,52557
50
- src/services/search_service/__init__.py,sha256=UZFXdd7r6wietQ2kESXEyGffdfBbpghquecQde7auF4,137
51
- src/services/search_service/indexer.py,sha256=u3-uwdAfmahWWsdebDF9i8XIyp7YtUMIHzlmBLBnPPM,7252
52
- src/services/search_service/search_engine.py,sha256=S9HqQ_mk-8W4d4MUOgBbEGQGV29-eSuceSFvVb4Xk-k,12500
53
- cite_agent-1.3.6.dist-info/METADATA,sha256=Nw2biNkpNAmCACEe_2dJ5dGy-KvL7DtxfFuNhgNWIN4,12231
54
- cite_agent-1.3.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
55
- cite_agent-1.3.6.dist-info/entry_points.txt,sha256=bJ0u28nFIxQKH1PWQ2ak4PV-FAjhoxTC7YADEdDenFw,83
56
- cite_agent-1.3.6.dist-info/top_level.txt,sha256=TgOFqJTIy8vDZuOoYA2QgagkqZtfhM5Acvt_IsWzAKo,15
57
- cite_agent-1.3.6.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"]