인프라로서의 LLM API: 확률론적 AI를 중심으로 결정론적 시스템 구축하기
요약
LLM API의 확률론적 특성을 이해하고, 이를 활용해 신뢰할 수 있는 결정론적 시스템을 구축하는 방법을 다룹니다. 단순한 API 호출을 넘어 스키마 검증과 비즈니스 규칙을 포함한 다단계 품질 게이트 설계의 중요성을 강조합니다.
핵심 포인트
- LLM은 확률적 모델이므로 동일한 입력에도 출력이 달라질 수 있음
- 전통적인 API의 성공/실패 개념을 넘어 응답의 정확성을 검증해야 함
- 단순 요청-응답 구조가 아닌 스키마 검증 등 추가적인 체크포인트 설계 필요
- 모델의 출력을 비즈니스 규칙과 결합하는 시스템적 접근이 필수적임
AI API의 본질
당신이 이 기술을 접하기 전, 누군가는 이미 이것을 만들어 두었습니다. 한 연구소(Lab)가 방대한 양의 텍스트로 이를 학습시키고, 정렬(Alignment)한 뒤, 엔드포인트(Endpoint) 뒤에 감싸서 당신에게 대여해 준 것입니다. 당신은 모델을 소유하지 않습니다. 당신은 그 인터페이스, 즉 모델의 능력, 한계, 컨텍스트 윈도우(Context window), 그리고 비용을 물려받는 것입니다.
프롬프트(Prompt)를 보낼 때, 모델은 두 가지 정보원을 가집니다: 학습 과정에서 배운 내용과 당신이 제공하는 컨텍스트(Context)입니다. 학습은 고정되어 있습니다. 컨텍스트는 당신의 것입니다. 이 두 가지 입력으로부터 모델은 가장 확률이 높은 연속된 내용을 생성합니다.
사람들은 "확률적(Probable)"이라는 말을 들으면 "추측(Guessing)"을 떠올립니다. 하지만 이것은 동전 던지기와 같은 추측이 아닙니다. 모델이 학습한 모든 것과 당신이 방금 말한 모든 것을 고려했을 때 가장 가능성 높은 연속된 내용을 생성하는 것 — 즉, 패턴과 분포에 의해 형성된 가중치가 부여된 구조화된 출력입니다.
대부분의 경우, 이는 유용합니다.
때로는, 자신 있게 틀리기도 합니다.
만약 모델이 무언가를 틀렸다고 해서, 그것이 패치되어야 할 버그(Bug)가 기다리고 있다는 뜻은 아닙니다.
그것은 당신이 그 동작을 고려하여 설계해야 하는 특성입니다.
API 호출 그 이상
전통적인 API는 우리가 예측 가능한 시스템 내에서 생각하도록 훈련시킵니다: 요청(Request)을 보내고, 응답(Response)을 기대하는 것입니다. 복잡한 시스템이라 할지라도 반복 가능한 동작을 가집니다. 상태 관리(State management), 캐싱(Caching), 재시도(Retries), 그리고 속도 제한(Rate limits)이 있을 수 있지만, 당신의 코드는 보통 성공과 실패가 어떤 모습인지 알고 있습니다.
AI 시스템은 모델 계층(Model layer)에서 그러한 기대를 깨뜨립니다. 동일한 프롬프트, 동일한 컨텍스트임에도 불구하고 출력은 달라집니다. 무언가 실패했기 때문이 아닙니다. 그것이 해당 구성 요소가 작동하는 방식이기 때문입니다. 요청은 성공할 수 있고, 응답은 200 OK일 수 있으며, JSON은 유효성 검사를 통과하고 로그는 깨끗해 보일 수 있지만, 답변은 여전히 틀릴 수 있습니다. 환각(Hallucinations), 누락된 지시사항, 잘못된 데이터가 포함된 유효한 JSON 등 말이죠. 기술적으로는 아무것도 실패하지 않았습니다. 모델이 단순히 잘못된 출력을 생성했을 뿐입니다. 이는 초점을 시스템이 응답했는지 여부에서, 응답이 올바른지 여부로 전환시킵니다.
전통적인 API는 보통 여러분의 코드가 이미 처리 방법을 알고 있는 방식으로 실패합니다. 요청은 해결(resolve)되거나 거부(reject)되며, 여러분의 에러 핸들링(error handling)이 이 두 가지를 모두 잡아냅니다.
try {
const response = await fetchClaim(id);
renderClaim(response);
...
전통적인 소프트웨어는 단순한 멘탈 모델 (mental model)을 권장합니다:
요청 (Request) → 응답 (Response) → 완료 (Done)
AI 시스템은 사용자의 입력과 최종 출력 사이에 더 많은 체크포인트 (checkpoints)가 필요하며, 무언가가 배포되기 전에 별도의 품질 게이트 (quality gate)가 필요합니다.
런타임 (Runtime) (모든 요청 시):
사용자 입력 (User Input) → 프롬프트/컨텍스트 (Prompt/Context) → LLM → 구조화된 출력 (Structured Output) → 스키마 검증 (Schema Validation) → 비즈니스 규칙 (Business Rules) → UI
...
API 호출은 단지 한 단계일 뿐입니다. 모델은 단지 한 부분일 뿐입니다. 그것이 앱 전체는 아닙니다.
결정론 (Determinism)이 중요한 지점
확률론적 시스템 (probabilistic system)에서 결정론은 출력이 데이터가 되거나, 동작을 트리거하거나, 애플리케이션의 상태를 변경할 때 중요해집니다. 만약 사용자가 "어제 뒤에서 받혔어요"라고 말한다면, 어시스턴트는 그 해석을 다양한 방식으로 설명할 수 있습니다. 하지만 제출된 사고 날짜 (incidentDate)는 매번 달라져서는 안 됩니다. 시스템에는 하나의 해결된 값, 하나의 검증 경로, 그리고 무엇이 제출되었는지에 대한 하나의 기록이 필요합니다. 필드 주변의 문구는 유연할 수 있지만, 시스템에 들어가는 값은 그럴 수 없습니다.
이것들은 고정하십시오 (Lock these down):
- 최종 양식 필드: 사고 날짜 (incidentDate), 부상 (injuries), 사고 유형 (accidentType)
- 필수 필드: 무언가가 완료되었는지 또는 누락되었는지 여부
- 비즈니스 규칙: 양식을 제출할 수 있는지 여부
- 동작 (Actions): 양식 전송, 기록 저장, 청구 승인
- 감사/이력 (Audit/history): 어떤 값이 사용되었고 그 이유는 무엇인지
이것들은 유연하게 두십시오 (Let these breathe):
- 어시스턴트의 문구
- 사용자에게 보여지는 설명
- 명확히 해야 할 사항에 대한 제안
- 동작을 트리거하지 않는 요약 또는 라벨
실제 적용에서의 경계
모델과 사용자 사이의 모든 단계는 여러분이 소유하고 제어하는 레이어 (layer)입니다. 실제 통합 사례에서는 다음과 같은 모습입니다.
양식 애플리케이션에서 모델은 사용자의 평이한 언어를 구조화된 데이터로 변환하는 데 도움을 줄 수 있습니다. 사용자는 다음과 같이 작성할 수 있습니다:
"어제 뒤에서 차가 들이받았어요. 사이드 미러가 깨졌지만, 다친 사람은 없었습니다."
애플리케이션은 다음과 같은 형태를 기대합니다:
interface IncidentExtraction {
formData: {
incidentDate: string;
...
하지만 모델은 TypeScript 인터페이스를 받는 것이 아닙니다. 모델은 지침(instructions)과 엄격한 스키마 (schema)를 받습니다.
import { GoogleGenAI, Type } from "@google/genai";
const ai = new GoogleGenAI({});
...
스키마는 출력을 형성(shape)할 뿐, 이를 보장(guarantee)하지는 않습니다. 이 줄 이후의 모든 것은 여러분의 규칙을 강제하는 여러분의 코드입니다:
let result: IncidentExtraction;
try {
...
스키마가 마련되면, 모델은 여러분의 구조와 일치하는 깔끔한 문자열을 반환할 수 있습니다:
{
"formData": {
"incidentDate": "2026-07-01",
...
신뢰성을 위한 핵심 관행:
- 엄격한 스키마 (schema)를 사용하여 구조화된 출력 (structured output)을 사용하세요.
- 코드에서 항상 출력을 검증(validate)하세요 — 스키마는 매우 유용하지만 100% 보장되지는 않습니다.
- 투명성을 위해 피드백 배열 (feedback array)을 포함하세요.
- 파싱 (parsing) 또는 검증 (validation)이 실패할 경우 우아하게 폴백 (fall back) 처리하세요.
이것이 경계선입니다. 모델은 언어를 해석합니다. 여러분의 시스템은 실행을 담당하며 신뢰할 수 있는 단일 원천 (source of truth)으로 남습니다.
평가(Evals)를 통한 테스트
위의 검증은 각 요청을 보호합니다. 하지만 이것은 여러분의 파이프라인(pipeline)이 전반적으로 우수한지에 대해서는 말해주지 않습니다. 그것이 바로 평가 (evals)가 필요한 이유입니다.
일반적인 소프트웨어에서는 무언가가 작동하는지 또는 실패하는지를 테스트합니다. AI의 경우, 답변이 충분히 좋은지도 테스트해야 합니다.
평가 (Evals)를 사용하면 테스트 데이터셋을 파이프라인에 통과시켜 명확한 기준에 따라 품질을 측정하고, 변경 사항이 사용자에게 도달하기 전에 취약한 출력을 잡아낼 수 있습니다.
const results = await Promise.all(
testDataset.map(test => extractIncidentFromLLM(test.input))
);
...
모범 사례: 에지 케이스 (edge cases), 모호한 언어, 그리고 적대적 입력 (adversarial inputs)을 포함하세요. 스키마 준수 여부, 환각 (hallucination) 비율, 그리고 피드백 품질을 추적하세요. CI/CD 환경에서 평가 (evals)를 실행하세요.
실제 파이프라인에서는 Promise.all을 재시도 (retry)와 실패한 요청을 처리하며 동시성을 제한하는 러너 (concurrency-limited runner)로 교체해야 합니다. 그래야 제공자 (provider)에게 과부하를 주지 않으면서 평가 (eval)를 수행할 수 있습니다. 프로덕션 환경의 평가 (eval)는 GPA 대신 성적표를 받는 것처럼, 발생할 수 있는 각 문제 요소에 대해 별도의 점수를 유지합니다. 그래야 수치가 떨어졌을 때 무엇을 수정해야 할지 정확히 알 수 있습니다.
결론 (Closing)
실수는 모델의 역할이 정답을 아는 것이라고 취급하는 것입니다. 아는 것은 결코 핵심이 아니었습니다.
LLM API를 인프라 (infrastructure)로 취급하세요. 데이터베이스 (database), 메시지 큐 (message queue), 또는 API 게이트웨이 (API gateway)와 마찬가지로, LLM API도 기능 (capabilities), 제약 사항 (constraints), 그리고 메커니즘 (mechanics)을 가지고 있습니다.
여러분의 역할은 모델을 결정론적 (deterministic)으로 만드는 것이 아닙니다. 확률론적 (probabilistic) 출력이 안전하고, 예측 가능하며, 유용해질 수 있도록 그 주변의 아키텍처 (architecture)를 설계하는 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기