들어가며
저번주 목요일부터 오늘까지, 워킹데이로 5일 간 간단한 챗봇을 구현해 보았다.
실제로 개발한 것은 거의 2일이고, 이번 주에는 조금씩 개선하기 정도로 진행했다.
웹 개발도 꽤 이전에 했다 보니 사실 자신감이 좀 부족했었다.
그래서 Claude Code와 Antigravity 등 바이브 코딩(이 가능하게 해 준) 도구의 위대함이 더더욱 크게 느껴졌다.
목표
국립중앙박물관의 학습 보조 챗봇을 개발하기로 했고, (목표는 욕심 없이.. 완성하기) 간단하게 다음과 같았다.
- LLM 기반 학습 보조 챗봇 프로토타입 완성
- Streamlit 과 Gemini API를 활용한 학습 지원 서비스의 핵심 기능(문제 풀이 및 해설) 구현 완료
개발 과정
1. 프로젝트 주제 및 목표 설정
- 학습 챗봇의 도메인 결정: 박물관 안내 챗봇의 확장형
- 챗봇 사용자군 결정: 체험학습으로 방문한 초등 고학년
2. 챗봇 서비스 기획
- 챗봇 주요 기능 도출
- 리스트에서 마음에 드는 유물 고르기(최소 3개, 최대 10개)
- 유물별로 생성된 질문을 바탕으로 4지선다 선택지 중 답을 선택하고 동시에 궁금한 점을 자연어로 입력할 수 있게 함
- 문제를 모두 풀면 결과로 해답지가 제공되는데, 문제-답변-정답-해설 구조로 반복되며, 사용자가 별도로 입력한 질문에 대해 하단에 추가적으로 답안이 제공됨(최대 300자)
- 주요 세션 구분 (intro / select / quiz / result)
- 화면설계 & 프로토타이핑 (개발된 서비스 캡쳐로 갈음)
![]() |
![]() |
![]() |
![]() |
![]() |
3. 아키텍처 설계 및 개발
- 프로젝트 디렉터리 구조
-

프로젝트 디렉터리 구조
-
- 유물 데이터1: 공공데이터(e-museum) API 연동 http://www.emuseum.go.kr/main
- SERVICE KEY IS NOT REGISTERED ERROR 에러로 고생하다가 (서비스키가 바로 연동되지 않을 수 있기 때문에 시간적 여유를 가지고 활용신청할 필요가 있음)
- 그다음에는 아예 아래 메시지가 떠서 포기했다.
- NO OPENAPI SERVICE ERROR (api 자체에 오류가 있다는 의미의 코드)

api 서비스 에러코드 - 그나저나, 유물 데이터로 DB를 생성하려면 어쨌든 한 번은 API를 호출해서 데이터를 모두 저장해야 한다.
- 유물 데이터2: json 하드코딩
- Claude Code에서 자동으로 생성한 데이터로, api를 못 받아오게 되어서 구동 테스트를 위해 일단 제작하게 되었다.
- image url을 저장해 유물별 대표 사진도 뜨도록 했다
- 하드코딩한 JSON을 활용하는 챗 시스템은 RAG는 아니고, "Context Injection" 방식이다.
간단히 말하면, 검색을 하는게 아니라 가져온 데이터를 LLM 모델이 문장형으로 만들거나 질문을 생성하는 것이기 때문에 DB에서 검색을 하는 RAG와 차이가 있다.
고정된 소규모 데이터를 LLM에 직접 전달하는 단순한 구조로, 현재 요구사항(소수 국보 데이터를 활용한 퀴즈)에는 적합했다. -

Context Injection VS RAG
- API 키 보안관리
- .env 파일을 사용하는 환경변수 관리 라이브러리 python-dotenv)
- .env 파일은 github에 올라가지 않도록 구성했기 때문에, 팀원에게 별도로 파일을 전달해 로컬에 저장할 수 있도록 한다.
- 시스템 프롬프트 커스터마이징
- AI의 역할, 사용자 특성 등 작성
- 문제-답변-정답-해설 구조로 반복되며, 사용자가 별도로 입력한 질문에 대해 하단에 추가적으로 답안이 제공된다(최대 300자) -> 이 요구사항을 Gemini 모델에 프롬프트로 전달했다.
- 기계와 기계어로 대화하는 것이 아니라 자연어로 대화할 수 있다는 것이 새삼 충격적이다.
# prompts.py
# ============================================================
# 시스템 프롬프트 (AI의 성격/역할 정의)
# ============================================================
SYSTEM_PROMPT = """당신은 박물관 유물 전문 가이드 AI입니다.
## 당신의 역할
- 박물관 관람객에게 유물에 대해 친절하고 교육적으로 설명합니다.
- 쉽게 설명하되, 학술적 정확성을 유지합니다.
- 유물에 대한 흥미로운 이야기와 역사적 맥락을 포함합니다.
## 응답 스타일
- 친근하고 따뜻한 톤으로 말해주세요
- 전문 용어는 쉽게 풀어서 설명해주세요
- 이모지를 적절히 사용하여 친근감을 표현해주세요
- 답변 끝에 추가 질문을 유도해주세요
- 너무 길지 않게, 핵심 위주로 설명해주세요
- 한국어로 응답하세요.
"""
# prompts.py
# ============================================================
# 퀴즈 생성 프롬프트
# ============================================================
QUIZ_PROMPT = """유물 "{artifact_name}"에 대한 퀴즈를 만들어주세요.
요구사항:
- 4지선다 형식
- 난이도: 중간 (일반인이 설명을 들으면 맞출 수 있는 수준)
- 유물의 시대, 특징, 의미 중 하나에 대한 문제
- 한국어로 작성
반드시 아래 JSON 형식으로만 응답하세요 (다른 텍스트 없이):
{{
"question": "문제 내용",
"options": ["선택지1", "선택지2", "선택지3", "선택지4"],
"correct_index": 0,
"explanation": "정답 해설 (2-3문장)"
}}
"""
# prompts.py
# ============================================================
# 유물 컨텍스트 템플릿
# ============================================================
ARTIFACT_CONTEXT = """
## 현재 설명 중인 유물 정보
- 이름: {name}
- 영문명: {name_en}
- 시대: {period}
- 재료: {material}
- 위치: {location}
- 설명: {description}
- 흥미로운 사실: {fun_facts}
이 정보를 바탕으로 관람객의 질문에 답변해주세요.
"""
#llm_service.py
def generate_enhanced_explanation(
self,
artifact: dict,
quiz: dict,
is_correct: bool,
user_question: str = None
) -> str:
"""사용자 질문을 반영한 맞춤 해설 생성"""
base_explanation = quiz.get("explanation", "")
# 사용자 질문이 없으면 기본 해설 반환
if not user_question or not user_question.strip():
return base_explanation
# Gemini API로 맞춤 해설 생성
if self.model:
try:
prompt = f"""당신은 박물관 큐레이터입니다.
사용자가 유물 퀴즈를 풀면서 궁금한 점을 질문했습니다.
**유물 정보:**
- 이름: {artifact.get('name', '')}
- 시대: {artifact.get('period', '')}
- 지정: {artifact.get('designation', '')}
- 설명: {artifact.get('description', '')}
**퀴즈 문제:** {quiz.get('question', '')}
**정답 여부:** {'정답' if is_correct else '오답'}
**기본 해설:** {base_explanation}
**사용자의 궁금한 점:** {user_question}
위 정보를 바탕으로:
1. 먼저 기본 해설을 제공하고
2. 사용자의 궁금한 점에 친절하게 답변해주세요
3. 추가로 흥미로운 정보가 있다면 알려주세요
응답은 300자 이내로 간결하게 작성해주세요."""
generation_config = {
"temperature": AI_CONFIG["temperature"],
"max_output_tokens": AI_CONFIG["max_tokens"]
}
response = self.model.generate_content(prompt, generation_config=generation_config)
return response.text
except Exception as e:
print(f"맞춤 해설 생성 오류: {e}")
# API 없으면 기본 해설 + 안내 메시지
return f"""{base_explanation}
**질문하신 내용:** {user_question}
→ API 키를 설정하면 궁금한 점에 대한 맞춤 답변을 받을 수 있어요!"""
특히, 위 코드에서 사용한 프롬프팅 기법은 3가지이다.
- 페르소나 설정: AI에게 '박물관 큐레이터'라는 역할(Role)을 부여하여 전문적이고 친절한 톤을 유도
- 콘텍스트 제공: 유물의 시대, 명칭, 설명뿐만 아니라 사용자가 퀴즈를 맞혔는지 틀렸는지도 전달하여 -> 상황에 맞는 답변으로
- 출력 가이드: 1. 기본 해설 2. 사용자 질문 답변 3. 흥미로운 사실 순서로 구성하며, 300자 이내로 제한하여 모바일 등에서 읽기 좋게 최적화
개선 필요점 & 피드백
- 내가 말로 응답하고 문제를 푸는 에이전틱한 방식으로 변경하는 것이 좋겠음. 현재는 AI 챗봇임에도 멀티턴 방식의 인터랙션을 구현하지 않고 질-답이라는 단순한 싱글턴 방식 대화로 기획했음. (인터랙션이 적음) => 자동, 프로액티브한 학습 질문 방식 / 교수법 추가
- 향후에는 RAG api를 만들어서 검색결과가 안나오면 어떻게 할지도 설계할 수 있음
- 실무에서는 사내 RAG에 퍼플렉시티 같은 api를 붙여, 정보를 합쳐서 검색 결과 늘림
- 텍스트 데이터를 모아서 RAG 개발
- 비용 문제에 대한 고려: 문화재의 경우 정보에 변동이 거의 없는데, AI api를 매번 호출하면 토큰이 너무 소모됨. 한 번 호출해서 제작된 질문-해답 쌍을 JSON에 저장하는 형식이 어떤지 제안
- 퀴즈가 다 끝나고 [다시 풀어보기]를 통해 학습을 지속적으로 해볼 수 있는 점은 좋으나, 랜덤으로 10개 리스트를 다시 제시하는 것보다는 유물 추천 기능을 넣어서 보다 적극적이고 실제로 도움이 되는 학습 제안을 하는 것이 어떨지
- 프로토타입 영상에서 이어서, 문제를 푸는 중 질문을 입력하는 것뿐만 아니라, 마지막에 최종 결과를 보는 중에도 질문 더 해보면서 한 번 더 학습하게 하는 것이 어떨지
겪었던 과제 & 알게된 점 (일부 상술함)
- 공공데이터 포털에서의 API 호출 문제
- Claude API가 구독 중인 200달러 claude max 요금제에 포함되어 있는 줄 알고 api 연동을 지속 시도함. Claude API는 별도의 과금이 된다고 해 (결제 안 해서 연결이 안 되었음) gemini api로 변경 (무료 체험 요금제)
- Streamlit의 편리함과 맞바꾼 한계 (html, js, css 코드가 섞여있는 프레임워크여서 편리하지만, 동시에 UI 디자인 자유도 매우 낮음. Figma MCP의 경우 레이아웃 구조 차이로 연동 불가에 가까움)
- Figma에서 디자인한 UI를 Cursor와 Claude에서 연동해서 사용하면 UI가 자동으로 생성된다. (Figma MCP)
- 따라서, 연동 시 UI가 깨지거나 변형되는 문제가 있을 수 있기 때문에 툴 간 연동에 대한 확인이 필요하다.
- PC 화면을 모바일 화면으로 보고 싶을 때는 크롬의 DevTool에서 Device toolbar를 모바일로 변경하면 된다. (매우 간단!)
회고
- 프로젝트 개발 방법론 준수: 기획 → 개발의 순서를 지키지 못하면 불필요하게 소모되는 개발 공수가 많아진다.
- 처음에 Figma mcp에 대한 이해를 할 시간이 없을 것 같아 일단 기본적인 기능은 streamlit으로 개발부터 해두고자 하였으나 streamlit이 js/html/css를 엄격하게 구분하는 것이 아니라, 이 위에 figma mcp를 연동 및 적용하는 것이 불가능했음. 특히 streamlit은 제공하는 레이아웃이 간단한 그리드 정도여서 figma mcp에서 적용하고자 하는 레이아웃을 전달했을 때 실제로는 오류처럼 보이는 문제가 지속적으로 발생했음
- 그리고 streamlit 프레임워크에서 구현 가능한 UI 디자인 범위가 한정적이어서 프로토타입과 상이하게 됨. 그래서 다소 만족스럽지 않은 완성도의 산출물이 도출되었음. 향후에는 Figma MCP를 사용해 디자인적 완성도도 높이고자 함
- 기능 도출, 와이어프레임 작성 등 기획 종결 후에 - 이를 구현하기 위한 기술 스택을 결정하고 - 개발 도입을 했어야 했는데, 기획과 개발을 일부 병행하면서 전체 개발 소요시간이 늘어난 것 같음
- 사용하고자 하는 도구의 개발 가능 범위와 한계를 미리 정확하게 이해하고 기획 및 디자인, 프로토타이핑에 들어가야 나중에 ‘알고보니 개발이 불가능함’과 같은 기술적 문제를 겪지 않음
- 이후에는 프로젝트 목표와 문제정의 등 프로젝트 초기 단계에서 그 다음 단계로 넘어갈 때 개발, 디자인 팀원들과 협의를 잘해야 할 것 같음
- 서비스의 기본적인 특징 & 사용자의 기대 충족 여부 판단 필요
- 챗봇의 기본적인 형식과 인터랙션 방식을 견지하고 프로젝트를 진행했어야 하는데
- AI 챗봇은 자연어 대화 위주로 진행되어야 하는 것을 간과하고 버튼 선택과 같은 UI를 다소 많이 넣음.
- 이 챗봇은 사용자가 계속 화면을 바라보면서 챗봇과 대화하고 터치도 병행하는 상황에서만 사용되는 챗봇일까?
(사용자가 멀티모달 인터랙션을 할 수 있는지, 또 하고 싶어하는지 분석 필요)
- 이후 계획
- 조만간 여유가 좀 생기면 streamlit을 제거하고 figma mcp 연동을 통해 프로토타입과 동일한 수준의 디자인적 완성도를 가진 챗봇으로 수정해야겠다.
건축가 루이스 설리번의 '형태는 기능을 따른다'.
한국 IT 산업에서의 서비스 개발 과정을 생각해보았을 때,
'형태는 기술을 따른다'에 가까울 때가 있다.
이 기술을 어떻게 잘 활용할까? 하는 고민이 깊어질 수록,
반대로 사용자에 대한 고민은 얕아지기 쉽다.
사용자가 모호해지면 기능도 모호해진다.
사용자, 기능, 기술이라는 세 축.
형태는 그 고민이 교차하는 무게중심 위에서 정의된다고 믿는다.

'컴퓨터공학 + HCI > AI Agent' 카테고리의 다른 글
| AI Agent 심화 캠프 4기: 수강신청부터 오리엔테이션까지 (0) | 2025.12.29 |
|---|




