TL;DR
우아한형제들은 설문 자유 응답과 인터뷰·UT 녹취록을 분석하는 사내 리서치 AI 도우미를 구축하면서 결과 생성과 결과 전달을 별도의 문제로 다뤘습니다. 분석 품질을 안정화하기 위해 응답 다듬기, 중분류 통합, 대분류 통합으로 LLM 작업을 나누고, Agentic RAG로 조사와 관련된 유저골만 골라 프롬프트에 넣었으며, Langfuse와 연구자 평가를 연결해 개선 지점을 추적했습니다. 분석 시간이 길어지는 문제에는 job_id 기반 SSE 스트림과 progress·completed·failed·cancelled·heartbeat 이벤트를 적용하고, Web Audio API로 완료 알림음을 만들었습니다. 결과가 나온 뒤에는 분석 결과를 근거로 질문하는 채팅을 붙이고, 세션을 첫 질문 직전에 만들며, React 바깥의 서비스 레이어에서 여러 탭의 답변 스트림을 유지했습니다. 글의 핵심은 LLM 프롬프트 한 문장보다 작업 단계·평가 루프·비동기 사용자 경험을 함께 설계해야 분석 결과가 실제 인사이트로 이어진다는 점입니다.
빠른 이해
새로운 점
LLM 분석 파이프라인과 비동기 프론트엔드 전달 경험을 함께 설계하고, job_id를 평가 추적·SSE 스트림·후속 채팅의 공통 연결점으로 사용한 점입니다.
핵심 메커니즘
자료와 조사 목적을 입력받아 응답 정제→중분류 통합→대분류 통합을 수행하고, 조사 주제와 Summary Point로 관련 유저골을 선별해 다음 LLM 단계에 주입합니다. 분석 작업은 job_id를 발급한 뒤 SSE로 진행 이벤트를 전송하며, 완료된 결과는 같은 식별자를 기준으로 채팅 세션과 답변 스트림에 연결합니다.
섹션별 상세
리서치 AI 도우미의 분석 범위
LLM 작업을 단계로 나눈 이유
user_goal_df = pd.read_csv(...)
filtered_df = filter_relevant_user_goals(
user_goal_df, summary_points=summary_points, topic=topic, max_rows=200,
)
return filtered_df.to_markdown(index=False)전체 유저골 목록에서 조사 주제와 Summary Point에 관련된 행을 선별하고, 선택 결과만 Markdown 표로 변환합니다.
리서처 지식과 평가 루프 연결
export class JobEventStream {
private eventSource: EventSource | null = null
start(): void {
const url = `${getBaseURL()}/jobs/${this.jobId}/stream`
this.eventSource = new EventSource(url)
this.eventSource.addEventListener('progress', (e) => {
this.handleEvent(e, 'onProgress')
})
this.eventSource.addEventListener('completed', (e) => {
this.handleEvent(e, 'onCompleted')
this.close() // 완료되면 스트림을 닫아요
})
// failed, cancelled, heartbeat ...
}
}EventSource 연결을 만들고 분석 진행·완료 이벤트를 상태 관리 함수에 전달하며, 완료 시 스트림을 닫습니다.
const oscillator = audioContext.createOscillator()
const gainNode = audioContext.createGain()
oscillator.type = 'sine' // 사인파
oscillator.frequency.value = frequency // 음 높이
// 부드럽게 시작하고(Attack) 자연스럽게 사라지도록(Decay)
gainNode.gain.linearRampToValueAtTime(0.25, now + 0.03)
gainNode.gain.exponentialRampToValueAtTime(0.01, now + duration)
oscillator.connect(gainNode)
gainNode.connect(audioContext.destination)
oscillator.start(now) // now = audioContext.currentTime
oscillator.stop(now + duration)Oscillator로 사인파를 만들고 Gain에서 음량을 조절한 뒤 오디오 출력 목적지로 연결해 완료 알림음을 생성합니다.
SSE와 알림음으로 긴 분석 전달
async ensureSession(conversationId, ...) {
const existing = this.sessions.get(conversationId)
// 세션이 있고 만료 전이면 그대로 사용해요
if (existing && !this.checkSessionExpiry(existing.expiresAt)) return
// 없거나 만료됐으면 새로 발급받아요
const response = await conversationAPI.startConversation({ ... })
this.sessions.set(conversationId, { ... })
}질문을 보내기 직전에 기존 대화 세션의 만료 시각을 확인하고, 없거나 만료된 경우에만 새 세션을 발급합니다.
결과 이후의 채팅과 세션 관리
모델 바깥의 서비스 설계
용어 해설
- 에이전틱 검색 증강 생성(Agentic RAG)
- — LLM이 전체 자료를 한꺼번에 받는 대신 현재 문제와 관련된 항목을 직접 선별한 뒤, 선택한 자료만 다음 처리 단계의 문맥으로 주입하는 방식입니다. 이 글에서는 수백 행의 유저골 목록에서 조사 주제와 Summary Point에 맞는 행만 골라 토큰 사용량과 무관한 정보의 혼입을 줄입니다.
- 서버 전송 이벤트(SSE)
- — HTTP 연결을 유지하면서 서버가 클라이언트로 이벤트를 계속 보내는 단방향 통신 방식입니다. 클라이언트가 별도 반복 요청을 하지 않아도 분석 진행률과 완료·실패 상태를 받을 수 있어, 서버 작업의 현재 상태를 실시간으로 전달하는 데 쓰입니다.
- EventSource API
- — 브라우저에서 SSE 스트림을 구독하는 API입니다. 이 글의 구조에서는 분석 시작 POST 요청으로 job_id를 받은 뒤 해당 식별자로 GET 기반 스트림을 열고, progress·completed·failed 같은 이벤트별 처리 함수를 연결합니다.
- Web Audio API
- — 브라우저에서 오디오를 코드로 생성하고 재생하는 API입니다. AudioContext 안에서 Oscillator로 파형과 음 높이를 만들고 Gain으로 음량 변화를 조절한 뒤 Destination으로 보내므로 별도 mp3 파일 없이 완료 알림음을 구현할 수 있습니다.
- Langfuse
- — LLM 호출 이력과 단계별 입출력을 추적하고 전체 성능을 관찰하는 도구입니다. 글에서는 분석 하나의 job_id를 추적 세션 ID로, 사내 계정 정보를 사용자 ID로 연결해 어떤 분석의 어느 단계에서 문제가 생겼는지 확인하는 데 활용합니다.
기술
- LLM
- Agentic RAG
- Python
- Langfuse
- SSE
- Server-Sent Events
- EventSource API
- Web Audio API
- AudioContext API
- React
- React 바깥 서비스 클래스
- conversationStore
- ConversationSessionService
언급된 리소스
AI 요약 · 북마크 · 개인 피드 설정 — 무료
출처 · 인용 안내
인용 시 "요약 출처: AI Trends (aitrends.kr)"를 표기하고, 사실 확인은 원문 보기 기준으로 진행해 주세요. 자세한 기준은 운영 정책을 참고해 주세요.