LLM으로 광고 카피 100개 + CTR 예측까지 — 운영자용 4단계 워크플로우
카피라이터 한 명이 하루에 30개 만들 시간에, LLM은 100개를 5분에 만듭니다. 그런데 그 100개를 그대로 광고에 태우면 안 돼요. 임베딩으로 중복 제거하고 과거 CTR로 사전 스코어링하는 운영 파이프라인.
“이번 캠페인 카피 30개만 더 뽑아주세요” — 마케터의 단골 주문이었던 이 한 줄이, GPT/Claude 등장 이후 의미가 달라졌어요. 이제 100개도 5분이면 나옵니다. 그런데 정작 광고 매니저에 100개를 다 태우면 학습 분산이 깨지고, 비슷한 카피끼리 서로 잠식해서 결과가 망가져요. 이 글은 LLM으로 양산한 카피를 중복 제거 → 사전 스코어링 → A/B 후보 선별까지 가는 운영자용 4단계 파이프라인입니다.
마케터가 이 글을 읽어야 하는 이유: “LLM으로 카피 뽑기”는 이미 많이들 합니다. 뽑은 다음 어떻게 고르고 어떻게 태우는지를 아는 팀이 실제로 광고 효율을 올립니다. 이 파이프라인을 한 번 세팅해두면 다음 캠페인부터 카피 선별에 드는 시간이 반 이상 줄고, 광고 학습 분배 효율도 높아집니다.
0단계: 왜 100개를 그대로 태우면 안 되나
처음 LLM으로 카피를 100개 뽑아 광고에 태웠을 때의 패턴 — 익숙한 사람 많을 거예요.
- 학습 데이터 분산: Meta·Google 광고 매니저는 카피별로 노출을 학습 분배합니다. 100개면 한 개당 노출이 100분의 1로 쪼개져, 통계적 유의에 도달하기 전에 캠페인이 끝남.
- 카니발리제이션: 비슷한 카피 5개가 같은 입찰 풀에서 서로 잠식. CPC만 올라가고 CTR은 안 오름.
- 품질 편차: LLM이 100개 중 70개는 평이하고 10개는 이상하고 20개만 쓸 만한데, 그걸 사람이 한 번도 안 거른 채 태움.
- 브랜드 톤 이탈: “재밌는 카피” 100개 중에 브랜드 가이드를 어기는 게 섞여 있음.
해결은 중간에 필터를 두는 것입니다. 4단계 파이프라인:
1단계: LLM 카피 양산 — 좋은 프롬프트 패턴
핵심은 페르소나 + 제약 + 다양성 강제예요. “광고 카피 100개 줘” 같은 빈약한 프롬프트는 비슷한 100개를 만들어냅니다.
[페르소나]당신은 30대 워킹맘을 타겟으로 하는 유아용품 브랜드의 카피라이터입니다.브랜드 톤은 "안심·따뜻함·과장 금지"입니다.
[제품 정보]- 제품: 무염 유아 간식 X- USP: 6개월부터 먹일 수 있는 유일한 무염 라이스 스낵- 가격: 한 박스 12,900원
[제약]- 길이: Meta 광고 primary text 기준 한 줄 (최대 90자)- 의문문 금지, 과장된 형용사("최고의", "유일한") 금지- 가격 명시 금지 (정책)- 같은 첫 단어로 시작하는 카피가 5개를 넘지 말 것
[요청]다음 5가지 다른 각도로 각각 4개씩, 총 20개의 카피를 만들어주세요.1. "안심" 프레임 (성분, 만든 사람)2. "일상" 프레임 (먹이는 장면)3. "후기" 프레임 (구매 고객의 목소리)4. "비교" 프레임 (기존 간식과의 차이)5. "FOMO" 프레임 (한정·시즌)
각 카피는 번호 + 카피 본문만 출력. 설명 금지.이렇게 프레임을 5개로 강제하면 LLM이 자연스럽게 다양성을 만들어냅니다. 한 번에 100개를 요청하지 말고 20개씩 5번 돌리면 더 안정적이에요.
운영 단계에서 한 가지 더 — 프롬프트 캐싱(Anthropic의 cache_control, OpenAI의 자동 캐시)을 걸면 페르소나·제약 부분이 재사용되어 토큰 비용이 70~80% 절감됩니다. 하루 수백 캠페인 돌리는 팀에는 필수.
2단계: 임베딩으로 중복 제거하기
100개 중에 사실 의미가 비슷한 카피가 30~40개씩 묶여 있어요. 사람 눈으로 골라내는 건 비효율이고 일관성도 떨어집니다. 임베딩 + cosine similarity로 자동화합니다.
핵심 수식은 두 카피의 의미적 거리:
거리 0이면 두 카피 의미가 거의 같음, 1이면 완전히 다른 컨셉. 거리 0.2 이하면 같은 컨셉으로 묶고 대표 1개만 살린다는 룰만 정하면 끝.
100개 카피를 임베딩한 뒤 Agglomerative Clustering(거리 0.2 임계)을 돌리면 보통 다음과 같은 결과가 나와요.
| 클러스터 | 카피 수 | 대표 카피 |
|---|---|---|
| 0 | 15개 | ”엄마가 먼저 먹어보고 골랐어요” |
| 1 | 22개 | ”6개월부터 시작하는 첫 간식” |
| 2 | 18개 | ”쌀로만 만든 진짜 무염” |
| 3 | 11개 | ”이유식 졸업 후 어떡할지 고민이라면” |
| 4 | 14개 | ”할머니가 좋아하시는 간식” |
| 5 | 12개 | ”성분표를 자세히 보세요” |
| 6 | 8개 | ”재구매율 높은 비밀” |
100개 → 7개 컨셉으로 압축. 다음 단계는 이 7개에 점수를 매기는 일.
3단계: 과거 CTR 데이터로 사전 스코어링
컨셉 7개가 나왔으면, 이제 어느 컨셉이 잘 먹힐지 광고로 태우기 전에 점수를 매겨봅니다. 과거 캠페인 데이터가 있으면 가능해요.
핵심 아이디어: 과거 카피들의 임베딩과 그 카피들의 실제 CTR을 가지고 회귀 또는 nearest-neighbor 모델을 학습. 새 카피의 임베딩을 넣으면 예상 CTR을 출력.
가장 단순한 방법은 k-NN(가장 가까운 이웃) 예측이에요.
새 카피의 임베딩과 가장 가까웠던 과거 카피 개(보통 5)의 실제 CTR 평균을 그대로 예측값으로 씁니다. 해석이 쉬워서 좋아요. “이 카피는 작년 X 캠페인의 카피 5개와 가장 비슷한데, 그것들 평균 CTR이 1.8이었으니 이번에도 비슷할 것”이라고 회의에서 설명할 수 있죠. 데이터가 800개 이상이면 Gradient Boosting Regressor로 한 단계 정밀도 높일 수 있고요.
7개 컨셉 카피의 예측 결과는 다음처럼 나옵니다.
| 컨셉 | 예측 CTR (%) | 순위 |
|---|---|---|
| 쌀로만 만든 진짜 무염 | 2.41 | 1 |
| 엄마가 먼저 먹어보고 골랐어요 | 2.18 | 2 |
| 재구매율 높은 비밀 | 2.05 | 3 |
| 6개월부터 시작하는 첫 간식 | 1.94 | 4 |
| 할머니가 좋아하시는 간식 | 1.32 | 5 |
| 이유식 졸업 후 어떡할지 고민이라면 | 1.18 | 6 |
| 성분표를 자세히 보세요 | 0.94 | 7 |
4단계: A/B 후보 선별
7개 컨셉 × 예상 CTR이 나왔으면, 어떻게 광고에 태울까. 실무 가이드:
| 시나리오 | 후보 수 | 분배 전략 |
|---|---|---|
| 주간 예산 100만 원 미만 | 3~5개 | 상위 점수 컨셉 위주, 컨셉당 1개 |
| 주간 100~500만 원 | 7~10개 | 상위 5컨셉 × 2개 변형, 하위 1~2개 챌린저 |
| 주간 500만 원 이상 | 10~15개 | 모든 컨셉 + 변형, 자동 학습 분배 |
최종 선별 룰 한 줄: 상위 점수 5개 + 하위 점수에서 무작위 1개 = 총 6개를 광고 매니저에 업로드. 컨셉당 변형 12개를 더 만들어 1012개로 확장하면 학습 분배가 안정적이에요.
5단계: 4단계 파이프라인 한 번에 실행하기 (E2E)
각 단계 코드를 하나로 연결해서 실제로 돌릴 수 있는 흐름을 보여줍니다. 실제 환경에서는 각 함수를 라이브러리로 분리하지만, 전체 흐름을 파악하는 데 의사코드로도 충분합니다.
import openaiimport numpy as npfrom sklearn.cluster import AgglomerativeClusteringfrom sklearn.neighbors import KNeighborsRegressor
client = openai.OpenAI()
# 1단계: 카피 양산def generate_copies(brief: str, n: int = 100) -> list[str]: resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": brief}] ) lines = [l.split(". ", 1)[-1].strip() for l in resp.choices[0].message.content.splitlines() if l.strip()] return lines[:n]
# 공통: 임베딩def embed(texts: list[str]) -> np.ndarray: res = client.embeddings.create(model="text-embedding-3-small", input=texts) return np.array([e.embedding for e in res.data])
# 2단계: 중복 제거def deduplicate(copies: list[str], threshold: float = 0.25) -> list[str]: vecs = embed(copies) norms = np.linalg.norm(vecs, axis=1, keepdims=True) dist = 1 - (vecs / norms) @ (vecs / norms).T labels = AgglomerativeClustering( n_clusters=None, distance_threshold=threshold, metric="precomputed", linkage="complete" ).fit_predict(dist) seen, reps = set(), [] for label, copy in zip(labels, copies): if label not in seen: seen.add(label); reps.append(copy) return reps # 보통 7~12개로 압축
# 3단계: k-NN CTR 예측def score_copies(candidates, past_copies, past_ctrs, k=5): knn = KNeighborsRegressor(n_neighbors=k, metric="cosine") knn.fit(embed(past_copies), past_ctrs) preds = knn.predict(embed(candidates)) return sorted(zip(candidates, preds), key=lambda x: -x[1])
# 4단계: 후보 선별def select_candidates(scored, top_n=5): return [c for c, _ in scored[:top_n]] + [scored[-1][0]] # 상위 + 챌린저 1
# E2E 실행if __name__ == "__main__": BRIEF = "..." # 1단계 프롬프트 PAST_COPIES = ["엄마가 직접 고른 간식", "아이 건강 먼저"] PAST_CTRS = [2.1, 1.8]
copies = generate_copies(BRIEF, n=100) unique = deduplicate(copies) scored = score_copies(unique, PAST_COPIES, PAST_CTRS) final = select_candidates(scored) print(f"생성 {len(copies)}개 → 압축 {len(unique)}개 → 최종 {len(final)}개") for c, ctr in scored[:len(final)]: print(f" {ctr:.2f}% {c}")생성 100개 → 압축 7개 → 최종 6개 2.41% 쌀로만 만든 진짜 무염 2.18% 엄마가 먼저 먹어보고 골랐어요 2.05% 재구매율 높은 비밀 1.94% 6개월부터 시작하는 첫 간식 1.32% 할머니가 좋아하시는 간식 0.94% 성분표를 자세히 보세요 ← 챌린저해석: 100개 → 7개 컨셉 → 예측 CTR 순위 → 상위 5개 + 챌린저 1개 = 6개만 광고 매니저에 올립니다. 나머지 94개는 “확인 완료 후 제외”된 것이지, 버린 게 아니에요. 다음 캠페인에서 성분표 프레임 카피를 다시 시도할 때 이 데이터가 baseline이 됩니다.
6단계: 운영 팁 — 실무에서 부딪히는 것들
1) 임베딩 모델 선택
OpenAI text-embedding-3-small이 가성비 좋고, 한국어도 충분히 잘 합니다. 더 좋은 한국어 성능이 필요하면 BGE-M3 (오픈소스) 같은 multilingual 모델 시도해볼 만해요.
2) 과거 데이터가 없을 때
신규 브랜드라 과거 캠페인이 없으면, 3단계는 건너뛰고 12단계 + 4단계의 편의적 분배(컨셉당 12개)만 적용. 첫 한 달 데이터 쌓이면 그때부터 학습.
3) 사람 검수는 빼지 마라
자동화의 마지막 안전장치는 사람 눈입니다. 정책 위반(가격 명시, 비교 광고), 브랜드 톤 이탈은 모델이 못 거르는 게 많아요. 광고 업로드 전에 1분만 훑기 룰 권장.
4) 카피의 다국어 변환은 따로
같은 캠페인을 영어·일본어로 확장할 때, LLM에 “한국어 카피를 영어로 번역해줘”는 잘 안 됩니다. 처음부터 각 언어의 페르소나·제약으로 다시 양산하세요. 번역은 의미는 옮기지만 광고 톤은 못 옮겨요.
마치며
LLM은 카피 작가의 일을 뺏는 도구가 아니라 카피 작가의 손을 100배로 늘리는 도구입니다. 양산은 LLM이 하지만, 컨셉 정의·페르소나 설계·최종 검수는 여전히 사람의 일이에요. 이 4단계 파이프라인을 한 번 만들어두면, 다음 캠페인부터는 카피라이터가 컨셉 5개만 적어줘도 광고에 태울 후보 10~15개가 자동으로 나옵니다.
다음에 읽으면 좋은 글:
- 임베딩 기초 — 2단계 중복 제거에서 쓰는 cosine similarity의 원리
- 크리에이티브 피로도 측정 — 카피가 반복될수록 CTR이 떨어지는 것을 임베딩으로 추적
- LLM-as-judge — 예측 CTR 대신 LLM이 카피 품질을 평가하게 하는 방법
참고
- Anthropic Prompt Caching — 페르소나·제약 캐싱
- OpenAI Embeddings 공식 문서 — text-embedding-3 시리즈
- BGE-M3 (BAAI) — 한국어 포함 multilingual 임베딩, 오픈소스
- scikit-learn Agglomerative Clustering — 카피 클러스터링
- Meta Ads — Creative Best Practices — 광고 매니저 학습 분배 작동 원리
AI·LLM 카테고리의 다른 글
전체 보기 →-
2026·05·16
LLM 운영 비용 폭주를 막는 6가지 guardrail — 마케팅 자동화의 cost·latency·품질 동시 관리
LLM을 운영에 올리면 어느 날 갑자기 비용이 10배로 튑니다. retry storm·프롬프트 폭증·모델 자동 승격·context 누적 등 폭주 패턴 6가지와 그것을 막는 guardrail을 정리합니다.
-
2026·05·10
LLM evaluation harness — 분기마다 챗봇 품질을 자동 평가하는 공장
챗봇·에이전트가 운영에 들어가면 한 번 평가가 아니라 분기 자동 평가가 필요합니다. 골든셋·regression·hyperparameter A/B를 묶는 evaluation harness 설계와 마케팅 자리에서의 적용.
-
2026·05·09
Context engineering — 200k 토큰 컨텍스트의 설계 원칙 5가지
컨텍스트 창이 200k 토큰까지 커졌지만 단순히 다 넣으면 lost-in-the-middle·비용 폭발·정확도 하락이 옵니다. 마케팅 자동화에 적용하는 5가지 컨텍스트 설계 원칙.
-
2026·05·09
Function calling 설계 패턴 — LLM이 도구를 부를 때 마케터가 점검할 것
LLM이 광고 API·BigQuery·Slack을 직접 부르기 시작하면, 답변 품질보다 "어느 도구를 언제 부를지"가 운영 사고의 진앙이 됩니다. function calling의 한 줄 직관과 마케터가 점검할 5가지.