모델을 호출하는 것은 쉽다. AI 시스템을 운영하는 것은 그렇지 않다
요약
단순한 모델 호출을 넘어, 실제 프로덕션 환경에서 신뢰할 수 있는 AI 시스템을 구축하기 위한 엔지니어링의 중요성을 강조합니다. 인증, 권한 부여, 비용 관리, 결과 검증 등 전체 애플리케이션 흐름을 중심으로 설계해야 함을 설명합니다.
핵심 포인트
- 모델 호출은 전체 AI 애플리케이션 흐름 중 하나의 중간 단계일 뿐임
- 프로덕션 환경에서는 보안, 비용, 관찰 가능성, 실패 처리가 필수적임
- 모델 응답의 성공이 곧 시스템의 신뢰성을 보장하지 않음
- 모델 중심이 아닌 전체 요청 경로와 아키텍처 중심으로 설계해야 함
성공적인 모델 호출은 하나의 요청이 하나의 모델에 도달하여 하나의 응답을 생성했음을 증명합니다. 하지만 그것이 해당 기능이 신뢰할 수 있는지, 안전한지, 관찰 가능한지(observable), 비용 효율적인지, 혹은 유용한지를 증명하지는 않습니다.
그 간극에 대부분의 엔지니어링 작업이 포함되어 있습니다.
모델 호출은 단 열 줄의 코드일 수 있습니다. 하지만 프로덕션(production) 기능은 그 열 줄의 코드 전후와 주변에서 일어나는 모든 것을 의미합니다. 여전히 사용자를 인증하고, 권한이 있는 데이터를 검색하며, 시간과 비용을 관리하고, 결과를 검증하며, 실패를 처리하고, 발생한 일을 기록하며, 출력이 동작을 트리거해도 될지 결정해야 합니다.
모델 호출을 중심으로 설계하지 마십시오. 모델 호출을 포함하는 전체 애플리케이션 흐름(application flow)을 중심으로 설계하십시오.
성공적인 응답은 거의 아무것도 증명하지 못한다
AI 기능의 첫 번째 작동 버전은 흔히 다음과 같은 모습입니다:
ChatResponse response = await chatClient.GetResponseAsync(
"Summarize this support ticket.",
cancellationToken: cancellationToken);
...
이것은 유용한 스파이크(spike)입니다. 자격 증명(credentials), 연결성(connectivity), 요청 형식(request formatting), 그리고 기본적인 모델 호환성을 검증합니다.
하지만 이는 해당 기능이 프로덕션(production)에서 실행될 수 있는지를 결정하는 질문들에 답해주지는 않습니다:
- 호출자가 티켓을 읽을 권한이 있었는가?
- 어떤 티켓 데이터가 모델로 전송되었는가?
- 민감한 콘텐츠가 제거되었는가, 아니면 유지되었는가?
- 검색(retrieval) 결과가 불완전한 컨텍스트를 반환하면 어떻게 되는가?
- 작업이 얼마나 오래 실행될 수 있는가?
- 몇 번의 재시도(retries)가 허용되는가?
- 토큰(token) 및 비용 예산은 얼마인가?
- 출력이 표시되거나 사용되기 전에 어떻게 확인되는가?
- 제공자(provider)를 사용할 수 없을 때는 어떻게 되는가?
- 운영자가 왜 이 요청이 실패했는지 설명할 수 있는가?
- 모델, 프롬프트(prompt), 또는 검색(retrieval)의 변경 사항을 어떻게 평가할 것인가?
모델 응답은 더 큰 애플리케이션 흐름 내의 하나의 중간 값일 뿐입니다.
전체 요청 경로를 추적하라
고객 지원 답변 기능을 예로 들어보겠습니다. 사용자가 애플리케이션에 티켓에 대한 답변을 준비해 달라고 요청합니다.
실제 경로는 다음과 것에 더 가깝습니다:
인증된 사용자 (authenticated user)
-> 요청 검증 (request validation)
-> 티켓 권한 부여 (ticket authorization)
...
각 화살표는 지연 시간 (latency), 실패 (failure), 비용 (cost), 또는 잘못된 가정 (incorrect assumptions)이 시스템에 유입될 수 있는 경계 (boundary)입니다.
모델은 성공적으로 답변할 수 있지만, 잘못된 고객 컨텍스트 (customer context)가 검색되었기 때문에 기능 (feature)은 여전히 실패할 수 있습니다. 기술적으로 유효한 응답이라 하더라도 지원되지 않는 주장을 포함할 수 있습니다. 잘 작성된 초안이 잘못된 테넌트 (tenant) 아래에 저장될 수도 있습니다. 타임아웃 (timeout)이 발생하면 사용자는 요청이 여전히 실행 중인지 확신할 수 없게 됩니다. 재시도 (retry)는 중복된 부작용 (side effect)을 생성할 수 있습니다.
이것이 바로 프로덕션 준비 상태 (production readiness)를 모델이 텍스트를 반환했는지 여부만으로 측정할 수 없는 이유입니다.
모델 호출 (model call)이 곧 아키텍처 (architecture)는 아닙니다. 그것은 요청 경로 (request path) 내부의 한 단계일 뿐이며, 모델이 성공하더라도 경로의 다른 부분들이 실패할 수 있습니다.
각 경계에 명시적인 계약 (contract)을 부여하세요
모델 호출이 일반적인 소프트웨어 경계 (software boundaries)로 둘러싸여 있을 때, 애플리케이션을 추론 (reason)하기가 더 쉬워집니다.
고객 지원 답변 (support-reply) 예시의 경우, 적어도 다음과 같은 책임 (responsibilities)들을 분리하겠습니다:
| 경계 (Boundary) | 계약 (Contract) |
|---|---|
| 요청 (Request) | 입력값이 유효하고, 제한 범위 내에 있으며, 인증된 호출자와 연결되어 있음. |
| ... |
몇 가지 명명된 책임과 명확한 강제 지점 (enforcement points)만으로도 충분합니다. 거대한 프레임워크 (framework)는 선택 사항입니다.
애플리케이션 서비스 뒤에 모델을 유지하세요
애플리케이션 코드의 엔드포인트 (endpoints), 백그라운드 워커 (background workers), 컨트롤러 (controllers)에 모델 호출을 흩뿌려 놓아서는 안 됩니다. 유스케이스 (use case)를 전체 흐름을 소유하는 서비스 (service) 뒤에 두십시오.
public sealed class SupportReplyService(
ITicketContextReader contextReader,
IChatClient chatClient,
...
이 예시는 의도적으로 불완전합니다. 프로덕션 구현에는 여전히 텔레메트리 (telemetry), 예산 (budgets), 실패 매핑 (failure mapping), 프롬프트 버전 관리 (prompt versioning), 그리고 테스트 (tests)가 필요합니다.
핵심은 소유권 경계 (ownership boundary)입니다. 엔드포인트는 지원 답변을 요청합니다. 애플리케이션 서비스는 유스케이스를 조정 (coordinate)합니다. 모델 클라이언트 (model client)는 그 내부의 하나의 의존성 (dependency)일 뿐입니다.
이는 UI, 컨트롤러 (controller), 또는 프로바이더 SDK (provider SDK)에 비즈니스 규칙 (business rules)을 가르치지 않고도 정책 (policy)을 추가할 수 있는 공간을 제공합니다.
신뢰성 (Reliability)은 재시도 정책 (retry policy)이 아니라 일련의 결정 사항입니다
재시도 (Retries)는 일부 일시적인 장애 (transient failures)에는 유용합니다. 하지만 그것이 완전한 신뢰성 전략은 아닙니다.
재시도는 다음과 같은 상황에서 상황을 악화시킬 수 있습니다:
- 원래 요청이 여전히 실행 중일 때
- 모델 호출 (model call) 비용이 많이 들 때
- 작업이 이미 상태를 변경했을 때
- 프로바이더 (provider)가 전체 워크로드 (workload)에 대해 스로틀링 (throttling)을 적용 중일 때
- 실패 원인이 잘못된 입력 (invalid input) 또는 지원되지 않는 기능 (unsupported capability) 때문일 때
- 다음 시도가 조용히 다른 컨텍스트 (context)를 사용할 때
모든 의존성 (dependency)에 대해, 해당 의존성이 느려지거나, 사용 불가능하거나, 사용할 수 없는 결과를 반환할 때 시스템이 무엇을 해야 할지 결정하십시오.
| 실패 (Failure) | 가능한 시스템 결정 (Possible system decision) |
|---|---|
| 모델 타임아웃 (Model timeout) | 시도를 취소하고 재시도 가능한 애플리케이션 결과 (retryable application result)를 반환합니다. |
| ... |
올바른 응답은 사용 사례 (use case)에 따라 달라집니다. 실제 운영 트래픽 (production traffic)이 대신 결정하게 만들기 전에 이러한 결정들을 미리 내리십시오.
품질 (Quality), 지연 시간 (latency), 그리고 비용 (cost)은 하나의 운영 엔벨로프 (operating envelope)입니다
AI 기능에는 단 하나의 성공 정의가 존재하지 않습니다.
응답이 정확하지만 너무 느릴 수 있습니다. 빠르지만 근거 (grounded)가 불충분할 수 있습니다. 사용자를 만족시키면서도 수용 불가능한 양의 토큰 (tokens)을 소비할 수 있습니다. 오프라인 평가 (offline evaluation)는 통과하지만 운영 환경의 동시성 (production concurrency) 하에서는 실패할 수 있습니다.
전체 기능에 대한 운영 엔벨로프 (operating envelope)를 정의하십시오. 검색 기반 (retrieval-backed) 지원 답변의 경우, 이는 허용 가능한 답변 품질 및 검색 누락률 (retrieval miss rate), 최대 유효 지연 시간 (maximum useful latency), 토큰 또는 비용 예산 (token or cost budget), 그리고 도구 호출 (tool calls) 또는 부작용 (side effects)에 대한 엄격한 제한을 의미합니다. 또한 타임아웃 발생 후 어떤 일이 일어날지, 어떤 폴백 (fallback)이 허용 가능한지, 그리고 애플리케이션이 텔레메트리 (telemetry)에 어떤 데이터를 유지할지를 결정하는 것도 의미합니다.
이러한 제약 조건들은 서로 상호작용합니다. 더 많은 컨텍스트 (context)를 추가하면 하나의 평가 점수는 향상될 수 있지만 지연 시간과 비용이 증가할 수 있습니다. 재시도를 추가하면 완료율 (completion rate)은 향상될 수 있지만 스로틀링 (throttling)을 증폭시킬 수 있습니다. 폴백 모델 (fallback model)은 가용성 (availability)을 향상시킬 수 있지만 동작 (behavior)을 변화시킬 수 있습니다.
이것들을 모델 설정 (model settings)이 아닌 시스템 트레이드오프 (system tradeoffs)로 취급하십시오.
평가 (Evaluation)와 관측 가능성 (Observability)는 서로 다른 질문에 답합니다
평가 (Evaluation)는 시스템이 대표적인 사례들에 대해 충분히 잘 동작하는지를 묻습니다.
관측 가능성 (Observability)은 실행 중인 환경에서의 특정 실행 중에 무엇이 일어났는지를 묻습니다.
두 가지 모두가 필요합니다.
평가 세트 (evaluation set)는 프롬프트 (prompt) 변경이 환불 관련 질문에 대한 근거성 (groundedness)을 감소시켰음을 감지할 수 있습니다. 트레이스 (trace)는 하나의 프로덕션 (production) 요청이 검색 (retrieval) 단계에서 6초를 소비하고, 두 번의 모델 호출 (model calls)을 수행했으며, 도구 (tool)를 호출한 후 영속화 (persistence) 과정에서 실패했음을 보여줄 수 있습니다.
로그 (logs)와 트레이스 (traces)는 답변의 품질을 증명하지 못하며, 품질 점수 (quality score)는 프로덕션 타임아웃 (production timeout)을 설명하지 못합니다. 유닛 테스트 (Unit tests)는 결정론적 (deterministic)인 애플리케이션 동작을 다루지만, 비결정론적 (nondeterministic)인 결과 품질을 측정하지는 않습니다. 한 번의 성공적인 수동 프롬프트 (manual prompt)는 품질도 신뢰성 (reliability)도 확립하지 못합니다.
인증 (authorization), 라우팅 (routing), 파싱 (parsing), 정책 (policy), 그리고 상태 변경 (state changes)에 대해서는 결정론적인 애플리케이션 테스트를 유지하십시오. 모델 (model) 및 검색 (retrieval) 동작에 대한 평가 (evaluations)를 추가하십시오. 운영자 (operators)가 진단해야 하는 경계 (boundaries) 전반에 걸쳐 텔레메트리 (telemetry)를 추가하십시오.
기능 (feature) 수준에서 프로덕션 준비 상태를 정의하십시오
AI 기능을 프로덕션 준비 완료 (production-ready) 상태라고 부르기 전에, 저는 다음 질문들에 대한 명시적인 답변을 원할 것입니다:
동작 (Behavior)
- 해당 기능은 어떤 사용자 결과 (user outcome)를 만들어냅니까?
- 어떤 사례들이 수용 가능한 동작과 수용 불가능한 동작을 보여줍니까?
경계 (Boundaries)
- 인증 (authentication)과 인가 (authorization)가 어디에서 강제됩니까?
- 어떤 입력 (inputs)과 출력 (outputs)이 신뢰할 수 없는 것 (untrusted)으로 취급됩니까?
- 어떤 작업들이 결정론적인 정책 (deterministic policy) 또는 사람의 승인을 필요로 합니까?
런타임 (Runtime)
- 시간 (time), 토큰 (token), 비용 (cost), 재시도 (retry), 그리고 도구 호출 (tool-call) 예산 (budgets)은 얼마입니까?
- 호출자 (caller)가 작업을 취소할 수 있습니까? 그리고 그 작업이 요청 (request) 내에 머물러야 합니까, 스트림 (stream) 형태여야 합니까, 아니면 백그라운드 작업 (background job)이 되어야 합니까?
장애 처리 (Failure handling)
- 어떤 장애들이 재시도 가능 (retryable)합니까?
- 의존성 (dependency)을 사용할 수 없을 때 사용자는 무엇을 보게 됩니까?
- 부수 효과 (side effect)를 반복하지 않고 부분적인 작업 (partial work)을 재개할 수 있습니까?
운영 (Operations)
- 트레이스 (traces)가 검색 (retrieval), 모델 (model), 도구 (tool), 그리고 지속성 (persistence) 시간을 분리할 수 있습니까?
- 민감한 입력 (inputs) 및 출력 (outputs)이 제외되거나 의도적으로 관리 (governed)되고 있습니까?
- 운영자 (operators)가 저하된 품질 (degraded quality), 지연 시간 (latency), 비용 (cost), 또는 에러율 (error rate)을 감지할 수 있습니까?
변경 제어 (Change control)
- 모델 (model), 프롬프트 (prompt), 검색 (retrieval), 그리고 도구 (tool)의 변경 사항이 출시 전에 어떻게 평가됩니까?
- 저장된 데이터 계약 (data contracts)을 변경하지 않고 애플리케이션을 롤백 (roll back)할 수 있습니까?
- 제공자별 기능 (provider-specific capabilities)이 명시적입니까, 아니면 모든 모델이 동일하게 동작하는 것처럼 가장하는 추상화 (abstraction) 뒤에 숨겨져 있습니까?
만약 이러한 질문들에 대한 답이 없다면, 그 시스템은 여전히 유용한 프로토타입 (prototype)일 수는 있습니다. 하지만 신뢰할 수 있는 제품 기능 (product feature)으로서 운영될 준비는 되지 않은 상태입니다.
이 프레임워크 (framing)가 유용한 경우
AI 결과가 사용자의 워크플로 (workflow)에 영향을 미치거나, 개인 데이터에 접근하거나, 검색 (retrieval)에 의존하거나, 도구 (tools)를 호출하거나, 상태 (state)를 변경하거나, 유의미한 비용이 발생하거나, 운영상의 약속 (operational promise)을 생성하는 경우라면 언제든 이 프레임워크가 중요합니다. 그 시점에서 모델 호출 (model call)은 더 이상 아키텍처 (architecture)가 아닙니다. 전체 요청 경로 (request path)가 아키텍처입니다.
직접적인 모델 호출만으로 충분한 경우
실패의 결과가 거의 없고 민감한 데이터나 부작용 (side effects)이 포함되지 않는 로컬 실험 (local experiment), 내부 스파이크 (internal spike), 또는 저위험의 일회성 도구 (disposable tool)의 경우에는 작은 규모의 직접적인 호출이 합리적입니다.
유스케이스 (use case)가 생기기 전에는 플랫폼을 구축하지 마십시오. 기능이 실제 제품으로서의 약속을 생성하게 되면, 모델 호출을 아키텍처로 취급하는 것을 중단하십시오.
실질적인 시사점 (Practical takeaway)
애플리케이션 내의 중요한 AI 요청 하나를 선택하여 그 전체 경로를 그려보십시오.
모든 의존성 (dependency), 정책 결정 (policy decision), 데이터 경계 (data boundary), 부작용 (side effect), 그리고 운영 신호 (operational signal)를 표시하십시오. 그런 다음 각 부분이 느려지거나, 사용할 수 없거나, 잘못되었을 때 어떤 일이 일어나야 하는지 정의하십시오.
모델 호출은 아마도 다이어그램에서 가장 작은 박스 중 하나로 남을 것입니다.
관련 읽을거리 (Related reading)
- AI 요청이 언제 백그라운드 작업 (Background Job)이 되어야 하는가
- .NET AI 시스템에서 CancellationToken이 더 중요한 이유
- OpenTelemetry, Aspire, Application Insights를 활용한 에이전트 (Agents) 관측성 (Observability)
- Eval-first: 왜 "한 번 작동했다"는 것이 품질의 징후가 아닌가
출처 (Sources)
- Microsoft Learn: Azure의 AI 워크로드 (AI workloads on Azure)
- Microsoft Learn: Azure의 AI 워크로드를 위한 설계 원칙 (Design principles for AI workloads on Azure)
- Microsoft Learn: Azure의 AI 워크로드를 위한 애플리케이션 설계 (Application design for AI workloads on Azure)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기