🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

고객 상담 AI 시스템 완벽 구축 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 18. · 11 Views

고객 상담 AI 시스템 완벽 구축 가이드

AWS Bedrock Agent와 Knowledge Base를 활용하여 실시간 고객 상담 AI 시스템을 구축하는 방법을 단계별로 학습합니다. RAG 기반 지식 검색부터 Guardrails 안전 장치, 프론트엔드 연동까지 실무에 바로 적용 가능한 완전한 시스템을 만들어봅니다.


목차

  1. 시스템_아키텍처_설계
  2. Knowledge_Base_구성
  3. 상담_에이전트_개발
  4. Guardrails_적용
  5. 프론트엔드_연동
  6. 프로덕션_배포

1. 시스템 아키텍처 설계

스타트업 개발자 김개발 씨는 고객센터 팀장님으로부터 급한 요청을 받았습니다. "매일 반복되는 고객 문의가 너무 많아요.

AI로 자동 응답할 수 있는 시스템을 만들 수 있나요?" 김개발 씨는 AWS Bedrock을 활용하면 가능할 것 같다고 생각했습니다.

AWS Bedrock Agent는 대화형 AI 시스템을 구축할 수 있는 완전 관리형 서비스입니다. 마치 숙련된 상담원처럼 고객의 질문을 이해하고, 회사의 지식 베이스에서 정확한 답변을 찾아 응답합니다.

RAG(Retrieval Augmented Generation) 패턴을 기반으로 환각 현상을 방지하면서도 정확한 정보를 제공할 수 있습니다.

다음 코드를 살펴봅시다.

// architecture/system-design.ts
interface AISystemArchitecture {
  // 1. 지식 저장소 - S3에 문서 업로드
  knowledgeBase: {
    dataSource: 'S3 Bucket',
    vectorDB: 'OpenSearch Serverless',
    embedding: 'Amazon Titan Embeddings'
  },

  // 2. AI 에이전트 - 대화 처리 엔진
  bedrockAgent: {
    model: 'Claude 3.5 Sonnet',
    knowledgeBaseIntegration: true,
    guardrailsEnabled: true
  },

  // 3. 안전 장치 - 부적절한 응답 차단
  guardrails: {
    contentFilters: ['HATE', 'VIOLENCE', 'SEXUAL'],
    topicFilters: ['경쟁사', '정치'],
    piiRedaction: true
  },

  // 4. 프론트엔드 - 실시간 채팅 UI
  frontend: {
    framework: 'Next.js',
    api: 'AWS SDK v3',
    streaming: 'Server-Sent Events'
  }
}

김개발 씨는 커피를 한 모금 마시며 생각했습니다. "AI 상담 시스템이라...

어디서부터 시작해야 할까?" 그때 옆자리 박시니어 씨가 화이트보드를 가리키며 말했습니다. "먼저 전체 아키텍처를 설계해야죠." 시스템 아키텍처란 건물의 설계도와 같습니다.

건물을 지을 때 기둥, 벽, 전기, 수도 배치를 먼저 결정하듯이, AI 시스템도 각 구성 요소가 어떻게 연결되고 작동할지 미리 계획해야 합니다. "자, 고객 상담 AI는 크게 네 가지 레이어로 구성됩니다." 박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다.

첫 번째는 지식 저장소 레이어입니다. 고객 상담 매뉴얼, FAQ 문서, 제품 설명서 같은 회사의 지식을 저장하는 곳입니다.

마치 도서관의 책장과 같습니다. 이 데이터는 S3 버킷에 업로드되고, 벡터 데이터베이스로 변환되어 빠른 검색이 가능해집니다.

두 번째는 AI 에이전트 레이어입니다. 이것이 실제로 고객과 대화하는 두뇌 역할을 합니다.

AWS Bedrock Agent는 고객의 질문을 받으면, 지식 저장소에서 관련 정보를 찾아낸 후, Claude 모델을 활용해 자연스러운 답변을 생성합니다. 세 번째는 안전 장치 레이어입니다.

AI가 아무리 똑똑해도 때로는 부적절한 답변을 할 수 있습니다. Guardrails는 마치 교통 신호등처럼 위험한 응답을 사전에 차단합니다.

욕설, 폭력적 표현, 개인정보 유출 같은 것들을 필터링합니다. 네 번째는 프론트엔드 레이어입니다.

아무리 좋은 AI라도 사용자가 쉽게 접근할 수 없다면 무용지물입니다. Next.js로 만든 채팅 인터페이스를 통해 고객은 카카오톡처럼 편하게 질문할 수 있습니다.

"이 네 가지가 유기적으로 연결되어야 진짜 상담 시스템이 완성됩니다." 박시니어 씨가 설명을 마쳤습니다. 김개발 씨는 고개를 끄덕였습니다.

"그렇다면 각 레이어를 어떤 순서로 만들어야 하나요?" "좋은 질문이에요. 보통은 아래에서 위로 올라갑니다.

먼저 지식 베이스를 구축하고, 그 다음 에이전트를 만들고, 안전 장치를 추가한 후, 마지막으로 프론트엔드를 연결하는 거죠." 이런 계층적 접근 방식은 실제 AWS의 Well-Architected Framework에서도 권장하는 방법입니다. 각 레이어를 독립적으로 개발하고 테스트할 수 있어, 문제가 생겼을 때 디버깅도 훨씬 쉬워집니다.

또한 이 아키텍처는 확장 가능합니다. 처음에는 FAQ 문서 몇 개로 시작했다가, 나중에 제품 매뉴얼, 법률 문서, 심지어 과거 상담 이력까지 추가할 수 있습니다.

에이전트는 자동으로 새로운 지식을 학습합니다. 비용 측면에서도 효율적입니다.

서버리스 아키텍처를 사용하기 때문에 사용한 만큼만 비용이 발생합니다. 고객 문의가 적은 새벽 시간에는 거의 비용이 들지 않고, 많은 낮 시간에만 자동으로 확장됩니다.

김개발 씨는 노트북을 열어 아키텍처 다이어그램을 그리기 시작했습니다. "이제 감이 잡히네요.

하나씩 만들어보겠습니다!" 설계 단계에서 충분한 시간을 들이면, 나중에 개발 단계에서 시행착오를 크게 줄일 수 있습니다. 많은 개발자들이 바로 코딩부터 시작하다가 나중에 전체 구조를 다시 뜯어고치는 실수를 합니다.

실전 팁

💡 - AWS 아키텍처 다이어그램은 draw.io나 Lucidchart를 활용하면 팀원들과 공유하기 좋습니다

  • 각 레이어의 예상 비용을 미리 AWS Pricing Calculator로 계산해보세요
  • 프로토타입은 작게 시작해서 점진적으로 확장하는 것이 안전합니다

2. Knowledge Base 구성

아키텍처 설계를 마친 김개발 씨는 본격적으로 개발에 들어갔습니다. 첫 번째 단계는 Knowledge Base 구성입니다.

"고객센터 팀에서 주신 FAQ 문서가 100개가 넘네요. 이걸 어떻게 AI가 이해할 수 있게 만들지?" 박시니어 씨가 옆에서 조언합니다.

"걱정 마세요. 벡터 임베딩만 이해하면 됩니다."

AWS Bedrock Knowledge Base는 문서를 AI가 이해할 수 있는 형태로 자동 변환합니다. PDF, 워드, 텍스트 파일을 S3에 업로드하면, Titan Embeddings 모델이 각 문장을 숫자 벡터로 변환합니다.

이렇게 만들어진 벡터는 OpenSearch Serverless에 저장되어, 고객 질문과 가장 유사한 내용을 0.1초 안에 찾아냅니다.

다음 코드를 살펴봅시다.

// knowledge-base/create-kb.ts
import { BedrockAgentClient, CreateKnowledgeBaseCommand } from '@aws-sdk/client-bedrock-agent'

async function createKnowledgeBase() {
  const client = new BedrockAgentClient({ region: 'us-east-1' })

  // 1. Knowledge Base 생성
  const command = new CreateKnowledgeBaseCommand({
    name: 'customer-service-kb',
    description: '고객 상담 FAQ 및 매뉴얼',
    roleArn: 'arn:aws:iam::123456789:role/BedrockKBRole',

    // 2. 임베딩 모델 설정 - 텍스트를 벡터로 변환
    knowledgeBaseConfiguration: {
      type: 'VECTOR',
      vectorKnowledgeBaseConfiguration: {
        embeddingModelArn: 'arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v1'
      }
    },

    // 3. OpenSearch Serverless 벡터 DB 연결
    storageConfiguration: {
      type: 'OPENSEARCH_SERVERLESS',
      opensearchServerlessConfiguration: {
        collectionArn: 'arn:aws:aoss:us-east-1:123456789:collection/kb-collection',
        vectorIndexName: 'customer-service-index',
        fieldMapping: {
          vectorField: 'embedding',
          textField: 'text',
          metadataField: 'metadata'
        }
      }
    }
  })

  const response = await client.send(command)
  console.log('Knowledge Base 생성 완료:', response.knowledgeBaseId)
  return response.knowledgeBaseId
}

김개발 씨는 화면을 보며 고개를 갸우뚱했습니다. "벡터 임베딩이요?

그게 뭔가요?" 박시니어 씨가 종이에 그림을 그리며 설명하기 시작했습니다. "자, 예를 들어볼게요.

'환불 정책이 어떻게 되나요?'라는 질문과 '반품은 어떻게 하나요?'라는 질문이 있다고 칩시다. 사람이 보기엔 비슷한 질문이죠?" 김개발 씨가 고개를 끄덕였습니다.

"하지만 컴퓨터는 단어만 비교하면 '환불'과 '반품'이 완전히 다른 단어라고 생각합니다. 여기서 임베딩이 필요한 거예요.

각 문장의 의미를 숫자 배열로 변환하는 겁니다." 벡터 임베딩은 마치 GPS 좌표와 같습니다. 서울과 부산의 거리를 계산할 때 위도, 경도 숫자로 계산하듯이, 문장의 의미도 1536차원의 숫자 배열로 표현됩니다.

비슷한 의미를 가진 문장들은 이 고차원 공간에서 가까운 위치에 모입니다. "아하!" 김개발 씨의 눈이 반짝였습니다.

"그러니까 '환불'과 '반품'이라는 단어는 다르지만, 벡터 공간에서는 가까운 곳에 위치하는 거네요?" "정확합니다!" 박시니어 씨가 웃으며 대답했습니다. AWS Bedrock의 Knowledge Base를 만드는 과정은 생각보다 간단합니다.

먼저 S3 버킷에 문서를 업로드합니다. PDF든 워드든 텍스트든 상관없습니다.

그 다음 Knowledge Base를 생성하면, AWS가 자동으로 다음 작업들을 수행합니다. 첫째, 문서를 작은 청크(chunk)로 나눕니다.

보통 300-500자 단위로 분할됩니다. 너무 크면 검색 정확도가 떨어지고, 너무 작으면 맥락이 사라지기 때문에 적절한 크기로 나누는 것이 중요합니다.

둘째, 각 청크를 Titan Embeddings 모델에 통과시킵니다. 이 모델은 한국어, 영어, 일본어 등 100개 이상의 언어를 지원하며, 각 텍스트를 1536차원의 벡터로 변환합니다.

셋째, 변환된 벡터를 OpenSearch Serverless에 저장합니다. 이것은 일종의 벡터 전용 데이터베이스입니다.

일반 데이터베이스가 정확한 값을 찾는다면, 벡터 데이터베이스는 유사한 값을 찾는 데 특화되어 있습니다. 김개발 씨가 실제로 Knowledge Base를 생성하는 과정을 지켜봤습니다.

AWS 콘솔에서 몇 번의 클릭만으로 설정이 완료되었습니다. "와, 생각보다 쉽네요!" "맞아요.

예전에는 이런 시스템을 직접 구축하려면 몇 주가 걸렸어요. 벡터 DB 설치하고, 임베딩 모델 학습시키고, 검색 알고리즘 최적화하고...

하지만 지금은 AWS가 다 해줍니다." 하지만 주의할 점도 있습니다. 문서 품질이 가장 중요합니다.

아무리 좋은 AI라도 잘못된 정보가 입력되면 잘못된 답변을 합니다. 따라서 Knowledge Base에 넣을 문서는 사전에 검수가 필요합니다.

또한 청킹 전략도 중요합니다. 기본 설정은 300자 단위이지만, 문서 특성에 따라 조절할 수 있습니다.

법률 문서처럼 문맥이 중요한 경우 청크를 크게 하고, FAQ처럼 독립적인 질문-답변 쌍은 청크를 작게 하는 것이 좋습니다. 김개발 씨는 고객센터 팀에서 받은 100개의 FAQ 문서를 S3에 업로드했습니다.

약 5분 후, Knowledge Base 동기화가 완료되었다는 알림이 왔습니다. "이제 AI가 이 모든 문서를 이해하고 검색할 수 있다는 거죠?" "그렇습니다.

테스트해볼까요?" 박시니어 씨가 간단한 검색 쿼리를 날렸습니다. 즉시 관련 문서 3개가 정확도 점수와 함께 반환되었습니다.

실제로 많은 기업들이 이 방식으로 사내 지식 검색 시스템을 구축하고 있습니다. 삼성, LG, 현대자동차 같은 대기업뿐만 아니라 스타트업들도 빠르게 도입하고 있습니다.

실전 팁

💡 - 문서는 markdown 형식으로 변환하면 구조화된 정보를 더 잘 인식합니다

  • S3 버킷에 새 문서를 추가하면 자동 동기화가 가능하도록 Lambda 트리거를 설정하세요
  • 검색 정확도를 높이려면 메타데이터(카테고리, 날짜, 작성자 등)를 함께 저장하세요

3. 상담 에이전트 개발

지식 베이스 구축을 마친 김개발 씨는 이제 실제로 대화하는 Agent를 만들 차례입니다. "에이전트가 정확히 뭘 하는 건가요?" 박시니어 씨가 답했습니다.

"고객의 질문을 받아서, Knowledge Base에서 정보를 찾고, 자연스러운 답변으로 만들어주는 오케스트레이터죠."

Bedrock Agent는 대화형 AI의 두뇌입니다. 사용자 입력을 받으면 먼저 의도를 파악하고, Knowledge Base를 검색한 후, Claude 모델로 자연스러운 문장을 생성합니다.

RetrieveAndGenerate API를 통해 RAG 패턴이 자동으로 구현되며, 스트리밍 응답으로 실시간 대화가 가능합니다.

다음 코드를 살펴봅시다.

// agent/invoke-agent.ts
import { BedrockAgentRuntimeClient, InvokeAgentCommand } from '@aws-sdk/client-bedrock-agent-runtime'

async function invokeAgent(userMessage: string, sessionId: string) {
  const client = new BedrockAgentRuntimeClient({ region: 'us-east-1' })

  const command = new InvokeAgentCommand({
    agentId: 'ABCDE12345',
    agentAliasId: 'TSTALIASID',
    sessionId: sessionId, // 세션 유지로 문맥 이해
    inputText: userMessage,

    // Knowledge Base 연동 설정
    sessionState: {
      knowledgeBaseConfigurations: [{
        knowledgeBaseId: 'KB123456',
        retrievalConfiguration: {
          vectorSearchConfiguration: {
            numberOfResults: 5, // 상위 5개 문서 검색
            overrideSearchType: 'HYBRID' // 벡터 + 키워드 하이브리드 검색
          }
        }
      }]
    }
  })

  const response = await client.send(command)

  // 스트리밍 응답 처리
  let fullResponse = ''
  for await (const event of response.completion) {
    if (event.chunk?.bytes) {
      const text = new TextDecoder().decode(event.chunk.bytes)
      fullResponse += text
      console.log(text) // 실시간으로 출력
    }
  }

  return fullResponse
}

김개발 씨는 드디어 핵심 부분에 도달했습니다. "지금까지는 준비 작업이었고, 이제 진짜 AI를 만드는 거네요." 박시니어 씨가 고개를 끄덕였습니다.

"맞아요. Agent는 전체 시스템의 지휘자 역할을 합니다.

오케스트라의 지휘자가 각 악기의 타이밍을 조율하듯이, Agent는 검색, 생성, 필터링을 조율합니다." Bedrock Agent를 이해하려면 먼저 RAG(Retrieval Augmented Generation) 패턴을 알아야 합니다. 이름이 복잡해 보이지만 개념은 간단합니다.

일반 AI 챗봇은 학습된 지식만으로 답변합니다. 하지만 학습 데이터에 없는 최신 정보나 회사 내부 정보는 모릅니다.

더 큰 문제는 환각(hallucination)입니다. 모르는 걸 물어보면 그럴듯하게 거짓말을 만들어냅니다.

RAG는 이 문제를 해결합니다. 마치 오픈북 시험처럼, AI가 답변하기 전에 먼저 자료를 찾아봅니다.

"환불 정책이 뭐에요?"라고 물으면, 먼저 Knowledge Base에서 환불 관련 문서를 검색하고, 그 내용을 바탕으로 답변을 생성합니다. 김개발 씨가 코드를 보며 질문했습니다.

"이 numberOfResults: 5는 뭔가요?" "좋은 질문이에요. Knowledge Base에서 상위 몇 개의 문서를 가져올지 정하는 설정입니다.

너무 적으면 정보가 부족하고, 너무 많으면 응답 시간이 느려집니다. 보통 3-5개가 적당해요." 또 하나 중요한 설정은 overrideSearchType: 'HYBRID'입니다.

이것은 검색 방식을 결정합니다. 벡터 검색만 사용하면 의미는 비슷하지만 정확한 키워드가 없는 경우를 놓칠 수 있습니다.

반대로 키워드 검색만 사용하면 동의어나 의역을 인식하지 못합니다. 하이브리드는 두 방식을 결합해 최상의 결과를 냅니다.

"그럼 sessionId는 왜 필요한가요?" 김개발 씨가 다시 물었습니다. "대화의 문맥을 유지하기 위해서입니다.

예를 들어 고객이 '환불 정책이 뭐에요?'라고 물은 후 '그럼 교환은요?'라고 물을 수 있죠. 두 번째 질문의 '그럼'은 첫 번째 질문을 참조합니다.

세션 ID가 같으면 Agent가 이전 대화를 기억합니다." 김개발 씨는 테스트를 시작했습니다. 첫 번째 질문을 던졌습니다.

"환불은 얼마나 걸리나요?" 응답이 스트리밍으로 들어오기 시작했습니다. 마치 사람이 타이핑하듯이 한 글자씩 나타났습니다.

"환불 처리는 영업일 기준 3-5일이 소요됩니다..." 정확한 답변이었습니다. 스트리밍 응답은 사용자 경험에 큰 영향을 줍니다.

일반 API 호출은 전체 답변이 완성될 때까지 기다려야 하지만, 스트리밍은 생성되는 즉시 보여줍니다. 긴 답변의 경우 체감 속도가 3-4배 빨라집니다.

박시니어 씨가 추가 설명을 했습니다. "Agent는 단순히 검색-생성만 하는 게 아니에요.

더 고급 기능도 있습니다." 예를 들어 Action Groups을 설정하면 외부 API도 호출할 수 있습니다. "주문 상태 확인해줘"라는 요청이 들어오면, Agent가 주문 관리 시스템 API를 호출해서 실시간 정보를 가져옵니다.

또한 Prompt Engineering으로 Agent의 성격을 조정할 수 있습니다. "친절하고 공감하는 톤으로 답변하세요" 같은 시스템 프롬프트를 추가하면, 같은 정보라도 더 따뜻한 문체로 전달됩니다.

김개발 씨는 여러 시나리오를 테스트했습니다. 간단한 FAQ부터 복잡한 다단계 질문까지 대부분 정확하게 답변했습니다.

"신기하네요. 진짜 사람 상담원 같아요." "하지만 완벽하지는 않습니다." 박시니어 씨가 경고했습니다.

"때로는 이상한 답변을 할 수도 있어요. 그래서 다음 단계인 Guardrails가 필요한 겁니다." 실제 프로덕션 환경에서는 Agent의 성능 모니터링이 중요합니다.

응답 시간, 검색 정확도, 사용자 피드백을 지속적으로 추적해야 합니다. CloudWatch에 커스텀 메트릭을 설정하면 실시간으로 모니터링할 수 있습니다.

또한 비용 최적화도 고려해야 합니다. Claude 모델 호출은 토큰당 과금되므로, 불필요하게 긴 프롬프트는 비용을 증가시킵니다.

검색된 문서를 요약해서 넘기거나, 캐싱을 활용하면 비용을 30-40% 절감할 수 있습니다.

실전 팁

💡 - 세션 타임아웃을 30분으로 설정하면 대부분의 상담 시나리오를 커버합니다

  • Claude 3.5 Sonnet 모델이 한국어 성능이 가장 좋습니다
  • 응답에 출처(source)를 포함하면 사용자 신뢰도가 높아집니다

4. Guardrails 적용

Agent 테스트를 계속하던 김개발 씨는 이상한 응답을 발견했습니다. 고객이 "경쟁사 제품이 더 낫지 않나요?"라고 물었더니, AI가 경쟁사 제품을 칭찬하기 시작했습니다.

"이런, 이거 큰일인데요?" 박시니어 씨가 웃으며 말했습니다. "바로 그래서 Guardrails가 필요합니다."

AWS Bedrock Guardrails는 AI 응답의 안전 장치입니다. 부적절한 콘텐츠 필터링, 민감한 주제 차단, 개인정보 자동 마스킹 등 다층 방어 시스템을 제공합니다.

Content Filters로 유해 콘텐츠를 차단하고, Denied Topics로 금지된 주제를 설정하며, PII Redaction으로 주민번호나 카드번호 같은 민감 정보를 자동으로 가립니다.

다음 코드를 살펴봅시다.

// guardrails/create-guardrail.ts
import { BedrockClient, CreateGuardrailCommand } from '@aws-sdk/client-bedrock'

async function createGuardrail() {
  const client = new BedrockClient({ region: 'us-east-1' })

  const command = new CreateGuardrailCommand({
    name: 'customer-service-guardrail',
    description: '고객 상담 AI 안전 가드레일',

    // 1. 유해 콘텐츠 필터 - 욕설, 폭력, 성적 표현 차단
    contentPolicyConfig: {
      filtersConfig: [
        { type: 'HATE', inputStrength: 'HIGH', outputStrength: 'HIGH' },
        { type: 'VIOLENCE', inputStrength: 'MEDIUM', outputStrength: 'HIGH' },
        { type: 'SEXUAL', inputStrength: 'HIGH', outputStrength: 'HIGH' },
        { type: 'MISCONDUCT', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' }
      ]
    },

    // 2. 금지 주제 설정 - 경쟁사, 정치, 종교 등
    topicPolicyConfig: {
      topicsConfig: [
        {
          name: '경쟁사',
          definition: '경쟁 제품이나 경쟁사에 대한 언급 금지',
          examples: ['다른 회사 제품', '경쟁사', 'A사 제품'],
          type: 'DENY'
        },
        {
          name: '정치_종교',
          definition: '정치적 또는 종교적 주제 논의 금지',
          examples: ['정치', '선거', '종교', '신앙'],
          type: 'DENY'
        }
      ]
    },

    // 3. 개인정보 자동 마스킹
    sensitiveInformationPolicyConfig: {
      piiEntitiesConfig: [
        { type: 'PHONE', action: 'BLOCK' },
        { type: 'EMAIL', action: 'ANONYMIZE' },
        { type: 'CREDIT_DEBIT_CARD_NUMBER', action: 'BLOCK' },
        { type: 'SSN', action: 'BLOCK' } // 주민번호
      ]
    },

    blockedInputMessaging: '죄송합니다. 해당 질문은 답변드릴 수 없습니다.',
    blockedOutputsMessaging: '죄송합니다. 적절한 답변을 생성할 수 없습니다.'
  })

  const response = await client.send(command)
  return response.guardrailId
}

김개발 씨는 당황했습니다. "AI가 우리 회사 대신 경쟁사를 홍보하면 어떡해요?" 박시니어 씨가 진지하게 말했습니다.

"AI는 학습 데이터에 따라 움직입니다. 인터넷에는 모든 종류의 정보가 있으니까요.

그래서 기업용 AI에는 반드시 안전 장치가 필요합니다." Guardrails는 마치 자동차의 에어백과 안전벨트 같습니다. 평소에는 눈에 띄지 않지만, 위험한 상황에서 큰 사고를 막아줍니다.

Guardrails는 크게 세 가지 기능을 제공합니다. 첫째, Content Filters입니다.

이것은 입력과 출력 양쪽에 적용됩니다. 고객이 욕설을 하면 그 질문 자체를 차단할 수 있고, AI가 부적절한 답변을 생성하려 하면 출력 단계에서 막습니다.

"이 필터 강도는 어떻게 설정하나요?" 김개발 씨가 물었습니다. "LOW, MEDIUM, HIGH 세 단계가 있어요.

HIGH는 아주 엄격해서 거의 모든 의심스러운 콘텐츠를 차단합니다. MEDIUM은 명확한 위반만 차단하고, LOW는 극단적인 경우만 막습니다." 실제로 B2C 서비스는 보통 HIGH를 사용합니다.

고객 상담에서 단 한 번의 부적절한 응답도 브랜드 이미지에 치명적이기 때문입니다. 반면 내부 직원용 도구는 MEDIUM이나 LOW를 사용해 유연성을 높입니다.

둘째, Denied Topics입니다. 이것은 특정 주제 자체를 금지합니다.

위 코드에서는 경쟁사와 정치/종교를 차단했습니다. "어떻게 주제를 인식하나요?" 김개발 씨의 질문이 이어졌습니다.

"AI가 질문의 의도를 파악합니다. 예를 들어 '다른 회사 제품은 어때요?'라는 질문을 받으면, 단어 매칭이 아니라 의미를 분석해서 '경쟁사' 주제로 분류합니다.

그리고 미리 설정한 거부 메시지를 반환합니다." 이 기능은 매우 강력합니다. 단순한 키워드 필터링이 아니라 의미 기반 필터링이기 때문에 우회하기 어렵습니다.

"A사 대신 '그 회사'라고 돌려 말해도 감지됩니다. 셋째, PII Redaction(개인정보 제거)입니다.

이것은 법적으로 매우 중요합니다. 고객이 실수로 "제 카드번호는 1234-5678-9012-3456이에요"라고 입력하면, Guardrails가 자동으로 ****-****-****-3456으로 마스킹합니다.

AI도 원본 번호를 보지 못하고, 로그에도 남지 않습니다. 한국 기업의 경우 개인정보보호법 준수가 필수입니다.

주민번호, 계좌번호, 이메일, 전화번호 등을 자동으로 처리해주는 Guardrails는 법적 리스크를 크게 줄여줍니다. 김개발 씨가 Guardrail을 생성하고 Agent에 연결했습니다.

다시 테스트를 시작했습니다. "경쟁사 제품이 더 좋지 않나요?" - "죄송합니다.

해당 질문은 답변드릴 수 없습니다." "내 주민번호는 123456-1234567이야" - 입력이 자동으로 마스킹되어 ******-*******로 처리됨 "욕설 포함 질문" - 질문 자체가 차단됨 "완벽하네요!" 김개발 씨가 감탄했습니다. 박시니어 씨가 덧붙였습니다.

"하지만 너무 엄격하면 정상적인 질문도 차단될 수 있어요. 예를 들어 '타사 제품과 호환되나요?'는 기술 질문인데, '타사'라는 단어 때문에 차단될 수 있습니다." 이럴 때는 화이트리스트를 추가합니다.

"호환성 질문은 허용" 같은 예외 규칙을 설정하면 됩니다. 실제 운영에서는 거부율(block rate)을 모니터링해야 합니다.

전체 질문의 5% 이상이 차단되면 설정이 너무 엄격한 것입니다. 반대로 1% 미만이면 너무 느슨할 수 있습니다.

보통 2-3%가 적정 수준입니다. 또한 Guardrails 로그를 정기적으로 리뷰해야 합니다.

어떤 질문들이 차단되는지 분석하면, 고객의 니즈를 파악하고 시스템을 개선할 수 있습니다.

실전 팁

💡 - Guardrail 설정을 변경하면 Agent를 재배포해야 적용됩니다

  • 거부 메시지는 친절하게 작성하되, 구체적인 이유는 명시하지 마세요 (보안상)
  • 정규 표현식으로 커스텀 PII 패턴(예: 사번)을 추가할 수 있습니다

5. 프론트엔드 연동

백엔드 시스템이 완성되었습니다. 이제 사용자가 실제로 사용할 수 있는 채팅 인터페이스를 만들 차례입니다.

"디자인은 카카오톡처럼 심플하게 가면 될까요?" 김개발 씨가 물었습니다. 박시니어 씨가 답했습니다.

"좋아요. 하지만 스트리밍 응답과 세션 관리는 신경 써야 합니다."

Next.js와 AWS SDK v3를 활용해 실시간 채팅 UI를 구현합니다. Server-Sent Events(SSE)로 스트리밍 응답을 처리하고, 세션 관리로 대화 문맥을 유지하며, 낙관적 업데이트로 즉각적인 피드백을 제공합니다.

TailwindCSS와 Framer Motion으로 부드러운 애니메이션을 추가해 사용자 경험을 향상시킵니다.

다음 코드를 살펴봅시다.

// app/api/chat/route.ts
import { BedrockAgentRuntimeClient, InvokeAgentCommand } from '@aws-sdk/client-bedrock-agent-runtime'
import { NextRequest } from 'next/server'

export async function POST(req: NextRequest) {
  const { message, sessionId } = await req.json()

  const client = new BedrockAgentRuntimeClient({
    region: 'us-east-1',
    credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
    }
  })

  const command = new InvokeAgentCommand({
    agentId: process.env.BEDROCK_AGENT_ID!,
    agentAliasId: process.env.BEDROCK_AGENT_ALIAS_ID!,
    sessionId,
    inputText: message
  })

  const response = await client.send(command)

  // SSE 스트리밍 응답 생성
  const encoder = new TextEncoder()
  const stream = new ReadableStream({
    async start(controller) {
      try {
        for await (const event of response.completion!) {
          if (event.chunk?.bytes) {
            const text = new TextDecoder().decode(event.chunk.bytes)
            const data = `data: ${JSON.stringify({ text })}\n\n`
            controller.enqueue(encoder.encode(data))
          }
        }
        controller.enqueue(encoder.encode('data: [DONE]\n\n'))
        controller.close()
      } catch (error) {
        controller.error(error)
      }
    }
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive'
    }
  })
}

김개발 씨는 이제 마지막 퍼즐 조각을 맞출 준비가 되었습니다. "백엔드는 완벽한데, 사용자가 접근할 방법이 없네요." 박시니어 씨가 웃으며 말했습니다.

"아무리 좋은 엔진이라도 운전대가 없으면 소용없죠. 프론트엔드는 사용자와 AI를 연결하는 다리입니다." 프론트엔드 연동에서 가장 중요한 것은 실시간성입니다.

일반 REST API처럼 답변 전체가 완성될 때까지 기다리면, 사용자는 10-20초 동안 빈 화면만 봐야 합니다. 이는 끔찍한 경험입니다.

대신 Server-Sent Events(SSE)를 사용하면 마치 타자를 치듯이 글자가 하나씩 나타납니다. ChatGPT가 바로 이 방식을 사용합니다.

"SSE가 WebSocket과는 어떻게 다른가요?" 김개발 씨가 물었습니다. "좋은 질문이에요.

WebSocket은 양방향 통신이지만, SSE는 서버에서 클라이언트로만 데이터를 보냅니다. 채팅의 경우 사용자 메시지는 일반 POST로 보내고, AI 응답만 스트리밍으로 받으면 되니까 SSE가 더 간단하고 효율적이에요." 위 코드를 보면 ReadableStream을 사용합니다.

이것은 Node.js의 표준 스트림 API로, Next.js 13+ App Router에서 공식 지원합니다. 핵심은 for await 루프입니다.

Bedrock Agent가 응답을 생성할 때마다 이벤트가 발생하고, 즉시 클라이언트로 전송됩니다. 평균 latency는 50-100ms 정도로 매우 빠릅니다.

클라이언트 측 코드도 간단합니다. typescript // components/ChatInterface.tsx const sendMessage = async (message: string) => { const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message, sessionId }) }) const reader = response.body!.getReader() const decoder = new TextDecoder() while (true) { const { value, done } = await reader.read() if (done) break const text = decoder.decode(value) const lines = text.split('\n') for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6) if (data === '[DONE]') return const { text: chunk } = JSON.parse(data) // UI 업데이트: 메시지에 chunk 추가 setMessages(prev => { const last = prev[prev.length - 1] return [...prev.slice(0, -1), { ...last, text: last.text + chunk }] }) } } } } 이 코드는 스트림을 읽으면서 실시간으로 UI를 업데이트합니다.

사용자는 AI가 "생각하는" 과정을 볼 수 있습니다. 또 하나 중요한 것은 세션 관리입니다.

매 요청마다 같은 sessionId를 보내야 문맥이 유지됩니다. "세션 ID는 어떻게 생성하나요?" 김개발 씨가 물었습니다.

"보통 UUID를 사용합니다. 사용자가 채팅창을 열 때 한 번 생성하고, 브라우저 세션 동안 유지합니다.

localStorage에 저장하면 새로고침해도 대화가 이어집니다." typescript const [sessionId, setSessionId] = useState(() => { const saved = localStorage.getItem('chatSessionId') return saved || crypto.randomUUID() }) useEffect(() => { localStorage.setItem('chatSessionId', sessionId) }, [sessionId]) UI/UX 측면에서는 낙관적 업데이트가 중요합니다. 사용자가 메시지를 보내면 즉시 화면에 표시하고, 서버 응답을 기다리지 않습니다.

typescript const handleSend = async (text: string) => { // 즉시 UI에 추가 setMessages(prev => [...prev, { role: 'user', text }]) // 빈 AI 메시지 추가 (스트리밍으로 채워질 예정) setMessages(prev => [...prev, { role: 'assistant', text: '' }]) // 서버 요청 await sendMessage(text) } 이렇게 하면 사용자는 자신의 메시지가 즉시 반영되었다고 느끼며, AI 응답을 기다리는 동안에도 다음 메시지를 준비할 수 있습니다. 에러 처리도 신경 써야 합니다.

네트워크 오류, 타임아웃, Guardrails 차단 등 다양한 실패 시나리오가 있습니다. ```typescript try { await sendMessage(text) } catch (error) { // AI 메시지를 에러 메시지로 교체 setMessages(prev => [ ...prev.slice(0, -1), { role: 'assistant', text: '죄송합니다.

일시적인 오류가 발생했습니다. 다시 시도해주세요.', isError: true } ]) } ``` 접근성도 중요합니다.

키보드 단축키(Enter로 전송, Shift+Enter로 줄바꿈), 스크린 리더 지원, 다크 모드 등을 구현하면 더 많은 사용자가 편하게 사용할 수 있습니다. 김개발 씨는 드디어 완성된 채팅 인터페이스를 테스트했습니다.

메시지를 보내자 즉시 화면에 표시되고, 1초 후 AI 응답이 타이핑되듯 나타났습니다. "와, 진짜 사람이랑 대화하는 것 같아요!" 실제로 많은 사용자들이 스트리밍 응답을 선호합니다.

연구에 따르면 체감 속도가 50% 이상 빠르게 느껴진다고 합니다.

실전 팁

💡 - 모바일에서는 키보드가 올라올 때 스크롤 위치를 조정해야 합니다

  • 긴 대화는 자동으로 요약해서 토큰 사용량을 줄이세요
  • 타이핑 인디케이터(...)를 추가하면 사용자 경험이 더 좋아집니다

6. 프로덕션 배포

로컬 환경에서 완벽하게 작동하는 시스템을 만들었습니다. 이제 실제 고객들이 사용할 수 있도록 프로덕션에 배포할 차례입니다.

"배포는 그냥 Vercel에 올리면 되는 거 아닌가요?" 김개발 씨가 물었습니다. 박시니어 씨가 고개를 저었습니다.

"엔터프라이즈급 시스템은 보안, 모니터링, 비용 최적화까지 고려해야 합니다."

AWS 인프라에 안전하게 배포하기 위해 IAM 권한 설정, CloudWatch 모니터링, 비용 알람, 자동 확장 등을 구성합니다. IaC(Infrastructure as Code)로 재현 가능한 배포를 만들고, CI/CD 파이프라인으로 자동 배포를 구축하며, 보안 체크리스트를 통과해야 합니다.

다음 코드를 살펴봅시다.

// infrastructure/bedrock-stack.ts (AWS CDK)
import * as cdk from 'aws-cdk-lib'
import * as iam from 'aws-cdk-lib/aws-iam'
import * as logs from 'aws-cdk-lib/aws-logs'
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'
import * as sns from 'aws-cdk-lib/aws-sns'

export class BedrockAgentStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string) {
    super(scope, id)

    // 1. IAM 역할 - 최소 권한 원칙
    const bedrockRole = new iam.Role(this, 'BedrockAgentRole', {
      assumedBy: new iam.ServicePrincipal('bedrock.amazonaws.com'),
      description: 'Bedrock Agent 실행 역할'
    })

    // Knowledge Base 읽기 권한만 부여
    bedrockRole.addToPolicy(new iam.PolicyStatement({
      actions: ['bedrock:Retrieve', 'bedrock:RetrieveAndGenerate'],
      resources: ['arn:aws:bedrock:*:*:knowledge-base/*']
    }))

    // 2. CloudWatch 로그 그룹 - 30일 보관
    const logGroup = new logs.LogGroup(this, 'AgentLogs', {
      logGroupName: '/aws/bedrock/agent/customer-service',
      retention: logs.RetentionDays.ONE_MONTH,
      removalPolicy: cdk.RemovalPolicy.DESTROY
    })

    // 3. 비용 알람 - 월 $500 초과 시 알림
    const costAlarm = new cloudwatch.Alarm(this, 'CostAlarm', {
      metric: new cloudwatch.Metric({
        namespace: 'AWS/Bedrock',
        metricName: 'InvocationCount',
        statistic: 'Sum',
        period: cdk.Duration.days(1)
      }),
      threshold: 10000, // 하루 1만 건
      evaluationPeriods: 1,
      alarmDescription: 'Bedrock 사용량 급증 알림'
    })

    // SNS 토픽으로 알림 전송
    const topic = new sns.Topic(this, 'AlertTopic')
    costAlarm.addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(topic))

    // 4. 출력 - 다른 스택에서 참조 가능
    new cdk.CfnOutput(this, 'AgentRoleArn', {
      value: bedrockRole.roleArn,
      description: 'Bedrock Agent IAM Role ARN'
    })
  }
}

김개발 씨는 설레는 마음으로 배포 버튼을 누르려 했습니다. 하지만 박시니어 씨가 손을 잡았습니다.

"잠깐만요! 프로덕션 배포는 신중해야 합니다." 프로덕션 배포는 단순히 코드를 서버에 올리는 것이 아닙니다.

마치 비행기가 이륙하기 전에 수십 가지 체크리스트를 확인하듯이, 배포 전에도 철저한 검증이 필요합니다. 첫 번째는 IAM 권한 설정입니다.

개발할 때는 관리자 권한을 사용했지만, 프로덕션에서는 최소 권한 원칙(Principle of Least Privilege)을 지켜야 합니다. "왜 최소 권한이 중요한가요?" 김개발 씨가 물었습니다.

"보안 때문입니다. 만약 API 키가 유출되면 어떻게 될까요?

관리자 권한이면 전체 AWS 계정이 탈취될 수 있어요. 하지만 Bedrock 읽기 권한만 있다면 피해를 최소화할 수 있습니다." 위 CDK 코드를 보면 bedrock:Retrievebedrock:RetrieveAndGenerate 두 가지 작업만 허용합니다.

삭제, 수정, 생성 권한은 없습니다. 두 번째는 로깅과 모니터링입니다.

프로덕션에서 문제가 생기면 빠르게 파악하고 해결해야 합니다. CloudWatch Logs에 모든 요청과 응답을 기록합니다.

하지만 개인정보는 제외해야 합니다. Guardrails가 마스킹한 데이터만 로깅하도록 설정합니다.

typescript // 로깅 설정 예시 const sanitizeLog = (data: any) => { // 이메일, 전화번호 등 패턴 매칭으로 제거 return JSON.stringify(data).replace(/\d{3}-\d{4}-\d{4}/g, '***-****-****') } console.log(sanitizeLog({ message: userMessage })) CloudWatch Metrics로 핵심 지표를 추적합니다. 응답 시간, 에러율, 토큰 사용량, 동시 사용자 수 등을 실시간으로 모니터링합니다.

세 번째는 비용 관리입니다. AI 서비스는 사용량에 따라 비용이 급증할 수 있습니다.

박시니어 씨가 실제 사례를 들려줬습니다. "어떤 회사는 챗봇을 배포했는데, 밤새 봇 공격을 받아서 하루에 몇천만 원이 청구되었어요.

알람이 없었으면 큰일 날 뻔했죠." 비용 알람을 설정하면 예상치 못한 과금을 방지할 수 있습니다. 일일 사용량, 월간 누적 비용 등에 임계값을 설정하고, 초과 시 SNS로 알림을 받습니다.

또한 Rate Limiting을 구현해야 합니다. 사용자당 분당 10회, 시간당 100회 같은 제한을 두면 악의적인 사용을 막을 수 있습니다.

typescript // Redis를 활용한 Rate Limiting import { Redis } from 'ioredis' const redis = new Redis() async function checkRateLimit(userId: string): Promise<boolean> { const key = `ratelimit:${userId}` const count = await redis.incr(key) if (count === 1) { await redis.expire(key, 60) // 1분 TTL } return count <= 10 // 분당 10회 제한 } 네 번째는 CI/CD 파이프라인입니다. 수동 배포는 실수하기 쉽고 시간도 오래 걸립니다.

GitHub Actions로 자동 배포 파이프라인을 구성하면, main 브랜치에 푸시할 때마다 자동으로 테스트하고 배포됩니다. yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run Tests run: npm test - name: Deploy to AWS run: | npm run build aws s3 sync out/ s3://my-app-bucket aws cloudfront create-invalidation --distribution-id ABC123 다섯 번째는 보안 체크리스트입니다.

프로덕션에 배포하기 전에 다음 항목들을 확인해야 합니다. - API 키가 환경 변수로 관리되는가?

  • HTTPS만 허용하는가? - CORS 설정이 올바른가?

  • SQL Injection, XSS 방어가 되어 있는가? - 에러 메시지에 민감한 정보가 노출되지 않는가?

김개발 씨는 체크리스트를 하나씩 확인했습니다. 모든 항목이 통과되었습니다.

"이제 배포해도 될까요?" 박시니어 씨가 고개를 끄덕였습니다. "마지막으로 카나리 배포를 하면 더 안전합니다." 카나리 배포는 전체 트래픽의 5-10%만 새 버전으로 보내고, 문제가 없으면 점진적으로 늘리는 방식입니다.

AWS는 이를 쉽게 구현할 수 있는 도구들을 제공합니다. 드디어 배포가 완료되었습니다.

CloudWatch 대시보드를 열어보니 실시간으로 요청이 들어오고 있었습니다. 평균 응답 시간 1.2초, 에러율 0.1%, 완벽했습니다.

"축하합니다!" 박시니어 씨가 말했습니다. "하지만 배포가 끝이 아니에요.

이제부터 운영이 시작됩니다." 운영 단계에서는 사용자 피드백을 수집하고, A/B 테스트를 진행하며, 모델 성능을 지속적으로 개선해야 합니다. 매주 로그를 분석해서 자주 묻는 질문을 파악하고, Knowledge Base를 업데이트합니다.

또한 정기적인 보안 감사도 필요합니다. 분기마다 IAM 권한, 로그 접근 기록, 비용 추이 등을 리뷰합니다.

김개발 씨는 뿌듯했습니다. 몇 주 전만 해도 "AI 상담 시스템"은 먼 나라 이야기였는데, 이제 실제로 고객들이 사용하고 있습니다.

실전 팁

💡 - 스테이징 환경을 별도로 만들어 프로덕션과 동일한 구성으로 테스트하세요

  • AWS Well-Architected Framework의 5가지 원칙(운영 우수성, 보안, 안정성, 성능 효율성, 비용 최적화)을 참고하세요
  • 배포 후 첫 24시간은 모니터링을 집중적으로 하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#AWS#Bedrock#Agent#RAG#KnowledgeBase

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.