LLM · SSE Streaming · Widget System
AI Agent
LLM 기반 대화형 에이전트
5-Layer 아키텍처
Data Flow Through Layers
사용자의 메시지부터 LLM 응답까지, 데이터는 5개 레이어를 거쳐 흐른다.
React Components
Composer, Message, Widget UI
Chat Runtime & State
런타임 훅, Zustand 상태 관리
API Client & Interceptors
HTTP 클라이언트, 인증 토큰 자동 주입
BFF Proxy
Next.js 서버, SSE 패스스루, 쿠키 → 헤더 변환
Backend + LLM
채팅 API 서버, LLM 스트리밍 응답
SSE 스트리밍 파이프라인
Real-time Streaming
LLM 응답은 SSE(Server-Sent Events)로 실시간 스트리밍된다. 프론트엔드는 5단계 파이프라인으로 바이트 스트림을 파싱하고 화면에 한 글자씩 렌더링한다.
SSE 연결
fetch()로 POST 요청, 응답 body를 ReadableStream으로 수신. AbortController로 취소 가능.
Async Generator 파싱
바이트 스트림 → 줄 단위 분리 → data:{...} JSON 파싱 → yield로 이벤트를 하나씩 방출.
누적 & 콜백
delta 텍스트를 배열에 누적하며 onDelta(누적값) 호출. done:true이면 onComplete(전체) 트리거.
requestAnimationFrame
각 delta 사이에 브라우저 렌더링 타이밍을 확보하여 부드러운 타이핑 효과 구현.
React 상태 반영
setState(accumulated) → 리렌더링 → 화면에 한 글자씩 나타남.
SSE 이벤트 포맷
data: { "type": "delta", "content": "안녕하" }
data: { "type": "delta", "content": "세요" }
data: { "type": "done", "content": "안녕하세요" }
위젯 시스템
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이 파싱한 조건 확인 후 검색 실행
프로필 카드
인물 정보 카드 렌더링 + 상세보기 액션
추천 목록
리스트 형태의 추천 결과 표시
워크플로우 상태
백엔드 처리 진행률 실시간 표시
공지 배너
시스템 공지·안내 메시지 표시
위젯 액션 플로우
User Interaction → Backend
사용자가 위젯에서 선택을 하면, 그 데이터는 ActionConfig 객체로 포장되어 handler 타입(server / client)에 따라 분기 처리된다.
사용자 인터랙션
- 위젯 UI에서 라디오 버튼 / 버튼 클릭
- 폼 상태 업데이트 (react-hook-form)
- "확인" 버튼 → handleSubmit() 트리거
ActionConfig 생성 & onAction 호출
- type: 액션 종류 (예: select, search, confirm)
- handler: 'server' 또는 'client'
- payload: 사용자가 선택한 데이터
- 중복 제출 방지 Lock 활성화
백엔드 전송 & LLM 응답
- server handler → API 전송 → LLM이 결과 처리
- client handler → 프론트에서 직접 처리 (링크 복사 등)
- 응답 수신 → 새 메시지 / 위젯으로 대화 계속
ActionConfig 구조
type: 'ambiguous_term.select',
handler: 'server', // 또는 'client'
payload: { selectedCode: '001' }
})
Client Tools
Backend → Frontend Function Call
위젯과 별개로, 백엔드가 tool_call 이벤트로 프론트엔드 함수를 직접 호출할 수 있다. 사용자 인터랙션 후 결과를 tool_result로 돌려주는 양방향 통신 구조.
| 구분 | Widget | Client Tool |
|---|---|---|
| 전달 방식 | 메시지 content 안에 포함 | tool_call 이벤트로 호출 |
| 렌더링 | URI 기반 라우팅 | render() 함수 직접 호출 |
| 결과 반환 | onAction → 서버 전송 | onComplete → tool_result 반환 |
Client Tool 실행 플로우
Backend → tool_call 전송
name: 'share_job_posting', args: { id, title, ... }
Frontend → UI 렌더링
render() 호출 → 전용 카드 컴포넌트 표시
사용자 → 버튼 클릭
링크 복사, 상세보기 등 사용자 액션 수행
Frontend → tool_result 반환
onComplete({ success: true, ... }) → 백엔드로 결과 전송
LLM → 결과 기반 응답
AI가 tool_result를 읽고 다음 응답 생성
메시지 파트 렌더링
Message Part Routing
하나의 AI 응답 메시지 안에는 텍스트, 위젯, 워크플로우, 이미지 등 다양한 "파트"가 공존한다. 타입 체크 함수로 분기하여 각 파트에 맞는 컴포넌트를 렌더링한다.
| 타입 체크 | 렌더링 컴포넌트 | 설명 |
|---|---|---|
| isTextPart() | MarkdownRenderer | 텍스트 → 마크다운 렌더링 |
| isWidgetPart() | WidgetRouter | 위젯 → URI 기반 라우팅 |
| isWorkflowPart() | WorkflowWidget | 백엔드 처리 진행 상태 |
| isProgressPart() | ProgressWidget | 진행률 표시 |
| isNoticePart() | NoticeBanner | 시스템 공지 |
| isImagePart() | ImageRenderer | AI 생성 이미지 |
이중 인증 체계
Dual Authentication
비즈니스 API 서버와 채팅 전담 서버가 분리되어 있어, 2단계 인증 체계를 사용한다. 세션 만료 시 자동 갱신으로 사용자 경험 끊김을 방지한다.
1단계 — 서비스 인증
httpOnly Cookie 기반 로그인 토큰
BFF 프록시가 Cookie → Header 변환
401 시 Refresh Token 자동 갱신
2단계 — 채팅 전용 인증
임시 토큰 (credentials) 기반
채팅 서버 전용 헤더로 전송
만료 시 세션 재발급 후 요청 재시도
세션 핸드오프 플로우
사용자 접속
로그인 상태
비즈니스 API
세션 발급 요청
채팅 credentials
URI + 토큰 반환
채팅 서버 직접 통신
SSE 스트리밍 시작
LLM · SSE · Widget · Dual Auth