8장. 최종 프로젝트
설계서 쓰기
집을 지을 때는 벽돌을 쌓기 전에 설계도부터 그립니다. 몇 명이 살지, 방은 몇 개인지, 물과 전기는 어디로 들어오는지, 다 지은 뒤 무엇을 확인할지 적어 두어야 짓는 도중에 길을 잃지 않습니다. 챗봇도 코드를 쓰기 전에 설계서 한 장을 씁니다. 누가 무엇을 묻는지, 어떤 문서로 답하는지, 어떤 부품을 넣는지, 무엇으로 잘 되었는지 잴지를 적습니다.
네 상자가 설계서의 네 칸입니다. 첫째 파랑 상자는 사용자와 자주 받을 질문, 둘째 보라 상자는 답의 근거가 될 문서, 셋째 하늘 상자는 넣을 부품(문서 검색, 대화 기억, 도구, 화면), 넷째 회청 상자는 평가 질문과 정답, 범위 밖 질문에 할 말입니다. 여기서 몇 명이 사는지가 사용자, 물과 전기가 들어오는 곳이 문서, 방의 배치가 부품, 다 지은 뒤의 점검이 평가입니다. 비유와 다른 점은, 집의 설계도는 한 번 그리면 잘 바꾸지 않지만 챗봇의 설계서는 시연과 피드백을 거치며 여러 번 고친다는 것입니다.
설계서의 칸
| 칸 | 적을 것 | 프로젝트 1의 예 |
|---|---|---|
| 이름 | 챗봇의 이름 | 솔마루상사 규정 도우미 |
| 사용자 | 누가 묻나 | 솔마루상사 직원 |
| 자주 받을 질문 | 3개 이상 | 재택근무 며칠, 출장비 얼마, 휴가 신청 기한 |
| 문서 | 답의 근거, 쪽수, 나누는 단위 | solmaru_rules.pdf 3쪽, 조항 단위 12조각 |
| 부품 | 넣을 것과 뺄 것, 까닭 | 문서 검색과 웹 화면은 넣고, 대화 기억은 뺀다(규정 질문은 한 번에 끝나는 일이 많다) |
| 범위 밖 질문 | 답하지 않을 것과 그때 할 말 | "규정에서 찾지 못했습니다. 인사팀에 물어 주세요." |
| 평가 | 평가 질문 수, 정답 위치, 잴 것 | 질문 10개와 정답 조항, hit@1·hit@3, 범위 밖 질문 3개 |
넣을 부품 고르기
| 부품 | 배운 장 | 넣는 때 | 빼도 되는 때 |
|---|---|---|---|
| 문서 검색(RAG) | 6장 | 답이 문서에 있다 | 답이 짧은 표 하나로 끝난다 |
| 대화 기억 | 4장 | "그럼 그건요?" 처럼 이어 묻는다 | 질문이 한 번에 끝난다 |
| 도구 | 5장 | 계산하거나 표에서 찾아야 한다(요금, 남은 자리) | 글만 읽으면 답이 된다 |
| 구조화 출력 | 4장 | 답을 다른 프로그램이 받아 쓴다 | 사람이 읽기만 한다 |
| 웹 화면 | 7장 | 다른 사람이 써 본다(시연) | 혼자 시험만 한다 |
짧은 예시
4장에서 배운 Pydantic 으로 설계서의 칸을 틀로 정해 두면, 빠진 칸을 코드가 찾아 줍니다. 초안에 일부러 세 군데를 비웠습니다.
from pydantic import BaseModel, Field, ValidationError
class BotDesign(BaseModel):
"""챗봇 설계서의 칸. 칸이 빠지거나 기준에 못 미치면 오류가 난다"""
name: str
users: str # 누가 묻나
questions: list[str] = Field(min_length=3) # 자주 받을 질문(3개 이상)
documents: list[str] = Field(min_length=1) # 답의 근거가 될 문서(1개 이상)
parts: list[str] # 넣을 부품
out_of_scope: str # 답하지 않을 것과 그때 할 말
eval_count: int = Field(ge=10) # 평가 질문 수(10개 이상)
draft = {
"name": "솔마루상사 규정 도우미",
"users": "솔마루상사 직원",
"questions": ["재택근무는 며칠까지 되나요?", "출장비는 얼마인가요?"],
"documents": ["solmaru_rules.pdf"],
"parts": ["문서 검색", "웹 화면"],
"eval_count": 5,
}
try:
BotDesign(**draft)
except ValidationError as e:
# errors(): 어긴 칸마다 칸 이름(loc)과 까닭(msg)을 담은 사전 목록
for err in e.errors():
print(err["loc"][0], "→", err["msg"])questions → List should have at least 3 items after validation, not 2
out_of_scope → Field required
eval_count → Input should be greater than or equal to 10| 출력 | 뜻 |
|---|---|
questions → List should have at least 3 items… | 자주 받을 질문을 2개만 적었다. min_length=3(가장 짧은 길이 3) 에 못 미친다 |
out_of_scope → Field required | 범위 밖 질문에 할 말을 적지 않았다. 기본값이 없는 칸이라 꼭 있어야 한다 |
eval_count → Input should be greater than or equal to 10 | 평가 질문을 5개로 적었다. ge=10(greater than or equal, 10 이상) 에 못 미친다 |
오류 글은 pydantic 이 영어로 냅니다. 칸 이름과 함께 찍으면 무엇을 고칠지 바로 보입니다. 설계서를 사람이 읽는 문서로만 두지 않고 이렇게 틀로 두면, 팀이 함께 쓸 때도 빠진 칸이 없게 됩니다.
코드가 의미하는 것
이 코드는 설계도의 빈칸을 검사관이 하나씩 짚어 주는 일을 흉내 냅니다. 설계서의 칸과 기준을 클래스로 정하고, 초안 사전을 넣어 보고, 어긴 칸을 모아 찍는 세 동작입니다.
| 코드 | 하는 일 | 챗봇에서 뜻하는 것 |
|---|---|---|
class BotDesign(BaseModel) | 설계서의 칸과 자료형을 정한다 | 설계서를 사람이 읽는 글에서 코드가 검사할 수 있는 틀로 바꿉니다. 4장의 구조화 출력에서 쓴 것과 같은 Pydantic 틀입니다 |
Field(min_length=3), Field(ge=10) | 칸마다 최소 기준을 단다 | "자주 받을 질문 3개 이상", "평가 질문 10개 이상"이라는 팀의 약속을 코드로 적은 것입니다 |
out_of_scope: str | 기본값이 없어 꼭 채워야 하는 칸이 된다 | 범위 밖 질문에 할 말을 빼먹은 설계서는 통과하지 못합니다 |
e.errors() | 어긴 칸을 한꺼번에 모아 준다 | 첫 오류에서 멈추지 않고 고칠 곳을 한 번에 보여 줍니다 |
결과에서 읽을 것은 두 가지입니다.
- 세 칸이 걸렸습니다.
questions(2개),out_of_scope(없음),eval_count(5)의 세 줄이 일부러 비운 세 군데와 꼭 맞습니다. - 칸 이름이 고칠 곳입니다.
err["loc"][0]로 칸 이름을 앞에 찍었기 때문에 영어 오류 글을 다 읽지 않아도 어느 칸을 고칠지 압니다.
주의할 점
- "범위 밖 질문에 할 말"은 가장 자주 빠지는 칸입니다. 이 칸이 없으면 챗봇은 모르는 것도 그럴듯하게 지어냅니다.
- 평가 질문은 코드를 쓰기 전에 만듭니다. 챗봇을 다 만든 뒤에 질문을 만들면, 챗봇이 잘 답하는 질문만 고르기 쉽습니다.