import os import pandas as pd from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from langchain_openai import ChatOpenAI from langchain_experimental.agents import create_pandas_dataframe_agent app = FastAPI(title="BiteSight Analytics API") app.add_middleware( CORSMiddleware, allow_origins=["*"], # In production, replace with your Vercel URL allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Load data on startup DATA_FILE = "data_client.csv" try: df = pd.read_csv(DATA_FILE) print(f"Successfully loaded {DATA_FILE} with {len(df)} rows.") except Exception as e: print(f"Warning: Could not load {DATA_FILE}: {e}") df = pd.DataFrame() # Fallback empty dataframe class ChatRequest(BaseModel): question: str @app.post("/chat") async def chat(request: ChatRequest): try: if df.empty: return {"answer": "Error: Data source is unavailable on the server."} akashml_api_key = os.environ.get("AKASHML_API_KEY") if not akashml_api_key: return {"answer": "Error: AKASHML_API_KEY environment variable tidak diset di Hugging Face Space Anda."} # Initialize AkashML LLM via ChatOpenAI (OpenAI-compatible) llm = ChatOpenAI( openai_api_key=akashml_api_key, openai_api_base="https://api.akashml.com/v1", model_name="MiniMaxAI/MiniMax-M2.5", temperature=0.2, max_tokens=8192 ) system_message = ( "You are a world-class F&B Executive Business Consultant and Data Scientist " "with 20+ years of experience advising top restaurant chains and retail F&B brands. " "You have deep expertise in sales analytics, consumer behavior, operational efficiency, and growth strategy.\n\n" "RESPONSE STYLE:\n" "- If the user sends a general greeting, respond warmly and naturally in Indonesian.\n" "- If the user asks a business/data question, give a THOROUGH, INSIGHTFUL, and EXECUTIVE-LEVEL analysis. " "Do NOT give shallow or overly brief answers. Go deep. Uncover layers of insight.\n" "- ALWAYS include: key data facts WITH specific numbers, analytical commentary explaining the 'why', " "patterns/anomalies/trends, business implications, AND concrete actionable recommendations.\n" "- Structure your response FREELY using rich Markdown: use headers (##, ###), bold text, " "bullet lists, and comparison tables when helpful. DO NOT use any rigid fixed template.\n" "- ALWAYS respond in fluent, professional INDONESIAN.\n\n" "PANDAS DATA ENGINE RULES (CRITICAL):\n" "- A pandas DataFrame named `df` with ALL rows of client transaction data is ALREADY LOADED.\n" "- ALWAYS run actual pandas operations to get real numbers. NEVER estimate or fabricate data.\n" "- DO NOT recreate the dataframe using StringIO or hardcoded text. Use `df` directly.\n" "- Run MULTIPLE pandas queries if needed to build a complete picture " "(e.g., value_counts, groupby, mean, corr, describe, resample, etc.).\n" "- Cross-reference multiple dimensions when relevant (e.g., payment method vs. branch vs. time of day).\n\n" "DATA PRESENTATION RULES:\n" "- ALWAYS state specific numbers, percentages, and timeframes. Vague statements are not acceptable.\n" "- Currency is US Dollars ($). ALWAYS format as $XX.XX. NEVER use 'Rp' or 'Rupiah'.\n" "- Do NOT explain the dataset schema or mention row counts unless explicitly asked.\n" "- When comparing metrics, provide both absolute numbers AND percentages for clarity.\n" ) agent = create_pandas_dataframe_agent( llm, df, agent_type="tool-calling", allow_dangerous_code=True, handle_parsing_errors=True, verbose=True, prefix=system_message ) response = agent.invoke({"input": request.question}) return {"answer": response.get("output", "No output generated.")} except Exception as e: error_msg = str(e) if "Could not parse" in error_msg: extracted = error_msg.split("Could not parse")[-1].replace("LLM output:", "").strip(" `'\"") return {"answer": extracted} return {"answer": f"Terjadi kendala saat memproses instruksi. Jika Anda sekadar menyapa, halo juga! Jika ini pertanyaan data, format Model mungkin tidak sesuai.\n\n**Log Server:** `{error_msg}`"} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7860) # Hugging Face Spaces typical port