안녕하세요. 새록입니다.
이번 23기에도 역시 "홈랩에서 24 시간 멈추지 않는 AI 에이전트 운영 인터페이스 만들기" 스터디에 참여하게 됐습니다. 저는 AI 교육 관련 일을 하고 있고, 개인적으로 AI 교육 콘텐츠를 유튜브에 올리는 것을 최근 시도하고 있는데요.
영상을 더 자주 올리고 싶은데, 영상 하나를 만들 때마다 ① 전체 영상 흐름 → ② 대본 → ③ 장표(슬라이드) → ④ 썸네일 → ⑤ 제목/본문을 매번 반복해서 만들어야 하는 게 늘 부담이었는습니다!
오프라인 모임때 같이 스터디원 분들과 인사 나눈 후에 각자 4주간의 주제를 잡는 시간을 가졌는데, 유튜브를 주제로 해보면 좋을 것 같다는 아이디어가 생각나서 이번 4주간 스터디에서 진행할 주제를 "유튜브 콘텐츠 제작 에이전트" 로 잡았습니다. 아이디어만 던지면 위 5가지를 알아서 뽑아주고, 최종적으로는 내 컴퓨터(홈랩)에서 24시간 돌면서 밤새 다음 영상 기획까지 준비해두는 나만의 제작 조수를 만드는 게 목표예입니다.
진행 방법
사용한 도구: Claude Code (바이브코딩) - 언어는 Python + FastAPI(서버 만드는 도구) 활용
활용 방식: 먼저 4주 전체 계획을 같이 정리해 문서(PLAN.md)로 저장하고, 그다음 1주차 목표인 "API 서버 뼈대" 를 만들었어요.
API 서버 = 부탁하면 데이터를 저장하거나 보여주는 '창구 직원' 같은 프로그램이라고 이해했습니다.
프롬프트 (전문):
[4주 계획 잡기]
"유튜브 하나 만들 때도 전체 영상 흐름, 대본 스크립트, 영상에 띄울 장표 제작,
썸 네일 제작, 제목/본문 만들기 이걸 해야 하는데, 이 각각을 가능하게 해주는 걸
스터디 4주 방향에 맞게 디벨롭하려고 해. 어떻게 하면 될지 정리해줘"
[24시간 서버와의 연결 고민]
"근데 이게 24시간 돌아가는 홈서버랑 무슨 관계임?" → (아이디어를 밤새 자동 기획해두는 '스케줄러'를 넣기로 방향 확정)
[문서화 + 1주차 시작]
"그 방향으로 4주 계획 정리해줘"
"문서로 저장하고 1주차 스캐폴딩 시작해줘"
만든 것: 아이디어를 저장하는 '인박스'와, 영상별로 5가지 산출물을 담는 '영상 프로젝트'를 저장·조회하는 서버입니다.
`main.py` — 서버 핵심 (요청을 받아 저장/조회)
```python
from typing import List
from fastapi import FastAPI, HTTPException
import store
from models import Idea, IdeaCreate, VideoProject, VideoCreate, OutputsUpdate
app = FastAPI(title="유튜브 콘텐츠 제작 에이전트 API", version="0.1.0 (week1)")
@app.get("/")
def health():
return {"status": "ok", "service": "youtube-agent", "week": 1}
# 아이디어 인박스
@app.post("/ideas", response_model=Idea)
def create_idea(body: IdeaCreate):
return store.add_idea(body.text)
@app.get("/ideas", response_model=List[Idea])
def get_ideas():
return store.list_ideas()
# 영상 프로젝트
@app.post("/videos", response_model=VideoProject)
def create_video(body: VideoCreate):
return store.add_video(body.idea)
@app.get("/videos", response_model=List[VideoProject])
def get_videos():
return store.list_videos()
@app.get("/videos/{video_id}", response_model=VideoProject)
def get_one_video(video_id: str):
video = store.get_video(video_id)
if not video:
raise HTTPException(status_code=404, detail="영상 프로젝트를 찾을 수 없습니다.")
return video
@app.put("/videos/{video_id}/outputs", response_model=VideoProject)
def put_outputs(video_id: str, body: OutputsUpdate):
patch = body.model_dump(exclude_unset=True)
video = store.update_outputs(video_id, patch)
if not video:
raise HTTPException(status_code=404, detail="영상 프로젝트를 찾을 수 없습니다.")
return video
```
**`models.py`** — 저장할 정보의 '양식' 정의
```python
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, Field
import uuid
def _now() -> str:
return datetime.now().isoformat(timespec="seconds")
def _new_id() -> str:
return uuid.uuid4().hex[:8]
# 아이디어 인박스
class Idea(BaseModel):
id: str = Field(default_factory=_new_id)
text: str
status: str = "queued" # queued(대기) → drafted(초안생성됨)
created_at: str = Field(default_factory=_now)
class IdeaCreate(BaseModel):
text: str
# 영상 산출물(5종)
class VideoOutputs(BaseModel):
flow: Optional[str] = None # ① 전체 영상 흐름
script: Optional[str] = None # ② 대본
slides_md: Optional[str] = None # ③ 장표(마크다운)
thumbnail_copy: Optional[str] = None # ④ 썸네일 문구/시안
title: Optional[str] = None # ⑤ 제목
description: Optional[str] = None # ⑤ 본문(설명)
tags: List[str] = Field(default_factory=list)
class VideoProject(BaseModel):
id: str = Field(default_factory=_new_id)
idea: str
status: str = "queued" # queued → drafting → ready
outputs: VideoOutputs = Field(default_factory=VideoOutputs)
created_at: str = Field(default_factory=_now)
updated_at: str = Field(default_factory=_now)
class VideoCreate(BaseModel):
idea: str
class OutputsUpdate(BaseModel):
flow: Optional[str] = None
script: Optional[str] = None
slides_md: Optional[str] = None
thumbnail_copy: Optional[str] = None
title: Optional[str] = None
description: Optional[str] = None
tags: Optional[List[str]] = None
status: Optional[str] = None
```
**`store.py`** — 데이터를 실제 파일(JSON)에 저장/조회
```python
import json
import threading
from pathlib import Path
from typing import Optional, List
from models import Idea, VideoProject, _now
DATA_FILE = Path(__file__).parent / "data" / "db.json"
_lock = threading.Lock()
def _read() -> dict:
if not DATA_FILE.exists():
return {"ideas": [], "videos": []}
return json.loads(DATA_FILE.read_text(encoding="utf-8"))
def _write(db: dict) -> None:
DATA_FILE.parent.mkdir(parents=True, exist_ok=True)
DATA_FILE.write_text(json.dumps(db, ensure_ascii=False, indent=2), encoding="utf-8")
# 아이디어 인박스
def add_idea(text: str) -> Idea:
with _lock:
db = _read()
idea = Idea(text=text)
db["ideas"].append(idea.model_dump())
_write(db)
return idea
def list_ideas() -> List[Idea]:
return [Idea(**i) for i in _read()["ideas"]]
# 영상 프로젝트
def add_video(idea: str) -> VideoProject:
with _lock:
db = _read()
video = VideoProject(idea=idea)
db["videos"].append(video.model_dump())
_write(db)
return video
def list_videos() -> List[VideoProject]:
return [VideoProject(**v) for v in _read()["videos"]]
def get_video(video_id: str) -> Optional[VideoProject]:
for v in _read()["videos"]:
if v["id"] == video_id:
return VideoProject(**v)
return None
def update_outputs(video_id: str, patch: dict) -> Optional[VideoProject]:
with _lock:
db = _read()
for v in db["videos"]:
if v["id"] == video_id:
status = patch.pop("status", None)
if status is not None:
v["status"] = status
for key, value in patch.items():
v["outputs"][key] = value
v["updated_at"] = _now()
_write(db)
return VideoProject(**v)
return None
```코드 없이 버튼으로 테스트하는 화면
한국사이트 스크린샷
결과와 배운 점
우선 4주간 어떻게 진행할지 방향을 잡고 계획을 세웠습니다. plan.md 로 계획을 정리하고, API 서버를 만들어냈습니다. 아이디어와 영상 프로젝트를 저장·조회할 수 있고, FastAPI가 만들어주는 /docs 화면에서 코드를 몰라도 버튼으로 테스트할 수 있습니다.
배운 점:
API 서버가 뭔지 약간 이해하게 되었습니다. ("부탁하면 저장하거나 보여주는 창구 직원" 같은 프로그램.)
스캐폴딩 = 살을 붙이기 전에 뼈대(폴더·파일·최소 동작)를 먼저 세우는 것. 집 지을 때 기둥부터 세우는 느낌.
시행착오:
처음엔 '스캐폴딩', '엔드포인트' 같은 개발 용어에 막혀서 "이게 대체 뭘 한 거지?" 싶었어요. 언제나 그랬듯 클로드 코드 비유로 다시 설명 듣고 이해했습니다.
연습용 로컬 서버가 자꾸 꺼져서 당황했는데, 다시 켜는 명령어만 있으면 바로 재구동할 수 있음을 알았습니다.
앞으로의 계획:
2주차: 서버를 Docker로 24시간 상주시키고, Agent SDK로 "대본을 자동 생성하는 도구"를 붙이기.
3주차: 생성을 로컬 LLM으로 바꾸고, Tailscale로 폰에서 접근 + 밤새 자동으로 영상 기획을 준비하는 스케줄러 추가.
4주차: "아이디어 1개 → 5종 산출물" 전체 시연
도움 받은 글 (옵션)