AI 견적 가드레일을 위한 테이블 기반 테스트 (Table-driven tests)
요약
AI가 생성하는 견적의 정확성을 보장하기 위해 프롬프트에 의존하는 대신, 결정론적인 코드와 테이블 기반 테스트를 활용하는 전략을 제안합니다. 모델은 자연어 변환 역할만 수행하고, 핵심 가격 규칙은 검증 가능한 일반 코드로 관리해야 합니다.
핵심 포인트
- 가격 결정 로직은 프롬프트가 아닌 결정론적 함수로 분리할 것
- 모델 출력값은 신뢰할 수 없는 입력으로 간주하고 검증할 것
- 프롬프트 스냅샷 대신 테이블 기반 테스트로 비즈니스 규칙 검증
- 감사(Audit)를 위해 결정 사유와 정책 버전을 함께 저장할 것
저는 Auto-Respond 팀에서 일하고 있습니다. 이 글은 저희가 AI 리드 처리(lead handling) 과정에서 사용하는 일반적인 테스트 패턴에 대해 설명합니다. 코드는 가짜 서비스(fake services)와 가짜 리드(fake leads)를 사용하며, 고객 데이터는 포함되어 있지 않습니다.
모델은 완전히 틀린 내용임에도 불구하고 설득력 있는 견적을 작성할 수 있습니다.
이것이 영업 워크플로우(sales workflow)에서 생성된 텍스트를 사용할 때 발생하는 불편한 부분입니다. 문구는 세련되어 보일 수 있지만, 숫자는 근거 없이 나왔거나, 서비스 점검이 필요하거나, 고객이 설정된 범위(scope) 밖의 작업을 요청했을 수도 있습니다.
저는 더 큰 프롬프트(prompt)가 이 문제를 해결할 수 있다고 생각하지 않습니다. 가격 규칙(Price rules)은 검토와 테스트가 가능한 일반 코드(ordinary code)에 있어야 합니다.
산문(prose)보다 결정을 앞세우세요
모델이 견적 허용 여부를 결정해서는 안 됩니다. 그 작업은 작고 결정론적인 함수(deterministic function)에 맡기세요. 모델은 정책 체크(policy check)를 통과한 후, 승인된 결정을 자연어(natural language)로 변환하는 역할만 수행하면 됩니다.
의도적으로 작게 만든 TypeScript 형태는 다음과 같습니다:
export type PriceRule = {
service: string;
mode: "fixed" | "range" | "inspection";
...
유용한 제약 사항은 반환 타입(return type)에 있습니다. 호출자가 모델 출력값으로부터 채울 수 있는 자유 형식의 price 필드는 존재하지 않습니다.
정책 함수를 지루하게 유지하세요
여기서 '지루하다'는 말은 찬사입니다.
export function decideQuote(
request: QuoteRequest,
rules: PriceRule[],
...
이 함수는 인간처럼 말하려고 노력하지 않습니다. 오직 한 가지 질문에만 답합니다: 이 워크플로우가 설정된 가격이나 범위를 제시해도 되는가?
프롬프트가 아닌 테이블을 테스트하세요
프롬프트 스냅샷(Prompt snapshots)은 유용하지만, 가격 제어 테스트로서는 취약합니다. 문구의 변경은 정책을 바꾸지 않고도 스냅샷을 노이즈가 많게 만들 수 있습니다. 반면 테이블(table)은 각 비즈니스 규칙에 이름과 예상되는 결정(expected decision)을 부여합니다.
import { describe, expect, it } from "vitest";
import { decideQuote, type PriceRule } from "./quote-policy";
...
다섯 번째 케이스는 놓치기 쉽습니다. 리드가 가격을 언급하거나, 할인을 요청하거나, 다른 사이트의 숫자를 붙여넣을 수 있습니다. 그 숫자는 컨텍스트(context)일 뿐, 승인된 규칙이 아닙니다.
모델 출력은 신뢰할 수 없는 입력으로 취급하세요
모델이 service 또는 requestedAmount를 추출한다면, decideQuote를 호출하기 전에 결과를 검증해야 합니다.
스키마 검증기 (schema validator)는 형태(shape)를 확인하는 데 도움을 줍니다. 하지만 추출된 값이 사실임을 증명하지는 않습니다. 검토를 위해 원본 리드 텍스트 (lead text)를 사용할 수 있도록 유지하고, 추출 내용이 불확실할 때는 수동 전달 (handoff) 방식을 선호하세요.
또한 결정 사항과 함께 네 가지 감사 데이터 (audit data)를 저장합니다:
- 정규화된 서비스 식별자 (normalized service identifier)
- 가격 정책 버전 (price-policy version)
- 결정 사유 (decision reason)
- 최종 허용된 작업 (final allowed action)
기본적으로 대화 전체를 로그에 남기지 마세요. 테스트에는 실제 전화번호, 주소 또는 고객 이름이 필요한 경우가 거의 없습니다. 합성 픽스처 (synthetic fixtures)가 공유하기 더 쉽고 디버깅하기에도 훨씬 안전합니다.
숫자가 승인된 후의 문장을 테스트하세요
결정이 quote를 반환하면, 모델은 고객에게 보여줄 응답 초안을 작성할 수 있습니다. 이 초안에도 여전히 최종 단언 (assertion)이 필요합니다.
$120에서 $220 사이의 범위인 경우, 생성된 메시지에서 모든 통화 금액을 추출하고, 어떤 금액이라도 승인된 범위를 벗어나면 응답을 실패 처리하세요. 또한 메시지가 범위를 확정된 최종 가격으로 바꾸어 표현하는 경우에도 실패 처리하세요.
이를 통해 두 가지 별도의 테스트를 수행할 수 있습니다:
- 견적 (quote)이 허용되었는가?
- 생성된 문장이 허용된 결정 범위 내에 머물렀는가?
이렇게 분리하는 것은 추가적인 코드 작성의 가치가 있습니다. 테스트가 실패했을 때, 문제가 정책 선택 (policy selection)에 있는지 아니면 문구 (wording)에 있는지 알 수 있기 때문입니다.
실질적인 경계
Auto-Respond의 AI 답변 워크플로우에서 안전한 제품 경계는, AI가 비즈니스 규칙에 따라 설정된 가격이나 범위를 견적할 수 있다는 점입니다. AI는 최종 가격을 임의로 만들어내거나 보장해서는 안 됩니다. 동일한 경계가 커스텀 시스템에서도 적용됩니다: 결정론적 정책 (deterministic policy)이 우선이고, 생성된 산문 (generated prose)이 그다음입니다.
현재 테스트가 단순히 expect(message).toContain("$")라고만 되어 있다면, 이를 결정 테이블 (decision table)로 교체하세요. 그러면 매끄럽게 다듬어진 데모 스크립트가 숨기고 있는 실패 사례들을 잡아낼 수 있을 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기