← 목록으로
2026-02-26plans

title: content-pipeline OpenRouter 전환 플랜 L0 date: 2026-02-26 status: draft reviewed_by: "jarvis" approved_by: "" okr_target: "O3 콘텐츠 — AI 생성 언블로킹"

content-pipeline OpenRouter 전환 L0 기획서

1. 목표

왜 전환하는가

  1. CEO 확정 정책: 모든 AI API 호출은 OpenRouter 단일 엔드포인트 경유 (openrouter-policy.md)
  2. CEO 블로킹 해소: 현재 GOOGLE_API_KEY 미발급으로 AI 생성이 mock 모드. OpenRouter 전환 시 OPENROUTER_API_KEY 하나만 발급받으면 모든 AI 호출 언블로킹
  3. 통합 관리: 개별 API 키(Google, Anthropic, OpenAI) 각각 관리 대신 OpenRouter에서 한도/비용 통합 관리
  4. SDK 단순화: @google/generative-ai 제거 → openai SDK 하나로 통일 (OpenRouter는 OpenAI 호환 API)

기대 효과

항목전환 전전환 후
API 키 관리GOOGLE_API_KEY (미발급)OPENROUTER_API_KEY (1개)
SDK@google/generative-aiopenai (OpenAI 호환)
모델gemini-2.0-flash (직접)google/gemini-2.0-flash-exp (OpenRouter 경유)
AI 생성 상태mock 모드 (블로킹)실제 생성 가능
비용 통제Google 콘솔 개별 관리OpenRouter 대시보드 한도 설정

2. 현황: 현재 코드 구조

Google Generative AI SDK 사용 파일 (3개)

파일역할SDK 사용 방식
src/pipeline/generate.ts뉴스레터 생성GoogleGenerativeAIgetGenerativeModel("gemini-2.0-flash")generateContent()
src/pipeline/generate-blog.ts블로그 포스트 생성GoogleGenerativeAIgetGenerativeModel("gemini-2.0-flash", systemInstruction)generateContent()
src/pipeline/stage-generate.ts생성 스테이지 오케스트레이터직접 SDK 미사용. generate-blog.ts 호출 + GOOGLE_API_KEY 유무로 모델명 로깅

현재 의존성

// package.json
"@google/generative-ai": "^0.21.0"

Mock 모드 동작

  • generate.ts L251: if (!process.env.GOOGLE_API_KEY)generateMockNewsletter() 반환
  • generate-blog.ts L392: if (!process.env.GOOGLE_API_KEY)generateMockBlogPost() 반환
  • stage-generate.ts L192: 로그에 GOOGLE_API_KEY ? 'gemini-2.0-flash' : 'mock' 기록

API 호출 패턴 (Google SDK)

// generate.ts (뉴스레터)
const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });
const result = await model.generateContent(fullPrompt);
const text = result.response.text();

// generate-blog.ts (블로그)
const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY!);
const model = genAI.getGenerativeModel({
  model: "gemini-2.0-flash",
  systemInstruction: systemPrompt,
});
const result = await model.generateContent(userPrompt);
const text = result.response.text();

3. 전환 범위

수정 파일 목록 (5개)

파일변경 내용난이도
src/pipeline/generate.tsGoogle SDK → OpenAI SDK (OpenRouter), 환경변수 변경, mock fallback 유지
src/pipeline/generate-blog.tsGoogle SDK → OpenAI SDK (OpenRouter), systemInstruction → messages 변환, mock fallback 유지
src/pipeline/stage-generate.ts환경변수 참조 변경 (GOOGLE_API_KEYOPENROUTER_API_KEY), 모델명 로깅 변경
package.json@google/generative-ai 제거, openai 추가
.env.example / Vercel env환경변수 키 변경

변경하지 않는 파일

  • src/pipeline/collect.ts — RSS 수집 (AI API 미사용)
  • src/pipeline/publish.ts / publish-blog.ts — 발행 (AI API 미사용)
  • src/pipeline/run.ts / run-blog-pipeline.ts — 오케스트레이터 (generate 함수 호출만, SDK 미사용)
  • src/lib/ — 유틸리티 (AI API 미사용)
  • 프롬프트 파일들 (prompts/) — 변경 불필요

4. 환경변수 변경

제거

GOOGLE_API_KEY=AIza...  (더 이상 사용 안 함)

추가

OPENROUTER_API_KEY=sk-or-...        # CEO 발급 필요
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1  # 고정값 (코드 내 default 가능)

적용 위치

  • 로컬: .env.local
  • Vercel: content-pipeline 프로젝트 환경변수
  • 향후: apppro.kr 등 다른 프로젝트도 동일 키 공유

5. 모델 매핑

용도현재 (Google 직접)전환 후 (OpenRouter)
뉴스레터 생성gemini-2.0-flashgoogle/gemini-2.0-flash-exp
블로그 포스트 생성gemini-2.0-flashgoogle/gemini-2.0-flash-exp

OpenRouter 모델명은 openrouter-policy.md에 CEO 확정된 매핑 기준. 향후 고품질 콘텐츠용 anthropic/claude-sonnet-4-6도 OpenRouter 경유로 전환 가능 (이번 범위 아님).


6. 코드 전환 패턴

Before (Google SDK)

import { GoogleGenerativeAI } from "@google/generative-ai";

const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });
const result = await model.generateContent(prompt);
const text = result.response.text();

After (OpenAI SDK via OpenRouter)

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: process.env.OPENROUTER_BASE_URL || "https://openrouter.ai/api/v1",
  apiKey: process.env.OPENROUTER_API_KEY,
});

const response = await client.chat.completions.create({
  model: "google/gemini-2.0-flash-exp",
  messages: [
    { role: "system", content: systemPrompt },  // generate-blog.ts만 해당
    { role: "user", content: userPrompt },
  ],
});

const text = response.choices[0]?.message?.content || "";

generate-blog.ts 특이사항

  • Google SDK의 systemInstruction 파라미터 → OpenAI SDK의 messages[0].role = "system" 으로 전환
  • 이 변환은 자연스럽게 매핑됨 (OpenRouter가 system message 지원)

Mock 모드 유지

// 전환 후에도 동일 패턴 유지
if (!process.env.OPENROUTER_API_KEY) {
  console.log("[generate] OPENROUTER_API_KEY not set. Generating mock.");
  return generateMockNewsletter(news);  // 또는 generateMockBlogPost()
}

7. 테스트 계획

로컬 테스트

  1. Mock 모드 테스트: OPENROUTER_API_KEY 미설정 상태에서 기존 mock 동작 확인

    npm run generate:blog "테스트 주제" AI도구리뷰
    npm run pipeline:generate
    
  2. 실제 API 테스트 (CEO 키 발급 후):

    OPENROUTER_API_KEY=sk-or-... npm run generate:blog "소상공인 ChatGPT 활용법" AI도구리뷰
    
  3. 품질 검증: validateQuality() 통과 확인 (6/8 이상)

Vercel 배포 테스트

  1. Vercel 환경변수에 OPENROUTER_API_KEY, OPENROUTER_BASE_URL 설정
  2. /api/pipeline/generate 엔드포인트 호출
  3. content_queue에 draft 정상 생성 확인
  4. 로그에 google/gemini-2.0-flash-exp 모델명 기록 확인

빌드 테스트

npm run build  # TypeScript 컴파일 에러 없는지 확인

8. CEO 블로킹

항목상태액션
OPENROUTER_API_KEY 발급CEO 발급 필요https://openrouter.ai 가입 → API 키 생성 → 한도 설정
Vercel env 설정CEO 키 발급 후OPENROUTER_API_KEY + OPENROUTER_BASE_URL 추가

코드 전환 자체는 CEO 블로킹 없이 진행 가능. 키 없어도 mock 모드로 동작. CEO 키 발급 후 즉시 실제 AI 생성 활성화.


9. 예상 소요

단계소요 시간설명
L1 상세 설계15분파일별 변경 사항 상세 명세
L2 구현30~45분SDK 교체, 테스트, 빌드 확인
Vercel 배포 + 검증15분push → 배포 → mock 모드 검증
합계약 1~1.5시간CEO 키 발급 대기 시간 제외

10. 리스크 및 대응

리스크확률대응
OpenRouter 경유 시 응답 지연낮음Gemini Flash 자체가 빠름. 타임아웃 30s 설정
OpenRouter 모델명 변경낮음환경변수로 모델명 주입 (하드코딩 최소화)
JSON 파싱 실패 (모델 차이)중간기존 robust 파싱 로직 유지 (escapeNewlinesInJsonStrings, extractField 등)
mock 모드 퇴행낮음환경변수 미설정 시 mock fallback 로직 그대로 보존

11. 다음 단계

  1. VP 검수 → 이 L0 승인
  2. L1 상세 설계 → 파일별 변경 diff 명세
  3. L2 구현 + 테스트 → PL 스폰하여 실행
  4. CEO 키 발급 요청 → Telegram으로 OPENROUTER_API_KEY 요청
  5. Vercel env 설정 + 실제 AI 생성 활성화
plans/2026/02/26/openrouter-migration-l0.md