Spaces:
Sleeping
Sleeping
Updated output for modelfit_chat
Browse files
main.py
CHANGED
|
@@ -283,6 +283,111 @@ async def _forward(path: str, method: str = "GET", json_body=None, user_token: s
|
|
| 283 |
# Fallback: return short text envelope if non-JSON
|
| 284 |
return JSONResponse(status_code=r.status_code, content={"text": r.text[:1000]})
|
| 285 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
async def _forward_stream(path: str, files=None, data=None, user_token: str | None = None, method: str = "POST"):
|
| 287 |
url = f"{UPSTREAM_URL}{path}"
|
| 288 |
headers = {
|
|
@@ -590,8 +695,7 @@ async def modelfit_chat(
|
|
| 590 |
"""
|
| 591 |
Fetch training data from data_url (HTTPS), infer schema from column order, then call /modelfit/ on the API.
|
| 592 |
Column order: 1st = id_col, 2nd = time_col, 3rd..second-to-last = current_features, last = y.
|
| 593 |
-
|
| 594 |
-
chat platform response size limits; errors are returned in full.
|
| 595 |
"""
|
| 596 |
user_token = _extract_user_token(req)
|
| 597 |
if not user_token:
|
|
@@ -619,13 +723,11 @@ async def modelfit_chat(
|
|
| 619 |
"meanvar_test": False,
|
| 620 |
"signif": 0.05,
|
| 621 |
}
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
"
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
success_response_override={"ok": True, "message": "Model fitted. Call modelforecast_chat with your forecast data URL next."},
|
| 628 |
-
)
|
| 629 |
|
| 630 |
|
| 631 |
@app.post("/modelforecast_chat/")
|
|
|
|
| 283 |
# Fallback: return short text envelope if non-JSON
|
| 284 |
return JSONResponse(status_code=r.status_code, content={"text": r.text[:1000]})
|
| 285 |
|
| 286 |
+
|
| 287 |
+
async def _forward_and_get_body(path: str, method: str = "GET", json_body=None, user_token: str | None = None) -> tuple[int, dict | list | None]:
|
| 288 |
+
"""Same as _forward but returns (status_code, parsed_json_body) so caller can build a custom response."""
|
| 289 |
+
url = f"{UPSTREAM_URL}{path}"
|
| 290 |
+
headers = {
|
| 291 |
+
"Authorization": f"Bearer {HF_TOKEN}",
|
| 292 |
+
"Accept": "application/json",
|
| 293 |
+
}
|
| 294 |
+
if user_token:
|
| 295 |
+
headers["X-API-Key"] = user_token
|
| 296 |
+
ping_url = f"{UPSTREAM_URL}/secure-ping/"
|
| 297 |
+
ping_headers = {"Authorization": f"Bearer {HF_TOKEN}"}
|
| 298 |
+
if user_token:
|
| 299 |
+
ping_headers["X-API-Key"] = user_token
|
| 300 |
+
timeout = httpx.Timeout(UPSTREAM_TIMEOUT)
|
| 301 |
+
r = None
|
| 302 |
+
|
| 303 |
+
async def do_request():
|
| 304 |
+
nonlocal r
|
| 305 |
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
| 306 |
+
r = await client.request(method, url, headers=headers, json=json_body)
|
| 307 |
+
|
| 308 |
+
request_task = asyncio.create_task(do_request())
|
| 309 |
+
wait_sec = PING_INTERVAL if PING_INTERVAL > 0 else 0.0
|
| 310 |
+
try:
|
| 311 |
+
while not request_task.done():
|
| 312 |
+
if wait_sec <= 0:
|
| 313 |
+
await request_task
|
| 314 |
+
break
|
| 315 |
+
ping_sleep = asyncio.create_task(asyncio.sleep(wait_sec))
|
| 316 |
+
done, pending = await asyncio.wait(
|
| 317 |
+
{request_task, ping_sleep},
|
| 318 |
+
return_when=asyncio.FIRST_COMPLETED,
|
| 319 |
+
timeout=UPSTREAM_TIMEOUT + 10,
|
| 320 |
+
)
|
| 321 |
+
for t in pending:
|
| 322 |
+
if t is not request_task:
|
| 323 |
+
t.cancel()
|
| 324 |
+
try:
|
| 325 |
+
await t
|
| 326 |
+
except asyncio.CancelledError:
|
| 327 |
+
pass
|
| 328 |
+
if request_task in done:
|
| 329 |
+
break
|
| 330 |
+
async with httpx.AsyncClient(timeout=10.0) as c:
|
| 331 |
+
if SELF_URL:
|
| 332 |
+
try:
|
| 333 |
+
await c.get(f"{SELF_URL}/keep-alive")
|
| 334 |
+
except Exception:
|
| 335 |
+
pass
|
| 336 |
+
try:
|
| 337 |
+
await c.get(ping_url, headers=ping_headers)
|
| 338 |
+
except Exception:
|
| 339 |
+
pass
|
| 340 |
+
if not request_task.done():
|
| 341 |
+
request_task.cancel()
|
| 342 |
+
try:
|
| 343 |
+
await request_task
|
| 344 |
+
except asyncio.CancelledError:
|
| 345 |
+
pass
|
| 346 |
+
await request_task
|
| 347 |
+
except asyncio.CancelledError:
|
| 348 |
+
request_task.cancel()
|
| 349 |
+
try:
|
| 350 |
+
await request_task
|
| 351 |
+
except asyncio.CancelledError:
|
| 352 |
+
pass
|
| 353 |
+
raise
|
| 354 |
+
exc = request_task.exception()
|
| 355 |
+
if exc is not None:
|
| 356 |
+
raise exc
|
| 357 |
+
if r is None:
|
| 358 |
+
raise RuntimeError("Upstream request did not complete")
|
| 359 |
+
|
| 360 |
+
ct = r.headers.get("content-type", "")
|
| 361 |
+
if "application/json" in ct:
|
| 362 |
+
try:
|
| 363 |
+
return (r.status_code, r.json())
|
| 364 |
+
except Exception:
|
| 365 |
+
return (r.status_code, {"error": r.text[:500]})
|
| 366 |
+
return (r.status_code, {"text": r.text[:1000]})
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def _build_modelfit_chat_minimal(body: dict) -> dict:
|
| 370 |
+
"""Extract formula, alpha, beta, and Performance subset (col 2, rows 3,4,5) for chat response."""
|
| 371 |
+
out = {"ok": True, "formula": body.get("formula")}
|
| 372 |
+
out["alpha"] = body.get("alpha")
|
| 373 |
+
out["beta"] = body.get("beta")
|
| 374 |
+
perf = body.get("Performance")
|
| 375 |
+
if isinstance(perf, list) and len(perf) >= 5:
|
| 376 |
+
keys = list(perf[0].keys()) if perf[0] else []
|
| 377 |
+
# Col 2 (1-based) = index 1; if only one column use index 0. Rows 3,4,5 = indices 2,3,4.
|
| 378 |
+
col_idx = 1 if len(keys) > 1 else 0
|
| 379 |
+
col2_name = keys[col_idx] if keys else None
|
| 380 |
+
if col2_name is not None:
|
| 381 |
+
row_names = ["R2 overall", "MSE", "MAE"]
|
| 382 |
+
values = [perf[i].get(col2_name) for i in [2, 3, 4]]
|
| 383 |
+
out["Performance_subset"] = {"metric_names": row_names, "column": col2_name, "values": values}
|
| 384 |
+
else:
|
| 385 |
+
out["Performance_subset"] = None
|
| 386 |
+
else:
|
| 387 |
+
out["Performance_subset"] = None
|
| 388 |
+
return out
|
| 389 |
+
|
| 390 |
+
|
| 391 |
async def _forward_stream(path: str, files=None, data=None, user_token: str | None = None, method: str = "POST"):
|
| 392 |
url = f"{UPSTREAM_URL}{path}"
|
| 393 |
headers = {
|
|
|
|
| 695 |
"""
|
| 696 |
Fetch training data from data_url (HTTPS), infer schema from column order, then call /modelfit/ on the API.
|
| 697 |
Column order: 1st = id_col, 2nd = time_col, 3rd..second-to-last = current_features, last = y.
|
| 698 |
+
On success returns a small informative JSON: formula, alpha, beta, Performance_subset (col 2, rows 3–5).
|
|
|
|
| 699 |
"""
|
| 700 |
user_token = _extract_user_token(req)
|
| 701 |
if not user_token:
|
|
|
|
| 723 |
"meanvar_test": False,
|
| 724 |
"signif": 0.05,
|
| 725 |
}
|
| 726 |
+
status, body = await _forward_and_get_body("/modelfit/", "POST", json_body=payload, user_token=user_token)
|
| 727 |
+
if status != 200 or not isinstance(body, dict):
|
| 728 |
+
return JSONResponse(status_code=status, content=body if isinstance(body, dict) else {"error": str(body)})
|
| 729 |
+
minimal = _build_modelfit_chat_minimal(body)
|
| 730 |
+
return JSONResponse(status_code=200, content=minimal)
|
|
|
|
|
|
|
| 731 |
|
| 732 |
|
| 733 |
@app.post("/modelforecast_chat/")
|