관광지 검색 · 챗봇 · 글쓰기 실습
공공데이터 API 하나로 웹페이지 → 챗봇 → 글쓰기 에이전트까지
대상: 입문 과정을 마치고 실제로 쓸 만한 것을 만들어 보고 싶은 사람
도구: Claude Code (Codex·Antigravity CLI도 프롬프트는 동일)
목표: 무료 API 키 두 개로 웹페이지 → 챗봇 → 글쓰기 에이전트 세 가지를 차례로 만들기
핵심: 코드는 한 줄도 직접 쓰지 않습니다. 키를 받아.env에 넣고, 프롬프트를 붙여넣는 것이 전부입니다.
1. 무엇을 만드나
폴더 하나에서 세 가지를 순서대로 만듭니다. 뒤 단계는 앞 단계 결과물을 재료로 씁니다.
공공데이터포털 관광정보 API
│
▼ 1단계 검색 웹페이지 → tour/web/index.html (HTML 한 파일, 약 30분)
│ 지역·키워드로 관광지를 찾아 목록으로 보여 줍니다
│
▼ 2단계 챗봇 → tour/chatbot/chatbot.py (파이썬, 약 30분)
│ 말로 물으면 관광공사 정보를 가져와 Gemini가 답합니다
│
▼ 3단계 글쓰기 에이전트 → tour/writing_agent/result.md (약 40분)
키워드 하나로 검색→요약→재미있는 글까지 자동 작성
| 단계 | 만드는 것 | 배우는 것 |
|---|---|---|
| 1단계 | 관광지 검색 웹페이지 | 공공 API 키 발급, 문서를 근거로 코드 만들기 |
| 2단계 | 관광 안내 챗봇 | 외부 API + LLM API를 엮기 |
| 3단계 | 글쓰기 에이전트 | 서브에이전트·스킬로 작업을 나누기 |
1·2단계 끝에는 (선택) 배포가 붙어 있습니다. 남에게 링크로 보여 주고 싶을 때만 하면 되고, 건너뛰어도 실습에는 지장이 없습니다.
2. 폴더와 가상환경 만들기
2-1. 폴더 만들기
원하는 위치에 tour 폴더를 만들고, 그 안에 하위 폴더 셋을 만듭니다.
mkdir tour
cd tour
mkdir web chatbot writing_agent
2-2. 가상환경(.venv) 만들기
2·3단계에서는 파이썬을 쓰고 requests·python-dotenv·google-genai 같은 패키지를 설치합니다. 이 패키지들이 컴퓨터 전체가 아니라 tour 폴더 안에만 들어가도록 가상환경을 먼저 만듭니다. 이걸 건너뛰면 뒤에서 ModuleNotFoundError로 막힙니다.
tour 폴더 안에서 실행합니다. 만들기는 한 번뿐이고, 켜기는 터미널을 새로 열 때마다 합니다.
# ① 만들기 — 한 번만 (Windows는 python, macOS/Linux는 python3)
python -m venv .venv
# ② 켜기 — Windows PowerShell
.venv\Scripts\Activate.ps1
# ② 켜기 — Windows 명령 프롬프트(cmd)
.venv\Scripts\activate.bat
# ② 켜기 — macOS / Linux
source .venv/bin/activate
켜지면 터미널 입력줄 맨 앞에 (.venv)가 붙습니다. 이 표시가 곧 "켜져 있다"는 뜻입니다.
(.venv) PS C:\vscode\tour>
이 상태에서 이 실습에 필요한 패키지를 미리 설치해 둡니다.
pip install requests python-dotenv google-genai
Activate.ps1실행이 막히면 (Windows PowerShell) —Set-ExecutionPolicy -Scope CurrentUser RemoteSigned를 한 번 실행하고Y로 답한 뒤 다시 시도하세요.
python을 못 찾으면 파이썬이 안 깔렸거나 PATH에 없는 것입니다. 파이썬 설치부터 가상환경까지 자세한 설명은 입문 가이드 2부에 있습니다.
2-3. VS Code에서 열기
완성되면 이런 모양입니다.
tour/
├── .venv/ # 가상환경 (2-2에서 만든 것)
├── .env # 열쇠 보관함 (3장에서 만듭니다)
├── .gitignore
├── web/ # 1단계 — 검색 웹페이지
├── chatbot/ # 2단계 — 챗봇
└── writing_agent/ # 3단계 — 글쓰기 에이전트
VS Code에서 File → Open Folder로 tour 폴더를 열고, 터미널에서 claude를 실행합니다.
✅ 체크포인트: 터미널 입력줄이
(.venv)로 시작하고 경로 끝이tour이며, 왼쪽 탐색기에web·chatbot·writing_agent세 폴더가 보이면 준비 완료입니다.
3. .env — 열쇠 보관함
이 실습에서 가장 먼저, 그리고 가장 정확히 해야 하는 일입니다. 여기서 어긋나면 뒤 단계가 전부 막힙니다.
3-1. .env가 뭔가요?
API 키(열쇠)를 코드와 분리해 따로 보관하는 파일입니다. tour 폴더 바로 아래(최상단)에 둡니다.
- 키를 코드 안에 직접 쓰면 파일을 공유하거나 깃허브에 올리는 순간 남에게 그대로 넘어갑니다.
.env에 모아 두면 이 파일 하나만 공유하지 않으면 됩니다.- 키가 바뀌어도
.env한 줄만 고치면 됩니다.
3-2. 이 실습에 필요한 키 두 개
| 이름 | 어디서 받나 | 어디에 쓰나 | 비용 |
|---|---|---|---|
DATAGOKR_API_KEY |
공공데이터포털 — 한국관광공사 관광정보 | 1·2·3단계 전부 (관광지 정보 조회) | 무료 |
GOOGLE_API_KEY |
Google AI Studio | 2단계 챗봇 (Gemini 답변 생성) | 무료 등급 있음 |
3-3. 파일 양식
tour/.env 파일을 만들고 아래 형식으로 씁니다. 이름=값을 한 줄에 하나씩입니다.
# 공공데이터포털 — 한국관광공사 관광정보 (4장에서 발급)
DATAGOKR_API_KEY=여기에_발급받은_인증키_붙여넣기
# Google AI Studio — Gemini API (5장에서 발급)
GOOGLE_API_KEY=여기에_발급받은_API_키_붙여넣기
지켜야 할 규칙은 다섯 가지뿐입니다.
| 규칙 | 맞는 예 | 틀린 예 |
|---|---|---|
= 앞뒤에 공백을 넣지 않습니다 |
GOOGLE_API_KEY=AIza... |
GOOGLE_API_KEY = AIza... |
| 따옴표를 붙이지 않습니다 | GOOGLE_API_KEY=AIza... |
GOOGLE_API_KEY="AIza..." |
| 한 줄에 하나씩 씁니다 | 위 예시대로 | 한 줄에 두 개 |
| 이름은 영문 대문자와 밑줄만 씁니다 | DATAGOKR_API_KEY |
datagokr-api-key |
설명은 #로 시작하는 줄에 씁니다 |
# 공공데이터포털 키 |
— |
파일 이름이
.env가 맞는지 확인하세요. 점(.)으로 시작하는 이름이라 Windows 탐색기에서는.env.txt로 저장되기 쉽습니다. VS Code에서 새 파일을 만들어.env로 저장하는 편이 안전합니다.
3-4. .gitignore — 실수로 올리지 않게
tour 폴더에 .gitignore 파일을 하나 더 만듭니다. 여기 적힌 파일은 깃허브에 올라가지 않습니다.
.env
.venv/
__pycache__/
키가 이미 공개됐다면 파일을 지우는 것만으로는 부족합니다. 발급처에서 키를 폐기하고 새로 받으세요. 공공데이터포털·AI Studio 모두 재발급이 무료입니다.
3-5. .env를 읽는 법
파이썬은 python-dotenv 패키지를 씁니다. 2-2절에서 이미 설치했습니다. 건너뛰었다면 (.venv)가 켜진 상태에서 지금 설치하세요.
pip install python-dotenv google-genai requests
import os
from dotenv import load_dotenv
load_dotenv() # tour/.env 를 읽어 온다
KEY = os.getenv("DATAGOKR_API_KEY") # 이름으로 꺼내 쓴다
HTML 파일은 .env를 읽지 못합니다(브라우저에는 파일 시스템 권한이 없습니다). 그래서 1단계 웹페이지는 키를 HTML 안에 직접 적습니다. 이 점은 4-5절에서 다시 다룹니다.
4. 1단계 — 관광지 검색 웹페이지
4-1. 공공데이터포털에서 인증키 받기
- 공공데이터포털에 접속해 회원가입·로그인합니다.
- 검색창에
한국관광공사 관광정보를 입력하고, 한국관광공사_국문 관광정보 서비스_GW를 엽니다. (바로가기) - 오른쪽 활용신청 버튼을 누릅니다. 활용목적은 "앱개발(교육용)" 정도로 적으면 됩니다.
- 승인되면 마이페이지 → 데이터활용 → 개발계정에서 일반 인증키(Encoding / Decoding) 두 가지를 확인할 수 있습니다.
자동 승인이지만 반영에 시간이 걸립니다. 신청 직후에는
SERVICE_KEY_IS_NOT_REGISTERED_ERROR가 뜰 수 있습니다. 보통 수 분에서 한 시간이면 풀립니다. 다른 API에 신청한 키는 이 API에서 쓸 수 없습니다 — 이 API에 별도로 신청해야 합니다.
4-2. 활용매뉴얼 내려받기
같은 페이지 아래쪽 참고문서에서 개방데이터_활용매뉴얼(국문).zip을 내려받습니다. 압축을 풀어 나온 문서를 tour/web 폴더에 그대로 저장합니다.
이 매뉴얼이 있어야 AI가 어떤 주소로, 어떤 항목을 요청해야 하는지 정확히 압니다. 없으면 AI가 파라미터를 지어내 엉뚱한 코드를 만듭니다.
tour/web/
└── 한국관광공사_개방데이터_활용매뉴얼.docx (또는 pdf/hwp)
4-3. .env에 키 저장
3-3절 양식대로 tour/.env의 DATAGOKR_API_KEY 줄에 인증키를 붙여넣습니다.
4-4. 프롬프트
tour 폴더에서 claude를 실행하고 아래를 그대로 붙여넣습니다.
프롬프트는 다섯 덩어리로 씁니다 —
[목표]무엇을 ·[참고 자료]무엇을 근거로 ·[요구사항]어떻게 ·[제약]하지 말 것 ·[확인]됐는지 어떻게 아는지. 한 줄로 던지면 AI가 빈칸을 상상으로 채웁니다. 이 문서의 프롬프트는 모두 이 양식입니다.
[목표]
web/index.html 하나로 도는 관광지 검색 페이지를 만들어 줘.
[참고 자료]
web 폴더의 「한국관광공사_개방데이터_활용매뉴얼」을 먼저 읽어.
요청 주소·오퍼레이션·파라미터 이름은 매뉴얼에 적힌 것만 쓰고 지어내지 마.
[요구사항]
1. 키워드 검색('키워드 검색 조회')과 시·도 드롭다운('지역코드 조회' + '지역기반 관광정보 조회')
2. 공통 파라미터(serviceKey, MobileOS, MobileApp, _type=json, numOfRows, pageNo)를 빠뜨리지 마
3. 결과는 카드로 — 관광지명·주소·사진·전화. 사진이 없으면 회색 상자
4. resultCode가 0000이 아니거나 결과가 0건이면 화면에 그대로 알려 줘
[제약]
- 파일은 web/index.html 하나. 라이브러리 없이 HTML+CSS+JS로. <meta charset="utf-8"> 필수
- 인증키는 Encoding 키를 파일 맨 위 const SERVICE_KEY 한 줄에 둬
[확인]
API를 한 번 호출해 정상 응답(resultCode 0000)이 오는지 확인하고, 받은 건수를 알려 줘.
AI가 매뉴얼을 읽고 계획을 보여 줍니다. 동의하면 tour/web/index.html이 만들어집니다.
# macOS
open web/index.html
# Windows
start web/index.html
✅ 체크포인트: 검색창에
경복궁을 넣었을 때 주소와 사진이 있는 목록이 나오면 성공입니다.
더 붙이고 싶은 기능도 같은 양식으로 말합니다.
[추가 요청]
결과 카드에 '상세보기' 버튼을 달아 줘. 누르면 '공통정보 조회'로 개요(overview)를 받아 카드 아래에 펼친다.
목록 아래에는 '더 보기' 버튼을 두고, 누르면 pageNo를 1 올려 이어 붙인다.
[제약]
지금 되는 검색 기능은 건드리지 마. 파일은 계속 web/index.html 하나.
4-5. 꼭 알아야 할 것 — 인증키 두 종류
공공데이터포털은 인증키를 Encoding과 Decoding 두 가지로 줍니다. 겉보기엔 비슷한데, 쓰는 자리가 다릅니다. 이걸 바꿔 쓰면 키가 멀쩡해도 403 SERVICE_KEY_IS_NOT_REGISTERED_ERROR가 뜹니다. 이 실습에서 가장 흔한 사고입니다.
| 어디서 쓰나 | 어떤 키 | 이유 |
|---|---|---|
| HTML/자바스크립트에서 주소 문자열에 직접 붙일 때 | Encoding 키 (%2B, %3D 같은 기호가 들어 있음) |
이미 인코딩된 상태라 그대로 붙이면 됩니다 |
파이썬 requests의 params=로 넘길 때 |
Decoding 키 (+, = 기호가 보임) |
라이브러리가 한 번 더 인코딩하므로, 인코딩된 키를 주면 이중 인코딩됩니다 |
막혔을 때 한 문장으로 물어보세요 — "403 SERVICE_KEY_IS_NOT_REGISTERED_ERROR가 나. 인코딩 키와 디코딩 키를 바꿔서 시험해 보고 되는 쪽으로 고쳐 줘." AI가 두 경우를 직접 호출해 보고 맞는 쪽으로 고쳐 줍니다.
웹페이지에 키가 노출됩니다. 단일 HTML 파일은 키를 코드 안에 적을 수밖에 없고, 배포하면 누구나 볼 수 있습니다. 이 실습은 무료·공개 데이터 키라 교육용으로는 괜찮지만, 유료 API 키는 절대 이렇게 쓰지 마세요. 제대로 하려면 키를 감추는 서버(백엔드)를 따로 둡니다.
4-6. (선택) GitHub Pages로 배포하기
만든 페이지를 링크로 공유하고 싶을 때만 하면 됩니다. 손으로 하는 일은 토큰 발급 하나뿐이고, 나머지(저장소 만들기 → 파일 올리기 → Pages 켜기)는 프롬프트 하나로 끝납니다.
① 토큰 발급 — 이것만 직접 합니다
- github.com에 가입·로그인합니다.
- 오른쪽 위 프로필 → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token (classic).
- 권한(Scopes)에서
repo에 체크합니다. 저장소 만들기·파일 올리기·Pages 켜기가 모두 이 하나에 들어 있습니다. - 만들어진 토큰을
tour/.env에 넣습니다. 창을 닫으면 다시 볼 수 없습니다.
# 깃허브 (선택 — 배포할 때만)
GITHUB_TOKEN=여기에_발급받은_토큰_붙여넣기
Fine-grained 토큰으로도 됩니다. 다만 Contents·Pages·Administration 세 가지를 따로 켜고 대상 저장소도 지정해야 해서 실수하기 쉽습니다. 처음이면 classic이 편합니다.
② 나머지는 프롬프트 하나로
[목표]
web/index.html 을 GitHub Pages로 배포해 줘. 저장소 만들기부터 Pages 켜기까지 다 해 줘.
[참고 자료]
tour/.env 의 GITHUB_TOKEN(classic, repo 권한)으로 인증해. 토큰 값은 어디에도 남기지 마.
gh CLI 가 깔려 있으면 그것으로, 없으면 GitHub REST API 로.
[할 일]
1. 내 깃허브 아이디를 토큰으로 알아낸다 (나에게 묻지 마)
2. tour-web 이라는 public 저장소를 만든다 (이미 있으면 그것을 쓴다)
3. web/index.html 을 main 최상단에 index.html 로 올린다
4. Pages 를 'main / 루트(/)' 로 켜고 배포 주소를 알려 준다
[제약]
올릴 파일은 web/index.html 하나뿐. tour/.env 와 .gitignore 에 적힌 것은 절대 올리지 마.
[확인]
올리기 전에 index.html 안의 인증키가 공개돼도 되는 키가 맞는지 나에게 한 번 물어봐 줘.
올린 뒤에는 배포 주소를 열어 페이지가 뜨는지 확인하고 알려 줘.
✅ 체크포인트: 배포 주소를 열었을 때 검색창이 뜨고
경복궁검색이 되면 성공입니다. 첫 빌드에 1~2분 걸립니다. 바로 404가 떠도 잠시 뒤 다시 열어 보세요.
③ 막히면 — 손으로 올리는 방법
- github.com 오른쪽 위 + → New repository. 이름은
tour-web, 공개 여부는 Public, Add a README file에 체크하고 Create repository. - 만들어진 저장소에서 Add file → Upload files로
web/index.html을 끌어다 놓고 아래 Commit changes. - Settings → Pages → Build and deployment에서 Source를 Deploy from a branch, Branch를 main / (root) 로 두고 Save.
- 1~2분 뒤 같은 화면 위쪽에 배포 주소가 나옵니다.
키가 웹에 그대로 노출됩니다. 4-5절에서 말한 대로 단일 HTML 파일은 인증키를 숨길 수 없습니다. 교육용 무료 키라 괜찮지만, 유료 API 키는 이렇게 배포하지 마세요.
5. 2단계 — 관광 안내 챗봇
5-1. Gemini API 키 받기
- aistudio.google.com/api-key에 구글 계정으로 접속합니다.
- Create API key를 누르면 키가 바로 나옵니다. 신용카드가 필요 없습니다.
tour/.env의GOOGLE_API_KEY줄에 붙여넣습니다.
키는 만들어진 순간 한 번만 온전히 보입니다. 창을 닫기 전에
.env에 저장하세요.
5-2. gemini_api.txt 만들기
aistudio.google.com/docs에서 Gemini API 파이썬 사용법을 찾아 복사하고, tour/chatbot/gemini_api.txt로 저장합니다. 4-2절의 활용매뉴얼과 같은 역할입니다 — AI에게 정답 문서를 쥐여 주는 것입니다.
파일 내용은 이 정도면 충분합니다.
# Gemini API — 파이썬 사용법 (이 실습 기준)
설치:
pip install google-genai python-dotenv
모델: gemini-3.1-flash-lite
무료 등급 한도:
RPM(분당 요청) : 약 15회/분
RPD(하루 요청) : 약 500~1,000회/일
※ 한도를 넘기면 429 오류가 납니다. 잠시 기다렸다 다시 시도하세요.
기본 코드:
import os
from dotenv import load_dotenv
from google import genai
load_dotenv()
client = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
response = client.models.generate_content(
model="gemini-3.1-flash-lite",
contents="경복궁을 한 문장으로 소개해 줘",
)
print(response.text)
5-3. 프롬프트
① 준비 확인
넷 중 하나라도 빠지면 AI가 API 호출을 지어냅니다.
| 확인할 것 | 만든 곳 |
|---|---|
터미널에 (.venv) 표시 |
2-2절 |
tour/.env 에 DATAGOKR_API_KEY·GOOGLE_API_KEY |
3-3 · 5-1 |
tour/chatbot/gemini_api.txt |
5-2 |
tour/web/index.html |
4-4 |
② 프롬프트 하나로
tour 폴더에서 claude를 실행하고 붙여넣습니다.
[목표]
chatbot/chatbot.py 하나로 도는 터미널 챗봇을 만들어 줘.
관광지를 물으면 한국관광공사 API로 실제 정보를 가져와, 그걸 근거로 Gemini가 한국어로 답한다.
[참고 자료]
- web/index.html — 1단계에서 동작을 확인한 관광공사 API 호출 방식. 그대로 옮겨 쓰고 새로 지어내지 마.
- chatbot/gemini_api.txt — Gemini 모델 이름과 호출 형식.
- 키는 tour/.env 에서 python-dotenv 로 읽어(DATAGOKR_API_KEY, GOOGLE_API_KEY). 코드에 직접 적지 마.
[요구사항]
1. 질문에서 지역·키워드를 뽑아 '키워드 검색 조회'(지역만 있으면 '지역기반 관광정보 조회')를 호출한다
2. 공통 파라미터(MobileOS=ETC, MobileApp, _type=json, numOfRows, pageNo)를 빠뜨리지 마
3. 결과 상위 5건(이름·주소·전화·개요)을 Gemini에 '참고 자료'로 넣고 "이 안의 사실만 쓰라"고 지시한다
4. 답변 아래에 참고한 관광지 이름·주소를 붙인다. 0건이면 "찾지 못했습니다"라고 답한다
5. '종료'를 넣을 때까지 계속 대화한다
[제약]
- requests 의 params= 로 넘기므로 인증키는 Decoding 키를 쓴다(이중 인코딩 주의)
- 파일은 chatbot/chatbot.py 하나, 입출력은 encoding="utf-8"
- API 호출이 실패하면 상태 코드와 응답을 그대로 출력해. 조용히 넘어가지 마
[확인]
"부산 해수욕장 추천해 줘"로 한 번 실행해서, API가 돌려준 건수와 최종 답변을 함께 보여 줘.
왜 1단계보다 정확한가요? 이미 동작을 확인한 API 호출 방식(
web/index.html)과 모델 이름(gemini_api.txt)을 그대로 물려받기 때문입니다.
python chatbot/chatbot.py
✅ 체크포인트: "부산에 갈 만한 바닷가 알려 줘"에 실제 관광지 이름과 주소가 섞인 답이 나오면 성공입니다.
③ 잘 안 되면
| 증상 | 볼 곳 |
|---|---|
| 관광지를 지어냄 | API 호출이 실패한 것. 인코딩/디코딩 키를 바꿔 썼는지 4-5절 확인 |
ModuleNotFoundError |
(.venv)가 꺼져 있습니다 → 2-2절 |
429 RESOURCE_EXHAUSTED |
Gemini 무료 한도(분당 15회) 초과. 1분 기다렸다 다시 |
잘 돌아가면 이어서 다듬습니다.
[추가 요청]
직전 3턴을 기억해서 "거기 근처에 먹을 데는?" 같은 이어지는 질문도 알아듣게 해 줘.
이때도 답은 새로 호출한 관광공사 API 결과에 근거해야 한다. 기억은 3턴까지만.
5-4. (선택) Hugging Face Spaces에 배포하기
챗봇을 웹 주소로 만들어 남에게 보여 주고 싶을 때만 하면 됩니다. 건너뛰어도 실습에는 지장이 없습니다.
먼저 알아 둘 것 — 지금 막 가입한 무료 계정으로는 파이썬 챗봇을 스페이스에 띄울 수 없습니다. 허깅페이스가 정책을 바꿨습니다. 이 절은 그 상황을 우회해 무료 계정도 오늘 바로 배포할 수 있는 방법을 안내합니다.
① 지금 허깅페이스는 이렇습니다
스페이스는 종류가 세 가지인데, 무료로 만들 수 있는 것은 하나뿐입니다.
| 스페이스 종류 | 무엇이 실행되나 | 무료 계정으로 새로 만들 수 있나 |
|---|---|---|
| Gradio · Docker | 파이썬이 서버에서 돈다 | 불가 — PRO 유료 구독이 필요합니다 |
| Gradio (ZeroGPU) | 파이썬이 서버에서 돈다 | 조건부 — 이메일 인증 + 계정 생성 후 30일 경과, 최대 2개 |
| Static | HTML·CSS·JS만. 서버가 없다 | 가능 — 누구나 무료 |
허깅페이스 공식 문서의 문구입니다.
Static Spaces are free for everyone. Gradio and Docker Spaces run on compute and require a paid plan to create: PRO for personal accounts, Team or Enterprise for organizations. Free personal accounts in good standing can still host up to 2 Gradio Spaces running on ZeroGPU.
— Spaces OverviewFree personal accounts: Maximum of 2 ZeroGPU Spaces, for accounts in good standing (verified email, account older than 30 days).
— Spaces ZeroGPU
정리하면 이렇습니다. 오늘 가입한 사람은 Gradio 스페이스를 만들 수 없습니다. 30일이 지나기를 기다려 ZeroGPU로 만들거나, PRO를 구독해야 합니다. 웹에서 손으로 만들어도 마찬가지입니다 — 막히는 곳이 파일 업로드가 아니라 스페이스 생성이기 때문입니다. 실패하면 402 Payment Required가 돌아옵니다.
② 그래서 이렇게 갑니다 — 정적 스페이스 + 이용자 키 입력
남은 길은 정적 스페이스인데, 여기서는 파이썬이 돌지 않습니다. 그래서 배포용으로 HTML 한 장을 더 만듭니다. 1단계에서 만든 web/index.html과 같은 방식입니다.
chatbot.py를 버리는 것이 아닙니다. 파이썬 챗봇은 2단계의 본체이고 그대로 둡니다. 배포용 HTML은 같은 로직을 자바스크립트로 옮긴 사본입니다. 나중에 PRO를 구독하거나 계정이 30일을 넘기면, 파이썬 파일을 그대로 Gradio 스페이스에 올리면 됩니다.
여기서 키를 어떻게 할 것인가가 핵심입니다. 정적 스페이스는 서버가 없으니 키를 숨길 곳이 없습니다. 게다가 허깅페이스 문서에 따르면 정적 스페이스는 Secret으로 넣어도 클라이언트 자바스크립트에서 읽힙니다.
For Static Spaces, both are available through client-side JavaScript in
window.huggingface.variables.
— Spaces Overview, Managing secrets and environment variables
그래서 Gemini 키는 아예 페이지에 넣지 않고, 쓰는 사람이 각자 자기 키를 입력하게 합니다. 입력한 키는 그 사람 브라우저에만 저장되고(localStorage), 구글 외의 어디로도 가지 않습니다. 여러 사람이 써도 각자 자기 할당량을 씁니다.
| 키 | 어디에 두나 | 왜 |
|---|---|---|
DATAGOKR_API_KEY |
HTML 안에 직접 (Encoding 키) | 무료 공개 데이터 키. 1단계 web/index.html과 같은 방식입니다 |
GOOGLE_API_KEY |
넣지 않는다. 이용자가 화면에서 입력 | 구글 계정에 묶인 키라 노출되면 안 됩니다 |
③ 토큰 발급 — 이것만 직접 합니다
- huggingface.co에 가입·로그인합니다.
- 오른쪽 위 프로필 → Settings → Access Tokens → Create new token.
- 종류는 반드시 Write를 고릅니다. Read 토큰으로는 스페이스를 만들 수 없습니다.
- 만들어진 토큰을
tour/.env에 넣습니다. 창을 닫으면 다시 볼 수 없습니다.
# Hugging Face (선택 — 배포할 때만)
HF_TOKEN=여기에_발급받은_토큰_붙여넣기
이름을
HF_TOKEN으로 두면huggingface_hub라이브러리가 알아서 찾아 씁니다.
④ 나머지는 프롬프트 하나로
먼저 라이브러리를 설치합니다((.venv)가 켜진 상태에서). 이 라이브러리가 스페이스 만들기와 파일 업로드를 해 줍니다.
pip install huggingface_hub
[목표]
챗봇을 Hugging Face 정적(static) 스페이스에 올려 웹 주소로 쓰게 해 줘.
Gemini 키는 페이지에 넣지 말고, 쓰는 사람이 화면에서 직접 입력하게 만든다.
[참고 자료]
- chatbot/chatbot.py — 관광공사 API·Gemini 호출 로직. 이 로직을 자바스크립트로 그대로 옮긴다.
이 파일 자체는 고치지 마. 2단계 본체다.
- web/index.html — 1단계에서 동작을 확인한 관광공사 API 호출 방식. 그대로 재사용한다.
- 허깅페이스 인증은 tour/.env 의 HF_TOKEN(Write 권한)으로. 토큰 값은 어디에도 남기지 마.
[할 일]
1. chatbot/space_static/ 에 두 파일을 만든다
- index.html 대화 화면 + 관광공사 API 호출 + Gemini 호출을 한 파일에
- README.md 머리말: title / sdk: static / app_file: index.html
2. index.html 안에서
- 맨 위에 Gemini API 키 입력란(password)을 둔다. 입력하면 localStorage 에 저장하고
'키 삭제' 버튼도 만든다. 키가 없으면 대화 대신 입력 안내를 보여 준다
- 관광공사 인증키는 Encoding 키를 const SERVICE_KEY 한 줄에 둔다
- Gemini 는 REST 로 부른다:
POST https://generativelanguage.googleapis.com/v1beta/models/<모델>:generateContent
헤더 x-goog-api-key 에 이용자가 입력한 키를 넣는다
- 답변 아래에 참고한 관광지 이름·주소와 호출한 오퍼레이션·총 건수를 붙인다
- 직전 3턴을 기억한다
3. whoami() 로 내 허깅페이스 아이디를 알아낸다. 나에게 묻지 마
4. create_repo(repo_id="<아이디>/tour", repo_type="space", space_sdk="static", exist_ok=True)
5. upload_folder 로 그 두 파일만 올리고, 스페이스 주소를 알려 준다
[제약]
- 올릴 파일은 index.html 과 README.md 둘뿐. .env·인증키·토큰은 절대 올리지 마
- Gemini 키를 코드·Secret·Variable 어디에도 넣지 마. 정적 스페이스는 Secret 도 브라우저에서 읽힌다
- add_space_secret 을 쓰지 마. 등록할 키가 없다
- chatbot/chatbot.py 는 건드리지 마. 로직을 읽어 오기만 한다
[확인]
올리기 전에 index.html 의 자바스크립트를 문법 검사하고, 실제로 API를 한 번 호출해
'부산 해수욕장 추천해 줘'가 동작하는지 확인해서 결과를 보여 줘.
올린 뒤에는 배포 주소를 열어 페이지가 뜨는지 확인하고 알려 줘.
✅ 체크포인트: 스페이스 주소를 열었을 때 키 입력 상자가 보이고, 자기 Gemini 키를 넣은 뒤 "제주 오름 알려 줘"에 실제 오름 이름과 주소가 나오면 성공입니다. 정적 스페이스는 빌드가 거의 즉시 끝납니다.
⑤ 막히면 — 손으로 올리는 방법
프롬프트가 중간에 실패해도, 파일 2개(chatbot/space_static/)만 만들어져 있으면 웹에서 직접 올릴 수 있습니다.
- huggingface.co 오른쪽 위 + → New Space. 이름은
tour, SDK는 Static, 공개 여부는 Public. - Files → + Add file → Upload files로
index.html과README.md를 끌어다 놓고 아래 Commit changes to main을 누릅니다. 새로 만든 스페이스에 들어 있던index.html·style.css는 덮어쓰거나 지우면 됩니다. - 위쪽 App 탭으로 가면 화면이 바로 뜹니다.
Secret은 등록하지 마세요. 정적 스페이스에서는 Secret도 브라우저에서 읽힙니다. Gemini 키는 이용자가 화면에서 넣는 것이 이 방식의 핵심입니다.
.env파일은 절대 올리지 마세요. 올릴 파일은index.html과README.md둘뿐입니다.
⑥ 그래도 파이썬으로 띄우고 싶다면
"파이썬이 서버에서 돌고 있다"를 보여 주는 것이 목적이라면 선택지는 넷입니다.
| 방법 | 비용 | 오늘 바로 되나 | 비고 |
|---|---|---|---|
| HF PRO 구독 | 유료 | ○ | chatbot.py를 Gradio로 감싼 app.py를 만들어 올리면 됩니다 |
| HF ZeroGPU | 무료 (2개까지) | ✕ | 계정이 30일을 넘겨야 합니다. Gradio 전용 |
| Streamlit Community Cloud | 무료 | ○ | GitHub 계정만 있으면 됩니다. Streamlit으로 고쳐 써야 합니다 |
| Render | 무료 티어 | ○ | Gradio 그대로 됩니다. 15분 무접속 시 잠들고 재기동에 ~1분 |
수업이나 발표 자리라면 Google Colab에서
demo.launch(share=True)한 줄이 가장 빠릅니다. 코드 셀과 실행 로그를 그대로 보여 주면서 챗봇을 돌릴 수 있습니다. 다만 공유 주소는 오래가지 않으니 과제 제출용 링크로는 쓰지 마세요.
6. 3단계 — 글쓰기 에이전트
여기부터가 심화 가이드에서 배운 서브에이전트와 스킬을 실제로 쓰는 단계입니다.
6-1. writing_tips.txt 만들기
먼저 재미있게 쓰는 법에 대한 기준을 파일로 만들어 둡니다. 참고 예시는 How to make your blog more entertaining (Indie Essentials) — 비유로 풀기, 이야기로 말하기, 자기 목소리 넣기, 관점 뒤집기 같은 요령을 짧게 정리해 둔 글입니다.
이것도 AI에게 맡기면 편합니다.
[목표]
writing_agent/writing_tips.txt 를 만들어 줘. 재미있는 글의 기준을 파일 하나로 고정하는 것이다.
[참고 자료]
https://indieessentials.co.uk/make-your-blog-more-entertaining/ 를 읽고 뼈대로 삼아.
여기에 한국어 블로그·에세이 글쓰기 요령을 두세 곳 더 검색해 보태.
[요구사항]
1. 실행할 수 있는 지시문으로 쓴다
○ "첫 문장은 질문이나 장면으로 시작한다" ✕ "재미있게 쓴다"
2. 8~12개로 추리고, 항목마다 한 줄 설명과 짧은 예시를 붙인다
3. '지키지 말아야 할 것'을 따로 목록으로 (문단·문장 길이, 과장 표현, 확인 안 된 사실 단정 등)
4. 맨 아래에 참고 링크를 출처로 남긴다
[제약]
일반 텍스트(UTF-8)로. 이 파일만 읽어도 기준을 알 수 있게 써 줘.
왜 이 파일이 필요한가요? 그냥 "재미있게 써 줘"라고 하면 AI마다, 그날그날 결과가 들쭉날쭉합니다. 기준을 파일로 고정해 두면 언제 실행해도 같은 톤이 나옵니다. 심화 가이드의 메모리 파일과 같은 원리입니다.
6-2. 프롬프트
[목표]
tour/writing_agent 에 글쓰기 파이프라인을 만들어 줘.
관광 키워드 하나 → 자료 수집 → 사실만 정리 → 재미있는 글 → result.md
[만들 것]
- CLAUDE.md tour 폴더 전체를 관장하는 메모리. 아래 [메모리] 참고
- .claude/agents/ tour-collector(수집) · tour-summarizer(정리) · tour-writer(작성)
- .claude/skills/ tour-search(관광공사 API 호출 절차) · fun-writing(글쓰기 절차)
[메모리 — tour/CLAUDE.md]
이 폴더에서 일할 때 매번 되풀이해 말하지 않아도 되도록, 아래를 한 파일에 적어 둬.
에이전트와 스킬이 이 파일을 바탕에 깔고 움직인다.
- 폴더 구조와 각 단계의 산출물
- 인증키 두 종류 규칙(주소에 붙일 땐 Encoding, requests params= 엔 Decoding)
- 관광공사 API 주소·공통 파라미터·쓰는 오퍼레이션·resultCode 확인법
- 파이썬 실행 경로(.venv) 와 한글 출력 설정
- 지킬 것: .env 와 키 값을 남기지 않는다, 없는 값을 지어내지 않는다,
호출 실패를 조용히 넘기지 않는다
짧게 써. 여기 적은 것을 에이전트·스킬 파일에 다시 베껴 적지 마.
[참고 자료 — 이대로 연결해 줘]
- tour-search 스킬에는 web/index.html 에서 이미 동작이 확인된 관광공사 API 호출 방식
(요청 주소·오퍼레이션·파라미터·인증키 자리)을 그대로 옮겨 적어. 새로 지어내지 마.
- 파이썬·명령줄에서 호출할 때는 .env 의 DATAGOKR_API_KEY(Decoding 키)를 쓰고,
공통 파라미터(MobileOS=ETC, MobileApp, _type=json, numOfRows, pageNo)를 빠뜨리지 마.
- fun-writing 스킬은 writing_agent/writing_tips.txt 를 매번 다시 읽어 적용한다.
스킬 안에 요령을 베껴 적지 마.
[각자 할 일]
- collector tour-search 로 자료 수집(이름·주소·전화·개요·contentid) → collected.md
- summarizer 확인된 사실만 추리고 근거 표시, 없으면 '출처 미확인' → summary.md
- writer summary.md 와 writing_tips.txt 만 근거로 작성 → result.md
[규칙]
- 순서대로 실행하고, 앞 단계가 남긴 파일만 다음 단계에 넘긴다
- 관광지 이름·주소는 API 응답 값을 그대로 쓴다
- 각 description 에 '언제 쓰는지'를 한 문장으로 분명히 적는다
- 공통 규칙은 CLAUDE.md 한 곳에만 두고, 에이전트·스킬은 그것을 전제로 쓴다
[산출물]
result.md — 제목, 본문 1,200자 내외, 맨 아래 '참고한 관광지' 표(이름·주소·출처)
[확인]
만든 파일 목록과 각 description 줄을 보여 줘. 실행은 아직 하지 마.
6-3. 만들어질 구조
AI가 대략 이런 구성을 만듭니다. 파일 이름은 조금씩 다를 수 있습니다.
tour/
├── CLAUDE.md # 이 폴더 전체를 관장하는 메모리 (항상 먼저 읽힌다)
├── writing_agent/
│ ├── writing_tips.txt # 글쓰기 기준 (내가 만든 것)
│ ├── collected.md # ① 수집한 원자료
│ ├── summary.md # ② 사실만 추린 정리본
│ └── result.md # ③ 최종 결과물
└── .claude/
├── agents/
│ ├── tour-collector.md # ① 검색·수집 — 관광공사 API + 웹 검색
│ ├── tour-summarizer.md # ② 요약·정리 — 사실만 추려 정돈
│ └── tour-writer.md # ③ 글 작성 — writing_tips 기준으로 재미있게
└── skills/
├── tour-search/SKILL.md # API 호출 절차 (web/index.html 참조)
└── fun-writing/SKILL.md # 글쓰기 절차 (writing_tips.txt 참조)
CLAUDE.md는 나머지 파일들이 딛고 서는 바닥입니다. 이 폴더에서 작업을 시작하면
매번 자동으로 읽힙니다. 인증키 규칙이나 API 주소처럼 세 에이전트가 모두 알아야 하는
것은 여기 한 번만 적습니다. 에이전트·스킬 파일에 같은 내용을 베껴 적으면, 나중에 하나를
고쳤을 때 나머지가 어긋납니다.
세 일꾼이 순서대로 일합니다. 각자 자기 책상(독립 문맥)을 쓰기 때문에, 수집한 원자료가 글쓰기 단계까지 그대로 흘러들어 대화가 지저분해지는 일이 없습니다.
키워드 "제주 오름"
│
▼ ① 검색·수집 → 관광공사 API + 웹에서 자료 모으기 → collected.md
▼ ② 요약·정리 → 사실만 추려 정돈, 출처 표시 → summary.md
▼ ③ 글 작성 → writing_tips.txt 기준으로 재미있게 → result.md
6-4. 만들어진 파일은 이렇게 생겼습니다
AI가 만들어 준 파일을 열어 보면 아래와 비슷합니다. 똑같이 나오지는 않습니다 — 내용을 읽어 보고 어색한 곳은 말로 고치라는 뜻으로 실어 둡니다.
파일 맨 위 --- 사이가 머리말(front matter)입니다. 그중 description 줄이 특히 중요합니다. AI는 이 한 줄만 보고 "지금 이 일꾼을 부를 때인가"를 판단하기 때문에, 여기가 모호하면 에이전트가 아예 불리지 않습니다.
⓪ tour/CLAUDE.md — 머리말이 없습니다. 그냥 읽히는 메모입니다.
# tour — 관광지 검색 · 챗봇 · 글쓰기 실습
공공데이터포털 한국관광공사 관광정보 API 하나로 세 가지를 만든다.
뒤 단계는 앞 단계의 결과물을 재료로 쓴다.
## 폴더
tour/
├── .env 키 보관함. 절대 커밋하지 않는다
├── web/ 1단계 — 검색 웹페이지
├── chatbot/ 2단계 — 챗봇
├── writing_agent/ 3단계 — 글쓰기 파이프라인
└── .claude/ 에이전트와 스킬
## 인증키 — 가장 자주 틀리는 곳
.env 의 DATAGOKR_API_KEY 에는 Encoding 키(%2B, %3D 포함)가 들어 있다.
- 주소 문자열에 직접 이어 붙일 때(HTML/JS) : .env 값 그대로
- requests 의 params= 로 넘길 때(파이썬) : urllib.parse.unquote() 로 푼 값
바꿔 쓰면 키가 멀쩡해도 403 SERVICE_KEY_IS_NOT_REGISTERED_ERROR 가 난다.
## 관광공사 API
- 주소: https://apis.data.go.kr/B551011/KorService2
- 공통 파라미터: serviceKey, MobileOS=ETC, MobileApp=tour, _type=json, numOfRows, pageNo
- response.header.resultCode 가 0000 이어야 정상. XML 이 오면 _type=json 이 빠진 것이다.
- 오퍼레이션: searchKeyword2(키워드) / areaBasedList2(지역) / ldongCode2(지역코드) /
detailCommon2(개요)
- 지역은 lDongRegnCd(법정동 시도 코드)로 받는다. areaCode 가 아니다.
## Gemini
모델 gemini-3.1-flash-lite. 키는 .env 의 GOOGLE_API_KEY. 무료 등급은 분당 약 15회.
## 실행 환경 (Windows)
파이썬은 .venv\Scripts\python.exe 를 쓴다. 한글이 깨지면
$env:PYTHONIOENCODING="utf-8" 를 먼저 설정한다.
## 글쓰기 파이프라인
키워드 → tour-collector → collected.md → tour-summarizer → summary.md
→ tour-writer → result.md
순서대로 실행하고, 앞 단계가 남긴 파일만 다음 단계에 넘긴다.
## 지킬 것
- .env 와 인증키 값을 화면·파일·커밋에 남기지 않는다.
- API 응답에 없는 이름·주소·전화번호·요금을 지어내지 않는다. 없으면 비워 둔다.
- 호출이 실패하면 조용히 넘어가지 말고 상태 코드와 응답을 그대로 드러낸다.
- 연속 호출 사이에 0.3초쯤 쉰다. 개발계정은 하루 1,000건 한도다.
① .claude/agents/tour-collector.md
---
name: tour-collector
description: 관광 키워드로 자료를 모을 때 사용한다. 한국관광공사 API에서 관광지 정보를 받아 오고, API로 채우지 못한 부분만 웹 검색으로 보완한다. 글쓰기 파이프라인 1단계.
tools: Read, Write, Bash, WebSearch, WebFetch
---
너는 자료 수집 담당이다. 판단하거나 글을 쓰지 않는다. 사실을 모아 파일로 남기는 것까지가 네 일이다.
## 절차
1. `tour-search` 스킬을 읽고, 거기 적힌 절차대로 한국관광공사 API를 호출한다.
2. 키워드로 '키워드 검색 조회'를 먼저 호출한다. 결과가 5건 미만이면 지역코드를 찾아
'지역기반 관광정보 조회'로 보완한다.
3. 관광지마다 contentid 로 '공통정보 조회'를 호출해 개요(overview)를 받아 온다.
4. API로 채우지 못한 항목(계절 정보, 최근 소식 등)만 웹 검색으로 보완하고, 출처 URL을 함께 적는다.
## 산출물 — writing_agent/collected.md
관광지 하나당 아래 항목을 적는다. 빈 항목은 비워 두고 추측으로 채우지 않는다.
- 관광지명 / 주소 / 전화 / 개요 / 대표 이미지 주소 / contentid
- 출처: `관광공사 API(오퍼레이션 이름)` 또는 웹 링크
## 하지 말 것
- API 응답에 없는 이름·주소·전화번호를 만들어 내지 않는다.
- 요약하거나 문장을 다듬지 않는다. 받은 값을 그대로 옮긴다.
- 호출이 실패하면 그냥 넘어가지 말고, 요청 주소와 오류 코드를 그대로 파일에 적어 남긴다.
② .claude/agents/tour-summarizer.md
---
name: tour-summarizer
description: collected.md 의 원자료를 사실만 남겨 정리할 때 사용한다. 출처가 없는 내용을 걸러 내는 것이 목적이다. 글쓰기 파이프라인 2단계.
tools: Read, Write
---
너는 정리 담당이다. 새 정보를 찾지 않는다. `writing_agent/collected.md` 에 있는 것만 다룬다.
## 절차
1. `collected.md` 를 읽는다. 파일이 없으면 멈추고 tour-collector 를 먼저 돌리라고 알린다.
2. 같은 contentid 를 가진 항목은 한 곳으로 합친다.
3. 관광지마다 한 문단으로 줄인다. 이름과 주소는 원문 그대로 옮긴다.
4. 근거가 관광공사 API 응답이면 문장 끝에 [관광공사], 웹이면 [웹: 링크] 를 붙인다.
5. 어느 쪽에도 없는 내용은 버리거나 [출처 미확인] 으로 표시한다.
## 산출물 — writing_agent/summary.md
- 맨 위: 키워드와 수집 건수
- 관광지별로: 이름 / 주소 / 한 줄 요약 / 특징 2~3문장(근거 표시 포함)
- 맨 아래: '확인하지 못한 것' 목록
## 하지 말 것
- 형용사를 덧붙여 인상을 바꾸지 않는다. 재미있게 만드는 일은 다음 단계의 몫이다.
- 건수를 맞추려고 없는 관광지를 채우지 않는다. 적으면 적은 대로 넘긴다.
③ .claude/agents/tour-writer.md
---
name: tour-writer
description: summary.md 를 재미있는 글로 바꿀 때 사용한다. writing_tips.txt 의 기준을 그대로 적용한다. 글쓰기 파이프라인 3단계.
tools: Read, Write
---
너는 글쓴이다. 자료를 더 찾지 않는다. `writing_agent/summary.md` 안에 있는 사실만 쓴다.
## 절차
1. `fun-writing` 스킬을 읽고, 거기 지시대로 `writing_agent/writing_tips.txt` 를 읽는다.
2. summary.md 를 읽고 글의 흐름을 먼저 세 줄로 잡는다 — 무엇으로 시작해 무엇으로 끝낼지.
3. writing_tips.txt 의 요령을 항목별로 적용해 초고를 쓴다.
4. 다 쓴 뒤 writing_tips.txt 의 '지키지 말아야 할 것' 목록을 하나씩 대조해 고친다.
## 산출물 — writing_agent/result.md
- 제목 한 줄
- 본문 1,200자 내외
- 맨 아래 '참고한 관광지' 표: 관광지명 · 주소 · 출처
## 하지 말 것
- summary.md 에 없는 관광지·주소·영업시간·요금을 쓰지 않는다.
- [출처 미확인] 이 붙은 내용을 단정해서 쓰지 않는다. 쓰려면 "~라고 한다"로 표시한다.
④ .claude/skills/tour-search/SKILL.md
---
name: tour-search
description: 한국관광공사 관광정보 API로 관광지를 검색할 때 사용한다. 요청 주소·오퍼레이션·필수 파라미터·인증키 규칙을 담고 있다.
---
# 한국관광공사 관광정보 API 호출 절차
## 0. 근거 파일
요청 주소와 오퍼레이션 이름은 `web/index.html` 안에 이미 동작이 확인된 값이 들어 있다.
새로 짓지 말고 항상 그 파일에서 읽어 온다.
## 1. 인증키
- 파이썬 requests 의 params= 로 넘길 때 → `.env` 의 DATAGOKR_API_KEY (**Decoding 키**)
- 주소 문자열에 직접 이어 붙일 때 → **Encoding 키**
- 둘을 바꿔 쓰면 키가 멀쩡해도 403 SERVICE_KEY_IS_NOT_REGISTERED_ERROR 가 난다.
## 2. 공통 파라미터 (하나라도 빠지면 오류)
- serviceKey : 인증키
- MobileOS : ETC
- MobileApp : 아무 이름 (예: tour)
- _type : json
- numOfRows : 10 / pageNo : 1
## 3. 쓰는 오퍼레이션
- 키워드로 찾기 — 키워드 검색 조회 (keyword)
- 지역으로 찾기 — 지역기반 관광정보 조회 (areaCode, contentTypeId)
- 지역 코드 목록 — 지역코드 조회
- 개요·상세 — 공통정보 조회 (contentId)
정확한 오퍼레이션 이름은 `web/index.html` 과 활용매뉴얼에 적힌 것을 그대로 쓴다.
## 4. 응답 확인
- response.header.resultCode 가 0000 이어야 정상이다.
- totalCount 가 0이면 결과가 없는 것이다. 다른 키워드로 다시 시도하되, 없는 결과를 만들어 내지 않는다.
- XML이 돌아오면 _type=json 이 빠진 것이다.
## 5. 호출 예의
- 연속 호출 사이에 0.3초쯤 쉰다.
- 같은 요청을 반복하지 않는다. 개발계정은 하루 호출 한도가 있다.
⑤ .claude/skills/fun-writing/SKILL.md
---
name: fun-writing
description: 정리된 자료를 재미있는 글로 쓸 때 사용한다. writing_tips.txt 의 기준을 적용하고, 다 쓴 뒤 점검하는 절차를 담고 있다.
---
# 재미있게 쓰기 절차
## 0. 기준 파일
글을 쓰기 전에 `writing_agent/writing_tips.txt` 를 **매번 다시 읽는다.**
이 스킬에는 요령을 베껴 적지 않는다. 기준은 그 파일 하나가 갖는다. 파일이 바뀌면 결과도 바뀌어야 한다.
## 1. 쓰기 전
- summary.md 에서 가장 의외인 사실 하나를 고른다. 그것이 첫 문단이 된다.
- 독자를 한 명으로 정한다. 예: 주말에 갈 곳을 찾는 직장인.
## 2. 쓰는 중
- writing_tips.txt 의 항목을 위에서부터 적용한다.
- 사실 문장과 감상 문장을 섞는다. 사실만 이어지면 목록이 되고, 감상만 이어지면 광고가 된다.
- 관광지 이름이 처음 나올 때 주소를 괄호로 붙인다.
## 3. 쓴 뒤 점검 — 하나씩 확인하고 결과를 보고한다
- writing_tips.txt 의 '지키지 말아야 할 것' 목록에 걸리는 문장이 있는가
- summary.md 에 없는 사실이 들어가지 않았는가
- 한 문단이 다섯 줄을 넘지 않는가
- 제목만 읽고도 무슨 글인지 알 수 있는가
## 4. 산출물
`writing_agent/result.md` 로 저장하고, 3번 점검에서 고친 내용을 한 줄로 함께 남긴다.
읽어 보고 고치세요. 특히
description줄이 "관광 관련 작업" 처럼 두루뭉술하면 에이전트가 불리지 않습니다. "언제 쓰는지"가 분명한 문장으로 바꿔 달라고 말하면 됩니다.
6-5. 실행
"제주 오름"으로 글 써 줘.
✅ 체크포인트:
writing_agent/result.md가 생기고, 그 안에 실제 존재하는 오름 이름과 주소가 들어 있으면 성공입니다.
단계를 끊어서 시키면 어느 일꾼이 무슨 일을 하는지 눈으로 볼 수 있어 실습용으로 좋습니다.
tour-collector 서브에이전트만 먼저 돌려서 "제주 오름" 자료를 모아 줘. collected.md 까지만 만들고 멈춰.
큰 작업 전에는
Shift+Tab으로 플랜 모드에 들어가세요. 어떤 서브에이전트와 스킬이 언제 작동하는지 계획으로 먼저 보여 줍니다.결과물은 반드시 사람이 확인하세요. AI는 그럴듯하게 지어내는 일이 있습니다. 관광지 이름·주소·영업시간은 대한민국 구석구석 같은 원출처에서 다시 확인한 뒤 쓰세요.
7. 자주 막히는 곳
| 증상 | 해결 |
|---|---|
SERVICE_KEY_IS_NOT_REGISTERED_ERROR (403) |
① 이 API에 활용신청을 했는지 ② 승인 후 시간이 지났는지 ③ 인코딩/디코딩 키를 바꿔 썼는지(4-5절) 순서로 확인 |
.env를 못 읽음 |
파일 위치가 tour/ 최상단인지, 이름이 .env.txt가 아닌지 확인. 파이썬은 load_dotenv()를 호출했는지 확인 |
ModuleNotFoundError: No module named 'dotenv' |
가상환경이 꺼져 있습니다. (.venv) 표시를 확인하고(2-2절) pip install python-dotenv 재실행 |
Gemini 429 RESOURCE_EXHAUSTED |
무료 한도(분당 15회) 초과입니다. 1분 기다렸다 다시 시도하세요 |
| 챗봇이 관광지를 지어냄 | API 호출이 실패해 AI가 자기 지식으로 답한 것입니다. "API 응답을 그대로 출력해 봐"라고 시켜 확인하세요 |
| 서브에이전트가 안 불림 | .claude/agents/의 description 줄이 모호하면 AI가 언제 쓸지 판단하지 못합니다. "언제 쓰는지"를 구체적으로 고쳐 주세요 |
| 한글이 깨짐 | 파일을 UTF-8로 저장. 파이썬은 open(..., encoding="utf-8") |
모든 오류의 1번 해결책 — 오류 메시지를 그대로 복사해서 AI에게 붙여 넣고 "원인과 해결 방법을 알려줘"라고 물어보세요.
8. 참고 링크
- 공공데이터포털 — 한국관광공사_국문 관광정보 서비스_GW. https://www.data.go.kr/data/15101578/openapi.do
- 한국관광공사 TourAPI (콘텐츠랩). https://api.visitkorea.or.kr
- Google AI Studio — API 키 발급. https://aistudio.google.com/api-key
- Google AI Studio — 개발 문서. https://aistudio.google.com/docs
- Gemini API 파이썬 SDK. https://googleapis.github.io/python-genai/
- python-dotenv 문서. https://pypi.org/project/python-dotenv/
- GitHub Pages 시작하기. https://docs.github.com/en/pages
- Hugging Face Spaces 문서. https://huggingface.co/docs/hub/spaces
- huggingface_hub — 스페이스 만들기·Secret 등록·업로드 API. https://huggingface.co/docs/huggingface_hub/package_reference/hf_api
- Indie Essentials — How to make your blog more entertaining. https://indieessentials.co.uk/make-your-blog-more-entertaining/
API 주소·모델명·무료 한도는 2026년 8월 기준이며 자주 바뀝니다. 막히면 각 도구에서
/help를 먼저 확인하고 위 문서를 참고하세요.