perf(ai): decouple AI suite from startup and enable lazy loading across services and CLI
- Decouple connapp.ai from get_parser() via DeferredAIProxy to queue plugin modifications without importing connpy.ai or Pydantic v2 upfront. - Guard connapp.start() finally block to invoke ai.cleanup() only if connpy.ai was actively imported. - Defer CopilotInterface and AIService imports in core.py to execute strictly upon receiving Copilot hotkey (\x00). - Convert heavy services (ai, sync, users, import_export, system, execution) into lazy properties on ServiceProvider and services/__init__.py. - Implement O(1) globals() dict caching in module __getattr__ (PEP 562) across connpy/__init__.py and services/__init__.py. - Defer Google Drive SDK in sync_service.py and interactive CLI components (inquirer) across handlers. - Reduced CLI startup & parser initialization latency from ~1.47s to ~183ms (~87% improvement).
This commit is contained in:
+8
-2
@@ -282,12 +282,18 @@ from .core import node,nodes
|
|||||||
from .configfile import configfile
|
from .configfile import configfile
|
||||||
from .connapp import connapp
|
from .connapp import connapp
|
||||||
from .api import *
|
from .api import *
|
||||||
from .ai import ai
|
|
||||||
from .plugins import Plugins
|
from .plugins import Plugins
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
from . import printer
|
from . import printer
|
||||||
|
|
||||||
__all__ = ["node", "nodes", "configfile", "connapp", "ai", "Plugins", "printer"]
|
def __getattr__(name: str):
|
||||||
|
if name == "ai":
|
||||||
|
from .ai import ai
|
||||||
|
globals()["ai"] = ai
|
||||||
|
return ai
|
||||||
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
|
||||||
|
__all__ = ["node", "nodes", "configfile", "connapp", "Plugins", "printer"]
|
||||||
__author__ = "Federico Luzzi"
|
__author__ = "Federico Luzzi"
|
||||||
__pdoc__ = {
|
__pdoc__ = {
|
||||||
'core': False,
|
'core': False,
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import sys
|
import sys
|
||||||
from connpy import *
|
from connpy import configfile, connapp
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
conf = configfile()
|
conf = configfile()
|
||||||
|
|||||||
@@ -21,14 +21,15 @@ from prompt_toolkit.history import InMemoryHistory
|
|||||||
|
|
||||||
from ..printer import connpy_theme
|
from ..printer import connpy_theme
|
||||||
from connpy.utils import log_cleaner
|
from connpy.utils import log_cleaner
|
||||||
from ..services.ai_service import AIService
|
|
||||||
|
|
||||||
class CopilotInterface:
|
class CopilotInterface:
|
||||||
def __init__(self, config, history=None, pt_input=None, pt_output=None, rich_file=None, session_state=None):
|
def __init__(self, config, history=None, pt_input=None, pt_output=None, rich_file=None, session_state=None):
|
||||||
|
from ..services.ai_service import AIService
|
||||||
self.config = config
|
self.config = config
|
||||||
self.history = history or InMemoryHistory()
|
self.history = history or InMemoryHistory()
|
||||||
self.pt_input = pt_input
|
self.pt_input = pt_input
|
||||||
self.pt_output = pt_output
|
self.pt_output = pt_output
|
||||||
|
self.rich_file = rich_file
|
||||||
self.ai_service = AIService(config)
|
self.ai_service = AIService(config)
|
||||||
self.mode_range, self.mode_single, self.mode_lines = 0, 1, 2
|
self.mode_range, self.mode_single, self.mode_lines = 0, 1, 2
|
||||||
|
|
||||||
|
|||||||
+35
-9
@@ -10,7 +10,6 @@ from .core import node,nodes
|
|||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
from . import printer
|
from . import printer
|
||||||
from .api import start_api,stop_api,debug_api
|
from .api import start_api,stop_api,debug_api
|
||||||
from .ai import ai
|
|
||||||
|
|
||||||
from .plugins import Plugins
|
from .plugins import Plugins
|
||||||
from .services.exceptions import ConnpyError, ProfileNotFoundError, ReservedNameError
|
from .services.exceptions import ConnpyError, ProfileNotFoundError, ReservedNameError
|
||||||
@@ -41,6 +40,32 @@ console = printer.console
|
|||||||
|
|
||||||
#functions and classes
|
#functions and classes
|
||||||
|
|
||||||
|
class DeferredAIProxy:
|
||||||
|
"""Proxy for connapp.ai that defers importing connpy.ai until ai is actually invoked or accessed."""
|
||||||
|
def __init__(self):
|
||||||
|
self._deferred_modifications = []
|
||||||
|
self._real_ai = None
|
||||||
|
|
||||||
|
def _load_real_ai(self):
|
||||||
|
if self._real_ai is None:
|
||||||
|
from .ai import ai
|
||||||
|
self._real_ai = ai
|
||||||
|
for mod in self._deferred_modifications:
|
||||||
|
self._real_ai.modify(mod)
|
||||||
|
return self._real_ai
|
||||||
|
|
||||||
|
def modify(self, modification_func):
|
||||||
|
if self._real_ai is not None:
|
||||||
|
self._real_ai.modify(modification_func)
|
||||||
|
else:
|
||||||
|
self._deferred_modifications.append(modification_func)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return self._load_real_ai()(*args, **kwargs)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self._load_real_ai(), name)
|
||||||
|
|
||||||
class connapp:
|
class connapp:
|
||||||
''' This class starts the connection manager app. It's normally used by connection manager but you can use it on a script to run the connection manager your way and use a different configfile and key.
|
''' This class starts the connection manager app. It's normally used by connection manager but you can use it on a script to run the connection manager your way and use a different configfile and key.
|
||||||
'''
|
'''
|
||||||
@@ -72,8 +97,8 @@ class connapp:
|
|||||||
self.start_api = start_api
|
self.start_api = start_api
|
||||||
self.stop_api = stop_api # Using SystemService logic eventually
|
self.stop_api = stop_api # Using SystemService logic eventually
|
||||||
self.debug_api = debug_api
|
self.debug_api = debug_api
|
||||||
self.ai = ai
|
self.ai = DeferredAIProxy()
|
||||||
|
|
||||||
# Register context filtering hooks (only on Client CLI, bypass on gRPC Server)
|
# Register context filtering hooks (only on Client CLI, bypass on gRPC Server)
|
||||||
is_api_server = len(sys.argv) > 1 and sys.argv[1] == "api"
|
is_api_server = len(sys.argv) > 1 and sys.argv[1] == "api"
|
||||||
if not is_api_server:
|
if not is_api_server:
|
||||||
@@ -524,12 +549,13 @@ class connapp:
|
|||||||
printer.warning("Operation cancelled by user.")
|
printer.warning("Operation cancelled by user.")
|
||||||
sys.exit(130)
|
sys.exit(130)
|
||||||
finally:
|
finally:
|
||||||
# Safely cleanup AI sessions (litellm)
|
# Safely cleanup AI sessions (litellm) if AI was loaded
|
||||||
try:
|
if "connpy.ai" in sys.modules:
|
||||||
from .ai import cleanup
|
try:
|
||||||
cleanup()
|
from .ai import cleanup
|
||||||
except ImportError:
|
cleanup()
|
||||||
pass
|
except (ImportError, Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class _store_type(argparse.Action):
|
class _store_type(argparse.Action):
|
||||||
#Custom store type for cli app.
|
#Custom store type for cli app.
|
||||||
|
|||||||
+3
-2
@@ -712,13 +712,14 @@ class node:
|
|||||||
|
|
||||||
def _copilot_handler(self, config):
|
def _copilot_handler(self, config):
|
||||||
"""Unified copilot handler for local session."""
|
"""Unified copilot handler for local session."""
|
||||||
from .cli.terminal_ui import CopilotInterface
|
|
||||||
from .services.ai_service import AIService
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
|
||||||
async def handler(buffer, node_info, stream, child_fd, cmd_byte_positions=None):
|
async def handler(buffer, node_info, stream, child_fd, cmd_byte_positions=None):
|
||||||
try:
|
try:
|
||||||
|
from .cli.terminal_ui import CopilotInterface
|
||||||
|
from .services.ai_service import AIService
|
||||||
|
|
||||||
interface = CopilotInterface(
|
interface = CopilotInterface(
|
||||||
config,
|
config,
|
||||||
history=getattr(stream, 'copilot_history', None),
|
history=getattr(stream, 'copilot_history', None),
|
||||||
|
|||||||
@@ -1,26 +1,34 @@
|
|||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .node_service import NodeService
|
from .node_service import NodeService
|
||||||
from .profile_service import ProfileService
|
from .profile_service import ProfileService
|
||||||
from .ai_service import AIService
|
|
||||||
from .plugin_service import PluginService
|
from .plugin_service import PluginService
|
||||||
from .config_service import ConfigService
|
from .config_service import ConfigService
|
||||||
|
|
||||||
def __getattr__(name: str):
|
def __getattr__(name: str):
|
||||||
if name == "ExecutionService":
|
if name == "ExecutionService":
|
||||||
from .execution_service import ExecutionService
|
from .execution_service import ExecutionService
|
||||||
|
globals()["ExecutionService"] = ExecutionService
|
||||||
return ExecutionService
|
return ExecutionService
|
||||||
elif name == "ImportExportService":
|
elif name == "ImportExportService":
|
||||||
from .import_export_service import ImportExportService
|
from .import_export_service import ImportExportService
|
||||||
|
globals()["ImportExportService"] = ImportExportService
|
||||||
return ImportExportService
|
return ImportExportService
|
||||||
elif name == "SystemService":
|
elif name == "SystemService":
|
||||||
from .system_service import SystemService
|
from .system_service import SystemService
|
||||||
|
globals()["SystemService"] = SystemService
|
||||||
return SystemService
|
return SystemService
|
||||||
elif name == "SyncService":
|
elif name == "SyncService":
|
||||||
from .sync_service import SyncService
|
from .sync_service import SyncService
|
||||||
|
globals()["SyncService"] = SyncService
|
||||||
return SyncService
|
return SyncService
|
||||||
elif name == "UserService":
|
elif name == "UserService":
|
||||||
from .user_service import UserService
|
from .user_service import UserService
|
||||||
|
globals()["UserService"] = UserService
|
||||||
return UserService
|
return UserService
|
||||||
|
elif name == "AIService":
|
||||||
|
from .ai_service import AIService
|
||||||
|
globals()["AIService"] = AIService
|
||||||
|
return AIService
|
||||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|||||||
@@ -271,13 +271,13 @@ class PluginService(BaseService):
|
|||||||
is_mock = True
|
is_mock = True
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
from ..core import node, nodes
|
from ..core import node, nodes
|
||||||
from ..ai import ai
|
from ..connapp import DeferredAIProxy
|
||||||
from ..services.provider import ServiceProvider
|
from ..services.provider import ServiceProvider
|
||||||
|
|
||||||
self.config = config
|
self.config = config
|
||||||
self.node = node
|
self.node = node
|
||||||
self.nodes = nodes
|
self.nodes = nodes
|
||||||
self.ai = ai
|
self.ai = DeferredAIProxy()
|
||||||
|
|
||||||
self.services = ServiceProvider(config, mode="local")
|
self.services = ServiceProvider(config, mode="local")
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class ServiceProvider:
|
|||||||
self._import_export = None
|
self._import_export = None
|
||||||
self._sync = None
|
self._sync = None
|
||||||
self._users = None
|
self._users = None
|
||||||
|
self._ai = None
|
||||||
|
|
||||||
if mode == "local":
|
if mode == "local":
|
||||||
self._init_local()
|
self._init_local()
|
||||||
@@ -32,14 +33,12 @@ class ServiceProvider:
|
|||||||
from .profile_service import ProfileService
|
from .profile_service import ProfileService
|
||||||
from .config_service import ConfigService
|
from .config_service import ConfigService
|
||||||
from .plugin_service import PluginService
|
from .plugin_service import PluginService
|
||||||
from .ai_service import AIService
|
|
||||||
from .context_service import ContextService
|
from .context_service import ContextService
|
||||||
|
|
||||||
self.nodes = NodeService(self.config)
|
self.nodes = NodeService(self.config)
|
||||||
self.profiles = ProfileService(self.config)
|
self.profiles = ProfileService(self.config)
|
||||||
self.config_svc = ConfigService(self.config)
|
self.config_svc = ConfigService(self.config)
|
||||||
self.plugins = PluginService(self.config)
|
self.plugins = PluginService(self.config)
|
||||||
self.ai = AIService(self.config)
|
|
||||||
self.context = ContextService(self.config)
|
self.context = ContextService(self.config)
|
||||||
|
|
||||||
def _init_remote(self):
|
def _init_remote(self):
|
||||||
@@ -145,3 +144,14 @@ class ServiceProvider:
|
|||||||
@users.setter
|
@users.setter
|
||||||
def users(self, value):
|
def users(self, value):
|
||||||
self._users = value
|
self._users = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ai(self):
|
||||||
|
if self._ai is None and self.mode == "local":
|
||||||
|
from .ai_service import AIService
|
||||||
|
self._ai = AIService(self.config)
|
||||||
|
return self._ai
|
||||||
|
|
||||||
|
@ai.setter
|
||||||
|
def ai(self, value):
|
||||||
|
self._ai = value
|
||||||
|
|||||||
Reference in New Issue
Block a user