feat(tools): Phase T.1-T.4 complete - manifest system, registry, implementations, runtime, collaboration, scheduler
This commit is contained in:
113
backend/app/tools/runtime/python_runtime.py
Normal file
113
backend/app/tools/runtime/python_runtime.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
Python Runtime
|
||||
|
||||
Native Python tool execution runtime.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict
|
||||
|
||||
from tools.runtime.base import BaseRuntime
|
||||
|
||||
|
||||
class PythonRuntime(BaseRuntime):
|
||||
"""Python runtime for executing Python-based tools"""
|
||||
|
||||
def __init__(self):
|
||||
self._executors: Dict[str, Callable] = {}
|
||||
self._modules: Dict[str, Any] = {}
|
||||
|
||||
def get_name(self) -> str:
|
||||
return "python"
|
||||
|
||||
async def validate(self, entry: str) -> bool:
|
||||
"""Validate Python tool entry point"""
|
||||
path = Path(entry)
|
||||
if not path.exists():
|
||||
return False
|
||||
if path.suffix != ".py":
|
||||
return False
|
||||
return True
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
entry: str,
|
||||
command: str,
|
||||
parameters: Dict[str, Any],
|
||||
timeout: int,
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute a Python tool"""
|
||||
try:
|
||||
# Load module dynamically
|
||||
module = self._load_module(entry, command)
|
||||
if module is None:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"Failed to load module from {entry}",
|
||||
}
|
||||
|
||||
# Get the execute function
|
||||
if not hasattr(module, "execute"):
|
||||
return {
|
||||
"status": "error",
|
||||
"error": "Module does not have 'execute' function",
|
||||
}
|
||||
|
||||
execute_func = module.execute
|
||||
|
||||
# Run in executor to avoid blocking
|
||||
loop = asyncio.get_event_loop()
|
||||
result = await asyncio.wait_for(
|
||||
loop.run_in_executor(
|
||||
None,
|
||||
lambda: execute_func(command, parameters),
|
||||
),
|
||||
timeout=timeout / 1000,
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"result": result,
|
||||
}
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"Execution timed out after {timeout}ms",
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
def _load_module(self, entry: str, command: str) -> Any:
|
||||
"""Load Python module from file path"""
|
||||
cache_key = f"{entry}:{command}"
|
||||
if cache_key in self._modules:
|
||||
return self._modules[cache_key]
|
||||
|
||||
try:
|
||||
path = Path(entry)
|
||||
module_name = path.stem
|
||||
|
||||
spec = importlib.util.spec_from_file_location(module_name, entry)
|
||||
if spec is None or spec.loader is None:
|
||||
return None
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
self._modules[cache_key] = module
|
||||
return module
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def clear_cache(self) -> None:
|
||||
"""Clear module cache"""
|
||||
self._modules.clear()
|
||||
Reference in New Issue
Block a user