사용자는 여행 지역, 날짜, 연령대, 동행 인원, 그리고 여행 목적을 입력한 뒤, '완료' 버튼을 누른다. 그러면 AI 에이전트가 입력된 데이터를 분석하여 최적의 여행 경로를 생성한다.
프론트엔드에서 백엔드로 데이터 전달 및 처리 과정
1. React 상태 관리 - 여행 계획 입력 폼
이 코드는 React의 useState 훅을 사용하여 여행 계획 입력 폼에서 필요한 상태를 관리하는 부분이다. 사용자가 입력하는 지역, 날짜, 연령대, 목적 등의 정보를 저장하고 조작할 수 있도록 설정되어 있다.
지역 리스트(allRegions)는 데이터베이스(DB)에서 가져오며, 사용자가 입력한 값에 따라 필터링된 지역 리스트(filteredRegions)를 업데이트한다.
const [region, setRegion] = useState<string>(""); // 지역 입력값
const [allRegions, setAllRegions] = useState<any[]>([]); // 모든 지역 리스트
const [filteredRegions, setFilteredRegions] = useState<any[]>([]); // 필터링된 지역 리스트
const [selectedDateRange, setSelectedDateRange] = useState<[Date, Date] | null>(null); // 날짜
const [selectedAge, setSelectedAge] = useState<string | null>(null); // 나이
const [selectedPurposes, setSelectedPurposes] = useState<string[]>([]); // 목적
2. 일정 입력 완료 버튼 클릭시 실행하는 이벤트 핸들러
이 함수는 사용자가 여행 계획 입력을 완료하고 "완료" 버튼을 클릭했을 때 실행되는 이벤트 핸들러이다.
입력된 데이터를 하나의 객체로 묶어 백엔드에 전송하며, 날짜 형식 변환 및 필터링 처리를 수행한다.
// 일정 계획 데이터 전송 이벤트 핸들러(완료 버튼 클릭시)
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
try {
// 날짜를 MySQL의 DATE 형식(YYYY-MM-DD)으로 변환
const formatToDate = (date: Date): string => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0"); // JavaScript는 월 0부터 시작하므로 +1
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
// 선택된 날짜 범위를 변환하거나, 없다면 null로 처리
const formattedDateRange = selectedDateRange
? {
start_date: formatToDate(selectedDateRange[0]),
end_date: formatToDate(selectedDateRange[1]),
}
: null;
// companions에서 count가 0 이상인 데이터만 필터링
const filteredCompanions = companions.filter((companion) => companion.count > 0);
// 전송할 데이터 구성
const requestData = {
location: region,
start_date: formattedDateRange?.start_date,
end_date: formattedDateRange?.end_date,
ageGroup: selectedAge,
companions: filteredCompanions,
concepts: selectedPurposes,
};
console.log("전송 데이터:", requestData);
// API 요청
const response = await axios.post(`${API_BASE_URL}/agents/plan`, requestData);
// 성공 처리
console.log("응답 데이터:", response.data);
navigate("/plan/list");
} catch (error) {
console.error("API 요청 중 오류 발생:", error);
alert("여행 계획 저장 중 오류가 발생했습니다. 다시 시도해주세요.");
}
};
3. FastAPI 라우터 등록 및 설정
FastAPI 백엔드에서 프론트엔드와 연동하기 위해 라우터를 설정하고, CORS 정책을 적용하며, 예외 처리를 포함한 기본적인 API 구성을 한다.
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.exceptions import HTTPException
from app.repository.db import lifespan
from app.routers.agents.travel_all_schedule_agent_router import router as agent_router
import os
from dotenv import load_dotenv
load_dotenv()
# FastAPI 애플리케이션 생성
app = FastAPI(lifespan=lifespan)
# CORS 설정
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # 모든 출처 허용
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def root():
return HTMLResponse(
"""
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>EasyTravel Server</title>
</head>
<body>
<h1>EasyTravel Server</h1>
<p>API 서버가 정상적으로 작동 중입니다.</p>
</body>
</html>
"""
)
@app.exception_handler(HTTPException)
async def custom_http_exception_handler(request: Request, exc: HTTPException):
"""
HTTPException 처리
"""
return JSONResponse(
status_code=exc.status_code,
content={"message": exc.detail},
)
# 라우터 추가
app.include_router(agent_router, prefix="/agents", tags=["agents"])
# 데이터베이스 초기화
# init_table_by_SQLModel()
4. FastAPI 라우터 설정 (여행 일정 생성 API)
이 라우터는 여행 일정 데이터를 받아 CrewAI를 실행하고, 최적의 여행 경로를 생성하는 역할을 한다.
Pydantic을 사용하여 요청 데이터를 검증하고, 일정 생성 서비스와 연결하여 처리한다.
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import List
from app.services.agents.travel_all_schedule_agent_service import create_plan
router = APIRouter()
# Pydantic 모델 정의
class Companion(BaseModel):
label: str
count: int
class TravelPlanRequest(BaseModel):
ageGroup: str
companions: List[
Companion
]
start_date: str
end_date: str
concepts: List[str]
location: str
@router.post("/plan")
async def generate_plan(user_input: TravelPlanRequest):
"""
여행 일정을 생성하는 엔드포인트.
- CrewAI 실행 후 일정(JSON) 반환.
"""
try:
print("프론트에서 받은 데이터:", user_input) # 요청 데이터 출력
print("Python dict 변환:", user_input.model_dump()) # dict로 변환 후 출력
result = await create_plan(user_input.model_dump())
return {
"status": "success",
"message": "일정과 장소 리스트가 생성되었습니다.",
"data": result,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
✅ 프론트엔드에서 받은 데이터의 구조와 dict 변환 확인
FastAPI 라우터에서 프론트엔드가 전달한 데이터를 user_input으로 받아왔고, 이를 Pydantic 모델(TravelPlanRequest)을 통해 객체로 변환했다.
이 데이터를 확인하기 위해 print()를 사용하여 객체 형태와 dict 형태로 변환된 결과를 출력했다.
프론트에서 받은 데이터: ageGroup='20대' companions=[Companion(label='성인', count=2)] start_date='2025-01-31' end_date='2025-02-01' concepts=['맛집', '바다'] location='부산광역시'
Python dict 변환: {'ageGroup': '20대', 'companions': [{'label': '성인', 'count': 2}], 'start_date': '2025-01-31', 'end_date': '2025-02-01', 'concepts': ['맛집', '바다'], 'location': '부산광역시'}
5. FastAPI 서비스 로직에서 user_input 데이터를 활용한 여행 일정 생성
이 서비스 함수는 프론트엔드에서 전달된 user_input 데이터를 받아, 여행 계획을 생성하는 핵심 로직을 담당한다.
사용자가 입력한 정보를 바탕으로 여행 일정 데이터를 구성하고, CrewAI를 활용해 최적의 여행 코스를 생성한 후 JSON 형태로 반환한다.
def create_plan(user_input):
try:
# location = user_input["location"]
location = user_input.get("location", "Unknown Location")
trip_days = calculate_trip_days(
user_input["start_date"], user_input["end_date"]
)
... 생략
# 4 CrewAI 결과를 JSON 형식으로 변환 (plan + spots)
response_json = {
"message": "요청이 성공적으로 처리되었습니다.",
"plan": {
"name": user_input.get("name", "여행 일정"),
"start_date": user_input["start_date"],
"end_date": user_input["end_date"],
"main_location": location,
"ages": user_input.get("ages", 0),
"companion_count": sum(
companion.get("count", 0)
for companion in user_input.get("companions", [])
),
"concepts": ", ".join(user_input.get("concepts", [])),
"member_id": user_input.get("member_id", 0),
"created_at": datetime.now().strftime("%Y-%m-%d"),
"updated_at": datetime.now().strftime("%Y-%m-%d"),
},
"spots": final_result.spots if hasattr(final_result, "spots") else [],
}
return response_json
except Exception as e:
print(f"[ERROR] {e}")
traceback.print_exc()
return {"message": "요청 처리 중 오류가 발생했습니다.", "error": str(e)}
'Python' 카테고리의 다른 글
[Python] print와 logging, 디버깅할 때 무엇을 쓸까? (0) | 2025.02.17 |
---|---|
[Python] 동기 vs 비동기, 언제 그리고 어떻게 사용해야 할까? (0) | 2025.02.07 |
[Python] Google Maps & SerpAPI를 활용한 맛집 추천 개발기 (0) | 2025.01.30 |
[Python] SQLAlchemy와 SQLModel의 모든 것: 데이터베이스 작업의 핵심 도구 (0) | 2025.01.26 |
[Python] Pydantic으로 안전하고 효율적인 데이터 검증하기 (0) | 2025.01.25 |