| """
|
| ๋ชจ๋ฐ์ผ ๋ฆฌ์กํธ ํ๋ฐํธ์์ ํธ์ถํ REST API ์๋ฒ (FastAPI)
|
|
|
| ์๋ํฌ์ธํธ:
|
| - POST /api/advise : ์ข
ํฉ ์๋ด (๊ธ์ต+์ ๋ณด+๋ณต์ง)
|
| - POST /api/qa : ์์ ์ง๋ฌธ/์ฑ๋ด ์๋ต
|
| - GET /api/welfare/search : ๋ณต์ง์ ์ฑ
๊ฒ์
|
| - GET /api/products/search : ๊ธ์ต์ํ RAG ๊ฒ์
|
| - GET /health : ์ํ ์ ๊ฒ
|
| """
|
|
|
| from fastapi import FastAPI, HTTPException, Query
|
| from fastapi.staticfiles import StaticFiles
|
| from fastapi.middleware.cors import CORSMiddleware
|
| from pydantic import BaseModel, Field
|
| from typing import List, Optional, Dict, Any
|
|
|
| from agents.multi_agent_system import get_meeting_account_advisor
|
| from agents.welfare_advisor import WelfareAdvisor
|
| from agents.universal_info_advisor import get_universal_info_advisor
|
| from models.rag_system import get_kb_rag
|
|
|
| import pandas as pd
|
| import uvicorn
|
|
|
|
|
| app = FastAPI(title="KB ๊ธ์ต AI Advisor API", version="1.0.0")
|
|
|
|
|
| app.add_middleware(
|
| CORSMiddleware,
|
| allow_origins=["*"],
|
| allow_credentials=True,
|
| allow_methods=["*"],
|
| allow_headers=["*"],
|
| )
|
|
|
|
|
| try:
|
| import os
|
| FRONT_DIST = os.path.join(os.path.dirname(__file__), "frontend", "dist")
|
| if os.path.isdir(FRONT_DIST):
|
| app.mount("/", StaticFiles(directory=FRONT_DIST, html=True), name="frontend")
|
| except Exception:
|
| pass
|
|
|
|
|
| class UserInfo(BaseModel):
|
| age_group: Optional[str] = None
|
| family_type: Optional[str] = None
|
| income_level: Optional[str] = None
|
| region: Optional[str] = None
|
|
|
|
|
| class AdviseRequest(BaseModel):
|
| purpose: str = Field(..., description="์: ์คํ์ธ ์ดํ์ฐ์, ๊ฒฐํผ์ค๋น ๋ฑ")
|
| duration_months: int = Field(..., ge=1, le=120)
|
| target_amount: int = Field(..., ge=1)
|
| detailed_purpose: Optional[str] = None
|
| current_amount: Optional[int] = 0
|
| user_info: Optional[UserInfo] = None
|
|
|
|
|
| class AdviseResponse(BaseModel):
|
| purpose_category: Optional[str]
|
| financial_recommendation: Optional[str]
|
| detailed_info: Optional[str]
|
| welfare_recommendation: Optional[str]
|
| comprehensive_advice: Optional[str]
|
| progress_info: Optional[str] = None
|
|
|
|
|
| class QARequest(BaseModel):
|
| question: str
|
| detailed_context: Optional[str] = None
|
|
|
|
|
| class QAResponse(BaseModel):
|
| answer: str
|
| category: Optional[str] = None
|
|
|
|
|
| @app.get("/health")
|
| def health() -> Dict[str, Any]:
|
| return {"status": "ok"}
|
|
|
|
|
| @app.post("/api/advise", response_model=AdviseResponse)
|
| def api_advise(req: AdviseRequest):
|
| try:
|
| advisor = get_meeting_account_advisor()
|
| results = advisor.get_comprehensive_advice(
|
| purpose=req.purpose,
|
| duration_months=req.duration_months,
|
| target_amount=req.target_amount,
|
| detailed_purpose=req.detailed_purpose,
|
| )
|
|
|
|
|
| progress_info = None
|
| if req.current_amount and req.current_amount > 0:
|
| progress_info = advisor.get_savings_progress_tracking(
|
| req.purpose,
|
| req.duration_months,
|
| req.target_amount,
|
| req.current_amount,
|
| )
|
|
|
|
|
| welfare_text = results.get("welfare_recommendation")
|
| if (not welfare_text or "์์ต๋๋ค" in welfare_text) and req.user_info:
|
| welfare = WelfareAdvisor().get_welfare_recommendations(
|
| req.purpose, req.user_info.model_dump()
|
| )
|
| if welfare:
|
| results["welfare_recommendation"] = welfare
|
|
|
| return AdviseResponse(
|
| purpose_category=results.get("purpose_category"),
|
| financial_recommendation=results.get("financial_recommendation"),
|
| detailed_info=results.get("detailed_info"),
|
| welfare_recommendation=results.get("welfare_recommendation"),
|
| comprehensive_advice=results.get("comprehensive_advice"),
|
| progress_info=progress_info,
|
| )
|
| except Exception as e:
|
| raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
| @app.post("/api/qa", response_model=QAResponse)
|
| def api_qa(req: QARequest):
|
| try:
|
| advisor = get_universal_info_advisor()
|
| info = advisor.get_comprehensive_info(
|
| purpose=req.question,
|
| detailed_purpose=req.detailed_context,
|
| )
|
| return QAResponse(
|
| answer=info.get("comprehensive_info", "๋ต๋ณ์ ์์ฑํ์ง ๋ชปํ์ต๋๋ค."),
|
| category=info.get("category"),
|
| )
|
| except Exception as e:
|
| raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
| @app.get("/api/welfare/search")
|
| def api_welfare_search(
|
| keyword: Optional[str] = Query(None, description="์ ์ฑ
๋ช
/๋ด์ฉ/๋์ ๊ฒ์์ด"),
|
| region: Optional[str] = Query(None, description="์ง์ญ๋ช
(์: ์์ธ, ๋ถ์ฐ)"),
|
| page: int = Query(1, ge=1),
|
| size: int = Query(10, ge=1, le=100),
|
| ):
|
| try:
|
| wa = WelfareAdvisor()
|
| df = wa.welfare_data
|
| if df.empty:
|
| return {"total": 0, "items": []}
|
|
|
| filtered = df.copy()
|
| if keyword:
|
| kw = keyword.strip()
|
| mask = (
|
| filtered["policy_name"].fillna("").str.contains(kw, case=False)
|
| | filtered["service_content_detail"].fillna("").str.contains(kw, case=False)
|
| | filtered["target_audience_description"].fillna("").str.contains(kw, case=False)
|
| )
|
| filtered = filtered[mask]
|
|
|
| if region and region != "์ ์ฒด":
|
| filtered = filtered[
|
| (filtered["region_name"].fillna("").str.contains(region, case=False))
|
| | (filtered["region_name"] == "์ ๊ตญ")
|
| ]
|
|
|
| total = len(filtered)
|
| start = (page - 1) * size
|
| end = start + size
|
| page_df = filtered.iloc[start:end]
|
|
|
| items = []
|
| for _, row in page_df.iterrows():
|
| items.append(
|
| {
|
| "policy_name": row.get("policy_name", ""),
|
| "governing_body": row.get("governing_body_name", ""),
|
| "target_audience": row.get("target_audience_description", ""),
|
| "service_content": row.get("service_content_detail", ""),
|
| "region": row.get("region_name", "์ ๊ตญ"),
|
| "application_link": row.get("application_link", ""),
|
| "last_updated": row.get("last_updated", ""),
|
| "tags": row.get("target_audience_tags", ""),
|
| }
|
| )
|
|
|
| return {"total": total, "items": items}
|
| except Exception as e:
|
| raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
| @app.get("/api/products/search")
|
| def api_products_search(q: str = Query(..., description="๊ฒ์ ์ง์"), k: int = Query(5, ge=1, le=20)):
|
| try:
|
| rag = get_kb_rag()
|
| results = rag.search_products(q, k=k) or []
|
| return {"items": results}
|
| except Exception as e:
|
| raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
| if __name__ == "__main__":
|
| uvicorn.run("api_server:app", host="0.0.0.0", port=8000, reload=True)
|
|
|
|
|
|
|