116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
# 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,
|
|
)
|