전체 프로젝트

디지엠 유닛원 · 실무

CPPM

AWS Bedrock 기반 다중 에이전트 설계·최적화

LG 전 계열사가 사용하는 공통 업무 플랫폼에서 자연어로 업무 데이터를 조회하고 분석 결과를 그래프로 확인하는 챗봇을 개발했습니다. AWS Bedrock Agent POC를 검증한 뒤 일반 대화, 데이터 조회, 그래프 생성을 담당하는 에이전트를 나누고, Lambda·SQS·WebSocket 기반의 비동기 처리와 React 화면을 연결했습니다.

기간
2025.10.01 - 2025.12.31
역할
프론트엔드·풀스택 개발
  • React
  • Next.js
  • TypeScript
  • Jest
  • Cypress
  • Express.js
  • MySQL
  • AWS Bedrock
  • Knowledge Base
  • OpenSearch
  • Lambda
  • SQS
  • DynamoDB
  • S3
  • API Gateway WebSocket
  • CloudWatch
  • IAM
Master·Supervisor·OpenSearch·Graph Agent와 Knowledge Base를 연결한 다중 에이전트 아키텍처
AWS Bedrock Agent 기반 다중 에이전트 아키텍처

CASE 01

다중 에이전트 아키텍처 설계 및 최적화

상황

초기 단일 Bedrock Agent에 일반 대화, 데이터 조회, 그래프 생성과 계열사별 업무 규칙이 집중되어 프롬프트 복잡도와 유지보수 비용이 증가했습니다. Trace·CloudWatch에서 검색 실패 뒤 기간 확장과 유사어 재검색이 반복되고, 검색 결과 없이 답변을 생성하는 흐름을 확인했습니다.

판단

AWS Bedrock Agent를 활용해 요청 분류와 작업 조율, 데이터 조회와 시각화의 책임을 나눴습니다. 업무 지식은 프롬프트에 모두 넣지 않고 필요한 시점에 Knowledge Base에서 조회하도록 구성했습니다.

구현

  • Master Agent가 일반 대화와 데이터 분석 요청을 분류하고, Supervisor Agent가 OpenSearch Agent와 Graph Agent의 작업을 조율하도록 설계했습니다.
  • 일반 대화는 전용 Chat Agent로 보내 분석 경로를 거치지 않도록 했습니다. Master는 하위 Agent의 응답을 임의로 재해석하거나 다른 Agent로 재라우팅하지 않고 전달하도록 지침을 구성했습니다.
  • 분석 유형은 Use Case Knowledge Base, 계열사별 필드·파생 지표·차트 규칙은 계열사별 Knowledge Base로 분리했습니다.
  • has_results=false 또는 hits.total.value=0이면 결과 없음으로 종료하고 후속 Graph Agent 호출을 중단하도록 했습니다. HTTP 500 오류는 재검색으로 해결하려 하지 않고 오류를 전달하도록 종료 규칙을 구성했습니다.
  • Graph Agent는 조회 결과와 차트 규칙을 받아 그래프 생성 코드를 만들고, Action Group의 Lambda 실행·S3 저장을 거쳐 이미지 URL을 반환하도록 연결했습니다.

결과

Agent별 프롬프트·Action Group·Knowledge Base를 독립적으로 관리하고 불필요한 재검색과 후속 호출을 제어했습니다. Agent 역할과 프롬프트 최적화로 평균 응답 시간을 약 2분에서 1분~1분 30초로 단축했습니다.

응답시간은 당시 프로젝트의 개선 기록입니다. Trace UI의 대기 경험 개선과는 별도의 성과입니다.

Master·Chat·Supervisor·OpenSearch·Graph Agent와 Knowledge Base를 분리한 설계 구조
Master·Chat·Supervisor·OpenSearch·Graph Agent와 Knowledge Base를 분리한 설계 구조

CASE 02

장시간 Agent 응답에 대한 사용자 대기 경험 개선

상황

OpenSearch 조회와 데이터 기반 그래프 생성은 System Prompt·Knowledge Base·Agent 협업·코드 생성·S3 업로드를 거치며 1분~2분 이상 걸렸습니다. 화면에는 로딩만 표시돼 사용자가 진행 중인지 오류인지 알기 어려웠습니다.

판단

Agent가 어떤 작업을 수행하는지 사용자에게 전달하고, 이벤트 수신·배포를 컴포넌트의 생명주기와 분리했습니다.

구현

  • PreProcessingTrace와 OrchestrationTrace에서 입력 분류·Knowledge Base 조회·Action Group 호출 상태를 추출했습니다.
  • Trace를 사용자 친화적인 처리 단계로 변환해 WebSocket으로 실시간 전달했습니다.
  • 싱글톤 AgentEventManager가 WebSocket 수신과 이벤트 배포를 담당하고 컴포넌트는 구독·해제하도록 구현했습니다.

결과

응답 속도를 억지로 줄이는 대신 처리 단계를 보여줘 체감 대기 시간을 줄였습니다. 이벤트 관리를 분리해 리스너 중복 등록을 방지하고 일관된 상태를 유지했습니다.

AgentEventManager 싱글톤과 컴포넌트 생명주기 분리

WebSocket 수신은 AgentEventManager 한 곳에서 담당하고, 화면 컴포넌트는 필요한 이벤트를 구독한 뒤 언마운트 시 해제하도록 구성한 구조 발췌입니다. 실제 사내 코드의 식별자와 전송 형식은 제거·축약했습니다.

type AgentEvent = { type: string; message: string };
type Listener = (event: AgentEvent) => void;

class AgentEventManager {
  private static instance: AgentEventManager;
  private listeners = new Set<Listener>();
  private socket?: WebSocket;

  private constructor() {}

  static getInstance() {
    return (this.instance ??= new AgentEventManager());
  }

  connect(socket: WebSocket) {
    if (this.socket === socket) return;
    this.socket = socket;
    socket.addEventListener("message", this.handleMessage);
  }

  subscribe(listener: Listener) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  private handleMessage = (message: MessageEvent<string>) => {
    const event = JSON.parse(message.data) as AgentEvent;
    this.listeners.forEach((listener) => listener(event));
  };
}

const agentEventManager = AgentEventManager.getInstance();

// React 컴포넌트에서는 구독과 해제만 담당
useEffect(() => {
  const unsubscribe = agentEventManager.subscribe(setAgentEvent);
  return unsubscribe;
}, []);

구조 발췌 · 수신 계층과 React 구독 계층을 분리

CASE 03

API Gateway 타임아웃 해결 · SQS 기반 비동기 구조 분리

상황

Bedrock Agent의 데이터 조회·그래프 생성은 1~2분이 걸렸습니다. 요청 Lambda가 최종 결과를 기다리면서 API Gateway WebSocket 통합 요청이 먼저 타임아웃됐습니다.

판단

장시간 작업을 요청 수신과 분리했습니다. 연결 식별자를 작업과 함께 전달하고, 결과는 기존 WebSocket 연결로 돌려주는 구조를 구성했습니다.

구현

  • 요청 Lambda가 connectionId와 요청 정보를 SQS에 적재한 뒤 즉시 반환하도록 구성했습니다.
  • Event Source Mapping으로 호출되는 Worker Lambda가 Bedrock·OpenSearch·그래프 생성 작업을 수행하도록 분리했습니다.
  • Worker가 API Gateway Management API의 PostToConnection으로 응답과 Trace를 전달하고, 화면에는 사용자 친화적인 처리 단계를 표시했습니다.

결과

요청 수신과 장시간 작업을 분리해 통합 타임아웃 문제를 해소하고, 사용자가 정상 진행 여부를 확인할 수 있는 흐름을 만들었습니다.

요청 수신 Lambda와 Worker Lambda를 SQS로 분리한 전후 구조
요청 수신 Lambda와 Worker Lambda를 SQS로 분리한 전후 구조

CASE 04

채팅 검색 및 탐색 경험 최적화

상황

화면에 로드되지 않은 이전 메시지는 브라우저 기본 검색으로 찾을 수 없어 채널과 대화를 직접 탐색해야 했습니다.

판단

검색 결과를 보여주는 데서 끝내지 않고 해당 채널·메시지로 이동하는 전체 흐름을 제안하고 구현했습니다.

구현

  • ERD·REST API와 React 검색 화면, 검색어 하이라이팅을 구현했습니다.
  • 300ms debounce와 LastEvaluatedKey 기반 20개 단위 페이지네이션으로 요청과 응답 범위를 제한했습니다.
  • 채널 이동 후 requestAnimationFrame에서 DOM을 조회해 해당 메시지 위치로 스크롤하도록 구현했습니다.

결과

이전 대화를 검색하고 해당 메시지의 맥락으로 돌아가는 탐색 흐름을 완성했습니다.

ARCHITECTURE & PROMPTS

역할 분리와 종료 조건을 프롬프트에 명시

Bedrock의 Agent 협업 기능을 사용하되, 어떤 요청을 누구에게 보내고 언제 멈출지는 직접 설계했습니다. 아래는 업무 기록에 남긴 Master·Supervisor 프롬프트의 일부입니다.

  • Master는 일반 대화와 분석 요청을 분류하고, 일반 대화는 Chat Agent에 바로 전달하도록 했습니다.
  • Supervisor는 Use Case KB에서 분석 방법을, 계열사별 KB에서 필드·파생 지표·차트 규칙을 찾아 OpenSearch·Graph Agent에 전달하도록 했습니다.
  • 검색 결과가 없거나 서버 오류가 발생하면 후속 그래프 생성과 불필요한 재호출을 멈추도록 응답 처리 규칙을 작성했습니다.

Master: 요청 분류와 응답 전달

일반 대화가 분석 Agent까지 거치지 않도록 경로를 나눴습니다. 하위 Agent가 결과를 반환한 뒤 Master가 다시 판단해 다른 Agent를 호출하지 않도록 제한했습니다.

## 라우팅 규칙

- **일반 대화** (인사, 질문, 시스템 기능 안내 등) → CppmChatAgent
- **데이터 검색/분석/그래프 생성** → CppmSupervisorAgent

## 응답 전달 (매우 중요 - 절대 규칙)

- 하위 에이전트(CppmChatAgent, CppmSupervisorAgent)의 응답을 **반드시 그대로** 사용자에게 전달합니다.
- 하위 에이전트의 응답이 만족스럽지 않아도 그대로 전달합니다. 절대 다른 에이전트로 다시 라우팅하지 않습니다.
Master Agent 프롬프트 · 원문 발췌

Supervisor: 검색 결과에 따른 종료

빈 결과와 서버 오류를 구분해 사용자에게 알리도록 했습니다. 특히 HTTP 500 응답을 받은 Supervisor가 검색을 다시 지시하지 않도록 종료 조건을 명시했습니다.

CppmOpenSearchAgent 응답 처리 규칙:
- CppmOpenSearchAgent가 검색 결과를 반환했을 때:
  - has_results가 false이거나 hits.total.value가 0인 경우: 검색 결과가 없는 것이므로, 사용자에게 "검색 결과가 없습니다"라고 명확히 전달하고 FINISH로 종료합니다.
  - error 필드가 있는 경우:
    * 에러 메시지에 "500", "service unavailable", "Internal Server Error"가 포함된 경우: "일시적 오류가 발생했습니다. 잠시 후 다시 시도해주세요"라고 사용자에게 전달하고 FINISH로 종료합니다.
    * 그 외의 에러: 에러 메시지를 사용자에게 전달하고 FINISH로 종료합니다.
  - 정상적인 검색 결과가 있는 경우: 다음 단계(예: CppmGraphAgent)로 진행하거나 FINISH로 종료합니다.
Supervisor Agent 프롬프트 · 원문 발췌