Source code for operators.update_knowledge_base
"""Operator that syncs a data source into a Bedrock Knowledge Base or a Kendra index."""
from airflow.models import BaseOperator
from airflow.utils.decorators import apply_defaults
from airflow.exceptions import AirflowSkipException
import logging
from typing import Optional, Dict
[docs]
class UpdateKnowledgeBaseOperator(BaseOperator):
"""
Updates either a Bedrock Knowledge Base or Kendra Index with data from a specified source.
This operator handles:
- Kendra index synchronization with status monitoring
- Bedrock knowledge base data ingestion
- Concurrent update protection
- Automatic retry logic for Kendra sync (up to 10 attempts)
Args:
data_source_id: ID of the data source to sync/ingest
knowledge_base_id: Optional Bedrock knowledge base ID
kendra_index_id: Optional Kendra index ID
"""
ui_color = "#fcba03"
logger = logging.getLogger(__name__)
MAX_SYNC_ATTEMPTS = 10
SYNC_POLL_INTERVAL = 60 # seconds
@apply_defaults
def __init__(
self,
data_source_id: str,
knowledge_base_id: Optional[str] = None,
kendra_index_id: Optional[str] = None,
*args,
**kwargs,
):
super(UpdateKnowledgeBaseOperator, self).__init__(*args, **kwargs)
self.data_source_id = data_source_id
self.knowledge_base_id = knowledge_base_id
self.kendra_index_id = kendra_index_id
[docs]
def update_kendra_index(self) -> Dict:
"""
Syncs Kendra index with specified data source and monitors until completion.
Retries up to 10 times with 60-second intervals between checks.
"""
import boto3
import time
client = boto3.client("kendra")
self.logger.info(
f"Starting Kendra sync - Index: {self.kendra_index_id}, Source: {self.data_source_id}"
)
try:
response = client.start_data_source_sync_job(
Id=self.data_source_id,
IndexId=self.kendra_index_id,
)
execution_id = response["ExecutionId"]
self.logger.info(f"Started sync job: {execution_id}")
for attempt in range(1, self.MAX_SYNC_ATTEMPTS + 1):
self.logger.info(
f"Checking sync status - Attempt {attempt}/{self.MAX_SYNC_ATTEMPTS}"
)
jobs = client.list_data_source_sync_jobs(
Id=self.data_source_id, IndexId=self.kendra_index_id, MaxResults=1
).get("History", [])
if jobs and jobs[0]["ExecutionId"] == execution_id:
status = jobs[0]["Status"]
self.logger.info(f"Current status: {status}")
if status == "FAILED":
error_msg = jobs[0].get("ErrorCode", "Unknown error")
self.logger.error(f"Sync failed: {error_msg}")
raise Exception(f"Sync job failed: {error_msg}")
if status in ["SUCCEEDED", "ABORTED"]:
break
if attempt < self.MAX_SYNC_ATTEMPTS:
self.logger.info(
f"Waiting {self.SYNC_POLL_INTERVAL}s before next check"
)
time.sleep(self.SYNC_POLL_INTERVAL)
self.logger.info(f"Kendra sync completed - Status: {status}")
return response
except client.exceptions.ConflictException:
raise AirflowSkipException("Kendra sync already in progress - skipping")
except Exception as e:
self.logger.error(f"Kendra sync error: {str(e)}")
raise
[docs]
def update_knowledge_base(self, description: str) -> Dict:
"""Updates Bedrock knowledge base with specified data source."""
import boto3
client = boto3.client("bedrock-agent")
self.logger.info(f"Starting Bedrock ingestion - KB: {self.knowledge_base_id}")
try:
response = client.start_ingestion_job(
dataSourceId=self.data_source_id,
description=description,
knowledgeBaseId=self.knowledge_base_id,
)
self.logger.info("Successfully started knowledge base ingestion")
return response
except client.exceptions.ConflictException:
raise AirflowSkipException("Knowledge base ingestion already in progress")
except Exception as e:
self.logger.error(f"Knowledge base update error: {str(e)}")
raise
[docs]
def execute(self, context: Dict) -> Dict:
"""Executes the appropriate update operation based on configuration."""
self.logger.info(
f"Starting execution - Source: {self.data_source_id}, "
f"KB: {self.knowledge_base_id}, Kendra: {self.kendra_index_id}"
)
if not self.data_source_id:
raise AirflowSkipException(
"data_source_id and (knowledge_base_id or kendra_index_id) required"
)
if self.kendra_index_id:
return self.update_kendra_index()
if self.knowledge_base_id:
return self.update_knowledge_base(
description=f"Ingestion from DAG {context['dag'].dag_id} at {context['execution_date']}"
)