Intelligent Agent Router: LLM-powered natural language routing with suggestion UI
This commit is contained in:
+166
-1
@@ -14,7 +14,7 @@ from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
from database import get_db, init_db
|
||||
from models import User, AgentCatalog, AgentInstance, Run, LLMProvider, Bridge
|
||||
from models import User, AgentCatalog, AgentInstance, Run, LLMProvider, Bridge, RouteLog
|
||||
|
||||
app = FastAPI(title="Agent Command Center", version="2026.04.12.01")
|
||||
|
||||
@@ -724,6 +724,171 @@ def admin_delete_catalog(catalog_id: str, admin: dict = Depends(require_admin),
|
||||
return {"status": "deleted"}
|
||||
|
||||
|
||||
# --- Agent Router ---
|
||||
|
||||
class RouterRequest(BaseModel):
|
||||
request: str
|
||||
|
||||
|
||||
@app.get("/api/catalog/all")
|
||||
def list_catalog_all(db: Session = Depends(get_db)):
|
||||
"""Internal: full catalog for the router (no auth)."""
|
||||
entries = db.query(AgentCatalog).all()
|
||||
return [{
|
||||
"id": e.id, "name": e.name, "description": e.description,
|
||||
"category": e.category, "supports_schedule": e.supports_schedule,
|
||||
"is_sub_agent": e.is_sub_agent, "requires_llm": e.requires_llm,
|
||||
} for e in entries]
|
||||
|
||||
|
||||
@app.post("/api/router")
|
||||
def ask_router(data: RouterRequest, user: dict = Depends(require_auth), db: Session = Depends(get_db)):
|
||||
"""Route a natural language request to the best agent."""
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# Call the router agent
|
||||
agent_dir = "/app/agents"
|
||||
config_path = f"/tmp/router_{user['user_id']}_{secrets.token_hex(4)}.json"
|
||||
with open(config_path, "w") as f:
|
||||
json.dump({"user_id": user["user_id"], "request": data.request}, f)
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["python3", "-c",
|
||||
f"import sys, json; sys.path.insert(0, '{agent_dir}'); "
|
||||
f"d = json.load(open('{config_path}')); "
|
||||
f"from agent_router import route; "
|
||||
f"r = route(d['user_id'], d['request']); "
|
||||
f"print(json.dumps(r))"],
|
||||
capture_output=True, text=True, timeout=120, cwd=agent_dir,
|
||||
env={**dict(os.environ), "PYTHONPATH": agent_dir},
|
||||
)
|
||||
os.remove(config_path)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(result.stderr[:500])
|
||||
|
||||
recommendation = json.loads(result.stdout.strip())
|
||||
except Exception as e:
|
||||
return {"error": str(e), "action": "not_possible", "reasoning": f"Router error: {e}"}
|
||||
|
||||
# Log the route
|
||||
log = RouteLog(
|
||||
user_id=user["user_id"],
|
||||
request_text=data.request,
|
||||
recommended_agent=recommendation.get("agent_name", ""),
|
||||
action=recommendation.get("action", ""),
|
||||
reasoning=recommendation.get("reasoning", ""),
|
||||
outcome="pending",
|
||||
metadata_={
|
||||
"instance_id": recommendation.get("instance_id"),
|
||||
"catalog_id": recommendation.get("catalog_id"),
|
||||
"config": recommendation.get("config"),
|
||||
"model": recommendation.get("model", ""),
|
||||
"tokens_in": recommendation.get("tokens_in", 0),
|
||||
"tokens_out": recommendation.get("tokens_out", 0),
|
||||
},
|
||||
)
|
||||
db.add(log)
|
||||
db.commit()
|
||||
|
||||
recommendation["route_id"] = log.id
|
||||
return recommendation
|
||||
|
||||
|
||||
@app.post("/api/router/{route_id}/accept")
|
||||
def accept_route(route_id: int, user: dict = Depends(require_auth), db: Session = Depends(get_db)):
|
||||
"""Accept a router suggestion and execute it."""
|
||||
log = db.query(RouteLog).filter(RouteLog.id == route_id, RouteLog.user_id == user["user_id"]).first()
|
||||
if not log:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
log.outcome = "accepted"
|
||||
meta = log.metadata_ or {}
|
||||
action = log.action
|
||||
instance_id = meta.get("instance_id")
|
||||
|
||||
# Execute the recommended action
|
||||
if action == "run_existing" and instance_id:
|
||||
# Trigger the instance
|
||||
import subprocess
|
||||
inst = db.query(AgentInstance).filter(AgentInstance.id == instance_id).first()
|
||||
if inst:
|
||||
agent_dir = "/app/agents"
|
||||
catalog_id = inst.catalog_id
|
||||
u = db.query(User).filter(User.id == user["user_id"]).first()
|
||||
env = {**dict(os.environ), "PYTHONPATH": agent_dir}
|
||||
|
||||
if catalog_id == "daily-briefing":
|
||||
script_map = {"eric": "eric_briefing.py", "angela": "angela_briefing.py"}
|
||||
script = script_map.get(u.username)
|
||||
if script:
|
||||
env_key = f"{u.username.upper().replace('.', '_')}_INSTANCE_ID"
|
||||
env[env_key] = str(instance_id)
|
||||
subprocess.Popen(["python3", f"{agent_dir}/{script}"], env=env, cwd=agent_dir)
|
||||
elif catalog_id == "project-monitor":
|
||||
config_path = f"/tmp/pm_config_{instance_id}.json"
|
||||
with open(config_path, "w") as f:
|
||||
json.dump({"config": inst.config or {}, "user_id": user["user_id"], "instance_id": instance_id}, f)
|
||||
subprocess.Popen(
|
||||
["python3", "-c",
|
||||
f"import sys, json; sys.path.insert(0, '{agent_dir}'); "
|
||||
f"d = json.load(open('{config_path}')); "
|
||||
f"from project_monitor import run; "
|
||||
f"run(d['config'], user_id=d['user_id'], instance_id=d['instance_id'])"],
|
||||
env=env, cwd=agent_dir,
|
||||
)
|
||||
db.commit()
|
||||
return {"status": "executing", "message": f"Running {inst.name}"}
|
||||
|
||||
elif action == "configure" and instance_id:
|
||||
inst = db.query(AgentInstance).filter(AgentInstance.id == instance_id).first()
|
||||
if inst and meta.get("config"):
|
||||
new_config = {**(inst.config or {}), **meta["config"]}
|
||||
inst.config = new_config
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
flag_modified(inst, "config")
|
||||
if new_config.get("project_name") and inst.catalog_id == "project-monitor":
|
||||
inst.name = f"Project Monitor - {new_config['project_name']}"
|
||||
db.commit()
|
||||
return {"status": "configured", "message": f"Updated {inst.name}"}
|
||||
|
||||
elif action == "create_and_run":
|
||||
catalog_id = meta.get("catalog_id")
|
||||
catalog = db.query(AgentCatalog).filter(AgentCatalog.id == catalog_id).first()
|
||||
if catalog:
|
||||
config = {**(catalog.default_config or {}), **(meta.get("config") or {})}
|
||||
inst = AgentInstance(
|
||||
user_id=user["user_id"],
|
||||
catalog_id=catalog_id,
|
||||
name=meta.get("instance_name") or catalog.name,
|
||||
config=config,
|
||||
schedule="manual",
|
||||
)
|
||||
db.add(inst)
|
||||
db.commit()
|
||||
return {"status": "created", "message": f"Created and ready: {inst.name}", "instance_id": inst.id}
|
||||
|
||||
elif action == "info":
|
||||
db.commit()
|
||||
return {"status": "info", "message": log.reasoning}
|
||||
|
||||
db.commit()
|
||||
return {"status": "accepted"}
|
||||
|
||||
|
||||
@app.post("/api/router/{route_id}/reject")
|
||||
def reject_route(route_id: int, user: dict = Depends(require_auth), db: Session = Depends(get_db)):
|
||||
"""Reject a router suggestion."""
|
||||
log = db.query(RouteLog).filter(RouteLog.id == route_id, RouteLog.user_id == user["user_id"]).first()
|
||||
if not log:
|
||||
raise HTTPException(status_code=404)
|
||||
log.outcome = "rejected"
|
||||
db.commit()
|
||||
return {"status": "rejected"}
|
||||
|
||||
|
||||
# --- Bridge Management ---
|
||||
|
||||
class BridgeRegister(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user