‘John Smith’ 문제: 팟캐스트 게스트 출연을 오탐 없이 감지하기

발행: (2026년 6월 9일 AM 02:13 GMT+9)
10 분 소요
원문: Dev.to

Source: Dev.to

나는 쇼 때문에 팟캐스트를 듣는 것이 아니라 사람 때문에 듣는다. 내가 좋아하는 연구자나 창업자가 누군가의 팟캐스트에 나올 때, 그 에피소드 하나만 듣고 싶지만 그들이 등장할 수 있는 모든 쇼의 400개 에피소드를 구독하고 싶지는 않다.
그런 버튼은 어디에도 없다. 그래서 나는 직접 만들었다: GuestVine. 사람을 팔로우하면, 그 사람이 어떤 팟캐스트에 게스트로 등장하든 그 단일 에피소드가 한 번만 구독하는 커스텀 RSS 피드에 들어가며, 이미 사용하고 있는 플레이어에서 재생한다.

재미있는 부분은 웹 앱이 아니라 탐지다. “이 사람이 이 에피소드에 게스트로 나왔나요?”는 겉보기엔 간단해 보이지만 절대 그렇지 않다. 내가 어떻게 만들었는지 보라.

새 플레이어도 없고, 오디오를 재호스팅도 하지 않는다. 전체 흐름은 RSS 입력 → RSS 출력이다:

[Podcast Index] --> [Detection Pipeline] --> [Postgres] --> [Feed Service] --> your RSS URL
                                                  ^
                                         [Control Panel]  [you]

우리가 내보내는 피드 아이템은 원본 퍼블리셔의 오디오 파일을 가리킨다. 사이트에서 인라인으로 재생하거나, 피드를 구독한 팟캐스트 앱에서 재생할 수 있지만, 우리는 오디오를 재호스팅하지 않는다: 모든 enclosure는 퍼블리셔 자체 파일이며 CDN에서 제공된다. 우리는 피드에 무엇을 넣을지만 결정한다. 즉, 인간 개입 없이 대규모로 정확히 한 질문에 답해야 한다.

예를 들어 John Smith를 팔로우한다고 하자. Podcast Index에서 후보 에피소드를 끌어와 각각을 분류해야 한다. 실패 모드는 여기저기 있다:

  • 그의 이름이 게스트라서 제목에 들어 있음. ✅
  • 호스트가 잠깐 언급해서 설명에 이름이 들어 있음. ❌
  • 다른 John Smith에 관한 에피소드 제목에 이름이 들어 있음. ❌
  • 구조화된 <person> 태그에 그가 게스트로 명시돼 있음. ✅

단순 문자열 매치는 쓰레기를 만든다. 그래서 탐지는 세 단계: 매치 → 스코어 → 검증.

매치 신호

증거마다 무게가 다르다. 우선순위대로 매치하고 어떤 신호가 작동했는지 기록한다:

export type MatchSignal =
  | "person_tag"          //  — structured, strongest
  | "title_guest"         // full name in TITLE + a guest cue ("with", "feat.")
  | "title_plain"         // full name in TITLE, no cue
  | "description_guest"   // full name in DESCRIPTION + guest cue
  | "description_plain";  // full name in DESCRIPTION, no cue (weakest)

골드 스탠다드는 팟캐스트 네임스페이스의 <person> 태그다 — 퍼블리셔가 명시적으로 “이 사람은 게스트였습니다”라고 적은 구조화 메타데이터. 이것이 있으면 추측이 사라진다. 보통은 없으니 텍스트에 의존하고, “guest cue” 단어( with, featuring, ft, joins, sits down with, in conversation with )를 활용해 이름 언급과 게스트를 구분한다.

각 신호마다 기본 신뢰도가 있다:

const SIGNAL_SCORE: Record<MatchSignal, number> = {
  person_tag: 0.98,
  title_guest: 0.9,
  title_plain: 0.6,
  description_guest: 0.55,
  description_plain: 0.3,
};

흔한 이름에 대한 페널티

두 개의 매우 흔한 토큰으로 이루어진 이름—“John Smith”, “Mike Jones”—은 “Lex Fridman”보다 우연히 매치될 확률이 훨씬 높다. 그래서 흔한 이름은 페널티를 부과한다:

function commonNamePenalty(name: string): number {
  const tokens = name.toLowerCase().split(/\s+/).filter(Boolean);
  if (tokens.length === 0) return 0;
  const commonCount = tokens.filter(t => COMMON_TOKENS.has(t)).length;
  if (commonCount >= 2) return 0.2;   // "john smith" — heavy damp
  if (commonCount === 1) return 0.08; // "john fridman" — light damp
  return 0;
}

핵심은 person_tag 매치에는 이 페널티가 적용되지 않는다 — 퍼블리셔가 구조적으로 게스트를 태그했으면 이름이 얼마나 흔하든 신뢰한다. 페널티는 실제로 우연 매치가 가능한 퍼지 텍스트 신호에만 적용된다.

스코어와 티어

스코어는 세 단계로 압축되고, 티어에 따라 행동이 결정된다:

let tier: Tier;
if (score >= 0.8) tier = "A";       // auto-deliver
else if (score >= 0.4) tier = "B";  // hold for verification
else tier = "C";                    // drop, silently
티어의미동작
A구조화된 태그 또는 제목에 손님 맥락자동 전달
B이름은 존재하지만 애매함보류; 전달 전 검증
C잠깐 언급 / 신호 약함버림

제품 결정

여기서 내린 제품 결정: 엄격하게 시작한다. 오직 Tier A만 자동 전달한다. 놓친 등장(오류)은 눈에 보이지 않는다—그 존재 자체를 몰랐을 뿐이다. 잘못된 등장(오류)은 크게 울리며 피드를 “쓰레기”라고 판단하게 만들고 구독을 취소한다. 신뢰 기반 제품에서는 정밀도가 재현성보다 항상 우선한다. 나는 오히려 전달을 적게 해서 신뢰성을 유지하고 싶다.

Tier B는 흥미로운 중간 단계다—실제 신호이지만 애매함. 나는 이를 단순히 버리기보다, 에피소드 메타데이터와 인물 구분 컨텍스트를 함께 LLM(Claude)에게 넘겨 한 가지 좁은 질문을 한다: “이 에피소드는 특정 인물이 게스트로 나온 것으로 plausibly 보이는가?” 매치가 강화되면 전달하고, 그렇지 않으면 보류한다.

핵심 제약: LLM은 타이브러커일 뿐, 파이프라인 자체는 아니다. Tier A(필요 없음)와 Tier C(비용 대비 가치 없음)에는 절대 보낸다 않으며, 오직 진짜 애매한 중간 밴드만 판단한다. 이렇게 하면 비용을 제한하고, 결정적인 90%는 deterministic scoring이 담당한다.

역할이 명시되지 않은 경우 기본값은 “host”(호스트)이며 “guest”(게스트)가 아니다. 스펙에 따라 역할이 없으면 호스트로 간주한다. 이걸 뒤집으면 모든 호스트를 게스트로 전달하게 되어, 가장 신뢰받는 신호에서 대량의 false positive가 발생한다.

플레이어는 RSS를 aggressively 캐시한다. “새 에피소드가 왜 안 보이나요?”는 거의 항상 플레이어 때문이지 내가 만든 피드 때문이 아니다. 이를 알면 피드 생성기를 한 시간 디버깅하는 시간을 절약한다.

전체 시스템은 네트워크 없이도 테스트 가능하다. 매치와 스코어는 정규화된 에피소드 구조체에 대한 순수 함수이므로, 테스트 스위트는 기록된 fixture에 대해 실행된다—API 키도 없고, 불안정성도 없다. 위 탐지 로직은 모두 순수 Vitest 단위 테스트로 커버돼 있어, 페널티 튜닝이 안전했다.

사용 기술 스택

  • Next.js (App Router) – 컨트롤 패널, API, RSS 서빙
  • Postgres + Prisma – 사람/피드/에피소드/등장 기록 및 팬아웃
  • 비밀번호 없는 인증 (magic link + 이메일 OTP)
  • 위에 소개한 탐지 워커 – cron 기반 실행
  • Claude – Tier‑B 검증기
  • Vitest – 매치/스코어/피드 로직 테스트

GuestVine의 핵심은 이 정밀‑우선 탐지다

0 조회
Back to Blog

관련 글

더 보기 »