안녕하세요. 새록입니다.
이번 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종 산출물" 전체 시연
도움 받은 글 (옵션)