Spring AI 실전 훈련북

이 문서의 사용법

이 문서는 읽고 지나가는 요약본이 아니다. 기존 Spring AI 문서를 바탕으로 직접 판단을 써보고, 해설과 비교하고, 다시 원문 문서로 돌아가기 위한 훈련북이다.

처음에는 각 훈련의 먼저 써보기에 실제 답을 적는다. 그 다음 힌트를 보고 빠뜨린 관점을 보충한다. 마지막으로 해설과 비교한 뒤 연결 문서를 다시 읽는다.

한 번에 완벽한 답을 쓰는 것이 목표가 아니다. 같은 Lab이나 Incident를 며칠 뒤 다시 풀어도 판단 순서가 자연스럽게 나오는 것이 목표다.

훈련 방식

  • Evidence를 먼저 읽고 바로 해설로 내려가지 않는다.
  • 5분 안에 떠오르는 첫 판단을 적는다.
  • 그 판단이 설정 문제, 계층 문제, 품질 문제, 운영 문제 중 어디에 가까운지 분류한다.
  • 해설을 읽으며 빠진 증거와 위험한 조치를 표시한다.
  • 마지막에는 연결 문서 중 최소 1개를 다시 연다.

좋은 답안은 길 필요가 없다. 대신 어떤 증거를 보고, 어떤 순서로, 무엇을 먼저 확인할지 드러나야 한다.

반복 학습 루프

  1. 상황을 읽는다.
  2. Evidence에서 직접 볼 수 있는 사실만 표시한다.
  3. 원인이나 설계를 바로 단정하지 않고 후보를 나눈다.
  4. 먼저 써보기에 내 판단을 적는다.
  5. 힌트와 해설을 비교한다.
  6. 연결 문서를 다시 읽는다.
  7. 반복 기록표에 놓친 관점과 다음 액션을 남긴다.

훈련 목록

구분훈련핵심 판단
LabLab 1. 첫 ChatClient 호출 환경 점검표 작성버전, provider 설정, secret, 실패 메시지
LabLab 2. Controller 직접 호출 코드를 AI Gateway 구조로 재설계계층 분리, usage 기록, 예외 변환
LabLab 3. Structured Output DTO와 Bean Validation 실패 정책 설계DTO 계약, validation, fallback
LabLab 4. 고객지원 RAG metadata와 권한 필터 설계metadata, retrieval 전 권한 필터, index version
IncidentIncident 1. Provider timeout, 429, retry storm 대응retry, quota, 비용, fallback
IncidentIncident 2. Structured output validation failure 대응invalid output, prompt/schema 변경, human review
IncidentIncident 3. RAG stale source와 vector DB 지연 대응retrieval, indexVersion, latency 분리
ReviewReview 1. Controller에서 ChatClient를 직접 호출하는 PR 리뷰운영 코드 경계와 테스트 가능성
ReviewReview 2. read-only tool과 write tool이 섞인 설계 리뷰side effect, approval, idempotency
ReviewReview 3. MCP server를 운영망에 노출하는 설계 리뷰인증, 인가, 감사, 네트워크 경계
Final고객지원 RAG 서비스 출시 전 최종 점검전체 release readiness

Part 1. 실습 랩

Lab 1. 첫 ChatClient 호출 환경 점검표 작성

상황

새 Spring Boot 프로젝트에 Spring AI를 붙였다. 로컬에서는 첫 호출이 실패하고 있고, 팀원은 “OpenAI key만 넣으면 되는 것 아닌가”라고 말한다.

제공된 Evidence

spring:
  ai:
    model:
      chat: openai
    openai:
      chat:
        model: gpt-4o-mini
        temperature: 0.2
2026-07-01T10:13:21.431 ERROR ai-call
message="OpenAI API key must be set"
feature=first-chat
profile=local

운영 조건:

  • 로컬 실습은 OpenAI 우선, 비용 없는 반복 실습은 Ollama도 허용한다.
  • API key는 repository와 Obsidian 노트에 남기면 안 된다.
  • 첫 호출 성공 뒤에는 바로 Controller에 붙이지 않고 service 경계를 만든다.

해야 할 일

  • 첫 호출 전 점검표를 작성한다.
  • OpenAI 경로와 Ollama 경로에서 각각 확인할 항목을 나눈다.
  • 실패했을 때 사용자에게 보여줄 메시지와 내부 로그에 남길 필드를 분리한다.

먼저 써보기

힌트

  • key, model 이름, BOM/starter 버전, local profile, provider quota를 분리해서 본다.
  • 첫 호출은 API 사용법 확인이면서 동시에 운영 경계의 출발점이다.
  • temperature는 모든 모델 family에서 똑같이 지원된다고 가정하지 않는다.

해설

좋은 점검표는 “OpenAI key가 있나”에서 끝나지 않는다. 먼저 Spring AI BOM과 provider starter 버전이 맞는지 확인한다. 다음으로 spring.ai.model.chat=openai, spring.ai.openai.api-key, spring.ai.openai.chat.model이 local profile에서 어떤 값으로 해석되는지 본다.

OpenAI 경로에서는 secret 위치, quota, network timeout, model option을 본다. Ollama 경로에서는 base-url, 모델 pull 여부, 모델명, 로컬 메모리와 latency를 본다.

로그에는 raw prompt 대신 feature, modelProfile, promptVersion, provider, errorType 정도를 남긴다. 사용자 메시지는 “일시적으로 답변을 만들지 못했습니다”처럼 안전하게 제한하고, 내부 로그에만 원인 분류를 남긴다.

연결 문서

가져갈 판단 기준

첫 호출 실패는 코드 문제일 수도 있지만, 대개 버전, secret, provider 설정, local runtime 중 하나에서 시작된다.

Lab 2. Controller 직접 호출 코드를 AI Gateway 구조로 재설계

상황

첫 데모가 성공한 뒤 Controller에서 ChatClient를 직접 호출하는 코드가 생겼다. 이제 이 기능을 고객지원 API에 붙이려 한다.

제공된 Evidence

@RestController
class SupportAiController {
 
    private final ChatClient chatClient;
 
    SupportAiController(ChatClient.Builder builder) {
        this.chatClient = builder
                .defaultSystem("Answer customer support questions in Korean.")
                .build();
    }
 
    @PostMapping("/api/support/ai")
    Map<String, String> answer(@RequestBody Map<String, String> body) {
        String answer = chatClient.prompt()
                .user(body.get("question"))
                .call()
                .content();
 
        return Map.of("answer", answer);
    }
}

추가 요구사항:

  • tenant/user 권한을 확인해야 한다.
  • prompt version과 model profile을 기록해야 한다.
  • provider timeout과 invalid output을 내부 error type으로 바꿔야 한다.
  • 테스트에서 실제 provider를 빼고 싶다.

해야 할 일

  • Controller, application service, AI Gateway의 책임을 나눈다.
  • gateway interface 이름과 command/response DTO 초안을 작성한다.
  • usage record에 들어갈 필드를 정한다.

먼저 써보기

힌트

  • Controller는 HTTP 계약과 인증 사용자 추출에 집중한다.
  • 업무 service는 권한, quota, 유스케이스 정책을 본다.
  • AI Gateway는 Spring AI 호출, prompt/model version, 예외 변환, usage 기록을 맡는다.

해설

이 코드는 실습으로는 좋지만 운영 코드로는 경계가 너무 얇다. Controller가 prompt, model 호출, 예외, 응답 계약을 모두 알고 있기 때문이다.

좋은 방향은 SupportAiGateway 같은 업무 언어의 interface를 만들고, SupportAnswerService가 권한과 quota를 확인한 뒤 gateway를 호출하는 구조다. Spring AI 타입은 gateway 구현체 뒤에 숨긴다.

usage record에는 feature, tenant, promptVersion, modelProfile, provider, latency, status, errorType, input/output token 추정치를 남긴다. 실패도 기록해야 timeout과 quota 문제를 볼 수 있다.

연결 문서

가져갈 판단 기준

Controller가 AI 호출 세부사항을 알수록 데모는 빠르고 운영 변경은 느려진다.

Lab 3. Structured Output DTO와 Bean Validation 실패 정책 설계

상황

고객 문의를 자동 분류해서 BILLING, TECH, REFUND, ESCALATE 중 하나로 저장하려 한다. 모델 출력은 DTO로 받을 계획이다.

제공된 Evidence

record TicketClassification(
        String category,
        String reason,
        int confidence
) {
}

실패 샘플:

{
  "category": "PAYMENT",
  "reason": "환불과 결제가 섞인 문의입니다.",
  "confidence": 114
}

운영 조건:

  • confidence는 승인/거절의 직접 근거로 쓰지 않는다.
  • 분류 실패 시 ticket 생성 자체를 막으면 안 된다.
  • 개인정보가 reason에 포함될 수 있다.

해야 할 일

  • DTO를 더 안전하게 고친다.
  • Bean Validation과 업무 rule을 나눠 적는다.
  • invalid output 발생 시 fallback 정책을 작성한다.

먼저 써보기

힌트

  • category는 enum으로 좁힌다.
  • confidence 범위와 reason 길이를 제한한다.
  • validation 실패는 사용자 장애와 운영 품질 지표를 동시에 만든다.

해설

String category는 모델이 새로운 값을 만들기 쉽다. enum을 쓰고, reason에는 길이 제한을 둔다. confidence는 0~100 범위로 제한하되, 이 값만 보고 자동 승인 같은 결정을 하지 않는다.

Bean Validation은 형식과 값 범위를 잡는다. 업무 rule은 “REFUND인데 confidence가 낮으면 ESCALATE로 전환한다”처럼 서비스 정책을 다룬다.

fallback은 ticket 생성을 막기보다 ESCALATEUNKNOWN_REVIEW_REQUIRED 같은 안전한 상태로 넘기는 쪽이 낫다. invalid output rate는 metric으로 남기고, 샘플링 로그에는 민감정보 마스킹이 필요하다.

연결 문서

가져갈 판단 기준

모델 출력이 DTO로 들어왔다는 것은 검증을 시작할 수 있다는 뜻이지, 검증이 끝났다는 뜻이 아니다.

Lab 4. 고객지원 RAG metadata와 권한 필터 설계

상황

고객지원 FAQ, 정책 문서, 장애 공지 문서를 vector store에 넣어 RAG 답변을 만들려고 한다. 검색은 되지만 권한과 최신성 기준이 아직 없다.

제공된 Evidence

record KnowledgeChunk(
        String sourceId,
        String text
) {
}
List<Document> results = vectorStore.similaritySearch(SearchRequest.builder()
        .query(question)
        .topK(8)
        .build());

운영 조건:

  • tenant별로 볼 수 있는 문서가 다르다.
  • 일부 문서는 ADMIN role만 볼 수 있다.
  • 정책 문서는 삭제와 개정이 잦다.
  • 답변에는 source id가 포함되어야 한다.

해야 할 일

  • KnowledgeChunk에 필요한 metadata를 추가한다.
  • retrieval 전에 적용할 filter 조건을 설계한다.
  • index version을 운영에서 어떻게 쓸지 적는다.

먼저 써보기

힌트

  • RAG 권한은 prompt로 “답하지 마”라고 부탁하는 문제가 아니다.
  • tenant, role, source, updatedAt, embeddingModel, indexVersion을 떠올린다.
  • 삭제된 문서가 vector store에 남는 경로를 생각한다.

해설

metadata에는 최소한 tenantId, sourceId, sourceType, role 또는 ACL, updatedAt, embeddingModel, indexVersion이 필요하다. 권한 필터는 retrieval 전에 적용해야 한다. 검색 후 prompt에서 제외하라고 지시하면 권한 없는 문서가 이미 모델 입력으로 넘어갈 수 있다.

index version은 재색인과 rollback의 기준이다. embedding model이나 chunk policy를 바꾸면 새 index version을 만들고 eval을 통과한 뒤 active version을 전환한다.

답변에는 source id를 유지한다. 그래야 사용자가 근거를 확인할 수 있고, 장애 시 어떤 문서가 잘못 들어갔는지 추적할 수 있다.

연결 문서

가져갈 판단 기준

RAG에서 권한과 source 추적은 답변 생성 이후가 아니라 검색 이전에 결정된다.

Part 2. 장애 대응 훈련

Incident 1. Provider timeout, 429, retry storm 대응

Incident 상황

고객지원 AI 답변 API의 p95 latency가 2초에서 18초로 증가했다. 동시에 provider 429와 timeout이 늘고, 비용 추정치도 평소보다 빠르게 올라간다.

현재 보이는 증상

  • /api/support/ai p95 latency 18초
  • provider 429 증가
  • retry count 증가
  • token 사용량 급증
  • 일부 요청은 결국 fallback 없이 500으로 종료

로그/지표/단서

app.ai.call.duration{feature="support-rag",modelProfile="default-chat",status="timeout"} p95=18.2s
app.ai.call.retry{feature="support-rag",reason="429"} count=1840/10m
app.ai.call.tokens{feature="support-rag",type="input"} +240%/1h
provider.error{code="rate_limit_exceeded"} +310%/10m

최근 변경:

modelProfile default-chat
- maxOutputTokens: 800
+ maxOutputTokens: 2000
 
retry.max-attempts
- 2
+ 5

5분 안에 확인할 것

  • 장애 시작 시각과 model profile 변경 시각이 맞물리는가?
  • retry가 provider 429를 완화하는가, 더 큰 traffic을 만드는가?
  • tenant별 호출량과 batch/eval traffic이 online traffic과 같은 quota를 쓰는가?
  • fallback이 정의되어 있는가?
  • provider 장애인지, 우리 retry 정책 문제인지 분리할 수 있는가?

원인 후보 분류

  • provider rate limit 또는 quota 부족
  • retry max-attempts 증가로 인한 retry storm
  • max token 증가로 인한 latency와 비용 증가
  • batch/eval job과 online API의 quota 충돌
  • circuit breaker 또는 rate limit 부재

하면 안 되는 조치

  • 원인 확인 없이 retry 횟수를 더 늘리지 않는다.
  • 모든 tenant에 같은 fallback을 강제로 적용하지 않는다.
  • raw prompt를 일반 로그에 대량으로 남기지 않는다.
  • provider 장애라고 단정하고 애플리케이션 metric을 보지 않는 태도를 피한다.

복구 절차

  1. feature flag로 default-chat profile을 이전 max token 설정으로 되돌린다.
  2. retry max-attempts를 낮추고 backoff를 확인한다.
  3. tenant별 rate limit과 global quota를 임시로 적용한다.
  4. batch/eval traffic을 중단하거나 별도 시간대로 분리한다.
  5. fallback을 적용한다. 예를 들어 검색 결과만 제공하거나 human review로 전환한다.
  6. provider error, retry count, p95 latency, token 사용량을 함께 본다.

완료 검증

  • p95 latency가 SLO 근처로 돌아왔는가?
  • 429와 timeout이 감소했는가?
  • retry count가 정상 범위로 내려왔는가?
  • token 사용량과 비용 추정치가 안정화되었는가?
  • fallback 적용 요청이 추적 가능한가?

회고 질문

  • model profile 변경에 eval과 cost budget 검증이 있었는가?
  • online traffic과 batch/eval traffic의 quota가 분리되어 있는가?
  • retry 변경은 release note와 rollback 문서에 남았는가?
  • token spike alert가 충분히 빨랐는가?

연결 문서

가져갈 판단 기준

retry는 실패를 줄이기도 하지만, 잘못 쓰면 실패를 더 비싸고 느리게 만든다.

Incident 2. Structured output validation failure 대응

Incident 상황

티켓 자동 분류 기능에서 InvalidAiOutputException이 급증했다. API 전체는 살아 있지만, 분류 실패 ticket이 human review queue로 몰리고 있다.

현재 보이는 증상

  • structured output validation failure 12%에서 47%로 증가
  • category enum mismatch 증가
  • reason 필드에 고객 개인정보가 섞인 사례 발견
  • prompt version은 전날 변경됨

로그/지표/단서

app.ai.output.validation.failure{feature="ticket-classifier",promptVersion="ticket-v4"} count=932/30m
violation="category must be one of BILLING, TECH, REFUND, ESCALATE"
sampleCategory="PAYMENT"
sampleReasonLength=1240

최근 prompt 변경:

- Classify ticket into one of: BILLING, TECH, REFUND, ESCALATE.
+ Classify ticket into a useful support category and explain in detail.

5분 안에 확인할 것

  • 실패가 특정 prompt version에서만 증가했는가?
  • DTO schema나 enum이 바뀌었는가?
  • model profile이 바뀌었는가?
  • fallback queue가 처리 가능한 수준인가?
  • reason 필드에 민감정보가 저장되는가?

원인 후보 분류

  • prompt가 enum 제한을 약하게 만들었다.
  • DTO schema와 prompt 지시가 어긋났다.
  • model profile 변경으로 출력 형식 안정성이 바뀌었다.
  • validation 실패 로그가 민감정보를 과하게 남긴다.
  • fallback queue capacity가 부족하다.

하면 안 되는 조치

  • validation을 임시로 끄지 않는다.
  • 모델이 주는 category를 DB에 그대로 저장하지 않는다.
  • 개인정보가 섞인 reason 원문을 일반 로그로 남기지 않는다.
  • 실패를 모두 provider 장애로 분류하지 않는다.

복구 절차

  1. prompt version을 이전 안정 버전으로 rollback한다.
  2. enum과 reason 길이를 DTO/Bean Validation으로 다시 확인한다.
  3. invalid output fallback을 ESCALATE 또는 review queue로 제한한다.
  4. validation failure sample은 마스킹 후 제한적으로 보관한다.
  5. 작은 eval set으로 schema valid와 category distribution을 확인한다.
  6. human review queue backlog를 운영자에게 공유한다.

완료 검증

  • validation failure rate가 정상 범위로 내려왔는가?
  • enum mismatch가 줄었는가?
  • 민감정보가 reason/log에 남지 않는가?
  • fallback queue backlog가 처리 가능한 수준인가?
  • eval set에서 schema valid와 금지 필드 검증이 통과했는가?

회고 질문

  • prompt 변경 전에 structured output eval이 있었는가?
  • DTO와 prompt의 category 계약이 한 곳에서 관리되는가?
  • validation failure alert threshold가 적절했는가?
  • human review capacity가 fallback 설계에 반영되었는가?

연결 문서

가져갈 판단 기준

validation failure는 귀찮은 예외가 아니라 prompt, schema, model profile 계약이 어긋났다는 운영 신호다.

Incident 3. RAG stale source와 vector DB 지연 대응

Incident 상황

고객지원 RAG가 이미 폐기된 정책 문서를 근거로 답변했다. 동시에 일부 요청에서 vector DB 검색 시간이 크게 증가했다.

현재 보이는 증상

  • stale source id가 답변에 포함됨
  • retrieval latency p95 1.8초에서 7.4초로 증가
  • 특정 tenant에서 no-answer 비율 증가
  • 전날 index rebuild job이 실패했지만 alert가 늦게 왔다.

로그/지표/단서

rag.retrieval.duration{tenant="t-42",indexVersion="support-2026-06-30"} p95=7.4s
rag.source.stale{sourceId="refund-policy-2025-12",status="deleted"} count=31
rag.retrieval.hit{tenant="t-42",requiredSource="refund-policy-2026-06"} missing=18
index.rebuild.job status=FAILED lastSuccess="2026-06-29T03:00:00Z"

검색 코드 일부:

DocumentRetriever retriever = VectorStoreDocumentRetriever.builder()
        .vectorStore(vectorStore)
        .topK(12)
        .similarityThreshold(0.60)
        .build();

5분 안에 확인할 것

  • active index version과 rebuild job last success가 무엇인가?
  • deleted source가 vector store에서 제거되었는가?
  • tenant/role metadata filter가 빠졌는가?
  • latency 증가는 filter 부재, topK 증가, vector DB health 중 어디와 관련 있는가?
  • stale source가 실제 사용자 답변에 얼마나 노출되었는가?

원인 후보 분류

  • index rebuild 실패 후 active version 전환이 잘못되었다.
  • 삭제 문서 cleanup job이 누락되었다.
  • retrieval 전에 metadata filter가 적용되지 않았다.
  • topK와 threshold 변경으로 검색 비용이 증가했다.
  • vector DB capacity 또는 index 상태 문제가 있다.

하면 안 되는 조치

  • stale source 영향 범위 확인 없이 전체 index를 즉시 삭제하지 않는다.
  • 권한 필터를 prompt 지시로만 보완하지 않는다.
  • retrieval latency를 provider timeout으로 오해하지 않는다.
  • eval 없이 chunk policy나 threshold를 즉시 바꾸지 않는다.

복구 절차

  1. active index version과 failed rebuild job 상태를 확인한다.
  2. stale source id의 노출 범위를 조회한다.
  3. deleted source cleanup 또는 이전 정상 index rollback을 선택한다.
  4. tenant/role filter를 retrieval 전에 적용했는지 확인한다.
  5. topK와 threshold 변경 이력을 확인한다.
  6. retrieval hit, stale source, no-answer, latency 지표를 함께 본다.

완료 검증

  • stale source id가 검색 결과와 답변에서 사라졌는가?
  • required source hit가 회복되었는가?
  • retrieval latency가 정상 범위로 돌아왔는가?
  • active index version과 rebuild job 상태가 일치하는가?
  • RAG eval set에서 source와 no-answer 기준이 통과했는가?

회고 질문

  • 삭제 문서가 vector store에서도 제거되는 절차가 있는가?
  • index rebuild 실패 alert가 충분히 빠른가?
  • index version rollback 절차가 문서화되어 있는가?
  • RAG eval에 stale source와 forbidden leak 케이스가 있는가?

연결 문서

가져갈 판단 기준

RAG 품질 장애는 검색, context, 생성 중 어느 단계가 깨졌는지 먼저 나눠야 복구가 빨라진다.

Part 3. 설계 리뷰 훈련

Review 1. Controller에서 ChatClient를 직접 호출하는 PR 리뷰

제안된 설계안

새 PR은 SupportController에서 ChatClient를 직접 주입받아 질문을 보내고 문자열 답변을 반환한다. prompt는 Controller 안에 있고, 실패 시 RuntimeException을 그대로 던진다.

겉보기 장점

  • 코드가 짧다.
  • 첫 데모를 빠르게 만들 수 있다.
  • Spring AI API 사용법이 한 파일에 보여서 이해하기 쉽다.

숨은 리스크

  • HTTP 계약과 provider 호출 정책이 섞인다.
  • provider 예외가 사용자 응답과 서버 로그에 섞일 수 있다.
  • prompt version, model profile, usage 기록이 빠진다.
  • fake gateway 테스트가 어렵다.
  • fallback 정책이 기능별로 흩어질 수 있다.

리뷰 질문

  • 이 기능은 production으로 갈 코드인가, 학습용 데모인가?
  • tenant/user 권한과 quota는 어느 계층에서 확인하는가?
  • prompt version과 model profile은 어디에 기록되는가?
  • 실패도 usage record로 남는가?
  • service 테스트에서 실제 provider를 제거할 수 있는가?

빠진 계약

  • 내부 request/response DTO
  • AI Gateway interface
  • provider exception translator
  • usage record schema
  • fallback policy
  • test double 전략

개선된 설계 방향

Controller는 HTTP request/response와 인증 사용자 추출만 맡긴다. Application service는 권한과 quota를 확인한다. AI Gateway는 Spring AI 호출, prompt/model version, 예외 변환, usage 기록을 맡는다.

승인 조건

  • ChatClient가 Controller에서 제거된다.
  • gateway interface와 command/response DTO가 추가된다.
  • promptVersion, modelProfile, latency, errorType 기록이 포함된다.
  • fake gateway를 사용한 service test가 있다.

연결 문서

가져갈 판단 기준

AI 기능 PR에서 짧은 코드는 장점이지만, 경계가 사라진 짧음은 나중에 비싸다.

Review 2. read-only tool과 write tool이 섞인 설계 리뷰

제안된 설계안

고객지원 assistant에 주문 조회, 환불 요청 생성, 고객에게 안내 메일 발송 tool을 한 번에 붙인다. 모델이 필요하다고 판단하면 각 tool을 직접 호출한다.

겉보기 장점

  • 상담 자동화 범위가 넓어진다.
  • 상담원이 하던 반복 작업을 줄일 수 있다.
  • 모델이 상황에 맞춰 tool을 고르므로 UX가 좋아 보인다.

숨은 리스크

  • read-only와 side effect tool의 위험도가 다르다.
  • 모델이 만든 user id, order id, refund reason을 그대로 믿을 수 없다.
  • provider retry와 application retry가 중복 실행을 만들 수 있다.
  • 메일 발송과 환불 요청은 승인과 감사 로그가 필요하다.
  • tool output이 다시 모델 context로 들어가며 개인정보가 섞일 수 있다.

리뷰 질문

  • 어떤 tool이 read-only이고 어떤 tool이 write인가?
  • write tool에는 approval gate가 있는가?
  • idempotency key와 tool execution id가 있는가?
  • tool input의 tenant/user 권한은 서버 context로 검증하는가?
  • tool output은 외부 노출 가능한 DTO인가?

빠진 계약

  • tool inventory
  • tool별 permission scope
  • approval policy
  • idempotency key
  • audit log schema
  • retry policy
  • output masking policy

개선된 설계 방향

주문 조회 같은 read-only tool부터 시작한다. 환불 요청 생성과 메일 발송은 모델이 action plan을 structured output으로 만들고, 사용자 승인이나 별도 command API가 실행하게 한다.

승인 조건

  • read-only와 write tool이 package/class 수준에서도 구분된다.
  • write tool은 approval, idempotency, audit log가 있다.
  • tool input은 Bean Validation과 서버 권한 검증을 통과한다.
  • provider/application retry 중복 실행 방지가 있다.

연결 문서

가져갈 판단 기준

모델이 실행할 수 있는 일은 사람이 승인하지 않아도 되는 일인지 먼저 구분해야 한다.

Review 3. MCP server를 운영망에 노출하는 설계 리뷰

제안된 설계안

운영자가 IDE agent에서 Spring 서비스의 MCP server를 호출해 배포 상태, index 상태, tenant별 장애 상태를 조회하게 한다. 내부망이므로 인증은 나중에 붙이기로 했다.

겉보기 장점

  • 운영 조회가 표준 tool/resource로 정리된다.
  • IDE나 agent에서 같은 기능을 재사용할 수 있다.
  • 운영자가 여러 dashboard를 오가지 않아도 된다.

숨은 리스크

  • 내부망만으로 권한이 해결되지 않는다.
  • tenant별 운영 정보와 장애 상태가 과하게 노출될 수 있다.
  • MCP server의 tool inventory와 owner가 불명확하다.
  • rate limit, timeout, audit log가 없으면 장애와 보안 사고를 추적하기 어렵다.
  • MCP server version 변경이 client 호환성 문제를 만들 수 있다.

리뷰 질문

  • MCP server를 누가 운영하고 누가 호출할 수 있는가?
  • tool/resource별 permission scope는 무엇인가?
  • read-only만 노출하는가?
  • audit log에는 caller, scope, result size, success/failure가 남는가?
  • ingress, egress, timeout, rate limit은 인프라팀과 합의되었는가?

빠진 계약

  • 인증/인가 방식
  • MCP tool/resource inventory
  • audit log schema
  • network boundary
  • rate limit과 timeout
  • version compatibility
  • incident owner

개선된 설계 방향

MCP server는 read-only 운영 조회부터 시작한다. 인증과 감사 로그를 먼저 붙이고, tool/resource 목록을 inventory로 관리한다. 네트워크 위치, transport, timeout, rate limit은 인프라팀과 배포 전 합의한다.

승인 조건

  • 인증 없는 운영망 노출을 제거한다.
  • tool/resource별 permission scope가 명시된다.
  • audit log와 rate limit이 있다.
  • MCP server version이 release metadata에 포함된다.
  • 장애 시 기능 degrade 경로가 있다.

연결 문서

가져갈 판단 기준

MCP server는 편한 운영 창구가 될 수 있지만, 인증 없는 창구는 운영 편의보다 위험을 먼저 만든다.

최종 종합 훈련

고객지원 RAG 서비스가 production 출시를 앞두고 있다. 기능은 staging에서 동작한다. 하지만 출시 리뷰에서 다음 단서가 발견되었다.

feature=support-rag
promptVersion=support-rag-v3
modelProfile=default-chat
indexVersion=support-2026-07-01
toolSchemaVersion=order-tools-v1
mcpServerVersion=ops-mcp-v1
eval.summary
- total=25
- passed=21
- failed=4
- failures:
  - required source missing: 2
  - validation failure: 1
  - latency budget exceeded: 1
readiness gaps
- vector DB restore test: not executed
- MCP auth: internal network only
- fallback user message: draft missing
- raw prompt logging: enabled in stage profile

작성할 것:

  • 5분 triage
  • 핵심 설계 경계
  • prompt/model/index/tool/MCP release metadata 점검
  • eval gate 판단
  • observability 지표
  • 장애 fallback
  • 인프라팀과 합의할 운영 계약
  • 출시 승인 여부와 보류 조건
  • 다시 볼 문서

힌트:

  • eval 실패 4개를 무시할 수 있는지부터 판단한다.
  • MCP는 내부망이라는 이유로 인증을 생략할 수 없다.
  • raw prompt logging은 stage와 production의 profile 차이를 확인해야 한다.
  • 출시 승인 여부는 기능 동작 여부가 아니라 rollback과 장애 대응 준비까지 포함한다.

해설:

이 상태는 “동작은 하지만 production readiness가 부족한” 상태다. required source missing은 RAG 품질과 권한/source 추적을 흔들 수 있고, validation failure는 structured output 계약 문제다. latency budget 초과는 model profile, token budget, retrieval latency 중 어디서 발생했는지 분리해야 한다.

vector DB restore test가 없으면 index 손상이나 잘못된 재색인에서 복구 근거가 약하다. MCP auth가 내부망만 믿는 구조라면 운영 조회 권한과 감사 로그가 부족하다. fallback 사용자 메시지가 없으면 장애 시 사용자 경험이 기능마다 달라진다. stage에서 raw prompt logging이 켜져 있다면 production profile에서 꺼지는지, 마스킹과 retention이 있는지 확인해야 한다.

출시는 조건부 보류가 타당하다. 최소 승인 조건은 eval failure 원인 분류와 보완, MCP 인증/감사 로그 적용, vector DB restore test, fallback 메시지와 런북 작성, raw prompt logging policy 확인이다.

연결 문서:

가져갈 판단 기준:

출시 판단은 “답변이 나온다”가 아니라 “틀리고 늦고 실패할 때 무엇을 볼 수 있는가”로 결정한다.

반복 기록표

날짜훈련처음 판단놓친 관점다시 볼 문서다음 액션
Lab 1
Lab 2
Lab 3
Lab 4
Incident 1
Incident 2
Incident 3
Review 1
Review 2
Review 3
최종 종합 훈련