Update Python version requirements and fix compatibility issues
- Bump minimum Python version from 3.11 to 3.13 across all apps - Add Python 3.11-3.13 test matrix to CLI workflow - Document Python 3.11+ requirement in .env.example - Fix Starlette Broadcast removal with in-process fallback implementation - Add _InProcessBroadcast class for tests when Starlette Broadcast is unavailable - Refactor API key validators to read live settings instead of cached values - Update database models with explicit
This commit is contained in:
627
cli/aitbc_cli/commands/agent.py
Normal file
627
cli/aitbc_cli/commands/agent.py
Normal file
@@ -0,0 +1,627 @@
|
||||
"""Agent commands for AITBC CLI - Advanced AI Agent Management"""
|
||||
|
||||
import click
|
||||
import httpx
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
from typing import Optional, Dict, Any, List
|
||||
from pathlib import Path
|
||||
from ..utils import output, error, success, warning
|
||||
|
||||
|
||||
@click.group()
|
||||
def agent():
|
||||
"""Advanced AI agent workflow and execution management"""
|
||||
pass
|
||||
|
||||
|
||||
@agent.command()
|
||||
@click.option("--name", required=True, help="Agent workflow name")
|
||||
@click.option("--description", default="", help="Agent description")
|
||||
@click.option("--workflow-file", type=click.File('r'), help="Workflow definition from JSON file")
|
||||
@click.option("--verification", default="basic", type=click.Choice(["basic", "full", "zero-knowledge"]),
|
||||
help="Verification level for agent execution")
|
||||
@click.option("--max-execution-time", default=3600, help="Maximum execution time in seconds")
|
||||
@click.option("--max-cost-budget", default=0.0, help="Maximum cost budget")
|
||||
@click.pass_context
|
||||
def create(ctx, name: str, description: str, workflow_file, verification: str,
|
||||
max_execution_time: int, max_cost_budget: float):
|
||||
"""Create a new AI agent workflow"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
# Build workflow data
|
||||
workflow_data = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"verification_level": verification,
|
||||
"max_execution_time": max_execution_time,
|
||||
"max_cost_budget": max_cost_budget
|
||||
}
|
||||
|
||||
if workflow_file:
|
||||
try:
|
||||
workflow_spec = json.load(workflow_file)
|
||||
workflow_data.update(workflow_spec)
|
||||
except Exception as e:
|
||||
error(f"Failed to read workflow file: {e}")
|
||||
return
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/agents/workflows",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=workflow_data
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
workflow = response.json()
|
||||
success(f"Agent workflow created: {workflow['id']}")
|
||||
output(workflow, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to create agent workflow: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@agent.command()
|
||||
@click.option("--type", "agent_type", help="Filter by agent type")
|
||||
@click.option("--status", help="Filter by status")
|
||||
@click.option("--verification", help="Filter by verification level")
|
||||
@click.option("--limit", default=20, help="Number of agents to list")
|
||||
@click.option("--owner", help="Filter by owner ID")
|
||||
@click.pass_context
|
||||
def list(ctx, agent_type: Optional[str], status: Optional[str],
|
||||
verification: Optional[str], limit: int, owner: Optional[str]):
|
||||
"""List available AI agent workflows"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {"limit": limit}
|
||||
if agent_type:
|
||||
params["type"] = agent_type
|
||||
if status:
|
||||
params["status"] = status
|
||||
if verification:
|
||||
params["verification"] = verification
|
||||
if owner:
|
||||
params["owner"] = owner
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/agents/workflows",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
workflows = response.json()
|
||||
output(workflows, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to list agent workflows: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@agent.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--inputs", type=click.File('r'), help="Input data from JSON file")
|
||||
@click.option("--verification", default="basic", type=click.Choice(["basic", "full", "zero-knowledge"]),
|
||||
help="Verification level for this execution")
|
||||
@click.option("--priority", default="normal", type=click.Choice(["low", "normal", "high"]),
|
||||
help="Execution priority")
|
||||
@click.option("--timeout", default=3600, help="Execution timeout in seconds")
|
||||
@click.pass_context
|
||||
def execute(ctx, agent_id: str, inputs, verification: str, priority: str, timeout: int):
|
||||
"""Execute an AI agent workflow"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
# Prepare execution data
|
||||
execution_data = {
|
||||
"verification_level": verification,
|
||||
"priority": priority,
|
||||
"timeout_seconds": timeout
|
||||
}
|
||||
|
||||
if inputs:
|
||||
try:
|
||||
input_data = json.load(inputs)
|
||||
execution_data["inputs"] = input_data
|
||||
except Exception as e:
|
||||
error(f"Failed to read inputs file: {e}")
|
||||
return
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/agents/{agent_id}/execute",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=execution_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
execution = response.json()
|
||||
success(f"Agent execution started: {execution['id']}")
|
||||
output(execution, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start agent execution: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@agent.command()
|
||||
@click.argument("execution_id")
|
||||
@click.option("--watch", is_flag=True, help="Watch execution status in real-time")
|
||||
@click.option("--interval", default=5, help="Watch interval in seconds")
|
||||
@click.pass_context
|
||||
def status(ctx, execution_id: str, watch: bool, interval: int):
|
||||
"""Get status of agent execution"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
def get_status():
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/agents/executions/{execution_id}",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
error(f"Failed to get execution status: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
return None
|
||||
|
||||
if watch:
|
||||
click.echo(f"Watching execution {execution_id} (Ctrl+C to stop)...")
|
||||
while True:
|
||||
status_data = get_status()
|
||||
if status_data:
|
||||
click.clear()
|
||||
click.echo(f"Execution Status: {status_data.get('status', 'Unknown')}")
|
||||
click.echo(f"Progress: {status_data.get('progress', 0)}%")
|
||||
click.echo(f"Current Step: {status_data.get('current_step', 'N/A')}")
|
||||
click.echo(f"Cost: ${status_data.get('total_cost', 0.0):.4f}")
|
||||
|
||||
if status_data.get('status') in ['completed', 'failed']:
|
||||
break
|
||||
|
||||
time.sleep(interval)
|
||||
else:
|
||||
status_data = get_status()
|
||||
if status_data:
|
||||
output(status_data, ctx.obj['output_format'])
|
||||
|
||||
|
||||
@agent.command()
|
||||
@click.argument("execution_id")
|
||||
@click.option("--verify", is_flag=True, help="Verify cryptographic receipt")
|
||||
@click.option("--download", type=click.Path(), help="Download receipt to file")
|
||||
@click.pass_context
|
||||
def receipt(ctx, execution_id: str, verify: bool, download: Optional[str]):
|
||||
"""Get verifiable receipt for completed execution"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/agents/executions/{execution_id}/receipt",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
receipt_data = response.json()
|
||||
|
||||
if verify:
|
||||
# Verify receipt
|
||||
verify_response = client.post(
|
||||
f"{config.coordinator_url}/v1/agents/receipts/verify",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json={"receipt": receipt_data}
|
||||
)
|
||||
|
||||
if verify_response.status_code == 200:
|
||||
verification_result = verify_response.json()
|
||||
receipt_data["verification"] = verification_result
|
||||
|
||||
if verification_result.get("valid"):
|
||||
success("Receipt verification: PASSED")
|
||||
else:
|
||||
warning("Receipt verification: FAILED")
|
||||
else:
|
||||
warning("Could not verify receipt")
|
||||
|
||||
if download:
|
||||
with open(download, 'w') as f:
|
||||
json.dump(receipt_data, f, indent=2)
|
||||
success(f"Receipt downloaded to {download}")
|
||||
else:
|
||||
output(receipt_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get execution receipt: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def network():
|
||||
"""Multi-agent collaborative network management"""
|
||||
pass
|
||||
|
||||
|
||||
@agent.add_command(network)
|
||||
|
||||
|
||||
@network.command()
|
||||
@click.option("--name", required=True, help="Network name")
|
||||
@click.option("--agents", required=True, help="Comma-separated list of agent IDs")
|
||||
@click.option("--description", default="", help="Network description")
|
||||
@click.option("--coordination", default="centralized",
|
||||
type=click.Choice(["centralized", "decentralized", "hybrid"]),
|
||||
help="Coordination strategy")
|
||||
@click.pass_context
|
||||
def create(ctx, name: str, agents: str, description: str, coordination: str):
|
||||
"""Create collaborative agent network"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
agent_ids = [agent_id.strip() for agent_id in agents.split(',')]
|
||||
|
||||
network_data = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"agents": agent_ids,
|
||||
"coordination_strategy": coordination
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/agents/networks",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=network_data
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
network = response.json()
|
||||
success(f"Agent network created: {network['id']}")
|
||||
output(network, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to create agent network: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@network.command()
|
||||
@click.argument("network_id")
|
||||
@click.option("--task", type=click.File('r'), required=True, help="Task definition JSON file")
|
||||
@click.option("--priority", default="normal", type=click.Choice(["low", "normal", "high"]),
|
||||
help="Execution priority")
|
||||
@click.pass_context
|
||||
def execute(ctx, network_id: str, task, priority: str):
|
||||
"""Execute collaborative task on agent network"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
task_data = json.load(task)
|
||||
except Exception as e:
|
||||
error(f"Failed to read task file: {e}")
|
||||
return
|
||||
|
||||
execution_data = {
|
||||
"task": task_data,
|
||||
"priority": priority
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/agents/networks/{network_id}/execute",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=execution_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
execution = response.json()
|
||||
success(f"Network execution started: {execution['id']}")
|
||||
output(execution, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start network execution: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@network.command()
|
||||
@click.argument("network_id")
|
||||
@click.option("--metrics", default="all", help="Comma-separated metrics to show")
|
||||
@click.option("--real-time", is_flag=True, help="Show real-time metrics")
|
||||
@click.pass_context
|
||||
def status(ctx, network_id: str, metrics: str, real_time: bool):
|
||||
"""Get agent network status and performance metrics"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {}
|
||||
if metrics != "all":
|
||||
params["metrics"] = metrics
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/agents/networks/{network_id}/status",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
status_data = response.json()
|
||||
output(status_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get network status: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@network.command()
|
||||
@click.argument("network_id")
|
||||
@click.option("--objective", default="efficiency",
|
||||
type=click.Choice(["speed", "efficiency", "cost", "quality"]),
|
||||
help="Optimization objective")
|
||||
@click.pass_context
|
||||
def optimize(ctx, network_id: str, objective: str):
|
||||
"""Optimize agent network collaboration"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
optimization_data = {"objective": objective}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/agents/networks/{network_id}/optimize",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=optimization_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Network optimization completed")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to optimize network: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def learning():
|
||||
"""Agent adaptive learning and training management"""
|
||||
pass
|
||||
|
||||
|
||||
@agent.add_command(learning)
|
||||
|
||||
|
||||
@learning.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--mode", default="reinforcement",
|
||||
type=click.Choice(["reinforcement", "transfer", "meta"]),
|
||||
help="Learning mode")
|
||||
@click.option("--feedback-source", help="Feedback data source")
|
||||
@click.option("--learning-rate", default=0.001, help="Learning rate")
|
||||
@click.pass_context
|
||||
def enable(ctx, agent_id: str, mode: str, feedback_source: Optional[str], learning_rate: float):
|
||||
"""Enable adaptive learning for agent"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
learning_config = {
|
||||
"mode": mode,
|
||||
"learning_rate": learning_rate
|
||||
}
|
||||
|
||||
if feedback_source:
|
||||
learning_config["feedback_source"] = feedback_source
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/agents/{agent_id}/learning/enable",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=learning_config
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Adaptive learning enabled for agent {agent_id}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to enable learning: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@learning.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--feedback", type=click.File('r'), required=True, help="Feedback data JSON file")
|
||||
@click.option("--epochs", default=10, help="Number of training epochs")
|
||||
@click.pass_context
|
||||
def train(ctx, agent_id: str, feedback, epochs: int):
|
||||
"""Train agent with feedback data"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
feedback_data = json.load(feedback)
|
||||
except Exception as e:
|
||||
error(f"Failed to read feedback file: {e}")
|
||||
return
|
||||
|
||||
training_data = {
|
||||
"feedback": feedback_data,
|
||||
"epochs": epochs
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/agents/{agent_id}/learning/train",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=training_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
training = response.json()
|
||||
success(f"Training started: {training['id']}")
|
||||
output(training, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start training: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@learning.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--metrics", default="accuracy,efficiency", help="Comma-separated metrics to show")
|
||||
@click.pass_context
|
||||
def progress(ctx, agent_id: str, metrics: str):
|
||||
"""Review agent learning progress"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {"metrics": metrics}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/agents/{agent_id}/learning/progress",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
progress_data = response.json()
|
||||
output(progress_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get learning progress: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@learning.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--format", default="onnx", type=click.Choice(["onnx", "pickle", "torch"]),
|
||||
help="Export format")
|
||||
@click.option("--output", type=click.Path(), help="Output file path")
|
||||
@click.pass_context
|
||||
def export(ctx, agent_id: str, format: str, output: Optional[str]):
|
||||
"""Export learned agent model"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {"format": format}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/agents/{agent_id}/learning/export",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
if output:
|
||||
with open(output, 'wb') as f:
|
||||
f.write(response.content)
|
||||
success(f"Model exported to {output}")
|
||||
else:
|
||||
# Output metadata about the export
|
||||
export_info = response.headers.get('X-Export-Info', '{}')
|
||||
try:
|
||||
info_data = json.loads(export_info)
|
||||
output(info_data, ctx.obj['output_format'])
|
||||
except:
|
||||
output({"status": "export_ready", "format": format}, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to export model: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--type", required=True,
|
||||
type=click.Choice(["optimization", "feature", "bugfix", "documentation"]),
|
||||
help="Contribution type")
|
||||
@click.option("--description", required=True, help="Contribution description")
|
||||
@click.option("--github-repo", default="oib/AITBC", help="GitHub repository")
|
||||
@click.option("--branch", default="main", help="Target branch")
|
||||
@click.pass_context
|
||||
def submit_contribution(ctx, type: str, description: str, github_repo: str, branch: str):
|
||||
"""Submit contribution to platform via GitHub"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
contribution_data = {
|
||||
"type": type,
|
||||
"description": description,
|
||||
"github_repo": github_repo,
|
||||
"target_branch": branch
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/agents/contributions",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=contribution_data
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
result = response.json()
|
||||
success(f"Contribution submitted: {result['id']}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to submit contribution: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
agent.add_command(submit_contribution)
|
||||
@@ -97,7 +97,7 @@ def status(ctx, node: int):
|
||||
# Map node to RPC URL
|
||||
node_urls = {
|
||||
1: "http://localhost:8082",
|
||||
2: "http://localhost:8081",
|
||||
2: "http://localhost:9080/rpc", # Use RPC API with correct endpoint
|
||||
3: "http://aitbc.keisanki.net/rpc"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ def status(ctx, node: int):
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{rpc_url}/status",
|
||||
f"{rpc_url}/head",
|
||||
timeout=5
|
||||
)
|
||||
|
||||
|
||||
654
cli/aitbc_cli/commands/marketplace_advanced.py
Normal file
654
cli/aitbc_cli/commands/marketplace_advanced.py
Normal file
@@ -0,0 +1,654 @@
|
||||
"""Advanced marketplace commands for AITBC CLI - Enhanced marketplace operations"""
|
||||
|
||||
import click
|
||||
import httpx
|
||||
import json
|
||||
import base64
|
||||
from typing import Optional, Dict, Any, List
|
||||
from pathlib import Path
|
||||
from ..utils import output, error, success, warning
|
||||
|
||||
|
||||
@click.group()
|
||||
def advanced():
|
||||
"""Advanced marketplace operations and analytics"""
|
||||
pass
|
||||
|
||||
|
||||
@click.group()
|
||||
def models():
|
||||
"""Advanced model NFT operations"""
|
||||
pass
|
||||
|
||||
|
||||
advanced.add_command(models)
|
||||
|
||||
|
||||
@models.command()
|
||||
@click.option("--nft-version", default="2.0", help="NFT version filter")
|
||||
@click.option("--category", help="Filter by model category")
|
||||
@click.option("--tags", help="Comma-separated tags to filter")
|
||||
@click.option("--rating-min", type=float, help="Minimum rating filter")
|
||||
@click.option("--limit", default=20, help="Number of models to list")
|
||||
@click.pass_context
|
||||
def list(ctx, nft_version: str, category: Optional[str], tags: Optional[str],
|
||||
rating_min: Optional[float], limit: int):
|
||||
"""List advanced NFT models"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {"nft_version": nft_version, "limit": limit}
|
||||
if category:
|
||||
params["category"] = category
|
||||
if tags:
|
||||
params["tags"] = [t.strip() for t in tags.split(',')]
|
||||
if rating_min:
|
||||
params["rating_min"] = rating_min
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/models",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
models = response.json()
|
||||
output(models, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to list models: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@models.command()
|
||||
@click.option("--model-file", type=click.Path(exists=True), required=True, help="Model file path")
|
||||
@click.option("--metadata", type=click.File('r'), required=True, help="Model metadata JSON file")
|
||||
@click.option("--price", type=float, help="Initial price")
|
||||
@click.option("--royalty", type=float, default=0.0, help="Royalty percentage")
|
||||
@click.option("--supply", default=1, help="NFT supply")
|
||||
@click.pass_context
|
||||
def mint(ctx, model_file: str, metadata, price: Optional[float], royalty: float, supply: int):
|
||||
"""Create model NFT with advanced metadata"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
# Read model file
|
||||
try:
|
||||
with open(model_file, 'rb') as f:
|
||||
model_data = f.read()
|
||||
except Exception as e:
|
||||
error(f"Failed to read model file: {e}")
|
||||
return
|
||||
|
||||
# Read metadata
|
||||
try:
|
||||
metadata_data = json.load(metadata)
|
||||
except Exception as e:
|
||||
error(f"Failed to read metadata file: {e}")
|
||||
return
|
||||
|
||||
nft_data = {
|
||||
"metadata": metadata_data,
|
||||
"royalty_percentage": royalty,
|
||||
"supply": supply
|
||||
}
|
||||
|
||||
if price:
|
||||
nft_data["initial_price"] = price
|
||||
|
||||
files = {
|
||||
"model": model_data
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/models/mint",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
data=nft_data,
|
||||
files=files
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
nft = response.json()
|
||||
success(f"Model NFT minted: {nft['id']}")
|
||||
output(nft, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to mint NFT: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@models.command()
|
||||
@click.argument("nft_id")
|
||||
@click.option("--new-version", type=click.Path(exists=True), required=True, help="New model version file")
|
||||
@click.option("--version-notes", default="", help="Version update notes")
|
||||
@click.option("--compatibility", default="backward",
|
||||
type=click.Choice(["backward", "forward", "breaking"]),
|
||||
help="Compatibility type")
|
||||
@click.pass_context
|
||||
def update(ctx, nft_id: str, new_version: str, version_notes: str, compatibility: str):
|
||||
"""Update model NFT with new version"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
# Read new version file
|
||||
try:
|
||||
with open(new_version, 'rb') as f:
|
||||
version_data = f.read()
|
||||
except Exception as e:
|
||||
error(f"Failed to read version file: {e}")
|
||||
return
|
||||
|
||||
update_data = {
|
||||
"version_notes": version_notes,
|
||||
"compatibility": compatibility
|
||||
}
|
||||
|
||||
files = {
|
||||
"version": version_data
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/models/{nft_id}/update",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
data=update_data,
|
||||
files=files
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Model NFT updated: {result['version']}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to update NFT: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@models.command()
|
||||
@click.argument("nft_id")
|
||||
@click.option("--deep-scan", is_flag=True, help="Perform deep authenticity scan")
|
||||
@click.option("--check-integrity", is_flag=True, help="Check model integrity")
|
||||
@click.option("--verify-performance", is_flag=True, help="Verify performance claims")
|
||||
@click.pass_context
|
||||
def verify(ctx, nft_id: str, deep_scan: bool, check_integrity: bool, verify_performance: bool):
|
||||
"""Verify model authenticity and quality"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
verify_data = {
|
||||
"deep_scan": deep_scan,
|
||||
"check_integrity": check_integrity,
|
||||
"verify_performance": verify_performance
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/models/{nft_id}/verify",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=verify_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
verification = response.json()
|
||||
|
||||
if verification.get("authentic"):
|
||||
success("Model authenticity: VERIFIED")
|
||||
else:
|
||||
warning("Model authenticity: FAILED")
|
||||
|
||||
output(verification, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to verify model: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def analytics():
|
||||
"""Marketplace analytics and insights"""
|
||||
pass
|
||||
|
||||
|
||||
advanced.add_command(analytics)
|
||||
|
||||
|
||||
@analytics.command()
|
||||
@click.option("--period", default="30d", help="Time period (1d, 7d, 30d, 90d)")
|
||||
@click.option("--metrics", default="volume,trends", help="Comma-separated metrics")
|
||||
@click.option("--category", help="Filter by category")
|
||||
@click.option("--format", "output_format", default="json",
|
||||
type=click.Choice(["json", "csv", "pdf"]),
|
||||
help="Output format")
|
||||
@click.pass_context
|
||||
def analytics(ctx, period: str, metrics: str, category: Optional[str], output_format: str):
|
||||
"""Get comprehensive marketplace analytics"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {
|
||||
"period": period,
|
||||
"metrics": [m.strip() for m in metrics.split(',')],
|
||||
"format": output_format
|
||||
}
|
||||
|
||||
if category:
|
||||
params["category"] = category
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/analytics",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
if output_format == "pdf":
|
||||
# Handle PDF download
|
||||
filename = f"marketplace_analytics_{period}.pdf"
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(response.content)
|
||||
success(f"Analytics report downloaded: {filename}")
|
||||
else:
|
||||
analytics_data = response.json()
|
||||
output(analytics_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get analytics: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@analytics.command()
|
||||
@click.argument("model_id")
|
||||
@click.option("--competitors", is_flag=True, help="Include competitor analysis")
|
||||
@click.option("--datasets", default="standard", help="Test datasets to use")
|
||||
@click.option("--iterations", default=100, help="Benchmark iterations")
|
||||
@click.pass_context
|
||||
def benchmark(ctx, model_id: str, competitors: bool, datasets: str, iterations: int):
|
||||
"""Model performance benchmarking"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
benchmark_data = {
|
||||
"competitors": competitors,
|
||||
"datasets": datasets,
|
||||
"iterations": iterations
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/models/{model_id}/benchmark",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=benchmark_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
benchmark = response.json()
|
||||
success(f"Benchmark started: {benchmark['id']}")
|
||||
output(benchmark, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start benchmark: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@analytics.command()
|
||||
@click.option("--category", help="Filter by category")
|
||||
@click.option("--forecast", default="7d", help="Forecast period")
|
||||
@click.option("--confidence", default=0.8, help="Confidence threshold")
|
||||
@click.pass_context
|
||||
def trends(ctx, category: Optional[str], forecast: str, confidence: float):
|
||||
"""Market trend analysis and forecasting"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {
|
||||
"forecast_period": forecast,
|
||||
"confidence_threshold": confidence
|
||||
}
|
||||
|
||||
if category:
|
||||
params["category"] = category
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/trends",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
trends_data = response.json()
|
||||
output(trends_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get trends: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@analytics.command()
|
||||
@click.option("--format", default="pdf", type=click.Choice(["pdf", "html", "json"]),
|
||||
help="Report format")
|
||||
@click.option("--email", help="Email address to send report")
|
||||
@click.option("--sections", default="all", help="Comma-separated report sections")
|
||||
@click.pass_context
|
||||
def report(ctx, format: str, email: Optional[str], sections: str):
|
||||
"""Generate comprehensive marketplace report"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
report_data = {
|
||||
"format": format,
|
||||
"sections": [s.strip() for s in sections.split(',')]
|
||||
}
|
||||
|
||||
if email:
|
||||
report_data["email"] = email
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/reports/generate",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=report_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
report_job = response.json()
|
||||
success(f"Report generation started: {report_job['id']}")
|
||||
output(report_job, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to generate report: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def trading():
|
||||
"""Advanced trading features"""
|
||||
pass
|
||||
|
||||
|
||||
advanced.add_command(trading)
|
||||
|
||||
|
||||
@trading.command()
|
||||
@click.argument("auction_id")
|
||||
@click.option("--amount", type=float, required=True, help="Bid amount")
|
||||
@click.option("--max-auto-bid", type=float, help="Maximum auto-bid amount")
|
||||
@click.option("--proxy", is_flag=True, help="Use proxy bidding")
|
||||
@click.pass_context
|
||||
def bid(ctx, auction_id: str, amount: float, max_auto_bid: Optional[float], proxy: bool):
|
||||
"""Participate in model auction"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
bid_data = {
|
||||
"amount": amount,
|
||||
"proxy_bidding": proxy
|
||||
}
|
||||
|
||||
if max_auto_bid:
|
||||
bid_data["max_auto_bid"] = max_auto_bid
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/auctions/{auction_id}/bid",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=bid_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Bid placed successfully")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to place bid: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@trading.command()
|
||||
@click.argument("model_id")
|
||||
@click.option("--recipients", required=True, help="Comma-separated recipient:percentage pairs")
|
||||
@click.option("--smart-contract", is_flag=True, help="Use smart contract distribution")
|
||||
@click.pass_context
|
||||
def royalties(ctx, model_id: str, recipients: str, smart_contract: bool):
|
||||
"""Create royalty distribution agreement"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
# Parse recipients
|
||||
royalty_recipients = []
|
||||
for recipient in recipients.split(','):
|
||||
if ':' in recipient:
|
||||
address, percentage = recipient.split(':', 1)
|
||||
royalty_recipients.append({
|
||||
"address": address.strip(),
|
||||
"percentage": float(percentage.strip())
|
||||
})
|
||||
|
||||
royalty_data = {
|
||||
"recipients": royalty_recipients,
|
||||
"smart_contract": smart_contract
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/models/{model_id}/royalties",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=royalty_data
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
result = response.json()
|
||||
success(f"Royalty agreement created: {result['id']}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to create royalty agreement: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@trading.command()
|
||||
@click.option("--strategy", default="arbitrage",
|
||||
type=click.Choice(["arbitrage", "trend-following", "mean-reversion", "custom"]),
|
||||
help="Trading strategy")
|
||||
@click.option("--budget", type=float, required=True, help="Trading budget")
|
||||
@click.option("--risk-level", default="medium",
|
||||
type=click.Choice(["low", "medium", "high"]),
|
||||
help="Risk level")
|
||||
@click.option("--config", type=click.File('r'), help="Custom strategy configuration")
|
||||
@click.pass_context
|
||||
def execute(ctx, strategy: str, budget: float, risk_level: str, config):
|
||||
"""Execute complex trading strategy"""
|
||||
config_obj = ctx.obj['config']
|
||||
|
||||
strategy_data = {
|
||||
"strategy": strategy,
|
||||
"budget": budget,
|
||||
"risk_level": risk_level
|
||||
}
|
||||
|
||||
if config:
|
||||
try:
|
||||
custom_config = json.load(config)
|
||||
strategy_data["custom_config"] = custom_config
|
||||
except Exception as e:
|
||||
error(f"Failed to read strategy config: {e}")
|
||||
return
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config_obj.coordinator_url}/v1/marketplace/advanced/trading/execute",
|
||||
headers={"X-Api-Key": config_obj.api_key or ""},
|
||||
json=strategy_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
execution = response.json()
|
||||
success(f"Trading strategy execution started: {execution['id']}")
|
||||
output(execution, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to execute strategy: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def dispute():
|
||||
"""Dispute resolution operations"""
|
||||
pass
|
||||
|
||||
|
||||
advanced.add_command(dispute)
|
||||
|
||||
|
||||
@dispute.command()
|
||||
@click.argument("transaction_id")
|
||||
@click.option("--reason", required=True, help="Dispute reason")
|
||||
@click.option("--evidence", type=click.File('rb'), multiple=True, help="Evidence files")
|
||||
@click.option("--category", default="quality",
|
||||
type=click.Choice(["quality", "delivery", "payment", "fraud", "other"]),
|
||||
help="Dispute category")
|
||||
@click.pass_context
|
||||
def file(ctx, transaction_id: str, reason: str, evidence, category: str):
|
||||
"""File dispute resolution request"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
dispute_data = {
|
||||
"transaction_id": transaction_id,
|
||||
"reason": reason,
|
||||
"category": category
|
||||
}
|
||||
|
||||
files = {}
|
||||
for i, evidence_file in enumerate(evidence):
|
||||
files[f"evidence_{i}"] = evidence_file.read()
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/disputes",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
data=dispute_data,
|
||||
files=files
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
dispute = response.json()
|
||||
success(f"Dispute filed: {dispute['id']}")
|
||||
output(dispute, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to file dispute: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@dispute.command()
|
||||
@click.argument("dispute_id")
|
||||
@click.pass_context
|
||||
def status(ctx, dispute_id: str):
|
||||
"""Get dispute status and progress"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/disputes/{dispute_id}",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
dispute_data = response.json()
|
||||
output(dispute_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get dispute status: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@dispute.command()
|
||||
@click.argument("dispute_id")
|
||||
@click.option("--resolution", required=True, help="Proposed resolution")
|
||||
@click.option("--evidence", type=click.File('rb'), multiple=True, help="Additional evidence")
|
||||
@click.pass_context
|
||||
def resolve(ctx, dispute_id: str, resolution: str, evidence):
|
||||
"""Propose dispute resolution"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
resolution_data = {
|
||||
"resolution": resolution
|
||||
}
|
||||
|
||||
files = {}
|
||||
for i, evidence_file in enumerate(evidence):
|
||||
files[f"evidence_{i}"] = evidence_file.read()
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/marketplace/advanced/disputes/{dispute_id}/resolve",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
data=resolution_data,
|
||||
files=files
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Resolution proposal submitted")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to submit resolution: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
470
cli/aitbc_cli/commands/multimodal.py
Normal file
470
cli/aitbc_cli/commands/multimodal.py
Normal file
@@ -0,0 +1,470 @@
|
||||
"""Multi-modal processing commands for AITBC CLI"""
|
||||
|
||||
import click
|
||||
import httpx
|
||||
import json
|
||||
import base64
|
||||
import mimetypes
|
||||
from typing import Optional, Dict, Any, List
|
||||
from pathlib import Path
|
||||
from ..utils import output, error, success, warning
|
||||
|
||||
|
||||
@click.group()
|
||||
def multimodal():
|
||||
"""Multi-modal agent processing and cross-modal operations"""
|
||||
pass
|
||||
|
||||
|
||||
@multimodal.command()
|
||||
@click.option("--name", required=True, help="Multi-modal agent name")
|
||||
@click.option("--modalities", required=True, help="Comma-separated modalities (text,image,audio,video)")
|
||||
@click.option("--description", default="", help="Agent description")
|
||||
@click.option("--model-config", type=click.File('r'), help="Model configuration JSON file")
|
||||
@click.option("--gpu-acceleration", is_flag=True, help="Enable GPU acceleration")
|
||||
@click.pass_context
|
||||
def agent(ctx, name: str, modalities: str, description: str, model_config, gpu_acceleration: bool):
|
||||
"""Create multi-modal agent"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
modality_list = [mod.strip() for mod in modalities.split(',')]
|
||||
|
||||
agent_data = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"modalities": modality_list,
|
||||
"gpu_acceleration": gpu_acceleration,
|
||||
"agent_type": "multimodal"
|
||||
}
|
||||
|
||||
if model_config:
|
||||
try:
|
||||
config_data = json.load(model_config)
|
||||
agent_data["model_config"] = config_data
|
||||
except Exception as e:
|
||||
error(f"Failed to read model config file: {e}")
|
||||
return
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/multimodal/agents",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=agent_data
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
agent = response.json()
|
||||
success(f"Multi-modal agent created: {agent['id']}")
|
||||
output(agent, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to create multi-modal agent: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@multimodal.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--text", help="Text input")
|
||||
@click.option("--image", type=click.Path(exists=True), help="Image file path")
|
||||
@click.option("--audio", type=click.Path(exists=True), help="Audio file path")
|
||||
@click.option("--video", type=click.Path(exists=True), help="Video file path")
|
||||
@click.option("--output-format", default="json", type=click.Choice(["json", "text", "binary"]),
|
||||
help="Output format for results")
|
||||
@click.pass_context
|
||||
def process(ctx, agent_id: str, text: Optional[str], image: Optional[str],
|
||||
audio: Optional[str], video: Optional[str], output_format: str):
|
||||
"""Process multi-modal inputs with agent"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
# Prepare multi-modal data
|
||||
modal_data = {}
|
||||
|
||||
if text:
|
||||
modal_data["text"] = text
|
||||
|
||||
if image:
|
||||
try:
|
||||
with open(image, 'rb') as f:
|
||||
image_data = f.read()
|
||||
modal_data["image"] = {
|
||||
"data": base64.b64encode(image_data).decode(),
|
||||
"mime_type": mimetypes.guess_type(image)[0] or "image/jpeg",
|
||||
"filename": Path(image).name
|
||||
}
|
||||
except Exception as e:
|
||||
error(f"Failed to read image file: {e}")
|
||||
return
|
||||
|
||||
if audio:
|
||||
try:
|
||||
with open(audio, 'rb') as f:
|
||||
audio_data = f.read()
|
||||
modal_data["audio"] = {
|
||||
"data": base64.b64encode(audio_data).decode(),
|
||||
"mime_type": mimetypes.guess_type(audio)[0] or "audio/wav",
|
||||
"filename": Path(audio).name
|
||||
}
|
||||
except Exception as e:
|
||||
error(f"Failed to read audio file: {e}")
|
||||
return
|
||||
|
||||
if video:
|
||||
try:
|
||||
with open(video, 'rb') as f:
|
||||
video_data = f.read()
|
||||
modal_data["video"] = {
|
||||
"data": base64.b64encode(video_data).decode(),
|
||||
"mime_type": mimetypes.guess_type(video)[0] or "video/mp4",
|
||||
"filename": Path(video).name
|
||||
}
|
||||
except Exception as e:
|
||||
error(f"Failed to read video file: {e}")
|
||||
return
|
||||
|
||||
if not modal_data:
|
||||
error("At least one modality input must be provided")
|
||||
return
|
||||
|
||||
process_data = {
|
||||
"modalities": modal_data,
|
||||
"output_format": output_format
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/multimodal/agents/{agent_id}/process",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=process_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success("Multi-modal processing completed")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to process multi-modal inputs: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@multimodal.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--dataset", default="coco_vqa", help="Dataset name for benchmarking")
|
||||
@click.option("--metrics", default="accuracy,latency", help="Comma-separated metrics to evaluate")
|
||||
@click.option("--iterations", default=100, help="Number of benchmark iterations")
|
||||
@click.pass_context
|
||||
def benchmark(ctx, agent_id: str, dataset: str, metrics: str, iterations: int):
|
||||
"""Benchmark multi-modal agent performance"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
benchmark_data = {
|
||||
"dataset": dataset,
|
||||
"metrics": [m.strip() for m in metrics.split(',')],
|
||||
"iterations": iterations
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/multimodal/agents/{agent_id}/benchmark",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=benchmark_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
benchmark = response.json()
|
||||
success(f"Benchmark started: {benchmark['id']}")
|
||||
output(benchmark, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start benchmark: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@multimodal.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--objective", default="throughput",
|
||||
type=click.Choice(["throughput", "latency", "accuracy", "efficiency"]),
|
||||
help="Optimization objective")
|
||||
@click.option("--target", help="Target value for optimization")
|
||||
@click.pass_context
|
||||
def optimize(ctx, agent_id: str, objective: str, target: Optional[str]):
|
||||
"""Optimize multi-modal agent pipeline"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
optimization_data = {"objective": objective}
|
||||
if target:
|
||||
optimization_data["target"] = target
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/multimodal/agents/{agent_id}/optimize",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=optimization_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Multi-modal optimization completed")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to optimize agent: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def convert():
|
||||
"""Cross-modal conversion operations"""
|
||||
pass
|
||||
|
||||
|
||||
multimodal.add_command(convert)
|
||||
|
||||
|
||||
@convert.command()
|
||||
@click.option("--input", "input_path", required=True, type=click.Path(exists=True), help="Input file path")
|
||||
@click.option("--output", "output_format", required=True,
|
||||
type=click.Choice(["text", "image", "audio", "video"]),
|
||||
help="Output modality")
|
||||
@click.option("--model", default="blip", help="Conversion model to use")
|
||||
@click.option("--output-file", type=click.Path(), help="Output file path")
|
||||
@click.pass_context
|
||||
def convert(ctx, input_path: str, output_format: str, model: str, output_file: Optional[str]):
|
||||
"""Convert between modalities"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
# Read input file
|
||||
try:
|
||||
with open(input_path, 'rb') as f:
|
||||
input_data = f.read()
|
||||
except Exception as e:
|
||||
error(f"Failed to read input file: {e}")
|
||||
return
|
||||
|
||||
conversion_data = {
|
||||
"input": {
|
||||
"data": base64.b64encode(input_data).decode(),
|
||||
"mime_type": mimetypes.guess_type(input_path)[0] or "application/octet-stream",
|
||||
"filename": Path(input_path).name
|
||||
},
|
||||
"output_modality": output_format,
|
||||
"model": model
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/multimodal/convert",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=conversion_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
|
||||
if output_file and result.get("output_data"):
|
||||
# Decode and save output
|
||||
output_data = base64.b64decode(result["output_data"])
|
||||
with open(output_file, 'wb') as f:
|
||||
f.write(output_data)
|
||||
success(f"Conversion output saved to {output_file}")
|
||||
else:
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to convert modality: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def search():
|
||||
"""Multi-modal search operations"""
|
||||
pass
|
||||
|
||||
|
||||
multimodal.add_command(search)
|
||||
|
||||
|
||||
@search.command()
|
||||
@click.argument("query")
|
||||
@click.option("--modalities", default="image,text", help="Comma-separated modalities to search")
|
||||
@click.option("--limit", default=20, help="Number of results to return")
|
||||
@click.option("--threshold", default=0.5, help="Similarity threshold")
|
||||
@click.pass_context
|
||||
def search(ctx, query: str, modalities: str, limit: int, threshold: float):
|
||||
"""Multi-modal search across different modalities"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
search_data = {
|
||||
"query": query,
|
||||
"modalities": [m.strip() for m in modalities.split(',')],
|
||||
"limit": limit,
|
||||
"threshold": threshold
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/multimodal/search",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=search_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
results = response.json()
|
||||
output(results, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to perform multi-modal search: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def attention():
|
||||
"""Cross-modal attention analysis"""
|
||||
pass
|
||||
|
||||
|
||||
multimodal.add_command(attention)
|
||||
|
||||
|
||||
@attention.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--inputs", type=click.File('r'), required=True, help="Multi-modal inputs JSON file")
|
||||
@click.option("--visualize", is_flag=True, help="Generate attention visualization")
|
||||
@click.option("--output", type=click.Path(), help="Output file for visualization")
|
||||
@click.pass_context
|
||||
def attention(ctx, agent_id: str, inputs, visualize: bool, output: Optional[str]):
|
||||
"""Analyze cross-modal attention patterns"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
inputs_data = json.load(inputs)
|
||||
except Exception as e:
|
||||
error(f"Failed to read inputs file: {e}")
|
||||
return
|
||||
|
||||
attention_data = {
|
||||
"inputs": inputs_data,
|
||||
"visualize": visualize
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/multimodal/agents/{agent_id}/attention",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=attention_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
|
||||
if visualize and output and result.get("visualization"):
|
||||
# Save visualization
|
||||
viz_data = base64.b64decode(result["visualization"])
|
||||
with open(output, 'wb') as f:
|
||||
f.write(viz_data)
|
||||
success(f"Attention visualization saved to {output}")
|
||||
else:
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to analyze attention: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@multimodal.command()
|
||||
@click.argument("agent_id")
|
||||
@click.pass_context
|
||||
def capabilities(ctx, agent_id: str):
|
||||
"""List multi-modal agent capabilities"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/multimodal/agents/{agent_id}/capabilities",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
capabilities = response.json()
|
||||
output(capabilities, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get agent capabilities: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@multimodal.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--modality", required=True,
|
||||
type=click.Choice(["text", "image", "audio", "video"]),
|
||||
help="Modality to test")
|
||||
@click.option("--test-data", type=click.File('r'), help="Test data JSON file")
|
||||
@click.pass_context
|
||||
def test(ctx, agent_id: str, modality: str, test_data):
|
||||
"""Test individual modality processing"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
test_input = {}
|
||||
if test_data:
|
||||
try:
|
||||
test_input = json.load(test_data)
|
||||
except Exception as e:
|
||||
error(f"Failed to read test data file: {e}")
|
||||
return
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/multimodal/agents/{agent_id}/test/{modality}",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=test_input
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Modality test completed for {modality}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to test modality: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
604
cli/aitbc_cli/commands/openclaw.py
Normal file
604
cli/aitbc_cli/commands/openclaw.py
Normal file
@@ -0,0 +1,604 @@
|
||||
"""OpenClaw integration commands for AITBC CLI"""
|
||||
|
||||
import click
|
||||
import httpx
|
||||
import json
|
||||
import time
|
||||
from typing import Optional, Dict, Any, List
|
||||
from ..utils import output, error, success, warning
|
||||
|
||||
|
||||
@click.group()
|
||||
def openclaw():
|
||||
"""OpenClaw integration with edge computing deployment"""
|
||||
pass
|
||||
|
||||
|
||||
@click.group()
|
||||
def deploy():
|
||||
"""Agent deployment operations"""
|
||||
pass
|
||||
|
||||
|
||||
openclaw.add_command(deploy)
|
||||
|
||||
|
||||
@deploy.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--region", required=True, help="Deployment region")
|
||||
@click.option("--instances", default=1, help="Number of instances to deploy")
|
||||
@click.option("--instance-type", default="standard", help="Instance type")
|
||||
@click.option("--edge-locations", help="Comma-separated edge locations")
|
||||
@click.option("--auto-scale", is_flag=True, help="Enable auto-scaling")
|
||||
@click.pass_context
|
||||
def deploy(ctx, agent_id: str, region: str, instances: int, instance_type: str,
|
||||
edge_locations: Optional[str], auto_scale: bool):
|
||||
"""Deploy agent to OpenClaw network"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
deployment_data = {
|
||||
"agent_id": agent_id,
|
||||
"region": region,
|
||||
"instances": instances,
|
||||
"instance_type": instance_type,
|
||||
"auto_scale": auto_scale
|
||||
}
|
||||
|
||||
if edge_locations:
|
||||
deployment_data["edge_locations"] = [loc.strip() for loc in edge_locations.split(',')]
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/openclaw/deploy",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=deployment_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
deployment = response.json()
|
||||
success(f"Agent deployment started: {deployment['id']}")
|
||||
output(deployment, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@deploy.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--instances", required=True, type=int, help="New number of instances")
|
||||
@click.option("--auto-scale", is_flag=True, help="Enable auto-scaling")
|
||||
@click.option("--min-instances", default=1, help="Minimum instances for auto-scaling")
|
||||
@click.option("--max-instances", default=10, help="Maximum instances for auto-scaling")
|
||||
@click.pass_context
|
||||
def scale(ctx, deployment_id: str, instances: int, auto_scale: bool, min_instances: int, max_instances: int):
|
||||
"""Scale agent deployment"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
scale_data = {
|
||||
"instances": instances,
|
||||
"auto_scale": auto_scale,
|
||||
"min_instances": min_instances,
|
||||
"max_instances": max_instances
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/openclaw/deployments/{deployment_id}/scale",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=scale_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Deployment scaled successfully")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to scale deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@deploy.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--objective", default="cost",
|
||||
type=click.Choice(["cost", "performance", "latency", "efficiency"]),
|
||||
help="Optimization objective")
|
||||
@click.pass_context
|
||||
def optimize(ctx, deployment_id: str, objective: str):
|
||||
"""Optimize agent deployment"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
optimization_data = {"objective": objective}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/openclaw/deployments/{deployment_id}/optimize",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=optimization_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Deployment optimization completed")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to optimize deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def monitor():
|
||||
"""OpenClaw monitoring operations"""
|
||||
pass
|
||||
|
||||
|
||||
openclaw.add_command(monitor)
|
||||
|
||||
|
||||
@monitor.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--metrics", default="latency,cost", help="Comma-separated metrics to monitor")
|
||||
@click.option("--real-time", is_flag=True, help="Show real-time metrics")
|
||||
@click.option("--interval", default=10, help="Update interval for real-time monitoring")
|
||||
@click.pass_context
|
||||
def monitor(ctx, deployment_id: str, metrics: str, real_time: bool, interval: int):
|
||||
"""Monitor OpenClaw agent performance"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {"metrics": [m.strip() for m in metrics.split(',')]}
|
||||
|
||||
def get_metrics():
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/openclaw/deployments/{deployment_id}/metrics",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
error(f"Failed to get metrics: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
return None
|
||||
|
||||
if real_time:
|
||||
click.echo(f"Monitoring deployment {deployment_id} (Ctrl+C to stop)...")
|
||||
while True:
|
||||
metrics_data = get_metrics()
|
||||
if metrics_data:
|
||||
click.clear()
|
||||
click.echo(f"Deployment ID: {deployment_id}")
|
||||
click.echo(f"Status: {metrics_data.get('status', 'Unknown')}")
|
||||
click.echo(f"Instances: {metrics_data.get('instances', 'N/A')}")
|
||||
|
||||
metrics_list = metrics_data.get('metrics', {})
|
||||
for metric in [m.strip() for m in metrics.split(',')]:
|
||||
if metric in metrics_list:
|
||||
value = metrics_list[metric]
|
||||
click.echo(f"{metric.title()}: {value}")
|
||||
|
||||
if metrics_data.get('status') in ['terminated', 'failed']:
|
||||
break
|
||||
|
||||
time.sleep(interval)
|
||||
else:
|
||||
metrics_data = get_metrics()
|
||||
if metrics_data:
|
||||
output(metrics_data, ctx.obj['output_format'])
|
||||
|
||||
|
||||
@monitor.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.pass_context
|
||||
def status(ctx, deployment_id: str):
|
||||
"""Get deployment status"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/openclaw/deployments/{deployment_id}/status",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
status_data = response.json()
|
||||
output(status_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get deployment status: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def edge():
|
||||
"""Edge computing operations"""
|
||||
pass
|
||||
|
||||
|
||||
openclaw.add_command(edge)
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--locations", required=True, help="Comma-separated edge locations")
|
||||
@click.option("--strategy", default="latency",
|
||||
type=click.Choice(["latency", "cost", "availability", "hybrid"]),
|
||||
help="Edge deployment strategy")
|
||||
@click.option("--replicas", default=1, help="Number of replicas per location")
|
||||
@click.pass_context
|
||||
def deploy(ctx, agent_id: str, locations: str, strategy: str, replicas: int):
|
||||
"""Deploy agent to edge locations"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
edge_data = {
|
||||
"agent_id": agent_id,
|
||||
"locations": [loc.strip() for loc in locations.split(',')],
|
||||
"strategy": strategy,
|
||||
"replicas": replicas
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/openclaw/edge/deploy",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=edge_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
deployment = response.json()
|
||||
success(f"Edge deployment started: {deployment['id']}")
|
||||
output(deployment, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start edge deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.option("--location", help="Filter by location")
|
||||
@click.pass_context
|
||||
def resources(ctx, location: Optional[str]):
|
||||
"""Manage edge resources"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {}
|
||||
if location:
|
||||
params["location"] = location
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/openclaw/edge/resources",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
resources = response.json()
|
||||
output(resources, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get edge resources: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--latency-target", type=int, help="Target latency in milliseconds")
|
||||
@click.option("--cost-budget", type=float, help="Cost budget")
|
||||
@click.option("--availability", type=float, help="Target availability (0.0-1.0)")
|
||||
@click.pass_context
|
||||
def optimize(ctx, deployment_id: str, latency_target: Optional[int],
|
||||
cost_budget: Optional[float], availability: Optional[float]):
|
||||
"""Optimize edge deployment performance"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
optimization_data = {}
|
||||
if latency_target:
|
||||
optimization_data["latency_target_ms"] = latency_target
|
||||
if cost_budget:
|
||||
optimization_data["cost_budget"] = cost_budget
|
||||
if availability:
|
||||
optimization_data["availability_target"] = availability
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/openclaw/edge/deployments/{deployment_id}/optimize",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=optimization_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Edge optimization completed")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to optimize edge deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@edge.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--standards", help="Comma-separated compliance standards")
|
||||
@click.pass_context
|
||||
def compliance(ctx, deployment_id: str, standards: Optional[str]):
|
||||
"""Check edge security compliance"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {}
|
||||
if standards:
|
||||
params["standards"] = [s.strip() for s in standards.split(',')]
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/openclaw/edge/deployments/{deployment_id}/compliance",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
compliance_data = response.json()
|
||||
output(compliance_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to check compliance: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def routing():
|
||||
"""Agent skill routing and job offloading"""
|
||||
pass
|
||||
|
||||
|
||||
openclaw.add_command(routing)
|
||||
|
||||
|
||||
@routing.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.option("--algorithm", default="load-balanced",
|
||||
type=click.Choice(["load-balanced", "skill-based", "cost-based", "latency-based"]),
|
||||
help="Routing algorithm")
|
||||
@click.option("--weights", help="Comma-separated weights for routing factors")
|
||||
@click.pass_context
|
||||
def optimize(ctx, deployment_id: str, algorithm: str, weights: Optional[str]):
|
||||
"""Optimize agent skill routing"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
routing_data = {"algorithm": algorithm}
|
||||
if weights:
|
||||
routing_data["weights"] = [w.strip() for w in weights.split(',')]
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/openclaw/routing/deployments/{deployment_id}/optimize",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=routing_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Routing optimization completed")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to optimize routing: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@routing.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.pass_context
|
||||
def status(ctx, deployment_id: str):
|
||||
"""Get routing status and statistics"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/openclaw/routing/deployments/{deployment_id}/status",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
status_data = response.json()
|
||||
output(status_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get routing status: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def ecosystem():
|
||||
"""OpenClaw ecosystem development"""
|
||||
pass
|
||||
|
||||
|
||||
openclaw.add_command(ecosystem)
|
||||
|
||||
|
||||
@ecosystem.command()
|
||||
@click.option("--name", required=True, help="Solution name")
|
||||
@click.option("--type", required=True,
|
||||
type=click.Choice(["agent", "workflow", "integration", "tool"]),
|
||||
help="Solution type")
|
||||
@click.option("--description", default="", help="Solution description")
|
||||
@click.option("--package", type=click.File('rb'), help="Solution package file")
|
||||
@click.pass_context
|
||||
def create(ctx, name: str, type: str, description: str, package):
|
||||
"""Create OpenClaw ecosystem solution"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
solution_data = {
|
||||
"name": name,
|
||||
"type": type,
|
||||
"description": description
|
||||
}
|
||||
|
||||
files = {}
|
||||
if package:
|
||||
files["package"] = package.read()
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/openclaw/ecosystem/solutions",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
data=solution_data,
|
||||
files=files
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
solution = response.json()
|
||||
success(f"OpenClaw solution created: {solution['id']}")
|
||||
output(solution, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to create solution: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@ecosystem.command()
|
||||
@click.option("--type", help="Filter by solution type")
|
||||
@click.option("--category", help="Filter by category")
|
||||
@click.option("--limit", default=20, help="Number of solutions to list")
|
||||
@click.pass_context
|
||||
def list(ctx, type: Optional[str], category: Optional[str], limit: int):
|
||||
"""List OpenClaw ecosystem solutions"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {"limit": limit}
|
||||
if type:
|
||||
params["type"] = type
|
||||
if category:
|
||||
params["category"] = category
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/openclaw/ecosystem/solutions",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
solutions = response.json()
|
||||
output(solutions, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to list solutions: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@ecosystem.command()
|
||||
@click.argument("solution_id")
|
||||
@click.pass_context
|
||||
def install(ctx, solution_id: str):
|
||||
"""Install OpenClaw ecosystem solution"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/openclaw/ecosystem/solutions/{solution_id}/install",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Solution installed successfully")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to install solution: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@openclaw.command()
|
||||
@click.argument("deployment_id")
|
||||
@click.pass_context
|
||||
def terminate(ctx, deployment_id: str):
|
||||
"""Terminate OpenClaw deployment"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
if not click.confirm(f"Terminate deployment {deployment_id}? This action cannot be undone."):
|
||||
click.echo("Operation cancelled")
|
||||
return
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.delete(
|
||||
f"{config.coordinator_url}/v1/openclaw/deployments/{deployment_id}",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Deployment {deployment_id} terminated")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to terminate deployment: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
518
cli/aitbc_cli/commands/optimize.py
Normal file
518
cli/aitbc_cli/commands/optimize.py
Normal file
@@ -0,0 +1,518 @@
|
||||
"""Autonomous optimization commands for AITBC CLI"""
|
||||
|
||||
import click
|
||||
import httpx
|
||||
import json
|
||||
import time
|
||||
from typing import Optional, Dict, Any, List
|
||||
from ..utils import output, error, success, warning
|
||||
|
||||
|
||||
@click.group()
|
||||
def optimize():
|
||||
"""Autonomous optimization and predictive operations"""
|
||||
pass
|
||||
|
||||
|
||||
@click.group()
|
||||
def self_opt():
|
||||
"""Self-optimization operations"""
|
||||
pass
|
||||
|
||||
|
||||
optimize.add_command(self_opt)
|
||||
|
||||
|
||||
@self_opt.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--mode", default="auto-tune",
|
||||
type=click.Choice(["auto-tune", "self-healing", "performance"]),
|
||||
help="Optimization mode")
|
||||
@click.option("--scope", default="full",
|
||||
type=click.Choice(["full", "performance", "cost", "latency"]),
|
||||
help="Optimization scope")
|
||||
@click.option("--aggressiveness", default="moderate",
|
||||
type=click.Choice(["conservative", "moderate", "aggressive"]),
|
||||
help="Optimization aggressiveness")
|
||||
@click.pass_context
|
||||
def enable(ctx, agent_id: str, mode: str, scope: str, aggressiveness: str):
|
||||
"""Enable autonomous optimization for agent"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
optimization_config = {
|
||||
"mode": mode,
|
||||
"scope": scope,
|
||||
"aggressiveness": aggressiveness
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/optimize/agents/{agent_id}/enable",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=optimization_config
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Autonomous optimization enabled for agent {agent_id}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to enable optimization: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@self_opt.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--metrics", default="performance,cost", help="Comma-separated metrics to monitor")
|
||||
@click.option("--real-time", is_flag=True, help="Show real-time optimization status")
|
||||
@click.option("--interval", default=10, help="Update interval for real-time monitoring")
|
||||
@click.pass_context
|
||||
def status(ctx, agent_id: str, metrics: str, real_time: bool, interval: int):
|
||||
"""Monitor optimization progress and status"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {"metrics": [m.strip() for m in metrics.split(',')]}
|
||||
|
||||
def get_status():
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/optimize/agents/{agent_id}/status",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
error(f"Failed to get optimization status: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
return None
|
||||
|
||||
if real_time:
|
||||
click.echo(f"Monitoring optimization for agent {agent_id} (Ctrl+C to stop)...")
|
||||
while True:
|
||||
status_data = get_status()
|
||||
if status_data:
|
||||
click.clear()
|
||||
click.echo(f"Optimization Status: {status_data.get('status', 'Unknown')}")
|
||||
click.echo(f"Mode: {status_data.get('mode', 'N/A')}")
|
||||
click.echo(f"Progress: {status_data.get('progress', 0)}%")
|
||||
|
||||
metrics_data = status_data.get('metrics', {})
|
||||
for metric in [m.strip() for m in metrics.split(',')]:
|
||||
if metric in metrics_data:
|
||||
value = metrics_data[metric]
|
||||
click.echo(f"{metric.title()}: {value}")
|
||||
|
||||
if status_data.get('status') in ['completed', 'failed', 'disabled']:
|
||||
break
|
||||
|
||||
time.sleep(interval)
|
||||
else:
|
||||
status_data = get_status()
|
||||
if status_data:
|
||||
output(status_data, ctx.obj['output_format'])
|
||||
|
||||
|
||||
@self_opt.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--targets", required=True, help="Comma-separated target metrics (e.g., latency:100ms,cost:0.5)")
|
||||
@click.option("--priority", default="balanced",
|
||||
type=click.Choice(["performance", "cost", "balanced"]),
|
||||
help="Optimization priority")
|
||||
@click.pass_context
|
||||
def objectives(ctx, agent_id: str, targets: str, priority: str):
|
||||
"""Set optimization objectives and targets"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
# Parse targets
|
||||
target_dict = {}
|
||||
for target in targets.split(','):
|
||||
if ':' in target:
|
||||
key, value = target.split(':', 1)
|
||||
target_dict[key.strip()] = value.strip()
|
||||
else:
|
||||
target_dict[target.strip()] = "optimize"
|
||||
|
||||
objectives_data = {
|
||||
"targets": target_dict,
|
||||
"priority": priority
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/optimize/agents/{agent_id}/objectives",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=objectives_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Optimization objectives set for agent {agent_id}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to set objectives: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@self_opt.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--priority", default="all",
|
||||
type=click.Choice(["high", "medium", "low", "all"]),
|
||||
help="Filter recommendations by priority")
|
||||
@click.option("--category", help="Filter by category (performance, cost, security)")
|
||||
@click.pass_context
|
||||
def recommendations(ctx, agent_id: str, priority: str, category: Optional[str]):
|
||||
"""Get optimization recommendations"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {}
|
||||
if priority != "all":
|
||||
params["priority"] = priority
|
||||
if category:
|
||||
params["category"] = category
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/optimize/agents/{agent_id}/recommendations",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
recommendations = response.json()
|
||||
output(recommendations, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get recommendations: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@self_opt.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--recommendation-id", required=True, help="Specific recommendation ID to apply")
|
||||
@click.option("--confirm", is_flag=True, help="Apply without confirmation prompt")
|
||||
@click.pass_context
|
||||
def apply(ctx, agent_id: str, recommendation_id: str, confirm: bool):
|
||||
"""Apply optimization recommendation"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
if not confirm:
|
||||
if not click.confirm(f"Apply recommendation {recommendation_id} to agent {agent_id}?"):
|
||||
click.echo("Operation cancelled")
|
||||
return
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/optimize/agents/{agent_id}/apply/{recommendation_id}",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Optimization recommendation applied")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to apply recommendation: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def predict():
|
||||
"""Predictive operations"""
|
||||
pass
|
||||
|
||||
|
||||
optimize.add_command(predict)
|
||||
|
||||
|
||||
@predict.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--horizon", default=24, help="Prediction horizon in hours")
|
||||
@click.option("--resources", default="gpu,memory", help="Comma-separated resources to predict")
|
||||
@click.option("--confidence", default=0.8, help="Minimum confidence threshold")
|
||||
@click.pass_context
|
||||
def predict(ctx, agent_id: str, horizon: int, resources: str, confidence: float):
|
||||
"""Predict resource needs and usage patterns"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
prediction_data = {
|
||||
"horizon_hours": horizon,
|
||||
"resources": [r.strip() for r in resources.split(',')],
|
||||
"confidence_threshold": confidence
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/predict/agents/{agent_id}/resources",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=prediction_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
predictions = response.json()
|
||||
success("Resource prediction completed")
|
||||
output(predictions, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to generate predictions: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@predict.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--policy", default="cost-efficiency",
|
||||
type=click.Choice(["cost-efficiency", "performance", "availability", "hybrid"]),
|
||||
help="Auto-scaling policy")
|
||||
@click.option("--min-instances", default=1, help="Minimum number of instances")
|
||||
@click.option("--max-instances", default=10, help="Maximum number of instances")
|
||||
@click.option("--cooldown", default=300, help="Cooldown period in seconds")
|
||||
@click.pass_context
|
||||
def autoscale(ctx, agent_id: str, policy: str, min_instances: int, max_instances: int, cooldown: int):
|
||||
"""Configure auto-scaling based on predictions"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
autoscale_config = {
|
||||
"policy": policy,
|
||||
"min_instances": min_instances,
|
||||
"max_instances": max_instances,
|
||||
"cooldown_seconds": cooldown
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/predict/agents/{agent_id}/autoscale",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=autoscale_config
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Auto-scaling configured for agent {agent_id}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to configure auto-scaling: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@predict.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--metric", required=True, help="Metric to forecast (throughput, latency, cost, etc.)")
|
||||
@click.option("--period", default=7, help="Forecast period in days")
|
||||
@click.option("--granularity", default="hour",
|
||||
type=click.Choice(["minute", "hour", "day", "week"]),
|
||||
help="Forecast granularity")
|
||||
@click.pass_context
|
||||
def forecast(ctx, agent_id: str, metric: str, period: int, granularity: str):
|
||||
"""Generate performance forecasts"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
forecast_params = {
|
||||
"metric": metric,
|
||||
"period_days": period,
|
||||
"granularity": granularity
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/predict/agents/{agent_id}/forecast",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=forecast_params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
forecast_data = response.json()
|
||||
success(f"Forecast generated for {metric}")
|
||||
output(forecast_data, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to generate forecast: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@click.group()
|
||||
def tune():
|
||||
"""Auto-tuning operations"""
|
||||
pass
|
||||
|
||||
|
||||
optimize.add_command(tune)
|
||||
|
||||
|
||||
@tune.command()
|
||||
@click.argument("agent_id")
|
||||
@click.option("--parameters", help="Comma-separated parameters to tune")
|
||||
@click.option("--objective", default="performance", help="Optimization objective")
|
||||
@click.option("--iterations", default=100, help="Number of tuning iterations")
|
||||
@click.pass_context
|
||||
def auto(ctx, agent_id: str, parameters: Optional[str], objective: str, iterations: int):
|
||||
"""Start automatic parameter tuning"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
tuning_data = {
|
||||
"objective": objective,
|
||||
"iterations": iterations
|
||||
}
|
||||
|
||||
if parameters:
|
||||
tuning_data["parameters"] = [p.strip() for p in parameters.split(',')]
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/tune/agents/{agent_id}/auto",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=tuning_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
tuning = response.json()
|
||||
success(f"Auto-tuning started: {tuning['id']}")
|
||||
output(tuning, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start auto-tuning: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@tune.command()
|
||||
@click.argument("tuning_id")
|
||||
@click.option("--watch", is_flag=True, help="Watch tuning progress")
|
||||
@click.pass_context
|
||||
def status(ctx, tuning_id: str, watch: bool):
|
||||
"""Get auto-tuning status"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
def get_status():
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/tune/sessions/{tuning_id}",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
error(f"Failed to get tuning status: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
return None
|
||||
|
||||
if watch:
|
||||
click.echo(f"Watching tuning session {tuning_id} (Ctrl+C to stop)...")
|
||||
while True:
|
||||
status_data = get_status()
|
||||
if status_data:
|
||||
click.clear()
|
||||
click.echo(f"Tuning Status: {status_data.get('status', 'Unknown')}")
|
||||
click.echo(f"Progress: {status_data.get('progress', 0)}%")
|
||||
click.echo(f"Iteration: {status_data.get('current_iteration', 0)}/{status_data.get('total_iterations', 0)}")
|
||||
click.echo(f"Best Score: {status_data.get('best_score', 'N/A')}")
|
||||
|
||||
if status_data.get('status') in ['completed', 'failed', 'cancelled']:
|
||||
break
|
||||
|
||||
time.sleep(5)
|
||||
else:
|
||||
status_data = get_status()
|
||||
if status_data:
|
||||
output(status_data, ctx.obj['output_format'])
|
||||
|
||||
|
||||
@tune.command()
|
||||
@click.argument("tuning_id")
|
||||
@click.pass_context
|
||||
def results(ctx, tuning_id: str):
|
||||
"""Get auto-tuning results and best parameters"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/tune/sessions/{tuning_id}/results",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
results = response.json()
|
||||
output(results, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to get tuning results: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@optimize.command()
|
||||
@click.argument("agent_id")
|
||||
@click.pass_context
|
||||
def disable(ctx, agent_id: str):
|
||||
"""Disable autonomous optimization for agent"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/optimize/agents/{agent_id}/disable",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Autonomous optimization disabled for agent {agent_id}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to disable optimization: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
246
cli/aitbc_cli/commands/swarm.py
Normal file
246
cli/aitbc_cli/commands/swarm.py
Normal file
@@ -0,0 +1,246 @@
|
||||
"""Swarm intelligence commands for AITBC CLI"""
|
||||
|
||||
import click
|
||||
import httpx
|
||||
import json
|
||||
from typing import Optional, Dict, Any, List
|
||||
from ..utils import output, error, success, warning
|
||||
|
||||
|
||||
@click.group()
|
||||
def swarm():
|
||||
"""Swarm intelligence and collective optimization"""
|
||||
pass
|
||||
|
||||
|
||||
@swarm.command()
|
||||
@click.option("--role", required=True,
|
||||
type=click.Choice(["load-balancer", "resource-optimizer", "task-coordinator", "monitor"]),
|
||||
help="Swarm role")
|
||||
@click.option("--capability", required=True, help="Agent capability")
|
||||
@click.option("--region", help="Operating region")
|
||||
@click.option("--priority", default="normal",
|
||||
type=click.Choice(["low", "normal", "high"]),
|
||||
help="Swarm priority")
|
||||
@click.pass_context
|
||||
def join(ctx, role: str, capability: str, region: Optional[str], priority: str):
|
||||
"""Join agent swarm for collective optimization"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
swarm_data = {
|
||||
"role": role,
|
||||
"capability": capability,
|
||||
"priority": priority
|
||||
}
|
||||
|
||||
if region:
|
||||
swarm_data["region"] = region
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/swarm/join",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=swarm_data
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
result = response.json()
|
||||
success(f"Joined swarm: {result['swarm_id']}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to join swarm: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@swarm.command()
|
||||
@click.option("--task", required=True, help="Swarm task type")
|
||||
@click.option("--collaborators", type=int, default=5, help="Number of collaborators")
|
||||
@click.option("--strategy", default="consensus",
|
||||
type=click.Choice(["consensus", "leader-election", "distributed"]),
|
||||
help="Coordination strategy")
|
||||
@click.option("--timeout", default=3600, help="Task timeout in seconds")
|
||||
@click.pass_context
|
||||
def coordinate(ctx, task: str, collaborators: int, strategy: str, timeout: int):
|
||||
"""Coordinate swarm task execution"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
coordination_data = {
|
||||
"task": task,
|
||||
"collaborators": collaborators,
|
||||
"strategy": strategy,
|
||||
"timeout_seconds": timeout
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/swarm/coordinate",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=coordination_data
|
||||
)
|
||||
|
||||
if response.status_code == 202:
|
||||
result = response.json()
|
||||
success(f"Swarm coordination started: {result['task_id']}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to start coordination: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@swarm.command()
|
||||
@click.option("--swarm-id", help="Filter by swarm ID")
|
||||
@click.option("--status", help="Filter by status")
|
||||
@click.option("--limit", default=20, help="Number of swarms to list")
|
||||
@click.pass_context
|
||||
def list(ctx, swarm_id: Optional[str], status: Optional[str], limit: int):
|
||||
"""List active swarms"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
params = {"limit": limit}
|
||||
if swarm_id:
|
||||
params["swarm_id"] = swarm_id
|
||||
if status:
|
||||
params["status"] = status
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/swarm/list",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
params=params
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
swarms = response.json()
|
||||
output(swarms, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to list swarms: {response.status_code}")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@swarm.command()
|
||||
@click.argument("task_id")
|
||||
@click.option("--real-time", is_flag=True, help="Show real-time progress")
|
||||
@click.option("--interval", default=10, help="Update interval for real-time monitoring")
|
||||
@click.pass_context
|
||||
def status(ctx, task_id: str, real_time: bool, interval: int):
|
||||
"""Get swarm task status"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
def get_status():
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.get(
|
||||
f"{config.coordinator_url}/v1/swarm/tasks/{task_id}/status",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
error(f"Failed to get task status: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
return None
|
||||
|
||||
if real_time:
|
||||
click.echo(f"Monitoring swarm task {task_id} (Ctrl+C to stop)...")
|
||||
while True:
|
||||
status_data = get_status()
|
||||
if status_data:
|
||||
click.clear()
|
||||
click.echo(f"Task ID: {task_id}")
|
||||
click.echo(f"Status: {status_data.get('status', 'Unknown')}")
|
||||
click.echo(f"Progress: {status_data.get('progress', 0)}%")
|
||||
click.echo(f"Collaborators: {status_data.get('active_collaborators', 0)}/{status_data.get('total_collaborators', 0)}")
|
||||
|
||||
if status_data.get('status') in ['completed', 'failed', 'cancelled']:
|
||||
break
|
||||
|
||||
time.sleep(interval)
|
||||
else:
|
||||
status_data = get_status()
|
||||
if status_data:
|
||||
output(status_data, ctx.obj['output_format'])
|
||||
|
||||
|
||||
@swarm.command()
|
||||
@click.argument("swarm_id")
|
||||
@click.pass_context
|
||||
def leave(ctx, swarm_id: str):
|
||||
"""Leave swarm"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
if not click.confirm(f"Leave swarm {swarm_id}?"):
|
||||
click.echo("Operation cancelled")
|
||||
return
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/swarm/{swarm_id}/leave",
|
||||
headers={"X-Api-Key": config.api_key or ""}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Left swarm {swarm_id}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to leave swarm: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
@swarm.command()
|
||||
@click.argument("task_id")
|
||||
@click.option("--consensus-threshold", default=0.7, help="Consensus threshold (0.0-1.0)")
|
||||
@click.pass_context
|
||||
def consensus(ctx, task_id: str, consensus_threshold: float):
|
||||
"""Achieve swarm consensus on task result"""
|
||||
config = ctx.obj['config']
|
||||
|
||||
consensus_data = {
|
||||
"consensus_threshold": consensus_threshold
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
f"{config.coordinator_url}/v1/swarm/tasks/{task_id}/consensus",
|
||||
headers={"X-Api-Key": config.api_key or ""},
|
||||
json=consensus_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
success(f"Consensus achieved: {result.get('consensus_reached', False)}")
|
||||
output(result, ctx.obj['output_format'])
|
||||
else:
|
||||
error(f"Failed to achieve consensus: {response.status_code}")
|
||||
if response.text:
|
||||
error(response.text)
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
error(f"Network error: {e}")
|
||||
ctx.exit(1)
|
||||
@@ -22,6 +22,12 @@ from .commands.config import config
|
||||
from .commands.monitor import monitor
|
||||
from .commands.governance import governance
|
||||
from .commands.exchange import exchange
|
||||
from .commands.agent import agent
|
||||
from .commands.multimodal import multimodal
|
||||
from .commands.optimize import optimize
|
||||
from .commands.openclaw import openclaw
|
||||
from .commands.marketplace_advanced import advanced
|
||||
from .commands.swarm import swarm
|
||||
from .plugins import plugin, load_plugins
|
||||
|
||||
|
||||
@@ -100,6 +106,12 @@ cli.add_command(config)
|
||||
cli.add_command(monitor)
|
||||
cli.add_command(governance)
|
||||
cli.add_command(exchange)
|
||||
cli.add_command(agent)
|
||||
cli.add_command(multimodal)
|
||||
cli.add_command(optimize)
|
||||
cli.add_command(openclaw)
|
||||
cli.add_command(advanced)
|
||||
cli.add_command(swarm)
|
||||
cli.add_command(plugin)
|
||||
load_plugins(cli)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user