This commit is contained in:
parent
ab277f7628
commit
6b2e42e585
@ -11,25 +11,22 @@ from django.http import JsonResponse, HttpRequest
|
||||
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)
|
||||
# -------------------------------------------------
|
||||
# ----- contracts version (best-effort) -----
|
||||
try:
|
||||
from pxy_contracts.version import SPEC_VERSION
|
||||
except Exception:
|
||||
SPEC_VERSION = "0.1.0"
|
||||
|
||||
# Where to call your internal APIs for /api/agents/execute proxying.
|
||||
# In prod: set AGENTS_INTERNAL_BASE=http://127.0.0.1:8002
|
||||
# ----- INTERNAL CALL BASES -----
|
||||
# For the generic /api/agents/execute proxy (kept for compatibility)
|
||||
AGENTS_INTERNAL_BASE = getattr(settings, "AGENTS_INTERNAL_BASE", "")
|
||||
|
||||
# -------------------------------------------------
|
||||
# Small helpers shared by formatter endpoints
|
||||
# -------------------------------------------------
|
||||
def _base(request: HttpRequest) -> str:
|
||||
"""Absolute base URL without trailing slash."""
|
||||
return request.build_absolute_uri("/")[:-1]
|
||||
# For the formatter endpoints we *force* an internal base and never guess from Host.
|
||||
# Set in .env: AGENTS_INTERNAL_BASE=http://127.0.0.1:8002
|
||||
# Fallback keeps you safe even if env is missing/misread.
|
||||
FORMAT_INTERNAL_BASE = AGENTS_INTERNAL_BASE or "http://127.0.0.1:8002"
|
||||
|
||||
# ===== helpers =====
|
||||
def _load_body(request: HttpRequest) -> Dict[str, Any]:
|
||||
try:
|
||||
raw = (request.body or b"").decode("utf-8")
|
||||
@ -46,7 +43,6 @@ def _extract_payload(body: Dict[str, Any]) -> Tuple[Dict[str, Any], str]:
|
||||
"""
|
||||
if isinstance(body.get("payload"), dict):
|
||||
return body["payload"], "payload"
|
||||
|
||||
args_raw = (body.get("input", {}) or {}).get("args_raw") or ""
|
||||
cleaned = re.sub(r"^/\w+\s*", "", args_raw).strip()
|
||||
if cleaned:
|
||||
@ -56,14 +52,14 @@ def _extract_payload(body: Dict[str, Any]) -> Tuple[Dict[str, Any], str]:
|
||||
pass
|
||||
return {}, "empty"
|
||||
|
||||
def _post_underlying(request: HttpRequest, agent: str, payload: Dict[str, Any], timeout: float = 60.0):
|
||||
def _post_underlying(agent: str, payload: Dict[str, Any], timeout: float = 60.0):
|
||||
"""
|
||||
Directly call the real internal APIs on THIS host:8002, bypassing the execute proxy.
|
||||
Call the *real* internal APIs via a fixed base (no build_absolute_uri):
|
||||
sami -> /api/sami/run
|
||||
sites -> /api/sites/search
|
||||
"""
|
||||
path = "/api/sami/run" if agent == "sami" else "/api/sites/search"
|
||||
url = f"{_base(request)}{path}"
|
||||
url = f"{FORMAT_INTERNAL_BASE.rstrip('/')}{path}"
|
||||
try:
|
||||
r = requests.post(url, json=payload, timeout=timeout)
|
||||
try:
|
||||
@ -72,11 +68,11 @@ def _post_underlying(request: HttpRequest, agent: str, payload: Dict[str, Any],
|
||||
data = {"code": "NON_JSON", "message": r.text[:2000]}
|
||||
return r.status_code, data
|
||||
except requests.Timeout:
|
||||
return 504, {"code": "UPSTREAM_TIMEOUT", "message": "agent upstream timed out"}
|
||||
return 504, {"code": "UPSTREAM_TIMEOUT", "message": "agent upstream timed out", "_debug_url": url}
|
||||
except Exception as e:
|
||||
return 500, {"code": "EXEC_ERROR", "message": str(e)}
|
||||
return 500, {"code": "EXEC_ERROR", "message": str(e), "_debug_url": url}
|
||||
|
||||
# Tiny text builders (handy for bots)
|
||||
# Tiny text builders for bot replies
|
||||
def _text_sami(data: Dict[str, Any]) -> str:
|
||||
if "beta" in data and "r2" in data:
|
||||
lines = [f"SAMI run: β={data['beta']:.3f}, R²={data['r2']:.3f}"]
|
||||
@ -102,13 +98,12 @@ def _text_sites(data: Dict[str, Any]) -> str:
|
||||
return f"⚠️ {data.get('code')}: {data.get('message','')}"
|
||||
return "Site scoring ready."
|
||||
|
||||
# -------------------------------------------------
|
||||
# Public endpoints
|
||||
# -------------------------------------------------
|
||||
# ===== public endpoints =====
|
||||
@csrf_exempt
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def agents_list(request: HttpRequest):
|
||||
base = _base(request)
|
||||
# use request host only for *outward* links (safe)
|
||||
base = request.build_absolute_uri("/")[:-1]
|
||||
agents = [
|
||||
{
|
||||
"agent": "sami",
|
||||
@ -129,7 +124,6 @@ def agents_list(request: HttpRequest):
|
||||
"description": "Site scoring (access, demand, competition) with maps",
|
||||
},
|
||||
]
|
||||
|
||||
lines = ["Available agents:"]
|
||||
for a in agents:
|
||||
lines.append(f"- {a['agent']}: {a['description']}")
|
||||
@ -145,10 +139,8 @@ def agents_list(request: HttpRequest):
|
||||
@require_POST
|
||||
def agents_execute(request: HttpRequest):
|
||||
"""
|
||||
POST /api/agents/execute
|
||||
Body: { "agent": "sami"|"sites", "payload": {...} }
|
||||
Proxies to: /api/sami/run or /api/sites/search
|
||||
(Uses AGENTS_INTERNAL_BASE if set; else same-host absolute URL.)
|
||||
Proxies to the *internal* API using AGENTS_INTERNAL_BASE (or same-host fallback).
|
||||
"""
|
||||
try:
|
||||
body = json.loads(request.body.decode("utf-8") or "{}")
|
||||
@ -156,43 +148,31 @@ def agents_execute(request: HttpRequest):
|
||||
payload = body.get("payload")
|
||||
|
||||
if agent not in {"sami", "sites"}:
|
||||
return JsonResponse(
|
||||
{"code": "AGENT_NOT_FOUND", "message": f"unknown agent '{agent}'"},
|
||||
status=404,
|
||||
)
|
||||
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,
|
||||
)
|
||||
return JsonResponse({"code": "BAD_REQUEST", "message": "missing 'payload'"}, status=400)
|
||||
|
||||
path = "/api/sami/run" if agent == "sami" else "/api/sites/search"
|
||||
url = (AGENTS_INTERNAL_BASE or "").rstrip("/") + path
|
||||
if not url.startswith("http"): # fall back to same host:port (8002)
|
||||
url = f"{_base(request)}{path}"
|
||||
base = (AGENTS_INTERNAL_BASE or "http://127.0.0.1:8002").rstrip("/")
|
||||
url = f"{base}{path}"
|
||||
|
||||
r = requests.post(url, json=payload, timeout=90)
|
||||
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,
|
||||
)
|
||||
return JsonResponse({"code": "UPSTREAM_TIMEOUT", "message": "agent upstream timed out"}, status=504)
|
||||
except ValueError as ve:
|
||||
return JsonResponse({"code": "BAD_JSON", "message": str(ve)}, status=400)
|
||||
except Exception as e:
|
||||
return JsonResponse({"code": "AGENT_EXEC_ERROR", "message": str(e)}, status=500)
|
||||
|
||||
# -------------------------------------------------
|
||||
# Formatter endpoints (call underlying APIs directly)
|
||||
# -------------------------------------------------
|
||||
# ----- formatters (call underlying APIs directly via fixed base) -----
|
||||
@csrf_exempt
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def format_sami(request: HttpRequest):
|
||||
body = _load_body(request)
|
||||
payload, src = _extract_payload(body)
|
||||
status, data = _post_underlying(request, "sami", payload, timeout=60.0)
|
||||
status, data = _post_underlying("sami", payload, timeout=60.0)
|
||||
data = data if isinstance(data, dict) else {"result": data}
|
||||
data.setdefault("_echo", {"src": src, "payload_keys": list(payload.keys())})
|
||||
try:
|
||||
@ -206,7 +186,7 @@ def format_sami(request: HttpRequest):
|
||||
def format_sites(request: HttpRequest):
|
||||
body = _load_body(request)
|
||||
payload, src = _extract_payload(body)
|
||||
status, data = _post_underlying(request, "sites", payload, timeout=60.0)
|
||||
status, data = _post_underlying("sites", payload, timeout=60.0)
|
||||
data = data if isinstance(data, dict) else {"result": data}
|
||||
data.setdefault("_echo", {"src": src, "payload_keys": list(payload.keys())})
|
||||
try:
|
||||
|
Loading…
x
Reference in New Issue
Block a user