- Restructure .env.example with security-focused documentation, service-specific environment file references, and AWS Secrets Manager integration - Update CLI tests workflow to single Python 3.13 version, add pytest-mock dependency, and consolidate test execution with coverage - Add comprehensive security validation to package publishing workflow with manual approval gates, secret scanning, and release
628 lines
22 KiB
Python
628 lines
22 KiB
Python
"""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}/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}/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}/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}/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}/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}/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}/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}/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}/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}/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}/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}/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}/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-path", type=click.Path(), help="Output file path")
|
|
@click.pass_context
|
|
def export(ctx, agent_id: str, format: str, output_path: 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}/agents/{agent_id}/learning/export",
|
|
headers={"X-Api-Key": config.api_key or ""},
|
|
params=params
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
if output_path:
|
|
with open(output_path, 'wb') as f:
|
|
f.write(response.content)
|
|
success(f"Model exported to {output_path}")
|
|
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}/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)
|