fos-blog/study
01 / 홈02 / 카테고리03 / 시리즈
01 / 홈02 / 카테고리03 / 시리즈

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • agent 페이지로 이동
    • claude-code 페이지로 이동
    • harness 페이지로 이동
    • llm 페이지로 이동
    • ops 페이지로 이동
    • practice 페이지로 이동
  • algorithm 페이지로 이동
    • Alias Method: 가중치 랜덤 선택을 전처리 O(n), 추출 O(1)로 바꾸기
    • Welford's Online Algorithm: 값을 저장하지 않고 평균과 분산 계산하기
  • architecture 페이지로 이동
    • distributed-systems 페이지로 이동
    • domain 페이지로 이동
    • evolution 페이지로 이동
    • patterns 페이지로 이동
  • database 페이지로 이동
    • milvus 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • qdrant 페이지로 이동
    • redis 페이지로 이동
    • vespa 페이지로 이동
    • DB Connection Pool Saturation과 Thread Pool 격리
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 한국어 형태소 분석기 Nori vs Lindera — OpenSearch에서 Milvus로 갈 때 어휘 검색은 유지되나
    • 정규화와 역정규화: 무결성과 조회 비용 사이의 선택
    • 벡터 DB 5종, 아키텍처는 어떻게 다른가
    • 벡터 DB 5종을 실제로 벤치마크했다 — 같은 recall에서 QPS는 얼마나 갈리나
    • 벡터 DB 어떻게 고를까 — OpenSearch · Milvus · Qdrant · Vespa · pgvector 비교
    • 벡터 DB를 실제로 도입한 사례 — 빅테크 프로덕션
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • Envoy Proxy
    • Graceful Shutdown
    • 종료 신호는 어디서 멈추는가
  • http 페이지로 이동
    • HTTP Connection Pool
    • HTTPS와 TLS: 핸드셰이크, 인증서, 종료 지점
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • testing 페이지로 이동
    • Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • 로그에 traceId 남기기 — MDC 부터 OpenTelemetry 까지
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • `setImmediate()`
  • kafka 페이지로 이동
    • Kafka 클러스터 아키텍처: Broker, KRaft Controller와 복제
    • Kafka 기본 개념: Topic, Partition, Offset과 Segment
    • Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • Kafka 파티션·리밸런스·컨슈머 지연 운영
    • Kafka를 로그로 이해하기: 복제, 재처리, 상태와 데이터 통합
    • Spring Kafka 컨슈머 오프셋 커밋과 트랜잭션 정렬: AckMode, manual ack, 멱등 처리
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • SSH forced command 로 배포 키가 실행할 수 있는 명령 제한하기
  • mlops 페이지로 이동
    • llm-serving 페이지로 이동
    • model-router 페이지로 이동
    • Python CUDA 버전 생태계 — nvidia-smi, nvcc, pip, conda가 다 다른 버전을 말하는 이유
    • GPU 컨테이너의 CUDA 버전 호환성 — nvidia-smi부터 이미지 다이어트까지
    • Kubernetes GPU 노드에서 /run tmpfs가 꽉 차서 Pod가 안 뜰 때
    • GPU·CUDA·MPS 기초 — 자바 백엔드 개발자가 처음 만나는 그림
    • Multi-process GPU 워크로드 — 자바 ThreadPool 사용자가 만나는 모델 차이
    • ML 서비스 성능 분석 워크플로 — 자바 백엔드 트러블슈팅과 다른 점
    • 한 GPU 를 여러 프로세스가 나눠 쓰기 — Time-Slicing 과 MPS
  • network 페이지로 이동
    • Connection reset by peer는 누가 보낸 걸까 — 리버스 프록시 홉마다 TCP 연결은 따로 논다
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • observability 페이지로 이동
    • Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • K8s 위 Spring Boot 앱의 메트릭 수집
  • python 페이지로 이동
    • Python async/await — CompletableFuture·Reactor 와 다른 점, 그리고 blocking I/O 함정
    • Python 의존성 관리 — Java Maven/Gradle 사용자가 만나는 첫 충격
    • FastAPI 기초 — Spring Boot 사용자가 빠르게 익히는 법
    • Java 개발자를 위한 Python 심화 — OOP·데코레이터·컨텍스트 매니저
    • PyTorch 기초 — 텐서, 디바이스, 그리고 모델 로딩이 무거운 이유
    • Java 개발자를 위한 Python 문법 핵심
    • ThreadLocal 에서 contextvars 로 — Python 의 요청 컨텍스트 전파
    • OCR 동작 원리 — Layout · Text · Post-process 3단계
    • Python 서버의 RSS가 줄지 않는 이유: CPython, gc.collect(), malloc_trim
  • rabbitmq 페이지로 이동
    • RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • resume 페이지로 이동
    • 경험 및 경력기술서
    • 김병태 경력기술서
    • 김병태 포트폴리오
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • thinking 페이지로 이동
    • 자기주도 학습: 질문에서 시작해 글로 검증하기
    • 좋은 일을 넘어 중요한 일을 하는 법
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • Glossary↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/AI/AI 제품 백엔드 안정성 설계
ai

AI 제품 백엔드 안정성 설계

LLM을 붙인 제품의 백엔드는 일반 CRUD 백엔드와 안정성 모델이 다르다. 일반 백엔드는 "DB가 살아 있으면 같은 입력에 같은 출력"이 기본 전제다. AI 제품 백엔드는 그렇지 않다. 응답이 비결정적이다 — 같은 입력에도 모델 출력이 매번 달라진다. 그래서 "정상 응답"의 정의 자체를 우리가 따로 만들어야 한다. 지연이 크고 가변적이다 — 모델 한 번...

2026.09.08·10 min read·46 views

왜 이 주제가 중요한가

LLM을 붙인 제품의 백엔드는 일반 CRUD 백엔드와 안정성 모델이 다르다. 일반 백엔드는 "DB가 살아 있으면 같은 입력에 같은 출력"이 기본 전제다. AI 제품 백엔드는 그렇지 않다.

  • 응답이 비결정적이다 — 같은 입력에도 모델 출력이 매번 달라진다. 그래서 "정상 응답"의 정의 자체를 우리가 따로 만들어야 한다.
  • 지연이 크고 가변적이다 — 모델 한 번 호출이 수백 ms~수십 초까지 튄다. p99가 평균의 5배를 넘는 경우가 흔하다.
  • 호출당 비용이 실시간으로 발생한다 — 토큰 단위로 돈이 나간다. 루프가 한 번 급증하면 장애가 아니라 청구서로 먼저 드러난다.
  • 외부 모델 제공자에 강하게 의존한다 — 우리 코드가 멀쩡해도 제공자 쪽 429/5xx/지연이 그대로 우리 SLA를 깬다.

그래서 AI 제품 백엔드의 안정성은 "장애가 안 나게 한다"가 아니라 비결정성·가변 지연·실시간 비용·외부 의존을 전제로 깔고, 나빠질 때 어디서 끊고 어떻게 우아하게 내려갈지를 설계한다 가 핵심이다. 이 문서는 그 설계를 지연·비용·권한·관측·실패 처리 다섯 축으로 정리한다.

도구 호출 자체의 설계(레지스트리, 스키마 검증, dispatcher)는 LLM Tool Calling과 Agent Workflow 설계에서, 일반 분산 시스템 resilience 스택(Timeout/Retry/Circuit Breaker/Bulkhead/Backpressure)은 Resilience 패턴 가이드에서 다룬다. 이 문서는 그 둘을 AI 제품 운영 관점에서 묶는 허브 역할을 한다.

핵심 개념: 안정성 5개 축

AI 제품 백엔드의 안정성은 다음 다섯 가지를 각각 "예산"과 "실패 정책"으로 설계하는 일이다.

축핵심 질문대표 통제 수단
지연사용자가 얼마나 기다리게 둘 것인가시간 예산, 스트리밍, 계층 timeout
비용한 요청에 얼마까지 쓸 것인가토큰/호출/누적 비용 가드, 캐싱, 모델 폴백
권한모델이 무엇을 건드리게 둘 것인가사용자 컨텍스트 권한 검증, 도구 노출 제한
관측무엇이 왜 실패했는지 사후에 알 수 있는가correlation id, audit JSONL, LLM 특화 메트릭
실패 처리나빠질 때 어떻게 내려갈 것인가폴백 사다리, 재시도, 사람 에스컬레이션

다섯 축은 독립적이지 않다. 지연 예산이 비용 예산을 제약하고, 실패 처리는 관측이 없으면 설계할 수 없다. 그래서 한 곳(보통 요청 단위 budget 객체)에 모아두는 것이 실무에서 가장 덜 깨진다.

지연 예산 (Latency Budget)

모델 호출은 느리다. 그러므로 "언제까지 기다리고, 어디서 포기할지"를 먼저 정한다.

계층 timeout

모델/도구 호출은 최소 세 계층의 timeout을 분리한다.

  • 단일 모델 호출 timeout — 한 번의 LLM 호출에 대한 상한. 스트리밍이면 "첫 토큰까지" 와 "전체 완료까지" 를 따로 둔다.
  • 단일 도구 호출 timeout — 외부 API/DB 호출 상한. 도구마다 평균/최대 응답 시간이 다르므로 도구 메타데이터에 SLO로 박는다.
  • 요청 전체 timeout(wall-clock budget) — 사용자 발화 하나를 처리하는 전체 상한. 루프가 여러 step을 돌아도 이 예산을 넘기면 끊는다.

스트리밍으로 체감 지연을 낮춘다

전체 응답이 8초 걸려도 첫 토큰이 400ms에 나오면 사용자 체감은 전혀 다르다. 스트리밍(SSE 또는 WebSocket)은 안정성 설계의 일부다. 단, 스트리밍 중간에 실패하면 "이미 일부를 보여준 상태"라는 새 실패 모드가 생긴다. 부분 출력 후 끊겼을 때 어떻게 마감할지(에러 토큰 주입, 재생성 버튼, 마지막 문장 절단 표시)를 미리 정한다.

text
요청 시작
 ├─ 첫 토큰까지 timeout: 5s   (안 오면 폴백)
 ├─ 전체 완료 timeout: 30s
 └─ 요청 wall-clock budget: 40s (도구 호출 포함 전체)

비용 예산 (Cost Budget)

AI 백엔드에서 비용 급증은 "느린 장애"다. 모니터링 그래프보다 다음 달 청구서에서 먼저 보인다. 그래서 비용을 런타임 가드로 다룬다.

요청 단위 가드

한 사용자 발화당 다음을 모두 건다.

  • 최대 step 수 — 에이전트 루프가 도구를 부를 수 있는 횟수 상한 (예: 6회)
  • 최대 누적 토큰 — 입력+출력 토큰 합 상한 (예: 30k)
  • 최대 누적 비용 — 모델별 단가를 곱한 추정 비용 상한

가드에 걸리면 예외가 아니라 정해진 폴백 응답과 사람 에스컬레이션으로 내려간다. "예산 초과로 죽었습니다"가 사용자에게 가서는 안 된다.

모델 폴백 사다리와 캐싱

비용과 안정성을 동시에 잡는 두 수단.

  • 모델 폴백 사다리 — 평소에는 작고 싼 모델로 처리하고, 신뢰도/복잡도가 임계 이상일 때만 큰 모델로 올린다. 반대로 큰 모델이 429를 던지면 같은 모델을 기다리지 말고 바로 다음 모델로 내려가는 전략도 있다. "싸 보이는 모델이 재생성을 반복해 오히려 더 비싸지는" 경우가 있으므로, 폴백 방향은 비용만이 아니라 재시도 총비용으로 판단한다.
  • 컨텍스트 캐싱 / 결과 캐싱 — 동일 시스템 프롬프트나 동일 입력에 대한 반복 호출은 제공자 컨텍스트 캐시 또는 자체 결과 캐시로 토큰을 줄인다. 캐시 키에 모델 버전과 프롬프트 버전을 포함하지 않으면 모델 교체 후 낡은 응답이 새는 함정이 있다.

권한 경계

모델은 "무엇을 할 수 있는지"를 알 필요가 없고, 알게 해서도 안 된다. 권한은 항상 현재 인증된 사용자 컨텍스트에서 검증한다. 자세한 dispatcher 구조는 Tool Calling 문서에 있으므로 여기서는 안정성 관점의 원칙만 짚는다.

  • 모델이 "시켰으니까" 권한을 잠깐 올려서 실행하는 우회는 절대 만들지 않는다. 도구 실행은 언제나 사용자 컨텍스트에서 일어난다.
  • 노출하는 도구 집합을 의도 분류 결과와 사용자 권한으로 동적으로 좁힌다. 잡담 컨텍스트에 민감 도구를 노출하면 prompt injection 표면이 넓어진다.
  • 거절 메시지는 내부 사유를 흘리지 않는 일반화된 형태로 모델에 돌려준다. 모델은 그것을 사용자에게 그대로 풀어 쓸 수 있다.

권한이 안정성 주제인 이유는, 권한 사고가 데이터 유출/오작동이라는 가장 비싼 장애로 직결되기 때문이다.

관측 (Observability)

AI 백엔드는 "왜 이 답이 나왔는가"를 사후에 재구성할 수 없으면 개선도 사고 대응도 못 한다. 일반 관측성 기본기에 더해, AI 특화 항목을 남긴다.

무엇을 남기는가

한 사용자 발화를 하나의 correlation id로 묶어 audit JSONL에 적재한다.

  • 의도 분류 결과와 confidence
  • 모델에 노출한 도구 목록과 system prompt 버전
  • 모델이 제안한 각 tool call의 이름·인자, 검증 결과(통과/거절 사유)
  • 실제 실행된 도구의 입력·출력 요약, 응답 시간, 결과 코드
  • 사용한 모델, 입력/출력 토큰, 추정 비용, step 수
  • 최종 사용자 노출 텍스트

LLM 특화 메트릭

일반 RED(Rate/Error/Duration) 메트릭에 더해 본다.

  • 모델 호출 실패율 (제공자 429/5xx 비율) — 우리 코드가 아니라 외부 의존의 건강 상태
  • 폴백 발동율 — 어떤 폴백 경로가 얼마나 자주 도는지. 폴백이 "가끔"이 아니라 "기본"이 되면 설계가 무너진 신호다.
  • 요청당 평균/누적 토큰·비용 — 비용 회귀를 조기에 잡는다
  • 검증 거절율 — 모델이 스키마/의미 검증에서 떨어지는 비율. hallucination 추세 지표.

audit는 운영뿐 아니라 모델 평가의 입력이다. 이 데이터로 어떤 분류가 자주 틀리고 어떤 도구의 인자 환각이 잦은지를 정량으로 본다. 평가 루프 설계는 LLM 평가 프레임워크와 연결된다.

도구·모델 실패 처리와 폴백 사다리

실패는 예외가 아니라 일정 확률로 항상 일어나는 사건이다. 핵심은 "전파를 어디서 끊고, 어떻게 한 단계씩 우아하게 내려갈지"다.

폴백 사다리 (degradation ladder)

가장 좋은 응답에서 최소한의 응답까지 단계를 미리 정의한다.

text
1. 큰 모델 + 전체 도구       (정상)
2. 작은 모델 + 전체 도구       (큰 모델 429/지연 시)
3. 작은 모델 + 캐시된 결과만    (외부 도구 장애 시)
4. 템플릿/규칙 기반 응답        (모델 전체 장애 시)
5. 사람 상담사로 에스컬레이션    (위 전부 실패 또는 민감 케이스)

각 단계 전환 조건(어떤 신호에서 한 칸 내려가는가)을 코드로 명시한다. "되는 데까지 해보다가 터지면 500"이 가장 나쁜 패턴이다.

재시도는 부작용 여부로 구분한다

  • 읽기/안전한 도구 (조회류): 지수 백오프와 jitter로 2~3회 재시도. 결과는 "성공" 또는 "최종 실패"로 정리해 모델에 준다.
  • 부작용 있는 도구 (발송/결제/취소류): idempotency key가 있을 때만 재시도한다. 없으면 즉시 실패로 보고한다. 모델이 같은 도구를 반복 호출해 부작용이 누적되는 사고를 막는 것이 재시도 정책보다 우선이다.
  • 모델 호출 자체의 429: 같은 모델을 무한정 기다리기보다 폴백 사다리의 다음 모델로 내려가는 편이 지연·비용 모두에 낫다.

부분 성공을 정직하게 전달한다

여러 외부 호출 중 일부만 성공한 경우, observation 페이로드에 부분 성공을 명시해 모델이 "모두 완료"라고 단정하지 않게 한다.

json
{ "sms": "sent", "push": "queue_failed" }

모델 답변은 "문자로는 보냈고 앱 알림은 잠시 후 다시 시도하겠다"가 되어야 한다.

사람 에스컬레이션은 기능이다

AI 제품에서 "사람에게 넘긴다"는 장애 처리의 마지막 수단이 아니라 설계된 정상 경로 중 하나다. 다음을 미리 정한다.

  • 언제 넘기는가 — 예산 초과, 권한 부족, 낮은 confidence, 민감 카테고리, 연속 실패.
  • 무엇을 함께 넘기는가 — 지금까지의 대화 맥락 요약, 시도한 도구와 결과, 실패 사유. 사람이 처음부터 다시 묻지 않게 한다.
  • 사용자에게 어떻게 보이는가 — "처리할 수 없습니다"가 아니라 "확인이 더 필요해 상담사에게 연결합니다" 같은 우아한 마감.

에스컬레이션 경로가 없는 AI 백엔드는 나쁜 케이스에서 무조건 사용자에게 실패를 떠넘긴다.

나쁜 예와 개선된 예

나쁜 예 1: 예산 없는 에이전트 루프

java
// 안티패턴: 종료 조건이 "모델이 끝났다고 할 때"뿐
while (!response.isFinal()) {
    response = llm.next();
    dispatch(response.toolCall());
}

모델이 같은 도구를 반복 호출하면 토큰·비용·지연이 무한히 늘어난다. 장애가 청구서로 드러난다.

개선

java
for (int step = 0; step < ctx.maxSteps(); step++) {
    LlmResponse r = session.next(ctx.budget()); // 토큰·시간·비용 예산 주입
    if (r.isFinal()) return AgentReply.text(r.text());
    dispatcher.dispatch(r.toolCall(), ctx.toToolContext());
    if (ctx.budget().exhausted()) break;        // 예산 가드
}
return AgentReply.escalate("budget_exceeded"); // 우아한 마감

나쁜 예 2: 모델 장애 시 그냥 500

java
// 안티패턴
String answer = llm.complete(prompt); // 제공자 5xx면 그대로 예외 → 사용자 500
return answer;

개선

폴백 사다리를 탄다. 큰 모델 실패 → 작은 모델 → 캐시된 결과 → 템플릿 응답 → 사람 에스컬레이션 순으로 한 칸씩 내려간다. 어느 단계에서 내려갔는지 audit에 남긴다.

로컬 실습 환경

  • JDK 21, Spring Boot 3.x
  • 의존성: spring-boot-starter-web, spring-boot-starter-validation, resilience4j-retry, resilience4j-circuitbreaker, micrometer-tracing
  • LLM: 키가 없으면 fake LLM으로 대체 — 시나리오별로 미리 준비된 tool call/응답을 결정적으로 돌려준다. 안정성 가드를 검증할 때는 진짜 모델보다 결정적인 가짜가 낫다.
  • 장애 주입: fake 모델/도구 어댑터가 일정 비율로 429·5xx·timeout·부분 성공을 주입할 수 있게 둔다.
  • 관측: logs/ai-audit.jsonl에 줄 단위 JSON으로 적재하고 jq로 폴백 발동율·평균 토큰을 집계한다.

실행 가능한 시나리오

손으로 굴려보며 실패 모드를 익히는 데 좋은 시나리오.

  1. 정상 경로 — 예산 안에서 도구 2회 호출 후 최종 답변. audit에 토큰·비용 기록.
  2. 모델 429 — 큰 모델이 429를 던지면 폴백 사다리 2단계(작은 모델)로 내려가고, audit에 폴백 발동을 남긴다.
  3. 외부 도구 5xx — 부작용 없는 조회 도구는 백오프 재시도 후 성공/실패 정리, 부작용 도구는 idempotency key 없으면 재시도 금지.
  4. 부분 성공 — 두 알림 채널 중 하나만 성공. observation에 부분 성공 명시, 모델이 "모두 완료"라고 답하지 않는지 확인.
  5. 예산 초과 — 모델이 같은 도구를 반복 호출. step 가드가 끊고 사람 에스컬레이션으로 마감.
  6. 권한 부족 — 사용자 권한 밖 도구 요청. dispatcher가 일반화된 거절을 돌려주고 모델이 안전한 경로로 우회.

각 시나리오는 fake LLM 기반 통합 테스트로 묶어 회귀 보호한다. 모델 응답이 바뀌어도 안정성 가드가 동작하는지를 결정적으로 검증할 수 있다.

설계 점검 질문

시니어 백엔드 관점에서 스스로 던져볼 질문과 답의 뼈대.

"AI 제품 백엔드가 일반 백엔드와 다른 점은?"

비결정성·가변 지연·실시간 토큰 비용·외부 모델 의존을 전제로 깔아야 한다는 점. 그래서 "정상 응답의 정의"와 "나빠질 때 내려가는 단계"를 우리가 직접 설계한다.

"비용과 지연의 과도한 증가를 어떻게 막는가?"

요청 단위 step/토큰/시간/비용 가드를 한 budget 객체에 모으고, 초과 시 폴백·에스컬레이션으로 내려간다. 모델 폴백 사다리와 캐싱으로 평소 비용을 낮추되, 폴백 방향은 재시도 총비용으로 판단한다.

"모델/도구 실패를 어떻게 다루는가?"

폴백 사다리로 한 칸씩 우아하게 내려간다. 재시도는 부작용 여부로 구분하고, 부작용 도구는 idempotency key가 있을 때만 재시도한다. 부분 성공은 정직하게 모델에 전달한다.

"관측은 어떻게 설계하는가?"

correlation id로 한 발화를 묶어 분류·노출 도구·tool call·검증·실행·토큰·비용·최종 답변까지 audit JSONL에 남긴다. 폴백 발동율·검증 거절율·요청당 비용을 LLM 특화 메트릭으로 본다. 이 데이터는 모델 평가의 입력이기도 하다.

체크리스트

  • 요청 단위로 step·토큰·시간·비용 예산이 한 곳에 모여 있는가
  • 단일 모델 호출 / 단일 도구 호출 / 요청 전체 timeout이 계층으로 분리돼 있는가
  • 스트리밍 중간 실패 시 마감 방식이 정의돼 있는가
  • 모델 폴백 사다리와 각 단계 전환 조건이 코드로 명시돼 있는가
  • 캐시 키에 모델 버전과 프롬프트 버전이 포함되는가
  • 도구 노출이 의도 분류와 사용자 권한으로 동적으로 좁혀지는가
  • 부작용 도구의 재시도는 idempotency key가 있을 때만 일어나는가
  • 부분 성공이 모델에게 부분 성공으로 정확히 전달되는가
  • 폴백·에스컬레이션 발동이 audit에 남고 메트릭으로 집계되는가
  • 사람 에스컬레이션이 맥락 요약과 함께 설계된 정상 경로로 존재하는가
  • 모든 단계가 correlation id 하나로 묶여 audit JSONL에 적재되는가
  • fake LLM 기반 통합 테스트로 429·외부 실패·부분 성공·예산 초과·권한 부족이 회귀 보호되는가
on this page
  • 01왜 이 주제가 중요한가
  • 02핵심 개념: 안정성 5개 축
  • 03지연 예산 (Latency Budget)
  • 계층 timeout
  • 스트리밍으로 체감 지연을 낮춘다
  • 04비용 예산 (Cost Budget)
  • 요청 단위 가드
  • 모델 폴백 사다리와 캐싱
  • 05권한 경계
  • 06관측 (Observability)
  • 무엇을 남기는가
  • LLM 특화 메트릭
  • 07도구·모델 실패 처리와 폴백 사다리
  • 폴백 사다리 (degradation ladder)
  • 재시도는 부작용 여부로 구분한다
  • 부분 성공을 정직하게 전달한다
  • 08사람 에스컬레이션은 기능이다
  • 09나쁜 예와 개선된 예
  • 나쁜 예 1: 예산 없는 에이전트 루프
  • 개선
  • 나쁜 예 2: 모델 장애 시 그냥 500
  • 개선
  • 10로컬 실습 환경
  • 11실행 가능한 시나리오
  • 12설계 점검 질문
  • "AI 제품 백엔드가 일반 백엔드와 다른 점은?"
  • "비용과 지연의 과도한 증가를 어떻게 막는가?"
  • "모델/도구 실패를 어떻게 다루는가?"
  • "관측은 어떻게 설계하는가?"
  • 13체크리스트
tags
#study

이런 글도

  • ai

    하네스 회고는 새 문서로 쌓지 않고 종류에 맞는 기존 단일 소스에 환원한다

    2026.10.02
  • ai

    에이전트가 배운 회피 패턴은 조건을 통과한 것만 파일로 쌓고 주기적으로 지운다

    2026.10.02
  • ai

    ADR 은 되돌리기 어려운 결정의 이유만 남기고, 대안 기각은 옵션마다 줄을 나눈다

    2026.10.02
  • ai

    스킬 문서는 반복 실행을 스크립트로 내리고 같은 지시를 한 곳에서만 소유하게 쓴다

    2026.10.02

댓글 (0)