Serverless AI Agent Vol 2: Amazon Bedrock AgentCore ve Strands

5 minute read

Published:


Serinin ilk yazısında, Amazon Bedrock, Lambda ve DynamoDB kullanarak tamamen serverless bir E-Ticaret Destek Asistanı oluşturmuştuk. Kullanıcının siparişini bulup, iade kurallarını Lambda üzerinde işleterek çalışan bir sistem tasarlamıştık. Bu mimarinin dezavantajı LLM’e yeni bir yetenek eklemek istediğimizde önce uzun uzun JSON formatında bir OpenAPI (Swagger) şeması yazıp bunu S3’e yükledikten sonra Lambda kodunu güncellemek ve en son da Bedrock konsoluna gidip ajanı Prepare ile ajanı tekrar oluşturmak gerekiyordu. Bunun önüne geçmek için ise Bedrock’ın yeni AgentCore ve Strands ekosistemini kullanarak tamamen kod tabanlı bir yaklaşım geliştirdik. Bu sayede artık ajanımızın yeteneklerini doğrudan Python fonksiyonları olarak tanımlayabiliyoruz ve OpenAPI şemalarıyla uğraşmak zorunda kalmıyoruz.

Bu yazıda V1 olarak geliştirdiğimiz ilk yaklaşımı AgentCore ve Strands ile V2’ye yükselterek, kod tabanlı bir yapıya geçişi ve canlıya almayı anlatacağız.

Neden V2?

V1 (Bedrock Agents) orkestrasyonu tamamen AWS’in yönettiği, hızlı prototipleme için güzel bir yapıydı. Ancak projeyi gerçek yazılım geliştirme döngüsüne entegre etmek istediğimizde şu ihtiyaçlar doğuyordu:

  1. OpenAPI Şeması: AgentCore’da JSON şemaya ihtiyaç yok. Sadece Python fonksiyonunuzun üzerine @tool dekoratörünü ekliyorsunuz.
  2. Lokal Debugging: V1’de bir veritabanı veya yetki hatası aldığınızda CloudWatch log’larına bakmanız gerekiyordu (veya Streamlit arayüzü/cli’ı). V2’de ajan doğrudan kendi terminalinizde çalıştığı için hataları anında görüyorsunuz.
  3. CI/CD: V1 konsoldan tıklanarak yapılandırıldığı için versiyonlamak zordu. V2’de her şey kod dosyasında olduğu için repo’nuza git push yapmanız yeterli.

Proje İskeleti ve Mimari

İlk olarak AWS’in resmi AgentCore CLI aracını kurarak ve proje klasör yapısını oluşturarak başlıyoruz. Ben yine yetkilendirme vs. ile uğraşmamak için Cloud9 üzerinde bir Ubuntu terminali açtım.

npm install -g @aws/agentcore
pip install bedrock-agentcore strands-agents boto3 uvicorn

Terminalde agentcore create komutunu çalıştırdığımızda bize framework (Strands) ve model tercihimizi sorar. Komut tamamlandığında elimizde şu temiz klasör yapısı oluşur:

ecomAgents/
├── agentcore/
│   ├── agentcore.json      
│   └── aws-targets.json    
└── app/
    └── EcomHelper/
        ├── main.py         
        └── pyproject.toml  

Kod Katmanı: Strands ve AgentCore

Gerekli paketleri tanımlayarak başlayalım. AgentCore CLI projeyi paketleyip taşırken Docker imajının içine hangi kütüphaneleri kuracağını pyproject.toml dosyasından okuyor.

# app/EcomHelper/pyproject.toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "ecommerce-support-agent"
version = "1.0.0"
description = "E-commerce support agent using Amazon Bedrock AgentCore and Strands"
requires-python = ">=3.10"

dependencies = [
    "bedrock-agentcore",
    "strands-agents",
    "boto3",
    "uvicorn"
]

Şimdi esas işin olduğu main.py koduna bakalım. Burada ne ayrı bir Lambda fonksiyonuna ne de S3’e yüklenen şemalara ihtiyacımız var. Veri tabanı sorguları da, iş kuralları da doğrudan Python fonksiyonları olarak ajanın içinde yer alıyor. Tek farklılık @tool dekoratörü ile fonksiyonları ajana tanımlamamız. Bir de öncekinden farklı olarak result.message döndürülüyor. Bu sayede ajan arka planda hangi araçları çağırdıysa, hangi yanıtları aldıysa hepsini tek bir mesaj olarak alabiliyoruz.

# app/EcomHelper/main.py
import os
import boto3
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from strands import Agent, tool
from strands.models import BedrockModel

app = BedrockAgentCoreApp()

dynamodb = boto3.resource('dynamodb', region_name='eu-central-1')
orders_table = dynamodb.Table('EcommerceOrders')
products_table = dynamodb.Table('EcommerceProducts')

@tool
def get_order_details(order_id: str) -> str:
    # önceki kod ile aynı, sipariş detaylarını DynamoDB'den alır ve döndürür
    ...

@tool
def process_return(order_id: str) -> str:
    # önceki kod ile aynı, iade işlemini DynamoDB'de günceller ve döndürür
    ...

model = BedrockModel(model_id="eu.amazon.nova-pro-v1:0")

system_prompt = (
    # önceki kod ile aynı
    ...)

agent = Agent(
    model=model,
    tools=[get_order_details, process_return],
    system_prompt=system_prompt)

@app.entrypoint
def invoke(payload):
    user_message = payload.get("prompt", "Hello")
    result = agent(user_message)
    return {"result": result.message} # öncekinden farklı olarak response.message['content][0]['text'] yerine direkt result.message döndürülüyor

if __name__ == "__main__":
    app.run()

Canlıya Alım

Lokalde python main.py diyerek ajanı test ettikten sonra sıra bunu AWS canlı ortama taşımaya geliyor. Ancak canlıya alırken karşılaşabileceğiniz bir durum var. AgentCore CLI projenizi deploy ederken arka planda AWS CDK (Cloud Development Kit) kullanır. Eğer AWS hesabınızda CDK daha önce yapılandırılmadıysa (veya kullanıcının yetkisi yoksa) CDK bootstrap failed hatası alırsınız.

Bunu aşmak için deploy öncesi hesabınızı bir kez bootstrap etmeniz gerekir:

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
npx cdk bootstrap aws://$ACCOUNT_ID/eu-central-1

Bootstrap tamamlandıktan sonra tek yapmamız gereken komutu çalıştırmak:

agentcore deploy

Bu komut arka planda kodumuzu paketler, Docker imajını ECR’a atar ve AgentCore Runtime üzerinde ayağa kaldırır. İşlem bittiğinde elimize bir agentRuntimeArn verir.

Burada kodu deploy ederken CloudFormation Stack’i oluşturur ve AgentCore Runtime’ı ayağa kaldırır. Hata almanız durumunda CloudFormation konsoluna gidip logları inceleyebilirsiniz.

Bir diğer karşılaşacağınız sorun ise geliştirme yaparken (ör: lokalde) sizin kendi AWS IAM kullanıcınızın yetkileri geçerlidir. Ancak agentcore deploy yaptığınızda ajan artık AWS üzerinde bağımsız bir ortamda çalışır.

Ajanının canlıda DynamoDB’ye erişmeye çalıştığında permission hatası almaması için Konsolundan Roles (IAM) sayfasına gidip AgentCore tarafından otomatik oluşturulan role (AgentCore-ecomAgents-...) şu inline policy eklenmelidir:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:UpdateItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan"
            ],
            "Resource": [
                "arn:aws:dynamodb:eu-central-1:*:table/EcommerceOrders",
                "arn:aws:dynamodb:eu-central-1:*:table/EcommerceProducts"
            ]
        }
    ]
}

Ajanı Çağırma (Invocation)

Ajanımız artık canlı ortamda izinleri tanımlanmış bir şekilde hazır. Artık herhangi bir Python backend uygulamasından, mobil uygulamadan veya mikroservisten boto3 ile ajanımızı çağırabiliriz. Bu örnek için Streamlit kullanmadım ama aşağıdaki invoke_agent_runtime metodu kullanılarak arayüze eklemek mümkün.

import boto3
import json

client = boto3.client('bedrock-agentcore', region_name='eu-central-1')

payload = json.dumps({"prompt": "I received my order ORD-1004 a while ago, but I want to return it now. Can you process a return for me?"}).encode()

response = client.invoke_agent_runtime(
    agentRuntimeArn='arn:aws:bedrock-agentcore:eu-central-1:123456789012:runtime/EcomHelper-xyz',
    runtimeSessionId='unique-user-session-12345678901234567890', # 33+ karakterli session ID gerekiyor
    payload=payload
)

result = json.loads(response['response'].read())
print(result)

Çıktı tam olarak istediğimiz gibi: Ajan arka planda get_order_details aracını çağıracak, ORD-1004 için iade uygunluğunu (Returnable: False) kontrol edecek ve müşteriye yanıt verecek 👇🏻



Dilerseniz arayüzden de test edebilirsiniz.


Sonuç

Serverless AI Agent serimizin bu ikinci bölümünde konsoldan ajan yapmaktan (V1) kod yazarak bir ajanı canlıya almaya (V2) geçiş yaptık. AgentCore ve Strands ikilisi OpenAPI şema ihtiyacı olmadan tüm mantığı tek bir Python dosyasında toplamamızı, lokal ortamda rahatça test yapabilmemizi ve agentcore deploy ile kısa sürede canlı ortama çıkmamızı sağladı. Bu yapı gerçek dünyada CI/CD süreçlerine dahil edilmiş, izlenebilir ve ölçeklenebilir ajan mimarisi için oldukça uygun.


Kaynaklar