Workers AI로 블로그 초안 파이프라인 만들기 (2편)
· cloudflare, workers, ai, github, automation
왜 만들었나
- AI와 나눈 대화를 md로 요약해 글감으로 쓰고 싶었다.
- git을 직접 만지지 않고, 폰에서도 폼 하나로 초안을 올리고 싶었다.
- 검토는 PR로 한다. 브랜치마다 Cloudflare Pages 미리보기가 생기므로 실제 모양으로 읽고 머지하면 게시된다.
구조
- Cloudflare Worker 하나가 폼 페이지와 처리를 모두 맡는다. 프레임워크 없이 TypeScript로 HTML 문자열을 돌려준다.
- 흐름: 폼에 주제 md 붙여넣기(또는 파일) → Workers AI가 제목·slug·설명·태그·본문 생성 → GitHub API로 주제 md와 초안을 커밋 하나에 담아
draft/날짜-slug브랜치 생성 →main으로 PR 열기 - 커밋은 Git Data API(tree → commit → ref)로 만든다. 파일 두 개를 한 커밋에 넣기 위해서다.
- 초안은 frontmatter
draft: true로 만든다. 미리보기에서만 보이고, 게시할 때false로 바꿔 머지한다. - 같은 이름의 브랜치나 같은 slug의 글이 있으면 덮어쓰지 않고 멈춘다. 사람이 고친 글을 AI가 덮어쓰지 않게 하려는 것이다.
- 날짜는 한국 시간 기준이다. 자정을 넘겨 올렸더니 다음 날짜로 만들어졌다.
보안
- 폼은 HTTP Basic 인증으로 막았다. 비밀번호는
wrangler secret으로 둔다. - GitHub 토큰도
wrangler secret으로만 둔다. 저장소에는 없다. - 다른 사이트에서 폼으로 POST하는 요청은 Origin 헤더로 거른다.
Workers AI 연결
{
"ai": {
"binding": "AI"
}
}
wrangler.jsonc에"ai": { "binding": "AI" }한 줄이면 된다. 같은 Cloudflare 계정 안이라 API 키가 필요 없고, 코드에서env.AI.run(모델, { messages })로 부른다.- 모델은
@cf/openai/gpt-oss-120b. 이름은 120B지만 MoE 구조라 한 번에 움직이는 건 약 5B 규모다. 실행은 Cloudflare GPU에서 하므로 내 서버 부담은 없다. - 모델마다 응답 모양이 달라
response,choices[0].message.content등 여러 형태를 모두 받도록 했다. - 출력 형식은 JSON 대신
TITLE:,SLUG:같은 머리말 몇 줄 +---+ 본문으로 정했다. 긴 Markdown을 JSON 문자열에 넣으면 이스케이프가 자주 깨지기 때문이다.
말투 가이드
- 초안이 내 글처럼 보이도록 말투 가이드를 따로 만들었다. 내용은 공개하지 않는다.
- 대화(재료)는 구어체지만 글은 문어체라, 대화 말투를 따라 하지 않고 글쓰기 규칙만 정리했다.
- 가이드는 저장소의 md 파일로 두고, Worker가 초안을 만들 때마다 GitHub에서 읽어 시스템 프롬프트에 붙인다. 말투를 고칠 때 Worker를 다시 배포하지 않아도 된다.
처음 돌려 보며 막힌 것
GitHub POST git/trees 실패 (403): Resource not accessible by personal access token- 원인: fine-grained 토큰의 Contents 권한이 읽기 전용이었다. 읽기(브랜치 확인)는 되고 쓰기(커밋)에서 막혔다.
- 해결: 토큰 설정에서 Contents, Pull requests를 Read and write로 변경. 토큰 값은 그대로라 재설정이나 재배포가 필요 없었다.
- 아쉬운 점: AI 초안은 이미 생성된 뒤라 그 한 번은 버려졌다.
비용 측정
- 실패한 1회 호출의 사용량을 대시보드(AI → Workers AI)에서 확인했다: 163 neurons.
- 가격표 기준 gpt-oss-120b는 100만 토큰당 입력 31,818, 출력 68,182 neurons. 163 neurons면 대략 입력 3천, 출력 1천 토큰 규모다.
- 무료 한도는 하루 10,000 neurons라 하루 60편쯤 가능하다. 유료 단가로 환산하면 1편에 약 $0.002.
- 이후 초안 PR 설명에 입력·출력 토큰과 추정 neurons를 자동으로 적도록 바꿨다. 추정값은 응답의 토큰 수에 가격표 단가를 곱해 계산한다.
첫 초안 결과
- 말투 가이드는 대체로 잘 따랐다.
- 문제점
- 주제에 없던 예시 코드(
curl https://my-worker.workers.dev)를 지어냈다. 원본에 없는 내용은 쓰지 말라고 지시했는데도 나왔다. - 사건 순서를 뒤집어 썼다(“git pull 후 비어 있으면” → 실제로는 pull 전).
- 주제에 있던 일부 항목은 빠졌다.
- 주제에 없던 예시 코드(
- 그래도 첫 글은 고치지 않고 그대로 게시하기로 했다. 흐름 전체가 돈다는 걸 확인하는 게 우선이었다.
확인 단계 추가
- 처음에는 폼을 제출하면 곧바로 커밋하고 PR까지 열었다. slug는 폼에 직접 적거나, 비워 두면 AI가 정했다.
- slug는 글 주소(
/blog/<slug>/), 파일명, 브랜치 이름에 그대로 쓰인다. 게시 후에 바꾸면 주소가 바뀌어 링크와 검색 색인이 깨진다. - 실제로 이 글의 주제를 처음 올렸을 때 AI가 제목의 “(2편)“을 보고 slug를
workers-ai-blog-draft-pipeline-2로 지었다. 의도와 달랐다. - 그래서 폼을 두 단계로 바꿨다. 주제 md를 넣으면 초안이 제목·slug·설명·태그·본문 편집 칸에 채워진 확인 화면이 뜨고, 고친 뒤 “PR 만들기”를 눌러야 커밋된다.
- DB 없이 확인 화면의 숨은 칸에 주제 md와 사용량을 담아 다시 보낸다. AI 호출은 한 번이고, 화면을 닫으면 아무것도 커밋되지 않는다.
정리하고 싶은 포인트
- 폼 하나로 “주제 → AI 초안 → PR → 미리보기 → 머지 게시”가 되는 구조
- 말투 가이드를 코드가 아니라 저장소 파일로 둔 이유
- fine-grained 토큰 권한에서 놓치기 쉬운 점
- Workers AI 비용 감각 (neurons 단위, 무료 한도)
- AI 초안을 그대로 믿으면 안 되는 지점
- 커밋 전에 사람이 확인하는 단계를 둔 이유