| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859 |
- """
- agentpaas CLI — Command-line interface for managing AgentPaaS platform.
- All commands (except serve/create-tenant/provider) call the AgentPaaS HTTP API.
- Usage:
- agentpaas serve [--dev] [--port 8000]
- agentpaas create-tenant --name "My Org"
- # Agent management
- agentpaas agent list
- agentpaas agent create --name my-agent --config agent.yml
- agentpaas agent get ag_xxx
- agentpaas agent delete ag_xxx
- agentpaas agent update ag_xxx --config new.yml
- agentpaas agent rollback ag_xxx --version 2
- agentpaas agent versions ag_xxx
- # Execution
- agentpaas run ag_xxx "Your input here"
- agentpaas run ag_xxx --input-file task.txt
- agentpaas run ag_xxx "input" --stream
- # Status & monitoring
- agentpaas status
- agentpaas status agents
- agentpaas status agent ag_xxx
- agentpaas health ag_xxx
- # Runs & traces
- agentpaas runs ag_xxx
- agentpaas trace run_xxx
- # Keys
- agentpaas key create --name prod-key
- agentpaas key list
- agentpaas key revoke key_xxx
- # Billing
- agentpaas usage
- agentpaas metrics
- # Provider management
- agentpaas provider list
- agentpaas provider add dashscope --api-key sk-xxx
- agentpaas provider test dashscope
- agentpaas provider models dashscope
- agentpaas provider remove xxx
- """
- from __future__ import annotations
- import argparse
- import json
- import os
- import sys
- import time
- import urllib.request
- import urllib.error
- import yaml
- # ============================================================
- # Config: server URL + API key
- # ============================================================
- CONFIG_FILE = os.path.expanduser("~/.agentpaas/config.json")
- PROVIDERS_FILE = os.path.expanduser("~/.agentpaas/providers.json")
- DEFAULT_PROVIDERS = {
- "anthropic": {"name": "Anthropic", "type": "anthropic", "base_url": "https://api.anthropic.com",
- "models": ["claude-sonnet-4-20250514", "claude-opus-4-20250514"], "env_key": "ANTHROPIC_API_KEY"},
- "openai": {"name": "OpenAI", "type": "openai", "base_url": "https://api.openai.com/v1",
- "models": ["gpt-4o", "gpt-4o-mini"], "env_key": "OPENAI_API_KEY"},
- "dashscope": {"name": "DashScope (Qwen)", "type": "openai_compatible",
- "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
- "models": ["qwen3-max-2026-01-23", "qwen-max", "qwen-plus"], "env_key": "DASHSCOPE_API_KEY"},
- "deepseek": {"name": "DeepSeek", "type": "openai_compatible", "base_url": "https://api.deepseek.com/v1",
- "models": ["deepseek-chat", "deepseek-reasoner"], "env_key": "DEEPSEEK_API_KEY"},
- "zhipu": {"name": "Zhipu AI (GLM)", "type": "openai_compatible",
- "base_url": "https://open.bigmodel.cn/api/paas/v4",
- "models": ["glm-4-plus", "glm-4-flash"], "env_key": "ZHIPU_API_KEY"},
- "moonshot": {"name": "Moonshot (Kimi)", "type": "openai_compatible",
- "base_url": "https://api.moonshot.cn/v1",
- "models": ["moonshot-v1-128k"], "env_key": "MOONSHOT_API_KEY"},
- "ollama": {"name": "Ollama (Local)", "type": "openai_compatible",
- "base_url": "http://localhost:11434/v1",
- "models": ["llama3", "qwen2"], "env_key": "", "api_key": "ollama"},
- }
- def _load_config():
- if os.path.exists(CONFIG_FILE):
- with open(CONFIG_FILE) as f:
- return json.load(f)
- return {"server": "http://localhost:8000", "api_key": ""}
- def _save_config(cfg):
- os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)
- with open(CONFIG_FILE, 'w') as f:
- json.dump(cfg, f, indent=2)
- def _load_providers():
- if os.path.exists(PROVIDERS_FILE):
- with open(PROVIDERS_FILE) as f:
- return json.load(f)
- return dict(DEFAULT_PROVIDERS)
- def _save_providers(p):
- os.makedirs(os.path.dirname(PROVIDERS_FILE), exist_ok=True)
- with open(PROVIDERS_FILE, 'w') as f:
- json.dump(p, f, indent=2, ensure_ascii=False)
- # ============================================================
- # HTTP client helper
- # ============================================================
- def api(method, path, data=None, stream=False):
- """Call AgentPaaS API. Returns parsed JSON or raises."""
- cfg = _load_config()
- url = "{}{}".format(cfg["server"].rstrip("/"), path)
- headers = {"Content-Type": "application/json"}
- if cfg.get("api_key"):
- headers["Authorization"] = "Bearer {}".format(cfg["api_key"])
- body = json.dumps(data, ensure_ascii=False).encode() if data else None
- req = urllib.request.Request(url, data=body, headers=headers, method=method)
- try:
- with urllib.request.urlopen(req, timeout=300) as resp:
- raw = resp.read().decode()
- return json.loads(raw) if raw else {}
- except urllib.error.HTTPError as e:
- body = e.read().decode()
- try:
- err = json.loads(body)
- msg = err.get("error", {}).get("message", body)
- except Exception:
- msg = body
- print("Error {}: {}".format(e.code, msg))
- sys.exit(1)
- except urllib.error.URLError as e:
- print("Connection failed: {}".format(e))
- print("Is the server running? Try: agentpaas serve --dev")
- sys.exit(1)
- def pp(data):
- """Pretty print JSON."""
- print(json.dumps(data, indent=2, ensure_ascii=False))
- # ============================================================
- # Commands: config
- # ============================================================
- def cmd_config_set(args):
- cfg = _load_config()
- if args.server:
- cfg["server"] = args.server
- if args.api_key:
- cfg["api_key"] = args.api_key
- _save_config(cfg)
- print("Config saved to {}".format(CONFIG_FILE))
- print(" Server: {}".format(cfg["server"]))
- print(" API Key: {}...".format(cfg["api_key"][:12]) if cfg.get("api_key") else " API Key: (not set)")
- def cmd_config_show(args):
- cfg = _load_config()
- print("Server: {}".format(cfg.get("server", "")))
- print("API Key: {}".format("{}...".format(cfg["api_key"][:12]) if cfg.get("api_key") else "(not set)"))
- print("Config: {}".format(CONFIG_FILE))
- # ============================================================
- # Commands: serve / create-tenant
- # ============================================================
- def cmd_serve(args):
- import uvicorn
- uvicorn.run("agentpaas.api.app:app", host=args.host, port=args.port, reload=args.dev)
- def cmd_create_tenant(args):
- from agentpaas.db.session import get_db
- from agentpaas.db.models import gen_id, now_utc
- from agentpaas.api.middleware.auth import hash_key
- import secrets
- db = get_db()
- tid = gen_id("tn_")
- uid = gen_id("usr_")
- key = "ap_{}".format(secrets.token_hex(16))
- now = now_utc()
- db.execute("INSERT INTO tenants (id,name,plan,status,created_at) VALUES (?,?,?,'active',?)", (tid, args.name, args.plan, now))
- db.execute("INSERT INTO users (id,tenant_id,email,role,created_at) VALUES (?,?,?,'admin',?)", (uid, tid, args.email, now))
- db.execute("INSERT INTO api_keys (id,tenant_id,user_id,key_hash,key_prefix,name,scopes,rate_limit,status,created_at) VALUES (?,?,?,?,?,'admin',?,600,'active',?)",
- (gen_id("key_"), tid, uid, hash_key(key), key[:8], json.dumps(["agents:*","keys:*","billing:read","admin:*"]), now))
- db.commit()
- print("Tenant: {}".format(tid))
- print("API Key: {}".format(key))
- # Auto-save to config
- cfg = _load_config()
- cfg["api_key"] = key
- _save_config(cfg)
- print("API key saved to config. Ready to use.")
- # ============================================================
- # Commands: agent
- # ============================================================
- def cmd_agent_list(args):
- data = api("GET", "/api/v1/agents")
- agents = data.get("agents", [])
- if not agents:
- print("No agents found.")
- return
- print("{:<16} {:<20} {:>4} {:<12} {}".format("ID", "Name", "Ver", "Status", "Updated"))
- print("-" * 80)
- for a in agents:
- print("{:<16} {:<20} {:>4} {:<12} {}".format(
- a["id"], a["name"][:20], a.get("current_version", "?"),
- a.get("status", ""), (a.get("updated_at") or "")[:19]))
- def cmd_agent_create(args):
- if args.config:
- with open(args.config) as f:
- config = yaml.safe_load(f)
- else:
- config = {"type": "simple", "systemPrompt": args.prompt or "You are a helpful assistant.",
- "model": {"provider": "dashscope", "name": "qwen3-max-2026-01-23"}}
- data = api("POST", "/api/v1/agents", {
- "name": args.name, "description": args.description or "",
- "config": config, "tags": args.tags.split(",") if args.tags else [],
- })
- print("Agent created: {}".format(data["agent_id"]))
- print("Version: {}".format(data["version"]))
- print("Endpoint: {}".format(data["endpoint"]))
- def cmd_agent_get(args):
- pp(api("GET", "/api/v1/agents/{}".format(args.agent_id)))
- def cmd_agent_delete(args):
- api("DELETE", "/api/v1/agents/{}".format(args.agent_id))
- print("Agent {} deleted.".format(args.agent_id))
- def cmd_agent_update(args):
- with open(args.config) as f:
- config = yaml.safe_load(f)
- data = api("PUT", "/api/v1/agents/{}".format(args.agent_id),
- {"config": config, "changelog": args.changelog or ""})
- print("Updated: {} -> v{}".format(args.agent_id, data.get("version")))
- def cmd_agent_rollback(args):
- data = api("POST", "/api/v1/agents/{}/rollback".format(args.agent_id),
- {"target_version": args.version})
- print("Rolled back to v{}".format(args.version))
- def cmd_agent_versions(args):
- data = api("GET", "/api/v1/agents/{}/versions".format(args.agent_id))
- versions = data.get("versions", [])
- print("{:>4} {:<8} {:<20} {}".format("Ver", "Current", "Created", "Changelog"))
- print("-" * 60)
- for v in versions:
- cur = "*" if v.get("is_current") else ""
- print("{:>4} {:<8} {:<20} {}".format(v["version"], cur, (v.get("created_at") or "")[:19], v.get("changelog", "")[:30]))
- # ============================================================
- # Commands: run
- # ============================================================
- def cmd_run(args):
- agent_id = args.agent_id
- if args.input_file:
- with open(args.input_file) as f:
- input_text = f.read()
- else:
- input_text = args.input
- if not input_text:
- print("Error: provide input text or --input-file")
- sys.exit(1)
- params = {}
- if args.temperature is not None:
- params["model.temperature"] = args.temperature
- if args.max_steps is not None:
- params["react.maxSteps"] = args.max_steps
- print("Running agent {}...".format(agent_id))
- t0 = time.time()
- data = api("POST", "/api/v1/agents/{}/run".format(agent_id),
- {"input": input_text, "parameters": params})
- elapsed = time.time() - t0
- print("\n--- Result ({:.1f}s) ---".format(elapsed))
- print(data.get("output", ""))
- print("--- Stats ---")
- usage = data.get("usage", {})
- print(" Run ID: {}".format(data.get("run_id")))
- print(" Tokens: {} (in: {}, out: {})".format(
- usage.get("total_tokens", 0), usage.get("input_tokens", 0), usage.get("output_tokens", 0)))
- print(" Steps: {}".format(usage.get("steps", 0)))
- print(" Duration: {}ms".format(usage.get("duration_ms", 0)))
- # ============================================================
- # Commands: chat (interactive)
- # ============================================================
- # ── Streaming colors ──
- _DIM = "\033[2m"
- _CYAN = "\033[36m"
- _YELLOW = "\033[33m"
- _GREEN = "\033[32m"
- _RED = "\033[31m"
- _BOLD = "\033[1m"
- _RESET = "\033[0m"
- def _chat_stream(agent_id, agent_name, input_text):
- """Stream agent execution via SSE. Returns final output or None if unavailable."""
- cfg = _load_config()
- url = "{}/api/v1/agents/{}/run/stream".format(cfg["server"].rstrip("/"), agent_id)
- headers = {"Content-Type": "application/json"}
- if cfg.get("api_key"):
- headers["Authorization"] = "Bearer {}".format(cfg["api_key"])
- body = json.dumps({"input": input_text}, ensure_ascii=False).encode()
- req = urllib.request.Request(url, data=body, headers=headers, method="POST")
- try:
- resp = urllib.request.urlopen(req, timeout=1800) # 30min for long agent runs
- except Exception:
- return None # Fallback to sync
- final_output = ""
- step_count = 0
- current_event = ""
- for raw_line in resp:
- line = raw_line.decode("utf-8", errors="replace").rstrip("\n").rstrip("\r")
- if line.startswith("event: "):
- current_event = line[7:]
- elif line.startswith("data: "):
- try:
- data = json.loads(line[6:])
- except json.JSONDecodeError:
- continue
- step = data.get("step", 0)
- content = data.get("content", "")
- tool = data.get("tool", "")
- if current_event == "think":
- step_count = step + 1
- short = content.split("\n")[0][:120]
- print("{} [Step {}] 💭 {}{}".format(_DIM, step + 1, short, _RESET))
- elif current_event == "tool_call":
- print("{} [Step {}] 🔧 {}{} {}".format(
- _CYAN, step + 1, tool, _RESET, _DIM + content[:80] + _RESET))
- elif current_event == "tool_result":
- short = content.replace("\n", " ")[:100]
- print("{} [Step {}] ✅ {} → {}{}".format(
- _GREEN, step + 1, tool, short, _RESET))
- elif current_event == "error":
- print("{} [Step {}] ❌ {}{}".format(_RED, step + 1, content[:200], _RESET))
- elif current_event == "answer":
- final_output = content
- elif current_event == "done":
- if data.get("output"):
- final_output = data["output"]
- steps = data.get("steps", step_count)
- tokens = data.get("total_tokens", 0)
- print()
- print("{}📊 {}: {}{}".format(_BOLD, agent_name, _RESET, final_output))
- if tokens:
- print("{} [{} steps | {} tokens]{}".format(_DIM, steps, tokens, _RESET))
- current_event = ""
- elif line == "":
- current_event = ""
- resp.close()
- return final_output
- def cmd_chat(args):
- """Interactive chat with an agent via PaaS API."""
- target = args.target
- # Resolve agent: by ID, by name, or auto-detect
- if not target:
- # Auto: list agents, pick the first (or only) one
- data = api("GET", "/api/v1/agents")
- agents = data.get("agents", [])
- if not agents:
- print("No agents found. Create one first:")
- print(" agentpaas agent create --name lambda --config agent-config.yml")
- return
- if len(agents) == 1:
- agent = agents[0]
- else:
- print("Multiple agents found. Select one:\n")
- for i, a in enumerate(agents):
- print(" [{}] {} — {} (v{})".format(i + 1, a["id"], a["name"], a.get("current_version", "?")))
- print()
- try:
- choice = input("Enter number or agent ID: ").strip()
- if choice.isdigit() and 1 <= int(choice) <= len(agents):
- agent = agents[int(choice) - 1]
- else:
- agent = next((a for a in agents if a["id"] == choice), agents[0])
- except (EOFError, KeyboardInterrupt):
- return
- elif target.startswith("ag_"):
- # By ID
- agent = api("GET", "/api/v1/agents/{}".format(target))
- else:
- # By name: search
- data = api("GET", "/api/v1/agents")
- agents = data.get("agents", [])
- agent = next((a for a in agents if a.get("name", "").lower() == target.lower()), None)
- if not agent:
- # Fuzzy match
- agent = next((a for a in agents if target.lower() in a.get("name", "").lower()), None)
- if not agent:
- print("Agent '{}' not found. Available agents:".format(target))
- for a in agents:
- print(" {} — {}".format(a["id"], a["name"]))
- return
- agent_id = agent["id"]
- agent_name = agent.get("name", agent_id)
- agent_version = agent.get("current_version", "?")
- print()
- print("╔══════════════════════════════════════════════════════════╗")
- print("║ 🐑 {} — via AgentPaaS".format(agent_name.ljust(44))[:56] + " ║")
- print("║ Agent: {} Version: {}".format(agent_id, agent_version).ljust(56) + " ║")
- print("╠══════════════════════════════════════════════════════════╣")
- print("║ 输入消息开始对话,exit 退出,history 查看历史 ║")
- print("╚══════════════════════════════════════════════════════════╝")
- print()
- conversation = []
- while True:
- try:
- user_input = input("You: ").strip()
- except (EOFError, KeyboardInterrupt):
- print("\n👋 对话结束")
- break
- if not user_input:
- continue
- if user_input.lower() in ("exit", "quit", "bye"):
- print("👋 对话结束")
- break
- if user_input.lower() == "history":
- if not conversation:
- print(" (无对话历史)")
- else:
- for entry in conversation:
- role = entry["role"]
- content = entry["content"][:150]
- print(" [{}] {}".format(role, content))
- print()
- continue
- # Build input with conversation history
- if conversation:
- history_text = "\n".join(
- "[{}] {}".format(e["role"], e["content"][:300])
- for e in conversation[-10:] # last 10 turns
- )
- full_input = "=== 对话历史 ===\n{}\n=== 历史结束 ===\n\n[用户] {}".format(
- history_text, user_input
- )
- else:
- full_input = user_input
- conversation.append({"role": "用户", "content": user_input})
- # Call PaaS API (stream first, fallback to sync)
- print()
- t0 = time.time()
- output = _chat_stream(agent_id, agent_name, full_input)
- elapsed = time.time() - t0
- if output is None:
- # Fallback to sync
- data = api("POST", "/api/v1/agents/{}/run".format(agent_id),
- {"input": full_input})
- elapsed = time.time() - t0
- output = data.get("output", "")
- usage = data.get("usage", {})
- print("📊 {}: {}".format(agent_name, output))
- print()
- print(" [{:.1f}s | {} tokens | {} steps]".format(
- elapsed, usage.get("total_tokens", 0), usage.get("steps", 0)))
- else:
- print()
- print(" [{:.1f}s]".format(elapsed))
- print()
- conversation.append({"role": agent_name, "content": (output or "")[:500]})
- # ============================================================
- # Commands: status / health
- # ============================================================
- def cmd_status(args):
- if args.target == "agents":
- data = api("GET", "/api/v1/status/agents?sort={}".format(args.sort or "last_run"))
- agents = data.get("agents", [])
- if not agents:
- print("No agents.")
- return
- print("{:<16} {:<16} {:>5} {:>6} {:>6} {:>8} {:>8} {}".format(
- "ID", "Name", "Score", "Runs", "Fail", "Succ%", "Lat(ms)", "Last Run"))
- print("-" * 100)
- for a in agents:
- h = a.get("health", {})
- score = h.get("score", -1)
- level = h.get("level", "?")
- icon = {"healthy": "+", "degraded": "~", "warning": "!", "critical": "X"}.get(level, "?")
- print("{:<16} {:<16} [{}]{:>3} {:>6} {:>6} {:>8} {:>8} {}".format(
- a["agent_id"], a["name"][:16], icon, score,
- a["runs_total"], a["runs_failed"], a["success_rate"],
- a["avg_latency_ms"], (a.get("last_run") or "never")[:19]))
- elif args.target and args.target.startswith("ag_"):
- pp(api("GET", "/api/v1/status/agents/{}".format(args.target)))
- else:
- data = api("GET", "/api/v1/status")
- print("=== AgentPaaS Status ===")
- print(" Active agents: {}".format(data.get("agents_active", 0)))
- print(" Total runs: {}".format(data.get("runs_total", 0)))
- print(" Running now: {}".format(data.get("runs_running", 0)))
- print(" Success rate: {}".format(data.get("success_rate", "N/A")))
- print(" Avg latency: {}ms".format(data.get("avg_latency_ms", 0)))
- print(" Total tokens: {}".format(data.get("total_tokens", 0)))
- def cmd_health(args):
- data = api("GET", "/api/v1/status/agents/{}/health".format(args.agent_id))
- score = data.get("health_score", -1)
- level = data.get("health_level", "?")
- factors = data.get("factors", {})
- icon = {"healthy": "OK", "degraded": "!!", "warning": "!!", "critical": "XX"}.get(level, "??")
- print("[{}] Agent {} health: {}/100 ({})".format(icon, args.agent_id, score, level))
- for k, v in factors.items():
- print(" {}: {}".format(k, v))
- # ============================================================
- # Commands: runs / trace
- # ============================================================
- def cmd_runs(args):
- data = api("GET", "/api/v1/agents/{}/runs?limit={}".format(args.agent_id, args.limit))
- runs = data.get("runs", [])
- if not runs:
- print("No runs.")
- return
- print("{:<16} {:<10} {:>8} {:>6} {:>8} {}".format("Run ID", "Status", "Dur(ms)", "Steps", "Tokens", "Created"))
- print("-" * 80)
- for r in runs:
- tokens = (r.get("input_tokens") or 0) + (r.get("output_tokens") or 0)
- print("{:<16} {:<10} {:>8} {:>6} {:>8} {}".format(
- r["id"], r["status"], r.get("duration_ms", 0), r.get("steps", 0),
- tokens, (r.get("created_at") or "")[:19]))
- def cmd_trace(args):
- pp(api("GET", "/api/v1/traces/{}".format(args.run_id)))
- # ============================================================
- # Commands: key
- # ============================================================
- def cmd_key_create(args):
- data = api("POST", "/api/v1/auth/keys",
- {"name": args.name, "scopes": args.scopes.split(",") if args.scopes else ["agents:read", "agents:execute"],
- "rate_limit": args.rate_limit})
- print("Key created: {}".format(data.get("key_id")))
- print("API Key: {}".format(data.get("api_key")))
- print("Store this key securely!")
- def cmd_key_list(args):
- data = api("GET", "/api/v1/auth/keys")
- keys = data.get("keys", [])
- print("{:<16} {:<10} {:<16} {:<8} {}".format("ID", "Prefix", "Name", "Status", "Last Used"))
- print("-" * 70)
- for k in keys:
- print("{:<16} {:<10} {:<16} {:<8} {}".format(
- k["id"], k["key_prefix"], k.get("name", ""), k["status"],
- (k.get("last_used_at") or "never")[:19]))
- def cmd_key_revoke(args):
- api("DELETE", "/api/v1/auth/keys/{}".format(args.key_id))
- print("Key {} revoked.".format(args.key_id))
- # ============================================================
- # Commands: billing / metrics
- # ============================================================
- def cmd_usage(args):
- data = api("GET", "/api/v1/billing/usage?group_by={}".format(args.group_by))
- pp(data)
- def cmd_metrics(args):
- data = api("GET", "/api/v1/metrics/overview")
- pp(data)
- # ============================================================
- # Commands: provider (local, no API call)
- # ============================================================
- def cmd_provider_list(args):
- providers = _load_providers()
- print("{:<15} {:<25} {:<45} {}".format("ID", "Name", "Base URL", "Status"))
- print("-" * 100)
- for pid, p in providers.items():
- env = p.get("env_key", "")
- has_key = bool(os.environ.get(env, p.get("api_key", "")))
- status = "OK" if has_key else "NO KEY ({})".format(env)
- print("{:<15} {:<25} {:<45} [{}]".format(pid, p["name"], p.get("base_url", "")[:45], status))
- def cmd_provider_add(args):
- providers = _load_providers()
- pid = args.provider_id
- if pid in providers:
- if args.api_key:
- providers[pid]["api_key"] = args.api_key
- if args.base_url:
- providers[pid]["base_url"] = args.base_url
- else:
- providers[pid] = {
- "name": args.name or pid, "type": "openai_compatible",
- "base_url": args.base_url or "", "models": args.models.split(",") if args.models else [],
- "env_key": "{}_API_KEY".format(pid.upper()),
- }
- if args.api_key:
- providers[pid]["api_key"] = args.api_key
- _save_providers(providers)
- if args.api_key:
- env_key = providers[pid].get("env_key", "")
- if env_key:
- os.environ[env_key] = args.api_key
- print("Provider '{}' configured.".format(pid))
- def cmd_provider_test(args):
- providers = _load_providers()
- p = providers.get(args.provider_id)
- if not p:
- print("Unknown provider: {}".format(args.provider_id)); return
- env_key = p.get("env_key", "")
- api_key = args.api_key or os.environ.get(env_key, "") or p.get("api_key", "")
- if not api_key:
- print("No API key. Set {} or use --api-key".format(env_key)); return
- base_url = p.get("base_url", "")
- model = args.model or (p.get("models", [""])[0])
- prompt = args.prompt or "Say hello in one sentence."
- print("Testing {} ({})...".format(args.provider_id, model))
- if p.get("type") == "anthropic":
- url = "{}/v1/messages".format(base_url.rstrip("/"))
- body = json.dumps({"model": model, "max_tokens": 256, "messages": [{"role": "user", "content": prompt}]}).encode()
- headers = {"Content-Type": "application/json", "x-api-key": api_key, "anthropic-version": "2023-06-01"}
- else:
- url = "{}/chat/completions".format(base_url.rstrip("/"))
- body = json.dumps({"model": model, "max_tokens": 256, "temperature": 0.3,
- "messages": [{"role": "user", "content": prompt}]}).encode()
- headers = {"Content-Type": "application/json", "Authorization": "Bearer {}".format(api_key)}
- t0 = time.time()
- try:
- req = urllib.request.Request(url, data=body, headers=headers)
- with urllib.request.urlopen(req, timeout=30) as resp:
- data = json.loads(resp.read().decode())
- text = data["content"][0]["text"] if p.get("type") == "anthropic" else data["choices"][0]["message"]["content"]
- print("OK ({:.1f}s): {}".format(time.time() - t0, text.strip()[:100]))
- except Exception as e:
- print("FAILED: {}".format(e))
- def cmd_provider_remove(args):
- providers = _load_providers()
- if args.provider_id in providers:
- del providers[args.provider_id]
- _save_providers(providers)
- print("Removed '{}'.".format(args.provider_id))
- else:
- print("Not found: {}".format(args.provider_id))
- def cmd_provider_models(args):
- providers = _load_providers()
- p = providers.get(args.provider_id)
- if not p:
- print("Not found."); return
- api_key = os.environ.get(p.get("env_key", ""), "") or p.get("api_key", "")
- if p.get("type") == "anthropic":
- print("Models: {}".format(", ".join(p.get("models", [])))); return
- try:
- url = "{}/models".format(p["base_url"].rstrip("/"))
- req = urllib.request.Request(url, headers={"Authorization": "Bearer {}".format(api_key)})
- with urllib.request.urlopen(req, timeout=10) as resp:
- data = json.loads(resp.read().decode())
- for m in sorted(m["id"] for m in data.get("data", []))[:20]:
- print(" {}".format(m))
- except Exception as e:
- print("Query failed: {}. Configured: {}".format(e, ", ".join(p.get("models", []))))
- # ============================================================
- # Main
- # ============================================================
- def main():
- parser = argparse.ArgumentParser(prog="agentpaas", description="AgentPaaS CLI — manage agents, runs, providers")
- sub = parser.add_subparsers(dest="command")
- # config
- cfg_p = sub.add_parser("config", help="Configure CLI (server URL, API key)")
- cfg_sub = cfg_p.add_subparsers(dest="config_cmd")
- cs = cfg_sub.add_parser("set"); cs.add_argument("--server", default=""); cs.add_argument("--api-key", default="")
- cfg_sub.add_parser("show")
- # serve
- s = sub.add_parser("serve", help="Start API server")
- s.add_argument("--host", default="0.0.0.0"); s.add_argument("--port", type=int, default=8000); s.add_argument("--dev", action="store_true")
- # create-tenant
- ct = sub.add_parser("create-tenant", help="Create tenant + API key")
- ct.add_argument("--name", required=True); ct.add_argument("--plan", default="free"); ct.add_argument("--email", default="")
- # agent
- ag = sub.add_parser("agent", help="Agent management")
- ag_sub = ag.add_subparsers(dest="agent_cmd")
- ag_sub.add_parser("list")
- ac = ag_sub.add_parser("create"); ac.add_argument("--name", required=True); ac.add_argument("--config", default=""); ac.add_argument("--prompt", default=""); ac.add_argument("--description", default=""); ac.add_argument("--tags", default="")
- ag_get = ag_sub.add_parser("get"); ag_get.add_argument("agent_id")
- ag_del = ag_sub.add_parser("delete"); ag_del.add_argument("agent_id")
- ag_up = ag_sub.add_parser("update"); ag_up.add_argument("agent_id"); ag_up.add_argument("--config", required=True); ag_up.add_argument("--changelog", default="")
- ag_rb = ag_sub.add_parser("rollback"); ag_rb.add_argument("agent_id"); ag_rb.add_argument("--version", type=int, required=True)
- ag_ver = ag_sub.add_parser("versions"); ag_ver.add_argument("agent_id")
- # run
- r = sub.add_parser("run", help="Execute an agent (single shot)")
- r.add_argument("agent_id"); r.add_argument("input", nargs="?", default=""); r.add_argument("--input-file", default="")
- r.add_argument("--temperature", type=float, default=None); r.add_argument("--max-steps", type=int, default=None)
- r.add_argument("--stream", action="store_true")
- # chat (interactive)
- ch = sub.add_parser("chat", help="Interactive chat with an agent (via PaaS)")
- ch.add_argument("target", nargs="?", default="", help="Agent ID (ag_xxx) or name")
- # status
- st = sub.add_parser("status", help="Platform/agent status")
- st.add_argument("target", nargs="?", default=""); st.add_argument("--sort", default="last_run")
- # health
- hl = sub.add_parser("health", help="Agent health score")
- hl.add_argument("agent_id")
- # runs
- rn = sub.add_parser("runs", help="List runs for an agent")
- rn.add_argument("agent_id"); rn.add_argument("--limit", type=int, default=20)
- # trace
- tr = sub.add_parser("trace", help="Get run trace details")
- tr.add_argument("run_id")
- # key
- ky = sub.add_parser("key", help="API key management")
- ky_sub = ky.add_subparsers(dest="key_cmd")
- kc = ky_sub.add_parser("create"); kc.add_argument("--name", default="default"); kc.add_argument("--scopes", default=""); kc.add_argument("--rate-limit", type=int, default=60)
- ky_sub.add_parser("list")
- kr = ky_sub.add_parser("revoke"); kr.add_argument("key_id")
- # usage / metrics
- us = sub.add_parser("usage", help="Billing usage"); us.add_argument("--group-by", default="agent")
- sub.add_parser("metrics", help="Platform metrics")
- # provider
- pv = sub.add_parser("provider", help="LLM provider management")
- pv_sub = pv.add_subparsers(dest="prov_cmd")
- pv_sub.add_parser("list")
- pa = pv_sub.add_parser("add"); pa.add_argument("provider_id"); pa.add_argument("--api-key", default=""); pa.add_argument("--base-url", default=""); pa.add_argument("--name", default=""); pa.add_argument("--models", default="")
- prm = pv_sub.add_parser("remove"); prm.add_argument("provider_id")
- pt = pv_sub.add_parser("test"); pt.add_argument("provider_id"); pt.add_argument("--api-key", default=""); pt.add_argument("--model", default=""); pt.add_argument("--prompt", default="")
- pm = pv_sub.add_parser("models"); pm.add_argument("provider_id")
- args = parser.parse_args()
- handlers = {
- "config": lambda: cmd_config_set(args) if args.config_cmd == "set" else cmd_config_show(args),
- "serve": lambda: cmd_serve(args),
- "create-tenant": lambda: cmd_create_tenant(args),
- "agent": lambda: {"list": cmd_agent_list, "create": cmd_agent_create, "get": cmd_agent_get,
- "delete": cmd_agent_delete, "update": cmd_agent_update,
- "rollback": cmd_agent_rollback, "versions": cmd_agent_versions
- }.get(args.agent_cmd, lambda a: ag.print_help())(args),
- "run": lambda: cmd_run(args),
- "chat": lambda: cmd_chat(args),
- "status": lambda: cmd_status(args),
- "health": lambda: cmd_health(args),
- "runs": lambda: cmd_runs(args),
- "trace": lambda: cmd_trace(args),
- "key": lambda: {"create": cmd_key_create, "list": cmd_key_list, "revoke": cmd_key_revoke
- }.get(args.key_cmd, lambda a: ky.print_help())(args),
- "usage": lambda: cmd_usage(args),
- "metrics": lambda: cmd_metrics(args),
- "provider": lambda: {"list": cmd_provider_list, "add": cmd_provider_add, "remove": cmd_provider_remove,
- "test": cmd_provider_test, "models": cmd_provider_models
- }.get(args.prov_cmd, lambda a: pv.print_help())(args),
- }
- if args.command in handlers:
- handlers[args.command]()
- else:
- parser.print_help()
- print("\nQuick start:")
- print(" agentpaas serve --dev # 1. Start server")
- print(" agentpaas create-tenant --name Org # 2. Get API key")
- print(" agentpaas agent create --name lambda --config agent.yml # 3. Deploy")
- print(" agentpaas chat lambda # 4. Interactive chat!")
- print()
- print("Other commands:")
- print(" agentpaas agent list # List agents")
- print(" agentpaas run ag_xxx 'Hello' # Single-shot run")
- print(" agentpaas status # Platform status")
- if __name__ == "__main__":
- main()
|