본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 17. · 7 Views
간단한 AI 챗봇 만들기 프로젝트 완벽 가이드
OpenAI API를 활용하여 대화형 AI 챗봇을 처음부터 끝까지 만들어봅니다. 대화 루프 구현부터 스트리밍 응답까지, 실무에서 바로 적용할 수 있는 챗봇 개발의 모든 것을 다룹니다.
목차
1. 프로젝트 목표 설정
김개발 씨는 회사에서 고객 응대용 챗봇을 만들어달라는 업무를 받았습니다. "AI 챗봇이요?
어떻게 시작해야 하죠?" 막막했지만, 선배 박시니어 씨가 말했습니다. "일단 목표부터 명확히 정하는 게 중요해요."
프로젝트를 시작하기 전에 무엇을 만들 것인지 명확히 정의하는 단계입니다. 마치 여행을 떠나기 전에 목적지를 정하는 것과 같습니다.
이 단계에서 필요한 기능과 기술 스택을 결정하면 개발이 훨씬 수월해집니다.
다음 코드를 살펴봅시다.
# 프로젝트 기본 설정
import openai
import os
from dotenv import load_dotenv
# 환경 변수 로드
load_dotenv()
# OpenAI API 키 설정
openai.api_key = os.getenv("OPENAI_API_KEY")
# 프로젝트 설정
PROJECT_NAME = "Simple AI Chatbot"
MODEL = "gpt-3.5-turbo"
MAX_TOKENS = 150
print(f"{PROJECT_NAME} 초기화 완료")
김개발 씨는 커피를 한 모금 마시고 생각에 잠겼습니다. AI 챗봇이라는 단어는 많이 들어봤지만, 실제로 만들어본 적은 없었습니다.
어디서부터 시작해야 할까요? 박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다.
"챗봇 프로젝트는 건물을 짓는 것과 비슷해요. 먼저 설계도가 필요하죠." 프로젝트 목표란 무엇일까요? 쉽게 비유하자면, 프로젝트 목표는 마치 여행 계획서와 같습니다.
어디로 갈지, 무엇을 할지, 어떻게 갈지를 미리 정하는 것입니다. 이처럼 챗봇 프로젝트도 무엇을 만들지, 어떤 기능이 필요한지를 먼저 정의해야 합니다.
목표 없이 시작하면 어떤 문제가 생길까요? 옛날 김개발 씨의 동료 이주니어 씨가 있었습니다. 이주니어 씨는 목표 설정 없이 바로 코딩을 시작했습니다.
"일단 만들면서 생각하면 되겠지!" 하지만 며칠 후, 코드는 스파게티처럼 꼬였고 어디서부터 잘못된 건지 알 수 없게 되었습니다. 더 큰 문제는 시간 낭비였습니다.
필요 없는 기능을 만들고, 중요한 기능은 놓쳤습니다. 결국 프로젝트를 처음부터 다시 시작해야 했습니다.
명확한 목표 설정이 주는 이점 박시니어 씨가 계속 설명했습니다. "목표를 명확히 하면 세 가지 이점이 있어요." 첫째, 개발 방향이 명확해집니다.
어떤 기능을 먼저 만들어야 할지, 어떤 기술을 사용해야 할지 알 수 있습니다. 둘째, 시간을 절약할 수 있습니다.
불필요한 기능을 만들지 않고 핵심에 집중할 수 있습니다. 셋째, 팀원과의 소통이 쉬워집니다.
모두가 같은 목표를 보고 달려갈 수 있습니다. 우리 챗봇의 목표 정의하기 김개발 씨는 메모장을 펼쳤습니다.
"그럼 우리 챗봇의 목표를 정해볼까요?" 먼저 기본 기능을 정의했습니다. 사용자의 질문을 받아서 AI가 답변하는 것, 이것이 핵심입니다.
그다음 기술 스택을 선택했습니다. Python을 사용하고, OpenAI API를 활용하기로 했습니다.
마지막으로 제약 조건을 정했습니다. 응답은 150토큰 이내로, 모델은 비용 효율적인 GPT-3.5-turbo로 결정했습니다.
코드로 목표를 표현하기 위의 코드를 살펴보겠습니다. 가장 먼저 필요한 라이브러리를 임포트합니다.
openai는 OpenAI API를 사용하기 위한 공식 라이브러리입니다. dotenv는 API 키를 안전하게 관리하기 위한 라이브러리입니다.
환경 변수를 로드하는 부분이 중요합니다. API 키를 코드에 직접 작성하면 보안 문제가 발생할 수 있습니다.
따라서 .env 파일에 저장하고 불러오는 방식을 사용합니다. 프로젝트 설정 부분에서는 상수를 정의합니다.
MODEL은 사용할 AI 모델을, MAX_TOKENS는 응답의 최대 길이를 제한합니다. 실무에서의 활용 실제 회사 프로젝트에서는 어떻게 목표를 설정할까요?
예를 들어 쇼핑몰 고객센터 챗봇을 만든다고 가정해봅시다. 목표는 "고객의 배송 문의에 즉시 응답하는 챗봇 개발"이 됩니다.
이때 필요한 기능은 주문 조회, 배송 추적, FAQ 응답 등으로 구체화됩니다. 많은 스타트업에서 이런 방식으로 MVP를 빠르게 개발합니다.
초보자가 주의할 점 김개발 씨가 질문했습니다. "목표를 너무 크게 잡으면 어떻게 되나요?" 박시니어 씨가 웃으며 답했습니다.
"좋은 질문이에요. 초보자들이 흔히 하는 실수가 바로 그거죠." 목표를 너무 크게 잡으면 완성하지 못하고 포기하게 됩니다.
처음에는 작게 시작해서 점진적으로 기능을 추가하는 것이 현명합니다. 첫 걸음을 내딛다 김개발 씨는 이제 무엇을 만들어야 할지 명확해졌습니다.
"간단한 대화형 챗봇, OpenAI API 사용, Python으로 구현!" 목표가 선명해지니 갑자기 자신감이 생겼습니다. 프로젝트를 시작할 때 가장 중요한 것은 명확한 목표입니다.
여러분도 챗봇을 만들기 전에 먼저 목표를 정의해보세요. 그 작은 시간 투자가 나중에 큰 시간 절약으로 돌아옵니다.
실전 팁
💡 - API 키는 절대 코드에 직접 작성하지 말고 환경 변수로 관리하세요
- 처음부터 완벽한 챗봇을 만들려 하지 말고, 작은 기능부터 시작하세요
- 사용할 모델과 토큰 제한을 미리 정하면 비용을 절약할 수 있습니다
2. 대화 루프 구현
목표를 정한 김개발 씨는 본격적으로 코딩을 시작했습니다. 하지만 첫 번째 문제에 부딪혔습니다.
"사용자 입력을 어떻게 계속 받죠?" 박시니어 씨가 말했습니다. "대화 루프가 필요해요."
대화 루프는 사용자의 입력을 받고 AI 응답을 출력하는 과정을 반복하는 구조입니다. 마치 전화 통화처럼 대화가 끊기지 않고 계속 이어지게 만듭니다.
이것이 챗봇의 핵심 동작 원리입니다.
다음 코드를 살펴봅시다.
def chat_loop():
"""메인 대화 루프"""
print("챗봇을 시작합니다. 종료하려면 'quit'를 입력하세요.")
while True:
# 사용자 입력 받기
user_input = input("\n당신: ").strip()
# 종료 조건 체크
if user_input.lower() in ['quit', 'exit', '종료']:
print("챗봇을 종료합니다.")
break
# 빈 입력 처리
if not user_input:
continue
# AI 응답 생성
response = get_ai_response(user_input)
print(f"AI: {response}")
김개발 씨는 화면을 바라보며 고민했습니다. 사용자가 한 번 질문하고 끝나는 프로그램은 만들 수 있습니다.
하지만 진짜 챗봇은 계속 대화를 이어가야 합니다. 어떻게 해야 할까요?
박시니어 씨가 의자를 끌어당기며 앉았습니다. "대화 루프라는 개념을 알아야 해요." 대화 루프란 무엇일까요? 대화 루프를 이해하는 가장 쉬운 비유는 전화 통화입니다.
전화를 걸면 상대방과 말을 주고받습니다. 한 사람이 말하면 다른 사람이 듣고 답합니다.
이 과정이 전화를 끊을 때까지 반복됩니다. 챗봇의 대화 루프도 정확히 같은 방식입니다.
사용자가 입력하면 AI가 응답하고, 다시 사용자가 입력하길 기다립니다. 이것이 무한 반복되는 구조입니다.
루프 없이 만들면 어떻게 될까요? 김개발 씨의 동료 최초보 씨의 이야기입니다. 최초보 씨는 루프를 사용하지 않고 챗봇을 만들었습니다.
사용자가 질문을 하면 AI가 답변하고 프로그램이 종료되었습니다. "한 번 질문할 때마다 프로그램을 다시 실행해야 해요?" 사용자들은 불편해했습니다.
실제 대화처럼 느껴지지 않았고, 사용성이 형편없었습니다. 이것이 대화 루프가 없을 때 생기는 문제입니다.
while True의 마법 위 코드의 핵심은 while True 구문입니다. 이것은 "조건이 참인 동안 계속 반복하라"는 의미입니다.
True는 항상 참이므로, 이론적으로는 영원히 반복됩니다. "영원히 반복하면 프로그램이 멈추지 않는 거 아닌가요?" 김개발 씨가 걱정스럽게 물었습니다.
박시니어 씨가 고개를 저었습니다. "그래서 break 문이 필요한 거예요." 종료 조건의 중요성 코드를 자세히 보면 종료 조건이 있습니다.
사용자가 'quit', 'exit', '종료' 같은 단어를 입력하면 break 문이 실행됩니다. break는 반복문을 강제로 빠져나오는 명령어입니다.
이것은 마치 전화 통화에서 "그럼 끊을게요"라고 말하는 것과 같습니다. 명시적인 종료 신호 없이는 대화가 계속되지만, 원할 때는 언제든 끝낼 수 있어야 합니다.
빈 입력 처리하기 코드 중간에 continue 문도 보입니다. 사용자가 아무것도 입력하지 않고 엔터만 누른 경우를 처리합니다.
continue는 "이번 반복은 건너뛰고 다음 반복으로 가라"는 의미입니다. 빈 입력에 대해 AI가 응답할 필요는 없으니, 다시 사용자 입력을 기다립니다.
이런 세심한 처리가 사용자 경험을 향상시킵니다. 실전에서의 활용 실무에서는 이 기본 루프를 어떻게 확장할까요?
예를 들어 고객센터 챗봇이라면 특정 키워드에 반응하는 기능을 추가할 수 있습니다. "상담원 연결"이라고 입력하면 루프를 빠져나와 상담원 연결 프로세스로 넘어갑니다.
또는 일정 시간 입력이 없으면 "아직 계신가요?"라고 물어보는 타임아웃 기능도 추가할 수 있습니다. 초보자의 흔한 실수 김개발 씨가 코드를 작성하다가 실수를 했습니다.
break 문을 잊어버린 것입니다. 프로그램을 실행했더니 종료할 방법이 없었습니다.
결국 Ctrl+C를 눌러서 강제 종료해야 했습니다. "항상 무한 루프에는 탈출구가 있어야 해요." 박시니어 씨의 조언입니다.
사용자가 원할 때 언제든 프로그램을 종료할 수 있어야 합니다. 대화의 흐름이 만들어지다 김개발 씨는 프로그램을 실행해봤습니다.
질문을 입력하고, AI가 답변하고, 다시 질문하고... 마치 진짜 대화하는 것 같았습니다.
"와, 이게 되네요!" 대화 루프는 간단하지만 강력합니다. 이 기본 구조만 이해하면 더 복잡한 챗봇도 만들 수 있습니다.
여러분도 while True와 break의 조합을 잘 활용해보세요.
실전 팁
💡 - 종료 명령어는 여러 개 준비하세요(quit, exit, 종료 등) - 사용자마다 다르게 입력할 수 있습니다
- 입력받은 텍스트는 .strip()으로 공백을 제거하고, .lower()로 소문자로 변환하여 비교하세요
- 대화 루프 안에서 예외 처리를 추가하면 에러로 인한 프로그램 종료를 방지할 수 있습니다
3. 대화 히스토리 관리
챗봇이 작동하는 것을 본 김개발 씨는 신이 났습니다. 하지만 곧 이상한 점을 발견했습니다.
"이전에 했던 말을 AI가 기억하지 못해요!" 박시니어 씨가 고개를 끄덕였습니다. "대화 히스토리를 관리해야 해요."
대화 히스토리는 이전 대화 내용을 저장하고 관리하는 시스템입니다. 마치 사람이 대화 중에 앞에서 한 말을 기억하듯이, AI도 맥락을 이해하려면 이전 대화를 알아야 합니다.
이를 통해 자연스러운 연속 대화가 가능해집니다.
다음 코드를 살펴봅시다.
# 대화 히스토리를 저장할 리스트
conversation_history = []
def add_to_history(role, content):
"""대화 히스토리에 메시지 추가"""
conversation_history.append({
"role": role,
"content": content
})
def get_ai_response(user_input):
"""대화 히스토리를 포함한 AI 응답 생성"""
# 사용자 메시지를 히스토리에 추가
add_to_history("user", user_input)
# OpenAI API 호출
response = openai.ChatCompletion.create(
model=MODEL,
messages=conversation_history,
max_tokens=MAX_TOKENS
)
# AI 응답을 히스토리에 추가
ai_message = response.choices[0].message.content
add_to_history("assistant", ai_message)
return ai_message
김개발 씨는 챗봇을 테스트하며 간단한 대화를 시도했습니다. "내 이름은 김철수야." AI가 답했습니다.
"안녕하세요, 김철수님!" 좋습니다. 그런데 다음 질문에서 문제가 생겼습니다.
"내 이름이 뭐라고 했지?" AI가 답했습니다. "죄송하지만, 이름을 말씀하신 적이 없는 것 같습니다." 김개발 씨는 당황했습니다.
방금 전에 분명히 말했는데요? 대화 히스토리가 필요한 이유 박시니어 씨가 설명을 시작했습니다.
"AI는 기억상실증 환자와 같아요." 매번 새로운 질문을 받을 때마다, AI는 이전 대화를 전혀 모릅니다. 마치 영화 '메멘토'의 주인공처럼 단기 기억이 없는 상태입니다.
따라서 개발자가 직접 이전 대화 내용을 AI에게 알려줘야 합니다. 이것이 바로 대화 히스토리 관리입니다.
모든 대화 내용을 저장했다가, 다음 질문을 할 때 함께 전달하는 것입니다. 히스토리 없이 대화하면 어떻게 될까요? 김개발 씨의 또 다른 동료 정망각 씨가 있었습니다.
정망각 씨는 대화 히스토리 없이 챗봇을 배포했습니다. 사용자들의 불만이 쏟아졌습니다.
"방금 말한 걸 또 물어봐요!" "앞뒤가 안 맞는 답변을 해요!" 심지어 "이 챗봇은 치매에 걸린 것 같아요"라는 혹평도 나왔습니다. 대화 히스토리가 없으면 자연스러운 대화가 불가능합니다.
리스트로 히스토리 저장하기 코드의 첫 줄을 보면 conversation_history라는 빈 리스트가 있습니다. 이것이 우리의 메모장입니다.
모든 대화를 여기에 기록합니다. 각 대화는 딕셔너리 형태로 저장됩니다.
role은 누가 말했는지를(user 또는 assistant), content는 무엇을 말했는지를 담습니다. 이렇게 구조화하면 나중에 찾기도 쉽고 관리하기도 편합니다.
히스토리에 추가하는 함수 add_to_history 함수는 매우 간단합니다. 새로운 메시지를 리스트에 추가할 뿐입니다.
하지만 이 단순한 동작이 챗봇에 기억력을 부여합니다. 함수로 분리한 이유는 재사용성입니다.
사용자 메시지를 추가할 때도, AI 응답을 추가할 때도 같은 함수를 사용할 수 있습니다. 코드 중복을 피하고 유지보수를 쉽게 만듭니다.
OpenAI API와 히스토리 get_ai_response 함수가 핵심입니다. 사용자 입력을 받으면 먼저 히스토리에 추가합니다.
그다음 OpenAI API를 호출할 때 전체 히스토리를 함께 보냅니다. API의 messages 파라미터에 주목하세요.
단일 메시지가 아니라 전체 히스토리 리스트를 전달합니다. 이렇게 하면 AI가 이전 대화 맥락을 모두 이해한 상태에서 응답합니다.
응답을 받으면 그것도 히스토리에 추가합니다. 이렇게 대화가 쌓여갑니다.
실무에서의 히스토리 관리 실제 서비스에서는 어떻게 관리할까요? 카카오톡 상담 챗봇을 예로 들어봅시다.
사용자가 "배송 조회해줘"라고 하면 주문번호를 물어봅니다. 사용자가 번호를 말하면, 이전 대화를 기억하고 있어야 "배송 조회 요청"임을 알 수 있습니다.
히스토리가 없으면 주문번호만 달랑 받아서 무엇을 해야 할지 모릅니다. 많은 기업에서는 세션 단위로 히스토리를 관리합니다.
대화가 종료되면 히스토리를 데이터베이스에 저장해서 나중에 분석하기도 합니다. 히스토리가 너무 길어지면? 김개발 씨가 손을 들었습니다.
"대화가 길어지면 히스토리도 계속 커지지 않나요?" 정확한 지적입니다. 히스토리가 너무 길면 두 가지 문제가 생깁니다.
첫째, API 비용이 증가합니다. 토큰 수가 많아지기 때문입니다.
둘째, 응답 속도가 느려집니다. 해결 방법은 여러 가지입니다.
가장 최근 N개의 메시지만 유지하거나, 중요한 정보만 요약해서 저장하거나, 토큰 수를 모니터링하여 일정 수준 이상이면 오래된 메시지를 삭제할 수 있습니다. 기억력을 갖춘 챗봇 김개발 씨는 히스토리 관리를 추가하고 다시 테스트했습니다.
"내 이름은 김철수야." "안녕하세요, 김철수님!" 그리고 "내 이름이 뭐라고 했지?" "김철수라고 하셨습니다." 완벽합니다! 이제 챗봇이 진짜 대화하는 것처럼 느껴졌습니다.
대화 히스토리는 자연스러운 대화의 핵심입니다. 여러분의 챗봇에도 기억력을 선물하세요.
실전 팁
💡 - 히스토리가 너무 길어지면 최근 10-20개 메시지만 유지하는 것을 고려하세요
- 각 메시지에 타임스탬프를 추가하면 대화 흐름을 더 잘 파악할 수 있습니다
- 민감한 정보(개인정보, 비밀번호 등)는 히스토리에 저장하지 않도록 필터링하세요
4. 시스템 프롬프트 설계
챗봇이 점점 완성되어 가는 모습에 김개발 씨는 뿌듯했습니다. 하지만 AI의 답변이 때로는 너무 격식 있고, 때로는 너무 장황했습니다.
"AI의 말투를 조절할 수 있나요?" 박시니어 씨가 웃으며 답했습니다. "시스템 프롬프트를 설계하면 돼요."
시스템 프롬프트는 AI의 역할과 성격, 답변 스타일을 정의하는 초기 지시문입니다. 마치 배우에게 대본을 주고 역할을 설명하는 것과 같습니다.
이것을 통해 AI가 일관된 페르소나로 대화할 수 있게 만듭니다.
다음 코드를 살펴봅시다.
# 시스템 프롬프트 정의
SYSTEM_PROMPT = """당신은 친절하고 유용한 AI 어시스턴트입니다.
다음 규칙을 따라주세요:
5. 전문 용어는 쉽게 풀어서 설명하세요"""
김개발 씨는 챗봇을 친구들에게 보여주며 자랑했습니다. 하지만 친구들의 반응은 예상과 달랐습니다.
"답변이 너무 길어요." "왠지 딱딱한 느낌이에요." "좀 더 친근했으면 좋겠어요." 김개발 씨는 고민에 빠졌습니다. AI의 성격을 어떻게 바꿀 수 있을까요?
시스템 프롬프트란 무엇인가? 박시니어 씨가 연극을 예로 들며 설명했습니다. "배우가 무대에 오르기 전에 연출가가 말합니다.
'당신은 친절한 집사 역할이에요. 항상 공손하게 말하고, 짧게 답하세요.' 이것이 바로 시스템 프롬프트예요." AI도 마찬가지입니다.
대화를 시작하기 전에 어떤 역할을 맡을지, 어떤 스타일로 답할지 미리 정해주는 것입니다. 이 지시문은 대화 전체에 걸쳐 영향을 미칩니다.
시스템 프롬프트 없이 대화하면? 김개발 씨의 동료 허무작 씨는 시스템 프롬프트 없이 챗봇을 만들었습니다. 결과는 예측 불가능했습니다.
어떤 때는 매우 격식 있게 답하고, 어떤 때는 지나치게 캐주얼했습니다. 같은 질문에도 답변 길이가 들쭉날쭉했습니다.
사용자들은 혼란스러워했습니다. "이 챗봇은 다중인격인가요?" 일관성 없는 답변은 신뢰를 떨어뜨립니다.
시스템 프롬프트가 이 문제를 해결합니다. 효과적인 시스템 프롬프트 작성법 위 코드의 시스템 프롬프트를 분석해봅시다.
먼저 역할을 정의합니다. "당신은 친절하고 유용한 AI 어시스턴트입니다." 이 한 문장이 AI의 정체성을 만듭니다.
다음으로 구체적인 규칙을 나열합니다. "답변은 간결하게", "친근한 말투", "모르면 모른다고 말하기" 등입니다.
숫자로 나열하면 AI가 더 잘 따릅니다. 특히 중요한 것은 제약 조건입니다.
"3-4문장 이내"라는 명확한 가이드라인이 장황한 답변을 방지합니다. 시스템 메시지의 특별한 역할 OpenAI API에서 메시지는 세 가지 role을 가질 수 있습니다.
system, user, assistant입니다. system 역할이 특별한 이유는 무엇일까요?
대화의 맨 처음에 한 번 들어가며, AI의 행동 지침이 됩니다. user와 assistant는 실제 대화 내용이지만, system은 무대 뒤의 연출 노트입니다.
코드를 보면 initialize_conversation 함수가 시스템 프롬프트를 히스토리의 맨 앞에 추가합니다. 프로그램이 시작될 때 단 한 번 실행됩니다.
실전에서의 프롬프트 디자인 실무에서는 어떻게 활용할까요? 고객 상담 챗봇이라면 이렇게 작성할 수 있습니다.
"당신은 쇼핑몰 고객센터 직원입니다. 항상 공손하게 답하되, 전문적인 톤을 유지하세요.
환불 정책은 구매일로부터 7일 이내입니다. 배송비는 3000원입니다." 제품별 정보를 시스템 프롬프트에 넣으면 AI가 그것을 기반으로 답변합니다.
회사의 톤앤매너를 반영할 수 있습니다. 프롬프트 튜닝의 기술 김개발 씨가 질문했습니다.
"프롬프트를 어떻게 개선하나요?" 박시니어 씨가 답했습니다. "실험과 반복이에요." 처음에는 간단하게 시작합니다.
테스트하면서 원하지 않는 답변이 나오면 규칙을 추가합니다. 예를 들어 AI가 너무 추측성 답변을 한다면 "확실하지 않으면 추측하지 마세요"를 추가합니다.
답변이 너무 길다면 "최대 2문단으로 제한"을 명시합니다. 프롬프트 엔지니어링의 중요성 시스템 프롬프트는 단순해 보이지만, 챗봇의 품질을 크게 좌우합니다.
같은 AI 모델이라도 프롬프트에 따라 완전히 다른 성격이 됩니다. 많은 AI 스타트업에서 프롬프트 엔지니어라는 직업이 생겨났습니다.
그만큼 중요한 기술입니다. 여러분도 다양한 프롬프트를 실험해보며 최적의 스타일을 찾아보세요.
일관된 페르소나 완성 김개발 씨는 시스템 프롬프트를 추가하고 다시 테스트했습니다. 이제 AI가 항상 같은 톤으로, 적절한 길이로 답변했습니다.
친구들도 만족스러워했습니다. "와, 이제 진짜 챗봇 같아요!" 시스템 프롬프트 하나로 완전히 달라진 모습이었습니다.
여러분의 챗봇에도 명확한 정체성을 부여하세요.
실전 팁
💡 - 시스템 프롬프트는 구체적일수록 좋습니다 - "친절하게"보다 "존댓말을 사용하고 이모티콘은 쓰지 마세요"가 더 명확합니다
- 금지어나 민감한 주제를 다루지 말라는 지시도 추가할 수 있습니다
- 여러 버전의 프롬프트를 A/B 테스트해보며 최적의 버전을 찾으세요
5. 스트리밍 응답 적용
챗봇이 거의 완성되었지만, 김개발 씨는 한 가지 아쉬운 점을 발견했습니다. AI가 답변을 생각하는 동안 화면이 멈춰 있었습니다.
"답변을 기다리는 게 답답해요." 박시니어 씨가 말했습니다. "스트리밍 응답을 적용해봐요."
스트리밍 응답은 AI의 답변을 한 번에 받는 대신 실시간으로 조금씩 받아서 표시하는 기술입니다. 마치 누군가 말하는 것을 듣듯이, 답변이 타이핑되는 모습을 볼 수 있습니다.
이것이 사용자 경험을 크게 향상시킵니다.
다음 코드를 살펴봅시다.
def get_ai_response_streaming(user_input):
"""스트리밍 방식으로 AI 응답 생성"""
add_to_history("user", user_input)
# 스트리밍 모드로 API 호출
response = openai.ChatCompletion.create(
model=MODEL,
messages=conversation_history,
max_tokens=MAX_TOKENS,
stream=True # 스트리밍 활성화
)
print("AI: ", end="", flush=True)
full_response = ""
# 스트림에서 청크를 하나씩 받아 출력
for chunk in response:
if chunk.choices[0].delta.get("content"):
content = chunk.choices[0].delta.content
print(content, end="", flush=True)
full_response += content
print() # 줄바꿈
add_to_history("assistant", full_response)
return full_response
김개발 씨는 챗봇을 사용하다가 불편한 점을 느꼈습니다. 질문을 하면 화면이 멈췄습니다.
5초, 10초... 때로는 15초까지 기다려야 했습니다.
"이게 작동하는 건가? 아니면 멈춘 건가?" 불안했습니다.
친구에게 보여줬을 때도 마찬가지였습니다. "왜 이렇게 느려요?
고장 난 거 아니에요?" 김개발 씨는 당황했습니다. 스트리밍이 필요한 이유 박시니어 씨가 영화 다운로드를 예로 들었습니다.
"옛날에는 영화를 완전히 다운로드할 때까지 기다려야 봤어요. 하지만 지금은 스트리밍으로 바로 재생되죠.
챗봇도 똑같아요." 전통적인 방식은 AI가 답변을 완전히 생성할 때까지 기다립니다. 모든 문장이 완성되면 그때 한 번에 보여줍니다.
사용자는 그동안 지루하게 기다려야 합니다. 스트리밍 방식은 다릅니다.
AI가 한 단어씩 생성할 때마다 즉시 화면에 표시합니다. 마치 사람이 타이핑하는 것처럼 보입니다.
훨씬 역동적이고 살아있는 느낌입니다. 사용자 경험의 차이 김개발 씨의 동료 느림보 씨는 스트리밍을 적용하지 않았습니다.
사용자 리뷰에 이런 댓글이 달렸습니다. "답변을 기다리는 게 너무 답답해요." "로딩 중인지 멈춘 건지 모르겠어요." 반면 스트리밍을 적용한 챗봇은 달랐습니다.
"실시간으로 답변이 나타나서 신기해요!" "ChatGPT처럼 타이핑되는 게 멋져요!" 같은 AI 모델인데도 사용자 만족도가 훨씬 높았습니다. stream=True의 마법 코드의 핵심은 stream=True 파라미터입니다.
이 한 줄이 모든 것을 바꿉니다. 일반 모드에서는 API가 완성된 응답 하나를 반환합니다.
하지만 스트리밍 모드에서는 작은 청크(chunk)들의 흐름을 반환합니다. 각 청크는 몇 글자 또는 한 단어를 담고 있습니다.
마치 수도꼭지에서 물이 흘러나오듯이, 데이터가 연속적으로 흘러옵니다. 우리는 그것을 받아서 즉시 화면에 출력합니다.
for 반복문으로 스트림 처리하기 for chunk in response 부분을 자세히 봅시다. 이 반복문은 스트림에서 청크를 하나씩 꺼냅니다.
각 청크는 복잡한 구조를 가지고 있습니다. 우리가 원하는 것은 content 부분입니다.
**chunk.choices[0].delta.get("content")**로 텍스트를 추출합니다. 때때로 청크에 content가 없을 수 있습니다(시작이나 끝 청크).
그래서 get 메서드를 사용하고, 존재할 때만 출력합니다. flush=True의 비밀 print(content, end="", flush=True) 이 코드가 중요합니다.
세 가지 포인트가 있습니다. 첫째, **end=""**는 줄바꿈을 하지 않습니다.
같은 줄에 계속 이어서 출력됩니다. 둘째, flush=True는 버퍼를 즉시 비웁니다.
파이썬은 기본적으로 출력을 버퍼에 모았다가 한 번에 내보냅니다. flush=True가 없으면 스트리밍 효과가 사라집니다.
셋째, 모든 청크를 full_response에 누적합니다. 나중에 히스토리에 저장하려면 완전한 답변이 필요하기 때문입니다.
실무에서의 스트리밍 활용 실제 서비스에서는 어떻게 사용할까요? 웹 챗봇이라면 WebSocket을 통해 청크를 프론트엔드로 전송합니다.
리액트에서 상태를 업데이트하며 화면에 실시간으로 표시합니다. 모바일 앱이라면 비슷하게 스트림을 받아 UI를 점진적으로 업데이트합니다.
ChatGPT, Claude, Bard 같은 유명한 AI 서비스들이 모두 스트리밍을 사용합니다. 이것이 현대적인 챗봇의 표준입니다.
스트리밍의 제약사항 박시니어 씨가 주의사항을 알려줬습니다. "스트리밍에도 단점이 있어요." 첫째, 코드가 약간 복잡해집니다.
둘째, 에러 처리가 까다롭습니다. 스트림 중간에 에러가 나면 처리하기 어렵습니다.
셋째, 모든 환경에서 지원되는 것은 아닙니다. 네트워크나 서버 설정에 따라 제한될 수 있습니다.
하지만 이런 단점을 감안해도, 사용자 경험 향상은 매우 큽니다. 살아있는 대화의 완성 김개발 씨는 스트리밍을 적용하고 다시 테스트했습니다.
질문을 하자마자 AI가 타이핑을 시작했습니다. 한 단어씩, 문장이 완성되어 갔습니다.
"우와, 이게 진짜 챗봇이네요!" 친구들이 감탄했습니다. 이제 완전한 대화형 AI 챗봇이 완성되었습니다.
여러분도 스트리밍을 적용해서 생동감 있는 챗봇을 만들어보세요.
실전 팁
💡 - 스트리밍 중 에러가 발생할 수 있으니 try-except 블록으로 감싸세요
- 웹 환경에서는 Server-Sent Events(SSE)나 WebSocket을 활용하세요
- 스트리밍 속도가 너무 빠르면 읽기 어려울 수 있으니, 필요시 약간의 딜레이를 추가하세요
6. 챗봇 테스트 및 개선
챗봇이 완성되었습니다. 김개발 씨는 뿌듯했지만, 박시니어 씨가 말했습니다.
"이제 진짜 시작이에요. 테스트하고 개선하는 과정이 남았어요." 김개발 씨는 고개를 끄덕였습니다.
"어떻게 테스트하나요?"
테스트와 개선은 챗봇의 품질을 높이는 필수 과정입니다. 다양한 시나리오를 테스트하고, 문제를 발견하고, 해결책을 적용하는 반복적인 과정입니다.
이것을 통해 실전에서 사용 가능한 수준으로 완성도를 높일 수 있습니다.
다음 코드를 살펴봅시다.
def test_chatbot():
"""챗봇 기능 테스트"""
test_cases = [
"안녕하세요",
"내 이름은 김철수입니다",
"내 이름이 뭐였죠?",
"", # 빈 입력
"exit", # 종료 명령
]
print("=== 챗봇 자동 테스트 시작 ===\n")
initialize_conversation()
for i, test_input in enumerate(test_cases, 1):
print(f"[테스트 {i}] 입력: '{test_input}'")
if not test_input:
print("결과: 빈 입력 스킵\n")
continue
if test_input.lower() in ['exit', 'quit']:
print("결과: 종료 명령 감지\n")
break
try:
response = get_ai_response(test_input)
print(f"응답: {response}\n")
except Exception as e:
print(f"에러 발생: {str(e)}\n")
print("=== 테스트 완료 ===")
김개발 씨는 챗봇을 완성하고 바로 배포하려 했습니다. "이제 다 됐죠?" 하지만 박시니어 씨가 손을 저었습니다.
"아직이에요. 테스트를 안 했잖아요." 실제로 사용해보니 예상치 못한 문제들이 나타났습니다.
특수문자를 입력하면 에러가 났고, 너무 긴 질문은 제대로 답하지 못했습니다. 빈 입력을 하면 프로그램이 멈춰버렸습니다.
테스트가 왜 중요한가? 박시니어 씨가 자동차 공장을 예로 들었습니다. "자동차를 만들면 바로 도로로 나가나요?
아니에요. 수백 가지 테스트를 거치죠." 소프트웨어도 마찬가지입니다.
개발자가 '작동한다'고 생각하는 것과, 실제 사용자 환경에서 잘 작동하는 것은 다릅니다. 예상치 못한 입력, 극단적인 상황, 에러 케이스를 모두 확인해야 합니다.
테스트 없이 배포했다가는 사용자들이 직접 버그를 발견하게 됩니다. 이것은 최악의 시나리오입니다.
체계적인 테스트 케이스 만들기 위 코드의 test_cases 리스트를 봅시다. 다양한 시나리오를 담고 있습니다.
첫 번째는 일반적인 인사입니다. 가장 기본적인 케이스죠.
두 번째는 정보 입력입니다. 이름을 알려주는 상황입니다.
세 번째는 맥락 이해 테스트입니다. 이전 대화를 기억하는지 확인합니다.
네 번째는 빈 입력입니다. 사용자가 실수로 엔터만 누를 수 있습니다.
다섯 번째는 종료 명령입니다. 프로그램이 깔끔하게 종료되는지 확인합니다.
자동화된 테스트의 힘 수동으로 매번 테스트하는 것은 비효율적입니다. 코드를 수정할 때마다 같은 테스트를 반복해야 합니다.
test_chatbot 함수는 이 과정을 자동화합니다. 버튼 하나로 모든 테스트 케이스를 실행할 수 있습니다.
수정 후에도 금방 확인할 수 있습니다. for 반복문이 각 테스트 케이스를 순회합니다.
try-except 블록으로 에러를 잡아냅니다. 에러가 나도 프로그램이 멈추지 않고 다음 테스트를 계속합니다.
실제 발견되는 문제들 김개발 씨가 테스트를 돌려봤더니 여러 문제가 발견되었습니다. 첫째, API 키가 잘못되면 프로그램 전체가 멈췄습니다.
해결책은 에러 메시지를 표시하고 재시도를 요청하는 것이었습니다. 둘째, 대화 히스토리가 너무 길어지면 API 요청이 실패했습니다.
해결책은 히스토리 길이를 제한하는 것이었습니다. 셋째, 특수문자나 이모지가 들어가면 인코딩 에러가 났습니다.
해결책은 입력을 UTF-8로 인코딩하는 것이었습니다. 사용자 피드백 수집하기 자동 테스트만으로는 부족합니다.
실제 사용자의 피드백이 중요합니다. 김개발 씨는 친구들에게 챗봇을 사용해달라고 부탁했습니다.
"이상한 점이 있으면 알려줘." 피드백이 쌓였습니다. "답변이 너무 길어요." "같은 질문에 매번 다른 답을 해요." "가끔 엉뚱한 답을 해요." 이런 피드백을 바탕으로 시스템 프롬프트를 수정하고, 히스토리 관리를 개선했습니다.
버전이 올라갈수록 품질이 좋아졌습니다. 지속적인 개선 사이클 박시니어 씨가 화이트보드에 그림을 그렸습니다.
원형 화살표였습니다. "개발 → 테스트 → 피드백 → 개선 → 개발.
이것이 무한 반복돼요." 소프트웨어는 한 번 만들고 끝이 아닙니다. 계속 발전시켜야 합니다.
새로운 기능을 추가하고, 버그를 수정하고, 사용자 경험을 개선합니다. ChatGPT도 GPT-3에서 GPT-4로, 계속 업데이트됩니다.
여러분의 챗봇도 버전 1.0에서 멈추지 마세요. 개선 아이디어 목록 김개발 씨는 개선할 점들을 메모했습니다.
첫째, 감정 분석 추가. 사용자가 화난 것 같으면 더 신중하게 답변합니다.
둘째, 다국어 지원. 영어, 일본어 등 다양한 언어로 대화합니다.
셋째, 음성 인식 연동. 타이핑 대신 말로 질문할 수 있습니다.
넷째, 데이터 분석 기능. 어떤 질문이 많은지, 만족도는 어떤지 분석합니다.
다섯째, 개인화. 사용자별로 대화 스타일을 학습합니다.
완성이 아닌 시작 몇 주 후, 김개발 씨의 챗봇은 크게 발전했습니다. 처음보다 훨씬 안정적이고, 답변 품질도 좋아졌습니다.
사용자들의 만족도도 높아졌습니다. 박시니어 씨가 어깨를 두드렸습니다.
"잘했어요. 하지만 기억하세요.
이게 끝이 아니라 시작이에요." 김개발 씨는 고개를 끄덕였습니다. 이제 챗봇 개발자로서 첫 발을 내디뎠습니다.
여러분도 만든 챗봇을 계속 테스트하고 개선하며 키워나가세요. 그것이 진짜 개발자의 일입니다.
실전 팁
💡 - 테스트 케이스는 문서화하여 관리하세요 - 나중에 회귀 테스트에 유용합니다
- 에러 로그를 파일로 저장하면 문제 분석이 쉬워집니다
- 사용자 통계(질문 빈도, 응답 시간 등)를 수집하면 개선 방향을 찾기 쉽습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.