Intelligent Agent Router: LLM-powered natural language routing with suggestion UI

This commit is contained in:
2026-04-14 14:41:44 +00:00
parent f39bd13fc6
commit f01553c511
4 changed files with 413 additions and 1 deletions
+166 -1
View File
@@ -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):