diff --git a/.gitea/workflows/api-endpoint-tests.yml b/.gitea/workflows/api-endpoint-tests.yml index 8de46419..39e3d133 100644 --- a/.gitea/workflows/api-endpoint-tests.yml +++ b/.gitea/workflows/api-endpoint-tests.yml @@ -42,10 +42,19 @@ jobs: echo "Waiting for AITBC services..." for port in 8000 8001 8003 8006; do for i in $(seq 1 15); do - if curl -sf "http://localhost:$port/health" >/dev/null 2>&1 || \ - curl -sf "http://localhost:$port/api/health" >/dev/null 2>&1 || \ - curl -sf "http://localhost:$port/" >/dev/null 2>&1; then - echo "✅ Port $port ready" + code=$(curl -so /dev/null -w '%{http_code}' "http://localhost:$port/health" 2>/dev/null) || code=0 + if [ "$code" -gt 0 ] && [ "$code" -lt 600 ]; then + echo "✅ Port $port ready (HTTP $code)" + break + fi + code=$(curl -so /dev/null -w '%{http_code}' "http://localhost:$port/api/health" 2>/dev/null) || code=0 + if [ "$code" -gt 0 ] && [ "$code" -lt 600 ]; then + echo "✅ Port $port ready (HTTP $code)" + break + fi + code=$(curl -so /dev/null -w '%{http_code}' "http://localhost:$port/" 2>/dev/null) || code=0 + if [ "$code" -gt 0 ] && [ "$code" -lt 600 ]; then + echo "✅ Port $port ready (HTTP $code)" break fi [ "$i" -eq 15 ] && echo "⚠️ Port $port not ready" diff --git a/.gitea/workflows/integration-tests.yml b/.gitea/workflows/integration-tests.yml index 33629820..0d83a09a 100644 --- a/.gitea/workflows/integration-tests.yml +++ b/.gitea/workflows/integration-tests.yml @@ -46,7 +46,11 @@ jobs: run: | echo "Starting AITBC services..." for svc in aitbc-coordinator-api aitbc-exchange-api aitbc-wallet aitbc-blockchain-rpc aitbc-blockchain-node; do - systemctl start "$svc" 2>/dev/null || echo "⚠️ $svc not available" + if systemctl is-active --quiet "$svc" 2>/dev/null; then + echo "✅ $svc already running" + else + systemctl start "$svc" 2>/dev/null && echo "✅ $svc started" || echo "⚠️ $svc not available" + fi sleep 1 done @@ -55,10 +59,20 @@ jobs: echo "Waiting for services..." for port in 8000 8001 8003 8006; do for i in $(seq 1 15); do - if curl -sf "http://localhost:$port/health" >/dev/null 2>&1 || \ - curl -sf "http://localhost:$port/api/health" >/dev/null 2>&1 || \ - curl -sf "http://localhost:$port/" >/dev/null 2>&1; then - echo "✅ Port $port ready" + code=$(curl -so /dev/null -w '%{http_code}' "http://localhost:$port/health" 2>/dev/null) || code=0 + if [ "$code" -gt 0 ] && [ "$code" -lt 600 ]; then + echo "✅ Port $port ready (HTTP $code)" + break + fi + # Try alternate paths + code=$(curl -so /dev/null -w '%{http_code}' "http://localhost:$port/api/health" 2>/dev/null) || code=0 + if [ "$code" -gt 0 ] && [ "$code" -lt 600 ]; then + echo "✅ Port $port ready (HTTP $code)" + break + fi + code=$(curl -so /dev/null -w '%{http_code}' "http://localhost:$port/" 2>/dev/null) || code=0 + if [ "$code" -gt 0 ] && [ "$code" -lt 600 ]; then + echo "✅ Port $port ready (HTTP $code)" break fi [ "$i" -eq 15 ] && echo "⚠️ Port $port not ready" diff --git a/.gitea/workflows/package-tests.yml b/.gitea/workflows/package-tests.yml index 6d13655e..bd2f60b9 100644 --- a/.gitea/workflows/package-tests.yml +++ b/.gitea/workflows/package-tests.yml @@ -134,13 +134,13 @@ jobs: npm install --legacy-peer-deps 2>/dev/null || npm install 2>/dev/null || true # Build - npm run build 2>/dev/null && echo "✅ Build passed" || echo "⚠️ Build failed" + npm run build && echo "✅ Build passed" || echo "⚠️ Build failed" # Lint npm run lint 2>/dev/null && echo "✅ Lint passed" || echo "⚠️ Lint skipped" # Test - npm test 2>/dev/null && echo "✅ Tests passed" || echo "⚠️ Tests skipped" + npm test && echo "✅ Tests passed" || echo "⚠️ Tests skipped" echo "✅ ${{ matrix.package.name }} completed" diff --git a/.gitea/workflows/python-tests.yml b/.gitea/workflows/python-tests.yml index 5c55a682..6d001274 100644 --- a/.gitea/workflows/python-tests.yml +++ b/.gitea/workflows/python-tests.yml @@ -39,7 +39,7 @@ jobs: source venv/bin/activate pip install -q --upgrade pip setuptools wheel pip install -q -r requirements.txt - pip install -q pytest pytest-asyncio pytest-cov pytest-mock pytest-timeout click + pip install -q pytest pytest-asyncio pytest-cov pytest-mock pytest-timeout click pynacl echo "✅ Python $(python3 --version) environment ready" - name: Run linting diff --git a/.gitea/workflows/smart-contract-tests.yml b/.gitea/workflows/smart-contract-tests.yml index 0a8d142c..2b5f91f8 100644 --- a/.gitea/workflows/smart-contract-tests.yml +++ b/.gitea/workflows/smart-contract-tests.yml @@ -55,11 +55,11 @@ jobs: # Compile if [[ -f "hardhat.config.js" ]] || [[ -f "hardhat.config.ts" ]]; then - npx hardhat compile 2>/dev/null && echo "✅ Compiled" || echo "⚠️ Compile failed" - npx hardhat test 2>/dev/null && echo "✅ Tests passed" || echo "⚠️ Tests failed" + npx hardhat compile && echo "✅ Compiled" || echo "⚠️ Compile failed" + npx hardhat test && echo "✅ Tests passed" || echo "⚠️ Tests failed" elif [[ -f "foundry.toml" ]]; then - forge build 2>/dev/null && echo "✅ Compiled" || echo "⚠️ Compile failed" - forge test 2>/dev/null && echo "✅ Tests passed" || echo "⚠️ Tests failed" + forge build && echo "✅ Compiled" || echo "⚠️ Compile failed" + forge test && echo "✅ Tests passed" || echo "⚠️ Tests failed" else npm run build 2>/dev/null || echo "⚠️ No build script" npm test 2>/dev/null || echo "⚠️ No test script" diff --git a/packages/py/aitbc-agent-sdk/src/aitbc_agent/__init__.py b/packages/py/aitbc-agent-sdk/src/aitbc_agent/__init__.py index 6696f3b6..dc42771d 100755 --- a/packages/py/aitbc-agent-sdk/src/aitbc_agent/__init__.py +++ b/packages/py/aitbc-agent-sdk/src/aitbc_agent/__init__.py @@ -2,7 +2,7 @@ AITBC Agent SDK - Python SDK for AI agents to participate in the AITBC network """ -from .agent import Agent +from .agent import Agent, AITBCAgent from .compute_provider import ComputeProvider from .compute_consumer import ComputeConsumer from .platform_builder import PlatformBuilder @@ -11,8 +11,9 @@ from .swarm_coordinator import SwarmCoordinator __version__ = "1.0.0" __all__ = [ "Agent", - "ComputeProvider", + "AITBCAgent", + "ComputeProvider", "ComputeConsumer", "PlatformBuilder", - "SwarmCoordinator" + "SwarmCoordinator", ] diff --git a/packages/py/aitbc-agent-sdk/src/aitbc_agent/agent.py b/packages/py/aitbc-agent-sdk/src/aitbc_agent/agent.py index dc44ca0e..35b25935 100755 --- a/packages/py/aitbc-agent-sdk/src/aitbc_agent/agent.py +++ b/packages/py/aitbc-agent-sdk/src/aitbc_agent/agent.py @@ -16,123 +16,123 @@ from cryptography.hazmat.primitives.asymmetric import padding logger = logging.getLogger(__name__) + @dataclass class AgentCapabilities: """Agent capability specification""" + compute_type: str # "inference", "training", "processing" gpu_memory: Optional[int] = None supported_models: Optional[List[str]] = None performance_score: float = 0.0 max_concurrent_jobs: int = 1 specialization: Optional[str] = None - + def __post_init__(self) -> None: if self.supported_models is None: self.supported_models = [] + @dataclass class AgentIdentity: """Agent identity and cryptographic keys""" + id: str name: str address: str public_key: str private_key: str - + def sign_message(self, message: Dict[str, Any]) -> str: """Sign a message with agent's private key""" message_str = json.dumps(message, sort_keys=True) private_key = serialization.load_pem_private_key( - self.private_key.encode(), - password=None + self.private_key.encode(), password=None ) - + if not isinstance(private_key, rsa.RSAPrivateKey): raise TypeError("Only RSA private keys are supported") - + signature = private_key.sign( message_str.encode(), padding.PSS( - mgf=padding.MGF1(hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH ), - hashes.SHA256() + hashes.SHA256(), ) - + return signature.hex() - + def verify_signature(self, message: Dict[str, Any], signature: str) -> bool: """Verify a message signature""" message_str = json.dumps(message, sort_keys=True) - public_key = serialization.load_pem_public_key( - self.public_key.encode() - ) - + public_key = serialization.load_pem_public_key(self.public_key.encode()) + if not isinstance(public_key, rsa.RSAPublicKey): raise TypeError("Only RSA public keys are supported") - + try: public_key.verify( bytes.fromhex(signature), message_str.encode(), padding.PSS( mgf=padding.MGF1(hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH + salt_length=padding.PSS.MAX_LENGTH, ), - hashes.SHA256() + hashes.SHA256(), ) return True except Exception: return False + class Agent: """Core AITBC Agent class""" - + def __init__(self, identity: AgentIdentity, capabilities: AgentCapabilities): self.identity = identity self.capabilities = capabilities self.registered = False self.reputation_score = 0.0 self.earnings = 0.0 - + @classmethod - def create(cls, name: str, agent_type: str, capabilities: Dict[str, Any]) -> 'Agent': + def create( + cls, name: str, agent_type: str, capabilities: Dict[str, Any] + ) -> "Agent": """Create a new agent with generated identity""" # Generate cryptographic keys - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=2048 - ) - + private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + public_key = private_key.public_key() private_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption() + encryption_algorithm=serialization.NoEncryption(), ) - + public_pem = public_key.public_bytes( encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo + format=serialization.PublicFormat.SubjectPublicKeyInfo, ) - + # Generate agent identity agent_id = f"agent_{uuid.uuid4().hex[:8]}" address = f"0x{uuid.uuid4().hex[:40]}" - + identity = AgentIdentity( id=agent_id, name=name, address=address, public_key=public_pem.decode(), - private_key=private_pem.decode() + private_key=private_pem.decode(), ) - + # Create capabilities object agent_capabilities = AgentCapabilities(**capabilities) - + return cls(identity, agent_capabilities) - + async def register(self) -> bool: """Register the agent on the AITBC network""" try: @@ -147,27 +147,27 @@ class Agent: "supported_models": self.capabilities.supported_models, "performance_score": self.capabilities.performance_score, "max_concurrent_jobs": self.capabilities.max_concurrent_jobs, - "specialization": self.capabilities.specialization + "specialization": self.capabilities.specialization, }, - "timestamp": datetime.utcnow().isoformat() + "timestamp": datetime.utcnow().isoformat(), } - + # Sign registration data signature = self.identity.sign_message(registration_data) registration_data["signature"] = signature - + # TODO: Submit to AITBC network registration endpoint # For now, simulate successful registration await asyncio.sleep(1) # Simulate network call - + self.registered = True logger.info(f"Agent {self.identity.id} registered successfully") return True - + except Exception as e: logger.error(f"Registration failed: {e}") return False - + async def get_reputation(self) -> Dict[str, float]: """Get agent reputation metrics""" # TODO: Fetch from reputation system @@ -175,14 +175,14 @@ class Agent: "overall_score": self.reputation_score, "job_success_rate": 0.95, "avg_response_time": 30.5, - "client_satisfaction": 4.7 + "client_satisfaction": 4.7, } - + async def update_reputation(self, new_score: float) -> None: """Update agent reputation score""" self.reputation_score = new_score logger.info(f"Reputation updated to {new_score}") - + async def get_earnings(self, period: str = "30d") -> Dict[str, Any]: """Get agent earnings information""" # TODO: Fetch from blockchain/payment system @@ -190,38 +190,42 @@ class Agent: "total": self.earnings, "daily_average": self.earnings / 30, "period": period, - "currency": "AITBC" + "currency": "AITBC", } - - async def send_message(self, recipient_id: str, message_type: str, payload: Dict[str, Any]) -> bool: + + async def send_message( + self, recipient_id: str, message_type: str, payload: Dict[str, Any] + ) -> bool: """Send a message to another agent""" message = { "from": self.identity.id, "to": recipient_id, "type": message_type, "payload": payload, - "timestamp": datetime.utcnow().isoformat() + "timestamp": datetime.utcnow().isoformat(), } - + # Sign message signature = self.identity.sign_message(message) message["signature"] = signature - + # TODO: Send through AITBC agent messaging protocol logger.info(f"Message sent to {recipient_id}: {message_type}") return True - + async def receive_message(self, message: Dict[str, Any]) -> bool: """Process a received message from another agent""" # Verify signature if "signature" not in message: return False - + # TODO: Verify sender's signature # For now, just process the message - logger.info(f"Received message from {message.get('from')}: {message.get('type')}") + logger.info( + f"Received message from {message.get('from')}: {message.get('type')}" + ) return True - + def to_dict(self) -> Dict[str, Any]: """Convert agent to dictionary representation""" return { @@ -234,9 +238,48 @@ class Agent: "supported_models": self.capabilities.supported_models, "performance_score": self.capabilities.performance_score, "max_concurrent_jobs": self.capabilities.max_concurrent_jobs, - "specialization": self.capabilities.specialization + "specialization": self.capabilities.specialization, }, "reputation_score": self.reputation_score, "registered": self.registered, - "earnings": self.earnings + "earnings": self.earnings, } + + +class AITBCAgent: + """High-level convenience wrapper for creating AITBC agents. + + Provides a simple keyword-argument constructor suitable for quick + prototyping and testing without manually building AgentIdentity / + AgentCapabilities objects. + """ + + def __init__( + self, + agent_id: str = "", + compute_type: str = "general", + capabilities: Optional[List[str]] = None, + **kwargs: Any, + ) -> None: + self.agent_id = agent_id + self.compute_type = compute_type + self.capabilities: List[str] = capabilities or [] + self.status = "initialized" + self._extra = kwargs + + # Build a backing Agent for crypto / network operations + self._agent = Agent.create( + name=agent_id, + agent_type=compute_type, + capabilities={"compute_type": compute_type}, + ) + + # Delegate common Agent methods + async def register(self) -> bool: + return await self._agent.register() + + def to_dict(self) -> Dict[str, Any]: + d = self._agent.to_dict() + d["agent_id"] = self.agent_id + d["status"] = self.status + return d diff --git a/packages/py/aitbc-agent-sdk/src/aitbc_agent/compute_consumer.py b/packages/py/aitbc-agent-sdk/src/aitbc_agent/compute_consumer.py index b4ac03f5..c228ab9c 100644 --- a/packages/py/aitbc-agent-sdk/src/aitbc_agent/compute_consumer.py +++ b/packages/py/aitbc-agent-sdk/src/aitbc_agent/compute_consumer.py @@ -15,6 +15,7 @@ logger = logging.getLogger(__name__) @dataclass class JobRequest: """Compute job request specification""" + consumer_id: str job_type: str model_id: Optional[str] = None @@ -28,6 +29,7 @@ class JobRequest: @dataclass class JobResult: """Result from a compute job""" + job_id: str provider_id: str status: str # "completed", "failed", "timeout" @@ -46,9 +48,13 @@ class ComputeConsumer(Agent): self.completed_jobs: List[JobResult] = [] self.total_spent: float = 0.0 - async def submit_job(self, job_type: str, input_data: Dict[str, Any], - requirements: Optional[Dict[str, Any]] = None, - max_price: float = 0.0) -> str: + async def submit_job( + self, + job_type: str, + input_data: Dict[str, Any], + requirements: Optional[Dict[str, Any]] = None, + max_price: float = 0.0, + ) -> str: """Submit a compute job to the network""" job = JobRequest( consumer_id=self.identity.id, diff --git a/packages/py/aitbc-agent-sdk/src/aitbc_agent/compute_provider.py b/packages/py/aitbc-agent-sdk/src/aitbc_agent/compute_provider.py index 48c9d576..4517303a 100755 --- a/packages/py/aitbc-agent-sdk/src/aitbc_agent/compute_provider.py +++ b/packages/py/aitbc-agent-sdk/src/aitbc_agent/compute_provider.py @@ -11,9 +11,11 @@ from .agent import Agent, AgentCapabilities logger = logging.getLogger(__name__) + @dataclass class ResourceOffer: """Resource offering specification""" + provider_id: str compute_type: str gpu_memory: int @@ -23,9 +25,11 @@ class ResourceOffer: max_concurrent_jobs: int quality_guarantee: float = 0.95 + @dataclass class JobExecution: """Job execution tracking""" + job_id: str consumer_id: str start_time: datetime @@ -34,9 +38,10 @@ class JobExecution: status: str = "running" # running, completed, failed quality_score: Optional[float] = None + class ComputeProvider(Agent): """Agent that provides computational resources""" - + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.current_offers: List[ResourceOffer] = [] @@ -45,39 +50,46 @@ class ComputeProvider(Agent): self.utilization_rate: float = 0.0 self.pricing_model: Dict[str, Any] = {} self.dynamic_pricing: Dict[str, Any] = {} - + @classmethod - def create_provider(cls, name: str, capabilities: Dict[str, Any], pricing_model: Dict[str, Any]) -> 'ComputeProvider': + def create_provider( + cls, name: str, capabilities: Dict[str, Any], pricing_model: Dict[str, Any] + ) -> "ComputeProvider": """Create and register a compute provider""" agent = super().create(name, "compute_provider", capabilities) provider = cls(agent.identity, agent.capabilities) provider.pricing_model = pricing_model return provider - - async def offer_resources(self, price_per_hour: float, availability_schedule: Dict[str, Any], max_concurrent_jobs: int = 3) -> bool: + + async def offer_resources( + self, + price_per_hour: float, + availability_schedule: Dict[str, Any], + max_concurrent_jobs: int = 3, + ) -> bool: """Offer computational resources on the marketplace""" try: offer = ResourceOffer( provider_id=self.identity.id, compute_type=self.capabilities.compute_type, gpu_memory=self.capabilities.gpu_memory or 0, - supported_models=self.capabilities.supported_models, + supported_models=self.capabilities.supported_models or [], price_per_hour=price_per_hour, availability_schedule=availability_schedule, - max_concurrent_jobs=max_concurrent_jobs + max_concurrent_jobs=max_concurrent_jobs, ) - + # Submit to marketplace await self._submit_to_marketplace(offer) self.current_offers.append(offer) - + logger.info(f"Resource offer submitted: {price_per_hour} AITBC/hour") return True - + except Exception as e: logger.error(f"Failed to offer resources: {e}") return False - + async def set_availability(self, schedule: Dict[str, Any]) -> bool: """Set availability schedule for resource offerings""" try: @@ -85,15 +97,21 @@ class ComputeProvider(Agent): for offer in self.current_offers: offer.availability_schedule = schedule await self._update_marketplace_offer(offer) - + logger.info("Availability schedule updated") return True - + except Exception as e: logger.error(f"Failed to update availability: {e}") return False - - async def enable_dynamic_pricing(self, base_rate: float, demand_threshold: float = 0.8, max_multiplier: float = 2.0, adjustment_frequency: str = "15min") -> bool: + + async def enable_dynamic_pricing( + self, + base_rate: float, + demand_threshold: float = 0.8, + max_multiplier: float = 2.0, + adjustment_frequency: str = "15min", + ) -> bool: """Enable dynamic pricing based on market demand""" try: self.dynamic_pricing = { @@ -101,149 +119,187 @@ class ComputeProvider(Agent): "demand_threshold": demand_threshold, "max_multiplier": max_multiplier, "adjustment_frequency": adjustment_frequency, - "enabled": True + "enabled": True, } - + # Start dynamic pricing task asyncio.create_task(self._dynamic_pricing_loop()) - + logger.info("Dynamic pricing enabled") return True - + except Exception as e: logger.error(f"Failed to enable dynamic pricing: {e}") return False - + async def _dynamic_pricing_loop(self) -> None: """Background task for dynamic price adjustments""" - while getattr(self, 'dynamic_pricing', {}).get('enabled', False): + while getattr(self, "dynamic_pricing", {}).get("enabled", False): try: # Get current utilization - current_utilization = len(self.active_jobs) / self.capabilities.max_concurrent_jobs - + current_utilization = ( + len(self.active_jobs) / self.capabilities.max_concurrent_jobs + ) + # Adjust pricing based on demand - if current_utilization > self.dynamic_pricing['demand_threshold']: + if current_utilization > self.dynamic_pricing["demand_threshold"]: # High demand - increase price multiplier = min( - 1.0 + (current_utilization - self.dynamic_pricing['demand_threshold']) * 2, - self.dynamic_pricing['max_multiplier'] + 1.0 + + ( + current_utilization + - self.dynamic_pricing["demand_threshold"] + ) + * 2, + self.dynamic_pricing["max_multiplier"], ) else: # Low demand - decrease price - multiplier = max(0.5, current_utilization / self.dynamic_pricing['demand_threshold']) - - new_price = self.dynamic_pricing['base_rate'] * multiplier - + multiplier = max( + 0.5, + current_utilization / self.dynamic_pricing["demand_threshold"], + ) + + new_price = self.dynamic_pricing["base_rate"] * multiplier + # Update marketplace offers for offer in self.current_offers: offer.price_per_hour = new_price await self._update_marketplace_offer(offer) - - logger.debug(f"Dynamic pricing: utilization={current_utilization:.2f}, price={new_price:.3f} AITBC/h") - + + logger.debug( + f"Dynamic pricing: utilization={current_utilization:.2f}, price={new_price:.3f} AITBC/h" + ) + except Exception as e: logger.error(f"Dynamic pricing error: {e}") - + # Wait for next adjustment await asyncio.sleep(900) # 15 minutes - + async def accept_job(self, job_request: Dict[str, Any]) -> bool: """Accept and execute a computational job""" try: # Check capacity if len(self.active_jobs) >= self.capabilities.max_concurrent_jobs: return False - + # Create job execution record job = JobExecution( job_id=job_request["job_id"], consumer_id=job_request["consumer_id"], start_time=datetime.utcnow(), - expected_duration=timedelta(hours=job_request["estimated_hours"]) + expected_duration=timedelta(hours=job_request["estimated_hours"]), ) - + self.active_jobs.append(job) self._update_utilization() - + # Execute job (simulate) asyncio.create_task(self._execute_job(job, job_request)) - + logger.info(f"Job accepted: {job.job_id} from {job.consumer_id}") return True - + except Exception as e: logger.error(f"Failed to accept job: {e}") return False - - async def _execute_job(self, job: JobExecution, job_request: Dict[str, Any]) -> None: + + async def _execute_job( + self, job: JobExecution, job_request: Dict[str, Any] + ) -> None: """Execute a computational job""" try: # Simulate job execution execution_time = timedelta(hours=job_request["estimated_hours"]) await asyncio.sleep(5) # Simulate processing time - + # Update job completion job.actual_duration = execution_time job.status = "completed" job.quality_score = 0.95 # Simulate quality score - + # Calculate earnings earnings = job_request["estimated_hours"] * job_request["agreed_price"] self.earnings += earnings - + # Remove from active jobs self.active_jobs.remove(job) self._update_utilization() - + # Notify consumer await self._notify_job_completion(job, earnings) - + logger.info(f"Job completed: {job.job_id}, earned {earnings} AITBC") - + except Exception as e: job.status = "failed" logger.error(f"Job execution failed: {job.job_id} - {e}") - + async def _notify_job_completion(self, job: JobExecution, earnings: float) -> None: """Notify consumer about job completion""" notification = { "job_id": job.job_id, "status": job.status, "completion_time": datetime.utcnow().isoformat(), - "duration_hours": job.actual_duration.total_seconds() / 3600 if job.actual_duration else None, + "duration_hours": ( + job.actual_duration.total_seconds() / 3600 + if job.actual_duration + else None + ), "quality_score": job.quality_score, - "cost": earnings + "cost": earnings, } - + await self.send_message(job.consumer_id, "job_completion", notification) - + def _update_utilization(self) -> None: """Update current utilization rate""" - self.utilization_rate = len(self.active_jobs) / self.capabilities.max_concurrent_jobs - + self.utilization_rate = ( + len(self.active_jobs) / self.capabilities.max_concurrent_jobs + ) + async def get_performance_metrics(self) -> Dict[str, Any]: """Get provider performance metrics""" completed_jobs = [j for j in self.active_jobs if j.status == "completed"] - + return { "utilization_rate": self.utilization_rate, "active_jobs": len(self.active_jobs), "total_earnings": self.earnings, - "average_job_duration": sum(j.actual_duration.total_seconds() for j in completed_jobs if j.actual_duration) / len(completed_jobs) if completed_jobs else 0, - "quality_score": sum(j.quality_score for j in completed_jobs if j.quality_score is not None) / len(completed_jobs) if completed_jobs else 0, - "current_offers": len(self.current_offers) + "average_job_duration": ( + sum( + j.actual_duration.total_seconds() + for j in completed_jobs + if j.actual_duration + ) + / len(completed_jobs) + if completed_jobs + else 0 + ), + "quality_score": ( + sum( + j.quality_score + for j in completed_jobs + if j.quality_score is not None + ) + / len(completed_jobs) + if completed_jobs + else 0 + ), + "current_offers": len(self.current_offers), } - - async def _submit_to_marketplace(self, offer: ResourceOffer): + + async def _submit_to_marketplace(self, offer: ResourceOffer) -> None: """Submit resource offer to marketplace (placeholder)""" # TODO: Implement actual marketplace submission await asyncio.sleep(0.1) - - async def _update_marketplace_offer(self, offer: ResourceOffer): + + async def _update_marketplace_offer(self, offer: ResourceOffer) -> None: """Update existing marketplace offer (placeholder)""" # TODO: Implement actual marketplace update await asyncio.sleep(0.1) - + @classmethod def assess_capabilities(cls) -> Dict[str, Any]: """Assess available computational capabilities""" @@ -252,5 +308,5 @@ class ComputeProvider(Agent): "gpu_memory": 24, "supported_models": ["llama3.2", "mistral", "deepseek"], "performance_score": 0.95, - "max_concurrent_jobs": 3 + "max_concurrent_jobs": 3, } diff --git a/packages/py/aitbc-agent-sdk/src/aitbc_agent/platform_builder.py b/packages/py/aitbc-agent-sdk/src/aitbc_agent/platform_builder.py index 40ad70e2..cde23bee 100644 --- a/packages/py/aitbc-agent-sdk/src/aitbc_agent/platform_builder.py +++ b/packages/py/aitbc-agent-sdk/src/aitbc_agent/platform_builder.py @@ -25,14 +25,18 @@ class PlatformBuilder: self.config.update(config) return self - def add_provider(self, name: str, capabilities: Dict[str, Any]) -> "PlatformBuilder": + def add_provider( + self, name: str, capabilities: Dict[str, Any] + ) -> "PlatformBuilder": """Add a compute provider agent""" agent = Agent.create(name, "compute_provider", capabilities) self.agents.append(agent) logger.info(f"Added provider: {name}") return self - def add_consumer(self, name: str, capabilities: Dict[str, Any]) -> "PlatformBuilder": + def add_consumer( + self, name: str, capabilities: Dict[str, Any] + ) -> "PlatformBuilder": """Add a compute consumer agent""" agent = Agent.create(name, "compute_consumer", capabilities) self.agents.append(agent) diff --git a/packages/py/aitbc-agent-sdk/src/aitbc_agent/swarm_coordinator.py b/packages/py/aitbc-agent-sdk/src/aitbc_agent/swarm_coordinator.py index d08f9861..fba2c762 100755 --- a/packages/py/aitbc-agent-sdk/src/aitbc_agent/swarm_coordinator.py +++ b/packages/py/aitbc-agent-sdk/src/aitbc_agent/swarm_coordinator.py @@ -12,9 +12,11 @@ from .agent import Agent logger = logging.getLogger(__name__) + @dataclass class SwarmMessage: """Swarm communication message""" + swarm_id: str sender_id: str message_type: str @@ -23,9 +25,11 @@ class SwarmMessage: timestamp: str swarm_signature: str + @dataclass class SwarmDecision: """Collective swarm decision""" + swarm_id: str decision_type: str proposal: Dict[str, Any] @@ -34,20 +38,21 @@ class SwarmDecision: implementation_plan: Dict[str, Any] timestamp: str + class SwarmCoordinator(Agent): """Agent that participates in swarm intelligence""" - + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.joined_swarms: Dict[str, Dict[str, Any]] = {} self.swarm_reputation: Dict[str, float] = {} self.contribution_score = 0.0 - + async def join_swarm(self, swarm_type: str, config: Dict[str, Any]) -> bool: """Join a swarm for collective intelligence""" try: swarm_id = f"{swarm_type}-v1" - + # Register with swarm registration = { "agent_id": self.identity.id, @@ -56,100 +61,106 @@ class SwarmCoordinator(Agent): "capabilities": { "compute_type": self.capabilities.compute_type, "performance_score": self.capabilities.performance_score, - "specialization": self.capabilities.specialization + "specialization": self.capabilities.specialization, }, "contribution_level": config.get("contribution_level", "medium"), - "data_sharing_consent": config.get("data_sharing_consent", True) + "data_sharing_consent": config.get("data_sharing_consent", True), } - + # Sign swarm registration signature = self.identity.sign_message(registration) registration["signature"] = signature - + # Submit to swarm coordinator await self._register_with_swarm(swarm_id, registration) - + # Store swarm membership self.joined_swarms[swarm_id] = { "type": swarm_type, "role": config.get("role", "participant"), "joined_at": datetime.utcnow().isoformat(), "contribution_count": 0, - "last_activity": datetime.utcnow().isoformat() + "last_activity": datetime.utcnow().isoformat(), } - + # Initialize swarm reputation self.swarm_reputation[swarm_id] = 0.5 # Starting reputation - + # Start swarm participation tasks asyncio.create_task(self._swarm_participation_loop(swarm_id)) - - logger.info(f"Joined swarm: {swarm_id} as {config.get('role', 'participant')}") + + logger.info( + f"Joined swarm: {swarm_id} as {config.get('role', 'participant')}" + ) return True - + except Exception as e: logger.error(f"Failed to join swarm {swarm_type}: {e}") return False - + async def _swarm_participation_loop(self, swarm_id: str) -> None: """Background task for active swarm participation""" while swarm_id in self.joined_swarms: try: # Listen for swarm messages await self._process_swarm_messages(swarm_id) - + # Contribute data if enabled swarm_config = self.joined_swarms[swarm_id] if swarm_config.get("data_sharing", True): await self._contribute_swarm_data(swarm_id) - + # Participate in collective decisions await self._participate_in_decisions(swarm_id) - + # Update activity timestamp swarm_config["last_activity"] = datetime.utcnow().isoformat() - + except Exception as e: logger.error(f"Swarm participation error for {swarm_id}: {e}") - + # Wait before next participation cycle await asyncio.sleep(60) # 1 minute - + async def broadcast_to_swarm(self, message: SwarmMessage) -> bool: """Broadcast a message to the swarm""" try: # Verify swarm membership if message.swarm_id not in self.joined_swarms: return False - + # Sign swarm message - swarm_signature = self.identity.sign_message({ - "swarm_id": message.swarm_id, - "sender_id": message.sender_id, - "message_type": message.message_type, - "payload": message.payload, - "timestamp": message.timestamp - }) + swarm_signature = self.identity.sign_message( + { + "swarm_id": message.swarm_id, + "sender_id": message.sender_id, + "message_type": message.message_type, + "payload": message.payload, + "timestamp": message.timestamp, + } + ) message.swarm_signature = swarm_signature - + # Broadcast to swarm network await self._broadcast_to_swarm_network(message) - + # Update contribution count self.joined_swarms[message.swarm_id]["contribution_count"] += 1 - - logger.info(f"Broadcasted to swarm {message.swarm_id}: {message.message_type}") + + logger.info( + f"Broadcasted to swarm {message.swarm_id}: {message.message_type}" + ) return True - + except Exception as e: logger.error(f"Failed to broadcast to swarm: {e}") return False - + async def _contribute_swarm_data(self, swarm_id: str) -> None: """Contribute data to swarm intelligence""" try: swarm_type = self.joined_swarms[swarm_id]["type"] - + if swarm_type == "load_balancing": data = await self._get_load_balancing_data() elif swarm_type == "pricing": @@ -158,7 +169,7 @@ class SwarmCoordinator(Agent): data = await self._get_security_data() else: data = await self._get_general_data() - + message = SwarmMessage( swarm_id=swarm_id, sender_id=self.identity.id, @@ -166,14 +177,14 @@ class SwarmCoordinator(Agent): priority="medium", payload=data, timestamp=datetime.utcnow().isoformat(), - swarm_signature="" # Will be added in broadcast_to_swarm + swarm_signature="", # Will be added in broadcast_to_swarm ) - + await self.broadcast_to_swarm(message) - + except Exception as e: logger.error(f"Failed to contribute swarm data: {e}") - + async def _get_load_balancing_data(self) -> Dict[str, Any]: """Get load balancing data for swarm contribution""" # TODO: Get actual load balancing metrics @@ -183,9 +194,9 @@ class SwarmCoordinator(Agent): "location": "us-west-2", "pricing_trend": "stable", "current_load": 0.6, - "capacity_utilization": 0.8 + "capacity_utilization": 0.8, } - + async def _get_pricing_data(self) -> Dict[str, Any]: """Get pricing data for swarm contribution""" # TODO: Get actual pricing data @@ -194,9 +205,9 @@ class SwarmCoordinator(Agent): "price_trends": "increasing", "resource_constraints": "gpu_memory", "competitive_landscape": "moderate", - "market_volatility": 0.15 + "market_volatility": 0.15, } - + async def _get_security_data(self) -> Dict[str, Any]: """Get security data for swarm contribution""" # TODO: Get actual security metrics @@ -205,21 +216,21 @@ class SwarmCoordinator(Agent): "anomaly_count": 2, "verification_success_rate": 0.98, "network_health": "good", - "security_events": [] + "security_events": [], } - + async def _get_general_data(self) -> Dict[str, Any]: """Get general performance data for swarm contribution""" return { "performance_metrics": { "response_time": 30.5, "success_rate": 0.95, - "quality_score": 0.92 + "quality_score": 0.92, }, "network_status": "healthy", - "agent_status": "active" + "agent_status": "active", } - + async def coordinate_task(self, task: str, collaborators: int) -> Dict[str, Any]: """Coordinate a collaborative task with other agents""" try: @@ -233,20 +244,22 @@ class SwarmCoordinator(Agent): "estimated_duration": "2h", "resource_requirements": { "compute_type": "general", - "min_performance": 0.8 - } + "min_performance": 0.8, + }, } - + # Submit to swarm for coordination coordination_result = await self._submit_coordination_proposal(proposal) - - logger.info(f"Task coordination initiated: {task} with {collaborators} collaborators") + + logger.info( + f"Task coordination initiated: {task} with {collaborators} collaborators" + ) return coordination_result - + except Exception as e: logger.error(f"Failed to coordinate task: {e}") return {"success": False, "error": str(e)} - + async def get_market_intelligence(self) -> Dict[str, Any]: """Get collective market intelligence from swarm""" try: @@ -259,76 +272,85 @@ class SwarmCoordinator(Agent): priority="high", payload={"request_type": "market_intelligence"}, timestamp=datetime.utcnow().isoformat(), - swarm_signature="" + swarm_signature="", ) - + await self.broadcast_to_swarm(intel_request) - + # Wait for intelligence response (simulate) await asyncio.sleep(2) - + return { "demand_forecast": "increasing", "price_trends": "stable_to_rising", "competition_analysis": "moderate", "opportunity_areas": ["specialized_models", "batch_processing"], - "risk_factors": ["gpu_shortages", "price_volatility"] + "risk_factors": ["gpu_shortages", "price_volatility"], } else: return {"error": "Not joined to pricing swarm"} - + except Exception as e: logger.error(f"Failed to get market intelligence: {e}") return {"error": str(e)} - + async def analyze_swarm_benefits(self) -> Dict[str, Any]: """Analyze benefits of swarm participation""" try: # Calculate benefits based on swarm participation total_contributions = sum( - swarm["contribution_count"] - for swarm in self.joined_swarms.values() + swarm["contribution_count"] for swarm in self.joined_swarms.values() ) - - avg_reputation = sum(self.swarm_reputation.values()) / len(self.swarm_reputation) if self.swarm_reputation else 0 - + + avg_reputation = ( + sum(self.swarm_reputation.values()) / len(self.swarm_reputation) + if self.swarm_reputation + else 0 + ) + # Simulate benefit analysis earnings_boost = total_contributions * 0.15 # 15% boost per contribution - utilization_improvement = avg_reputation * 0.25 # 25% utilization improvement - + utilization_improvement = ( + avg_reputation * 0.25 + ) # 25% utilization improvement + return { "earnings_boost": f"{earnings_boost:.1%}", "utilization_improvement": f"{utilization_improvement:.1%}", "total_contributions": total_contributions, "swarm_reputation": avg_reputation, - "joined_swarms": len(self.joined_swarms) + "joined_swarms": len(self.joined_swarms), } - + except Exception as e: logger.error(f"Failed to analyze swarm benefits: {e}") return {"error": str(e)} - - async def _register_with_swarm(self, swarm_id: str, registration: Dict[str, Any]) -> None: + + async def _register_with_swarm( + self, swarm_id: str, registration: Dict[str, Any] + ) -> None: """Register with swarm coordinator (placeholder)""" # TODO: Implement actual swarm registration await asyncio.sleep(0.1) - + async def _broadcast_to_swarm_network(self, message: SwarmMessage) -> None: """Broadcast message to swarm network (placeholder)""" # TODO: Implement actual swarm broadcasting await asyncio.sleep(0.1) - + async def _process_swarm_messages(self, swarm_id: str) -> None: """Process incoming swarm messages (placeholder)""" # TODO: Implement actual message processing await asyncio.sleep(0.1) - + async def _participate_in_decisions(self, swarm_id: str) -> None: """Participate in swarm decision making (placeholder)""" # TODO: Implement actual decision participation await asyncio.sleep(0.1) - - async def _submit_coordination_proposal(self, proposal: Dict[str, Any]) -> Dict[str, Any]: + + async def _submit_coordination_proposal( + self, proposal: Dict[str, Any] + ) -> Dict[str, Any]: """Submit coordination proposal to swarm (placeholder)""" # TODO: Implement actual proposal submission await asyncio.sleep(0.5) @@ -336,5 +358,5 @@ class SwarmCoordinator(Agent): "success": True, "proposal_id": proposal["task_id"], "status": "coordinating", - "expected_collaborators": 5 + "expected_collaborators": 5, } diff --git a/packages/py/aitbc-agent-sdk/tests/test_agent_sdk.py b/packages/py/aitbc-agent-sdk/tests/test_agent_sdk.py index 45450b24..94fe7cdf 100644 --- a/packages/py/aitbc-agent-sdk/tests/test_agent_sdk.py +++ b/packages/py/aitbc-agent-sdk/tests/test_agent_sdk.py @@ -1,76 +1,73 @@ """Test suite for AITBC Agent SDK""" import pytest -from aitbc_agent.agent import AITBCAgent -from aitbc_agent.compute_provider import ComputeProvider -from aitbc_agent.swarm_coordinator import SwarmCoordinator +from aitbc_agent.agent import AITBCAgent, Agent, AgentCapabilities, AgentIdentity class TestAITBCAgent: - """Test AITBC Agent functionality""" - + """Test AITBC Agent high-level wrapper""" + def test_agent_initialization(self): """Test agent can be initialized""" agent = AITBCAgent(agent_id="test-agent") assert agent.agent_id == "test-agent" assert agent.status == "initialized" - + def test_agent_config_validation(self): """Test agent configuration validation""" config = { "agent_id": "test-agent", "compute_type": "gpu", - "capabilities": ["inference", "training"] + "capabilities": ["inference", "training"], } agent = AITBCAgent(**config) assert agent.compute_type == "gpu" assert "inference" in agent.capabilities -class TestComputeProvider: - """Test Compute Provider functionality""" - - def test_provider_registration(self): - """Test provider can register with network""" - provider = ComputeProvider( - provider_id="test-provider", - gpu_count=4, - memory_gb=32 +class TestAgentCore: + """Test core Agent class""" + + def test_create_agent(self): + """Test Agent.create factory""" + agent = Agent.create( + name="provider-1", + agent_type="compute_provider", + capabilities={"compute_type": "inference"}, ) - assert provider.provider_id == "test-provider" - assert provider.gpu_count == 4 - assert provider.memory_gb == 32 - - def test_resource_availability(self): - """Test resource availability reporting""" - provider = ComputeProvider( - provider_id="test-provider", - gpu_count=2, - memory_gb=16 + assert agent.identity.name == "provider-1" + assert agent.capabilities.compute_type == "inference" + assert agent.registered is False + + def test_agent_to_dict(self): + """Test agent serialisation round-trip""" + agent = Agent.create( + name="worker", + agent_type="general", + capabilities={"compute_type": "processing"}, ) - resources = provider.get_available_resources() - assert resources["gpu_count"] == 2 - assert resources["memory_gb"] == 16 + d = agent.to_dict() + assert "id" in d + assert d["capabilities"]["compute_type"] == "processing" + + def test_capabilities_defaults(self): + """Test AgentCapabilities default values""" + caps = AgentCapabilities(compute_type="inference") + assert caps.supported_models == [] + assert caps.max_concurrent_jobs == 1 + assert caps.gpu_memory is None -class TestSwarmCoordinator: - """Test Swarm Coordinator functionality""" - - def test_coordinator_initialization(self): - """Test coordinator initialization""" - coordinator = SwarmCoordinator(coordinator_id="test-coordinator") - assert coordinator.coordinator_id == "test-coordinator" - assert len(coordinator.agents) == 0 - - def test_agent_registration(self): - """Test agent registration with coordinator""" - coordinator = SwarmCoordinator(coordinator_id="test-coordinator") - agent = AITBCAgent(agent_id="test-agent") - - success = coordinator.register_agent(agent) - assert success is True - assert len(coordinator.agents) == 1 - assert "test-agent" in coordinator.agents +class TestImports: + """Verify public API surface""" + + def test_all_exports(self): + import aitbc_agent + for name in ( + "Agent", "AITBCAgent", "ComputeProvider", + "ComputeConsumer", "PlatformBuilder", "SwarmCoordinator", + ): + assert hasattr(aitbc_agent, name), f"Missing export: {name}" if __name__ == "__main__": diff --git a/packages/solidity/aitbc-token/package.json b/packages/solidity/aitbc-token/package.json index 0a17d4d9..5cc420d2 100644 --- a/packages/solidity/aitbc-token/package.json +++ b/packages/solidity/aitbc-token/package.json @@ -36,6 +36,6 @@ "@openzeppelin/contracts": "^5.0.2" }, "engines": { - "node": ">=24.14.0" + "node": ">=18.0.0" } } diff --git a/requirements.txt b/requirements.txt index 137e39a0..764f6db0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,6 +32,7 @@ aiohttp>=3.9.0 # Cryptocurrency & Blockchain cryptography>=46.0.0 +pynacl>=1.5.0 ecdsa>=0.19.0 base58>=2.1.1 web3>=6.11.0 diff --git a/scripts/ci/test_api_endpoints.py b/scripts/ci/test_api_endpoints.py index 2fec80d5..28a5e4b9 100755 --- a/scripts/ci/test_api_endpoints.py +++ b/scripts/ci/test_api_endpoints.py @@ -12,16 +12,9 @@ SERVICES = { "coordinator": {"url": "http://localhost:8000", "endpoints": ["/", "/health", "/info"]}, "exchange": {"url": "http://localhost:8001", "endpoints": ["/", "/api/health", "/health", "/info"]}, "wallet": {"url": "http://localhost:8003", "endpoints": ["/", "/health", "/wallets"]}, - "blockchain_rpc": {"url": "http://localhost:8006", "endpoints": []}, + "blockchain_rpc": {"url": "http://localhost:8006", "endpoints": ["/health", "/rpc/head", "/rpc/info", "/rpc/supply"]}, } -RPC_METHODS = [ - {"method": "eth_blockNumber", "params": []}, - {"method": "eth_getBalance", "params": ["0x0000000000000000000000000000000000000000", "latest"]}, - {"method": "eth_chainId", "params": []}, - {"method": "eth_gasPrice", "params": []}, -] - def test_service_endpoints(name, base_url, endpoints, timeout=5): results = {"service": name, "endpoints": [], "success": True} @@ -41,25 +34,6 @@ def test_service_endpoints(name, base_url, endpoints, timeout=5): return results -def test_rpc(base_url, timeout=5): - results = {"service": "blockchain_rpc", "methods": [], "success": True} - for m in RPC_METHODS: - payload = {"jsonrpc": "2.0", "method": m["method"], "params": m["params"], "id": 1} - try: - r = requests.post(base_url, json=payload, timeout=timeout) - ok = r.status_code == 200 - result_val = r.json().get("result", "N/A") if ok else None - results["methods"].append({"method": m["method"], "status": r.status_code, "result": str(result_val), "success": ok}) - print(f" {'✅' if ok else '❌'} {m['method']}: {result_val}") - if not ok: - results["success"] = False - except Exception as e: - results["methods"].append({"method": m["method"], "error": str(e), "success": False}) - print(f" ❌ {m['method']}: {e}") - results["success"] = False - return results - - def test_performance(apis, rounds=10, timeout=5): results = {} for name, url in apis: @@ -95,10 +69,7 @@ def main(): for name, cfg in SERVICES.items(): print(f"\n🧪 Testing {name}...") - if name == "blockchain_rpc": - r = test_rpc(cfg["url"]) - else: - r = test_service_endpoints(name, cfg["url"], cfg["endpoints"]) + r = test_service_endpoints(name, cfg["url"], cfg["endpoints"]) all_results[name] = r if not r["success"]: overall_ok = False @@ -108,6 +79,7 @@ def main(): ("Coordinator", "http://localhost:8000/health"), ("Exchange", "http://localhost:8001/api/health"), ("Wallet", "http://localhost:8003/health"), + ("Blockchain RPC", "http://localhost:8006/health"), ]) all_results["performance"] = perf