AWS 기술 블로그
아이지에이웍스 AI 에이전트 클레어: Amazon Bedrock 기반 Text-to-SQL/Chart 에이전트로 이룬 데이터 분석 혁신 (Part 2)
1부에서 살펴본 클레어의 비즈니스 가치와 전체 아키텍처를 바탕으로, 이번 편에서는 각 컴포넌트의 기술적 구현 방식과 최적화 전략을 자세히 공유하고자 합니다.
멀티 에이전트 구현
1부에서 설명한대로, 클레어의 개별 Agent들의 흐름은 다음과 같습니다.
Question Agent(질문 분석 및 정제, 분할) → Query Agent(SQL 생성 및 쿼리 실행) → Answer Agent(데이터 기반 답변)
각 에이전트는 독립적이면서도 유기적으로 연결되어 있습니다.
1. Question Agent: 질문 분석과 정제의 전문가
Question Agent는 모호성 분석 → 해소 → 질문 분할의 3단계를 통해 사용자 질문을 SQL 쿼리 생성에 최적화된 형태로 변환합니다. 사용자의 모호한 질문을 분석하고 해소하여 실행 가능한 구체적인 질의로 정제·분할하는 것이 주요 역할입니다.
자연어 질문의 모호함 분석
대부분의 자연어 질문들은 SQL 쿼리로 변환하기에 모호한 부분들이 존재했습니다. 하지만 모든 모호함에 대해 되묻는 것은 사용자 경험을 해칠 수 있습니다. 저희는 이러한 모호성을 두 가지 유형으로 분류하여 처리합니다.
- 자연스러운 모호성: 업계의 표준이나 보편적인 상식에 기반해 가장 가능성 높은 해석이 존재하는 경우입니다. 예를 들어, “이번 달 매출액 보여줘”라는 질문에서 ‘매출액’은 합계(SUM)나 평균(AVG) 등으로 해석될 수 있지만, 비즈니스 맥락상 일반적으로 ‘총매출액(SUM)’을 의미합니다. 이처럼 AI 에이전트가 자율적으로 판단할 수 있는 모호성은 가장 확률 높은 해석을 채택하여 자동으로 처리합니다.
- 치명적인 모호성: 여러 갈래로 해석될 수 있어 AI가 임의로 판단할 경우, 사용자의 의도와 전혀 다른 결과를 제공할 위험이 큰 경우입니다. 가령 “성과가 좋은 고객을 분석해줘”라는 요청에서 ‘성과’의 기준은 매출액, 구매 빈도, 충성도 등 다양하며, 어떤 기준을 택하느냐에 따라 분석 결과가 크게 달라집니다. 이처럼 결과에 결정적인 영향을 미치는 모호함은 “‘성과가 좋은 고객’의 기준을 더 구체적으로 정의해주세요”와 같이 사용자에게 질문하여 의도를 명확히 하는 과정을 거칩니다.

모호함 해소 과정에서의 Human In the Loop
Question Agent는 구현 과정에서 모호함 해소가 필요한 경우에 Human-In-the-Loop(HITL) 방식으로 사용자 답변을 기다리도록 하였습니다.
LangGraph 워크플로우는 기본적으로 메모리 내에서 상태(State)를 관리합니다. 하지만 사용자의 답변은 수초에서 길게는 수 시간 뒤에 돌아올 수 있습니다. 만약 서버가 재시작되거나, 여러 서버 인스턴스가 요청을 처리하는 분산 환경이라면 메모리 내의 상태는 쉽게 유실됩니다.
저희는 이 문제를 해결하기 위해 LangGraph가 제공하는 체크포인트(Checkpoint) 기능을 활용하여 워크플로우의 상태를 외부 스토리지에 영속화했습니다. ‘치명적인 모호성’으로 인해 사용자 응답을 기다리며 워크플로우가 중단되면, 그 시점의 상태(State)가 데이터베이스에 저장됩니다.
이때 각 상태는 고유한 thread_id와 함께 저장되므로, 나중에 사용자의 응답이 들어왔을 때 어떤 워크플로우를 재개해야 할지 식별할 수 있습니다.
사용자가 답변을 제출하면, Question Agent는 해당 thread_id를 사용해 데이터베이스에서 이전 상태를 조회하여 로드한 후, 중단되었던 바로 그 지점부터 워크플로우를 재개합니다. 이를 통해 사용자는 마치 하나의 끊김 없는 대화처럼 자연스럽게 AI와 상호작용을 이어갈 수 있습니다.
물론 모든 Agent의 상태를 데이터베이스에 저장하는 것은 아닙니다. 중단될 일이 없는 에이전트의 경우, 매번 체크포인트를 저장하는 것은 불필요한 I/O 오버헤드를 유발할 수 있습니다. 따라서 저희는 HITL이 필요한 Agent에 대해서만 선택적으로 상태 영속화를 적용하여, 시스템의 전체적인 응답 속도와 효율성을 높였습니다.
정제된 질문 분할
위와 같은 과정을 거쳐 모호함이 해소된 질문이라 해도 여전히 문제가 존재합니다. 사용자의 질문이 “매출 분석해줘”처럼 포괄적인 요청일 경우, 단순히 총합만 계산하는 것은 사용자가 기대하는 분석의 깊이나 범위와 맞지 않을 수 있습니다. 실제 데이터 분석가라면 매출에 대해 다각도로 접근하여 시간별 추이, 제품별 비교, 지역별 분포 등 여러 관점에서 종합적으로 분석할 것입니다. 이러한 간극을 해결하기 위해 Question Agent는 질문 분할 단계를 통해 사용자의 포괄적인 질문을 여러 개의 구체적인 분석 질의로 자동 변환합니다.
- 분할 필요성 판단
먼저 입력된 질문에 다차원적 분석이나 복합적인 의도가 포함되어 있는지 탐지합니다. 이를 통해 단일 질문으로 처리할지, 아니면 여러 세부 질문으로 나눌지 결정합니다. - 분석 의도 구조화
질문분할이 필요하다고 판단되면 데이터 분석가의 사고 방식을 모방하여 사용자의 포괄적 요청을 체계적인 분석 관점들로 분해합니다. 이때 비즈니스 중요도에 따라 분석의 우선순위를 정합니다. - 언어 정제 및 구체화
분할된 각 질문에서 모호하거나 추상적인 표현을 SQL 쿼리로 변환 가능한 명확하고 구체적인 용어로 치환합니다. 특히 “분석”과 같은 포괄적 용어를 실행 가능한 동작어로 변환합니다. - 개별 질문의 독립성 확보
마지막으로 분할된 모든 개별 질문이 원래 질문의 핵심 맥락과 조건을 완전히 포함하도록 보장합니다. 이를 통해 각 질문이 독립적으로 실행되더라도, 전체적인 일관성을 유지하며 의미 있는 결과를 도출할 수 있습니다.
이러한 논리적 흐름을 통해 “매출 분석해줘”와 같은 포괄적 요청이 체계적이고 실행 가능한 여러 개의 구체적 질의로 변환되어 사용자가 기대하는 종합적인 분석 결과를 제공할 수 있습니다.
분할된 질문은 각각 병렬적으로 Query Agent에 전달됩니다.

2. Query Agent: 안전하고 정확한 SQL 생성의 전문가
Query Agent는 Question Agent로부터 정제된 질문을 전달받아 SQL을 생성하고 실행하는 핵심 컴포넌트입니다.

Query Agent는 위에서 정제된 질문에 대하여 SQL을 생성하여 실행합니다.
스키마, 샘플 쿼리, 비즈니스 용어 매핑 및 쿼리 생성
LLM이 정확한 SQL을 만들기 위해서는 다양한 Context들이 필요합니다. 저희는 RAG(Retrieval-Augmented Generation)을 적용하여 필요한 Context들을 LLM에 주입해주었습니다.
- 관련 테이블/스키마 검색: 사용자의 질문을 임베딩하여 미리 임베딩된 테이블 설명 정보와 비교함으로써 가장 관련성 높은 테이블을 추려냅니다. 이렇게 하면 핵심 관련 테이블 스키마 정보에만 집중하여 SQL을 생성할 수 있습니다.
- 유사 샘플 쿼리 검색: 성공적으로 실행되었던 과거의 (질문, SQL) 쌍을 샘플 쿼리로 저장해 둡니다. 새로운 질문이 들어왔을 때 가장 유사한 샘플 쿼리를 찾아 SQL 생성 단계에서 힌트로 제공합니다. 이는 LLM이 더 빠르고 정확하게 쿼리를 생성하도록 돕는 Few-shot Learning의 역할을 합니다.
- 표준화된 비즈니스 용어 매핑: 고객사마다 동일한 개념을 다른 용어로 표현하는 경우가 많습니다. 시스템이 사용자의 자연어 질문에서 비즈니스 용어를 추출하여 표준화된 정의로 매핑합니다. 예를 들어, “신규 유저”, “새로운 회원”, “첫 가입자” 등의 다양한 표현을 모두 “회원가입이 발생한 사용자”라는 명확한 정의로 변환합니다. 이를 통해 LLM은 일관된 기준으로 정확한 SQL 조건문을 생성할 수 있습니다.
멀티 테넌트 애플리케이션을 위한 데이터 격리
멀티 테넌트 아키텍처에서 이 기능을 구현하기 위한 필수 전제 조건은 LLM이 생성한 쿼리가 다른 고객사의 데이터에 접근하지 못하도록 실행 범위를 엄격히 제한하는 것이었습니다.
하지만 LLM이 SQL을 생성하는 과정 자체의 위험을 고려해야 했습니다. 예를 들어, LLM이 쿼리 작성을 위해 스키마 정보를 참조할 때 다른 고객사의 테이블 구조(메타데이터)를 보게 되거나, 연관 테이블을 탐색하며 권한 밖의 정보를 인지할 수 있었습니다.
이처럼 쿼리 실행 전 단계에서 발생하는 정보 노출을 원천 차단하기 위해 여러 단계의 정교한 격리 방안이 필요했습니다.
- 연관 테이블 조회 시 권한 검증: 분석 과정에서 연관 테이블을 조회할 때는 해당 고객사가 권한을 가지고 있는 테이블 리스트와 대조하여 유효성을 검증합니다. 이를 통해 권한이 없는 테이블에 대한 접근을 원천 차단하여 데이터 격리를 강화했습니다.
- OpenSearch 인덱스별 스키마 정보 격리 (RAG): LLM이 참조하는 테이블 스키마 정보 또한 격리가 필요했습니다. Amazon OpenSearch Service를 RAG의 벡터 저장소로 활용하면서 고객사별로 독립된 인덱스를 생성하여 각각의 메타데이터를 별도로 관리했습니다. 사용자가 질문을 하면 해당 고객사의 전용 인덱스에서만 관련 스키마 정보를 검색하기 때문에 LLM이 답변을 생성할 때 다른 고객사의 데이터베이스 구조나 테이블 정보에 대해서는 전혀 알 수 없도록 구현했습니다.
3. Answer Agent: 데이터 시각화와 인사이트 전달의 전문가
Query Agent가 생성한 SQL 실행 결과를 사용자가 직관적으로 이해할 수 있도록 변환하는 것이 Answer Agent의 핵심 역할입니다.
Text-to-Chart
데이터를 텍스트가 아닌 차트로 시각화할 때 사용자는 더 빠르고 직관적인 인사이트를 얻을 수 있습니다.
기존에는 데이터를 차트로 시각화하기 위해 개발자가 데이터의 특성을 직접 분석하고 프론트엔드에서 차트 라이브러리 코드를 일일이 작성해야 했습니다. 이 방식은 데이터의 종류나 분석 목적이 바뀔 때마다 유연하게 대응하기 어려웠습니다.
이러한 문제를 해결하기 위해 생성형 AI로 사용자 질문의 의도와 데이터베이스 조회 결과를 함께 분석하여 데이터를 가장 효과적으로 표현할 수 있는 차트 유형을 선택하고 이를 Highcharts 라이브러리에서 렌더링 가능한 JSON 설정값으로 생성하는 기능을 구현했습니다.
프론트엔드는 생성된 JSON 설정값을 바로 활용하여 별도의 복잡한 시각화 로직 구현 없이도 차트를 렌더링할 수 있게 되었습니다.

LLM 및 프롬프트 관리
AI Agent 개발에서 LLM과 프롬프트 관리는 시스템의 성능과 유지보수성을 결정짓는 핵심 요소입니다. 이번 섹션에서는 클레어 개발 과정에서 습득한 Amazon Bedrock을 활용한 프롬프트 관리 노하우를 공유하고자 합니다.
1. 프롬프트 버전 관리
개발 초기 저희는 YAML 파일을 통해 프롬프트를 관리했습니다. 하지만 이 방식은 프롬프트의 잦은 수정과 테스트가 필요한 특성을 고려하지 못한 방식이었습니다.
기존 방식의 문제점
- 어려운 이력 관리: 잦은 프롬프트 수정으로 인해 어떤 버전이 더 좋은 성능을 냈는지 추적하고 비교하기가 어려웠습니다.
- 느린 반영 속도: 작은 문구 하나를 수정해도 매번 코드 배포 파이프라인을 거쳐야만 실제 시스템에 반영할 수 있었습니다.
이러한 비효율을 해결하기 위해 저희는 Amazon Bedrock Prompt Management 기능을 도입했습니다.
Bedrock Prompt Management의 버전 관리 체계 이해
도입 과정에서 Bedrock Prompt Manager의 버전 체계를 이해하는 것이 중요했습니다.
- DRAFT 버전 생성 순서
프롬프트 수정 → DRAFT 상태 저장 → 버전 생성 → 1, 2, 3... 순차 번호 부여 - DRAFT 특성: 모든 변경사항은 먼저 DRAFT로 저장되며, 명시적으로 버전을 생성하지 않으면 다음 수정 시 덮어쓰여집니다.
- API 조회 방식:
get-promptAPI는 태그가 아닌 정확한 버전 번호로만 프롬프트를 조회할 수 있어 최신 버전을 동적으로 가져오려면 별도 로직이 필요합니다.
초기에는 하나의 프롬프트를 두고 개발 환경에서는 DRAFT를, 운영 환경에서는 최신 버전을 사용하는 방식을 고려했습니다. 하지만 이 방식은 개발 중인 DRAFT가 계속 덮어써지고 최신 버전을 확인하기 위한 추가 작업이 필요하다는 단점이 있었습니다.
이 문제를 해결하기 위해 저희는 개발 환경과 운영 환경의 프롬프트를 완전히 분리하여 관리하는 방식을 채택했습니다.
- 개발 환경 (dev-prompt): DRAFT 상태에서는 프롬프트를 자유롭게 수정하며 빠른 테스트를 진행합니다. 의미 있는 개선이나 변경 사항이 있을 때마다 개발 환경 내에서 버전을 생성하여 변경 이력을 명확히 추적하고 필요시 안정적인 이전 버전으로 롤백할 수 있는 안전장치를 마련하였습니다.
- 운영 환경 (prod-prompt): 오직 검증이 완료된 안정적인 버전만
dev-prompt에서 복사하여 새로운 숫자 버전으로 생성합니다. 애플리케이션은 이 고정된 버전 번호를 바라보게 하여 안정성을 확보합니다.
이 방식을 통해 저희는 개발의 유연성과 이력 추적, 운영의 안정성이라는 성과를 모두 거둘 수 있었습니다.
2. 프롬프트 캐싱
클레어는 각 사용자 질의를 처리할 때마다 시스템 프롬프트 내 정의된 AI의 역할 및 행동지침, SQL 작성 가이드라인, 데이터 분석 방법론 등 상당히 많은 양의 컨텍스트 정보를 프롬프트에 포함해야 합니다. 이러한 정보들은 대화 도중 거의 변경되지 않는 정적인 특성을 가지고 있음에도, 매 요청마다 반복적으로 처리되어 불필요한 토큰 소비와 지연을 발생시켰습니다. 이 문제를 해결하기 위해 Amazon Bedrock의 프롬프트 캐싱 기능을 도입했습니다.
최적의 비용 효율을 위한 캐싱 운영 전략
- 콘텐츠 분리: 재사용 빈도가 높은 정적 정보(시스템 프롬프트 등)는 프롬프트 상단에 배치하고 CachePoints를 지정하여 캐시 대상으로 만들었습니다. 반면 사용자 질문이나 세션 컨텍스트처럼 매번 달라지는 동적 정보는 캐시 대상에서 제외하여 정적 캐시의 재사용성을 높였습니다.
- 최소 토큰 요구사항 충족: 캐시를 유효하게 생성하려면 모델별 최소 토큰 요구사항을 충족해야 합니다. 저희가 사용하는 Claude 3.7 Sonnet 모델의 경우 캐시 포인트당 최소 1,024 토큰이 필요했으며 이 기준에 맞춰 정적 컨텍스트의 길이를 조절했습니다.
- 캐시 유효 시간과 제약사항: 프롬프트 캐시는 5분간만 유지되며 캐시 히트 시 유효 시간이 다시 연장됩니다. 하지만 5분 이상의 간격으로 드물게 호출되는 기능에 캐싱을 적용하면 캐시가 만료되어 재사용 없이 쓰기 비용만 불필요하게 발생할 수 있습니다.
- 선별적 적용을 통한 최적화: 이러한 제약사항을 고려하여 각 기능별 호출 빈도를 면밀히 분석했습니다. 그 결과 호출이 잦은 핵심 기능에만 캐싱을 선별적으로 적용하여 불필요한 비용을 최소화하고 캐시 히트 성공률을 높이는 운영 전략을 수립했습니다.
3. 프롬프트 최적화
클레어의 핵심 기능인 자연어 → SQL 변환의 정확도를 높이기 위해 다양한 프롬프트 엔지니어링 기법을 적용했습니다. 이 섹션에서는 주요 프롬프트 최적화 전략과 그 효과를 공유하고자 합니다.
1. 역할 기반 프롬프팅
LLM에게 명확한 전문가 역할을 부여하여 해당 영역의 전문 지식과 사고 패턴을 활성화하도록 유도했습니다.
당신은 자연어 쿼리를 AWS Redshift SQL로 변환하는 데 특화된 AI 어시스턴트입니다.
당신은 대화 맥락을 분석하여 사용자의 실제 의도를 파악하는 전문가입니다.
2. Chain of Thought 프롬프팅
복잡한 SQL 생성 전에 <thinking> 태그를 활용해 모델이 사고 과정을 단계별로 먼저 작성하도록 지시했습니다. 모델이 내부적으로만 생각하게 하는 것보다 추론 과정을 직접 출력하게 만들어야 성능이 향상되며 출력 결과에 포함된 답변의 근거를 통해 디버깅에도 큰 도움이 되었고 결과의 일관성을 확보하는 데에도 효과적이었습니다.
답변하기 전에 핵심 사고 과정을 <thinking> 태그 안에 정리해 주세요.
3. 구조화된 지시사항
전체 작업 과정을 <instructions> XML 태그로 감싸고 1. → a. → • 형태의 계층적 구조로 단계별 지시를 명시했습니다. 이를 통해 LLM이 지시의 우선순위와 범위를 정확히 인식하도록 했습니다.
<instructions>
1. 기간 조회 표준화
a. 기간 키워드 식별:
- 명시적: "오늘", "어제", "이번 주", "지난 달", "최근 N일"
2. 비즈니스 용어 표준화
a. 도메인 용어 통일:
- 고객 분류: 신규/기존/VIP/활성 고객 등
.....
</instructions>
4. 명확한 제약 조건
명확한 제약 조건을 설정하여 LLM의 자유로운 해석을 제한하고 결과의 예측 가능성을 확보했습니다.
데이터 품질 및 형식 제약:
- 모든 숫자 값은 소수점 2자리까지 반올림하여 표시
- 날짜 형식은 YYYY-MM-DD 표준 준수
- NULL 값 처리 시 COALESCE 함수 필수 사용
응답 범위 제약:
- 사용자가 요청하지 않은 추가 분석 제안 금지
- 데이터에 존재하지 않는 컬럼 기반 대안 제시 금지
5. 다단계 검증 프로세스
LLM이 생성한 결과물에 대해 구조, 내용, 실행 가능성을 체크하는 검증 단계를 프롬프트 내에 포함시켜 할루시네이션을 최소화하였습니다.
검증 및 최종 확인:
a. 구조 검증: 질의 개수가 의도 분석 결과와 일치하는가?
b. 내용 검증: 시계열 질의에 모두 구체적 날짜 범위가 포함되었는가?
c. 비교 분석 검증: 비교 키워드가 완전히 제거되고 개별 질의로 분할되었는가?
d. 실행 가능성 검증: 핵심 지표 측정이 스키마에서 직접 가능한가?
이러한 프롬프트 엔지니어링을 통해 클레어는 복잡한 비즈니스 질의를 정확하고 효율적인 SQL로 변환할 수 있게 되었으며 데이터 분석 정확도와 신뢰도를 향상시킬 수 있었습니다.
클레어의 향후 계획
디파이너리는 클레어를 통해 데이터 분석의 민주화라는 첫 걸음을 내디뎠습니다. 앞으로 더 많은 사용자들이 더 똑똑하고 빠른 의사 결정을 내릴 수있도록 지속적으로 진화할 예정입니다.
- 고도화된 분석 기능 확장
다단계 분석과 복잡한 퍼널 분석 같은 고급 분석 기능을 자연어 대화 방식으로 제공할 예정입니다. 여러 대화에 걸친 맥락을 깊이 있게 이해하여, 더욱 정교한 분석이 가능하도록 개선하겠습니다. - 의도 파악 엔진 고도화
사용자 질문의 잠재적 모호성을 해소하기 위한 의도 명료화(Intent Clarification) 단계를 도입할 계획입니다. 이러한 대화형 의도 구체화를 통해 분석의 정확성을 한층 높이겠습니다.예시 시나리오: 사용자: "상위 유저 분석해줘" 클레어: "어떤 기준으로 분석할까요?" "1) 매출액 기준" "2) 방문 빈도 기준" - 내부 데이터 연동을 위한 MCP(Model Context Protocol) 서버 구축
운영 데이터와의 실시간 연동을 위해 통합 API 관리 시스템과 Model Context Protocol(MCP) 서버를 구축할 예정입니다. 이를 통해 CRM 캠페인 운영정보를 실시간으로도 조회에 분석에 활용할 계획입니다. - LLM 평가 및 테스트 환경 구축
체계적인 에이전트 성능 관리와 비용 효율화를 위해 자체 평가 데이터셋과 자동화된 테스트 환경을 구현할 예정입니다.
Amazon Bedrock과 함께한 디파이너리의 AI 혁신 여정은 이제 시작입니다. 앞으로도 데이터와 AI 기술을 통해 모든 사람이 데이터의 가치를 온전히 누릴 수 있도록 최선을 다하겠습니다.