1장. 과정 소개와 LLM 의 이해
실습 1 — 규칙 챗봇 만들고 한계 확인하기
구름다리여행의 자주 묻는 질문(faq.csv)으로 낱말 규칙 챗봇을 만들고, 손님이 쓸 법한 말투의 질문 20개(user_questions.csv)에 얼마나 맞게 답하는지 셉니다. 두 파일 모두 연습용으로 지어낸 가상 데이터입니다. 질문마다 사람이 붙여 둔 의도(정답)와 챗봇이 고른 의도를 견줍니다.
1단계. FAQ 읽기
import pandas as pd
# 구름다리여행의 자주 묻는 질문 10개를 표로 읽는다
faq = pd.read_csv("faq.csv")
# 결과: (행 수, 열 수)
print(faq.shape)
# 의도와 질문만 앞의 다섯 줄을 본다
print(faq[["의도", "질문"]].head())(10, 3)
의도 질문
0 예약변경 예약 날짜를 바꿀 수 있나요?
1 환불 예약을 취소하면 환불되나요?
2 수하물 짐은 몇 kg 까지 실을 수 있나요?
3 여권 여권 만료일이 얼마 남지 않았어요.
4 결제 카드 할부가 되나요?코드가 의미하는 것 pd.read_csv 로 읽은 FAQ 표는 규칙 챗봇이 아는 지식의 전부입니다. faq.shape 의 (10, 3) 은 챗봇이 답할 수 있는 갈래가 열 가지뿐이라는 뜻이고, 이 열 가지 밖의 질문에는 처음부터 답이 없습니다.
2단계. 낱말 규칙 만들기
# 의도마다 "이 낱말이 들어 있으면 그 의도"라고 사람이 정한 규칙
# 사전의 순서가 곧 규칙을 보는 순서다. 위에 있는 규칙이 먼저 걸린다
RULES = {
"예약변경": ["변경", "바꾸"],
"환불": ["환불", "취소"],
"수하물": ["짐", "수하물"],
"여권": ["여권"],
"결제": ["할부", "결제", "카드"],
"집합": ["공항", "몇 시"],
"보험": ["보험"],
"상담시간": ["상담 시간", "영업"],
"단체": ["단체"],
"반려동물": ["강아지", "반려"],
}
# 의도 이름으로 답을 바로 찾도록 {의도: 답} 사전을 만든다
ANSWERS = dict(zip(faq["의도"], faq["답"]))
def guess(question):
"""규칙을 위에서부터 보고, 낱말이 처음 걸린 의도를 돌려준다. 하나도 안 걸리면 "없음"."""
for intent, words in RULES.items():
# any(...) 는 낱말 가운데 하나라도 질문에 들어 있으면 True
if any(w in question for w in words):
return intent
return "없음"
def rule_bot(question):
# 의도를 고른 뒤 써 둔 답을 꺼낸다. 의도가 "없음"이면 사람에게 넘긴다
return ANSWERS.get(guess(question), "죄송합니다. 상담원에게 연결해 드릴게요.")
# 뜻이 같은 두 질문을 물어본다
for q in ["예약 날짜 바꾸고 싶어요", "출발일을 다음 주로 미룰 수 있을까요?"]:
print(q, "→", rule_bot(q))예약 날짜 바꾸고 싶어요 → 출발 7일 전까지 마이페이지에서 한 번 무료로 바꿀 수 있습니다.
출발일을 다음 주로 미룰 수 있을까요? → 죄송합니다. 상담원에게 연결해 드릴게요.코드가 의미하는 것 RULES 는 사람이 손으로 적은 "이 낱말이면 이 의도" 목록이고, guess 는 그 목록을 위에서부터 훑는 점원입니다. ANSWERS.get(…, "상담원에게 연결") 은 모르는 질문을 사람에게 넘기는 장치로, 규칙 챗봇이 실제 서비스에서 쓰는 안전한 마무리 방식입니다. 출력의 두 줄은 같은 뜻이라도 낱말이 다르면 결과가 갈린다는 것을 보여 줍니다.
3단계. 손님 질문 20개로 채점하기
# 손님 질문 20개와 사람이 붙인 의도(정답)를 읽는다
questions = pd.read_csv("user_questions.csv")
# map(guess): 질문마다 guess 를 불러 챗봇이 고른 의도를 새 열에 적는다
questions["추측"] = questions["질문"].map(guess)
# 챗봇이 고른 의도가 정답과 같으면 True
questions["맞음"] = questions["추측"] == questions["의도"]
# True 를 1 로 세어 맞힌 수를 구한다
print("맞힌 질문:", int(questions["맞음"].sum()), "/", len(questions))맞힌 질문: 11 / 20코드가 의미하는 것 map(guess) 로 손님 질문 스무 개를 한꺼번에 넣고, 사람이 붙인 정답(의도)과 견줘 맞힌 수를 셉니다. 챗봇을 감으로 평가하지 않고 "정답이 있는 질문 묶음"으로 점수를 매기는 방법이며, 8장의 평가 질문도 같은 생각입니다. 11 / 20 은 이 규칙 챗봇의 점수입니다.
4단계. 틀린 질문 읽고 저장하기
# ~ 는 True 와 False 를 뒤집는다. 틀린 질문만 골라 읽는다
wrong = questions[~questions["맞음"]]
print(wrong[["질문", "의도", "추측"]].to_string(index=False))
# 채점 결과를 파일로 남긴다. utf-8-sig 는 엑셀에서 한글이 깨지지 않게 하는 저장 방식이다
questions.to_csv("out_rule_bot_result.csv", index=False, encoding="utf-8-sig")
print("저장: out_rule_bot_result.csv") 질문 의도 추측
해외에서 다치면 보상받을 수 있나요? 보험 없음
캐리어 무게 제한이 궁금해요 수하물 없음
친구 12명이랑 가면 싸지나요? 단체 없음
비행기 타기 전에 언제까지 도착하면 돼요? 집합 없음
짐 싸는 요령 알려 주세요 없음 수하물
여행을 못 가게 됐는데 위약금이 있나요? 환불 없음
출발일을 다음 주로 미룰 수 있을까요? 예약변경 없음
고양이도 데려갈 수 있어요? 반려동물 없음
전화 상담은 몇 시까지 해요? 상담시간 집합
저장: out_rule_bot_result.csv코드가 의미하는 것 ~questions["맞음"] 으로 틀린 질문만 골라 읽는 것은 점수 뒤의 까닭을 찾는 일입니다. "없음"으로 끝난 것은 규칙에 없는 표현이고, 엉뚱한 의도로 간 것은 낱말이 겹친 경우입니다. 결과를 out_rule_bot_result.csv 로 남겨 두면 3장에서 LLM 챗봇과 같은 질문으로 견줄 수 있습니다.
이 실습이 뜻하는 것
이 실습은 ARS 번호 메뉴를 만든 뒤 실제 손님 전화 스무 통을 받아 보는 일과 같습니다. 안내문을 펼쳐 보고(1단계), "이 낱말이 나오면 이 안내"라는 메뉴를 짜고(2단계), 손님 질문을 넣어 맞힌 수를 세고(3단계), 틀린 통화를 다시 들어 봅니다(4단계). 여기서 안내문이 faq.csv, 번호 메뉴가 RULES, 손님 전화가 user_questions.csv 입니다. 비유와 다른 점은, ARS 는 손님이 번호를 고르지만 이 챗봇은 손님이 쓴 글에서 낱말을 찾아 스스로 고른다는 것입니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. FAQ 읽기 | (10, 3) | 자주 묻는 질문 10개, 열 셋(의도, 질문, 답)짜리 표입니다 |
| 2. 규칙 만들기 | "바꾸고"는 답, "미룰"은 상담원 연결 | 뜻이 같은 두 질문인데, "바꾸"라는 낱말이 있는 쪽만 답했습니다 |
| 3. 채점 | 11 / 20 | 손님 질문 스무 개 가운데 열한 개만 맞혔습니다 |
| 4. 틀린 질문 | 9개 | 일곱 개는 규칙의 낱말이 없어서 "없음"이 됐습니다(캐리어, 위약금, 고양이 등) |
| 4. 틀린 질문 | 엉뚱하게 걸린 2개 | "짐 싸는 요령"은 "짐"에 걸려 수하물로, "전화 상담은 몇 시까지"는 집합 규칙의 "몇 시"에 먼저 걸렸습니다 |
한 줄로 줄이면, 낱말 규칙은 표현이 조금만 달라져도 놓치고, 낱말이 겹치면 엉뚱한 답을 낸다는 것을 숫자로 확인한 실습입니다. 규칙을 더 적으면 맞힌 수는 늘겠지만, 손님의 말투는 끝이 없어서 규칙도 끝없이 늘어납니다. 뜻을 읽는 LLM 이 필요한 까닭입니다. 다만 이 스무 질문은 한계를 보이려고 지어낸 것이라, 실제 손님 질문에서 맞히는 비율은 이와 다를 수 있습니다.
코드 설명
RULES는 사전이라 적은 순서대로 규칙을 봅니다. "전화 상담은 몇 시까지 해요?"가 상담시간 대신 집합으로 간 것은, "몇 시"를 가진 집합 규칙이 상담시간 규칙보다 위에 있기 때문입니다.any(w in question for w in words)는 낱말 목록 가운데 하나라도 질문 안에 들어 있으면 True 입니다.in은 글자가 그대로 들어 있는지만 보므로 "취소"와 "위약금"이 같은 뜻인지는 모릅니다.questions["질문"].map(guess)는 질문 스무 개에guess를 하나씩 불러 결과를 새 열로 붙입니다. 반복문을 쓰지 않고 열 전체에 함수를 적용하는 pandas 의 방법입니다.int(questions["맞음"].sum())은 True 를 1, False 를 0 으로 더해 맞힌 수 11 을 냅니다.int()로 감싸 보통 정수로 찍습니다.out_rule_bot_result.csv에는 질문, 의도, 추측, 맞음의 네 열이 남습니다. 엑셀에서 열어 틀린 질문을 다시 볼 수 있습니다.