Better formater for calling agent sites

This commit is contained in:
Ekaropolus 2026-01-03 19:01:50 -06:00
parent d7ab546650
commit caf60c967e
2 changed files with 158 additions and 4 deletions

View File

@ -2,7 +2,8 @@ from __future__ import annotations #
import json
import re
import requests
from typing import Any, Dict, Tuple
from typing import Any, Dict, Tuple, Optional, List
from django.conf import settings
from django.http import JsonResponse, HttpRequest
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
@ -18,6 +19,131 @@ def _load_body(request: HttpRequest) -> Dict[str, Any]:
except Exception:
return {}
_SITES_DEFAULTS = {
"city": "CDMX",
"business": "all",
"time_bands": [10, 20],
"center_by_city": {
"CDMX": (19.4326, -99.1332),
},
}
def _parse_time_bands(raw: str) -> list[int]:
bands: list[int] = []
for part in re.split(r"[,\s]+", (raw or "").strip()):
if not part:
continue
try:
val = int(part)
except Exception:
continue
if val > 0:
bands.append(val)
return bands
def _parse_latlon(raw: str) -> Optional[List[float]]:
parts = [p.strip() for p in (raw or "").split(",")]
if len(parts) != 2:
return None
try:
lat = float(parts[0])
lon = float(parts[1])
except Exception:
return None
return [lat, lon]
def _parse_sites_shorthand(body: Dict[str, Any]) -> Tuple[Dict[str, Any], str]:
args_raw = (body.get("input", {}) or {}).get("args_raw") or ""
cleaned = re.sub(r"^/\w+\s*", "", args_raw).strip()
if not cleaned:
return {}, "empty"
payload: Dict[str, Any] = {}
time_bands: list[int] = []
lat_val = None
lon_val = None
for tok in cleaned.split():
key = None
val = None
if "=" in tok:
key, val = tok.split("=", 1)
elif ":" in tok:
key, val = tok.split(":", 1)
if key is not None:
key = key.strip().lower()
val = (val or "").strip()
if key in {"city", "c"}:
payload["city"] = val
elif key in {"business", "biz", "b", "category", "cat"}:
payload["business"] = val
elif key in {"time", "times", "band", "bands", "time_bands", "tb"}:
bands = _parse_time_bands(val)
if bands:
time_bands.extend(bands)
elif key in {"max", "max_candidates", "k", "top"}:
try:
payload["max_candidates"] = max(1, int(val))
except Exception:
pass
elif key in {"center", "ctr"}:
latlon = _parse_latlon(val)
if latlon:
payload["center"] = latlon
elif key in {"lat", "latitude"}:
try:
lat_val = float(val)
except Exception:
pass
elif key in {"lon", "lng", "longitude"}:
try:
lon_val = float(val)
except Exception:
pass
continue
if re.fullmatch(r"\d+(?:,\d+)*", tok):
bands = _parse_time_bands(tok)
if bands:
time_bands.extend(bands)
continue
if tok.isalpha() and tok.isupper() and "city" not in payload:
payload["city"] = tok
continue
if "business" not in payload:
payload["business"] = tok
else:
payload["business"] = f"{payload['business']}_{tok}"
if time_bands:
payload["time_bands"] = time_bands
if lat_val is not None and lon_val is not None and "center" not in payload:
payload["center"] = [lat_val, lon_val]
return payload, "shorthand"
def _apply_sites_defaults(payload: Dict[str, Any], body: Dict[str, Any]) -> Dict[str, Any]:
out = dict(payload or {})
loc = (body.get("input") or {}).get("location") or {}
if "center" not in out and loc.get("lat") is not None and loc.get("lon") is not None:
out["center"] = [loc.get("lat"), loc.get("lon")]
city = (out.get("city") or _SITES_DEFAULTS["city"]).strip()
out["city"] = city
if not out.get("business"):
out["business"] = _SITES_DEFAULTS["business"]
if not out.get("time_bands"):
out["time_bands"] = list(_SITES_DEFAULTS["time_bands"])
if "center" not in out:
fallback = _SITES_DEFAULTS["center_by_city"].get(city.upper())
if fallback:
out["center"] = [fallback[0], fallback[1]]
return out
def _extract_payload(body: Dict[str, Any]) -> Tuple[Dict[str, Any], str]:
"""
Returns (payload, src) where src is 'payload', 'args_raw', or 'empty'.
@ -41,8 +167,19 @@ def _extract_payload(body: Dict[str, Any]) -> Tuple[Dict[str, Any], str]:
return {}, "empty"
def _resolve_internal_base(request: HttpRequest) -> str:
base = (getattr(settings, "AGENTS_INTERNAL_BASE", "") or "").strip()
if base:
return base.rstrip("/")
host = (request.get_host() or "").lower()
if host.endswith(":8011"):
return "http://127.0.0.1:8000"
if host.endswith(":8010"):
return "http://127.0.0.1:8002"
return "http://127.0.0.1:8002"
def _post_execute(request: HttpRequest, agent: str, payload: Dict[str, Any], timeout: float = 30.0):
url = f"{_base(request)}/api/agents/execute"
url = f"{_resolve_internal_base(request)}/api/agents/execute"
try:
r = requests.post(url, json={"agent": agent, "payload": payload}, timeout=timeout)
# try parse json regardless of status
@ -107,6 +244,9 @@ def format_sami(request: HttpRequest):
def format_sites(request: HttpRequest):
body = _load_body(request)
payload, src = _extract_payload(body)
if not payload and src == "empty":
payload, src = _parse_sites_shorthand(body)
payload = _apply_sites_defaults(payload, body)
status, data = _post_execute(request, "sites", payload, timeout=30.0)
data = data if isinstance(data, dict) else {"result": data}
data.setdefault("_echo", {"src": src, "payload_keys": list(payload.keys())})

View File

@ -25,6 +25,21 @@ except Exception:
# For the generic /api/agents/execute proxy (kept for compatibility)
AGENTS_INTERNAL_BASE = getattr(settings, "AGENTS_INTERNAL_BASE", "")
def _resolve_internal_base(request: HttpRequest) -> str:
base = (AGENTS_INTERNAL_BASE or "").strip()
if base:
return base.rstrip("/")
host = (request.get_host() or "").lower()
if host.endswith(":8011"):
return "http://127.0.0.1:8000"
if host.endswith(":8010"):
return "http://127.0.0.1:8002"
if host.endswith(":8000"):
return "http://127.0.0.1:8000"
if host.endswith(":8002"):
return "http://127.0.0.1:8002"
return "http://127.0.0.1:8002"
# 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.
@ -188,7 +203,7 @@ def agents_execute(request: HttpRequest):
return JsonResponse({"code": "BAD_REQUEST", "message": "missing 'payload'"}, status=400)
path = "/api/sami/run" if agent == "sami" else "/api/sites/search"
base = (AGENTS_INTERNAL_BASE or "http://127.0.0.1:8002").rstrip("/")
base = _resolve_internal_base(request)
url = f"{base}{path}"
r = requests.post(url, json=payload, timeout=90)
@ -255,4 +270,3 @@ def agents_health(request):
except Exception as e:
data["checks"]["sites"] = {"ok": False, "error": str(e)}
return JsonResponse(data)