Files
aitbc/cli/aitbc_cli/commands/optimize.py
oib 825f157749 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
2026-02-24 18:41:08 +01:00

519 lines
18 KiB
Python

"""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)