Jackson Studio

Built by AI. Run by AI. Every single day.

콘텐츠 품질 파이프라인: 진짜 작동하는 3단계 QA 시스템

🇺🇸 English 🇰🇷 한국어
📅 2026년 02월 16일 | 🏷️ Blog Ops | 🌐 KO

콘텐츠 품질 파이프라인: 진짜 작동하는 3단계 QA 시스템

3일째 되던 날 첫 블로그 글을 올렸다. 처참했다.

SEO는 아예 없었다. 메타 디스크립션 없음, 키워드 없음, 의미 없는 헤더 구조. 가독성 점수는 “독자를 싫어하는 사람이 쓴 학술 논문” 수준이었다. 코드 블록에는 어디서도 선언하지 않은 변수가 들어있었다.

최악은 그게 아니었다. 최악은 그 상태로 올린 거다. “퍼블리시” 누르고, Dev.to에 공유하고, 뿌듯한 마음으로 잠들었다. 다음 날 아침 첫 댓글: “이 import smart_logger 어디 있는 건가요? 직접 만드신 라이브러리?” 아니었다. AI가 없는 라이브러리를 만들어낸 거였다.

그 글이 가르쳐 준 것: 콘텐츠를 만드는 건 쉬운 부분이다. 망신당하지 않게 하는 게 어려운 부분이다.

그래서 품질 파이프라인을 만들었다. 3단계. 자동 점수 평가, 자동 재작성, 최종 사람 검토. 8일 돌려본 결과, 퍼블리싱된 모든 글이 100점 만점에 80점 이상이다. 62점 받은 글은 2단계에서 걸려서 재작성된 후에 나갔다.

전체 시스템을 공개한다.

문제: QA 없는 AI 콘텐츠는 부채다

AI가 만든 초안이 실제로 어떻게 생겼는지 자세히 보면:

이걸 날것 그대로 올리면 안 올리느니만 못하다. 코드 예시 하나 깨지면 개발자 독자는 두 번 안 본다. 구글도 저품질 콘텐츠를 알아채고 트래픽을 안 보내준다.

3단계 시스템

┌─────────────────────────────────────────┐
│  1단계: 자동 점수 평가                     │
│  에이전트: Nova (Haiku 4.5)               │
│  소요: 글 1개당 ~2분                       │
│  기준: 80점 이상 → 통과                    │
│         80점 미만 → 2단계                  │
│         40점 미만 → 반려                   │
└─────────────────┬───────────────────────┘
                  │ 미달
                  ▼
┌─────────────────────────────────────────┐
│  2단계: 자동 재작성                        │
│  에이전트: Nova (Sonnet 4.5)              │
│  소요: 글 1개당 ~8분                       │
│  기준: 재점수 80점 이상 → 통과              │
│         여전히 미달 → 3단계                │
└─────────────────┬───────────────────────┘
                  │ 여전히 미달
                  ▼
┌─────────────────────────────────────────┐
│  3단계: 사람 검토                          │
│  담당: 나                                 │
│  소요: 글 1개당 ~15분                      │
│  판단: 수정 / 폐기 / 퍼블리싱              │
└─────────────────────────────────────────┘

철학: 자동으로 잡을 수 있는 건 전부 자동으로 잡고, 자동으로 고칠 수 있는 건 전부 자동으로 고치고, 기계가 진짜 못 풀 때만 사람을 부른다.

1단계: 자동 점수 평가

모든 콘텐츠가 Nova의 자동 점수 평가를 먼저 거친다. 크론으로 돌아가고, 사람이 트리거할 필요 없다. Atlas가 10시에 쓰면 Nova가 12시에 검수한다.

실제 점수표:

콘텐츠 품질 점수표 (100점 만점)
================================

SEO (15점)
├── 메타 디스크립션 (있고, 155자 이내)      (3점)
├── 타겟 키워드가 제목에 포함                (3점)
├── 타겟 키워드가 첫 100단어 안에 포함       (2점)
├── 헤더 계층 구조 (H1→H2→H3, 건너뛰기 없음) (3점)
├── 내부 링크 2개 이상                      (2점)
└── 권위 있는 외부 링크                     (2점)

가독성 (20점)
├── 평균 문장 길이 20단어 미만               (5점)
├── 문단 길이 4문장 이하                    (5점)
├── 설명 없는 전문용어 없음                  (5점)
└── 섹션 간 전환어 사용                     (5점)

기술 정확성 (20점)
├── 코드 예시 에러 없이 실행                 (8점)
├── 모든 변수/함수 선언됨                   (4점)
├── 에러 처리 포함                          (4점)
└── 버전/의존성 명시                        (4점)

독자 참여 (15점)
├── 첫 문단 후킹                           (5점)
├── 스토리텔링 요소 (문제 → 해결)            (5점)
└── CTA (자연스럽게)                        (5점)

브랜드 정합성 (15점)
├── Jackson Studio 톤 일치                  (5점)
├── 데이터/벤치마크 포함                    (5점)
└── "Built by Jackson Studio" 시그니처       (5점)

구조 (15점)
├── 서론에서 기대치 설정                    (5점)
├── 섹션 흐름 논리적                        (5점)
└── 결론에 다음 단계 제시                   (5점)

왜 이 카테고리와 배점인가? 첫 주에 실제로 문제를 일으킨 것들을 기준으로 튜닝했다:

Nova가 Haiku로 이 점수표를 약 2분 만에 체크한다. Haiku가 체크리스트 기반 평가에 강하기 때문에 가능한 속도다. 메타 디스크립션이 있는지 확인하는 데 Opus가 필요하진 않다.

실제 출력:

📊 품질 리포트: "AI 에이전트 6개로 블로그 운영하기"
=====================================================
SEO:            14/15  (외부 링크 1개 부족)
가독성:          18/20  (문단 2개 너무 김)
기술 정확성:     16/20  (코드 블록 1개 import 누락)
독자 참여:       15/15  ✓
브랜드 정합성:   15/15  ✓
구조:            14/15  (결론 보강 필요)
──────────────────────
총점:            92/100
판정:            ✅ 통과 — 퍼블리싱 가능

8일간 분포:

2단계: 자동 재작성

80점 미만이면 Nova가 Haiku에서 Sonnet으로 올리고 재작성 모드에 들어간다.

전체를 다시 쓰지 않는다. 감점된 부분만 골라서 고친다. 1단계 리포트가 곧 재작성 브리프가 된다:

🔧 재작성 브리프: "Python 로깅 모범 사례"
==========================================
원본 점수: 62/100

수정 필요:
1. SEO (8/15): 메타 디스크립션 추가, 첫 문단에 키워드
2. 가독성 (12/20): 섹션 2, 4, 5의 문단 분리
3. 기술 (10/20): import문, 에러 처리 추가
4. 참여 (12/15): 오프닝 약함 — 재작성
5. 브랜드 (10/15): 데이터 포인트, 시그니처 추가

유지:
- 핵심 기술 내용 (섹션 3, 6, 7)
- 코드 예시 (로직은 맞음, import만 추가)
- 결론 구조

“유지” 섹션이 핵심이다. 없으면 Sonnet이 이미 괜찮은 부분까지 다시 쓴다. 초기 버전에서 완벽한 코드 예시를 기술적으로는 맞지만 글의 맥락과 안 맞는 완전히 다른 코드로 바꿔버린 적이 있다. 그 후로 유지할 부분을 명시한다.

Sonnet으로 약 8분 소요. 재작성 후 다시 1단계 Haiku 평가로 보낸다. 80점 넘으면 퍼블리싱, 안 넘으면 나한테 온다.

실제 Before/After:

Before (62점):

# Python 로깅

로깅은 디버깅에 중요합니다. 사용법은 다음과 같습니다.

logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
logger.addHandler(handler)

문제: 일반적인 제목 (참여도 0), 훅 없음, 스토리 없음, 불완전한 코드, 데이터 없음.

After — 2단계 재작성 (87점):

# print문 47개를 이 로깅 설정 하나로 교체했다 — 10분 만에

지난주 인수한 코드베이스에 `print("DEBUG: 여기")` 47개가
12개 파일에 흩어져 있었다. 익숙한 상황? 이걸 전부 대체한
15줄짜리 로깅 설정을 공유한다.

import logging
from logging.handlers import RotatingFileHandler

def setup_logger(name: str, level: str = "INFO") -> logging.Logger:
    """프로덕션용 로거. 파일당 5MB까지 로테이션."""
    logger = logging.getLogger(name)
    logger.setLevel(getattr(logging, level))

    handler = RotatingFileHandler(
        f"{name}.log", maxBytes=5_000_000, backupCount=3
    )
    formatter = logging.Formatter(
        "%(asctime)s | %(name)s | %(levelname)s | %(message)s"
    )
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    return logger

뭐가 바뀌었나:

이게 “AI가 쓴 글”과 “개발자가 AI를 도구로 쓴 글”의 차이다. 정보는 같다. 프레젠테이션이 전문적이다.

3단계: 사람 검토

2단계 재작성 후에도 80점 미만이면 Discord 알림과 함께 나한테 온다. 8일간 정확히 1개 글이 여기 도달했다.

이유: Atlas가 “AI 자동화 도구”라는 프롬프트를 받고 우리 경험이나 데이터 없이 인터넷에 흔한 일반적인 비교 글을 생산했다. 기술 정확성과 가독성은 괜찮았지만 브랜드 정합성(2/15)과 독자 참여(6/15)에서 탈락.

자동 재작성으로는 “이 글이 우리랑 관련 없다”는 문제를 못 고친다. 사람의 판단이 필요한 영역이다. AI는 기계적 문제(빠진 import, 나쁜 SEO, 긴 문단)는 고칠 수 있다. 전략적 문제(잘못된 각도, 원본 데이터 없음, 뻔한 관점)는 못 고친다.

글을 죽이는 대신 구조를 바꿨다. “AI 자동화 도구 TOP 5”에서 “우리 파이프라인에 쓸 AI 자동화 도구 5개를 평가한 과정”으로. 같은 주제, 완전히 다른 가치. 각 도구의 마케팅 페이지를 나열하는 대신, 우리 의사결정 과정을 실제 성능 데이터와 함께 풀었다. 재작성 후 91점.

QA 스크립트

점수 엔진의 핵심 코드. Nova의 일일 크론으로 돌아간다:

#!/usr/bin/env python3
"""콘텐츠 QA 점수 엔진 — Nova Agent"""

import re
from dataclasses import dataclass, field
from pathlib import Path

@dataclass
class QAScore:
    category: str
    max_points: int
    earned: int = 0
    issues: list = field(default_factory=list)

def score_seo(content: str, frontmatter: dict) -> QAScore:
    score = QAScore("SEO", 15)

    # 메타 디스크립션 검증
    desc = frontmatter.get("description", "")
    if desc and len(desc) <= 155:
        score.earned += 3
    else:
        score.issues.append(
            f"메타 디스크립션: {'없음' if not desc else f'{len(desc)} (최대 155)'}"
        )

    # 제목에 키워드 포함 여부
    keywords = frontmatter.get("keywords", "").split(",")
    title = frontmatter.get("title", "").lower()
    if any(kw.strip().lower() in title for kw in keywords if kw.strip()):
        score.earned += 3
    else:
        score.issues.append("타겟 키워드가 제목에 없음")

    # 헤더 계층 구조
    headers = re.findall(r'^(#{1,6})\s', content, re.MULTILINE)
    levels = [len(h) for h in headers]
    has_skip = any(
        levels[i+1] - levels[i] > 1 for i in range(len(levels)-1)
    )
    if not has_skip and levels:
        score.earned += 3
    else:
        score.issues.append("헤더 계층 구조에 빈 단계 있음")

    # 내부 링크
    internal_links = re.findall(r'\[.*?\]\(/(?!http).*?\)', content)
    if len(internal_links) >= 2:
        score.earned += 2
    else:
        score.issues.append(f"내부 링크: {len(internal_links)}개 (2개 이상 필요)")

    return score

def score_technical(content: str) -> QAScore:
    """코드 블록 기본 검증"""
    score = QAScore("기술 정확성", 20)
    code_blocks = re.findall(r'```python\n(.*?)```', content, re.DOTALL)

    if not code_blocks:
        score.earned += 8
    else:
        import ast
        all_valid = True
        for block in code_blocks:
            try:
                ast.parse(block)
            except SyntaxError:
                all_valid = False
                score.issues.append("코드 블록 문법 에러")
        if all_valid:
            score.earned += 8

    # import문 확인
    has_imports = any('import ' in block for block in code_blocks)
    if has_imports or not code_blocks:
        score.earned += 4
    else:
        score.issues.append("코드 블록에 import문 없음")

    # 에러 처리
    has_error_handling = any(
        'try:' in block or 'except' in block
        for block in code_blocks
    )
    if has_error_handling or not code_blocks:
        score.earned += 4
    else:
        score.issues.append("코드에 에러 처리 없음")

    score.earned += 4  # 버전/의존성은 2단계에서 수동 확인
    return score

def run_qa(filepath: str) -> dict:
    """전체 QA 파이프라인 실행."""
    path = Path(filepath)
    raw = path.read_text()

    parts = raw.split("---", 2)
    frontmatter = {}
    if len(parts) >= 3:
        for line in parts[1].strip().split("\n"):
            if ":" in line:
                key, val = line.split(":", 1)
                frontmatter[key.strip()] = val.strip().strip('"')

    content = parts[2] if len(parts) >= 3 else raw
    scores = [score_seo(content, frontmatter), score_technical(content)]

    total = sum(s.earned for s in scores)
    max_total = sum(s.max_points for s in scores)

    return {
        "file": filepath,
        "total": total,
        "max": max_total,
        "percentage": round(total / max_total * 100),
        "verdict": "PASS" if total / max_total >= 0.8 else "REWRITE"
    }

Nova가 잡아낸 것들 (8일간 실제 사례)

빈도 순으로 정리한 가장 자주 플래그된 이슈:

  1. 5문장 넘는 문단 (6회) — 텍스트 벽 증후군. AI가 정보를 거대한 문단에 우겨넣는다.
  2. 내부 링크 없음 (5회) — 모든 글이 고립된 섬. Nova가 관련 포스트 링크를 추가해서 SEO와 독자 이탈률을 개선.
  3. 메타 디스크립션 누락 (4회) — Atlas가 일관적으로 빠뜨림. AI 모델이 잘 내재화하지 못하는 기계적 요구사항.
  4. import 없는 코드 블록 (3회) — 가장 위험한 이슈. 독자가 코드 복사해서 돌리면 ModuleNotFoundError — 글 전체가 의심받는다.
  5. “Built by Jackson Studio” 누락 (3회) — 브랜드 일관성. 간단한 규칙, 쉽게 잊는다.
  6. 약한 도입부 (2회) — “이 글에서는…“으로 시작하는 대신 구체적 스토리나 문제 제시가 필요.

패턴이 명확하다: AI 모델은 콘텐츠 생성에 강하고 기계적 요구사항 기억에 약하다. Python 데코레이터를 기막히게 설명하면서 import functools를 까먹는다. 정확히 자동 QA가 존재하는 이유다.

왜 프롬프트를 더 잘 쓰면 안 되나?

좋은 질문이다. 프롬프트가 더 좋으면 초안 품질이 높아지지 않을까?

그렇기도 하고 아니기도 하다.

더 좋은 프롬프트는 도움이 된다. 매일 밤 Sentinel이 프롬프트를 리뷰하고 개선한다. 하지만 근본적 긴장이 있다:

QA 파이프라인은 프롬프트가 막을 수 없는 예측 불가능한 실패를 처리하도록 설계됐다. 코드를 잘 짜되(좋은 프롬프트) 테스트도 쓰는 것(QA 파이프라인)의 차이다.

소프트웨어 엔지니어링에서 테스트 없이 배포하는 사람은 없다. 콘텐츠 운영에서는 왜 다들 검수 없이 퍼블리싱하는가? 코드에 적용하는 엄격함을 콘텐츠에도 적용하기로 했다.

금지 문구 리스트

예상 못한 추가: 금지 오프닝 문구 리스트. Atlas가 같은 뻔한 오프닝을 반복 사용하는 걸 발견했다:

BANNED_OPENERS = [
    "오늘날 빠르게 변화하는",
    "현대 개발자라면",
    "누구나 한 번쯤",
    "이번 글에서는",
    "이 가이드에서 살펴볼",
    "개발을 하다 보면",
]

이 중 하나로 시작하면 독자 참여에서 자동 -5점. 투박하지만 효과적이다. 이 리스트 추가 후 도입부가 극적으로 개선됐다 — 이제 모든 글이 구체적 스토리, 데이터 포인트, 또는 도발적 문장으로 시작한다.

품질의 비용

QA 파이프라인 추가 비용:

주 3-5개 글 기준, QA 비용은 대략 월 $0.05. 반올림 오차가 아니라 사실상 무료다.

QA를 안 하는 비용? 측정 불가. 깨진 코드 예시가 든 글 하나가 기술 독자들에게 신뢰를 영구히 깎는다. 코드 복사해서 에러 나면 그 블로그 글은 두 번 안 본다. $0.05가 아깝지 않다. 솔직히 이 수준의 확신에 $50/월이어도 낼 것이다.

다음 글

이 시리즈 다음 편은 경제학이다: 월 $10으로 기술 블로그 운영하기 — 인프라 비용의 전체 분해. AWS, Vercel, 외부 서버 없이 왜 가능한지.

이 QA 시스템을 직접 구현하고 싶다면, Gumroad에서 점수 엔진과 점수표 템플릿을 패키지로 준비하고 있다.


Built by Jackson Studio. 모든 글은 점수를 받는다. 모든 점수는 번 것이다.

💖 이 글이 도움되셨나요?

AI 블로그 실험을 응원해주세요.

☕ PayPal로 지원하기