LLM은 프롬프트라는 언어로 대화하고, RAG는 그 대화에 '참고 자료'를 제공한다.
AWS의 생성형 AI 플랫폼인 Amazon Bedrock을 이용하면 Claude와 같은 강력한 대형 언어 모델을 직접 인프라 구축 없이 API로 호출할 수 있습니다. 이 글에서는 Bedrock의 기본 API 구조부터 프롬프트 엔지니어링, 그리고 RAG(검색 증강 생성) 시스템 구축까지, 코드 스타일 검사와 보안 취약점 탐지를 직접 구현하는 흐름을 중심으로 핵심 개념을 정리합니다.
단순한 API 호출에서 시작해 지식 기반을 갖춘 AI 리뷰어를 만들기까지의 여정을 따라가면 생성형 AI를 실무에 적용하는 데 필요한 개념적 기반을 갖출 수 있을 것입니다.
🗺️ 전체 아키텍처 개요
이 시리즈는 "CodeBuddy"라는 이름의 AI 코드 리뷰 에이전트를 단계적으로 완성해 나가는 프로젝트입니다. 전체 구조를 먼저 조감하면 각 장의 개념이 어디에 위치하는지 이해하기 쉽습니다.
- 1장: AWS 계정 설정, Bedrock 접근 권한, 개발 환경 준비
- 2장: Boto3로 Claude 호출, 프롬프트 엔지니어링, 코드 분석 기초
- 3장: RAG 개념, 임베딩, Knowledge Base 구축
- 4장: RAG 기반 코드 스타일/보안 검사 자동화, 검색 품질 최적화
이 네 단계를 거치면 코드를 입력했을 때 버그 가능성, 스타일 위반, 보안 취약점을 자동으로 리포트해주는 시스템이 완성됩니다. 여기서 주목할 점은, 처음에는 LLM 자체의 일반 지식만 활용하지만 후반부에서 PEP8, OWASP 같은 외부 문서를 '지식 창고'로 연결하면서 시스템이 훨씬 정확하고 맥락에 맞는 답변을 제공하게 된다는 점입니다.
🔌 Bedrock API 구조 이해하기
Foundation Model이란 무엇인가
Amazon Bedrock은 Anthropic, Meta, Amazon 등 여러 회사의 대형 언어 모델을 단일 플랫폼에서 관리하고 호출할 수 있게 해주는 완전 관리형 서비스입니다. 각 모델은 '기반 모델(Foundation Model)'이라고 불리며, 수천억 개의 파라미터로 훈련된 범용 AI 모델입니다. 기반 모델의 핵심 특징은 단 하나의 모델이 번역, 요약, 코드 생성, 질의응답, 창작 등 매우 다양한 작업을 수행할 수 있다는 점입니다. 이는 이전 세대 AI가 특정 작업(예: 이미지 분류, 감성 분석)에 특화된 별도의 모델을 요구했던 것과 근본적으로 다른 패러다임입니다.
Bedrock이 편리한 이유 중 하나는 인프라 관리가 필요 없다는 점입니다. GPU 서버를 직접 구축하거나 모델 가중치를 직접 다운로드해 운영할 필요 없이, API 호출만으로 세계 최고 수준의 모델을 즉시 사용할 수 있습니다. 이 과정은 AWS IAM 권한 설정과 Boto3 클라이언트 생성만으로 시작됩니다.
InvokeModel vs Converse API
Bedrock에서 모델을 호출하는 방법은 크게 두 가지입니다. 기존의 InvokeModel API와 더 최신의 Converse API입니다.
InvokeModel은 모델별로 다른 요청 형식을 사용합니다. Claude를 호출할 때와 Meta의 Llama를 호출할 때 요청 본문(body)의 구조가 완전히 다르기 때문에, 개발자가 각 모델의 스펙을 개별적으로 파악하고 코드를 다르게 작성해야 했습니다. 새로운 모델로 교체하면 코드 전체를 다시 작성해야 하는 번거로움이 있었습니다.
반면 Converse API는 Claude, Llama, Titan 등 다양한 모델을 동일한 인터페이스로 호출할 수 있는 통합 API입니다. 메시지 구조, 토큰 계산, 대화 기록 관리 방식이 모델과 무관하게 표준화되어 있습니다. 따라서 모델을 교체하더라도 modelId 파라미터 값만 바꾸면 됩니다. 이 과정에서 배운 내용들은 이 표준화된 인터페이스를 기반으로 하기 때문에 특정 모델에 종속되지 않는 코드 작성이 가능합니다.
# Converse API의 기본 호출 구조
response = bedrock.converse(
modelId='global.anthropic.claude-sonnet-4-6',
messages=[
{
"role": "user",
"content": [{"text": "코드를 분석해줘"}]
}
]
)
응답 구조 이해하기
Converse API의 응답은 일관된 JSON 구조를 가집니다. 핵심 응답 텍스트는 response['output']['message']['content'][0]['text']에 담겨 있습니다. 그 외에 usage 필드에는 입력 토큰 수, 출력 토큰 수, 총 토큰 수가 기록됩니다. 이 토큰 정보는 비용 계산과 성능 최적화에 중요하게 활용됩니다.
stopReason 필드는 모델이 왜 응답을 멈췄는지를 나타냅니다. 정상적인 완료는 end_turn이고, 토큰 한도를 초과해서 멈추면 max_tokens가 됩니다. max_tokens로 응답이 끊겼다면 리포트가 중간에 잘린 것이므로, 이를 감지해서 토큰 한도를 늘리거나 입력 코드를 분할하는 처리가 필요합니다.
🪙 토큰이란 무엇인가
LLM을 다루다 보면 "토큰"이라는 단위가 계속 등장합니다. 토큰은 LLM이 텍스트를 처리하는 기본 단위로, 단어보다 작거나 단어 자체이거나 여러 단어의 조합일 수 있습니다. 영어의 경우 "tokenization"이라는 단어는 하나의 토큰이 아니라 ["token", "ization"] 두 개의 토큰으로 쪼개질 수 있습니다. 한국어의 경우 문자 하나 하나가 별도의 토큰이 되는 경우가 많아, 같은 내용이라도 영어보다 토큰 수가 더 많아집니다.
토큰이 중요한 이유는 세 가지입니다.
첫째, 비용과 직결됩니다. API 호출 비용은 입력 토큰 수와 출력 토큰 수를 기준으로 책정됩니다. Claude Sonnet 4.6 기준으로 입력은 약 $0.003/1K 토큰, 출력은 약 $0.015/1K 토큰으로, 출력 토큰이 입력 토큰보다 약 5배 비쌉니다.
둘째, 모델의 컨텍스트 윈도우 크기가 토큰 수로 제한됩니다. 한 번의 API 호출에서 처리할 수 있는 최대 토큰이 정해져 있으며, 이를 초과하면 에러가 발생하거나 내용이 잘립니다.
셋째, 응답 품질에도 영향을 미칩니다. maxTokens 설정이 너무 작으면 모델이 답변을 완성하기 전에 강제로 종료됩니다.
실용적인 가이드라인으로, 단순한 질문에는 500토큰, 함수 하나 분석에는 2000토큰, 여러 파일 분석이나 긴 리포트에는 4000~8000토큰이 적합합니다.
✍️ 프롬프트 엔지니어링 기초
프롬프트가 왜 중요한가
"코드 리뷰해줘"라고 입력하면 모델은 아마 "좋아요, 잘 짜여진 코드네요"처럼 막연한 답변을 할 것입니다. 반면 "당신은 시니어 개발자입니다. 다음 코드를 버그 가능성, 성능, 스타일 측면에서 분석하고 Markdown 형식으로 정리해주세요."라고 입력하면 구조화된 전문적인 리포트를 받을 수 있습니다.
이처럼 LLM에게 '일하는 방식'을 알려주는 기술이 프롬프트 엔지니어링입니다. 모델 자체를 재훈련하지 않고도 입력 텍스트를 잘 설계하는 것만으로 완전히 다른 결과를 얻을 수 있습니다. 프롬프트 엔지니어링은 AI의 잠재 능력을 최대한 끌어내는 기술이자, 비용 효율적으로 고품질 결과를 얻는 핵심 방법입니다.
프롬프트의 구성 요소
효과적인 프롬프트는 크게 네 가지 요소로 구성됩니다.
- 역할(Role)은 AI에게 특정 페르소나를 부여합니다. "당신은 실무 경험 10년차 시니어 개발자입니다"라고 역할을 지정하면 모델은 그 역할에 맞는 어조, 관점, 전문성 수준으로 답변하게 됩니다. 단순히 "전문가처럼 답해줘"보다 구체적인 역할 부여가 훨씬 효과적입니다.
- 작업(Task)은 구체적으로 해야 할 일을 명시합니다. "코드를 분석해줘"보다 "다음 코드에서 SQL Injection 취약점이 있는지 확인하고, 발견 시 위치와 수정 방법을 알려줘"처럼 구체적일수록 원하는 답변을 얻기 쉽습니다.
- 형식(Format)은 출력 구조를 지정합니다. "버그: [위치] - [설명]" 형식으로 작성해달라거나, JSON으로 결과를 반환해달라거나, Markdown 테이블로 정리해달라는 형식 지시는 결과를 자동화 파이프라인에 연결할 때 특히 중요합니다.
- 맥락(Context)은 배경 정보를 제공합니다. 이 코드가 Django로 개발된 웹 서비스의 일부라는 것, 사용 중인 Python 버전이 3.11이라는 것, 또는 회사의 특정 코딩 컨벤션을 따른다는 것을 알려주면 모델이 더 맥락에 맞는 조언을 제공할 수 있습니다.
Temperature: 창의성의 조절 다이얼
Temperature는 LLM이 답변을 생성할 때 단어를 선택하는 방식의 '무작위성'을 제어하는 파라미터입니다. 기술적으로는 소프트맥스 함수를 통해 계산된 다음 토큰의 확률 분포를 얼마나 평탄하게(균일하게) 만들지를 결정합니다.

Temperature 값이 0에 가까울수록 모델은 항상 가장 확률이 높은 토큰을 선택합니다. 이렇게 되면 같은 질문에 항상 거의 동일한 답변이 나오는 결정론적인 동작이 됩니다. Temperature 값이 높아질수록 낮은 확률의 토큰도 선택될 기회를 얻어 더 다양하고 창의적인 답변이 나옵니다.
코드 분석처럼 정확성이 중요한 작업에서는 낮은 Temperature(0.1~0.3)를 사용하는 것이 좋습니다. 같은 코드를 여러 번 분석해도 일관된 결과를 얻을 수 있기 때문입니다. 반면 새로운 아이디어를 브레인스토밍하거나 창의적인 글쓰기를 할 때는 높은 Temperature(0.7~1.0)가 더 유용합니다.
| Temperature 범위 | 특징 | 적합한 용도 |
| 0.0 ~ 0.3 | 일관적, 보수적, 예측 가능 | 코드 분석, 사실 기반 질답 |
| 0.4 ~ 0.7 | 균형 잡힌 답변 | 일반 대화, 요약 |
| 0.8 ~ 1.0 | 창의적, 다양한 답변 | 아이디어 생성, 시나리오 작성 |
Guardrails: AI의 안전장치
Guardrails는 모델이 부적절한 응답을 생성하지 못하도록 막는 필터 레이어입니다. "해킹 코드 알려줘"라는 요청에 Guardrails 없이는 실제 악성 코드가 반환될 수 있지만, Guardrails를 설정하면 "죄송합니다. 해당 요청은 처리할 수 없습니다"와 같은 거절 응답이 반환됩니다.
Guardrails가 차단하는 대상에는 해킹, 불법 복제 같은 불법 콘텐츠, 차별적 혐오 발언, 주민번호나 계좌번호 같은 개인정보, 악성코드나 바이러스 같은 유해 코드 등이 포함됩니다. Bedrock 콘솔에서 Guardrail을 생성하면 고유 ID를 받게 되고, API 호출 시 이 ID를 파라미터로 전달하면 됩니다.
코드 리뷰 에이전트처럼 내부 도구로 사용한다면 Guardrails가 반드시 필요하지 않을 수 있습니다. 그러나 외부 사용자가 임의의 코드를 입력할 수 있는 환경이라면, 보안 강화와 책임 관리 측면에서 Guardrails 도입을 고려하는 것이 좋습니다.
🔍 RAG: LLM에게 참고 자료를 주다
왜 RAG가 필요한가
순수하게 LLM의 사전 학습 지식만으로 "이 코드가 우리 회사 코딩 컨벤션에 맞나요?"라고 물으면 모델은 "죄송합니다, 회사 코딩 컨벤션을 모릅니다"라고 대답할 수밖에 없습니다. LLM은 훈련 시점까지의 공개된 정보를 학습했을 뿐, 특정 조직의 내부 규칙이나 훈련 이후에 등장한 새로운 보안 취약점 데이터베이스는 알지 못합니다.
여기서 Hallucination(환각) 문제도 중요합니다. 모델이 모르는 내용을 물어봤을 때, 정직하게 "모른다"고 답하는 대신 그럴듯하게 들리지만 사실이 아닌 내용을 자신 있게 생성하는 현상입니다. 실제로 구글의 Bard가 제임스 웹 우주망원경에 대한 틀린 정보를 자신 있게 제공해 구글 주가가 1000억 달러 하락하는 사건이 있었습니다. 코드 보안 검사에서 모델이 없는 취약점을 있다고 하거나, 실제 취약점을 간과하는 것은 매우 위험할 수 있습니다.
RAG(Retrieval-Augmented Generation, 검색 증강 생성)는 이 문제를 해결합니다. 질문에 답하기 전에 먼저 외부 문서 데이터베이스에서 관련 내용을 검색해 그 내용을 LLM에게 '참고 자료'로 제공하는 방식입니다. 모델이 처음부터 모든 것을 알고 있을 필요 없이, 답변할 때 관련 최신 문서를 참조하도록 만드는 것입니다. 마치 '오픈북 시험'과 같습니다.
RAG의 동작 원리
RAG는 크게 오프라인 단계와 실시간 단계 두 부분으로 나뉩니다.

오프라인 단계(문서 색인)에서는 PEP8, OWASP, 회사 내부 코딩 가이드 등의 문서를 임베딩 모델을 통해 벡터로 변환하고 벡터 데이터베이스에 저장합니다.
실시간 단계(질의 응답)에서는 사용자의 질문을 임베딩 벡터로 변환하고, 벡터 데이터베이스에서 질문과 의미적으로 유사한 문서 조각들을 검색합니다. 검색된 문서 조각들을 LLM에게 컨텍스트로 제공하면, LLM은 그 내용을 참조해 답변을 생성합니다.
이 방식의 핵심 장점은 세 가지입니다. 첫째, 모델을 재훈련하지 않고도 새로운 정보를 반영할 수 있습니다. 새로운 보안 취약점이 발견되면 문서를 업데이트하고 재색인하면 됩니다. 둘째, 답변에 출처를 포함할 수 있어 검증이 가능합니다. "PEP8 문서 3번째 섹션에 따르면..."처럼 근거를 제시할 수 있습니다. 셋째, 할루시네이션을 크게 줄일 수 있습니다. 모델이 주어진 참고 자료에 기반해 답변하도록 유도하기 때문입니다.
🧮 임베딩과 벡터 검색의 원리
텍스트를 숫자로 변환하다
임베딩(Embedding)은 텍스트를 고차원 공간의 숫자 벡터로 변환하는 기술입니다. Amazon Titan Embeddings v2를 사용하면 어떤 텍스트든 1024개의 숫자로 이루어진 벡터로 변환됩니다.
임베딩의 핵심 특성은 의미적으로 유사한 텍스트가 벡터 공간에서 서로 가까운 위치에 배치된다는 점입니다. "Python 변수명은 snake_case를 사용합니다"와 "함수명은 소문자로 작성합니다"는 모두 파이썬 네이밍 규칙에 관한 내용이므로 벡터 공간에서 가까이 위치합니다. 반면 "맛있는 피자 레시피"는 프로그래밍과 전혀 관계없으므로 멀리 떨어집니다. 따라서 질문을 벡터로 변환한 후 가까운 벡터들을 찾으면, 의미적으로 관련 있는 문서들을 찾을 수 있습니다.
벡터 = 문장의 '수학적 지문'이라고 이해하면 됩니다. 지문이 사람을 고유하게 식별하듯, 벡터는 텍스트의 의미를 고유하게 표현합니다.
코사인 유사도
두 벡터가 얼마나 유사한지 측정하는 방법 중 가장 널리 쓰이는 것이 코사인 유사도입니다. 코사인 유사도는 두 벡터 사이의 각도를 측정하는데, 각도가 작을수록(방향이 비슷할수록) 유사도가 높습니다. 값의 범위는 -1에서 1 사이이며, 1에 가까울수록 매우 유사, 0에 가까울수록 관련 없음, -1에 가까울수록 반대 의미를 뜻합니다.
수식으로는 cos(θ) = (A · B) / (|A| × |B|)로 표현됩니다. 두 벡터의 내적을 각 벡터의 크기의 곱으로 나눈 값입니다. 실제로 "calculate_total"과 "get_sum"처럼 기능은 비슷하지만 이름이 다른 두 함수의 임베딩 벡터는 코사인 유사도가 0.35 수준으로 나타나는 반면, 전혀 다른 도메인의 함수는 0.15 수준으로 낮게 나타납니다.
Amazon Titan Embeddings v2
Amazon Bedrock에서 기본 임베딩 모델로 제공하는 Titan Embeddings v2는 1024차원 벡터를 출력하며, 한국어를 포함한 100개 이상의 언어를 지원합니다. 벡터 차원(256, 512, 1024)을 선택할 수 있는데, 차원이 높을수록 의미를 더 정밀하게 표현할 수 있지만 저장 공간과 검색 속도에서 트레이드오프가 있습니다. 일반적으로 1024차원이 적정 균형을 제공합니다.
🏗️ Amazon Bedrock Knowledge Base 아키텍처
구성 요소와 그 역할

Bedrock Knowledge Base는 RAG 시스템을 구축하기 위해 S3, 임베딩 모델, 벡터 데이터베이스를 연결해주는 완전 관리형 오케스트레이터입니다. 세 가지 주요 구성 요소가 있습니다.
- S3(Simple Storage Service)는 원본 문서를 저장하는 '서류 창고' 역할을 합니다. PDF, TXT, Markdown 형식의 문서를 S3 버킷에 업로드하면 Bedrock이 자동으로 읽어서 처리합니다. S3의 버전 관리 기능 덕분에 문서를 업데이트해도 이전 버전을 보존할 수 있습니다.
- Bedrock Knowledge Base 자체는 전체 RAG 파이프라인을 관리하는 오케스트레이터입니다. S3에서 문서를 읽어오고, 텍스트를 청크(Chunk)로 분할하고, 임베딩 모델을 호출해 벡터를 생성하고, 그 벡터를 벡터 데이터베이스에 저장하는 전 과정을 자동으로 처리합니다.
- OpenSearch Serverless는 벡터를 저장하고 빠르게 검색하는 벡터 데이터베이스 역할을 합니다. 수백만 개의 벡터 중에서 유사한 것을 밀리초 단위로 찾아낼 수 있습니다. 'Serverless'라는 이름처럼 클러스터를 직접 관리할 필요 없이 사용한 만큼만 비용을 지불합니다.
Knowledge Base ID는 생성 후 AWS가 자동으로 할당하는 고유 식별자입니다(예: ABC123DEFG). API 호출 시 어떤 지식 기반을 참조해야 하는지 지정하는 '주소'로 사용되며, IAM 권한 제어에도 활용됩니다.
청킹(Chunking) 전략
50페이지짜리 PEP8 문서 전체를 하나의 벡터로 만들면 검색의 정밀도가 떨어집니다. "변수명 규칙"에 대해 물었을 때, 인덱싱 규칙이나 들여쓰기 규칙까지 포함된 거대한 문서 전체가 검색 결과로 나올 수 있기 때문입니다. 이를 해결하기 위해 문서를 작은 조각(청크)으로 분할해서 각 조각별로 벡터를 생성합니다. 이 과정을 청킹이라고 합니다.
청킹 크기는 검색 품질에 직접적인 영향을 미칩니다. 청크가 너무 작으면 검색 정밀도는 높아지지만 앞뒤 맥락이 부족해집니다. 반면 너무 크면 맥락은 풍부하지만 관련 없는 내용도 함께 검색될 수 있습니다. Amazon Bedrock은 기본 청킹(300토큰), 고정 크기 청킹(원하는 토큰 수 지정), 계층적 청킹(작은 청크와 큰 청크를 함께 사용) 등 다양한 전략을 제공합니다.
Overlap(겹침)도 중요한 개념입니다. 청크와 청크 사이에 일정 비율의 내용이 겹치도록 설정하면, 경계에 걸친 정보가 어느 청크에도 포함됩니다. 예를 들어 "함수 X는 Y를 반환합니다"라는 문장이 두 청크 경계에 걸쳐 있더라도, 20%의 Overlap을 설정하면 양쪽 청크 모두에 이 문장이 포함되어 어떤 청크가 검색되더라도 해당 정보를 얻을 수 있습니다.
코드 문서의 경우 함수나 클래스 단위로 청크를 나누는 것이 의미적으로 가장 자연스럽습니다. 함수 정의의 일부가 한 청크에, 나머지가 다른 청크에 나뉘는 상황을 방지할 수 있기 때문입니다.
메타데이터 활용
메타데이터는 문서에 추가로 붙이는 태그 정보입니다. S3에 파일을 업로드할 때 "언어: Python", "규칙 유형: 스타일"과 같은 메타데이터를 함께 저장하면, 검색 시 필터링에 활용할 수 있습니다. JavaScript 코드를 분석할 때 Python 스타일 가이드 대신 JavaScript Airbnb 스타일 가이드만 검색하도록 제한하는 방식입니다. 이렇게 하면 검색 결과의 정확도가 크게 올라갑니다.
데이터 소스 동기화
S3에 새 문서를 추가하거나 기존 문서를 수정했다고 해서 자동으로 Knowledge Base가 업데이트되지는 않습니다. Bedrock의 Ingestion Job을 실행해야 S3의 변경 사항이 벡터 데이터베이스에 반영됩니다. 동기화 상태는 STARTED → IN_PROGRESS → COMPLETE 또는 FAILED로 변화하며, 일반적으로 수십 초에서 수 분이 소요됩니다.
🛠️ RetrieveAndGenerate API: 검색과 생성의 통합
수동 방식의 복잡함
RAG를 직접 구현하면 네 단계가 필요합니다. 먼저 사용자 질문을 임베딩 벡터로 변환하고, 그 벡터로 벡터 데이터베이스에서 유사한 문서를 검색한 다음, 검색된 문서들을 LLM이 이해할 수 있는 컨텍스트로 조합하고, 마지막으로 LLM에게 컨텍스트와 함께 질문을 전달해 답변을 받아야 합니다. 각 단계마다 별도의 API 호출이 필요하고 오류 처리도 복잡합니다.
RetrieveAndGenerate의 단순함
RetrieveAndGenerate는 이 모든 단계를 단일 API 호출로 처리합니다. 질문을 입력하면 Bedrock이 알아서 임베딩 변환, 문서 검색, 컨텍스트 조합, LLM 호출의 모든 과정을 처리하고 최종 답변과 참조한 문서 목록을 반환합니다.
이 API의 중요한 장점 중 하나는 retrievedResults 필드를 통해 어떤 문서가 답변 생성에 활용됐는지 확인할 수 있다는 점입니다. 각 결과에는 관련성 점수(Relevance Score)도 함께 제공되어, 검색 품질을 평가하고 조정하는 데 활용할 수 있습니다.
📊 검색 품질 최적화
Relevance Score 해석
벡터 검색의 결과는 단순히 문서 목록만 반환하는 것이 아니라 각 문서의 관련성 점수도 함께 제공합니다. 이 점수를 기준으로 품질이 낮은 검색 결과를 필터링할 수 있습니다.
점수 범위 의미 권장 조치
| 0.8 ~ 1.0 | 매우 관련 높음 | 컨텍스트로 그대로 사용 |
| 0.5 ~ 0.8 | 중간 관련 | 추가 검토 또는 프롬프트 보강 |
| 0.3 ~ 0.5 | 낮은 관련 | 청크 크기 조정 또는 임베딩 모델 재검토 |
| 0.0 ~ 0.3 | 거의 무관 | 관련 문서 추가 및 재색인 필요 |
관련성 점수가 낮은 결과를 필터링하는 방법도 있습니다. 먼저 retrieve API로 상위 10개 문서와 점수를 받아온 뒤, 점수가 0.7 이상인 문서만 선별해 LLM에게 컨텍스트로 제공하는 방식입니다. 이렇게 하면 관련 없는 정보가 섞여 들어가는 것을 방지할 수 있습니다.
TopK 튜닝
Knowledge Base에서 가져올 검색 결과 수를 결정하는 파라미터를 TopK라고 합니다. 3개만 가져오면 빠르고 비용이 낮지만 관련 문서를 누락할 수 있습니다. 10개를 가져오면 더 많은 정보를 얻지만 관련 없는 내용이 섞일 가능성이 높아지고 비용도 올라갑니다. 일반적으로 5~7개가 품질과 비용의 균형을 맞추기에 적당합니다.
하이브리드 검색
벡터 검색만 사용할 경우의 약점이 있습니다. "SQL Injection"이라는 정확한 단어가 포함된 문서를 찾을 때 벡터 검색은 의미적으로 유사한 "query injection", "parameterized query" 같은 문서를 잘 찾아내지만, 정확한 단어 매칭에서는 상대적으로 약할 수 있습니다.
하이브리드 검색은 키워드 기반의 BM25 검색과 벡터 기반의 시맨틱 검색을 결합해 두 방식의 장점을 모두 취합니다. "SQL Injection"처럼 정확한 기술 용어가 있는 질문에는 키워드 검색이, "사용자 입력을 쿼리에 직접 넣으면 어떤 문제가 있나요?"처럼 의미 기반 질문에는 시맨틱 검색이 더 효과적으로 동작하도록 두 결과를 결합합니다.
🔒 보안 취약점 검사: RAG가 빛나는 순간
OWASP Top 10과 RAG
OWASP(Open Worldwide Application Security Project)는 매년 가장 위험한 웹 애플리케이션 보안 취약점 10가지를 발표합니다. 이 문서는 정기적으로 업데이트되는데, LLM의 훈련 데이터에는 특정 시점 이후의 최신 OWASP 업데이트가 포함되지 않을 수 있습니다. RAG를 사용하면 최신 OWASP 문서를 Knowledge Base에 추가함으로써 모델이 항상 최신 보안 기준에 따라 코드를 검사하도록 만들 수 있습니다.
SQL Injection은 사용자 입력을 SQL 쿼리에 직접 삽입할 때 발생하는 취약점으로, OWASP A03:2021(Injection)에 해당합니다. query = f"SELECT * FROM users WHERE id = {user_id}" 같은 코드는 user_id에 1 OR '1'='1' 같은 값이 들어오면 인증을 우회할 수 있는 매우 위험한 패턴입니다. RAG를 통해 OWASP 문서를 참조하는 LLM은 이 패턴을 식별하고 "Prepared Statement(파라미터 바인딩)를 사용하라"는 구체적인 수정 제안까지 제시할 수 있습니다.
코드 리뷰에 RAG가 필요한 이유
회사 내부 코딩 컨벤션은 외부에 공개되지 않으므로 LLM이 알 수 없습니다. 회사의 아키텍처 규칙, 특정 라이브러리 사용 지침, 팀 고유의 네이밍 규칙 등을 Knowledge Base에 담아두면 LLM이 이를 참조해 회사 맞춤형 코드 리뷰를 수행할 수 있습니다. 새로운 팀원이 입사했을 때 방대한 사내 문서를 모두 읽지 않아도 AI가 컨벤션 위반을 즉시 알려주는 것입니다.
프레임워크 가이드도 마찬가지입니다. React, Django, Spring 등의 공식 문서와 베스트 프랙티스를 Knowledge Base에 추가하면, LLM이 "이 방식은 React 18에서 deprecated되었으며 대신 이것을 사용하세요"처럼 프레임워크 버전에 맞는 조언을 제공할 수 있습니다.
💰 비용과 성능 최적화
캐싱으로 중복 비용 방지
PR(Pull Request)을 리뷰할 때마다 같은 코드에 대해 반복적으로 RAG 검사를 실행하면 비용이 낭비됩니다. 코드의 MD5 해시를 캐시 키로 사용하고, Redis 같은 캐시 시스템에 결과를 저장해두면 동일한 코드는 두 번째부터 캐시에서 즉시 반환할 수 있습니다. TTL(Time to Live)을 1시간으로 설정하면 코드가 변경됐을 때 자동으로 캐시가 만료되어 새로운 검사가 실행됩니다. 이 방식으로 비용을 약 70% 절감할 수 있다고 알려져 있습니다.
작은 모델의 전략적 활용
단순한 스타일 검사(공백, 들여쓰기 등)는 Claude Haiku 같은 더 작고 저렴한 모델로도 충분히 처리할 수 있습니다. 반면 복잡한 보안 취약점 분석이나 리팩토링 제안에는 Claude Sonnet이나 Claude Opus 같은 강력한 모델이 필요할 수 있습니다. 작업의 복잡도에 따라 모델을 다르게 선택하면 비용을 약 80%까지 절감할 수 있습니다.
월간 비용 예측
500줄 코드 한 번 리뷰에 드는 예상 비용을 분해하면, 임베딩 비용은 약 $0.001, OpenSearch 검색 비용은 약 $0.0005, LLM 입력 비용은 약 $0.005, LLM 출력 비용은 약 $0.01으로 합계 약 $0.0165입니다. 일 100회 리뷰 기준으로 월간 약 $36 정도가 됩니다.
🧩 코드 분석용 프롬프트 설계 패턴
효과적인 코드 분석 프롬프트의 구성
단순히 "코드를 분석해줘"라고 하는 것보다, 분석 항목을 명확하게 지정하고 출력 형식을 구체적으로 제시하면 훨씬 일관되고 활용도 높은 결과를 얻을 수 있습니다.
버그 가능성 분석에서는 논리적 오류(예: 0으로 나누기)와 예외 처리 누락을 중점적으로 검토하도록 지시합니다. 보안 취약점 분석에서는 SQL Injection, XSS, 하드코딩된 비밀번호 등 특정 취약점 유형을 명시합니다. 코드 스타일 검사에서는 PEP8 같은 특정 스타일 가이드를 기준으로 삼도록 지시하고, Cyclomatic Complexity(순환 복잡도) 분석을 추가하면 코드의 유지보수 가능성도 평가할 수 있습니다.
출력 형식을 "### 버그 - [위치] 문제 설명" 같은 구조로 고정하면 결과를 파싱해 다른 시스템(JIRA 티켓 생성, Slack 알림 등)과 연동하기 쉬워집니다.
Cyclomatic Complexity 이해
Cyclomatic Complexity(순환 복잡도)는 코드의 복잡성을 수치로 측정하는 지표입니다. 조건문(if), 반복문(for, while), 예외 처리(try/except) 등 분기가 생기는 지점마다 복잡도가 1씩 올라갑니다. 값이 낮을수록 이해하고 테스트하기 쉬운 코드입니다.
복잡도 평가 의미
| 1 ~ 10 | 좋음 | 구조가 단순하며 쉬운 이해 |
| 11 ~ 20 | 주의 | 복잡도가 높아지고 있어 리팩토링 고려 |
| 21 ~ 50 | 복잡 | 로직이 얽혀 있어 함수 분할 필요 |
| 50+ | 위험 | 완전한 재작성 권장 |
🌐 다중 언어 지원 아키텍처
Knowledge Base에 PEP8(Python), Airbnb Style Guide(JavaScript), Google Java Style Guide(Java), Effective Go(Go) 등의 언어별 스타일 가이드를 모두 저장해두면 단일 시스템으로 여러 언어를 지원할 수 있습니다. 프롬프트에 언어를 명시하면 Knowledge Base가 해당 언어의 가이드라인을 우선적으로 검색해 반환합니다. 메타데이터 필터링을 활용하면 언어별로 다른 Knowledge Base를 쓰는 것보다 효율적으로 관리할 수 있습니다.
💡 이 과정을 통해 배운 것들
이 4장의 여정을 돌아보면 몇 가지 중요한 관찰이 눈에 띕니다.
LLM은 만능이 아닙니다. 훈련 데이터에 없는 최신 정보, 회사 내부 문서, 특정 도메인 지식에서는 한계를 보입니다. RAG는 이 한계를 코드 변경 없이 보완하는 우아한 방법입니다.
프롬프트 엔지니어링은 기술이자 투자입니다. 프롬프트를 잘 설계하는 데 드는 시간은 코드 품질 향상과 일관된 결과라는 형태로 돌아옵니다. "코드 리뷰해줘"와 "실무 경험 10년차 시니어 개발자로서 SQL Injection, XSS, 하드코딩된 비밀번호를 중심으로 다음 코드의 보안 취약점을 찾아주세요"의 결과는 차원이 다릅니다.
비용과 품질은 트레이드오프입니다. 더 강력한 모델, 더 많은 검색 결과, 더 긴 응답은 더 좋은 품질을 제공하지만 비용도 함께 올라갑니다. 캐싱, 적절한 TopK 설정, 작업 복잡도에 따른 모델 선택 등의 최적화를 통해 품질을 유지하면서 비용을 관리하는 것이 실무에서 매우 중요합니다.
RAG는 단순한 검색이 아닙니다. 문서를 어떻게 청킹하고, 어떤 임베딩 모델을 사용하고, 메타데이터를 어떻게 설계하고, 검색 결과를 어떻게 필터링하는지에 따라 시스템의 품질이 크게 달라집니다. RAG는 구현보다 튜닝이 더 중요한 기술입니다.
🔗 참고 자료
'Concepts > Cloud Infra' 카테고리의 다른 글
| Amazon Bedrock Agent를 활용한 AI 에이전트 구축 (0) | 2026.05.25 |
|---|---|
| AWS Bedrock RAG로 코드 스타일 & 보안 검사 자동화 구현하기 (0) | 2026.05.22 |
| AWS 기초개념 : 가상화&EC2 (0) | 2026.05.19 |
| AWS 구성 및 IAM (0) | 2026.05.18 |
| 클라우드 컴퓨팅 (0) | 2026.05.13 |
