Download main.py from NabilHzs/payparse: direct link, hf CLI and curl.
- Browser
- Download file 4.6 kB
-
https://huggingface.co/spaces/NabilHzs/payparse/resolve/main/main.py
- Command line
-
hf download hf://spaces/NabilHzs/payparse/main.py
-
curl -L -o main.py https://huggingface.co/spaces/NabilHzs/payparse/resolve/main/main.py
4.6 kB
| """ | |
| main.py | |
| ------- | |
| FastAPI application serving the PayParse multi-turn NLU assistant. | |
| Endpoints | |
| --------- | |
| POST /api/chat : one dialogue turn (NLU -> slot filling -> reply) | |
| DELETE /api/session/{id} : reset a conversation | |
| GET /health : liveness probe + engine mode | |
| Run locally: | |
| uvicorn main:app --reload | |
| Run on Hugging Face Spaces: | |
| The Dockerfile sets PORT=7860. GEMINI_API_KEY is injected as a HF Secret. | |
| PAYPARSE_SESSION_BACKEND=memory (HF filesystem is ephemeral). | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import os | |
| from contextlib import asynccontextmanager | |
| from pathlib import Path | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse, JSONResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from nlu_engine import MockNLUEngine, create_engine | |
| from schema import ChatRequest, ChatResponse | |
| from session_store import SessionStore | |
| from state_manager import StateManager | |
| STATIC_DIR = Path(__file__).parent / "static" | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| async def lifespan(app: FastAPI): | |
| """Initialize shared singletons once at startup.""" | |
| offline = os.getenv("PAYPARSE_OFFLINE", "0") == "1" | |
| use_paraphrase = os.getenv("PAYPARSE_LLM_FOLLOWUP", "0") == "1" | |
| engine = create_engine(offline=offline) | |
| app.state.engine = engine | |
| app.state.store = SessionStore() | |
| app.state.manager = StateManager( | |
| nlu_engine=engine, use_llm_paraphrase=use_paraphrase | |
| ) | |
| logger.info( | |
| "PayParse started (engine=%s, llm_followup=%s)", | |
| type(engine).__name__, | |
| use_paraphrase, | |
| ) | |
| yield | |
| app.state.store.close() | |
| app = FastAPI( | |
| title="PayParse — Indonesian NLU Voice Assistant", | |
| description=( | |
| "Zero-shot LLM NLU (Gemini Flash) + slot-filling state machine " | |
| "for Indonesian financial voice queries." | |
| ), | |
| version="1.0.0", | |
| lifespan=lifespan, | |
| ) | |
| # CORS — allow the Android app (and any local dev client) to call the API. | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # portfolio project; tighten for production | |
| allow_methods=["GET", "POST", "DELETE", "OPTIONS"], | |
| allow_headers=["*"], | |
| ) | |
| app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") | |
| def serve_ui(): | |
| """Serve the simple chat UI, or API info if static dir is missing.""" | |
| index = STATIC_DIR / "index.html" | |
| if index.exists(): | |
| return FileResponse(index) | |
| return { | |
| "name": "PayParse — Indonesian NLU Voice Assistant", | |
| "endpoints": { | |
| "chat": "POST /api/chat", | |
| "reset": "DELETE /api/session/{id}", | |
| "health": "GET /health", | |
| }, | |
| "docs": "/docs", | |
| } | |
| def health() -> dict: | |
| return { | |
| "status": "ok", | |
| "engine": type(app.state.engine).__name__, | |
| "offline_mode": isinstance(app.state.engine, MockNLUEngine), | |
| } | |
| def chat(request: ChatRequest) -> ChatResponse: | |
| """Process one user turn in a (possibly multi-turn) conversation.""" | |
| store: SessionStore = app.state.store | |
| state = store.get_or_create(request.session_id) | |
| nlu_result = app.state.engine.extract(request.message, state=state) | |
| logger.info( | |
| "session=%s intent=%s entities=%s", | |
| request.session_id, | |
| nlu_result.intent.value, | |
| nlu_result.entities.model_dump(exclude_none=True), | |
| ) | |
| result = app.state.manager.process( | |
| state, nlu_result, request.message, request.resolve_contacts_locally | |
| ) | |
| store.save(result.state) | |
| # A completed transaction ends the session; a fresh one starts next turn. | |
| if result.is_complete: | |
| store.delete(request.session_id) | |
| return ChatResponse( | |
| session_id=request.session_id, | |
| reply=result.reply, | |
| intent=result.state.intent, | |
| is_complete=result.is_complete, | |
| missing_slots=result.state.missing_slots, | |
| payload=result.payload, | |
| needs_contact_resolution=result.needs_contact_resolution, | |
| target_kontak=result.target_kontak, | |
| ) | |
| def reset_session(session_id: str) -> dict: | |
| """Discard a conversation so the user can start over.""" | |
| deleted = app.state.store.delete(session_id) | |
| if not deleted: | |
| raise HTTPException(status_code=404, detail="Session not found.") | |
| return {"status": "reset", "session_id": session_id} | |