Amazon Bedrock을 이용한 Text API 구축하기

1. 개요

Amazon Bedrock을 활용하여 AI 모델을 호출하는 API를 구축하고, 이를 API Gateway를 통해 외부에서 접근할 수 있도록 구성하는 과정을 소개합니다. 이번 포스팅에서는 Visual Studio Code를 활용하여 API 요청을 보내고, 응답을 확인하는 과정까지 다룹니다

2. Amazon Bedrock 소개

Amazon Bedrock은 OpenAI의 GPT, Anthropic의 Claude, Stability AI의 Stable Diffusion 등 다양한 AI 모델을 제공하여 텍스트 생성, 이미지 생성, 번역, 요약 등의 작업을 쉽게 수행할 수 있도록 합니다.

2.1 주요 기능

  • 다양한 AI 모델 제공 (Anthropic Claude, Stability AI, Amazon Titan 등)
  • 서버리스 환경에서 AI 서비스 실행 가능
  • API 기반으로 쉽게 통합 가능
  • 보안 및 개인정보 보호 지원

3. Bedrock Summary API Architecture

3.1 실습 전 아키텍처 개요 설명

이 아키텍처는 Amazon Bedrock을 활용하여 긴 텍스트를 요약하는 API를 구축하는 방법을 보여줍니다. 사용자는 API Gateway를 통해 텍스트를 입력하면, AWS Lambda가 이를 받아 Amazon Bedrock의 모델을 호출하여 요약된 결과를 반환하는 구조입니다.

3.1.1 사용자 입력

  • 사용자는 긴 텍스트를 API에 POST 요청으로 보냅니다.

3.1.2 Amazon API Gateway

  • API Gateway는 사용자 요청을 AWS Lambda로 전달하는 역할을 합니다.
  • API Gateway는 인증, 로깅, 트래픽 관리 등의 기능도 수행할 수 있습니다.

3.1.3 AWS Lambda

  • Lambda 함수는 API Gateway에서 받은 텍스트를 Amazon Bedrock으로 전달합니다.
  • Bedrock에서 반환된 요약된 텍스트를 다시 API Gateway로 전달합니다.

3.1.4 Amazon Bedrock

  • Amazon Bedrock은 텍스트 요약을 수행하는 AI 모델을 제공하는 AWS 서비스입니다.
  • Lambda 함수는 Amazon Bedrock의 Text-to-Text 모델을 호출하여 긴 텍스트를 요약합니다.

3.1.5 IAM Role

  • Amazon Bedrock과 AWS Lambda 간의 원활한 통신을 위해 IAM Role이 필요합니다.
  • Lambda 함수가 Bedrock을 호출할 수 있도록 적절한 권한을 부여합니다.

3.1.6 응답 반환

  • Lambda 함수는 Bedrock에서 생성한 요약된 텍스트를 API Gateway를 통해 사용자에게 반환합니다.

Hands-on

아래 Python 코드는 Amazon Bedrock의 Claude 3.5 Sonnet 모델을 이용해 긴 텍스트를 요약하는 Lambda 함수를 정의하고, 실제로 테스트하는 스크립트를 포함하고 있습니다.

# summary.py 파일

import boto3
import json

AWS_REGION_BEDROCK = "ap-northeast-2"
client = boto3.client(service_name="bedrock-runtime", region_name=AWS_REGION_BEDROCK)

def lambda_handler(event, context):
    try:
        # Request Body Parsing
        body = json.loads(event.get("body", "{}"))  # body가 None일 경우 대비
        text = body.get("text", "")

        # Query String Parsing (None 방지)
        query_params = event.get("queryStringParameters") or {}  # None 방지
        points = query_params.get("points", "3")  # 기본값 "3"

        if not text:
            return {
                "statusCode": 400,
                "body": json.dumps({"error": "Text is required!"})
            }

        # Claude API 요청 생성
        claude_request = get_claude_request(text, points)

        response = client.invoke_model(
            modelId="anthropic.claude-3-5-sonnet-20240620-v1:0",
            accept="application/json",
            contentType="application/json",
            body=json.dumps(claude_request)
        )

        response_body = json.loads(response["body"].read())

        # API 응답 처리 수정
        if isinstance(response_body, dict) and "content" in response_body:
            content = response_body["content"]
            if isinstance(content, list) and len(content) > 0:
                summary_text = content[0].get("text", "No summary generated")
            else:
                summary_text = "No valid summary generated"
        else:
            summary_text = "Invalid API response"

        return {
            "statusCode": 200,
            "body": json.dumps({"summary": summary_text})
        }

    except Exception as e:
        return {
            "statusCode": 500,
            "body": json.dumps({"error": str(e)})
        }

def get_claude_request(text: str, points: str):
    prompt = f"""Text: {text} \n
        From the text above, summarize the story in {points} points.\n    
    """
    return {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 1000,
        "messages": [
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": prompt}
                ]
            }
        ]
    }

# 로컬 실행용 테스트
if __name__ == "__main__":
    test_event = {
        "body": json.dumps({"text": "This is a sample text that needs to be summarized."}),
        "queryStringParameters": {"points": "3"}
    }
    response = lambda_handler(test_event, None)
    print(response)
# Summary.test.py 파일

import json
from summary import handler

event = {
    "body": json.dumps(
        {
            "text": """
        Vincent was a jazzman, a saxophonist with a smooth, soulful sound that could melt the coldest of hearts. He had been playing the clubs and 
bars of the city for years, honing his craft and building a reputation as one of the best musicians in town.
Mia was an actress, a rising star with a talent for drama and a fierce determination to succeed. She had been working non-stop for the past few years, taking on any role she could get, no matter how small, in order to make a name for herself in the competitive world of Hollywood.
One night, Vincent and Mia crossed paths at a jazz club in the city. Vincent was playing a set with his band, and Mia was in the audience, 
sipping on a martini and enjoying the music. As soon as their eyes met, Vincent knew he was in trouble. Mia was the most beautiful woman he had ever seen, with piercing green eyes and long, curly hair that cascaded down her back like a golden waterfall.
Despite the fact that Mia was a celebrity and Vincent was just a struggling musician, they quickly hit it off. They talked for hours after 
the show, exchanging stories and laughter, and before long, they were inseparable.
As the weeks went by, Vincent and Mia found themselves spending more and more time together. They would go on long walks through the city, 
holding hands and talking about their dreams and aspirations. They would sit in Vincent's small apartment, listening to jazz records and drinking coffee until the early hours of the morning.
Despite their differences - Vincent was a struggling artist, while Mia was a successful celebrity - they found a deep connection in their shared passion for their craft. They inspired each other to work harder and push themselves to be the best they could be.
As the months passed, Vincent and Mia's relationship grew stronger. They began to talk about their future together, about the possibility of starting a family and building a life filled with love, laughter, and music.
But as their love for each other grew, so did the challenges they faced. Mia's career was taking off, and she found herself constantly on the road, filming in different locations and attending red carpet events.
    """
        }
    ),
    "queryStringParameters": {
        "points": "3"
    }
}

response = handler(event, {})

print(response)
summary.py에서 handler 함수를 불러와 실행한 후, 긴 텍스트를 전달하여 요약을 요청합니다. 이때 "points": "3"을 설정하면 Claude 모델이 3가지 요약 포인트를 생성하도록 요청하게 됩니다.

코드 실행 흐름은 다음과 같습니다. 먼저, 사용자가 event 객체에 긴 텍스트를 입력하면 handler() 함수가 호출되어 해당 텍스트가 Claude 모델로 전달됩니다. 이후, Claude 모델이 입력된 텍스트를 분석하여 요약을 생성하고, 결과를 JSON 형태로 반환하게 됩니다.


이제 AWS Lambda와 API Gateway에 배포하여 Serverless Summary API 로 활용 해보겠습니다.

4. AWS Lambda 배포 (Hands-on)

py-text-funtion 이라는 lambda funtion을 생성하고 Runtime은 Python 최신 버전인 3.13 버전을 선택하고 Lambda Funtion을 생성합니다.
Summary.py 파일의 코드를 복사하고 생성된 Lambda 함수에 붙여넣고 배포합니다.
배포된 Lambda Funtion의 실행시간은 3초가 걸렸는데 현재 Hands-on 실습에서 요청이 들어오면 최소 4~5초는 걸리기 때문에 General configuration 을 편집해야 합니다.
따라서 Timeout을 “30초”로 설정하고 Save 합니다.
현재 생성된 Lambda Funtion IAM Role에는 CloudWatch의 새로운 Logs Group 및 Log Stream 생성 및 Lambda 함수가 실행될 때 로그를 기록 가능한 상태이며 이 Lambda Funtion IAM Role에 Amazon Bedrock InvokeModel 를 허용하는 IAM Role을 추가해야 합니다.
Amazon Bedrock 모델 사용: 모든 Bedrock 모델에 대한 호출(InvokeModel) 허용 특정 모델이 아닌 모든 모델을 호출할 수 있는 정책을 허용해줍니다.
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": "logs:CreateLogGroup",
			"Resource": "arn:aws:logs:ap-northeast-2:[Account Number]:*"
		},
		{
			"Effect": "Allow",
			"Action": [
				"logs:CreateLogStream",
				"logs:PutLogEvents"
			],
			"Resource": [
				"arn:aws:logs:ap-northeast-2:[Account Number]:log-group:/aws/lambda/py-text-funtuin:*"
			]
		},
		{
			"Effect": "Allow",
			"Action": "bedrock:InvokeModel",
			"Resource": "*"
		}
	]
}
IAM Policy를 추가하고 Save 합니다.
IAM Policy를 편집하고 저장한 후, 생성된 Lambda Funtion의 Role을 보면 Amazon Bedrock:InvokeModel 권한이 생긴것을 확인할 수 있습니다.
이제 테스트를 위해 “text”라는 test event를 생성하고 Event JSON 값에 아래 코드를 복사해서 붙여 넣습니다.
{
    "body": "{\"text\":\"Vincent was a jazzman, a saxophonist with a smooth, soulful sound that could melt the coldest of hearts. He had been playing the clubs and bars of the city for years, honing his craft and building a reputation as one of the best musicians in town. Mia was an actress, a rising star with a talent for drama and a fierce determination to succeed. She had been working non-stop for the past few years, taking on any role she could get, no matter how small, in order to make a name for herself in the competitive world of Hollywood. One night, Vincent and Mia crossed paths at a jazz club in the city. Vincent was playing a set with his band, and Mia was in the audience, sipping on a martini and enjoying the music. As soon as their eyes met, Vincent knew he was in trouble. Mia was the most beautiful woman he had ever seen, with piercing green eyes and long, curly hair that cascaded down her back like a golden waterfall. Despite the fact that Mia was a celebrity and Vincent was just a struggling musician, they quickly hit it off. They talked for hours after the show, exchanging stories and laughter, and before long, they were inseparable. As the weeks went by, Vincent and Mia found themselves spending more and more time together. They would go on long walks through the city, holding hands and talking about their dreams and aspirations. They would sit in Vincent's small apartment, listening to jazz records and drinking coffee until the early hours of the morning. Despite their differences - Vincent was a struggling artist, while Mia was a successful celebrity - they found a deep connection in their shared passion for their craft. They inspired each other to work harder and push themselves to be the best they could be. As the months passed, Vincent and Mia's relationship grew stronger. They began to talk about their future together, about the possibility of starting a family and building a life filled with love, laughter, and music. But as their love for each other grew, so did the challenges they faced. Mia's career was taking off, and she found herself constantly on the road, filming in different locations and attending red carpet events.\"}",
    "queryStringParameters": {
        "points": "3"
    }
   }

body : 요약할 원본 텍스트를 포함합니다.
JSON 형식이지만 문자열("body": "{\"text\":\"...\"}")로 인코딩되어 있어, Lambda 내부에서 json.loads(event["body"])를 통해 디코딩해야 합니다.
queryStringParameterspoints : 값으로 요약의 주요 포인트 개수를 지정합니다. 현재 "points": "3"으로 설정되어 있어, 요약 시 3가지 핵심 내용을 추출하도록 요청됩니다. 이렇게 작성하고 TEST 버튼을 실행해보겠습니다.

테스트 결과 텍스트의 세 가지 요약본을 호출하는데 성공했고 지금까지 구축했던 아키텍처가 잘 작동하는 것을 확인할 수 있습니다 🙂

5. API Gateway and Lambda Funtion 통합 (Hands-on)

5.1 API Gateway와 Lambda의 연결 흐름

AWS Lambda를 API Gateway와 통합하면 REST API 또는 HTTP API를 생성하여 클라이언트(웹, 모바일 앱 등)가 Lambda 함수를 호출할 수 있도록 합니다. 이 구조는 Serverless Architecture 에서 매우 유용하며, API를 통해 Lambda 함수를 쉽게 호출하고 응답을 받을 수 있습니다.

  1. 사용자 요청

클라이언트(브라우저, 앱, Postman 등)가 API Gateway의 엔드포인트로 HTTP 요청을 보냄 (POST /summary).

  1. API Gateway가 요청을 Lambda로 전달

API Gateway는 요청을 Lambda로 포워딩하고, 이벤트(JSON)를 Lambda에 전달.

  1. Lambda가 요청을 처리

Lambda 함수가 요청을 받아 Amazon Bedrock을 호출하고, 텍스트 요약을 생성.
결과(JSON)를 API Gateway로 반환.

  1. API Gateway가 클라이언트에 응답 반환

Lambda의 응답을 받아 HTTP Response(200 OK, JSON 데이터 포함)로 Client에 반환.

5.2 API Gateway 생성

“SummaryApi” 라는 API를 생성합니다.
API Gateway를 생성했으면 지금은 아무런 리소스 가 없기 때문에 리소스를 생성해야 합니다. “Create resource”를 클릭합니다.
“text” 라는 리소스를 생성합니다.

1. Method Type 선택

-> POST Method → 클라이언트가 데이터를 전달할 때 사용됨.

2. Integration Type 선택 (Lambda Function)

-> Lambda Function을 선택하면 API Gateway가 요청을 AWS Lambda로 전달하여 실행.
-> Lambda 함수는 Serverless 방식으로 요청을 처리하고 응답을 반환.

3. Lambda Proxy Integration 활성화

-> Lambda Proxy Integration을 사용하면, API Gateway가 요청을 구조화된 이벤트(JSON 형식)로 Lambda에 전달.
-> 이점: API Gateway가 요청 본문(Body), HTTP 헤더, 경로 변수, 쿼리 매개변수 등을 Lambda에 그대로 전달. Lambda가 API Gateway에서 오는 원본 요청을 직접 처리 가능하고 응답도 API Gateway를 거치지 않고 Lambda에서 직접 HTTP 응답(JSON)을 구성.

4. Lambda Function 연결

-> 위에서 생성한 Lambda 함수 (py-text-funtuin) 선택됨. API Gateway가 Lambda를 호출할 수 있도록 권한(IAM Role) 자동 부여.

5. Integration Timeout 설정

-> 기본값 29,000ms (29초) → 요청이 너무 오래 걸리면 API Gateway에서 자동으로 중단.

위와 같이 선택하고 API Gateway를 생성합니다.
POST Method를 생성한 후에, API Gateway를 배포 합니다.
Stage는 “New stage”를 선택하고 stage name은 “prod”로 설정 후 배포 합니다.
API gateway가 배포되면 Invoke URL이 생성 됩니다. API Gateway의 Invoke URL배포된 API Endpoint로, 클라이언트(브라우저, 모바일, 다른 서버)가 HTTP 요청을 보낼 때 사용하는 URL입니다 이제 이 API Endpoint로 Request를 날려 보겠습니다.
API Gateway의 Invoke URL을 사용하여 요청을 보내야 합니다. 아래는 request.http 파일을 생성하여 API를 호출하는 예제입니다.
POST https://0f75fti1oe.execute-api.ap-northeast-2.amazonaws.com/prod/text
Content-Type: application/json

{
    "text": "Vincent was a jazzman, a saxophonist with a smooth, soulful sound that could melt the coldest of hearts..."
}
요청을 보내면 API Gateway에서 화면에 표시된 응답을 받을 수 있습니다.
필드설명
HTTP/1.1 200 OK요청이 정상적으로 처리되었음을 의미
summary요청한 텍스트의 요약 결과
x-amz-apigw-idAPI Gateway가 요청을 추적하는 고유 ID
X-Amzn-Trace-IdAWS X-Ray에서 트레이싱을 위한 ID
x-amzn-RequestIdAmazon API Gateway에서 할당한 요청 ID

6. 결론

이 포스팅에서는 Lambda + API Gateway를 활용하여 Amazon Bedrock과 통신하는 API를 호출하는 과정을 살펴보았습니다.
Visual Studio Code에서 request.http 파일을 사용하여 쉽게 API 요청을 보낼 수 있으며, 응답을 통해 AI 모델이 생성한 요약 결과를 확인할 수 있었습니다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다