AWS 기술 블로그

생성형 AI로 만드는 나만의 이력서: 웅진 IT의 Amazon Bedrock과 SageMaker 활용 사례

웅진은 디지털 신기술을 활용하여 기업 고객의 디지털 전환을 선도하는 IT기업으로, SAP ERP, 렌탈 솔루션, 모빌리티 솔루션 및 클라우드 서비스를 포함하여 지난 20년 동안 대외 사업을 진행하고 있습니다. 현재 전체 매출의 85%를 대외 사업에서 창출하며, 1,300여개 고객사와 협력하고 있습니다. 고객사의 산업별, 규모별 특성을 고려하여 최적의 고객 맞춤형 IT 솔루션을 제공하고 있습니다.

‘AI 이력서’ 서비스는 웅진이 개발한 애플리케이션이며, 24년 World IT Show 이벤트에 전시되었습니다. ‘AI 이력서’는 크게 사진 생성 부분과 이력서 작성 부분으로 구성되어 있습니다. 사용자가 업로드한 사진을 분석하고 적합한 정장 이미지로 변경하여 스튜디오 촬영 수준의 사진을 생성합니다. 또한, MBTI 정보와 주요 기업의 인재상 데이터를 바탕으로 사용자가 원하는 기업에 맞춰 자기소개서를 생성합니다. 이 게시글을 통해 생성형 AI를 활용한 고급 이미지/텍스트 생성 기법에 대해 배울 수 있습니다.

솔루션 및 구현 내용

AI 이력서는 생성형 AI에서 자주 발생하는 환각 (hallucination) 없이 빠르게, 사례 기반의 구체적인 자기소개서를 생성하여 사용자가 즐겁게 생성형 AI를 체험하게 하는 것이 목표입니다.

사용자 경험을 위해 생성형 AI가 사진을 생성하는 과정에서 업로드한 이미지로부터 나이를 예측하여 로딩 화면에 표시합니다. 그동안 내부적으로는 사진에서 얼굴 영역의 마스킹 정보를 얻고, 인물의 바깥 영역과 의상 영역을 프롬프트에 맞게 이미지를 생성하게 됩니다.

이력서의 자기 소개서 텍스트의 생성은 기업들의 인재상을 벡터스토어에 저장하고, 사용자의 MBTI 정보에 맞는 인재상을 검색하여, 가장 적당한 3개의 회사를 선택하게 됩니다. 이후에 사용자의 나이, 성별, MBTI에 맞게 이력서의 자기 소개서를 생성 합니다.

이러한 ‘AI 이력서’ 서비스는 Amazon Bedrock, Amazon SageMaker, Amazon Rekoginition 등의 서비스를 활용하여 개발했고, 본 게시글에서 구현 과정을 설명하고자 합니다.

AI 이력서 서비스의 아키텍처

Figure 1. 이력서 서비스 전체 아키텍처

사용자 입력 흐름

‘AI 이력서’를 이용하는 사용자는 모바일 웹을 통해서 접속한 후 아래 절차에 따라 이력서 생성 요청을 수행합니다.

  1. 이름, 휴대폰 번호, MBTI 정보와 사용자가 정면으로 찍은 인물 사진을 업로드 합니다. 사용자의 요청은 백엔드 서버에서 수신 받아 사진 파일은 S3에 저장하고, 사용자의 프로필 정보는 Aurora MySQL 데이터베이스에 저장합니다.
  2. MBTI에 따라 백엔드 서버로부터 추천된 회사들 중 하나를 선택하게 되면 백엔드 서버에서는 생성형 AI를 활용한 이력서 생성을 시작합니다. 이 때, 생성형 AI를 활용하는 부분은 서비스를 분리하여 AWS Lambda를 활용한 서버리스로 구현했습니다.

사진 생성 흐름

사진 생성 부분은 아래 절차와 같이 AWS Lambda 호출을 통해 Amazon Rekognition, Amazon SageMaker, Amazon Bedrock 서비스를 순차적으로 호출합니다.

  1. 사용자가 정면으로 찍은 인물 사진이 S3에 업로드되면 Amazon EventBridge를 통해 AWS Lambda를 호출합니다.
  2. AWS Lambda에서는 Amazon Rekognition을 통해 사진의 얼굴 부분을 감지(Detect)하고, 얼굴을 분석하여 기분, 나이 등을 예측한 값을 반환합니다.
  3. 다음으로 AWS Lambda는 얼굴 영역의 bounding box 좌표를 Amazon SageMaker에 올라간 Meta AI의 SAM (Segment Anything Model) 모델에 전달하여 얼굴 윤곽 부분과 그 외 다른 부분을 대비한 mask image를 반환합니다.
  4. 마지막으로 AWS Lambda에서 원본 이미지와 masking 이미지를 Amazon Bedrock Titan Image Generator G1 모델을 통해 outpainting 작업을 수행합니다. Outpainting은 mask image로 지정한 물체는 그대로 두고 나머지 배경을 입력한 prompt에 맞게 변경하는 작업입니다. 해당 과정을 통해 최종 이력서 사진이 생성되어 S3 Bucket에 저장됩니다.

이력서 생성 흐름

사례 기반의 상세한 자기 소개서를 생성하기 위해서는 AWS Lambda 호출을 통해 Amazon Bedrock과 Amazon OpenSearch Service를 이용한 RAG (Retrieval-Augmented Generation) 구성을 활용하고 있습니다. 아래 절차에 따라 이력서 내 자기소개서 생성이 진행됩니다.

  1. 백엔드 서버에서 Aurora MySQL로 사용자 프로필 저장이 완료되면 추천할 회사 선정을 위해 AWS Lambda를 실행합니다.
  2. AWS Lambda는 사용자 MBTI의 성격과 회사의 인재상을 Amazon OpenSearch Service에서 keyword와 매칭하여 MBTI 성격과 인재상이 가장 잘 맞는 세 개의 회사를 선정합니다
  3. 세 개 회사 중 하나를 사용자가 선택하면 생성형 AI로 이력서를 작성하기 위해 AWS Lambda를 실행합니다.
  4. AWS Lambda는 Bedrock Claude 3 Sonnet을 통해 이력서를 작성하게 됩니다. 이 때, 상세하게 설계된 프롬프트를 통해 선택한 회사의 인재상에 적합한 이력서를 만들어줍니다.

위 과정을 통해 완성된 이력서 사진과 이력서 내용을 합쳐 실시간으로 결과값을 보여줍니다.

구현 상세 내용

AI 이력서의 정장 사진 생성하기

사진 생성은 Amazon Bedrock에서 제공하는 Amazon Titan Image Generator G1 모델을 사용하여 두단계로 생성합니다. 첫번째는 Inpainting 방식으로 사진 내 인물의 옷 영역을 정장으로 변경하고, 두번째는 Outpainting 방식으로 사진 내 인물 밖의 배경을 바꾸어서 증명 사진 느낌이 나도록 생성했습니다. 하지만, 이 방식에는 두 가지 문제가 있었는데 하나는 Bedrock을 두 번 호출하여 결과를 받기까지 지연이 발생한다는 것이었고, 다른 하나는 정장을 입은 인물에 깨끗한 하늘색 배경을 기대했으나, 실제 결과는 화려한 정장이나 일반적인 옷을 입은 사진이 나오기도 했으며, 배경 또한 화려하게 패턴이 들어가 있는 경우가 있었습니다. 우리는 이러한 문제점을 보완하기 위해 다른 접근 방법을 찾아야 했습니다.

Figure 2. Outpainting 생성 시 참조 아키텍처 (Napkin-DL/image-segmentation-with-bedrock-rekognition)

새로운 접근 방법에서 활용한 방식은 위 Github 코드를 참고하였습니다. 먼저 AWS 내에서 고급 컴퓨터 비전 기능을 API 방식을 통해 쉽게 추가할 수 있는 클라우드 기반 이미지 및 비디오 분석 서비스인 Amazon Rekognition을 사용하였습니다. Amazon Rekognition의 DetectFaces API를 이용하면 사용자의 사진에서 얼굴 위치의 bounding box 외에도 Confidence, Facial landmarks, Facial attributes 등 다양한 정보를 얻을 수 있습니다.

def get_rekognition_response(image, filename, session):
    # Rekognition 클라이언트 생성
    client = session.client('rekognition')
    
    # Rekognition에서 얼굴 감지 요청
    response = client.detect_faces(
        Image={
            'S3Object': {
                'Bucket': 'my-bucket-name',  # 이미지가 저장된 S3 버킷 이름
                'Name': f'profile/{filename}',  # S3 버킷 내 이미지 경로
            }
        },
        Attributes=['ALL']  # 모든 얼굴 속성 반환
    )
    
    return response  # 응답 반환

# 응답 예시:
# "BoundingBox": {
#     "Width": 0.7919622659683228,  # 감지된 얼굴의 너비 비율
#     "Height": 0.7510867118835449,  # 감지된 얼굴의 높이 비율
#     "Left": 0.08881539851427078,  # 감지된 얼굴의 왼쪽 경계 비율
#     "Top": 0.151064932346344  # 감지된 얼굴의 상단 경계 비율
# },

Figure 3. Amazon Rekgnition의 detect_faces 호출

‘AI 이력서’ 서비스는 얼굴 위치의 bounding box 와 함께 응답 값 중 AgeRange, Emotions 등을 활용하여 아래와 같이 사용자의 재미를 더했습니다. 또한, 사용자가 얼굴을 인식할 수 없는 사진(반려동물 사진 등)을 업로드한 경우에도 Amazon Rekognition로 체크하여 필터링하였습니다.

Figure 4. Amazon Rekgnition을 이용한 얼굴 감정(emotion) 활용 사례

이렇게 Amazon Rekognition에서 추출한 bounding box는 mask image를 추출하는데 활용할 수 있습니다. mask image는 Meta AI가 만든 SAM (Segment Anything Model)을 이용하여 Rekognition 서비스에서 얻은 bounding box를 기반으로 얼굴 부분의 mask image를 추출하였습니다. SAM 모델은 GPU 기반 AWS 인스턴스에서 빠르게 mask image를 추출할 수 있으므로 우리는 SAM을 Amazon SageMaker Deployment를 통해 호스팅하였습니다. SageMaker Deployment는 기계 학습 모델을 쉽고 빠르게 학습한 다음 프로덕션 준비가 완료된 호스팅 환경에 직접 배포 가능한 완전 관리형 기계 학습 서비스로서 복잡한 과정 없이 SAM을 서비스 환경에서 활용할 수 있었습니다.

import boto3
import json

def invoke_sam_endpoint(face_image, response):
    # 얼굴 이미지를 인코딩하여 UTF-8 문자열로 변환
    encode_face_image = encode_image(face_image).decode("utf-8")
    
    # 얼굴 감지 결과에서 얼굴의 위치와 크기 정보 가져오기
    f_left, f_top, f_width, f_height = get_face_detail(response, face_image)

    # 입력 데이터를 딕셔너리로 구성
    inputs = dict(
        encode_image=encode_face_image,  # 인코딩된 얼굴 이미지
        input_box=[f_left, f_top, f_left+f_width, f_top+f_height]  # 얼굴 위치와 크기 정보
    )
    
    # SageMaker 엔드포인트 이름 설정
    endpoint_name = 'sageMaker-endpoint-name'
    
    # SageMaker Runtime 클라이언트 생성
    smr_client = boto3.client("sagemaker-runtime")
    
    # 입력 데이터를 JSON 문자열로 변환
    json_payload = json.dumps(inputs)
    
    # SageMaker 엔드포인트에 요청을 보내고 응답 받기
    response = smr_client.invoke_endpoint(
        EndpointName=endpoint_name,  # 엔드포인트 이름
        Accept="application/json",  # 응답 데이터 타입
        ContentType="application/json",  # 요청 데이터 타입
        Body=json_payload  # 요청 본문
    )

    # 응답 본문을 읽고 UTF-8 문자열로 변환
    data = response["Body"].read().decode("utf-8")
    return data  # 변환된 응답 데이터 반환

Figure 5. Amazon SageMaker Deployment를 이용한 mask image 호출

이렇게 얻은 mask image를 이용하여 Amazon Bedrock의 Amazon Titan Image Generator G1 model에서 Outpainting 방식으로 얼굴을 제외한 나머지 부분을 생성하도록 처리하였습니다. 이미지 생성에 사용하는 Prompt는 Amazon Rekognition에서 추출한 gender 정보로 사용자의 성별에 따라 각각 다른 prompt를 사용할 수 있도록 분기 처리하여 Amazon Bedrock을 호출하였습니다.

if gender == 'Female':
    # 성별이 'Female'인 경우의 프롬프트 설정
    outpaint_prompt = f"a korean young woman in a well tailored formal business suit, plain light blue background portrait photo"
else:
    # 성별이 'Female'이 아닌 경우의 프롬프트 설정
    outpaint_prompt = f"a korean young man in a well tailored formal business suit, plain light blue background portrait photo"

# 요청 본문 생성
body = json.dumps(
    {
        "taskType": "OUTPAINTING",  # 작업 유형 설정
        "outPaintingParams": {
            "text": outpaint_prompt,  # 생성 이미지에 대한 텍스트 프롬프트
            "negativeText": "Beard, Accessory, Scarf",  # 생성 이미지에서 제외할 요소
            "image": image_to_base64(photo),  # 원본 이미지를 base64로 인코딩
            "maskImage": image_to_base64(mask_image),  # 마스크 이미지를 base64로 인코딩
            "outPaintingMode": "DEFAULT"  # 아웃페인팅 모드 설정
        },
        "imageGenerationConfig": {
            "numberOfImages": 3,  # 생성할 이미지 수
            "cfgScale": 8,  # 이미지 생성에 대한 설정 값
            "seed": 42,  # 이미지 생성 시드 값
            "width": 384,  # 생성 이미지의 너비
            "height": 576  # 생성 이미지의 높이
        }
    }
)

# 모델 호출
client = session.client('bedrock-runtime')  # Bedrock Runtime 클라이언트 생성
response = client.invoke_model(
    body=body,  # 요청 본문 설정
    modelId="amazon.titan-image-generator-v1",  # 모델 ID 설정
    accept="application/json",  # 응답 데이터 타입 설정
    contentType="application/json"  # 요청 데이터 타입 설정
)

Figure 6. Amazon Bedrock의 Outpainting 생성

AI 이력서의 자기소개서 생성하기

우리는 우선 MBTI와 인재상을 매칭시키기 위해 Amazon OpenSearch Service에 주요 기업 33개에 대한 인재상 지식 베이스 (Knowledge bases)를 구축했습니다. 사용자가 MBTI를 선택하면 Amazon OpenSearch Service에서 keyword 방식으로 검색해 성격과 인재상이 가장 잘 부합하는 기업 3개를 추천해 줍니다.

    client = OpenSearch(
        hosts = [{'host': host, 'port': 443}],
        http_auth = auth,
        use_ssl = True,
        verify_certs = True,
        connection_class = RequestsHttpConnection,
        pool_maxsize = 20
    )
    
    
   
    # 쿼리 정의
    query = {
        "size": 3,
        "query": {
            "match": {
                "qualified": qualified_value
            }
        }
    }
    
    # OpenSearch 클러스터에 검색 요청
    res = client.search(
        index='company',
        body=query,   
    )

Figure 7. Amazon OpenSearch Service의 관련 기업 정보 검색

사용자가 3개 기업 중 지원하고 싶은 회사를 하나 선택하면 Amazon Bedrock의 Claude 모델을 이용하여 선택한 회사에 맞춘 이력서를 작성할 수 있습니다. 초기 개발 단계에서는 Claude 2.1 버전을 사용했으며, 4개 항목에 대해 1,200자~1,700자 정도를 작성하는데 약 1분 정도의 소요 시간이 필요했습니다. 하지만 Amazon Bedrock의 Claude 3 – Sonnet 모델로 변경하면서 모든 결과를 출력하는데 까지 소요되는 시간이 30초 이내로 크게 단축되었습니다. 더불어, 우리는 보다 빠르게 서비스에서 결과를 보여주기 위해 Bedrock 내의 Streaming 방식을 사용하여 이력서를 작성하였습니다. 그리고, AWS Lambda의 streamifyResponse로 스트리밍 응답을 처리할 수 있도록 설정했습니다. 아래는 기본적인 핸들러 설정 코드입니다:

// AWS Lambda 핸들러를 스트리밍 응답으로 설정
export const handler = awslambda.streamifyResponse(async (event, responseStream, _context) => {
    try {
        // 클라이언트 초기화
        const client = await initializeClient();
        
        // 사용 모델 ID 설정
        const modelId = "anthropic.claude-3-sonnet-20240229-v1:0";
        
        // 이벤트에서 본문(body) 파싱
        const body = JSON.parse(event['body']);
        const prompt = body.prompt;
        
        // 모델에 전달할 페이로드 생성
        const payload = createPayload(prompt);
        
        // InvokeModelWithResponseStreamCommand 명령 생성
        const command = new InvokeModelWithResponseStreamCommand({
           body: JSON.stringify(payload), // 페이로드를 JSON 문자열로 변환하여 설정
           contentType: 'application/json', // 콘텐츠 타입 설정
           accept: 'application/json', // 응답 콘텐츠 타입 설정
           modelId, // 모델 ID 설정
        });
        
        // 명령 실행하여 응답 받기
        const response = await client.send(command);
        
        // 변환 스트림 생성
        const transformStream = createTransformStream();
        
        // 파이프라인 실행
        await executePipeline(response.body, transformStream, responseStream, client, pipelinePromise);
    } catch (error) {
        // 오류 발생 시 로그 출력
        console.error('main error occurred:', error.message);
    }
});

Figure 8. AWS Lambda에서 Amazon Bedrock Claude 모델의 Streaming 요청

추가로, 변환 스트림을 생성하여 응답 데이터를 실시간으로 처리합니다. JSON 형식의 응답 데이터를 파싱하여 필요한 텍스트 데이터를 추출하고 스트림으로 전달합니다.

// 변환 스트림을 생성하는 함수
function createTransformStream() {
    return new Transform({
        objectMode: true, // 객체 모드로 설정하여 개별 객체를 스트림으로 처리
        transform(chunk, encoding, callback) {
            // chunk 객체의 bytes 속성을 배열로 변환
            const bytesArray = Object.values(chunk.chunk.bytes);
            // 배열을 버퍼로 변환
            const buffer = Buffer.from(bytesArray);
            // 버퍼를 UTF-8 문자열로 변환
            const jsonString = buffer.toString('utf8');

            try {
                // JSON 문자열을 객체로 파싱
                const jsonObject = JSON.parse(jsonString);
                // JSON 객체의 타입이 'content_block_delta'인지 확인
                if (jsonObject.type === 'content_block_delta') {
                    // delta 속성의 텍스트 데이터를 추출
                    const textData = jsonObject.delta.text;
                    const output = textData;
                    // 변환된 텍스트 데이터를 스트림으로 전달
                    this.push(output);
                }
                // 변환 완료를 콜백으로 알림
                callback();
            } catch (error) {
                // 오류 발생 시 콜백으로 오류 전달
                callback(error);
            }
        }
    });
}

Figure 9. AWS Lambda에서 Amazon Bedrock Claude 모델의 Streaming 응답 처리

응답 body, 변환 스트림, 응답 스트림을 연결하여 파이프라인을 실행합니다. 오류 발생 시 로깅하고 최종적으로 클라이언트를 종료합니다

// 파이프라인을 실행하는 비동기 함수
async function executePipeline(responseBody, transformStream, responseStream, client, pipelinePromise) {
    try {
        // 파이프라인을 실행하여 responseBody를 transformStream과 responseStream으로 연결
        await pipelinePromise(responseBody, transformStream, responseStream);
        return { success: true }; // 성공 시 결과 반환
    } catch (error) {
        console.error('Pipeline failed', error); // 오류 발생 시 로그 출력
        return { success: false, errorCode: error.code }; // 실패 시 오류 코드와 함께 결과 반환
    } finally {
        responseStream.end(); // 응답 스트림 종료
        client.destroy(); // 클라이언트 리소스 해제
    }
}

Figure 10. AWS Lambda에서 Amazon Bedrock Claude 모델의 파이프라인 실행

이 설정을 통해 이력서 작성 시간은 대폭 단축되었으며, 사용자에게 보다 신속한 결과를 제공할 수 있게 되었습니다.

마지막으로, 자기소개서를 작성하기 위해 상세한 prompt engineering을 진행하였습니다. 27살 대학 졸업 취업준비생의 관점에서 작성하도록 역할을 부여해주고 최대한 구체적으로 생성할 수 있도록 각 항목별 작성 지침을 상세하게 설정하였습니다. 또한, 예시를 추가하여 지원자가 자기소개서를 체계적으로 작성할 수 있도록 하였으며, 각 항목의 중요 요소를 빠짐없이 기술할 수 있도록 도왔습니다.

prompt = ‘’’
당신은 27살의 막 대학을 졸업한 취업준비생입니다. 당신의 mbti는 ${mbti} 입니다. 회사가 원하는 사람은 ${data} 입니다.아래 자기소개서 항목에 맞게 작성해주세요. 
각 항목은 700자 이상 1000자 이하로 답변하세요.
mbti를 직접적으로 언급하지 마세요. 답변에 <> 태그 포함 금지입니다. 글자수는 미표기하세요.
 회사에 지원하게 된 동기에 대해 구체적으로 설명해주세요. ${companyName}과의 연결고리는 무엇이며, 어떤 가치와 목표를 공유하나요? 이 회사를 선택한 구체적인 이유와 그 과정에서 느낀 개인적인 감정을 포함해서 작성해주세요. 
 당신의 성장 과정 중에서 자신에게 가장 큰 영향을 미친 사건이나 인물에 대해 자세히 기술해주세요. 그 사건이나 인물이 당신의 삶과 가치관에 어떤 변화를 가져왔는지, 그리고 그 경험이 오늘날 당신을 어떻게 만들었는지 설명해주세요.
 참여했던 프로젝트 중 가장 기억에 남는 것을 선택해주세요. 그 프로젝트에서 당신의 역할은 무엇이었고, 어떤 결과를 달성했나요? 프로젝트를 통해 배운 교훈과 이를 통해 어떻게 성장했는지 상세히 설명해주세요.
 본인의 장점과 단점을 솔직하게 평가해주세요. 단점을 극복하기 위해 취한 구체적인 조치는 무엇이었나요? 이 과정에서 어떤 도전에 직면했으며, 그것을 어떻게 극복했는지 공유해주세요. 구체적인 사례와 예를 들어 설명하는 것이 중요합니다. 이력서 작성에 필요한 모든 정보를 포함시키기 위해 세부적인 내용을 꼼꼼히 기술해주세요.

각 항목 뒤에 꼭 줄 바꿈 해주세요. 글머리기호(한글자), 줄 바꿈, 서두 없이 바로 시작하세요. 각 항목이 끝나면 줄 바꿈 해주세요. 아래는 예시입니다. 

제가 ${companyName}에서 일하고 싶은 이유는 회사의 가치관과 제 개인적 목표가 일치하기 때문입니다. ${companyName}은 최신 기술을 연구하고 혁신을 추구하는 기업으로, 저 또한 지속적인 성장을 목표로 삼고 있습니다. 대학에서 프로그래밍을 전공하면서 다양한 기술 스택을 익혔고, 졸업 후에도 독학으로 최신 기술 트렌드를 꾸준히 공부해왔습니다.
${companyName}의 도전적인 문화는 저의 호기심과 기술 발전에 대한 열정을 자극했습니다. 특히, ${companyName}이 지속 가능한 성장을 중시하는 점에서 깊이 공감하였습니다. 저는 단순히 기술을 개발하는 것에서 그치지 않고, 그 기술이 사회에 어떤 긍정적인 영향을 미칠 수 있는지에 대해 고민해왔습니다. 이러한 점에서 ${companyName}의 비전과 저의 목표가 일치한다고 느꼈습니다.
${companyName}에 지원하기로 결심한 과정에서 큰 희망과 설렘을 느꼈습니다. 혁신을 향한 열정이 더욱 커졌고, ${companyName}과 함께 성장하며 사회에 기여할 수 있는 기회를 얻고자 합니다. ${companyName}의 일원이 되어, 끊임없이 발전하는 환경 속에서 제 역량을 최대한 발휘하고 싶습니다.
‘’’

Figure 11. 자기 소개서 생성 prompt

AI 이력서 서비스 개발 시 트러블 슈팅

Bedrock 사용 시 ThrottlingException

개발 기간 동안 이틀 간 ThrottlingException이 발생하여 애플리케이션의 안정성이 저하되는 문제가 발생했습니다. 오류 메시지는 다음과 같습니다:

[error] ThrottlingException: Too many requests, please wait before trying again. You have sent too many requests. Wait before trying again.

이 문제의 원인은 Amazon Bedrock의 Service quota제한으로 Amazon Titan Image Generator G1의 경우 AWS 리전 별 분 당 요청 처리 수가 60개로 제한된다는 점을 확인하였습니다. 이를 해결하기 위해 에러 발생 시 자동으로 재시도하는 로직을 추가하였습니다.

import boto3
from botocore.config import Config
config = Config(
    read_timeout=600, ## Timeout 시간 조정
    retries = dict(
        max_attempts = 8 ## Retry 횟수 조정
    )
)
bedrock_runtime = boto3.client(service_name='bedrock-runtime',
                            region_name='us-east-1',
                            config=config)

Figure 12. boto3 라이브러리를 사용한 ThrottlingException 시 재시도 설정

AWS SDK에서 제공하는 boto3 라이브러리를 사용하여, ThrottlingException이 발생할 경우 자동으로 재시도하는 설정을 추가하였습니다. 이를 통해 요청이 실패할 경우 재시도하여 애플리케이션의 안정성을 향상시켰습니다.

동물 사진 또는 사물 사진 업로드에 대한 예외 로직 추가

팀 내 테스트 과정에서 사람 사진 대신 반려동물 사진이나 사물 사진을 업로드하는 경우, 애플리케이션이 예상치 못한 오류를 발생시켰습니다. 이를 해결하기 위해 사람의 얼굴이 포함된 사진인지 확인하는 로직을 추가하여, 유효한 사진만 처리되도록 개선하였습니다.

Amazon Rekognition의 FaceDetails 응답이 빈 배열일 경우, 이는 얼굴이 감지되지 않았음을 의미합니다. 이 특성을 활용하여 업로드한 사진에 사람 얼굴이 포함되어 있는지 확인하는 로직을 추가하였습니다. 아래는 해당 문제를 해결하기 위한 리팩토링한 코드입니다.

const { RekognitionClient, DetectFacesCommand } = require("@aws-sdk/client-rekognition");

// AWS Rekognition 클라이언트 설정
const client = new RekognitionClient({
    region: process.env.AWS_S3_BUCKET_REGION, // AWS S3 버킷의 리전 설정
    credentials: {
        accessKeyId: process.env.AWS_REKOGNITION_ACCESS_KEY, // Rekognition 액세스 키
        secretAccessKey: process.env.AWS_REKOGNITION_SECRET_ACCESS_KEY, // Rekognition 시크릿 액세스 키
    }
});

// 얼굴 감지 함수
const detectFaces = async (req, res) => {
    let fileContent = req.file.buffer; // 업로드된 파일의 버퍼를 가져옴

    const command = new DetectFacesCommand({
        Image: {
            Bytes: fileContent // 이미지 버퍼 설정
        },
        Attributes: ['ALL'] // 모든 얼굴 속성 가져오기
    });

    try {
        // Rekognition에서 얼굴 감지 실행
        const response = await client.send(command);
        const faceCount = response.FaceDetails.length; // 감지된 얼굴의 수

        if (faceCount === 0) {
            return res.status(400).send('얼굴이 포함된 사진을 업로드 해주세요.'); // 얼굴이 없는 경우 에러 메시지 전송
        }

        return res.status(200).send('얼굴 감지 성공'); // 얼굴이 감지된 경우 성공 메시지 전송
    } catch (error) {
        console.error("Rekognition 에러: ", error); // 에러 로그 출력
        return res.status(500).send('서버 에러. 다시 시도해 주세요.'); // 서버 에러 발생 시 에러 메시지 전송
    }
};

module.exports = { detectFaces }; // detectFaces 함수 모듈 내보내기

Figure 13. Amazon Rekognition을 활용한 얼굴 사진 포함 여부 확인

스트리밍 방식에서의 항목 분리 문제

스트리밍 방식으로 데이터를 수신할 때, 데이터가 작은 청크 단위로 나누어 전달되었습니다. 이러한 작은 청크 단위의 데이터는 항목을 구분하는 과정에서 어려움을 초래했습니다. 일반적인 말머리 기호(예: 가., a.)를 사용하면 예상치 못한 위치에서 항목이 분리되는 문제가 발생했습니다. 예를 들어, 다음과 같이 청크가 전달 되었습니다.

청크 1: ". 첫 번째 항"
청크 2: "목 내용나. 두 번째"
청크 3: " 항목 내용"

이 문제를 해결하기 위해, ‘Figure 11. 자기 소개서 생성 prompt’와 같이 한국어에 존재하지 않는 ‘갹, 냑, 댝, 먁’이라는 글자를 말머리 기호로 설정하고, 각 항목 끝에 줄 바꿈을 포함하여 명확히 구분되도록 했습니다.

AI 이력서 서비스의 결과물

Amazon Bedrock의 Amazon Titan Image Generator G1 모델을 통해 일관되고 높은 품질의 이미지를 생성할 수 있었으며, Amazon Bedrock과 Amazon OpenSearch Service를 활용한 RAG 구성으로 사용자가 선택한 MBTI와 주요 기업의 인재상 지식 베이스와 매칭하여 자기소개서를 생성하였습니다. 또한, Amazon Bedrock의 Claude 2.1에서 Claude 3 – Sonnet 모델로 업그레이드하면서 결과 품질과 생성 속도가 크게 향상되었습니다. 이를 통해 사용자에게 사례 기반의 구체적인 자기소개서를 빠르게 제공할 수 있게 되었습니다.

마무리

‘AI 이력서’ 서비스 개발을 위해 AWS의 생성형 AI 서비스와 AI/ML 서비스를 활용하여 복잡한 서비스 개발에 AI에 대한 기술적 배경이 없이도 고품질의 AI 기반 서비스를 구현할 수 있음을 확인했습니다.

“AWS Korea의 솔루션즈 아키텍트 분들과 CSM분들의 참여로 진행된 Gen AI EBA 프로그램을 통해 웅진은 ‘AI 이력서’ 서비스를 이번 2024 World IT show에 선보일 수 있었습니다. 프로그램을 통해 제공해 주신 다양한 기술적인 가이드는 웅진의 AI 기술력 향상에 많은 도움이 되었으며, 이번 서비스 개발에 큰 도움이 되었습니다. 특히 ‘AI 이력서’ 서비스에 활용된 Amazon Bedrock과 SageMaker 서비스 및 다양한 파운데이션 모델(Amazon Titan, Anthropic Claude 3) 을 통해 생성형 AI에서 원하는 것 이상으로 이미지와 텍스트의 좋은 결과를 얻을 수 있었기에 많은 관람객의 주목을 받았으며 언론에도 노출되는 성과를 거두었습니다. 이번 프로그램에 참여한 모든 AWS의 솔루션즈 아키텍트 분들, CSM, 영업대표를 포함한 AWS Korea의 모든 분들께 감사드립니다.”

— 강양수, 웅진 클라우드사업본부 본부장

향후, 생성형 AI를 접목하는 서비스가 늘어나는 만큼, 개별 사용자의 취향과 필요에 맞춘 맞춤형 서비스를 제공할 수 있어 사용자 경험을 크게 향상시킬 것으로 예상됩니다. 또한, AWS의 다양한 AI 서비스를 사용하여 새로운 아이디어와 솔루션을 신속하게 개발하고 구현할 수 있어, 웅진은 AWS와 함께 시장을 선도하는 혁신을 계속 추구할 것으로 기대됩니다.

이름

Hyunyoung Hwang

황현영 소프트웨어 엔지니어는 웅진 클라우드전략고객팀에서 AI 기술을 활용한 서비스 개발을 담당하고 있습니다. 특히, RAG (Retrieval-Augmented Generation) 기술과 벡터 데이터베이스(Vector DB)를 활용한 솔루션 개발에 주력하고 있습니다. 이러한 기술을 활용해 기업의 데이터 활용 능력을 극대화하며, AI를 통해 고객 비즈니스의 경쟁력 강화와 지속 가능한 성장을 지원합니다.

이름

Wangi Hong

홍완기 개발자는 웅진 클라우드전략고객팀에서 시스템 개발과 DevOps 업무를 담당하고 있습니다. 다양한 경험을 바탕으로, 모니터링 시스템과 생성형 AI를 활용한 솔루션을 개발하며, AWS 클라우드를 활용해 고객의 혁신과 비즈니스 성과를 달성할 수 있도록 돕고 있습니다. 최신 기술을 도입해 시스템 효율성을 극대화하고, 고객 요구에 맞춘 맞춤형 솔루션을 제공하도록 노력하고 있습니다.

이름

Seungung Kim

김승웅 SA(Solutions Architect)는 웅진 클라우드전략고객팀 소속으로 고객의 비즈니스 요구를 이해하고, 이를 충족하기 위한 최적의 AWS 솔루션을 설계하는 역할을 맡고 있습니다. 다양한 클라우드 기술과 서비스를 활용하여 고객 시스템을 최적화하고, 기술 지원을 제공하며, 혁신적인 클라우드 아키텍처를 구축하는 역할을 수행하고 있습니다.

이름

Minkyung Cho

조민경 솔루션즈 아키텍트는 웅진 클라우드전략고객팀 소속으로 다양한 AWS 서비스를 통해 고객이 클라우드 환경에서 효율적이고 원활한 경험을 제공받을 수 있도록 지원하고 있습니다.

Youngjoon Choi

Youngjoon Choi

최영준 Principal AI/ML Expert SA는 제조, 하이테크, 금융 등의 다양한 산업에서 엔터프라이즈 IT를 경험하면서 개발자로, 아키텍트로, 데이터 과학자로 다양한 활동을 하였습니다. 기계학습과 딥러닝 연구를 진행하였고, 구체적으로 Hyperparameter optimization과Domain adaptation등을 주제로 알고리즘 연구와 논문 발표를 진행하였습니다. AWS에서는 AI/ML를 전문 분야로 다양한 산업군에서 분산학습/초거대 모델 개발과 ML 파이프라인 구축 등에 대해 AWS 서비스를 이용한 기술 검증 지원, 아키텍처 제안 및 검토 등을 수행하고 있으며, AI/ML 생태계가 더욱 확장할 수 있도록 다양한 기여를 하고자 합니다.

SooMin Kim

SooMin Kim

김수민 Customer Solutions Manager는 고객의 클라우드 여정에서 발생하는 다양한 과제를 해결하고 클라우드 전환을 가속화함으로써 고객의 비즈니스 가치 실현을 지원하는 프로그램 매니저 역할을 수행하고 있습니다.

Yongho Choi

Yongho Choi

최용호 솔루션즈 아키텍트는 개발 경험을 바탕으로 고객의 애플리케이션 현대화 여정과 함께 효율적인 아키텍처를 구성하실 수 있도록 돕고 있습니다.