AWS Bedrock ve CDK ile RAG Agent Kurmak
AWS Bedrock + Knowledge Bases + OpenSearch Serverless üstüne CDK ile TypeScript kullanarak RAG agent kurmak: mimari, IAM bağlantısı, otomatik ingestion ve chat UI.
AWS, Bedrock Agent ile Knowledge Bases ve OpenSearch Serverless üçlüsünü yönetilen RAG yolu olarak konumlandırıyor: retrieval kodunu kendin yazmadan, belgeye dayalı sohbet uygulaması çıkarıyorsun. DigitalOcean rag-assistant şablonunun şeklini AWS-native servislere TypeScript ile CDK üzerinde taşıdım, uçtan uca dağıttım ve ilk koşunun düştüğünü gördüm. AWS Bedrock yönetilen RAG yığını CDK ile çalışıyor, ancak belge boşluğundan doğan iki tuzak ilk dağıtımını düşürecek: bölgeler arası inference profile için IAM açığı ve dağıtım anında otomatik ingestion job’un olmaması.
Mimari eşleme
Şekil, DO şablonu ile bire bir aynı. DO tarafında tek bir GenAI Agent yönetilen inference kümesi üzerinde, KBaaS de yönetilen bir vektör deposu ile birlikte geliyor. AWS tarafında aynı parçalar isimli servislere bölünmüş ve aralarını IaC ile sen kuruyorsun. Yedi servis, tek CDK yığını, 51 kaynak.
| DigitalOcean Şablonu | AWS Karşılığı (bu repo) |
|---|---|
| GenAI Agent (Nemotron) | Bedrock Agent + Claude Sonnet 4.5 |
| Knowledge Base (Qwen3 embed) | Bedrock KB + Titan Embed v2 |
| KBaaS yönetilen vektör depo | OpenSearch Serverless VECTORSEARCH |
| Guardrails (jailbreak/içerik/PII) | Bedrock Guardrails (içerik + PII) |
| App Platform FastAPI sohbet UI | CloudFront + S3 SPA, Lambda Function URL |
Yalnızca tor1 bölgesi | Herhangi bir Bedrock bölgesi (us-east-1 kullandım) |
| Terraform | AWS CDK (TypeScript) |
Tüm yığın us-east-1’de temiz şekilde yaklaşık 757 saniyede dağıtıldı. Aşağıdaki iki tuzak ilk koşuda sırasıyla yüzeye çıktı.
Yığın yapısı
CDK kodu üç küçük özel construct’tan oluşuyor (VectorStore, KnowledgeBase, Agent); her biri aws-cdk-lib/aws-bedrock L1 primitive’leri (CfnKnowledgeBase, CfnDataSource, CfnAgent, CfnGuardrail) ile standart S3 ve IAM üzerine kurulu. Üçüncü taraf bir CDK kütüphanesi yok. Tuzaklara geçmeden önce üç somut şekil görmeye değer.
S3 bucket + seed corpus. Belge bucket’i standart bir s3.Bucket; stack destroy edilince auto-delete oluyor. BucketDeployment yerel docs-sample/ dizinindeki seed corpus’u synth anında bucket’a yüklüyor, böylece ilk dağıtım elle yükleme adımı olmadan uçtan uca çalışıyor.
const documents = new s3.Bucket(this, 'Documents', {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
new s3deploy.BucketDeployment(this, 'SeedDocs', {
sources: [s3deploy.Source.asset(path.join(__dirname, '..', '..', 'docs-sample'))],
destinationBucket: documents,
});
Knowledge Base + S3 data source. CfnKnowledgeBase, OpenSearch Serverless collection’a işaret ediyor ve Bedrock’un beklediği field mapping’i veriyor. CfnDataSource S3 bucket’ı bağlıyor ve sabit boyutta chunking stratejisi kuruyor: %20 örtüşmeli, 512 token’lık parçalar yoğun referans materyali için uygun.
const kb = new bedrock.CfnKnowledgeBase(this, 'Kb', {
name: `${basename}-kb`,
roleArn: kbRole.roleArn,
knowledgeBaseConfiguration: {
type: 'VECTOR',
vectorKnowledgeBaseConfiguration: {
embeddingModelArn: `arn:aws:bedrock:${region}::foundation-model/amazon.titan-embed-text-v2:0`,
},
},
storageConfiguration: {
type: 'OPENSEARCH_SERVERLESS',
opensearchServerlessConfiguration: {
collectionArn: vectorStore.collectionArn,
vectorIndexName: vectorStore.indexName,
fieldMapping: {
vectorField: 'bedrock-knowledge-base-default-vector',
textField: 'AMAZON_BEDROCK_TEXT_CHUNK',
metadataField: 'AMAZON_BEDROCK_METADATA',
},
},
},
});
new bedrock.CfnDataSource(this, 'S3DataSource', {
knowledgeBaseId: kb.attrKnowledgeBaseId,
name: `${basename}-s3-source`,
dataSourceConfiguration: {
type: 'S3',
s3Configuration: { bucketArn: documents.bucketArn },
},
vectorIngestionConfiguration: {
chunkingConfiguration: {
chunkingStrategy: 'FIXED_SIZE',
fixedSizeChunkingConfiguration: { maxTokens: 512, overlapPercentage: 20 },
},
},
});
Guardrail. CfnGuardrail bir içerik politikası (altı filtre tipi, input/output strength) ve hassas-bilgi politikası (PII varlıkları için anonymise-veya-block aksiyonları) alıyor. Agent’a bağlamak CfnAgent üzerinde bu kaynağın attrGuardrailId’sini referans alan tek bir guardrailConfiguration bloğu.
new bedrock.CfnGuardrail(this, 'Guardrail', {
name: `${basename}-guardrail`,
blockedInputMessaging: 'I cannot process that request.',
blockedOutputsMessaging: 'I cannot share that response.',
contentPolicyConfig: {
filtersConfig: [
{ type: 'SEXUAL', inputStrength: 'HIGH', outputStrength: 'HIGH' },
{ type: 'VIOLENCE', inputStrength: 'HIGH', outputStrength: 'HIGH' },
{ type: 'HATE', inputStrength: 'HIGH', outputStrength: 'HIGH' },
{ type: 'INSULTS', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'MISCONDUCT', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'PROMPT_ATTACK', inputStrength: 'HIGH', outputStrength: 'NONE' },
],
},
sensitiveInformationPolicyConfig: {
piiEntitiesConfig: [
{ type: 'EMAIL', action: 'ANONYMIZE' },
{ type: 'PHONE', action: 'ANONYMIZE' },
{ type: 'CREDIT_DEBIT_CARD_NUMBER', action: 'BLOCK' },
{ type: 'US_SOCIAL_SECURITY_NUMBER', action: 'BLOCK' },
],
},
});
Her construct’ın tam kaynağı repo’nun lib/constructs/ dizininde. İki tuzak da bu şeklin içinde: biri agent’ın IAM politikasında, diğeri veri kaynağının yaşam döngüsünde.
Bölgeler arası inference profile için IAM açığı
Yola çıktığım örnek snippet’lar bedrock:InvokeModel iznini arn:aws:bedrock:${region}::foundation-model/* üzerine veriyor. Bu ARN, yığının dağıtıldığı bölgeye sabitlenmiş durumda. Agent’a verdiğim Claude Sonnet 4.5 model kimliği ise bölgeler arası inference profile formunda: us.anthropic.claude-sonnet-4-5-20250929-v1:0. us.* ailesindeki profile’lar, altta yatan çağrıyı kapasitesi olan bölgeye yönlendiriyor. Havuz, us-east-1’den çağrıldığında us-east-1, us-east-2 ve us-west-2’den oluşuyor. Yönlendirilen hedef küme başka kaynak bölgelerde farklı olabilir; kendi senaryon için Bedrock’un inference-profile belgelerine bak. IAM kontrolünü ise çağıranın bölgesi değil, yönlendirilen bölge belirliyor.
Sonuçta dağıtım başarılı oluyor, Agent Prepare başarılı oluyor, ingestion da geçiyor; Titan Embed v2 bölgesel olduğu için hatayı maskeliyor. Sonra SPA’dan ilk InvokeAgent çağrısı şu yanıtla dönüyor:
"Access denied when calling Bedrock. Check your request permissions and retry the request."
Servis izi yok, hata mesajında foundation-model ARN’i yok, bölge sabitlemesine işaret eden hiçbir şey yok. AgentRole üzerinde aws iam get-role-policy çalıştırdım, foundation-model satırını taradım ve ${region} değişkeninin tek bir değere pişmiş olduğunu gördüm. Çözüm: foundation model için bölge joker karakteri ve profile’ın kendisi için açık inference-profile/* kaynağı.
agentRole.addToPolicy(
new iam.PolicyStatement({
actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
resources: [
`arn:aws:bedrock:*::foundation-model/*`,
`arn:aws:bedrock:*:${account}:inference-profile/*`,
],
}),
);
Foundation-model ARN’leri tasarım gereği hesap segmenti taşımıyor; bölge yuvasındaki *, politikayı us.* profile’ın yönlendirebileceği tüm bölgelere genişletiyor. Profile ARN’inin kendisi ise hesap segmenti içeriyor.
Bir tur fazladan kaybettiren küçük bir not: cdk deploy --hotswap yalnızca Lambda ve Step Functions kodu için iş gördüğünden, IAM-only diff’lerde “no changes” raporluyor. Politikayı gerçekten itmek için sade cdk deploy gerekiyor.
Eksik ingestion tetikleyicisi
CfnKnowledgeBase ile CfnDataSource deklaratif kaynaklar. KB’yi kuruyor, S3 veri kaynağını bağlıyor. Ingestion job başlatmıyor. S3’e seed edilen korpus, biri bedrock-agent start-ingestion-job çağrısı yapana kadar OpenSearch’te sıfır vektör olarak duruyor.
Bu tuzak IAM açığından daha sinsi, çünkü hata bir retrieval kalitesi sorunu gibi görünüyor. Sohbet her soruya genel bir “bilgim yok” yanıtı veriyor. İlk refleks chunking’i, embedding’i ya da korpusu suçlamak oluyor. Asıl neden yığında belgeleri embed eden hiçbir şeyin tetiklenmemiş olması.
Üç tetikleyiciyi tarttım:
- Dağıtım sonrası
aws bedrock-agent start-ingestion-jobkoşan bir kabuk adımı. Çalışıyor, ama IaC dışında kalıyor ve sonraki dağıtımda unutuluyor. - EventBridge kuralı ve küçük bir Lambda. Her dağıtımda tam olarak bir kez çalışması gereken bir davranış için iki ekstra kaynak ve bir izin sıçraması demek.
- Aynı API’yi doğrudan CloudFormation’dan çağıran bir
AwsCustomResource.
AwsCustomResource’u seçtim. CloudFormation içinde tetikleniyor, izlenecek ayrı bir runtime’ı yok ve PhysicalResourceId’yi her synth’te değiştirince her dağıtımda yeniden tetikleniyor. Politika tek aksiyon ve tek ARN ile dar tutuluyor.
const ingestParams = {
knowledgeBaseId: kb.attrKnowledgeBaseId,
dataSourceId: dataSource.attrDataSourceId,
};
const physicalIdBase = `${cdk.Stack.of(this).stackName}-ingest`;
const ingestionTrigger = new cr.AwsCustomResource(this, 'IngestionTrigger', {
onCreate: {
service: 'BedrockAgent',
action: 'StartIngestionJob',
parameters: ingestParams,
physicalResourceId: cr.PhysicalResourceId.of(`${physicalIdBase}-create`),
},
onUpdate: {
service: 'BedrockAgent',
action: 'StartIngestionJob',
parameters: ingestParams,
physicalResourceId: cr.PhysicalResourceId.of(`${physicalIdBase}-${Date.now()}`),
},
policy: cr.AwsCustomResourcePolicy.fromStatements([
new iam.PolicyStatement({
actions: ['bedrock:StartIngestionJob'],
resources: [kb.attrKnowledgeBaseArn],
}),
]),
installLatestAwsSdk: false,
});
ingestionTrigger.node.addDependency(seedDeployment);
ingestionTrigger.node.addDependency(dataSource);
İki ayrıntı önemli. Update tarafındaki PhysicalResourceId içindeki Date.now(), her synth’i CloudFormation’a gerçek bir değişiklik gibi gösterip her dağıtımda yeniden ingestion’u zorluyor. Sonraki koşular incremental: Bedrock yalnızca yeni veya değişen S3 nesnelerini yeniden işliyor. Custom resource ayrıca korpusu seed eden BucketDeployment’a bağımlı, böylece job hiçbir zaman boş bir bucket üzerinde sıraya girmiyor.
Çağrı asenkron. CDK ingestion’un bitmesini beklemiyor; custom resource job sıraya girer girmez dönüyor. Bu testte üç küçük markdown belgesi, cdk deploy döndükten sonra yaklaşık 40 saniyede indekslendi. Bu davranış genellenebilir değil: ingestion süresi nesne sayısına, chunk boyutuna ve Bedrock’un yoğunluğuna bağlı. 40 saniyeyi kendi koşunda kabaca bir kontrol noktası olarak kullan.
Maliyet (ve ucuz tutmanın yolu)
Faturayı OpenSearch Serverless domine ediyor. Collection 2 OCU minimumla çalışıyor ve indeksleme ya da sorgu olmasa bile saatlik ücret işliyor; us-east-1’de bırakılırsa ayda yaklaşık 350 USD’ye geliyor (2 OCU × 0,24 USD/OCU-saat × 730 saat). Bedrock çağrıları, Titan embedding’leri, S3, Lambda ve CloudWatch istek başına ücretlendirildiği için demo ölçekte ihmal edilebilir. Öğrenme projeleri için anlamlı tek akış: dağıt, test et, cdk destroy çalıştır, hepsi aynı saat içinde.
Bu yığın ne zaman işe yarar
Bedrock yönetilen RAG, retrieval’i sıfırdan kurmaya kıyasla gerçek bir kısayol. Korpus küçükse, retrieval ihtiyacı sıradansa ve embedding hattını sahiplenmektense IAM’i bir kez döşemeyi tercih ediyorsan bu yol tutar. Collection’ın sürekli ayakta kalması gerektiği anda ucuz seçenek olmaktan çıkıyor; faturayı Bedrock çağrılarından çok önce OCU tabanı belirliyor. Üretim ölçeğinde OCU boyutlandırma, çok kiracılı erişim desenleri ve rerank modeli entegrasyonu ayrı birer deneme istiyor.
Başlamak için repoyu klonla ve README’yi izle.
Kaynaklar
- rag-agent-cdk reposu - Denemeye konu CDK yığını: construct’lar, SPA ve
mainüzerindeki iki düzeltme. - DigitalOcean rag-assistant şablonu - AWS portunun şekil olarak yansıttığı şablon.
- AWS Bedrock Agents kullanıcı kılavuzu - Agent yapılandırması ve
InvokeAgentsemantikleri için birincil kaynak. - AWS Bedrock Knowledge Bases kullanıcı kılavuzu - KB, veri kaynağı ve ingestion job semantikleri için birincil kaynak.
- Bedrock cross-region inference profiles - IAM açığının arkasındaki
us.*havuz-bölge yönlendirme modelini anlatır. - Anthropic Claude on Bedrock model kimlikleri -
anthropic.claude-sonnet-4-5-20250929-v1:0veus.*cross-region profile ailesini doğrular. - AWS CDK aws_bedrock modülü -
CfnKnowledgeBase,CfnDataSource,CfnAgentreferansı. - AWS CDK custom_resources modülü - Ingestion job’u tetikleyen construct olan
AwsCustomResource. - CloudFormation
AWS::Bedrock::DataSource- Kaynağın ingestion job lifecycle hook’u olmadığını doğrular. - Amazon OpenSearch Serverless fiyatlandırma - 2 OCU taban rakamının kaynağı.
- Vercel AI Elements - SPA’da kullanılan
Conversation,Message,PromptInputveSourcesbileşenleri. - Streamdown - AI Elements
Message’in sardığı markdown render edici; AI Elements geçişinde v2 plugin API’si belirleyici. - shadcn/ui CLI - AI Elements kurulumunun kullandığı
initbayrakları.
İlgili yazılar
Bedrock Knowledge Base aslında nedir, hangi veri kaynakları ve vektör depoları birinci sınıf desteklenir ve konsol varsayılanı küçük bir korpusa neden nadiren uyar.
AgentCore Runtime üzerinde minimal bir Strands agent'ı CDK ile deploy etme rehberi: parametrize stack, arm64 build, deploy ve invoke, IAM ve Marketplace ön koşulları.
Hybrid search, reranking, GraphRAG ve self-corrective pattern'ler dahil gelişmiş RAG tekniklerine dair kapsamlı rehber. Production AWS implementasyonu örnekleriyle.
Bir kodlama ajanının evi olarak devcontainer, Codespaces ve AWS Lambda MicroVM: her basamak ne katıyor ve ajanı laptoptan çıkarmak ne zaman kazandırıyor.
CDK TypeScript Lambda için 9 bundler ve 3 cdk synth runner'ının ölçümlü karşılaştırması; her katman için varsayılan ve onu seçtiren kural.