LLM · SSE Streaming · Widget System

AI Agent

LLM 기반 대화형 에이전트

01

5-Layer 아키텍처

Data Flow Through Layers

사용자의 메시지부터 LLM 응답까지, 데이터는 5개 레이어를 거쳐 흐른다.

1

React Components

Composer, Message, Widget UI

2

Chat Runtime & State

런타임 훅, Zustand 상태 관리

3

API Client & Interceptors

HTTP 클라이언트, 인증 토큰 자동 주입

4

BFF Proxy

Next.js 서버, SSE 패스스루, 쿠키 → 헤더 변환

5

Backend + LLM

채팅 API 서버, LLM 스트리밍 응답

02

SSE 스트리밍 파이프라인

Real-time Streaming

LLM 응답은 SSE(Server-Sent Events)로 실시간 스트리밍된다. 프론트엔드는 5단계 파이프라인으로 바이트 스트림을 파싱하고 화면에 한 글자씩 렌더링한다.

1

SSE 연결

fetch()로 POST 요청, 응답 body를 ReadableStream으로 수신. AbortController로 취소 가능.

2

Async Generator 파싱

바이트 스트림 → 줄 단위 분리 → data:{...} JSON 파싱 → yield로 이벤트를 하나씩 방출.

3

누적 & 콜백

delta 텍스트를 배열에 누적하며 onDelta(누적값) 호출. done:true이면 onComplete(전체) 트리거.

4

requestAnimationFrame

각 delta 사이에 브라우저 렌더링 타이밍을 확보하여 부드러운 타이핑 효과 구현.

5

React 상태 반영

setState(accumulated) → 리렌더링 → 화면에 한 글자씩 나타남.

SSE 이벤트 포맷

event: message
data: { "type": "delta", "content": "안녕하" }
data: { "type": "delta", "content": "세요" }
data: { "type": "done", "content": "안녕하세요" }
03

위젯 시스템

Dynamic UI from Backend JSON

일반 채팅은 텍스트만 보여주지만, AI 에이전트는 백엔드가 JSON으로 "어떤 UI를 그려라"라고 지시한다. 프론트엔드는 URI 기반으로 어떤 컴포넌트를 렌더링할지 자동 분기한다.

백엔드가 보내는 위젯 데이터

{
  "type": "widget",
  "widget": {
    "uri": "ui://agent/ambiguous-term-clarification",
    "title": "직무 선택",
    "options": [{ "value": "001", "label": "프로듀서" }, ...]
  }
}

URI → 컴포넌트 라우팅

백엔드 JSON

uri: ui://...

URI 파서

path 추출

Widget Router

컴포넌트 매핑

위젯 렌더링

동적 UI

위젯 종류

선택지 명확화

라디오 버튼 N개 중 1개 선택 → 서버 전송

검색 조건 미리보기

LLM이 파싱한 조건 확인 후 검색 실행

프로필 카드

인물 정보 카드 렌더링 + 상세보기 액션

추천 목록

리스트 형태의 추천 결과 표시

워크플로우 상태

백엔드 처리 진행률 실시간 표시

공지 배너

시스템 공지·안내 메시지 표시

04

위젯 액션 플로우

User Interaction → Backend

사용자가 위젯에서 선택을 하면, 그 데이터는 ActionConfig 객체로 포장되어 handler 타입(server / client)에 따라 분기 처리된다.

1

사용자 인터랙션

  • 위젯 UI에서 라디오 버튼 / 버튼 클릭
  • 폼 상태 업데이트 (react-hook-form)
  • "확인" 버튼 → handleSubmit() 트리거
2

ActionConfig 생성 & onAction 호출

  • type: 액션 종류 (예: select, search, confirm)
  • handler: 'server' 또는 'client'
  • payload: 사용자가 선택한 데이터
  • 중복 제출 방지 Lock 활성화
3

백엔드 전송 & LLM 응답

  • server handler → API 전송 → LLM이 결과 처리
  • client handler → 프론트에서 직접 처리 (링크 복사 등)
  • 응답 수신 → 새 메시지 / 위젯으로 대화 계속

ActionConfig 구조

onAction({
  type: 'ambiguous_term.select',
  handler: 'server', // 또는 'client'
  payload: { selectedCode: '001' }
})
05

Client Tools

Backend → Frontend Function Call

위젯과 별개로, 백엔드가 tool_call 이벤트로 프론트엔드 함수를 직접 호출할 수 있다. 사용자 인터랙션 후 결과를 tool_result로 돌려주는 양방향 통신 구조.

구분WidgetClient Tool
전달 방식메시지 content 안에 포함tool_call 이벤트로 호출
렌더링URI 기반 라우팅render() 함수 직접 호출
결과 반환onAction → 서버 전송onComplete → tool_result 반환

Client Tool 실행 플로우

1

Backend → tool_call 전송

name: 'share_job_posting', args: { id, title, ... }

2

Frontend → UI 렌더링

render() 호출 → 전용 카드 컴포넌트 표시

3

사용자 → 버튼 클릭

링크 복사, 상세보기 등 사용자 액션 수행

4

Frontend → tool_result 반환

onComplete({ success: true, ... }) → 백엔드로 결과 전송

5

LLM → 결과 기반 응답

AI가 tool_result를 읽고 다음 응답 생성

06

메시지 파트 렌더링

Message Part Routing

하나의 AI 응답 메시지 안에는 텍스트, 위젯, 워크플로우, 이미지 등 다양한 "파트"가 공존한다. 타입 체크 함수로 분기하여 각 파트에 맞는 컴포넌트를 렌더링한다.

타입 체크렌더링 컴포넌트설명
isTextPart()MarkdownRenderer텍스트 → 마크다운 렌더링
isWidgetPart()WidgetRouter위젯 → URI 기반 라우팅
isWorkflowPart()WorkflowWidget백엔드 처리 진행 상태
isProgressPart()ProgressWidget진행률 표시
isNoticePart()NoticeBanner시스템 공지
isImagePart()ImageRendererAI 생성 이미지
07

이중 인증 체계

Dual Authentication

비즈니스 API 서버와 채팅 전담 서버가 분리되어 있어, 2단계 인증 체계를 사용한다. 세션 만료 시 자동 갱신으로 사용자 경험 끊김을 방지한다.

1단계 — 서비스 인증

httpOnly Cookie 기반 로그인 토큰
BFF 프록시가 Cookie → Header 변환
401 시 Refresh Token 자동 갱신

2단계 — 채팅 전용 인증

임시 토큰 (credentials) 기반
채팅 서버 전용 헤더로 전송
만료 시 세션 재발급 후 요청 재시도

세션 핸드오프 플로우

사용자 접속

로그인 상태

비즈니스 API

세션 발급 요청

채팅 credentials

URI + 토큰 반환

채팅 서버 직접 통신

SSE 스트리밍 시작

LLM · SSE · Widget · Dual Auth