4 min read

Part 1. Prompt는 인터페이스다: 시스템 경계와 계약으로 다시 보기

LLM 기능의 본질은 프롬프트 문장 자체가 아니라 경계, 계약, 상태, 실패 처리에 있다는 점을 시스템 관점으로 정리한다.

LLM 기능을 제품에 붙일 때 초기에 가장 눈에 들어오는 것은 프롬프트다. 실제로 데모 단계에서는 프롬프트 한두 줄 수정으로 체감 품질이 크게 바뀐다. 문제는 이 경험이 운영 환경에서도 그대로 통한다고 믿기 쉽다는 점이다. 운영 환경에서의 실패는 대부분 문장 선택보다 경계 설계와 계약 부재에서 시작된다.

문제 제기

현업에서 자주 보는 실패 패턴은 다음과 같다.

  • 프롬프트를 수정했더니 응답 포맷이 바뀌어 다운스트림 파서가 연쇄 실패한다.
  • 모델이 정상이더라도 외부 툴 호출이 타임아웃되어 전체 요청 SLA가 깨진다.
  • 같은 사용자 요청이 재시도로 두 번 실행되어 결제/예약 같은 부작용(side effect)이 중복 발생한다.
  • 운영 로그에는 "LLM 호출 실패"만 남아 있고, 어느 단계에서 어떤 정책이 깨졌는지 추적이 안 된다.

여기서 공통 원인은 모델의 지능 부족이 아니다. "입력-출력 계약(contract), 상태(state), 실패 정책(policy)"가 시스템으로 정의되지 않았다는 점이다.

실전 예시 A: 고객센터 답변 자동화

고객센터에서 LLM이 JSON 형태로 답변 초안을 반환하고, 이후 규칙 엔진이 금칙어 검사와 톤 교정을 수행한다고 가정하자. 프롬프트만으로 포맷을 강제하면 모델 업데이트나 컨텍스트 변화로 필드 누락이 발생할 수 있다. 이때 금칙어 검사기가 answer.body를 찾지 못해 전체 처리 파이프라인이 중단된다.

실전 예시 B: 운영자용 SQL 분석 에이전트

운영자 질문을 받아 SQL을 생성하고 실행하는 에이전트에서 프롬프트를 "자유롭게 생각해도 된다" 쪽으로 튜닝했더니, 안전 가드 없이 DELETE 쿼리가 도구 실행 단계로 넘어갔다. 프롬프트가 아니라 정책 게이트가 없었던 것이 본질 문제다.

핵심 개념

핵심은 LLM을 "똑똑한 함수"로 보지 않고 "확률적 컴포넌트(stochastic component)"로 취급하는 것이다. 그러면 시스템은 아래 원칙으로 설계된다.

  1. 프롬프트는 계약을 전달하는 매개체다. 계약 그 자체가 아니다.
  2. 계약은 코드와 스키마로 검증 가능해야 한다.
  3. 부작용은 상태 머신과 멱등성(idempotency)으로 제어한다.
  4. 실패는 예외가 아니라 정상 흐름의 한 분기다.
레이어책임변경 빈도테스트 방식
Prompt Layer의도 전달, 스타일 제어높음오프라인 샘플 평가
Contract Layer입출력 타입/제약중간스키마 검증, 계약 테스트
Orchestration Layer상태 전이, 재시도, 타임아웃중간시뮬레이션, 장애 주입 테스트
Policy Layer권한, 데이터 접근, 도구 실행 제한낮음(엄격)보안 테스트, 회귀 테스트
Observability Layer추적, 비용, 품질 신호 수집중간지표 경보 검증

아키텍처 관점

Mermaid diagram rendering...

이 구조에서 프롬프트는 Model Runtime에 전달되지만, 시스템의 신뢰성은 Contract Validator, Policy Guard, State Store에서 결정된다.

실전 패턴

패턴 1: Typed Contract + Strict Parser

프롬프트에 "JSON으로 답해"라고 쓰는 것만으로는 부족하다. 시스템은 스키마 검증 실패를 1급 이벤트로 다뤄야 한다.

import { z } from "zod";

const AnswerSchema = z.object({
  answer: z.string().min(20),
  confidence: z.number().min(0).max(1),
  citations: z.array(z.string()).max(5),
  safe_to_send: z.boolean(),
});

type Answer = z.infer<typeof AnswerSchema>;

export function parseModelOutput(raw: string): Answer {
  const parsed = JSON.parse(raw);
  return AnswerSchema.parse(parsed);
}

운영 포인트:

  • 파싱 실패율을 모델 품질 지표와 분리해 별도 경보로 운영한다.
  • 실패 시 "재질문(re-ask)"과 "폴백 템플릿"의 선택 규칙을 명시한다.
  • 파싱 실패 원문은 민감정보 마스킹 후 샘플링 저장한다.

패턴 2: Orchestrator 상태 머신 + 멱등 키

LLM 호출은 종종 외부 툴과 결합된다. 이때 단순 함수 체인보다 상태 전이 기반 설계가 안정적이다.

type Phase = "RECEIVED" | "VALIDATED" | "MODEL_CALLED" | "TOOL_EXECUTED" | "COMPLETED" | "FAILED";

type JobState = {
  requestId: string;
  phase: Phase;
  retries: number;
  idempotencyKey: string;
};

export async function runJob(state: JobState) {
  if (await alreadyCompleted(state.idempotencyKey)) {
    return { status: "deduped" };
  }

  const validated = await validateInput(state);
  const modelResult = await callModelWithTimeout(validated, 3000);
  const safeResult = await enforcePolicies(modelResult);
  const toolResult = await executeToolSafely(safeResult);

  await markCompleted(state.idempotencyKey, toolResult);
  return { status: "ok", toolResult };
}

운영 포인트:

  • idempotencyKey는 사용자 입력 해시 + 업무 키로 생성한다.
  • 각 phase별 latency/error를 분리 수집해 병목 구간을 찾는다.
  • 도구 실행은 별도 큐로 분리해 모델 지연과 격리한다.

실패 사례/안티패턴

장애 시나리오: "프롬프트 핫픽스 후 응답 파싱 폭증"

상황:

  • 금요일 20:10에 프롬프트를 긴급 수정해 답변 품질은 좋아졌지만, JSON 키 이름이 citations에서 references로 변했다.
  • 20:14부터 파서 실패율이 2%에서 37%로 급등했다.
  • 재시도 로직이 동일 프롬프트를 그대로 사용해 실패를 증폭했다.

탐지:

  1. contract_parse_error_rate 경보 발생
  2. trace에서 MODEL_CALLED -> VALIDATION_FAILED 구간 집중 확인
  3. 배포 SHA와 프롬프트 버전 매핑으로 변경 지점 식별

완화:

  1. 파서 호환 계층에서 references -> citations 임시 매핑
  2. 재시도 비활성화 및 폴백 응답 비율 상향
  3. 신규 트래픽 20%를 이전 프롬프트 버전으로 즉시 라우팅

회복:

  1. 프롬프트/계약 버전 동기화 규칙 추가
  2. 계약 변경 없는 핫픽스만 허용하는 배포 게이트 적용
  3. 회귀 테스트에 "키 이름 변경" 시나리오 추가

대표 안티패턴

  • "좋은 프롬프트 하나"로 문제를 끝내려는 접근
  • 계약 없이 자연어 결과를 바로 도구 실행에 연결
  • 실패를 재시도로만 덮고 원인 분류를 생략
  • 프롬프트 변경을 코드 변경보다 가볍게 취급

체크리스트

내일 바로 적용할 수 있는 운영 체크리스트다.

  • 프롬프트 버전과 계약 버전을 분리해 저장하고 추적하는가?
  • 모델 출력은 항상 스키마 검증을 통과해야 다음 단계로 가는가?
  • 스키마 실패, 정책 실패, 툴 실패를 별도 지표로 수집하는가?
  • 툴 실행에는 권한 컨텍스트와 allow-list가 강제되는가?
  • 부작용 작업(결제, 예약, 발송)은 멱등 키를 사용하는가?
  • 재시도 정책이 "같은 요청 반복"이 아닌 "분기별 정책"으로 설계되어 있는가?
  • 장애 시 프롬프트 롤백 경로와 시스템 롤백 경로가 분리되어 있는가?

요약

프롬프트 엔지니어링은 중요하지만, 운영 안정성의 핵심은 아니다. 운영에서 승부처는 계약, 상태, 정책, 관측성이다. 프롬프트는 시스템의 한 레이어로 다뤄야 하며, 나머지 레이어가 설계되지 않으면 품질 개선은 우연에 머문다.

다음 편 예고

다음 편에서는 "품질(Quality)은 프롬프트가 아니라 평가 시스템에서 나온다"를 다룬다. 오프라인 벤치마크, 온라인 지표, 휴먼 피드백 루프를 어떻게 한 체계로 묶어야 하는지 설명한다. 특히 "정확도는 올랐는데 CS 불만이 증가하는" 역설이 왜 생기는지와, 이를 탐지하는 평가 지표 설계법을 사례로 정리한다.


이전 편: 시리즈 시작

다음 편: Part 2. 품질은 Prompt가 아니라 평가 루프에서 나온다

댓글