# polisplexity/pxy_agents_coral/views.py from __future__ import annotations import json import requests from django.conf import settings from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods, require_POST # Contracts spec version (best-effort import) try: from pxy_contracts.version import SPEC_VERSION except Exception: SPEC_VERSION = "0.1.0" # Where to call your internal APIs from inside the container. # In prod you likely want: "http://127.0.0.1:8002" AGENTS_INTERNAL_BASE = getattr(settings, "AGENTS_INTERNAL_BASE", "") @csrf_exempt @require_http_methods(["GET", "POST"]) def agents_list(request): base = request.build_absolute_uri("/")[:-1] agents = [ { "agent": "sami", "name": "SAMI-Agent", "version": "1.0.0", "spec_version": SPEC_VERSION, "contracts_url": f"{base}/api/contracts/sami.json", "execute_url": f"{base}/api/agents/execute", "description": "Urban scaling (β, R²) + SAMI residuals + chart", }, { "agent": "sites", "name": "Sites-Agent", "version": "1.0.0", "spec_version": SPEC_VERSION, "contracts_url": f"{base}/api/contracts/sites.json", "execute_url": f"{base}/api/agents/execute", "description": "Site scoring (access, demand, competition) with maps", }, ] # 👇 add a simple text summary the bot can send lines = ["Available agents:"] for a in agents: lines.append(f"- {a['agent']}: {a['description']}") lines.append("") lines.append("Try:") lines.append('/sami {"indicator":"imss_wages_2023","cities":["CDMX","GDL","MTY"]}') lines.append('/sites {"city":"CDMX","business":"cafe","time_bands":[10,20]}') return JsonResponse({"agents": agents, "text": "\n".join(lines)}) @csrf_exempt @require_POST def agents_execute(request): """ POST /api/agents/execute Body: { "agent": "sami"|"sites", "payload": {...} } Proxies to: /api/sami/run or /api/sites/search """ try: body = json.loads(request.body.decode("utf-8") or "{}") agent = (body.get("agent") or "").strip().lower() payload = body.get("payload") if agent not in ("sami", "sites"): return JsonResponse( {"code": "AGENT_NOT_FOUND", "message": f"unknown agent '{agent}'"}, status=404, ) if payload is None: return JsonResponse( {"code": "BAD_REQUEST", "message": "missing 'payload'"}, status=400, ) # Resolve proxy target path = "/api/sami/run" if agent == "sami" else "/api/sites/search" # Prefer explicit internal base; otherwise same-host absolute URL url = (AGENTS_INTERNAL_BASE or "").rstrip("/") + path if not url.startswith("http"): base = request.build_absolute_uri("/")[:-1] url = f"{base}{path}" # Proxy r = requests.post(url, json=payload, timeout=90) # Pass through JSON (may be list/dict) & status code # Use safe=False because upstream could return a list return JsonResponse(r.json(), status=r.status_code, safe=False) except requests.Timeout: return JsonResponse( {"code": "UPSTREAM_TIMEOUT", "message": "agent upstream timed out"}, status=504, ) except ValueError as ve: # Bad JSON in request or from upstream return JsonResponse({"code": "BAD_JSON", "message": str(ve)}, status=400) except Exception as e: # Last-resort error envelope return JsonResponse( {"code": "AGENT_EXEC_ERROR", "message": str(e)}, status=500, )