test / api_server.py
JOhyeongi's picture
Upload 6 files
d8a3c6f verified
"""
๋ชจ๋ฐ”์ผ ๋ฆฌ์•กํŠธ ํ”„๋ŸฐํŠธ์—์„œ ํ˜ธ์ถœํ•  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")
# CORS (๋ฆฌ์•กํŠธ/๋ชจ๋ฐ”์ผ ์›น์—์„œ ํ˜ธ์ถœ ํ—ˆ์šฉ)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ํ”„๋ŸฐํŠธ์—”๋“œ ์ •์  ํŒŒ์ผ ์ œ๊ณต (frontend/dist๊ฐ€ ์žˆ์œผ๋ฉด ์ž๋™ ์„œ๋น™)
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,
)
# ์ง„ํ–‰ ํ˜„ํ™ฉ (current_amount ์ œ๊ณต ์‹œ)
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,
)
# WelfareAdvisor์— ์‚ฌ์šฉ์ž ์ •๋ณด ์ „๋‹ฌ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋ฅผ ์œ„ํ•ด ๋ณด์กฐ ํ˜ธ์ถœ (์„ ํƒ)
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)