데이터 인제스션 (Data Ingestion): RSS 피드, 지식 기반 (Knowledge Base), S3 벡터 및 메타데이터 필터링
요약
AWS Briefing Agent 구축 과정 중 데이터 인제스션 아키텍처를 최적화하는 방법을 다룹니다. 기존 RSS 피드 방식의 비용 및 지연 시간 문제를 해결하기 위해 Amazon Bedrock Knowledge Base를 도입하여 RAG(검색 증강 생성) 기반의 효율적인 데이터 검색 구조를 구현합니다.
핵심 포인트
- RSS 피드 직접 조회 방식의 비용 효율성 및 지연 시간 문제 식별
- Amazon Bedrock Knowledge Base를 활용한 RAG(Retrieval-Augmented Generation) 구현
- S3 벡터 및 메타데이터 필터링을 통한 의미론적 검색 최적화
- Python CDK를 이용한 지식 기반(Knowledge Base) 인프라 자동화
이 글은 Amazon Bedrock AgentCore Runtime에 배포된 개인화된 AWS 어시스턴트인 AWS Briefing Agent를 구축하며 얻은 아키텍처, 구현 및 교훈을 기록하는 시리즈 포스트 중 두 번째 글입니다.
Part 1: Bedrock AgentCore에서 풀스택 AI 에이전트 구축
Part 2: 데이터 인제스션 (Data Ingestion): RSS 피드, 지식 기반 (Knowledge Base), S3 벡터 및 메타데이터 필터링
Part 3: Strands Agents + AgentCore Runtime - 완벽한 조합
Part 4: 에이전트에 메모리 추가하기
Part 5: API Gateway 실험하기
Part 6: 관측성 (Observability) 및 평가 (Evaluations)
Part 7: 제3자 통합 - ID, 게이트웨이 및 Slack 알림
AWS Briefing Agent 구축을 시작했을 때, 첫 번째 버전은 호출될 때마다 AWS What's New RSS 피드를 조회했습니다. 이는 에이전트가 클라이언트에게 맞춤형 정보를 반환할 수 있음을 보여주는 측면에서는 작동했습니다. 하지만 동일한 데이터를 반복적으로 가져오기 때문에 비용이 많이 들고 낭비적이었으며, 모든 호출에 지연 시간 (Latency)을 추가했습니다. 또한 RSS 피드는 최근 정보만 다루기 때문에, 지난 6개월 또는 그 이상 전에 출시된 릴리스를 검색하기 시작해야 할 가능성도 높았습니다. 따라서 다음 단계는 에이전트에 의한 검색 (Retrieval)과 인제스션 (Ingestion)을 분리하는 것이었습니다.
Amazon Bedrock 지식 기반 (Knowledge Base)
주요 설계 목표 중 하나는 에이전트가 "이번 주 Bedrock의 새로운 소식은 무엇인가요?"와 같은 자연어 질의 (Natural Language Query)를 대규모 문서 코퍼스 (Corpus)와 대조하여 의미론적으로 가장 유사한 결과를 반환할 수 있도록 하는 것이었습니다. 여기서 Amazon Bedrock 지식 기반 (Knowledge Base)의 진가가 발휘됩니다. 이를 통해 에이전트는 RAG (Retrieval-Augmented Generation, 검색 증강 생성)를 사용할 수 있습니다. 지식 기반을 쿼리함으로써 질의 시점에 관련 문서를 검색할 수 있고, 이를 컨텍스트 (Context)로서 프롬프트 (Prompt)에 주입할 수 있습니다. 그러면 LLM (Large Language Model)은 검색된 사실로 확인된 정보를 바탕으로 응답을 생성합니다.
지식 기반을 생성하는 Python CDK 코드는 아래와 같습니다:
knowledge_base = bedrock .
지식 기반을 생성하는 Python CDK 코드는 아래와 같습니다:
knowledge_base = bedrock.
CfnKnowledgeBase(self, "AnnouncementKnowledgeBase", name="aws-briefing-agent-announcements", ..., knowledge_base_configuration=bedrock.CfnKnowledgeBase.KnowledgeBaseConfigurationProperty(type="VECTOR", vector_knowledge_base_configuration=bedrock.CfnKnowledgeBase.VectorKnowledgeBaseConfigurationProperty(embedding_model_arn=f"arn:aws:bedrock: {self.region} ::foundation-model/amazon.titan-embed-text-v2:0")), storage_configuration=bedrock.CfnKnowledgeBase.StorageConfigurationProperty(type="S3_VECTORS", s3_vectors_configuration=bedrock.CfnKnowledgeBase.S3VectorsConfigurationProperty(index_name="announcements", vector_bucket_arn=f"arn:aws:s3vectors: {self.region} : {self.account} :bucket/briefing-agent-vectors")), ))
이 코드는 임베딩 모델로 amazon.titan-embed-text-v2:0을, 벡터 저장소 유형으로 S3_VECTORS를 사용하도록 선언합니다. 임베딩과 같은 측면을 처리하는 데 필요한 코드는 없습니다. 대신 Bedrock이 이 모든 것을 관리해 줍니다.
Amazon S3 Vectors
Amazon Bedrock Knowledge Bases는 여러 벡터 저장소를 지원합니다. 벡터 저장소란 RAG(Retrieval-Augmented Generation)가 작동하도록 만드는 검색 엔진입니다. 이는 임베딩 모델에 의해 생성된 수치적 임베딩(벡터) 형태로 문서를 저장합니다. 쿼리 시점에 사용자의 질문이 임베딩되고, 벡터 저장소는 의미적으로 가장 가까운 문서의 임베딩을 찾아냅니다. 이 프로토타입은 Amazon S3 Vectors를 기본 벡터 저장소로 사용합니다. S3 Vectors는 OpenSearch Serverless와 같은 대안보다 업로드, 저장 및 쿼리 시 비용이 최대 90%까지 저렴한, 비용 효율적이고 탄력적이며 내구성 있는 벡터 스토리지를 제공합니다. 관리할 인프라가 없으며, 이 사용 사례에 적합한 서브초(sub-second) 쿼리 지연 시간도 제공합니다.
스케줄링하는 Ingestion
Ingestion 파이프라인은 Amazon EventBridge Scheduler를 사용하여 6시간마다 실행됩니다.
이 서비스는 내장된 재시도 정책 (retry policies), 시간대 (time zone) 지원, 데드 레터 큐 (dead-letter queues)와 같은 기능을 제공합니다. 스케줄은 필요한 처리를 수행하는 AWS Lambda 함수를 트리거합니다. 여기에는 다음 사항이 포함됩니다:
- S3에 존재하는 기존 문서 해시 (hashes) 목록화
- AWS What’s New RSS 피드 가져오기 (~100개의 공지사항)
- 13개의 AWS 블로그 RSS 피드 가져오기 (aws, machine-learning, compute, security, database, containers, devops, networking, storage, infrastructure-and-automation, developer, big-data, iot)
- AWS Security Bulletins RSS 피드 가져오기
- 각 새로운 블로그 게시물에 대해, 정규 URL (canonical URL)을 가져오고 표준 라이브러리 (stdlib) HTML 파서를 사용하여 전체 기사 본문을 추출
- 발행 날짜를 YYYYMMDD 정수로 파싱
- 새로운 항목당 .txt 및 .metadata.json 파일을 S3에 작성
- Bedrock KB 인제스션 (ingestion) 작업 트리거
중복 제거 및 증분 쓰기 (Deduplication and Incremental Writes)
인제스션 파이프라인이 실행될 때, 다양한 RSS 피드의 콘텐츠 대부분은 새로운 것이 아닙니다. 6시간마다 수백 개의 공지사항을 다시 가져오고 다시 쓰는 것을 방지할 방법을 찾는 것이 중요했습니다. 이를 지원하기 위해, 우리는 블로그 게시물 URL의 MD5 해시를 생성하고 이를 12자리의 16진수 (hex characters)로 절삭했습니다. 이 해시는 S3 파일 이름으로 사용됩니다. 샘플 코드 스니펫은 아래와 같습니다:
def write_to_s3 ( items , existing_keys = None ):
existing = existing_keys or set ()
for item in items :
url_hash = hashlib . md5 ( item [ " link " ]. encode ( )). hexdigest ( )[: 12 ]
if url_hash in existing :
continue # 이미 S3에 있음, 건너뜀
# ... 문서 + 메타데이터 파일 작성
시작 시, get_existing_keys()는 S3의 모든 .txt 파일을 나열하고 각 파일 이름에서 해시를 추출하여 세트 (set)에 담습니다. 블로그 게시물을 처리할 때, Lambda 함수는 URL 해시를 계산하고 그것이 이미 세트에 있는지 확인합니다. 만약 이미 존재한다면, 이는 이전 실행에서 인제스션되었음을 의미하며 페이지를 다시 가져올 필요가 없습니다. 해시가 존재하지 않으면, 함수는 페이지를 가져오고, 콘텐츠를 추출하여 S3에 작성합니다. 이 해시는 URL로부터 유도된 안정적이고 결정론적인 (deterministic) 파일 이름을 제공합니다.
동일한 URL은 항상 동일한 해시를 생성합니다.
청킹 전략 (Chunking Strategy)
청킹 전략은 아래에 표시된 것처럼 CDK 스택의 Data Source 리소스에 설정됩니다:
data_source = bedrock . CfnDataSource ( self , " AnnouncementDataSource " , name = " aws-announcements-s3 " , knowledge_base_id = knowledge_base . attr_knowledge_base_id , data_source_configuration = bedrock . CfnDataSource . DataSourceConfigurationProperty ( type = " S3 " , s3_configuration = bedrock . CfnDataSource . S3DataSourceConfigurationProperty ( bucket_arn = data_bucket . bucket_arn , ), ), vector_ingestion_configuration = bedrock . CfnDataSource . VectorIngestionConfigurationProperty ( chunking_configuration = bedrock . CfnDataSource . ChunkingConfigurationProperty ( chunking_strategy = " SEMANTIC " , semantic_chunking_configuration = bedrock . CfnDataSource . SemanticChunkingConfigurationProperty ( breakpoint_percentile_threshold = 92 , buffer_size = 1 , max_tokens = 600 , ), ), ), )
우리는 SEMANTIC (의미론적) 청킹 전략을 사용합니다. 이는 임베딩 모델 (embedding model) 자체를 사용하여 분할 지점을 결정합니다. 다음 세 가지 파라미터가 이 동작을 제어합니다:
- breakpoint_percentile_threshold=92 - 분할을 유발하는 백분위수 임계값 (percentile threshold)을 제어합니다. 임계값이 높을수록 문서를 서로 다른 청크로 나누기 위해 문장 간의 구분이 더 명확해야 합니다.
- max_tokens=600 - 문장 경계를 준수하면서 단일 청크에 포함되어야 하는 최대 토큰 (token) 수입니다.
- buffer_size=1 - 특정 문장에 대해, 버퍼 크기는 임베딩 생성을 위해 추가될 주변 문장의 수를 정의합니다. 버퍼 크기가 크면 더 많은 문맥 (context)을 포착할 수 있지만 노이즈가 발생할 수 있으며, 버퍼 크기가 작으면 중요한 문맥을 놓칠 수 있지만 더 정밀한 청킹을 보장합니다.
날짜별 필터링 (Filtering by Date)
에이전트 (agent)를 작성할 때 목표 중 하나는 사용자가 정보의 최신성을 기준으로 범위를 제한할 수 있도록 하는 것이었습니다. 예를 들어, "지난 7일 동안 무엇이 새로 나왔나요?"와 같은 질문이 가능해야 합니다.
이를 달성하기 위해, 각 문서의 인제스션 (Ingestion) 시점에 문서에 구조화되고 필터링 가능한 속성을 부착하는 연관된 metadata.json 사이드카 (sidecar) 파일을 생성합니다. 이를 통해 에이전트 (Agent)가 의미론적 유사성 (Semantic Similarity)에만 의존하지 않고 검색 결과의 범위를 좁힐 수 있습니다. 예시 파일은 다음과 같습니다:
{ "metadataAttributes" : { "published_date" : 20260415 , "service" : "amazon-bedrock" , "category" : "artificial-intelligence" , "source_type" : "announcement" } }
지식 기반 (Knowledge Base) 동기화 과정에서, Bedrock은 이 사이드카 파일을 읽고 해당 문서로부터 생성된 모든 벡터 청크 (Vector Chunk)에 이러한 속성들을 부착합니다. 쿼리 (Query) 시점에 에이전트는 의미론적 검색과 메타데이터 필터를 결합할 수 있습니다:
- "이번 주 Bedrock의 새로운 소식은 무엇인가요?" → "Bedrock"에 대한 벡터 유사성 +
published_date에 대한greaterThanOrEquals필터 - "보안 게시물을 보여주세요" → 벡터 유사성 +
source_type에 대한equals필터: "security-bulletin" - "지난 한 달간의 Lambda 공지사항" → 벡터 유사성 +
service및published_date모두에 대한 필터
메타데이터 파일이 없다면, 에이전트는 날짜나 서비스와 관계없이 의미론적으로 가장 유사한 결과만을 가져오게 됩니다. 따라서 "이번 주"에 대한 질문을 하더라도 텍스트 내용이 우연히 유사한 3개월 전의 공지사항이 반환될 수 있습니다. 메타데이터 필터를 사용하면 에이전트가 관련성 순위(Ranking)를 매기기 전에 결과를 정확한 시간 범위나 서비스로 제한할 수 있습니다.
명명 규칙(.metadata.json)은 Bedrock KB의 규칙입니다. 이는 인제스션 중에 사이드카를 부모 문서와 자동으로 연관시킵니다. 별도의 코드로 이들을 연결할 필요는 없으며, 파일 이름 패턴만으로 충분합니다. Bedrock Knowledge Base 메타데이터는 STRING, NUMBER, BOOLEAN, STRING_LIST의 네 가지 타입을 지원합니다. 고유한 데이터 타입(Native Data Type)은 존재하지 않습니다. 비교 연산자(greaterThan, greaterThanOrEquals, lessThan, lessThanOrEquals)는 NUMBER 타입에서만 작동합니다. 저희의 초기 구현에서는 published_date를 문자열("2026-05-14")로 저장했습니다.
에이전트가 필터링을 시도했을 때, 다음과 같은 예외(Exception)가 발생했습니다: ValidationException: The filter value type provided isn 't supported for the given operation: GREATER_THAN_OR_EQUALS. 해결 방법은 날짜를 YYYYMMDD 형태의 숫자(즉, "2026-05-14" 대신 "20260514" 사용)로 저장하는 것이었습니다. 또한, LLM(Large Language Model)이 상대적인 날짜를 쉽게 계산할 수 있도록 런타임(Runtime) 시 시스템 프롬프트(System Prompt)에 오늘 날짜를 주입합니다. Amazon S3 Vectors는 벡터당 필터링 가능한 메타데이터(Metadata)에 대해 2 KB라는 엄격한 제한이 있다는 점에 유의하십시오. 저희는 Bedrock Knowledge Base의 내부 메타데이터 키(AMAZON_BEDROCK_TEXT 및 AMAZON_BEDROCK_METADATA)가 기본적으로 필터링 가능하도록 설정되어 있어, 빈번한 ValidationException 오류가 발생하는 것을 발견했습니다. 해결 방법은 벡터 인덱스(Vector Index)를 생성할 때 이 두 키를 필터링 불가능(Non-filterable)하도록 표시하는 것이었습니다:
vector_index = s3vectors.CfnIndex(
self,
"AnnouncementVectorIndex",
index_name = "announcements",
vector_bucket_name = vector_bucket.vector_bucket_name,
dimension = 1024, # Titan Embed Text v2
distance_metric = "cosine",
data_type = "float32",
metadata_configuration = s3vectors.CfnIndex.MetadataConfigurationProperty(
non_filterable_metadata_keys = [
"AMAZON_BEDROCK_TEXT",
"AMAZON_BEDROCK_METADATA",
],
),
)
이는 필터링 가능한 유일한 메타데이터가 .metadata.json 필드에만 포함됨을 의미하며, 저희는 바로 이 필드들만을 대상으로 필터링을 수행합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기