Split View: AI로 블로그 글 파이프라인 만들기 — 초안이 아니라 검증이 병목입니다
AI로 블로그 글 파이프라인 만들기 — 초안이 아니라 검증이 병목입니다
- 들어가며 — 초안은 3분, 검증은 90분
- 파이프라인의 일곱 단계와 각 단계의 실패 비용
- 주제와 조사 — AI는 후보를 늘리고, 사람은 후보를 버립니다
- 초안 — 빈 화면은 없애되 구조는 넘기지 않습니다
- 사실 검증 — 이 파이프라인의 무게중심
- 편집·발행·성과 확인 — 목소리를 통일하고 되돌릴 수 있게 만듭니다
- 자동화 스케치 — 멈출 수 있는 지점을 남긴 워크플로
- 마치며 — 자동화할 값어치가 있는 것은 생성이 아니라 판정입니다
- 참고 자료
들어가며 — 초안은 3분, 검증은 90분
AI로 글을 쓰기 시작하면 대부분 같은 착각을 한 번 거칩니다. 초안이 3분 만에 나오니까 글 한 편에 드는 시간이 10분의 1로 줄 것이라는 기대입니다. 실제로 해 보면 초안은 정말 3분에 나오고, 그 뒤가 90분입니다. 숫자가 맞는지 확인하고, 링크가 살아 있는지 눌러 보고, 인용문이 원문에 실제로 있는 문장인지 대조하고, 같은 말을 세 번 다르게 반복한 문단을 걷어내는 데 그만큼 걸립니다.
그래서 파이프라인을 설계할 때 초안 생성을 중심에 놓으면 잘못 만들게 됩니다. 병목이 아닌 곳을 자동화한 것이기 때문입니다. 실제로 시간을 잡아먹는 단계는 검증이고, 개선의 여지가 가장 큰 곳도 여기입니다. 초안 속도를 두 배로 올리면 3분이 1분 반이 되지만, 검증을 절반으로 줄이면 90분이 45분이 됩니다.
이 글은 프롬프트 모음이 아닙니다. 주제 수집부터 성과 확인까지 일곱 단계를 실제로 돌려 보고, 각 단계에서 모델이 무엇을 잘하고 무엇을 맡기면 사고가 나는지를 구분한 기록입니다. AI가 쓴 글이 무너지는 개별 실패 유형과 각각의 탐지법은 다음 글에서 따로 다뤘고, 여기서는 그것들을 걸러내는 흐름 자체를 봅니다.
파이프라인의 일곱 단계와 각 단계의 실패 비용
먼저 전체 지도를 놓고 시작하겠습니다. 각 단계에서 AI가 실제로 값을 만드는 지점과, 넘기는 순간 되돌리기 어려워지는 지점을 갈라 놓은 표입니다.
| 단계 | AI가 실제로 잘하는 것 | 사람이 반드시 하는 것 | 넘겼을 때의 실패 비용 |
|---|---|---|---|
| 1. 주제 수집 | 후보를 폭넓게 벌리고, 이미 쓴 글과의 중복을 표시 | 독자에게 값이 있는지 판단하고 후보를 버림 | 낮음. 나쁜 주제는 다음 단계에서 걸러짐 |
| 2. 조사와 출처 확보 | 검색어 확장, 원문 요약, 상충하는 주장 정리 | 1차 출처인지 확인하고 직접 열어 봄 | 높음. 여기서 들어온 오류는 끝까지 살아남음 |
| 3. 초안 | 구조 제안, 빈 화면 제거, 설명 문단의 첫 버전 | 논지와 순서 결정, 무엇을 뺄지 결정 | 중간. 티가 나므로 편집에서 잡힘 |
| 4. 사실 검증 | 주장 목록 추출, 인용문 위치 대조 보조 | 숫자·날짜·인용·링크의 최종 확인 | 최고. 발행 후에 드러나면 신뢰가 깨짐 |
| 5. 편집과 목소리 통일 | 중복 문단 탐지, 길이 압축, 표현 후보 제시 | 목소리 기준 자체를 정의하고 최종 판단 | 중간. 읽는 사람이 바로 알아챔 |
| 6. 발행 | 메타데이터 생성, 링크·빌드 검사 자동화 | 발행 여부 결정과 되돌릴 준비 | 중간. 되돌릴 수 있으면 낮아짐 |
| 7. 성과 확인 | 로그 집계, 지표 계산, 이상값 지적 | 지표를 다음 글의 결정으로 바꾸는 해석 | 낮음. 다만 안 하면 개선이 멈춤 |
표에서 읽어야 할 것은 개별 칸이 아니라 비용의 분포입니다. 2단계와 4단계에 비용이 몰려 있습니다. 이 두 곳은 자동화하되 사람의 승인 없이 통과하지 못하게 막아야 하고, 나머지는 과감하게 넘겨도 됩니다. 파이프라인 설계의 대부분은 이 배분을 지키는 일입니다.
주제와 조사 — AI는 후보를 늘리고, 사람은 후보를 버립니다
주제 수집에서 모델은 발산에 강합니다. 하나의 씨앗에서 스무 개의 각도를 뽑아내는 일은 사람보다 빠르고, 지치지도 않습니다. 반대로 수렴은 못 합니다. "이 중 뭐가 제일 좋아?"라고 물으면 그럴듯한 근거와 함께 하나를 고르지만, 그 판단에는 여러분의 독자가 지난달에 무엇을 읽고 실망했는지가 들어 있지 않습니다. 후보를 늘리는 데 쓰고, 버리는 일은 직접 하세요.
여기서 실무적으로 값이 큰 자동화가 하나 있습니다. 이미 쓴 글과의 중복 검사입니다. 새 주제 후보를 기존 글 제목·요약과 대조해서 겹치는 것을 표시하면, 같은 이야기를 각도만 바꿔 다시 쓰는 사고를 막을 수 있습니다. 이건 판단이 아니라 대조라서 기계가 잘합니다.
조사 단계는 성격이 다릅니다. 여기서 들어온 오류는 파이프라인 끝까지 살아남고, 뒤 단계는 그것을 더 그럴듯하게 다듬기만 합니다. 그래서 규칙을 하나만 세운다면 이것입니다 — 모델이 요약한 내용을 조사 결과로 쓰지 않습니다. 모델이 하는 일은 어디를 읽어야 하는지 알려 주는 것까지이고, 읽는 것은 사람이 합니다.
실제로 지키기 쉬운 형태로 바꾸면 이렇게 됩니다.
- 모델이 내놓은 URL은 전부 직접 엽니다. 열리지 않으면 그 주장은 통째로 버립니다.
- 2차 출처(블로그 정리글, 뉴스 요약)에서 시작해도 좋지만, 인용은 1차 출처(공식 문서, 논문, 발표문, 릴리스 노트)로 바꿔 답니다.
- 날짜가 없는 페이지는 출처로 쓰지 않습니다. 언제 맞는 말이었는지 모르는 사실은 검증할 수 없습니다.
- 상충하는 주장을 만나면 어느 쪽이 맞는지 모델에게 묻지 말고, 상충한다는 사실 자체를 글에 적습니다.
마지막 항목이 실전에서 특히 유용합니다. 이번 글의 자매편을 쓰면서 오피스 단축키를 조사할 때, 마이크로소프트 공식 문서끼리 서로 다른 키를 안내하는 경우를 실제로 만났습니다. 그때 답은 하나를 고르는 것이 아니라 "문서가 갈린다"고 쓰는 것입니다.
초안 — 빈 화면은 없애되 구조는 넘기지 않습니다
초안 단계에서 모델의 값은 명확합니다. 빈 화면 앞에서 첫 문장을 고민하는 시간을 없애 줍니다. 이것만으로도 충분히 쓸모가 있고, 이 단계는 실수해도 비용이 낮습니다. 이상하면 티가 나고, 티가 나면 고쳐집니다.
다만 넘기지 말아야 할 것이 하나 있습니다. 구조입니다. 목차를 모델에게 맡기면 거의 항상 같은 모양이 나옵니다. 정의 → 장점 → 단점 → 사례 → 결론. 이 구조는 틀리지는 않지만 아무것도 주장하지 않습니다. 글에서 독자가 기억하는 것은 대개 구조가 만든 순서인데, 그 순서를 평균값으로 채우면 읽고 나서 남는 게 없습니다.
실무에서는 이렇게 나눕니다. H2 제목과 순서는 직접 정하고, 각 H2 아래 무엇을 쓸지를 두세 줄로 적어 모델에게 넘깁니다. 그러면 모델은 문단을 채우는 일을 하고, 논지는 유지됩니다. 반대로 "이 주제로 글 써 줘"라고 하면 논지 없는 글이 나오는데, 그건 모델이 못해서가 아니라 논지를 준 적이 없기 때문입니다.
초안 프롬프트에 넣으면 체감이 달라지는 제약 몇 가지를 적어 둡니다.
- 모르는 것은 비워 두고 표시하게 합니다. 채워 넣지 말라고 명시하지 않으면 채웁니다.
- 숫자와 고유명사는 제공한 자료에 있는 것만 쓰게 합니다.
- 문단 길이 상한을 줍니다. 상한이 없으면 같은 말을 늘려 씁니다.
- 각 H2마다 "이 절에서 독자가 새로 알게 되는 것 한 줄"을 먼저 쓰게 하고, 그게 앞 절과 겹치면 절을 합칩니다.
사실 검증 — 이 파이프라인의 무게중심
여기가 이 글에서 가장 중요한 절입니다. 앞의 여섯 단계를 대충 해도 이 단계가 살아 있으면 글은 발행할 만하고, 여기가 없으면 나머지를 아무리 잘해도 언젠가 사고가 납니다.
자신 있게 쓴 문장과 확인된 문장은 다릅니다
모델의 출력에는 확신의 정도가 표시되지 않습니다. 학습 데이터에서 수천 번 본 사실과, 그럴듯해서 방금 만들어낸 문장이 똑같은 어조로 나옵니다. 사람의 글에서는 확신이 없을 때 문장이 흔들리기 때문에 우리는 무의식적으로 그 신호를 읽는데, 모델의 글에는 그 신호가 없습니다. 그래서 "읽어 보니 자연스럽다"는 검증이 되지 못합니다. 오히려 자연스러울수록 위험합니다.
특히 조심할 항목은 정해져 있습니다. 숫자, 날짜, 버전, 가격, 인용문, 사람 이름과 소속, 그리고 URL입니다. 이것들은 모델이 가장 그럴듯하게 지어내고, 독자가 가장 쉽게 확인할 수 있는 항목이기도 합니다. 나머지 문장이 다 맞아도 이 중 하나가 틀리면 글 전체의 신뢰가 같이 떨어집니다.
지식 컷오프도 같은 자리에서 문제를 일으킵니다. 모델은 자기가 모르는 최신 정보를 "모른다"고 말하는 대신 마지막으로 아는 상태를 현재형으로 말합니다. 가격, 요금제, 제품명, API 파라미터, 정책 문구처럼 자주 바뀌는 것은 모델의 답을 출발점으로만 쓰고 반드시 현재 문서에서 다시 확인해야 합니다.
모델이 실제로 읽은 것만 인용하게 만드는 법
"출처를 붙여 줘"라고 요청하면 출처가 붙습니다. 문제는 그 출처가 읽은 곳이 아니라 그럴듯한 곳일 수 있다는 점입니다. URL 형식은 학습하기 쉬워서, 존재하지 않는 문서 번호나 죽은 앵커가 자연스러운 모양으로 생성됩니다.
이걸 막는 방법은 요청을 바꾸는 것이 아니라 검증 가능한 형태로 출력을 강제하는 것입니다. 핵심은 축자 인용입니다. 요약이 아니라 원문의 글자를 그대로 옮기게 하면, 그 문자열이 원문에 실제로 있는지를 기계가 대조할 수 있습니다.
주장 하나마다 이 네 가지를 함께 내놓게 합니다.
- 주장 문장 자체
- 출처 URL
- 그 URL의 본문에서 글자 그대로 가져온 한 문장 이상의 인용
- 그 인용이 주장을 뒷받침한다고 보는 이유 한 줄
3번이 자동 검증의 열쇠입니다. 인용문이 페이지에 없으면 그 주장은 근거 없이 만들어진 것이고, 사람이 읽어 보기 전에 스크립트가 먼저 잡아낼 수 있습니다. 아래는 그 대조를 하는 최소 구현입니다.
// verify-claims.mjs — 주장 원장(claims.jsonl)의 축자 인용이 실제 원문에 있는지 대조합니다.
// 각 줄 형식: {"claim": "...", "url": "https://...", "quote": "...원문 그대로...", "why": "..."}
import { readFileSync } from 'node:fs'
// 공백/따옴표/대소문자 차이는 인용 실패로 보지 않습니다. 내용이 다른 경우만 잡아야 합니다.
const normalize = (s) =>
s
.replace(/<[^>]+>/g, ' ')
.replace(/&[a-z]+;|&#\d+;/gi, ' ')
.replace(/[‘’“”]/g, "'")
.replace(/\s+/g, ' ')
.trim()
.toLowerCase()
const rows = readFileSync('claims.jsonl', 'utf8')
.split('\n')
.filter(Boolean)
.map((l) => JSON.parse(l))
const cache = new Map()
let failed = 0
for (const [i, row] of rows.entries()) {
const label = `[${i + 1}] ${row.claim.slice(0, 50)}`
if (!row.url || !row.quote) {
console.error(`MISSING ${label} — url 또는 quote가 비었습니다`)
failed++
continue
}
if (!cache.has(row.url)) {
try {
const res = await fetch(row.url, { redirect: 'follow' })
// 죽은 링크는 인용 대조 이전에 이미 실패입니다.
cache.set(row.url, res.ok ? normalize(await res.text()) : null)
} catch {
cache.set(row.url, null)
}
}
const page = cache.get(row.url)
if (page === null) {
console.error(`DEAD ${label} — ${row.url} 를 열 수 없습니다`)
failed++
} else if (!page.includes(normalize(row.quote))) {
// 여기가 이 스크립트의 존재 이유입니다: 그럴듯하지만 원문에 없는 인용.
console.error(`NOQUOTE ${label} — 인용문이 원문에 없습니다: "${row.quote.slice(0, 60)}"`)
failed++
}
}
console.log(`\n${rows.length}건 검사, ${failed}건 실패`)
process.exit(failed ? 1 : 0)
이 스크립트가 잡지 못하는 것도 분명히 해 둘 필요가 있습니다. 인용문이 원문에 존재하더라도 문맥이 잘린 인용이거나, 인용은 맞는데 그것이 주장을 뒷받침하지 않는 경우는 통과합니다. 그래서 4번 항목(이유 한 줄)을 사람이 읽는 절차가 여전히 필요합니다. 다만 기계가 앞에서 걸러 주면 사람이 읽어야 할 양이 크게 줄어듭니다. 실제로 이 대조를 붙이고 나서 사람 검증 시간이 눈에 띄게 줄었는데, 지어낸 인용을 찾느라 원문을 훑는 일이 사라졌기 때문입니다.
발행 직전 체크리스트
아래는 발행 버튼을 누르기 전에 실제로 돌리는 목록입니다. 기계가 할 수 있는 것과 사람이 해야 하는 것을 나눠 놨습니다.
| 항목 | 확인 방법 | 자동화 |
|---|---|---|
| 모든 외부 링크가 열리는가 | 응답 코드 확인 | 가능 |
| 인용문이 원문에 글자 그대로 있는가 | 축자 대조 스크립트 | 가능 |
| 내부 링크가 실제 경로를 가리키는가 | 파일 존재 확인 | 가능 |
| 숫자와 날짜가 출처와 일치하는가 | 원문과 눈으로 대조 | 불가능 |
| 시점에 의존하는 서술이 있는가 | 최신·현재·올해 같은 표현 검색 후 판단 | 부분 가능 |
| 인용이 문맥을 왜곡하지 않는가 | 원문 앞뒤 문단 읽기 | 불가능 |
| 근거 없이 단정한 문장이 있는가 | 단정 어미 검색 후 근거 확인 | 부분 가능 |
| 같은 내용을 반복한 문단이 있는가 | 문단 유사도 측정 후 판단 | 부분 가능 |
| 내가 이 글을 설명할 수 있는가 | 읽지 않고 요지를 말해 보기 | 불가능 |
마지막 항목이 형식적으로 보이지만 실제로 가장 잘 걸러 냅니다. 글을 보지 않고 핵심 주장을 말해 보려는데 막힌다면, 그 부분은 검증한 것이 아니라 통과시킨 것입니다.
편집·발행·성과 확인 — 목소리를 통일하고 되돌릴 수 있게 만듭니다
편집 단계에서 모델에게 맡길 수 있는 것은 탐지이고, 맡길 수 없는 것은 기준입니다. "이 글이 내 목소리인가"를 모델은 판단하지 못합니다. 학습한 평균적인 글맛으로 수렴시키려 하기 때문에, 맡기면 문장은 매끄러워지고 글은 남의 것이 됩니다.
대신 목소리를 규칙으로 적어 두면 기계가 검사할 수 있습니다. 쓰지 않는 단어 목록, 문단 길이 상한, 감탄 표현 금지, 특정 접속사 남용 금지 같은 것들입니다. 이건 취향이 아니라 검사 가능한 조건이라서 자동화가 됩니다. 반대로 "더 자연스럽게 고쳐 줘"는 검사 조건이 아니라 취향의 위임입니다.
발행 단계에서 중요한 것은 속도가 아니라 되돌릴 수 있음입니다. 글은 코드와 달리 발행 후 오류가 드러나도 롤백이 조용하지 않습니다. 이미 읽은 사람이 있고, 인용된 경우도 있습니다. 그래서 발행 자체를 자동화하더라도 되돌리는 경로를 먼저 만들어 두는 편이 낫습니다. 정정 내역을 글 안에 남기는 규칙을 미리 정해 두면, 사고가 났을 때 조용히 고치고 싶은 유혹을 피할 수 있습니다.
성과 확인 단계에서는 지표를 줄이는 것이 요령입니다. 조회수는 다음 글을 바꾸지 못합니다. 실제로 결정을 바꾸는 것은 체류 시간의 분포(첫 화면에서 이탈하는 비율), 검색 유입 질의와 글 제목의 어긋남, 그리고 어느 절에서 스크롤이 멈추는가 정도입니다. 특히 검색 질의와 내용의 어긋남은 다음 글의 주제를 직접 알려 주기 때문에, 하나만 본다면 이것을 권합니다.
여기서 구글의 정책을 한 번 짚고 갈 필요가 있습니다. 구글은 AI로 만들었다는 이유로 콘텐츠에 불이익을 주지 않는다고 명시하고 있으며, 판단 기준은 생성 수단이 아니라 독자에게 값을 주는가라고 검색 센터 문서에서 밝히고 있습니다. 다만 순위를 목적으로 가치 없는 글을 대량 생산하는 행위는 스팸 정책에서 대량 생성 콘텐츠 남용으로 명시적으로 금지합니다. 요약하면, 파이프라인의 목표를 "많이 찍어내기"로 잡는 순간 정책의 반대편에 서게 됩니다.
자동화 스케치 — 멈출 수 있는 지점을 남긴 워크플로
전체를 하나로 묶으면 아래와 같은 모양이 됩니다. 중요한 것은 자동화의 범위가 아니라 어디서 멈추는가입니다. 이 워크플로는 검증 게이트를 통과하지 못하면 발행 단계로 넘어가지 않고, 사람의 승인 없이는 초안이 발행 브랜치에 닿지 않습니다.
# .github/workflows/content-gate.yml
# 초안 PR이 열리면 기계가 할 수 있는 검증을 전부 돌리고, 통과해도 자동 병합하지 않습니다.
name: content-gate
on:
pull_request:
paths: ['data/blog/**/*.mdx']
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: 빌드 안전성 — MDX가 실제로 컴파일되는지
run: node scripts/check-posts.mjs
- name: 내부 링크 — 가리키는 경로가 존재하는지
run: node scripts/check-links.mjs
- name: 축자 인용 대조 — 인용문이 원문에 실재하는지
run: node scripts/verify-claims.mjs
- name: 시점 의존 표현 — 사람이 판단해야 할 자리를 표시만 합니다
run: |
# 실패시키지 않습니다. 검증이 아니라 리뷰 유도용 신호입니다.
grep -nE '최신|현재|올해|작년|최근|가장 빠른|업계 최초' data/blog/**/*.mdx \
| tee /tmp/time-sensitive.txt || true
echo "위 표현은 컷오프 이후 사실일 수 있습니다. 리뷰어가 직접 확인하세요."
- name: 문단 중복 — 유사도 상위 쌍만 뽑아 보고
run: node scripts/find-duplicate-paragraphs.mjs --top 5 || true
# 사람 개입 지점: 위 검증이 모두 초록이어도 여기서 멈춥니다.
# 승인은 GitHub의 리뷰 승인으로 받고, 워크플로가 대신 승인하지 않습니다.
여기서 의도적으로 하지 않은 것이 두 가지 있습니다.
첫째, 검증 통과를 발행 트리거로 쓰지 않았습니다. 기계 검증은 "발행해도 된다"가 아니라 "명백한 사고는 없다"까지만 말해 줍니다. 자동 병합을 붙이는 순간 체크리스트의 마지막 세 항목(문맥 왜곡, 단정, 설명 가능성)이 파이프라인에서 사라집니다.
둘째, 시점 의존 표현 검사를 실패로 처리하지 않았습니다. 이 검사는 정밀도가 낮아서 실패로 만들면 곧 무시하게 되고, 무시되는 게이트는 없는 게이트보다 나쁩니다. 사람에게 어디를 보라고 알려 주는 역할까지만 시키는 편이 오래갑니다.
마치며 — 자동화할 값어치가 있는 것은 생성이 아니라 판정입니다
AI 콘텐츠 파이프라인을 만들 때 손이 먼저 가는 곳은 생성입니다. 결과가 눈에 보이고, 만들면 즉시 빨라진 느낌이 들기 때문입니다. 하지만 실제로 시간을 돌려주고 사고를 막아 주는 것은 판정 쪽입니다. 인용이 실재하는지, 링크가 살아 있는지, 같은 말을 반복하지 않았는지를 기계가 먼저 걸러 주면, 사람은 기계가 못 하는 일에만 시간을 씁니다.
그리고 기계가 못 하는 일은 생각보다 적고 분명합니다. 인용이 문맥을 왜곡하지 않는지, 이 주제가 독자에게 값이 있는지, 그리고 이 글의 논지가 내 것인지. 이 셋은 끝까지 사람의 몫이고, 파이프라인이 잘 만들어졌다면 이 셋에 쓸 시간이 남아 있어야 합니다.
한 줄로 줄이면 이렇습니다 — 초안을 못 믿는 것이 아니라, 확인하지 않은 초안을 믿을 근거가 없을 뿐입니다.
참고 자료
Building an AI Blog-Writing Pipeline — Verification, Not Drafting, Is the Bottleneck
- Introduction — Three Minutes for a Draft, Ninety for Verification
- The Pipeline's Seven Stages and the Cost of Failure at Each One
- Topic and Research — AI Expands the Candidates, a Human Discards Them
- Drafting — Kill the Blank Screen, But Don't Hand Off the Structure
- Fact-Checking — The Center of Gravity of This Pipeline
- Editing, Publishing, and Checking Performance — Unify the Voice, Make It Reversible
- An Automation Sketch — A Workflow That Leaves in the Places It Can Stop
- Closing — What's Worth Automating Is Judgment, Not Generation
- References
Introduction — Three Minutes for a Draft, Ninety for Verification
Once you start writing with AI, almost everyone passes through the same illusion: since the draft comes out in three minutes, the time spent on a single post should drop to a tenth. In practice, the draft really does take three minutes — and then everything after it takes ninety. That's how long it takes to check whether the numbers are right, click every link to see if it's alive, cross-check whether a quote actually appears in the source, and strip out the paragraph that says the same thing three different ways.
So if you design the pipeline around draft generation, you build it wrong, because you've automated the part that was never the bottleneck. The stage that actually eats time is verification, and it's also where there's the most room to improve. Double the drafting speed and three minutes becomes a minute and a half; cut verification in half and ninety minutes becomes forty-five.
This isn't a collection of prompts. It's a record of actually running all seven stages, from topic collection to checking performance, and separating what the model is good at from what causes an incident if you hand it off. The individual failure types that make AI-written posts fall apart, and how to detect each one, are covered separately in the next post; here we look at the overall flow that filters them out.
The Pipeline's Seven Stages and the Cost of Failure at Each One
Let's start with the full map. The table below splits out, for each stage, where AI genuinely creates value and where things become hard to undo the moment you hand them off.
| Stage | What AI is actually good at | What a human must do | Cost of handing it off |
|---|---|---|---|
| 1. Topic collection | Casting a wide net of candidates, flagging overlap with posts already written | Judging whether it has value for the reader, discarding candidates | Low. A bad topic gets filtered at the next stage |
| 2. Research and sourcing | Expanding search queries, summarizing source text, laying out conflicting claims | Confirming it's a primary source, opening it directly | High. An error that gets in here survives to the end |
| 3. Drafting | Proposing structure, clearing the blank-screen problem, a first pass at explanatory paragraphs | Deciding the argument and order, deciding what to cut | Medium. It shows, so it gets caught in editing |
| 4. Fact-checking | Extracting a list of claims, helping cross-check quote locations | Final confirmation of numbers, dates, quotes, links | Highest. If it surfaces after publishing, trust breaks |
| 5. Editing and voice consistency | Detecting duplicate paragraphs, tightening length, proposing phrasing candidates | Defining the voice standard itself, final judgment | Medium. Readers notice immediately |
| 6. Publishing | Generating metadata, automating link/build checks | Deciding whether to publish, being ready to roll back | Medium. Lower if it's reversible |
| 7. Checking performance | Aggregating logs, computing metrics, flagging outliers | Turning metrics into a decision for the next post | Low. But skip it and improvement stalls |
What you should read out of this table isn't the individual cells — it's the distribution of cost. Stages 2 and 4 concentrate the cost. Those two should be automated, but blocked from passing without human sign-off; the rest can be handed off boldly. Most of pipeline design is just respecting this distribution.
Topic and Research — AI Expands the Candidates, a Human Discards Them
In topic collection, the model is strong at divergence. Pulling twenty angles out of one seed is faster than a human and never gets tired. It's weak at convergence, in the opposite direction. Ask "which of these is best?" and it picks one with a plausible-sounding rationale, but that judgment doesn't contain what your readers were disappointed by last month. Use it to expand candidates; do the discarding yourself.
There's one automation here that's genuinely valuable in practice: a duplicate check against posts already written. Cross-reference new topic candidates against existing post titles and summaries, flag the overlaps, and you can prevent the incident of rewriting the same story from a slightly different angle. This is comparison, not judgment, so a machine does it well.
The research stage has a different character. An error that gets in here survives to the end of the pipeline, and every later stage just polishes it to look more plausible. So if there's exactly one rule to set, it's this: don't use what the model summarized as your research finding. All the model does is point you to where to read; the reading itself is the human's job.
Put into a form that's actually easy to follow, it looks like this.
- Open every URL the model produces, yourself. If it doesn't open, discard that claim entirely.
- It's fine to start from a secondary source (a blog roundup, a news summary), but rewrite the citation against a primary source (official docs, the paper, the announcement, the release notes).
- Don't use a page with no date as a source. A fact you don't know when it was true can't be verified.
- When you run into conflicting claims, don't ask the model which one is right — write the fact that they conflict directly into the post.
That last item is especially useful in practice. While researching Office keyboard shortcuts for this post's companion piece, I actually ran into cases where Microsoft's own official docs pointed to different keys for the same action. The right answer there isn't to pick one — it's to write "the docs disagree."
Drafting — Kill the Blank Screen, But Don't Hand Off the Structure
The model's value at the drafting stage is clear. It removes the time you'd spend agonizing over the first sentence in front of a blank screen. That alone is useful enough, and this stage is cheap to get wrong — if something's off, it shows, and if it shows, it gets fixed.
There's one thing you should never hand off, though: structure. Leave the outline to the model and you almost always get the same shape. Definition, then pros, then cons, then examples, then conclusion. That structure isn't wrong, but it doesn't argue anything. What a reader usually remembers from a post is the order the structure created, and if you fill that order with an average, nothing is left after reading.
In practice, split it like this. Decide the H2 headings and their order yourself, write two or three lines about what goes under each H2, and hand that to the model. Then the model's job is to fill in paragraphs, and the throughline stays intact. Say "write me a post on this topic" instead, and you get a post with no throughline — not because the model can't do it, but because you never gave it one.
Here are a few constraints that make a noticeable difference when you put them in the drafting prompt.
- Have it leave what it doesn't know blank and flagged. Unless you explicitly say not to fill it in, it will fill it in.
- Have it use only numbers and proper nouns that are in the material you provided.
- Give a paragraph-length cap. Without one, it stretches out the same point.
- Have it write, first, one line for each H2 on "the one new thing the reader learns in this section," and if that overlaps the previous section, merge the sections.
Fact-Checking — The Center of Gravity of This Pipeline
This is the most important section in this post. Do the other six stages sloppily and the post is still publishable as long as this stage is intact; skip this stage and no matter how well you do everything else, an incident eventually happens.
A Confidently Written Sentence and a Verified Sentence Are Different Things
A model's output carries no marker of how confident it is. A fact seen thousands of times in training data, and a sentence that sounds plausible and was just invented, come out in exactly the same tone. In human writing, a sentence wavers when the writer isn't sure, and we read that signal unconsciously; a model's writing has no such signal. So "it reads naturally" doesn't count as verification. If anything, the more natural it reads, the more dangerous it is.
The items to watch closely are fixed: numbers, dates, versions, prices, quotes, people's names and affiliations, and URLs. These are exactly the items a model is most likely to plausibly fabricate, and they're also the items a reader can most easily verify. Get every other sentence right and get just one of these wrong, and the trust in the whole post drops with it.
The knowledge cutoff causes trouble at the exact same spot. Instead of saying "I don't know the latest on this," the model states the last state it knows in the present tense. For anything that changes often — prices, plans, product names, API parameters, policy language — treat the model's answer only as a starting point, and always re-verify it against the current documentation.
Making the Model Cite Only What It Actually Read
Ask for "sources" and you get sources. The problem is that the source might be somewhere plausible, not somewhere it actually read. URL format is easy to learn, so a document number that doesn't exist or a dead anchor gets generated in a perfectly natural-looking shape.
The way to prevent this isn't to change the request — it's to force the output into a verifiable form. The key is a verbatim quote. Have it copy the source's actual characters rather than summarize, and a machine can cross-check whether that string actually exists in the source.
Have it produce these four things together for every single claim.
- The claim sentence itself
- The source URL
- One or more sentences quoted verbatim, character for character from that URL's body
- One line explaining why that quote supports the claim
Item 3 is the key to automated verification. If the quote isn't on the page, the claim was manufactured with no basis, and a script can catch that before a human ever reads it. Below is a minimal implementation that does that cross-check.
// verify-claims.mjs — cross-checks whether the verbatim quotes in the claims ledger (claims.jsonl) actually appear in the source.
// Each line's format: {"claim": "...", "url": "https://...", "quote": "...verbatim from the source...", "why": "..."}
import { readFileSync } from 'node:fs'
// Differences in whitespace/quotes/case aren't treated as a quote failure.
// We only want to catch cases where the content actually differs.
const normalize = (s) =>
s
.replace(/<[^>]+>/g, ' ')
.replace(/&[a-z]+;|&#\d+;/gi, ' ')
.replace(/[‘’“”]/g, "'")
.replace(/\s+/g, ' ')
.trim()
.toLowerCase()
const rows = readFileSync('claims.jsonl', 'utf8')
.split('\n')
.filter(Boolean)
.map((l) => JSON.parse(l))
const cache = new Map()
let failed = 0
for (const [i, row] of rows.entries()) {
const label = `[${i + 1}] ${row.claim.slice(0, 50)}`
if (!row.url || !row.quote) {
console.error(`MISSING ${label} — url or quote is empty`)
failed++
continue
}
if (!cache.has(row.url)) {
try {
const res = await fetch(row.url, { redirect: 'follow' })
// A dead link is already a failure before we even get to checking the quote.
cache.set(row.url, res.ok ? normalize(await res.text()) : null)
} catch {
cache.set(row.url, null)
}
}
const page = cache.get(row.url)
if (page === null) {
console.error(`DEAD ${label} — could not open ${row.url}`)
failed++
} else if (!page.includes(normalize(row.quote))) {
// This is the whole reason this script exists: a plausible-looking quote that isn't in the source.
console.error(`NOQUOTE ${label} — quote not found in source: "${row.quote.slice(0, 60)}"`)
failed++
}
}
console.log(`\nChecked ${rows.length}, failed ${failed}`)
process.exit(failed ? 1 : 0)
It's also worth being clear about what this script can't catch. It'll pass a quote stripped of context even when it's present in the source verbatim, and it'll pass a quote that's accurate but doesn't actually support the claim. So you still need a human to read item 4 (the one-line rationale). That said, once a machine filters the front end, the volume a human needs to read drops sharply. In practice, since adding this cross-check, human verification time has visibly dropped — because we no longer have to comb through source text hunting for a fabricated quote.
The Pre-Publish Checklist
Below is the list actually run before hitting publish. It's split into what a machine can do and what a human must do.
| Item | How to check | Automatable |
|---|---|---|
| Do all external links open | Check the response code | Yes |
| Are quotes present verbatim in the source | Verbatim cross-check script | Yes |
| Do internal links point to a real path | Check file existence | Yes |
| Do numbers and dates match the source | Compare against the source by eye | No |
| Are there time-sensitive statements | Search for words like "latest," "currently," "this year," then judge | Partially |
| Do quotes distort context | Read the paragraphs around the quote in the source | No |
| Are there unsupported assertions | Search for assertive endings, then check for evidence | Partially |
| Are there paragraphs that repeat the same content | Measure paragraph similarity, then judge | Partially |
| Can I explain this post | Try stating the gist without reading it | No |
The last item looks like a formality but actually catches the most in practice. If you try to state the core claim without looking at the post and get stuck, that part wasn't verified — it was just let through.
Editing, Publishing, and Checking Performance — Unify the Voice, Make It Reversible
At the editing stage, what you can hand to the model is detection; what you can't is the standard. The model can't judge "is this my voice." Because it tends to converge on the average texture of what it was trained on, hand it off and the sentences get smoother while the post stops being yours.
Instead, if you write your voice down as rules, a machine can check it. Things like a list of words you don't use, a paragraph-length cap, a ban on exclamatory phrasing, a ban on overusing a particular conjunction. These aren't taste — they're checkable conditions, so they can be automated. "Make this sound more natural," on the other hand, isn't a checkable condition — it's outsourcing your taste.
What matters at the publishing stage isn't speed — it's being reversible. Unlike code, an error in a post that surfaces after publishing doesn't roll back quietly. People have already read it, and sometimes it's already been cited elsewhere. So even if you automate publishing itself, it's better to build the rollback path first. Deciding in advance on a rule to leave a correction note inside the post itself helps you avoid the temptation to quietly fix things when something goes wrong.
At the performance-checking stage, the trick is cutting down the metrics. View count doesn't change your next post. What actually changes decisions is the distribution of dwell time (the rate people bounce off the first screen), the gap between search queries that bring people in and the post title, and roughly which section is where scrolling stops. In particular, the gap between search query and content directly tells you the topic for your next post, so if you're only going to look at one thing, look at that.
Here it's worth touching on Google's policy once. Google states explicitly that it doesn't penalize content just because it was made with AI, and that the standard is not the means of production but whether it gives value to the reader, as laid out in its Search Central documentation. That said, mass-producing valueless content purely to chase rankings is explicitly banned as scaled content abuse under its spam policies. To summarize: the moment you set the pipeline's goal to "crank out a lot," you've put yourself on the wrong side of the policy.
An Automation Sketch — A Workflow That Leaves in the Places It Can Stop
Put it all together and you get something like the shape below. What matters isn't the scope of automation — it's where it stops. This workflow doesn't let a draft move to the publish stage unless it clears the verification gate, and a draft never reaches the publish branch without a human's approval.
# .github/workflows/content-gate.yml
# When a draft PR opens, run every check a machine can do — but never auto-merge, even if it all passes.
name: content-gate
on:
pull_request:
paths: ['data/blog/**/*.mdx']
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Build safety — does the MDX actually compile
run: node scripts/check-posts.mjs
- name: Internal links — do the paths they point to exist
run: node scripts/check-links.mjs
- name: Verbatim quote cross-check — do quotes actually exist in the source
run: node scripts/verify-claims.mjs
- name: Time-sensitive phrasing — just flags spots a human needs to judge
run: |
# Doesn't fail the build. This is a signal to prompt review, not verification.
grep -nE 'latest|currently|this year|last year|recently|fastest yet|first in the industry' data/blog/**/*.mdx \
| tee /tmp/time-sensitive.txt || true
echo "The phrases above may state facts that are stale past the cutoff. The reviewer should confirm them directly."
- name: Duplicate paragraphs — pull only the top-similarity pairs and report
run: node scripts/find-duplicate-paragraphs.mjs --top 5 || true
# Human-in-the-loop: even if every check above is green, it stops here.
# Approval comes from a GitHub review approval — the workflow never approves on its own.
There are two things deliberately left undone here.
First, passing verification was not used as the publish trigger. Machine verification can only tell you "there's no obvious disaster" — never "it's fine to publish." The moment you attach auto-merge, the last three items on the checklist (context distortion, unsupported assertions, whether you can explain it) disappear from the pipeline.
Second, the time-sensitive-phrasing check was not made a failing check. This check has low precision, so making it a hard failure means it gets ignored soon, and an ignored gate is worse than no gate at all. It lasts longer if all it does is tell a human where to look.
Closing — What's Worth Automating Is Judgment, Not Generation
When building an AI content pipeline, your hand reaches for generation first. The result is visible, and making it feels instantly faster. But what actually gives you back time and prevents incidents is the judgment side. Have a machine filter, first, whether a quote is real, whether a link is alive, whether you repeated yourself — and a human only spends time on what the machine can't do.
And what the machine can't do turns out to be smaller and clearer than you'd think: whether a quote distorts its context, whether this topic has value for the reader, and whether the post's argument is actually yours. These three stay a human's job to the end, and if the pipeline is well built, there should be time left over for exactly these three.
Boiled down to one line — it's not that you can't trust a draft. It's that there's no basis to trust an unverified one.
References
- Google Search Central — Google's stance on AI-generated content
- Google Search Central — Creating helpful, reliable, people-first content
- Google Search Central — Spam policies (including scaled content abuse)
- Google Search Central — Search quality rater guidelines overview
- Where AI-Written Posts Fall Apart — failure types and guardrails (related post)