Spaces:
Running
Running
Commit ·
de5b38c
1
Parent(s): 1d31773
Add JSON API endpoint (/api/ask, /api/remaining) for custom chat UI
Browse filesCo-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
app.py
CHANGED
|
@@ -459,7 +459,91 @@ def build_app() -> gr.Blocks:
|
|
| 459 |
return demo
|
| 460 |
|
| 461 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
| 463 |
WORKSPACE_DIR = load_workspace()
|
| 464 |
-
|
| 465 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
return demo
|
| 460 |
|
| 461 |
|
| 462 |
+
def ask_question(question: str) -> dict:
|
| 463 |
+
"""Synchronous API for the custom chat UI."""
|
| 464 |
+
if not _check_and_increment():
|
| 465 |
+
return {
|
| 466 |
+
"answer": None,
|
| 467 |
+
"error": "daily_limit",
|
| 468 |
+
"remaining": 0,
|
| 469 |
+
"trace_html": "",
|
| 470 |
+
"stats": "",
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
if not MODEL:
|
| 474 |
+
return {"answer": None, "error": "LH_MODEL not set", "remaining": _remaining()}
|
| 475 |
+
|
| 476 |
+
scratch_dir = Path(tempfile.mkdtemp(prefix="lh-scratch-"))
|
| 477 |
+
system_prompt = build_system_prompt(base_prompt="", workspace=WORKSPACE_DIR)
|
| 478 |
+
messages: list[Message] = [
|
| 479 |
+
{"role": "system", "content": system_prompt},
|
| 480 |
+
{"role": "user", "content": question},
|
| 481 |
+
]
|
| 482 |
+
|
| 483 |
+
start = time.monotonic()
|
| 484 |
+
agent_run = run_agent_loop(
|
| 485 |
+
model=MODEL,
|
| 486 |
+
messages=messages,
|
| 487 |
+
tools=TOOL_DEFINITIONS,
|
| 488 |
+
completion=litellm.completion,
|
| 489 |
+
workspace=WORKSPACE_DIR,
|
| 490 |
+
scratch_dir=scratch_dir,
|
| 491 |
+
sandbox_fn=e2b_run_python,
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
try:
|
| 495 |
+
for event in agent_run:
|
| 496 |
+
pass
|
| 497 |
+
except Exception as exc:
|
| 498 |
+
return {"answer": None, "error": str(exc), "remaining": _remaining()}
|
| 499 |
+
|
| 500 |
+
trace = agent_run.trace
|
| 501 |
+
trace.wall_time_s = round(time.monotonic() - start, 2)
|
| 502 |
+
result = {
|
| 503 |
+
"question": question,
|
| 504 |
+
"source": SOURCE,
|
| 505 |
+
"passed": True,
|
| 506 |
+
"assertions": {},
|
| 507 |
+
"trace": asdict(trace),
|
| 508 |
+
}
|
| 509 |
+
upload_trace(result)
|
| 510 |
+
trace_html = render_trace(result, max_chars=2000)
|
| 511 |
+
|
| 512 |
+
return {
|
| 513 |
+
"answer": trace.answer or "(no answer)",
|
| 514 |
+
"stats": format_stats(trace),
|
| 515 |
+
"trace_html": trace_html,
|
| 516 |
+
"remaining": _remaining(),
|
| 517 |
+
"error": None,
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
|
| 521 |
if __name__ == "__main__":
|
| 522 |
+
import fastapi
|
| 523 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 524 |
+
|
| 525 |
WORKSPACE_DIR = load_workspace()
|
| 526 |
+
demo = build_app()
|
| 527 |
+
|
| 528 |
+
# Mount the JSON API on the same FastAPI app
|
| 529 |
+
fastapi_app = fastapi.FastAPI()
|
| 530 |
+
fastapi_app.add_middleware(
|
| 531 |
+
CORSMiddleware,
|
| 532 |
+
allow_origins=["*"],
|
| 533 |
+
allow_methods=["POST"],
|
| 534 |
+
allow_headers=["*"],
|
| 535 |
+
)
|
| 536 |
+
|
| 537 |
+
@fastapi_app.post("/api/ask")
|
| 538 |
+
async def api_ask(request: fastapi.Request):
|
| 539 |
+
body = await request.json()
|
| 540 |
+
question = body.get("question", "")
|
| 541 |
+
if not question:
|
| 542 |
+
return {"error": "No question provided"}
|
| 543 |
+
return ask_question(question)
|
| 544 |
+
|
| 545 |
+
@fastapi_app.get("/api/remaining")
|
| 546 |
+
async def api_remaining():
|
| 547 |
+
return {"remaining": _remaining(), "limit": DAILY_LIMIT}
|
| 548 |
+
|
| 549 |
+
demo.launch(ssr_mode=False, app=fastapi_app)
|