Gemini + Claude 병렬 리뷰 파이프라인: 5개 AI Critic으로 블로그 검수 자동화
목차
Gemini와 Claude 5개 에이전트가 병렬로 블로그 초안을 검수하고, 피드백을 반영한 최종본을 PR로 발행하는 구조를 만들었다.
Telegram에서 “블로그 발행해줘” 한 마디로 전체 파이프라인이 돌아간다.

개인 블로그를 운영하면서 가장 부담스러운 단계는 발행 후 검수였다. AI가 변환한 초안을 직접 읽으면서 라인 단위로 수정하는 작업이 매번 반복됐다. 글 하나에 30분에서 1시간이 들었다. 여러 AI 모델이 서로 다른 관점에서 검수하면 이 부담을 줄일 수 있지 않을까.
영감은 유튜브에서 본 telepty/deliberation 프로젝트였다. 7개 AI CLI를 공통 메시지 버스로 연결해 구조화된 라운드 토론으로 합의에 도달하는 시스템이다. 하지만 블로그 리뷰에 이 정도 인프라는 과했다. AI들이 서로의 발언을 읽고 실시간으로 반박하는 난상 토론이 아니라, 각자 독립적으로 평가한 뒤 중재자가 통합하는 구조가 더 통제 가능하고 실용적이었다.
도구는 이미 손에 있었다. gemini -p와 claude -p — 두 CLI의 non-interactive 모드를 Bash에서 &로 병렬 호출하고 wait로 대기하면 됐다.
Gemini CLI는 Google이 공식 제공하는 터미널 AI 도구다.
claudeCLI가 Anthropic 구독을 사용하는 것과 같은 구조로, OAuth 로그인을 통해 Google AI Pro 구독을 직접 활용한다. Gemini API key는 구독과 별개로 무료 티어 제한이 걸리지만,gemini -p는 Pro 구독 quota를 그대로 쓴다.npm install -g @google/gemini-cli로 설치한다.
# Gemini에게 리뷰 요청 (Pro 구독 사용)
gemini -p "당신은 시니어 기술 블로그 독자입니다. 다음 글을 평가하세요. ..."
# Claude에게 리뷰 요청
claude -p "당신은 기술 블로그 편집자입니다. 다음 글의 구조를 평가하세요. ..."단, gemini -p는 non-interactive 모드에서 도구(tool) 사용이 차단된다. 텍스트 리뷰는 되지만 이미지 생성은 불가능하다. 블로그 cover image는 Gemini Pro 웹 UI를 Playwright로 자동화하는 별도 스킬(gemini-browser)이 담당한다.
5개 Critic 설계
단순히 “검토해줘”가 아니라 역할을 명확히 분리해야 다양한 관점의 피드백이 나온다. Gemini 2개, Claude 2개, 현재 세션 1개로 총 5개 Critic을 구성했다.
| Critic | 모델 | 페르소나 | 평가 항목 |
|---|---|---|---|
| A-1 | Gemini Pro | 이 주제를 처음 접하는 시니어 개발자 | 가독성, 흐름, 독자 피로도 |
| A-2 | Gemini Pro | 해당 기술의 실무 경험자 | 사실 오류, 오해 소지, 누락된 trade-off |
| B-1 | Claude | 기술 블로그 편집자 | 구조/서사 흐름, 블로그 톤 적합성 |
| B-2 | Claude | 실용주의 개발자 독자 | 코드 예시 검증, 따라하기 가능성 |
| C | 현재 세션 | SEO/메타 전문가 | 제목, 태그, description, tldr |
A-1과 A-2에 같은 Gemini를 쓰는 이유는 모델의 차이가 아니라 페르소나의 차이를 활용하기 위해서다. 같은 모델이라도 “초심자 관점에서 읽어라”와 “실무 경험자로서 검증하라”는 전혀 다른 피드백을 낸다. Claude 쪽도 마찬가지로 편집자와 독자를 분리했다.
각 Critic은 JSON으로 점수와 개선 제안을 반환하도록 프롬프트를 설계했다. 핵심은 평가 범위를 명시적으로 제한하는 것이다. “전반적으로 평가해줘”라고 하면 5개 Critic이 비슷한 피드백을 내놓는다. “가독성만 평가하세요. 코드 정확성은 무시하세요”라고 범위를 잘라야 역할 분리가 실제로 동작한다.
DRAFT=$(cat 초안.md)
# Critic A-1: 독자 관점 (Gemini Pro)
gemini -p "당신은 이 기술 주제를 처음 접하는 시니어 개발자입니다.
[평가 범위] 가독성(1-10), 독자 피로도
[평가 범위가 아닌 것 — 무시] 코드 정확성, 기술 사실 오류, SEO
[필수] 반드시 3개의 개선점을 제시. '특별히 없음' 불허.
JSON: {readability: number, fatigue_point: string, suggestions: string[]}
---
$DRAFT" > /tmp/critic-a1.json &
# Critic A-2: 기술 정확성 (Gemini Flash)
gemini -m gemini-2.5-flash -p "당신은 이 기술 분야의 실무 경험이 풍부한 엔지니어입니다.
[평가 범위] 기술 정확성(1-10), 최신성(1-10), 누락된 trade-off
[평가 범위가 아닌 것 — 무시] 가독성, 문체, 구조, SEO
[필수] 반드시 3개의 수정/보완 제안.
JSON: {accuracy: number, freshness: number, missing_tradeoffs: string[], suggestions: string[]}
---
$DRAFT" > /tmp/critic-a2.json &
# Critic B-1: 구조/서사 (Claude Opus)
cd /tmp && claude -p --model opus "당신은 기술 블로그 편집자입니다.
[평가 범위] 구조(1-10), 톤 적합성(1-10)
[평가 범위가 아닌 것 — 무시] 코드 정확성, 기술 사실 오류, SEO
[필수] 반드시 3개의 개선점을 제시.
JSON: {structure: number, tone: number, suggestions: string[]}
---
$DRAFT" > /tmp/critic-b1.json &
# Critic B-2: 실용성 (Claude Haiku)
cd /tmp && claude -p --model haiku "당신은 실용주의 개발자 독자입니다.
[평가 범위] 코드 예시(1-10), 실용성(1-10)
[평가 범위가 아닌 것 — 무시] 문체, 서사 흐름, 구조, SEO
[필수] 반드시 3개의 개선점을 제시.
JSON: {code_quality: number, practicality: number, suggestions: string[]}
---
$DRAFT" > /tmp/critic-b2.json &
wait # 4개 완료 대기A-2에 Gemini Flash를 쓰는 이유가 있다. 같은 Pro 모델로 A-1과 A-2를 돌리면 페르소나가 달라도 응답 패턴이 수렴하는 경향이 있었다. 모델 크기가 다르면 관대함의 정도가 달라진다 — Flash는 Pro보다 직접적이고 덜 관대해서 기술 오류를 더 엄격하게 잡아냈다.
LLM이 JSON을 markdown 코드블록으로 감싸는 경우가 많아서, 파싱 전 전처리가 필요했다.
# JSON 파싱 헬퍼: 코드블록 제거 + jq, 실패 시 점수 0 처리
parse_json() {
echo "$1" | sed 's/^```json//;s/^```//' | jq '.' 2>/dev/null \
|| echo '{"score": 0, "suggestions": ["파싱 실패 — 원문 참조"]}'
}수렴 조건은 단순하게 잡았다. 전체 점수 평균 8.0 이상이면 통과, 미만이면 피드백을 반영하여 수정 후 재평가했다. 최대 2라운드까지만 반복했다. 무한 루프를 방지하면서도 의미 있는 개선이 일어날 수 있는 횟수다.
발행 방식: push에서 PR로
멀티 모델 리뷰가 들어가면서 발행 방식도 바뀌었다. 기존에는 git push origin main으로 직접 발행했다. AI가 검수했다 해도, 5개 에이전트가 합의한 결과물이 정말 괜찮은지 사람이 확인할 수 있어야 한다.
PR 방식으로 전환한 이유는 세 가지다.
- diff 확인: 원본 vault 노트에서 얼마나 변환됐는지 한눈에 볼 수 있다
- Critic 결과 가시화: PR body에 각 Critic의 점수와 주요 피드백을 테이블로 포함한다
- 되돌리기 용이: 문제가 있으면 PR을 닫으면 된다. main에 직접 push하면 revert가 필요하다
# 브랜치 생성 + PR
git checkout -b "blog/$(date +%Y%m%d)-{slug}"
git add src/data/blog/ public/images/blog/
git commit -m "feat: 블로그 발행 - {제목}"
git push -u origin "blog/..."
gh pr create --title "feat: 블로그 발행 - {제목}" --body "..."PR body에는 이런 형태의 Critic 결과가 들어간다:
## Critic 결과 (Round 2)
| Critic | 모델 | 점수 | 주요 피드백 |
|--------|------|:---:|-----------|
| A-1 (독자 관점) | Gemini | 9/10 | 흐름 자연스러움 |
| A-2 (기술 정확성) | Gemini | 9/10 | deprecated 정보 1건 수정 |
| B-1 (구조/서사) | Claude | 8/10 | 서술체 일관성 확보 |
| B-2 (실용성) | Claude | 7/10 | 코드 예시 보충 반영 |
| C (SEO/메타) | Claude | 8/10 | 태그 추가, tldr 개선 |
| **평균** | | **8.2** | |PR은 단순한 발행 게이트가 아니라 Human-in-the-Loop 인터페이스다. merge 전에 사람이 diff를 읽고, 부족한 부분이 있으면 PR에 라인 코멘트를 남긴다. AI가 그 코멘트를 읽고 수정 커밋을 추가한다. 사람이 만족할 때까지 이 루프를 반복할 수 있다. 5개 Critic의 자동 검수와 사람의 최종 판단이 PR이라는 하나의 인터페이스에서 만나는 구조다.
글 관리(숨기기, 삭제, 복원)는 즉시 반영이 필요한 작업이라 PR이 과도하다. 이런 건 기존대로 직접 push한다.
실전 테스트: TTS 노트로 첫 파이프라인
NanoClaw 로컬 TTS 파이프라인 구축 노트를 첫 번째 테스트 대상으로 선택했다. 전체 흐름을 돌려보는 게 목적이었다.
Round 1에서 평균 7.6이 나왔다. 통과 기준(8.0)에 미달이었고, B-2(실용성) Critic이 코드 품질 3점을 매겼다. 파일명만 나열하고 실제 코드를 보여주지 않은 게 치명적이었다. Gemini A-1은 가독성 9점으로 높았지만, Claude B-1은 트러블슈팅 섹션의 나열식 구성을 지적했다.
피드백을 반영했다. 핵심 코드 블록 3개(tts.ts spawn, IPC fire-and-forget, MCP 도구 등록)를 추가하고, 트러블슈팅 5건 나열을 에피소드 서사 1건 + 축약 리스트로 재구성했다. fire-and-forget 패턴의 trade-off(실패 인지 불가)와 모델 Cold Start 문제도 언급했다.
Round 2에서 평균 8.4로 통과. B-2가 3에서 7로, 실용성이 6에서 8로 올라갔다. 코드 블록 추가가 결정적이었다. PR #1을 생성하고, Critic 결과 테이블과 반영 사항을 body에 포함시켰다. merge 후 GitHub Actions가 자동 배포했다.
Telegram Channels 연결
파이프라인의 검수 로직은 완성됐다. 다음 과제는 이것을 어디서든 트리거할 수 있게 만드는 것이었다.
마지막 퍼즐은 모바일 트리거였다. 이동 중에 Telegram으로 “이 노트 블로그로 발행해줘”라고 보내면, 데몬 세션이 파이프라인을 돌리고 PR 링크와 Critic 요약을 Telegram으로 돌려보내는 구조를 만들었다.
Claude Code의 Telegram 플러그인을 활용했다. macOS의 launchd로 Claude Code 데몬 세션을 상시 실행하고, Telegram Bot API로 메시지를 수신했다. 데몬은 ~/claude-telegram/ 디렉토리에서 실행되며, 이 디렉토리의 settings.local.json에 Telegram 플러그인이 활성화되어 있다.
“주간 기술 뉴스 블로그로 발행해줘”라고 보내면 vault-to-blog 스킬이 로드되고 전체 파이프라인이 자동으로 실행되었다. 제목 후보도 Telegram으로 전달되었다. 번호를 선택하면 해당 제목으로 frontmatter를 조정하고, cover image를 생성한 뒤, PR을 만들어 링크를 Telegram으로 회신했다.
첫 테스트에서 claude -p 실행 위치 때문에 Telegram 연결이 끊기는 문제를 만났다. 데몬 디렉토리에서 claude -p를 실행하면 settings.local.json의 Telegram 플러그인 설정을 로드해 새로운 polling을 시작한다. Telegram Bot API는 하나의 getUpdates 연결만 허용하므로, 기존 데몬의 MCP 서버가 죽었다. 해결은 단순했다 — cd /tmp && claude -p로 플러그인 설정이 없는 디렉토리에서 실행하면 된다.
AI에게 파이프라인 비평을 받다
파이프라인이 완성됐다고 생각하던 날, Gemini에게 구조적 한계를 물어봤다. 세 가지 문제가 돌아왔다 — 중재자 부재, 평균의 함정, 파싱 실패 시 전체 중단. 각각의 해결책이 이 글의 후반부를 구성한다.
Synthesis: 충돌 중재자
A-1이 “글이 너무 길다”고 하고 B-2가 “코드 예시를 더 추가하라”고 하면, 상충하는 두 피드백 중 어느 쪽을 따를지 Writer가 혼자 판단해야 했다. 일관성이 없고, 두 피드백 중 하나는 필연적으로 무시됐다.
해결책은 Synthesis 에이전트를 추가하는 것이었다. 4개 Critic이 완료되면 별도 claude -p로 Synthesis를 호출한다. 이 에이전트는 Editor-in-Chief 역할을 한다 — raw 피드백을 Writer에게 직접 전달하지 않고, 반드시 Synthesis를 거쳐 통합 지시로 변환한다.
SYNTHESIS=$(cd /tmp && claude -p "당신은 편집 중재자입니다.
다음은 동일 초안에 대한 4개의 Critic 피드백입니다.
[A-1] $CRITIC_A1 [A-2] $CRITIC_A2 [B-1] $CRITIC_B1 [B-2] $CRITIC_B2
1. 피드백 간 충돌 항목을 찾아 명시
2. 각 충돌에 대해 우선순위를 결정하고 이유를 한 줄로 설명
3. Writer가 바로 적용할 수 있는 통합 수정 지시 목록 작성 (최대 7개)
JSON 응답: {conflicts: [...], unified_feedback: string[], priority_order: string[]}")Synthesis가 반환하는 JSON이 이 역할을 실제로 수행한다:
{
"conflicts": [
{
"topic": "Telegram 섹션 분량",
"a_says": "A-1: Polling 충돌 디버깅 과정을 축소해 아키텍처 응집도 강화",
"b_says": "B-2: 데몬 설정·환경 변수를 더 구체적으로 명시",
"resolution": "B-2 우선 — 독자층이 실무 개발자이므로 재현 가능한 설정 정보가 필수"
}
],
"unified_feedback": [
"Synthesis 섹션에 conflicts/unified_feedback 구조의 실제 예시 JSON 삽입",
"Critic→Synthesis→Veto 파이프라인 데이터 흐름을 다이어그램으로 표현"
],
"priority_order": [
"1. Synthesis JSON 예시 삽입 — 글의 핵심 기능 이해도에 직결",
"2. 파이프라인 다이어그램 추가 — 구조 파악 진입 장벽 제거"
]
}Writer는 raw Critic 4개의 피드백 대신 이 unified_feedback과 priority_order만 받아서 수정한다. 실제로는 Synthesis의 JSON 출력을 다시 claude -p에 넘겨서 초안 수정을 지시한다.
# Synthesis 결과를 Writer에게 전달
cd /tmp && claude -p "다음 블로그 초안을 아래 통합 피드백에 따라 수정하세요.
[통합 피드백] $SYNTHESIS
[초안] $DRAFT" > 수정본.md“글을 줄여라”와 “코드를 추가하라”가 충돌하면, 어떤 섹션을 줄이고 어느 코드를 추가할지 Synthesis가 구체적인 지시로 변환해준다. Writer는 방향 판단 없이 실행만 하면 된다.
Note: Synthesis도 LLM이므로 편향이 없지는 않다. 특히 Synthesis를 Claude로 실행하면 Claude 기반 Critic(B-1, B-2)의 의견을 과대 대표할 가능성이 있다. 이를 감지하기 위해 Synthesis에 점수 표준편차 계산을 포함시켰다 — 표준편차가 0.5 미만이면 “의견 수렴도가 높다”는 경고가 추가되어, groupthink 가능성을 사람이 인지할 수 있다.
파이프라인 전체 흐름
flowchart TD
A[vault 노트] --> B[Writer: 변환 초안 생성]
B --> C{멀티 모델 리뷰}
C --> D[A-1 Gemini<br>독자 관점]
C --> E[A-2 Gemini<br>기술 정확성]
C --> F[B-1 Claude<br>구조/서사]
C --> G[B-2 Claude<br>실용성]
D & E & F & G --> H[Synthesis<br>충돌 중재]
H --> I{Veto 조건 평가}
I -->|평균 8.0+<br>최저 6.0+<br>A-2 not 5 이하| J[통과]
I -->|미달| K[Writer 재수정<br>최대 2라운드]
K --> C
J --> L[Critic C<br>SEO/메타]
L --> M[Cover Image 생성]
M --> N[PR 생성]
N --> O[사람이 merge]
O --> P[GitHub Actions<br>자동 배포]Veto: 평균만으로는 부족하다
A-2(기술 정확성)가 5점이어도 나머지 Critic이 모두 9점을 주면 평균 8.0을 넘는다. 사실 오류가 있는 글이 “합의 통과”될 수 있다. 이 평균의 함정은 수렴 조건에 복합 조건을 추가해서 해결했다.
기존: 전체 평균 8.0 이상 → 통과
변경:
- 전체 평균 8.0 이상 AND 최저 점수 6.0 이상 → 통과
- A-2(기술 정확성) 5 이하 → 평균 무관, 강제 재검토
핵심은 최저 점수 6.0 조건이다. 이 조건은 4개 Critic 모두에게 거부권을 부여한다. A-1이 가독성 5점을 매기면 나머지가 만점이어도 통과하지 못한다. B-2가 실용성 4점을 주면 마찬가지다. 어떤 Critic이든 6 미만이면 veto가 걸린다. “평균은 괜찮지만 가독성이 형편없는” 글, “평균은 높지만 코드가 따라할 수 없는” 글이 발행되는 걸 막는다.
A-2(기술 정확성)에는 한 단계 더 강한 조건이 있다. 점수 5 이하면 평균에 관계없이 강제 재검토한다. 기술 정확성은 블로그 글의 신뢰도와 직결되기 때문이다. 잘못된 코드 예시나 deprecated API 사용은 다른 관점에서 아무리 좋은 점수를 받아도 발행해서는 안 된다.
More: 파싱 에러와 Cover Image
LLM이 마크다운 코드블록으로 감싸거나 불완전한 JSON을 반환하면 파이프라인 전체가 중단될 수 있다. 앞서 소개한 parse_json 헬퍼가 이 문제를 처리한다 — 파싱 실패 시 점수 0으로 처리하고 텍스트 피드백만 수집한 뒤 계속 진행한다. 점수 0은 자동으로 Veto 조건에 걸리므로 재검토가 트리거된다.
Cover image도 파이프라인에 포함되어 있다. gemini -p는 non-interactive 모드에서 이미지 생성이 불가능하므로, Gemini Pro 웹 UI를 Playwright로 자동화하는 별도 스킬(gemini-browser)이 담당한다. 프롬프트 톤을 “Isometric illustration, soft blue-purple palette, clean minimal style, white background”로 통일해 블로그 전체의 시각적 일관성을 유지한다.
직접 해보기
Telegram이나 데몬 구성 없이도 핵심 개념을 확인할 수 있다. 필요한 건 두 가지뿐이다.
- Gemini CLI:
npm install -g @google/gemini-cli후gemini auth로 Google AI Pro 로그인 - Claude CLI:
npm install -g @anthropic-ai/claude-code후claude실행하여 인증
gemini -p와 claude -p를 병렬 호출하고 JSON 응답을 비교하는 것이 이 파이프라인의 출발점이다.
# 최소 재현: 멀티 모델 리뷰 (Telegram/데몬 불필요)
DRAFT="path/to/your-draft.md"
# 긴 글은 stdin으로 전달 (bash 명령어 길이 제한 회피)
cat "$DRAFT" | gemini -p "가독성을 1-10으로 평가하세요.
JSON: {readability: number, suggestion: string}" > /tmp/critic-a.json &
cat "$DRAFT" | claude -p "구조를 1-10으로 평가하세요.
JSON: {structure: number, suggestion: string}" > /tmp/critic-b.json &
wait
echo "Gemini: $(cat /tmp/critic-a.json)"
echo "Claude: $(cat /tmp/critic-b.json)"여기에 Synthesis와 Veto를 얹으면 이 글에서 설명한 전체 파이프라인이 된다. 핵심은 각 Critic의 평가 범위를 겹치지 않게 분리하는 것이다.
정리: 트레이드오프
이 파이프라인은 공짜가 아니다.
비용. Round 2까지 가면 Critic 4개 x 2 + Synthesis 2 = 10회 이상의 LLM 호출이 발생한다. Claude는 호출마다 토큰을 소모하고, Gemini는 Pro 구독 quota를 사용한다. 개인 블로그 용도라 주 2-3회 발행 기준으로 부담 가능한 수준이지만, 팀 블로그나 일 단위 발행이라면 비용 구조를 재검토해야 한다. 전체 파이프라인 1회에 약 3-5분이 걸리고(Critic 병렬 ~2분, Synthesis ~1분, Cover Image ~80초), Round 2까지 가면 8-10분이다.
프롬프트 유지보수. 5개 Critic 프롬프트는 모델 변경이나 요구사항 변화 시 함께 수정해야 한다. 현재는 vault-to-blog 스킬 파일 하나에 모든 프롬프트가 포함되어 있어 관리 가능하지만, Critic을 추가하거나 평가 기준을 세분화하면 별도 관리가 필요해진다.
공급자 종속. 파이프라인 전체가 Gemini와 Claude 두 공급자에 의존한다. API 변경, 가격 정책 변화, 서비스 중단 시 파이프라인이 멈춘다. gemini -p와 claude -p라는 CLI 인터페이스에 의존하는 것도 리스크다 — CLI의 출력 형식이 바뀌면 파싱이 깨진다. 현재는 parse_json 헬퍼로 방어하고 있지만, 근본적 해결은 아니다.
이런 트레이드오프에도 불구하고, 이 파이프라인의 핵심 가치는 검수 부담의 이동이다. 사람이 라인 단위로 읽고 고치던 작업을 AI 에이전트들이 구조화된 평가 항목으로 대신한다. 사람은 PR에서 최종 결과만 확인하면 된다.
결국 이 파이프라인의 설계 원칙은 하나다 — 평가 항목의 분리가 합의의 품질을 결정한다. 각 Critic에게 명확하게 다른 역할을 부여하지 않았다면, 5개 에이전트가 돌아도 같은 피드백을 5번 받는 것에 불과했을 것이다. 다음 실험은 Critic 가중치를 글 유형에 따라 자동 조정하는 것이다 — 튜토리얼은 B-2(실용성)에, 에세이는 A-1(가독성)에 더 높은 가중치를 주는 식으로.