🤖

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

⚠️

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

이미지 로딩 중...

AWS Bedrock 검색과 응답 생성 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 18. · 6 Views

AWS Bedrock 검색과 응답 생성 완벽 가이드

Amazon Bedrock의 Retrieve API와 RetrieveAndGenerate API를 활용하여 지식 베이스를 검색하고 자연어 응답을 생성하는 방법을 초급자도 이해할 수 있도록 쉽게 설명합니다. 실무에서 바로 활용할 수 있는 코드 예제와 함께 제공됩니다.


목차

  1. Retrieve API 이해
  2. RetrieveAndGenerate API
  3. 검색 결과 개수 설정
  4. 응답 생성 모델 선택
  5. API 응답 구조
  6. 프롬프트 템플릿 활용
  7. 마지막에 "추가 질문이 있으시면 언제든 물어보세요"를 붙이세요

1. Retrieve API 이해

어느 날 신입 개발자 김개발 씨는 회사의 방대한 기술 문서를 검색하는 기능을 만들어야 했습니다. "수천 개의 문서 중에서 사용자 질문과 관련된 내용만 찾아내려면 어떻게 해야 하지?" 고민하던 김개발 씨에게 팀장님이 AWS Bedrock의 Retrieve API를 추천해 주었습니다.

Retrieve API는 지식 베이스에서 관련 문서를 검색하는 핵심 기능입니다. 마치 도서관 사서가 질문을 듣고 관련된 책을 찾아주는 것처럼, 사용자의 질문을 분석해 가장 연관성 높은 문서 조각들을 찾아줍니다.

이를 통해 방대한 데이터 속에서 필요한 정보만 정확하게 추출할 수 있습니다.

다음 코드를 살펴봅시다.

import boto3

# Bedrock Agent Runtime 클라이언트 생성
client = boto3.client('bedrock-agent-runtime', region_name='us-east-1')

# 지식 베이스에서 관련 문서 검색
response = client.retrieve(
    knowledgeBaseId='YOUR_KB_ID',  # 지식 베이스 ID
    retrievalQuery={
        'text': 'AWS Lambda의 동시 실행 제한은?'  # 검색할 질문
    },
    retrievalConfiguration={
        'vectorSearchConfiguration': {
            'numberOfResults': 5  # 상위 5개 결과 반환
        }
    }
)

# 검색 결과 확인
for result in response['retrievalResults']:
    print(result['content']['text'])

김개발 씨는 입사 2개월 차 주니어 개발자입니다. 오늘 팀장님으로부터 흥미로운 프로젝트를 배정받았습니다.

회사에 축적된 수천 개의 기술 문서를 효과적으로 검색하는 시스템을 만드는 것입니다. 처음에는 막막했습니다.

"이렇게 많은 문서를 어떻게 검색하지? 단순 키워드 검색으로는 부족한데..." 고민하던 김개발 씨에게 선배 개발자 박시니어 씨가 다가왔습니다.

"AWS Bedrock의 Retrieve API를 사용해 보는 건 어때요?" Retrieve API란 정확히 무엇일까요? 쉽게 비유하자면, Retrieve API는 마치 경험 많은 도서관 사서와 같습니다.

여러분이 "기후 변화에 관한 책 좀 찾아주세요"라고 질문하면, 사서는 단순히 제목에 '기후 변화'가 들어간 책만 찾는 것이 아닙니다. '지구 온난화', '탄소 배출', '환경 보호' 같은 관련 주제의 책도 함께 찾아줍니다.

Retrieve API도 이처럼 질문의 의미를 이해하고 관련성 높은 문서를 찾아냅니다. 전통적인 키워드 검색이 없던 시절을 떠올려 볼까요?

개발자들은 정확히 일치하는 단어만 찾을 수 있었습니다. 사용자가 "람다 함수 제한"이라고 검색하면 "Lambda 동시 실행"이라는 표현으로 작성된 문서는 찾지 못했습니다.

더 큰 문제는 맥락을 전혀 고려하지 못한다는 점이었습니다. 검색 결과의 정확도가 떨어지고, 사용자들은 원하는 정보를 찾기 위해 여러 번 검색어를 바꿔가며 시도해야 했습니다.

바로 이런 문제를 해결하기 위해 벡터 검색 기반의 Retrieve API가 등장했습니다. Retrieve API를 사용하면 의미 기반 검색이 가능해집니다.

단어가 정확히 일치하지 않아도 의미상 유사한 내용을 찾아낼 수 있습니다. 또한 질문의 맥락을 이해하여 더욱 정확한 결과를 제공합니다.

무엇보다 AWS가 관리하는 인프라에서 실행되므로 확장성과 안정성이 뛰어나다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 boto3 라이브러리로 bedrock-agent-runtime 클라이언트를 생성합니다. 이 클라이언트가 AWS Bedrock 서비스와 통신하는 창구 역할을 합니다.

다음으로 retrieve 메서드를 호출하는데, 이때 세 가지 핵심 파라미터를 전달합니다. knowledgeBaseId는 검색 대상이 되는 지식 베이스를 지정하고, retrievalQuery에는 사용자의 질문을 담습니다.

retrievalConfiguration에서는 검색 방식과 결과 개수를 설정할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 고객 지원 챗봇 서비스를 개발한다고 가정해봅시다. 고객이 "환불 처리는 얼마나 걸리나요?"라고 질문하면, Retrieve API가 회사의 정책 문서에서 환불 관련 내용을 찾아냅니다.

이때 '환불'이라는 단어뿐만 아니라 '반품', '결제 취소', '환급' 같은 유사 개념도 함께 검색되어 더욱 완전한 답변이 가능해집니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 numberOfResults를 너무 많이 설정하는 것입니다. 결과를 100개씩 가져오면 관련성 낮은 문서도 포함되어 응답 품질이 떨어질 수 있습니다.

일반적으로 3~10개 정도가 적절합니다. 또한 검색 쿼리는 명확하고 구체적일수록 좋습니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 "아, 단순한 키워드 검색이 아니라 의미를 이해하는 검색이군요!"라고 감탄했습니다.

Retrieve API를 제대로 이해하면 사용자에게 훨씬 정확한 정보를 제공하는 검색 시스템을 만들 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - numberOfResults는 3~10 사이로 설정하는 것이 일반적입니다

  • 검색 쿼리는 질문 형태로 작성하면 더 좋은 결과를 얻을 수 있습니다
  • 지식 베이스의 데이터 품질이 검색 결과의 품질을 결정합니다

2. RetrieveAndGenerate API

김개발 씨가 Retrieve API로 관련 문서를 잘 찾아내자, 팀장님이 새로운 요구사항을 추가했습니다. "검색 결과를 그대로 보여주지 말고, 사용자가 이해하기 쉬운 자연어로 답변을 생성해 주면 좋겠어요." 막막해하는 김개발 씨에게 박시니어 씨가 웃으며 말했습니다.

"그럴 땐 RetrieveAndGenerate API를 사용하면 돼요."

RetrieveAndGenerate API는 검색과 답변 생성을 한 번에 처리하는 통합 API입니다. 마치 전문가 비서가 자료를 찾아보고 요약해서 알려주는 것처럼, 지식 베이스에서 관련 정보를 검색한 뒤 LLM 모델을 활용해 자연스러운 답변을 만들어냅니다.

개발자는 복잡한 프롬프트 엔지니어링 없이도 고품질의 응답을 생성할 수 있습니다.

다음 코드를 살펴봅시다.

import boto3

client = boto3.client('bedrock-agent-runtime', region_name='us-east-1')

# 검색과 답변 생성을 한 번에 수행
response = client.retrieve_and_generate(
    input={
        'text': 'AWS Lambda 동시 실행 제한을 늘리려면?'
    },
    retrieveAndGenerateConfiguration={
        'type': 'KNOWLEDGE_BASE',
        'knowledgeBaseConfiguration': {
            'knowledgeBaseId': 'YOUR_KB_ID',
            'modelArn': 'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0',
            'retrievalConfiguration': {
                'vectorSearchConfiguration': {
                    'numberOfResults': 3
                }
            }
        }
    }
)

# 생성된 답변 출력
print(response['output']['text'])

김개발 씨는 Retrieve API로 관련 문서를 찾는 데 성공했습니다. 하지만 새로운 고민이 생겼습니다.

검색 결과로 나온 문서 조각들을 사용자에게 그대로 보여주자니 너무 기술적이고 어려웠습니다. "사용자들은 개발자가 아니에요.

이 복잡한 문서를 읽고 이해하기 힘들 거예요." 김개발 씨는 검색 결과를 보기 좋게 정리해야겠다고 생각했습니다. 그런데 수동으로 문서를 요약하고 답변을 작성하려니 시간이 너무 오래 걸렸습니다.

RetrieveAndGenerate API가 정확히 무엇인지 알아볼까요? 쉽게 비유하자면, RetrieveAndGenerate API는 마치 유능한 비서와 같습니다.

여러분이 비서에게 "내일 회의 자료 좀 찾아봐"라고 하면, 비서는 관련 파일을 찾는 것에서 그치지 않습니다. 그 내용을 읽고 핵심만 추려서 "내일 회의는 2시이고, 주요 안건은 세 가지입니다"라고 정리해서 알려줍니다.

RetrieveAndGenerate API도 이처럼 검색과 답변 생성을 모두 수행합니다. Retrieve API만 사용하던 시절의 문제점을 생각해 볼까요?

개발자는 먼저 Retrieve API로 문서를 검색하고, 그 결과를 다시 LLM에 전달하여 답변을 생성해야 했습니다. 두 단계를 거쳐야 하니 코드가 복잡해지고 관리하기 어려워졌습니다.

더 큰 문제는 프롬프트 엔지니어링이었습니다. 검색 결과를 LLM에 어떻게 전달할지, 어떤 형식으로 답변을 요청할지 모두 개발자가 직접 설계해야 했습니다.

바로 이런 번거로움을 해결하기 위해 RetrieveAndGenerate API가 탄생했습니다. RetrieveAndGenerate API를 사용하면 한 번의 호출로 검색부터 답변 생성까지 완료됩니다.

AWS가 내부적으로 최적화된 프롬프트를 사용하므로 개발자는 프롬프트 엔지니어링에 신경 쓸 필요가 없습니다. 또한 검색 결과의 출처를 자동으로 추적하여 답변의 신뢰성을 높일 수 있습니다.

무엇보다 코드가 간결해져 유지보수가 훨씬 쉬워진다는 큰 장점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

retrieve_and_generate 메서드를 호출할 때 input에는 사용자의 질문을 담습니다. retrieveAndGenerateConfiguration에서는 type을 KNOWLEDGE_BASE로 설정하여 지식 베이스를 활용한다고 명시합니다.

knowledgeBaseConfiguration 안에서 지식 베이스 ID, 사용할 LLM 모델, 검색 설정을 모두 지정할 수 있습니다. 응답으로 받은 output.text에는 LLM이 생성한 자연어 답변이 담겨 있습니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 기업용 Q&A 시스템을 개발한다고 가정해봅시다.

직원이 "재택근무 신청 절차가 어떻게 되나요?"라고 질문하면, RetrieveAndGenerate API가 인사 규정 문서에서 관련 내용을 찾고 "재택근무 신청은 3단계로 진행됩니다. 첫째, 팀장 승인을 받으세요..."처럼 친절하게 답변을 생성해 줍니다.

많은 기업들이 사내 헬프데스크 시스템에 이런 방식을 적극 활용하고 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수는 모델 선택을 소홀히 하는 것입니다. 간단한 질문에 너무 큰 모델을 사용하면 비용이 증가하고 응답 속도가 느려집니다.

반대로 복잡한 질문에 작은 모델을 사용하면 답변 품질이 떨어집니다. 용도에 맞는 적절한 모델을 선택해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. RetrieveAndGenerate API를 적용한 김개발 씨는 놀라운 결과를 보았습니다.

검색부터 답변 생성까지 자동으로 처리되어 사용자들이 매우 만족했습니다. RetrieveAndGenerate API를 제대로 이해하면 훨씬 적은 코드로 강력한 Q&A 시스템을 구축할 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 간단한 질문에는 Claude Haiku, 복잡한 질문에는 Claude Sonnet을 권장합니다

  • 응답의 citations 필드를 활용하면 답변의 출처를 확인할 수 있습니다
  • sessionId를 사용하면 대화 맥락을 유지할 수 있습니다

3. 검색 결과 개수 설정

시스템을 운영하던 어느 날, 김개발 씨는 이상한 현상을 발견했습니다. 어떤 질문은 답변이 매우 정확한데, 어떤 질문은 엉뚱한 내용이 섞여 나왔습니다.

"왜 이런 차이가 날까?" 고민하던 김개발 씨에게 박시니어 씨가 물었습니다. "검색 결과를 몇 개로 설정했어요?"

numberOfResults 파라미터는 지식 베이스에서 가져올 문서 조각의 개수를 지정합니다. 마치 도서관에서 몇 권의 책을 빌릴지 정하는 것과 같습니다.

너무 적으면 충분한 정보를 얻지 못하고, 너무 많으면 불필요한 내용까지 포함되어 답변 품질이 떨어집니다. 적절한 균형을 찾는 것이 핵심입니다.

다음 코드를 살펴봅시다.

import boto3

client = boto3.client('bedrock-agent-runtime', region_name='us-east-1')

# 검색 결과 개수를 조정하여 호출
response = client.retrieve_and_generate(
    input={
        'text': '쿠버네티스 오토스케일링 설정 방법은?'
    },
    retrieveAndGenerateConfiguration={
        'type': 'KNOWLEDGE_BASE',
        'knowledgeBaseConfiguration': {
            'knowledgeBaseId': 'YOUR_KB_ID',
            'modelArn': 'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0',
            'retrievalConfiguration': {
                'vectorSearchConfiguration': {
                    'numberOfResults': 5  # 결과 개수: 1~100 사이
                }
            }
        }
    }
)

print(response['output']['text'])

김개발 씨가 만든 Q&A 시스템이 실제 사용자들에게 공개되었습니다. 처음 며칠은 모든 것이 순조로웠습니다.

사용자들의 만족도도 높았습니다. 그런데 일주일쯤 지나자 이상한 패턴이 발견되었습니다.

"AWS Lambda 가격 정책"에 대한 질문에는 정확한 답변이 나왔는데, "Lambda와 ECS 중 어떤 걸 선택해야 하나요?"라는 질문에는 관련 없는 S3 스토리지 이야기까지 섞여 나왔습니다. "왜 이럴까?" 김개발 씨는 로그를 분석하기 시작했습니다.

numberOfResults 설정이 왜 중요한지 알아볼까요? 쉽게 비유하자면, numberOfResults는 마치 레시피를 찾을 때 참고할 요리책의 개수와 같습니다.

파스타를 만들려고 하는데 요리책을 1권만 보면 그 책에 원하는 레시피가 없을 수 있습니다. 하지만 요리책을 50권이나 꺼내면 중국 요리, 한식 레시피까지 섞여서 오히려 혼란스러워집니다.

적당히 3~5권 정도가 딱 좋습니다. numberOfResults도 이처럼 적절한 개수를 찾는 것이 중요합니다.

검색 결과가 너무 적을 때의 문제점을 생각해 볼까요? numberOfResults를 1로 설정하면 가장 관련성 높은 문서 하나만 가져옵니다.

하지만 복잡한 질문은 여러 측면의 정보가 필요합니다. 예를 들어 "마이크로서비스 아키텍처의 장단점"을 물으면 장점 문서 하나만 가져와서 단점은 설명하지 못할 수 있습니다.

답변이 불완전해지는 것입니다. 반대로 검색 결과가 너무 많으면 어떤 문제가 생길까요?

numberOfResults를 100으로 설정하면 관련성이 낮은 문서까지 모두 가져옵니다. LLM은 이 많은 정보를 처리하려다 중요한 내용과 부차적인 내용을 구분하지 못할 수 있습니다.

게다가 처리해야 할 토큰 수가 증가하여 응답 속도가 느려지고 비용도 증가합니다. 더 많은 정보가 항상 더 나은 답변을 만드는 것은 아닙니다.

적절한 numberOfResults 값은 어떻게 찾을까요? 일반적으로 3~10 사이의 값이 적절합니다.

간단한 사실 확인 질문에는 3~5개면 충분합니다. 예를 들어 "Lambda의 최대 타임아웃은?"처럼 명확한 답이 있는 질문입니다.

복잡한 비교나 분석이 필요한 질문에는 7~10개가 좋습니다. "서버리스와 컨테이너의 비용 비교"처럼 다양한 관점이 필요한 경우입니다.

위의 코드를 한 줄씩 살펴보겠습니다. vectorSearchConfiguration 안의 numberOfResults 필드에 원하는 개수를 지정합니다.

값은 1부터 100까지 설정할 수 있습니다. 이 예제에서는 5로 설정했는데, 이는 균형 잡힌 선택입니다.

충분한 정보를 제공하면서도 불필요한 내용은 배제할 수 있는 적절한 수준입니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 고객 지원 챗봇을 운영한다고 가정해봅시다. "비밀번호 재설정 방법"처럼 단순한 질문에는 numberOfResults를 3으로 설정합니다.

하지만 "요금제 변경 시 주의사항"처럼 여러 조건과 예외사항을 설명해야 하는 질문에는 8~10으로 늘립니다. 질문의 복잡도에 따라 동적으로 조정할 수도 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수는 모든 질문에 같은 numberOfResults를 사용하는 것입니다.

질문의 종류와 복잡도를 고려하지 않고 고정 값을 사용하면 답변 품질이 일관되지 않습니다. A/B 테스트를 통해 서비스에 가장 적합한 값을 찾아야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 로그 분석 결과, numberOfResults를 20으로 설정했던 것이 문제였습니다.

박시니어 씨의 조언대로 5로 조정하자 답변 품질이 크게 개선되었습니다. numberOfResults 설정을 제대로 이해하면 비용을 절감하면서도 높은 품질의 답변을 제공할 수 있습니다.

여러분도 실제 서비스에서 다양한 값을 테스트해 보세요.

실전 팁

💡 - 처음에는 5로 시작해서 품질을 평가하며 조정하세요

  • 질문 복잡도에 따라 동적으로 numberOfResults를 변경할 수 있습니다
  • 비용과 품질의 균형을 고려하여 최적값을 찾으세요

4. 응답 생성 모델 선택

시스템이 안정화되자 김개발 씨는 새로운 과제에 직면했습니다. 비용 청구서를 보니 생각보다 많은 금액이 나왔습니다.

"이렇게 많이 나올 줄 몰랐는데..." 고민하던 김개발 씨에게 박시니어 씨가 조언했습니다. "모든 질문에 Sonnet 모델을 쓸 필요는 없어요.

질문에 따라 적절한 모델을 선택하면 비용을 크게 줄일 수 있어요."

modelArn 파라미터는 답변 생성에 사용할 LLM 모델을 지정합니다. AWS Bedrock은 Claude Haiku, Sonnet, Opus 같은 다양한 모델을 제공하며, 각각 성능과 비용이 다릅니다.

마치 배송 서비스에서 일반 배송과 특급 배송을 선택하는 것처럼, 질문의 복잡도에 맞는 모델을 선택하면 비용 효율성을 높일 수 있습니다.

다음 코드를 살펴봅시다.

import boto3

client = boto3.client('bedrock-agent-runtime', region_name='us-east-1')

# 간단한 질문: Claude Haiku 사용 (저비용)
simple_response = client.retrieve_and_generate(
    input={'text': 'Lambda의 최대 메모리는?'},
    retrieveAndGenerateConfiguration={
        'type': 'KNOWLEDGE_BASE',
        'knowledgeBaseConfiguration': {
            'knowledgeBaseId': 'YOUR_KB_ID',
            'modelArn': 'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-haiku-20240307-v1:0',  # Haiku 모델
            'retrievalConfiguration': {
                'vectorSearchConfiguration': {'numberOfResults': 3}
            }
        }
    }
)

# 복잡한 질문: Claude Sonnet 사용 (고성능)
complex_response = client.retrieve_and_generate(
    input={'text': 'Lambda와 ECS의 아키텍처 차이점과 각각의 사용 시나리오를 비교해주세요'},
    retrieveAndGenerateConfiguration={
        'type': 'KNOWLEDGE_BASE',
        'knowledgeBaseConfiguration': {
            'knowledgeBaseId': 'YOUR_KB_ID',
            'modelArn': 'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0',  # Sonnet 모델
            'retrievalConfiguration': {
                'vectorSearchConfiguration': {'numberOfResults': 8}
            }
        }
    }
)

김개발 씨의 Q&A 시스템은 매우 성공적이었습니다. 하루 수천 건의 질문이 들어왔고, 사용자들의 만족도도 높았습니다.

그런데 한 달 후 받은 AWS 청구서를 보고 깜짝 놀랐습니다. "이렇게 많이 나온다고?" 팀장님도 비용에 대해 우려를 표했습니다.

"좋은 시스템이긴 한데, 비용을 좀 줄일 방법이 없을까요?" 김개발 씨는 비용 분석을 시작했습니다. 대부분의 질문이 간단한 사실 확인인데도 모두 비싼 Sonnet 모델을 사용하고 있었습니다.

모델 선택이 왜 중요한지 알아볼까요? 쉽게 비유하자면, 모델 선택은 마치 교통수단 선택과 같습니다.

집 앞 편의점에 가는데 택시를 탈 필요가 없습니다. 걸어가거나 자전거를 타면 충분합니다.

하지만 공항에 늦게 가야 한다면 택시나 KTX가 필요합니다. LLM 모델도 마찬가지입니다.

간단한 질문에는 가벼운 모델, 복잡한 질문에는 강력한 모델을 사용하는 것이 효율적입니다. AWS Bedrock이 제공하는 주요 모델들을 살펴볼까요?

Claude Haiku는 가장 빠르고 저렴한 모델입니다. 간단한 사실 확인, 정의 설명, 기본적인 요약 작업에 적합합니다.

응답 속도가 매우 빠르고 비용도 저렴해서 대량의 간단한 질문을 처리하기에 좋습니다. 반면 복잡한 추론이나 깊이 있는 분석에는 부족할 수 있습니다.

Claude Sonnet은 성능과 비용의 균형이 잘 잡힌 모델입니다. 대부분의 업무용 질문을 처리하기에 충분한 능력을 갖추고 있습니다.

비교 분석, 다단계 추론, 상세한 설명이 필요한 질문에 적합합니다. 가격 대비 성능이 뛰어나 많은 기업에서 기본 모델로 선택합니다.

Claude Opus는 가장 강력하지만 가장 비싼 모델입니다. 매우 복잡한 추론, 창의적인 작업, 긴 문서 분석이 필요할 때 사용합니다.

대부분의 일반적인 Q&A 시스템에서는 오버스펙일 수 있습니다. 그렇다면 어떤 질문에 어떤 모델을 사용해야 할까요?

Haiku를 사용하면 좋은 경우: "Lambda의 최대 타임아웃은?", "S3 버킷 생성 명령어는?", "VPC란 무엇인가?" 같은 단순 사실 확인 질문입니다. 명확한 답이 문서에 있고 복잡한 추론이 필요 없습니다.

Sonnet을 사용하면 좋은 경우: "Lambda와 EC2의 비용 비교", "RDS Multi-AZ 설정 방법과 장점", "API Gateway 인증 방식 추천" 같은 중간 복잡도 질문입니다. 여러 정보를 종합하고 맥락을 고려한 설명이 필요합니다.

Opus를 사용하면 좋은 경우: "대규모 트래픽 처리를 위한 AWS 아키텍처 설계", "마이크로서비스 전환 시 고려사항 분석" 같은 고도의 전문성이 필요한 질문입니다. 다양한 요소를 종합적으로 분석해야 합니다.

위의 코드를 한 줄씩 살펴보겠습니다. modelArn 필드에 사용할 모델의 ARN을 지정합니다.

간단한 질문에는 Haiku 모델의 ARN을 사용하고, 복잡한 질문에는 Sonnet 모델의 ARN을 사용합니다. 각 모델은 고유한 ARN 형식을 가지고 있으며, 리전과 버전 정보가 포함되어 있습니다.

실제 서비스에서는 질문 분류 로직을 만들어 자동으로 적절한 모델을 선택하게 할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 사내 IT 헬프데스크 시스템을 운영한다고 가정해봅시다. 질문을 받으면 먼저 질문의 복잡도를 분석합니다.

키워드가 명확하고 길이가 짧으면 Haiku를 사용합니다. "왜", "어떻게", "비교" 같은 단어가 있고 문장이 길면 Sonnet을 사용합니다.

이렇게 하면 월 비용을 30~50% 정도 절감할 수 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수는 무조건 저렴한 모델만 사용하는 것입니다. 비용만 생각해서 모든 질문에 Haiku를 사용하면 복잡한 질문의 답변 품질이 떨어집니다.

사용자 만족도가 낮아지면 시스템 자체의 가치가 감소합니다. 적절한 균형을 찾아야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언대로 질문을 분류하여 Haiku와 Sonnet을 적절히 사용하도록 시스템을 개선했습니다.

그 결과 답변 품질은 유지하면서 비용을 40% 절감할 수 있었습니다. 모델 선택을 제대로 이해하면 비용 효율적이면서도 높은 품질의 서비스를 제공할 수 있습니다.

여러분도 질문 유형에 따라 적절한 모델을 선택해 보세요.

실전 팁

💡 - 처음에는 Sonnet으로 시작해서 간단한 질문만 Haiku로 전환하세요

  • 질문 길이, 키워드, 복잡도 점수를 기준으로 모델을 자동 선택할 수 있습니다
  • 주기적으로 각 모델의 답변 품질을 평가하여 분류 기준을 개선하세요

5. API 응답 구조

시스템이 잘 돌아가던 어느 날, 김개발 씨는 답변의 출처를 표시해 달라는 요청을 받았습니다. "답변이 어떤 문서에서 왔는지 알려주면 신뢰도가 높아질 것 같아요." 하지만 김개발 씨는 응답에서 텍스트만 뽑아 쓰고 있었습니다.

"출처 정보가 어디 있지?" 박시니어 씨가 웃으며 말했습니다. "응답 구조를 자세히 보면 citations 필드에 다 있어요."

API 응답 구조는 생성된 답변뿐만 아니라 출처, 메타데이터, 세션 정보 등 다양한 정보를 담고 있습니다. 마치 신문 기사가 본문뿐 아니라 출처, 작성 시간, 카테고리 정보를 함께 제공하는 것처럼, Bedrock API도 풍부한 정보를 제공합니다.

이를 활용하면 답변의 신뢰도를 높이고 사용자 경험을 개선할 수 있습니다.

다음 코드를 살펴봅시다.

import boto3
import json

client = boto3.client('bedrock-agent-runtime', region_name='us-east-1')

response = client.retrieve_and_generate(
    input={'text': 'DynamoDB의 파티션 키 설계 원칙은?'},
    retrieveAndGenerateConfiguration={
        'type': 'KNOWLEDGE_BASE',
        'knowledgeBaseConfiguration': {
            'knowledgeBaseId': 'YOUR_KB_ID',
            'modelArn': 'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0'
        }
    }
)

# 생성된 답변
answer = response['output']['text']
print(f"답변: {answer}\n")

# 출처 정보 (citations)
citations = response.get('citations', [])
for i, citation in enumerate(citations):
    print(f"출처 {i+1}:")
    for reference in citation.get('retrievedReferences', []):
        print(f"  문서: {reference['location']['s3Location']['uri']}")
        print(f"  내용: {reference['content']['text'][:100]}...")

# 세션 ID (대화 맥락 유지용)
session_id = response.get('sessionId', '')
print(f"\n세션 ID: {session_id}")

김개발 씨의 Q&A 시스템은 정확한 답변을 제공하고 있었습니다. 하지만 사용자들로부터 이런 피드백이 들어오기 시작했습니다.

"답변은 좋은데, 이 정보가 어디서 나온 건지 확인하고 싶어요." 법무팀에서도 비슷한 요청을 했습니다. "고객에게 제공하는 정보는 출처가 명확해야 합니다.

혹시 잘못된 정보를 제공하면 책임 소재를 따져야 하니까요." 김개발 씨는 응답에서 텍스트만 뽑아 쓰고 있었는데, 출처 정보도 함께 제공해야 했습니다. API 응답 구조가 어떻게 구성되어 있는지 알아볼까요?

쉽게 비유하자면, API 응답은 마치 잘 정리된 연구 보고서와 같습니다. 보고서에는 본문만 있는 것이 아닙니다.

요약, 참고 문헌, 작성 날짜, 버전 정보가 모두 포함되어 있습니다. Bedrock API 응답도 이처럼 다층적인 정보를 담고 있어서, 개발자가 필요에 따라 다양하게 활용할 수 있습니다.

응답의 주요 구성 요소를 하나씩 살펴보겠습니다. output 필드는 가장 핵심적인 부분입니다.

output.text에는 LLM이 생성한 자연어 답변이 담겨 있습니다. 이것이 사용자에게 직접 보여주는 내용입니다.

대부분의 개발자들이 이 필드만 사용하는데, 사실 그것은 응답의 일부분일 뿐입니다. citations 필드는 답변의 근거를 제공합니다.

이 배열에는 답변을 생성할 때 참고한 문서들의 정보가 담겨 있습니다. 각 citation은 retrievedReferences를 포함하는데, 여기에 실제 문서의 위치와 내용이 들어 있습니다.

마치 학술 논문의 참고 문헌 목록과 같습니다. retrievedReferences 안의 정보를 더 자세히 보면, location 필드에는 문서의 실제 위치가 담겨 있습니다.

S3에 저장된 문서라면 s3Location.uri에 버킷과 키 정보가 있습니다. content.text에는 실제로 검색된 문서 조각의 텍스트가 포함됩니다.

metadata에는 문서의 제목, 작성일, 카테고리 같은 추가 정보가 들어 있을 수 있습니다. sessionId 필드는 대화형 시스템에서 매우 유용합니다.

이 ID를 다음 요청에 포함하면 이전 대화의 맥락을 유지할 수 있습니다. 예를 들어 사용자가 "그것의 장점은?"이라고 물으면, 이전 대화에서 무엇을 말했는지 알아야 "그것"이 무엇인지 이해할 수 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 response 객체에서 output.text를 추출하여 기본 답변을 얻습니다.

다음으로 citations 배열을 순회하면서 각 출처의 retrievedReferences를 확인합니다. location.s3Location.uri에서 문서의 S3 경로를 얻고, content.text에서 실제 참고된 텍스트를 확인할 수 있습니다.

마지막으로 sessionId를 저장해 두었다가 다음 대화에 활용할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 금융 서비스의 규정 안내 시스템을 만든다고 가정해봅시다. 사용자에게 답변을 보여줄 때 하단에 "이 정보는 '금융거래 규정 2024.pdf' 3페이지에서 가져왔습니다"라고 표시합니다.

사용자는 원본 문서를 직접 확인할 수 있어 신뢰도가 높아집니다. 또한 대화형 상담에서는 sessionId를 활용하여 "추가 질문이 있으신가요?"라고 물었을 때 맥락을 유지할 수 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수는 citations를 무시하는 것입니다.

텍스트만 뽑아서 사용하면 편하지만, 출처 정보를 제공하지 않으면 답변의 신뢰성이 떨어집니다. 특히 의료, 법률, 금융 같은 민감한 분야에서는 출처 표시가 필수입니다.

또한 citations 배열이 비어 있을 수 있으므로 항상 존재 여부를 확인해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨가 응답 구조를 설명해 주자 김개발 씨는 즉시 코드를 수정했습니다. 답변 하단에 출처 문서 링크를 추가하고, 대화형 기능도 sessionId를 활용해 구현했습니다.

사용자들의 만족도가 크게 높아졌습니다. API 응답 구조를 제대로 이해하면 단순한 Q&A를 넘어 신뢰할 수 있는 지식 관리 시스템을 만들 수 있습니다.

여러분도 응답의 모든 필드를 활용해 보세요.

실전 팁

💡 - citations를 활용하여 답변 하단에 "참고 문서" 섹션을 만드세요

  • sessionId를 데이터베이스에 저장하면 대화 히스토리를 관리할 수 있습니다
  • metadata 필드에는 커스텀 정보를 추가할 수 있어 문서 분류에 유용합니다

6. 프롬프트 템플릿 활용

시스템이 안정화되자 김개발 씨는 새로운 요구사항을 받았습니다. "답변을 항상 '안녕하세요'로 시작하고, 마지막에 '추가 질문이 있으시면 언제든 물어보세요'를 붙여주세요." 하지만 RetrieveAndGenerate API는 프롬프트를 직접 제어할 수 없었습니다.

"어떻게 하지?" 고민하던 김개발 씨에게 박시니어 씨가 말했습니다. "orchestrationConfiguration을 사용하면 프롬프트 템플릿을 커스터마이징할 수 있어요."

프롬프트 템플릿은 LLM이 답변을 생성할 때 사용하는 지시사항을 커스터마이징하는 기능입니다. 마치 직원에게 업무 매뉴얼을 제공하는 것처럼, LLM에게 답변 형식, 톤, 제약사항을 명확히 지시할 수 있습니다.

기본 프롬프트로는 충족할 수 없는 특수한 요구사항을 구현할 때 매우 유용합니다.

다음 코드를 살펴봅시다.

import boto3

client = boto3.client('bedrock-agent-runtime', region_name='us-east-1')

# 커스텀 프롬프트 템플릿 정의
prompt_template = """다음 맥락 정보를 바탕으로 질문에 답변하세요.

답변 작성 규칙:

4. 마지막에 "추가 질문이 있으시면 언제든 물어보세요"를 붙이세요

김개발 씨의 Q&A 시스템은 이제 매우 성숙한 단계에 이르렀습니다. 기술적으로는 완벽했지만, 마케팅팀과 고객 서비스팀에서 새로운 요청이 들어왔습니다.

"답변이 너무 딱딱해요. 우리 회사의 친근한 이미지를 반영했으면 좋겠어요." "매번 '추가로 도움이 필요하시면 고객센터로 연락주세요'라는 안내를 붙여주면 좋겠어요." "기술 문서팀에서는 반대로 더 격식 있고 정확한 표현을 원해요." 김개발 씨는 막막했습니다.

RetrieveAndGenerate API는 내부적으로 프롬프트를 자동 생성하는데, 어떻게 답변 스타일을 바꿀 수 있을까요? 프롬프트 템플릿이 무엇인지 알아볼까요?

쉽게 비유하자면, 프롬프트 템플릿은 마치 직원 교육 매뉴얼과 같습니다. 신입 사원에게 "고객 응대를 잘 하세요"라고만 하면 각자 다른 방식으로 일합니다.

하지만 "항상 웃으며 인사하고, 고객 이름을 부르며, 마지막에 감사 인사를 하세요"처럼 구체적인 매뉴얼을 주면 일관된 서비스를 제공할 수 있습니다. 프롬프트 템플릿도 이처럼 LLM에게 구체적인 지침을 제공합니다.

기본 프롬프트의 한계를 생각해 볼까요? AWS가 제공하는 기본 프롬프트는 범용적으로 설계되어 있습니다.

대부분의 상황에서 잘 작동하지만, 특정 회사의 브랜드 톤이나 특수한 답변 형식을 반영하지 못합니다. 예를 들어 의료 기관은 매우 신중한 표현이 필요하고, 스타트업은 친근하고 캐주얼한 톤이 필요합니다.

이런 차이를 기본 프롬프트로는 표현할 수 없습니다. 커스텀 프롬프트 템플릿을 사용하면 어떤 이점이 있을까요?

첫째, 답변의 일관성을 보장할 수 있습니다. 모든 답변이 같은 형식과 톤을 유지하므로 브랜드 이미지가 강화됩니다.

둘째, 도메인 특화 지식을 주입할 수 있습니다. "의학 용어는 일반인도 이해할 수 있게 쉽게 풀어서 설명하세요"같은 지침을 추가할 수 있습니다.

셋째, 규제 준수가 가능합니다. "법률 조언은 하지 말고, 전문가 상담을 권유하세요"같은 제약을 명시할 수 있습니다.

프롬프트 템플릿의 구조를 살펴보겠습니다. 템플릿에는 두 가지 변수가 자동으로 치환됩니다.

**$search_results$**에는 지식 베이스에서 검색한 문서 내용이 들어갑니다. **$query$**에는 사용자의 질문이 들어갑니다.

개발자는 이 변수들을 활용하여 원하는 형식의 프롬프트를 만들 수 있습니다. 좋은 프롬프트 템플릿을 작성하는 핵심 원칙이 있습니다.

명확성이 가장 중요합니다. "친절하게 답변하세요"보다는 "항상 '안녕하세요'로 시작하고, 존댓말을 사용하세요"처럼 구체적으로 작성합니다.

또한 우선순위를 명시해야 합니다. "정확성이 가장 중요하며, 불확실하면 '확실하지 않습니다'라고 답하세요"처럼 판단 기준을 제공합니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 prompt_template 변수에 커스텀 프롬프트를 문자열로 정의합니다.

답변 작성 규칙을 번호로 정리하여 명확하게 제시했습니다. generationConfiguration 안의 promptTemplate 필드에 이 템플릿을 전달합니다.

textPromptTemplate 키에 실제 프롬프트 문자열을 넣으면, API가 이 템플릿을 사용하여 답변을 생성합니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 전자상거래 회사의 고객 지원 시스템을 만든다고 가정해봅시다. 프롬프트 템플릿에 "고객이 환불을 요청하면 친절하게 절차를 안내하되, 직접 환불 처리는 하지 말고 고객센터 연락처를 안내하세요"라고 명시합니다.

"부정적인 표현 대신 긍정적인 대안을 제시하세요"같은 고객 서비스 원칙도 추가합니다. 이렇게 하면 수천 건의 질문에도 일관된 브랜드 경험을 제공할 수 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수는 프롬프트를 너무 복잡하게 만드는 것입니다.

10가지 이상의 규칙을 나열하면 LLM이 혼란스러워하고 일부 규칙을 무시할 수 있습니다. 3~5개의 핵심 규칙에 집중하는 것이 효과적입니다.

또한 프롬프트가 길어질수록 비용이 증가하므로 간결함과 명확성의 균형을 찾아야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 도움으로 프롬프트 템플릿을 도입한 김개발 씨는 부서별로 다른 템플릿을 적용했습니다. 마케팅팀용은 친근한 톤, 기술 문서팀용은 전문적인 톤으로 설정했습니다.

각 부서의 만족도가 크게 높아졌습니다. 프롬프트 템플릿을 제대로 이해하면 단순한 검색 시스템을 회사의 브랜드와 문화를 반영하는 지능형 어시스턴트로 발전시킬 수 있습니다.

여러분도 조직의 특성에 맞는 템플릿을 만들어 보세요.

실전 팁

💡 - 템플릿은 짧고 명확하게 작성하세요 (3-5개 규칙 권장)

  • 부서나 사용 사례별로 다른 템플릿을 준비하면 유연성이 높아집니다
  • A/B 테스트로 어떤 프롬프트가 더 나은 답변을 만드는지 검증하세요

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

#AWS#Bedrock#RetrieveAPI#RAG#KnowledgeBase

댓글 (0)

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