소개
큐레이션 챗봇을 위해 RAG 데이터를 다양한 방법으로 구성해보았습니다.
고품질 데이터를 확보 및 가공하면 좋은데,
이 작업이 어려운 경우 다른 방식으로 빠르게 해보려는 의도와
여러 데이터간에 연동해서 데이터 가치를 높여보려고 의도를 실험 했습니다.
진행 방법
RAG 데이터 구성 방법으로
1) 테스트를 위한 시뮬레이션 데이터를 만든다.
2) API를 여러개 사용해서 데이터를 연동한다.
개발하려는 챗봇의 역할은 서울시에 있는 맛집을 목적에 맞게 소개하는 것 입니다.
첫번째 데이터 구성방법은 기존에 수집된 맛집데이터 포맷을 기준으로
가짜 맛집데이터와 후기를 파이썬을 사용해서 랜덤으로 만들어 냈습니다.
할루시네이션은 최소화 하기 위해서
서울시 도로명 정보와 전화번호 정보는 주어진 데이터를 활용하도록 했고,
음식정보는 기존에 네이버에서 수집한 데이터를 참고했고
후기는 긍정, 부정, 중립의 톤을 바탕으로 생성하게 했어요.
import random
import pandas as pd
from faker import Faker
import sys
# Faker 객체 생성 (한국어 로케일 설정)
fake = Faker('ko_KR')
# 서울시 자치구별 주요 도로명
seoul_areas = {
'강남구': ['테헤란로', '강남대로', '논현로', '도산대로', '압구정로'],
'마포구': ['월드컵로', '홍익로', '신촌로', '대흥로', '상암산로'],
'용산구': ['이태원로', '한강대로', '녹사평대로', '후암로', '청파로'],
'중구': ['을지로', '퇴계로', '세종대로', '청계천로', '명동길'],
'종로구': ['종로', '삼일대로', '율곡로', '종각길', '인사동길'],
'송파구': ['올림픽로', '석촌호수로', '송파대로', '잠실로', '방이동길'],
'서초구': ['서초대로', '반포대로', '양재대로', '강남대로', '잠원로'],
'광진구': ['능동로', '자양로', '화양로', '군자동길', '아차산로'],
'영등포구': ['여의대로', '국회대로', '선유로', '양평로', '당산로']
}
# 음식 종류 및 식당명/메뉴 데이터
food_data = {
'한식': {
'식당명': ['맛찬들', '한옥집', '서울 불고기', '비빔밥 전문점', '고기 굽는 집'],
'메뉴': ['불고기', '비빔밥', '갈비찜', '된장찌개', '김치찌개']
},
'중식': {
'식당명': ['만리장성', '중화반점', '황금룡', '상하이 딤섬', '사천요리 전문점'],
'메뉴': ['짜장면', '짬뽕', '탕수육', '고추잡채', '마라탕']
},
'일식': {
'식당명': ['스시 도쿄', '오사카 롤하우스', '일식 가정식', '사케 전문점', '도쿄 라멘'],
'메뉴': ['스시', '사시미', '라멘', '가츠동', '우동']
},
'양식': {
'식당명': ['이탈리안 테이블', '버거 팩토리', '스테이크 하우스', '파스타 전문점', '비스트로 카페'],
'메뉴': ['스테이크', '파스타', '피자', '버거', '리조또']
},
'비건': {
'식당명': ['채식주의자', '비건 가든', '그린 플래터', '비건 키친', '헬시 다이닝'],
'메뉴': ['채소 샐러드', '비건 버거', '두부 스테이크', '채소 카레', '비건 파스타']
},
'디저트': {
'식당명': ['스위트 카페', '디저트 하우스', '케이크 앤 초콜릿', '베이커리 카페', '아이스크림 파라다이스'],
'메뉴': ['초콜릿 케이크', '치즈 케이크', '아이스크림', '마카롱', '와플']
}
}
# 중복 방지를 위한 사용된 이름 및 리뷰 기록
used_restaurant_names = set()
used_reviews = set()
# 리뷰 생성에 사용할 단어 목록
review_words_positive = ["맛있어요", "최고예요", "추천합니다", "친절해요", "훌륭해요", "즐거웠어요", "다시 방문하고 싶어요"]
review_words_negative = ["별로예요", "실망했어요", "불편했어요", "맛없어요", "느렸어요", "재방문 의사 없어요"]
review_words_neutral = ["괜찮아요", "무난했어요", "보통이에요", "평범했어요", "특별하진 않아요", "깔끔했어요"]
# 리뷰 생성 함수
def generate_random_review():
review_type = random.choice(['positive', 'negative', 'neutral'])
if review_type == 'positive':
words = random.sample(review_words_positive, k=min(3, len(review_words_positive)))
elif review_type == 'negative':
words = random.sample(review_words_negative, k=min(3, len(review_words_negative)))
else:
words = random.sample(review_words_neutral, k=min(3, len(review_words_neutral)))
return f"{' '.join(words)}."
# 랜덤 리뷰 생성 함수
def generate_random_review():
review_type = random.choice(['positive', 'negative', 'neutral'])
if review_type == 'positive':
words = random.choices(review_words_positive, k=3) # 단어 중복 허용
elif review_type == 'negative':
words = random.choices(review_words_negative, k=3)
else:
words = random.choices(review_words_neutral, k=3)
return f"{' '.join(words)}."
# 가상 맛집 데이터 생성 함수
def generate_fake_restaurant(id):
# 지역 및 도로명 선택
area = random.choice(list(seoul_areas.keys()))
street = random.choice(seoul_areas[area])
# 음식 종류, 식당명, 메뉴 생성
food_type = random.choice(list(food_data.keys()))
# 중복되지 않는 식당명 생성
available_names = [name for name in food_data[food_type]['식당명'] if name not in used_restaurant_names]
if not available_names:
# 사용 가능한 이름이 없으면 초기화
used_restaurant_names.clear()
available_names = food_data[food_type]['식당명']
restaurant_name = random.choice(available_names)
used_restaurant_names.add(restaurant_name)
# 메뉴 생성
menu_items = random.sample(food_data[food_type]['메뉴'], k=random.randint(3, 5))
# 리뷰 생성 (함수 호출)
review = generate_random_review()
# 전화번호 생성: 02-XXXX-XXXX 형식
phone_number = f"02-{random.randint(1000, 9999)}-{random.randint(1000, 9999)}"
return {
'ID': id,
'음식 종류': food_type,
'식당명': restaurant_name,
'메뉴': ', '.join(menu_items),
'주소': f'서울특별시 {area} {street} {random.randint(1, 999)}-{random.randint(1, 99)}',
'전화번호': phone_number,
'영업시간': f'{random.randint(9, 11)}:00 ~ {random.randint(20, 23)}:00',
'휴무일': random.choice(['매주 월요일', '매주 화요일', '없음']),
'평점': round(random.uniform(3.5, 5.0), 1),
'리뷰 요약': review,
'가격대': f'₩{random.randint(5000, 20000)//1000 * 1000} ~ ₩{random.randint(25000, 50000)//1000 * 1000}',
'특징': random.choice(['예약 필수', '비건 옵션 제공', '영어 메뉴판 제공', '무료 와이파이']),
'주차 여부': random.choice(['가능', '불가능']),
'웹사이트/SNS': fake.url()
}
# 엑셀로 저장
def create_restaurant_data_to_excel(file_name, num_records):
data = []
for i in range(1, num_records + 1):
# 현재 진행 상태 출력 (캐리지 리턴)
sys.stdout.write(f"\r[{i}/{num_records}] 데이터를 생성 중...")
sys.stdout.flush()
# 데이터 생성
data.append(generate_fake_restaurant(i))
# 완료 메시지 출력
sys.stdout.write("\r데이터 생성 완료! 엑셀 파일로 저장 중...\n")
sys.stdout.flush()
# DataFrame으로 저장
df = pd.DataFrame(data)
df.to_excel(file_name, index=False)
print(f"{file_name} 파일로 {num_records}개의 레코드가 저장되었습니다.")
# 실행
if __name__ == "__main__":
create_restaurant_data_to_excel('seoul_restaurants_fake.xlsx', 100)
두번째 방법으로는, 구글맵 API는 맛집 상호명을 기준으로 좌표값을 얻을 수 있는데
공공데이터 날씨 API 가 위치좌표값을 입력정보로 받아서 그 지역의 날씨를 출력해주는 역할을 하기에 상호명과 방문날짜에 따라 날씨정보를 같이 제공할 수도 있겠다라고 생각해서 시도해 보았습니다. 확보한 정보를 연동해서 데이터 가치를 높이려고 하였습니다.
import requests
import pandas as pd
import datetime
# 오늘 날짜 가져오기 (YYYYMMDD 형식)
today = datetime.datetime.today().strftime("%Y%m%d")
# API 기본 정보
api_key = "YOUR_API_KEY"
api_url = "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst"
# 요청 파라미터 설정
params = {
"serviceKey": api_key,
"numOfRows": 10,
"pageNo": 1,
"dataType": "JSON",
"base_date": today,
"base_time": "0500",
"nx": 55,
"ny": 127,
}
# API 요청 실행
response = requests.get(api_url, params=params)
# ✅ 응답 상태 코드 확인
print(f"응답 상태 코드: {response.status_code}")
# ✅ 응답 본문 확인
if response.text:
print("응답 본문:", response.text[:500]) # 응답 일부 출력 (길이 제한)
else:
print("응답 없음")
# ✅ JSON 변환 시도
try:
data = response.json()
print("JSON 변환 성공")
# ✅ JSON에서 필요한 데이터 추출 (데이터가 없을 경우 대비)
items = data.get("response", {}).get("body", {}).get("items", {}).get("item", [])
if not items:
print("❌ 예보 데이터 없음. 요청 파라미터 확인 필요.")
else:
# ✅ pandas 데이터프레임 변환
df = pd.DataFrame(items)
print(df.head()) # 데이터 일부 출력
except requests.exceptions.JSONDecodeError:
print("❌ JSON 변환 실패: 응답이 비어 있거나 JSON 형식이 아님")
except Exception as e:
print(f"❌ 데이터 처리 중 오류 발생: {e}")
결과와 배운 점
파이썬, 커서와 친해지기
공공데이터 API 활용하기
데이터간 연동하기