본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 26. 17:55

PHP로 뉴스룸 AI 모듈 구축하기: 50개 이상의 특화된 워크플로우 (Workflows)

요약

뉴스룸 환경에서 범용 채팅창 대신, 작고 구체적인 50개 이상의 특화된 AI 워크플로우를 PHP로 구축하는 아키텍처를 제안합니다. 프롬프트 단위가 아닌 고정된 규약을 가진 '태스크(Task)' 단위로 설계하여 비용, 오류 처리, UX를 최적화하는 방법을 다룹니다.

핵심 포인트

  • 단일 거대 프롬프트 대신 세분화된 태스크(Task) 단위 설계 권장
  • 태스크 내부에서 출력 형식을 스스로 검증하는 파싱 로직 필수
  • 작업별 특성에 따른 비용 계층 및 오류 처리 전략 차별화
  • PHP 인터페이스를 활용한 확장 가능한 워크플로우 레지스트리 구축

사람들이 "뉴스룸에서의 AI"를 떠올릴 때, 대개 편집기에 커다란 채팅창 하나가 붙어 있는 모습을 상상합니다. 그것은 잘못된 사고 모델이며, 대부분의 그러한 기능들이 두 번 사용된 후 무시되는 이유이기도 합니다.

뉴스룸에는 단 _하나_의 AI 니즈(need)가 있는 것이 아닙니다. 수십 개의 작고 구체적이며 반복적인 니즈가 존재합니다: 세 가지 헤드라인 옵션 작성, 280자 이내의 소셜 문구 추출, 카테고리 제안, 엔티티(entity) 태깅, 대표 이미지의 대체 텍스트(alt text) 생성, 명예훼손 위험 플래그 표시, 데크(dek)를 영어로 번역, 60자 미만의 SEO 제목 제안 등입니다. 각각은 고유한 입력 형태, 고유한 출력 규약(contract), 그리고 오류에 대한 고유한 허용 범위를 가진 아주 작은 작업들입니다.

200개 이상의 뉴스 사이트에 이를 적용해 본 결과, 실제로 정착된 아키텍처는 "하나의 AI 기능"이 아니었습니다. 그것은 통일된 인터페이스 뒤에 있는 **작고 특화된 워크플로우(workflows)의 레지스트리(registry)**였습니다. 이 글은 이를 PHP로 구축하는 방법과, 어떤 모델을 선택하느냐보다 더 중요한 몇 가지 결정 사항들에 대해 다룹니다.

작업의 단위: 프롬프트(prompt)가 아닌 태스크(task)

가장 먼저 바로잡아야 할 것은 경계입니다. 재사용 가능한 단위는 자유롭게 떠다니는 프롬프트(prompt) 문자열이 아니라, 고정된 규약을 가진 이름이 지정된 워크플로우인 **태스크(task)**입니다. 태스크는 세 가지를 알고 있습니다: 무엇이 필요한지, 무엇을 반환하기로 약속했는지, 그리고 비용이 어느 정도까지 허용되는지입니다.

interface AiTask
{
    public function name(): string;          // 'headline.suggest'
...

parse() 메서드는 팀들이 건너뛰고 나중에 후회하는 부분입니다. JSON을 기대했는데 모델이 산문(prose)을 반환하는 것은 예외적인 상황이 아니라, 흔히 일어나는 일입니다. 모든 태스크가 자신의 출력을 스스로 검증할 책임을 진다면, 잘못된 응답이 편집기로 잘못된 데이터를 유출하는 대신, 재시도하거나 폴백(fallback)할 수 있는 _태스크 내부_에서 실패하게 됩니다.

구체적인 태스크는 다음과 같습니다:

final class HeadlineSuggestTask implements AiTask
{
    public function name(): string { return 'headline.suggest'; }
...

이런 태스크를 하나 만들고 나면, 50개를 만들 수 있는 형태가 갖춰집니다. 흥미로운 작업은 50번째 태스크를 작성하는 것이 아니라, 그들을 둘러싼 메커니즘을 만드는 것입니다.

"50+"이 자랑이 아닌 아키텍처 선택인 이유

숫자가 중요한 것이 아니라,
_세분성 (Granularity)_이 핵심입니다. "헤드라인 제안", "SEO 타이틀 제안", "소셜 문구 제안"을 세 가지를 모두 반환하는 하나의 거대한 프롬프트 (Mega-prompt)로 합칠 수도 있습니다. 하지만 그렇게 하지 마십시오. 세 가지 이유가 있습니다.

  • 서로 다른 비용 계층 (Cost tiers). 카테고리 제안은 빠르고 저렴한 모델에서 실행될 수 있습니다. 하지만 법적 리스크 플래그 (Legal-risk flag)는 그렇게 해서는 안 됩니다.
  • 서로 다른 오류 처리 (Failure handling). 헤드라인 생성이 실패하면, 그냥 어깨를 으쓱하고 편집자가 직접 입력하면 그만입니다. 하지만 엔티티 태깅 (Entity tagging)이 조용히 실패한다면, 아카이브 검색 기능은 소리 없이 망가집니다.
  • 서로 다른 UX 접점 (UX surfaces). 소셜 문구는 소셜 스케줄러에 속하며, 대체 텍스트 (Alt text)는 이미지 선택기에 속합니다. 이들을 하나의 호출 (Call)로 결합하는 것은 서로 관련 없는 두 화면을 결합하는 것과 같습니다.

작은 태스크들은 구성(Compose)되지만, 거대한 프롬프트는 경직(Calcify)됩니다.

다음은 나타난 대략적인 분류 체계입니다. 그룹화는 편집자들이 실제로 태스크를 찾는 방식이기에 그룹별로 묶었습니다.

그룹예시 태스크
헤드라인 및 프레이밍 (Headlines & framing)헤드라인 옵션, SEO 타이틀, 소셜 문구, 푸시 알림 텍스트
...

언어별, 섹션별 변형을 계산하기 전에도 이미 25개 이상입니다. 레지스트리 (Registry)는 이것이 혼돈으로 변하지 않도록 유지해 주는 역할을 합니다.

레지스트리 (Registry)와 라우터 (Router)

레지스트리는 의도적으로 지루하게 설계되었습니다. 즉, 이름과 태스크를 매핑하는 지도입니다. 라우터는 진정으로 가치 있는 아이디어가 살아있는 곳입니다. 바로 느낌(Vibes)이 아닌 계층(Tier)에 따른 라우팅입니다.

final class AiRouter
{
    /** @param array<string, AiTask> $tasks */
...

여기서 세 가지 요소가 실제로 작업을 수행하며, 각각은 그 자리에 있을 가치가 있습니다.

  1. (task, input) 해시를 통한 캐싱 (Caching by (task, input) hash). 동일한 기사 본문에 대한 헤드라인 제안은 단 한 번만 수행됩니다. 편집자들은 이 버튼들을 반복해서 클릭하는데, 캐시가 없다면 불안해서 다시 클릭할 때마다 비용을 지불해야 합니다. 이 단일 계층이 우리가 측정한 가장 큰 비용 절감 요소였습니다.
  2. 잘못된 출력에 대한 계층 기반 에스컬레이션 (Tier-based escalation on bad output). 저렴한 모델을 먼저 사용합니다. 만약 모델이 parse()에 실패하는 쓰레기 값을 반환하면, 더 강력한 계층(tier)에서 한 번 더 재시도합니다. 저렴한 모델의 실패 대부분은 포맷팅 실패이며, 더 나은 모델에서는 이러한 문제가 반복되지 않습니다. 이를 통해 저렴한 모델의 경제성을 유지하면서도, 예외적인 상황(tail)에서는 프리미엄 모델의 신뢰성을 얻을 수 있습니다.
  3. 게이트웨이가 제공자(provider)를 숨깁니다. complete(tier, messages)는 작업(task)이 접하는 전체 인터페이스입니다. 이번 달에 'cheap'이 특정 제공자에 매핑되었다가 다음 달에 다른 제공자로 바뀌더라도, 이는 운영상의 결정일 뿐 50개의 작업 코드를 수정해야 하는 작업이 아닙니다.

게이트웨이: 모든 제공자를 위한 단일 접점

게이트웨이는 제공자의 다양성을 감당 가능하게 만드는 핵심입니다. 뉴스 업무는 폭발적으로 몰리는 특성이 있고 속도 제한(rate limits)은 실재하기 때문에, 작업 코드를 건드리지 않고도 제공자 간에 계층(tier)을 이동할 수 있는 자유가 필요합니다.

final class ModelGateway
{
    public function __construct(private array $tierConfig) {}
...

이러한 방식의 보상은 건축적 우아함 그 자체를 위한 것이 아니라 운영상의 이점입니다. 선거일 오전 9시에 특정 제공자의 성능이 저하될 때 — 실제로 그런 일이 발생할 것입니다 — 배포를 새로 하는 것이 아니라 설정 맵(config map)만 변경하면 됩니다. 모델의 정체성을 작업(task)에서 분리하여 계층 설정(tier config) 안에 두는 것이, 5분 만에 문제를 완화하느냐 아니면 패닉에 빠지느냐의 차이를 만듭니다.

품질 게이트: 저렴한 모델은 자신 있게 거짓말을 합니다

특화된 작업(specialized-task) 설계는 각 작업이 작기 때문에 모든 곳에 저렴한 모델을 사용하고 싶은 유혹을 느끼게 합니다. 함정은 작은 작업이라 할지라도 여전히 자신 있게 틀린 출력을 내놓는다는 점입니다. 이에 대한 방어책은 **비결정론적 출력(non-deterministic output)에 대한 결정론적 게이트(deterministic gates)**를 두는 것입니다. 즉, 또 다른 모델이 아니라 코드가 지루한 제약 조건들을 검사하게 하는 것입니다.

function validateHeadlines(array $headlines): array
{
    return array_values(array_filter($headlines, function (string $h): bool {
...

해당 함수에는 모델이 포함되어 있지 않다는 점에 주목하세요. 비용이 많이 드는 판단("이것이 좋은 헤드라인인가?")은 인간 편집자의 몫으로 남겨둡니다. 저렴하고 기계적인 판단("이것이 헤드라인 형태의 유효한 문자열인가?")은 마이크로초 단위로 실행되며 절대 환각 (Hallucination)을 일으키지 않는 순수 PHP의 영역입니다. 코드로 표현할 수 있는 모든 제약 조건은 프롬프트 (Prompt)에서 제거하여 게이트 (Gate)로 옮기세요. 프롬프트는 취향을 위한 것이고, 코드는 규칙을 위한 것입니다.

프리미엄 모델을 의도적으로 사용해야 할 유일한 곳은 리스크 (Risk) — 명예훼손, 민감한 주장, 잘못된 판단이 법적 무게를 갖는 모든 상황입니다. 해당 작업은 가장 강력한 티어 (Tier)에서 실행해야 하며, "괜찮아 보임"이라는 판정을 절대 오래 캐싱 (Cache)해서는 안 되며, 항상 인간에게 권고 (Advisory) 형태로 제시해야 합니다. AI는 플래그 (Flag)를 표시하고, 결정은 사람이 합니다.

기본적으로 비동기 방식 (Asynchronous by default)

편집자들은 버튼 하나를 누르고 4초 동안 기다려주지 않습니다. 대략 1초보다 느린 모든 작업은 백그라운드 (Background)에서 처리되어야 하며, 저장을 차단하는 대신 준비가 되었을 때 결과가 도착하도록 해야 합니다.

// 저장 시: 차단하지 말고 큐 (Queue)에 삽입하세요.
$queue->push('ai.enrich', [
    'article_id' => $id,
...

우리의 고통을 실제로 줄여준 두 가지 규칙:

  • 제안은 초안이며, 절대 조용히 쓰여서는 안 됩니다. AI 출력물은 "제안됨 (Suggested)" 상태로 들어갑니다. 인간이 이를 수락합니다. 모델이 발행 필드에 직접 쓰도록 허용하는 날은, 편집국장에게 환각된 날짜 정보를 설명해야 하는 날이 될 것입니다.
  • 멱등성 (Idempotent) 작업. 큐는 재시도합니다. 만약 summary.make가 두 번 실행된다면, 두 번째 실행은 중복을 생성하는 것이 아니라 동일한 제안 슬롯을 덮어써야 합니다. 쓰기 작업의 키를 (article_id, task_name)으로 설정하세요.

과거의 나에게 해주고 싶은 말

  • 프롬프트보다 작업 계약(Task Contract)을 먼저 모델링하세요. 인터페이스는 특정 모델보다 더 오래 지속됩니다.
  • 작업 내부에서 출력을 검증하세요. 잘못된 형식의 응답은 흔한 일입니다. 이를 세계관에 대한 예외가 아닌, 제어 흐름 (Control Flow)의 일부로 취급하세요.
  • 티어(Tier)별로 라우팅하고, 입력값별로 캐싱하세요. 이 두 가지를 결합하는 것이 그 어떤 기발한 프롬프트 엔지니어링 (Prompt-engineering)보다 비용과 신뢰성 측면에서 더 큰 효과를 발휘했습니다.
  • 규칙은 코드에, 취향은 프롬프트에 담으세요. 결정론적 (Deterministic)으로 확인할 수 있는 모든 제약 조건은 모델이 위반할 수 없는 제약 조건입니다.
  • 비동기(Async), 권고(Advisory), 멱등성(Idempotent). 뉴스룸은 제안을 할 뿐 결코 예기치 못한 동작을 하지 않는 도구를 신뢰합니다.

'50+'라는 숫자는 슬라이드에 넣기 위한 기능 개수가 아닙니다. 각 AI 작업 (AI Job)이 단일하고 명확한 계약을 가질 수 있을 만큼 충분히 작아지면 자연스럽게 도출되는 결과입니다. 이음새(Seam) — 작업 (Task), 레지스트리 (Registry), 라우터 (Router), 게이트웨이 (Gateway), 게이트 (Gate) — 를 구축해 두면, 51번째 워크플로우 (Workflow)를 추가하는 것은 하나의 프로젝트가 아니라 단지 오후 시간의 작업일 뿐입니다.

우리는 Alesta WEB의 실제 뉴스 소프트웨어 프로덕션 환경에서 규모가 매우 다양한 발행사들을 대상으로 이 패턴을 지속적으로 개선해 왔으며, 다음과 같은 교훈을 반복해서 얻고 있습니다. 뉴스룸 AI를 단순한 눈속임이 아닌 신뢰할 수 있는 도구로 만드는 것은 모델이 아니라 바로 아키텍처 (Architecture)라는 사실입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0