사내 위키나 운영 문서를 뒤지다가 "이 설정 어디에 적혀 있더라" 하고 시간을 버리는 일이 잦다. 이런 문서를 검색해서 답을 만들어 주는 RAG 챗봇을 직접 짜려면 임베딩, 벡터 DB, 프롬프트 조립까지 손이 많이 간다. AWS Bedrock Knowledge Bases는 이 과정을 관리형으로 묶어 준다. 제가 실제로 구성해 보면 코드보다 IAM 권한과 벡터 인덱스 설정에서 시간을 더 쓴다. 그래서 이 글은 그 두 지점을 특히 자세히 다룬다.
요약: Bedrock Knowledge Bases는 S3에 올린 문서를 자동으로 청킹, 임베딩해서 OpenSearch Serverless 같은 벡터 스토어에 넣고, retrieveAndGenerate API 한 번으로 검색과 답변 생성을 처리한다. 준비물은 문서용 S3 버킷, 벡터 스토어(콘솔 quick-create로 자동 생성 가능), Bedrock 서비스 역할(IAM)이다. 나머지는 데이터 소스 동기화(ingestion) 한 번과 런타임 호출 한 번으로 끝난다.
1. 핵심 용어 정리
RAG(Retrieval-Augmented Generation)는 질문과 관련된 문서 조각을 먼저 검색한 뒤, 그 내용을 근거로 LLM이 답을 만드는 방식이다. 모델을 재학습하지 않고도 사내 문서 기반 답변을 얻는다.
Knowledge Base는 Bedrock이 제공하는 관리형 RAG 리소스다. 데이터 소스(S3), 임베딩 모델, 벡터 스토어를 하나로 묶어 준다.
벡터 스토어는 문서 조각을 임베딩(숫자 벡터)으로 바꿔 저장하고 유사도 검색을 해 주는 저장소다. 이 글에서는 Amazon OpenSearch Serverless를 쓴다.
청킹(chunking)은 긴 문서를 검색 단위로 쪼개는 작업이다. 조각이 너무 크면 검색 정밀도가 떨어지고, 너무 작으면 문맥이 잘린다.
2. 전체 구성
구성 요소와 순서는 다음과 같다.
- 문서를 S3 버킷에 적재한다(데이터 소스).
- OpenSearch Serverless 벡터 검색 컬렉션과 인덱스를 만든다(또는 quick-create로 자동 생성).
- Bedrock이 대신 접근할 IAM 서비스 역할을 만든다.
- Knowledge Base를 생성하고 임베딩 모델과 청킹 전략을 지정한다.
- 데이터 소스를 동기화(ingestion job) 한다. 이 단계에서 청킹, 임베딩, 인덱싱이 일어난다.
- 애플리케이션에서 retrieveAndGenerate를 호출한다.
리전은 Bedrock과 OpenSearch Serverless가 모두 되는 곳으로 통일한다. 버전, 모델 가용성은 리전에 따라 다르다.
3. S3에 문서 적재
지원 형식은 텍스트 계열이다. PDF, TXT, Markdown(.md), HTML, CSV, DOC/DOCX, XLS/XLSX 등을 넣을 수 있다. 문서용 버킷을 하나 파고 올린다.
$ aws s3 mb s3://sarc-kb-docs --region us-west-2 make_bucket: sarc-kb-docs $ aws s3 cp ./docs/ s3://sarc-kb-docs/docs/ --recursive upload: docs/runbook-deploy.md to s3://sarc-kb-docs/docs/runbook-deploy.md upload: docs/onboarding.pdf to s3://sarc-kb-docs/docs/onboarding.pdf upload: docs/network-policy.docx to s3://sarc-kb-docs/docs/network-policy.docx
여기서 한 가지 팁. 메타데이터 필터링을 쓸 계획이라면 문서마다 같은 이름의 .metadata.json 파일을 나란히 올려 둔다. 예를 들어 onboarding.pdf 옆에 onboarding.pdf.metadata.json을 두고 부서, 연도 같은 속성을 넣으면 나중에 검색에서 필터로 걸 수 있다.
{
"metadataAttributes": {
"team": "infra",
"year": 2026
}
}
4. OpenSearch Serverless 벡터 스토어 연결
두 가지 길이 있다. 콘솔에서 Knowledge Base를 만들 때 quick-create를 고르면 Bedrock이 벡터 컬렉션과 인덱스를 자동으로 만들어 준다. 처음이라면 이 길을 권한다. 손이 덜 가고 설정 실수가 적다.
인덱스를 직접 만들어야 하는 경우(기존 컬렉션 재사용 등)에는 규칙이 정해져 있다. 콘솔의 벡터 검색 컬렉션에서 인덱스를 만들 때 다음을 맞춘다.
- Engine:
faiss를 선택한다. Serverless에서는 이게 필수다. - Dimensions: 임베딩 모델의 차원과 같아야 한다. Titan Text Embeddings V2는 기본 1024차원(256, 512도 지원), Titan V1은 1536차원이다.
- Distance metric: 부동소수점 임베딩에는 Euclidean을 권장한다.
- 메타데이터 필드: 원문 청크를 담을
textField와 Bedrock이 관리하는metadataField를 함께 정의한다.
생성 후 Collection ARN, 벡터 인덱스 이름, 벡터 필드 이름, 텍스트 필드 이름, 메타데이터 필드 이름을 적어 둔다. Knowledge Base를 만들 때 그대로 입력한다.
주의할 점. Serverless 컬렉션을 만들면 암호화 정책, 네트워크 정책, 데이터 액세스 정책 세 가지가 붙는다. 이 중 데이터 액세스 정책에 Bedrock 서비스 역할을 넣지 않으면 동기화가 권한 오류로 실패한다. 이건 IAM 정책과 별개라서 자주 놓친다(아래 5절에서 다룬다).
5. IAM 권한 설정
Bedrock이 내 S3와 OpenSearch에 대신 접근하려면 서비스 역할이 필요하다. 신뢰 정책과 권한 정책, 그리고 OpenSearch 데이터 액세스 정책까지 세 갈래를 맞춰야 한다.
5.1 신뢰 정책
역할을 bedrock.amazonaws.com이 맡을 수 있게 한다.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "bedrock.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
5.2 권한 정책
S3 읽기, 임베딩 모델 호출, OpenSearch API 접근을 허용한다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3Read",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::sarc-kb-docs",
"arn:aws:s3:::sarc-kb-docs/*"
]
},
{
"Sid": "InvokeEmbeddingModel",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel"],
"Resource": "arn:aws:bedrock:us-west-2::foundation-model/amazon.titan-embed-text-v2:0"
},
{
"Sid": "OpenSearchServerless",
"Effect": "Allow",
"Action": ["aoss:APIAccessAll"],
"Resource": "arn:aws:aoss:us-west-2:123456789012:collection/abcd1234efgh"
}
]
}
5.3 OpenSearch 데이터 액세스 정책
IAM 권한이 있어도 OpenSearch Serverless 컬렉션 자체의 데이터 액세스 정책에 역할 ARN이 없으면 인덱스에 못 쓴다. 컬렉션의 데이터 액세스 정책에 서비스 역할을 principal로 추가한다.
[{
"Rules": [
{ "ResourceType": "index",
"Resource": ["index/sarc-kb-collection/*"],
"Permission": ["aoss:*"] },
{ "ResourceType": "collection",
"Resource": ["collection/sarc-kb-collection"],
"Permission": ["aoss:*"] }
],
"Principal": [
"arn:aws:iam::123456789012:role/AmazonBedrockExecutionRoleForKB"
]
}]
막상 돌려보면 여기서 자주 막힌다. 증상은 동기화 job이 failed로 끝나면서 접근 거부 메시지가 남는 것이다. IAM 정책만 보고 있으면 원인을 못 찾는다. 데이터 액세스 정책부터 확인한다.
6. Knowledge Base 생성과 청킹 전략
콘솔에서 만드는 게 가장 빠르지만, CLI 형태로 옵션을 보면 무엇을 정하는지 명확하다. Knowledge Base를 만들 때 벡터 스토어 정보를 연결한다.
$ aws bedrock-agent create-knowledge-base \
--name sarc-internal-docs \
--role-arn arn:aws:iam::123456789012:role/AmazonBedrockExecutionRoleForKB \
--knowledge-base-configuration '{
"type": "VECTOR",
"vectorKnowledgeBaseConfiguration": {
"embeddingModelArn": "arn:aws:bedrock:us-west-2::foundation-model/amazon.titan-embed-text-v2:0"
}
}' \
--storage-configuration '{
"type": "OPENSEARCH_SERVERLESS",
"opensearchServerlessConfiguration": {
"collectionArn": "arn:aws:aoss:us-west-2:123456789012:collection/abcd1234efgh",
"vectorIndexName": "sarc-kb-index",
"fieldMapping": {
"vectorField": "embeddings",
"textField": "AMAZON_BEDROCK_TEXT_CHUNK",
"metadataField": "AMAZON_BEDROCK_METADATA"
}
}
}'
다음으로 S3를 데이터 소스로 연결하면서 청킹 전략을 지정한다. 청킹은 chunkingConfiguration에 담는다. 선택지는 네 가지다.
FIXED_SIZE
정해진 토큰 수로 자른다. maxTokens와 overlapPercentage(조각 간 겹침 비율)를 준다. 문서 성격이 제각각일 때 빠르게 반복 실험하기 좋다.
HIERARCHICAL
큰 조각(부모)과 작은 조각(자식) 두 층으로 나눈다. 검색은 작은 조각으로 정밀하게 하고, 답변에는 부모의 넓은 문맥을 함께 준다. 운영 문서처럼 구조가 있는 텍스트에 잘 맞는다.
SEMANTIC
자연어 처리로 의미가 비슷한 문장을 묶어 자른다. 문단 경계가 뚜렷하지 않은 서술형 문서에 유리하다.
NONE
파일 하나를 통째로 한 조각으로 본다. 이미 조각 단위로 파일을 쪼개 둔 경우에 쓴다.
chunkingConfiguration을 아예 빼면 기본값이 적용된다. 문장 경계를 지키면서 약 300토큰 단위로 자르는 방식이다. 어디서 시작할지 모르겠으면 기본값으로 한번 돌려 검색 품질을 보고, 아쉬우면 HIERARCHICAL로 올리는 순서를 권한다. 다음은 고정 크기 청킹을 지정한 예다.
$ aws bedrock-agent create-data-source \
--knowledge-base-id KB12345678 \
--name s3-docs \
--data-source-configuration '{
"type": "S3",
"s3Configuration": { "bucketArn": "arn:aws:s3:::sarc-kb-docs" }
}' \
--vector-ingestion-configuration '{
"chunkingConfiguration": {
"chunkingStrategy": "FIXED_SIZE",
"fixedSizeChunkingConfiguration": {
"maxTokens": 300,
"overlapPercentage": 20
}
}
}'
주의: 청킹 전략은 데이터 소스 생성 시점에 정해지고, 바꾸면 다시 동기화해서 임베딩을 새로 만들어야 한다. 임베딩 재생성은 토큰 사용량만큼 비용이 든다.
7. 데이터 소스 동기화
여기서 실제 청킹, 임베딩, 인덱싱이 일어난다. 문서를 올리거나 청킹을 바꿀 때마다 다시 돌린다.
$ aws bedrock-agent start-ingestion-job \
--knowledge-base-id KB12345678 \
--data-source-id DS87654321
{
"ingestionJob": {
"ingestionJobId": "job-0a1b2c3d",
"status": "STARTING"
}
}
$ aws bedrock-agent get-ingestion-job \
--knowledge-base-id KB12345678 \
--data-source-id DS87654321 \
--ingestion-job-id job-0a1b2c3d \
--query 'ingestionJob.{status:status,stats:statistics}'
{
"status": "COMPLETE",
"stats": {
"numberOfDocumentsScanned": 42,
"numberOfNewDocumentsIndexed": 42,
"numberOfDocumentsFailed": 0
}
}
status가 FAILED면 대부분 권한이나 인덱스 필드 매핑 불일치다. failureReasons 필드를 먼저 본다. 벡터 필드 차원이 임베딩 모델 차원과 다르면 인덱싱 단계에서 걸린다.
8. retrieveAndGenerate 호출
검색과 답변 생성을 한 번에 처리하는 API다. 최소 입력은 질문 텍스트, Knowledge Base ID, 답변 생성용 모델 ARN 세 가지다. 답변 생성 모델은 임베딩 모델과 별개다. Claude 계열 등 텍스트 생성 모델을 쓰며, 리전 간 추론 프로파일 ARN을 넣는 편이 안정적이다.
$ aws bedrock-agent-runtime retrieve-and-generate \
--input '{"text": "배포 롤백 절차 알려줘"}' \
--retrieve-and-generate-configuration '{
"type": "KNOWLEDGE_BASE",
"knowledgeBaseConfiguration": {
"knowledgeBaseId": "KB12345678",
"modelArn": "arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
}
}'
응답에는 생성된 답변 output.text와 근거 출처 citations가 함께 온다. citations의 retrievedReferences[].location.s3Location.uri로 어느 문서에서 나온 답인지 추적할 수 있다. 사내 챗봇이라면 이 출처 링크를 답변 밑에 붙여 주는 게 신뢰도에 크게 도움 된다.
{
"output": { "text": "롤백은 이전 태스크 정의로 서비스를 업데이트한 뒤 ..." },
"citations": [
{
"generatedResponsePart": { "textResponsePart": { "text": "..." } },
"retrievedReferences": [
{
"content": { "text": "롤백 절차: 1) 배포 파이프라인 중단 ..." },
"location": { "type": "S3",
"s3Location": { "uri": "s3://sarc-kb-docs/docs/runbook-deploy.md" } }
}
]
}
],
"sessionId": "a1b2c3d4-..."
}
대화형으로 이어가려면 응답에 담긴 sessionId를 다음 요청에 그대로 넣는다. 이걸로 Bedrock이 이전 문맥을 유지한다. sessionId는 직접 만들지 못하고, 첫 호출 응답에서 받은 값을 재사용해야 한다.
Python(boto3)에서는 bedrock-agent-runtime 클라이언트의 retrieve_and_generate로 같은 구조를 호출한다.
import boto3
rt = boto3.client("bedrock-agent-runtime", region_name="us-west-2")
resp = rt.retrieve_and_generate(
input={"text": "배포 롤백 절차 알려줘"},
retrieveAndGenerateConfiguration={
"type": "KNOWLEDGE_BASE",
"knowledgeBaseConfiguration": {
"knowledgeBaseId": "KB12345678",
"modelArn": "arn:aws:bedrock:us-west-2:123456789012:"
"inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0",
},
},
)
print(resp["output"]["text"])
for c in resp["citations"]:
for r in c["retrievedReferences"]:
print(" 출처:", r["location"]["s3Location"]["uri"])
9. 주의사항
- 모델 액세스: 임베딩, 생성 모델 모두 Bedrock 콘솔의 Model access에서 사전 승인을 받아야 한다. 미승인 상태로 호출하면 AccessDenied가 난다.
- 권한은 두 겹: IAM 권한 정책과 OpenSearch 데이터 액세스 정책은 별개다. 둘 다 서비스 역할을 허용해야 한다.
- 차원 일치: 벡터 인덱스 차원과 임베딩 모델 차원이 어긋나면 동기화가 실패한다. Titan V2는 1024가 기본이다.
- 비용: OpenSearch Serverless는 최소 용량 단위(OCU) 과금이 상시 발생한다. 소규모 문서에 상시 비용이 부담되면 S3 Vectors 같은 저비용 벡터 스토어를 검토한다.
- 동기화 재실행: 문서 추가, 삭제, 청킹 변경 후에는 동기화를 다시 돌려야 인덱스에 반영된다.
정리하면, 코드보다 IAM 두 겹과 벡터 인덱스 설정을 먼저 맞추는 게 이 작업의 8할이다. 그 두 가지를 통과하면 동기화 한 번, 호출 한 번으로 사내 문서 챗봇이 돈다.