FlyBrain-Lab / src /tools /base.py
timfromhcs's picture
FlyBrain v4.1.0 Space build (REAL_SUBGRAPH, CPU-only, honest backend)
3d46076 verified
Raw
History Blame Contribute Delete
3.31 kB
import abc
import time
import uuid
import hashlib
import json
from typing import Dict, Any, Optional
class ToolConnector(abc.ABC):
"""
Abstract Base Class for all typed tool connectors exposed to the brain.
Enforces input schema, output schema, error schema, timeouts, execution IDs,
logging, and result hashes.
"""
def __init__(self, name: str, description: str, timeout_sec: float = 30.0):
self.name = name
self.description = description
self.timeout_sec = timeout_sec
self.execution_history = []
@property
@abc.abstractmethod
def input_schema(self) -> Dict[str, Any]:
pass
@property
@abc.abstractmethod
def output_schema(self) -> Dict[str, Any]:
pass
@property
def error_schema(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"error_code": {"type": "string"},
"message": {"type": "string"},
"recoverable": {"type": "boolean"}
},
"required": ["error_code", "message"]
}
def compute_hash(self, data: Any) -> str:
if isinstance(data, (dict, list)):
serialized = json.dumps(data, sort_keys=True)
elif isinstance(data, bytes):
serialized = data
else:
serialized = str(data)
if isinstance(serialized, str):
serialized = serialized.encode("utf-8")
return hashlib.sha256(serialized).hexdigest()
def run(self, params: Dict[str, Any], execution_id: Optional[str] = None) -> Dict[str, Any]:
exec_id = execution_id or str(uuid.uuid4())
start_time = time.time()
try:
# Validate input params against schema keys
req_keys = self.input_schema.get("required", [])
for k in req_keys:
if k not in params:
raise ValueError(f"Missing required parameter '{k}' for connector '{self.name}'")
result_payload = self._execute(params, exec_id)
duration = time.time() - start_time
res_hash = self.compute_hash(result_payload)
record = {
"execution_id": exec_id,
"tool": self.name,
"status": "SUCCESS",
"duration_sec": round(duration, 4),
"params": params,
"result": result_payload,
"result_hash": res_hash
}
self.execution_history.append(record)
return record
except Exception as e:
duration = time.time() - start_time
record = {
"execution_id": exec_id,
"tool": self.name,
"status": "ERROR",
"duration_sec": round(duration, 4),
"params": params,
"error": {
"error_code": type(e).__name__,
"message": str(e),
"recoverable": True
},
"result_hash": None
}
self.execution_history.append(record)
return record
@abc.abstractmethod
def _execute(self, params: Dict[str, Any], execution_id: str) -> Dict[str, Any]:
pass