WIKI생성형 AI 행정비서
프로덕션·2025.06 ~ 2026.03·프론트·백엔드 주도

생성형 AI 행정비서

공무원이 대화로 업무를 굴리는 행정비서.

ReactViteTypeScriptSTOMPSpring BootJava

무엇을 만들었나

공공 SI 구축 사업에서, 공무원이 자연어로 업무를 처리하는 생성형 AI 행정비서의 프론트엔드(React)와 스트리밍 백엔드(Spring Boot)를 주로 맡아 개발했다. 성격이 다른 여러 상용 LLM·RPA 시스템을 하나의 프로토콜로 묶어, 대화만으로 실제 업무가 돌아가게 했다.

정부·공공 클라이언트 프로젝트다. 회사·부처명, 내부 화면, 소스, API 규격은 전부 비공개다. 여기 남기는 건 기술 접근과 문제 유형을 일반화한 것뿐이다. RAG 인제스트 파이프라인·쿠버네티스 배포·CI는 팀 담당으로, 내 범위가 아니다.

어떤 문제였나

공무원이 업무 시스템을 여러 개 오가는 대신 한 채팅창에서 자연어로 처리하게 하는 것. 진짜 난제는 화면이 아니라 통합이었다. 성격이 완전히 다른 백엔드들(즉시 토큰을 흘리는 스트리밍 LLM, 실행에 시간이 걸려 나중에 콜백으로 결과를 주는 비동기 RPA)을, 사용자에게는 하나의 매끄러운 대화로 보이게 만드는 것이 관건이었다.

전체 구조

계층 구조 — 프로바이더 추상화가 여러 LLM·RPA를 하나의 스트림으로 묶는다.
계층 구조 — 프로바이더 추상화가 여러 LLM·RPA를 하나의 스트림으로 묶는다.

핵심

멀티 LLM·RPA 프로바이더를 단일 프로토콜로 추상화
실시간 스트리밍 채팅 (WebSocket / STOMP)
RAG 인용 렌더링 — 스트리밍 청크 경계 처리
관측성 — 커스텀 메트릭·로그로 디버깅 + 부하 테스트
스트림 취소 · 중복 요청 방지 · 세션 관리
정부 웹접근성 (KRDS · WCAG 2.1 AA)

멀티 프로바이더 추상화, 모델 핸들링

이 프로젝트에서 내가 가장 무겁게 다룬 부분이다. 성격이 완전히 다른 두 시스템을 하나로 묶었다. 하나는 토큰을 실시간으로 흘려보내는 동기 스트리밍 LLM, 다른 하나는 실행에 시간이 걸려 나중에 콜백으로 결과를 던지는 비동기 RPA다. 이 둘을 프론트 입장에선 똑같은 "스트림 이벤트"로 보이게 추상화해서, 사용자는 뒤에서 무엇이 도는지 모른 채 하나의 대화를 이어간다. 새 프로바이더가 붙어도 프론트는 바뀌지 않는다.

실시간 스트리밍 채팅

WebSocket(STOMP)으로 스트림 시작·청크·종료·중단 이벤트를 흘린다. 세션 레지스트리로 누가 어떤 스트림을 받는지 관리하고, 사용자가 도중에 취소하거나 요청이 중복으로 겹치는 상황을 경계에서 방어한다.

RAG 인용 렌더링

모델이 스트리밍으로 뱉는 인용 마커는 청크 경계에서 잘리기 일쑤다. carry 버퍼로 경계를 이어붙여 파싱한 뒤 각주·출처 뱃지로 렌더한다. 불안정한 모델 출력을 UI 경계에서 정규화하는 레이어다.

관측성, 안 보이면 못 고친다

실시간 스트리밍에 성격이 다른 백엔드(스트리밍 LLM + 비동기 RPA)를 붙이면, 어디서 느려지고 어디서 세션이 꼬이는지 눈으로는 안 보인다. 그래서 커스텀 메트릭과 구조화 로깅을 직접 심었다. 그걸로:

  • 비정상·멈춘 세션을 자동으로 탐지·정리하고,
  • APM·로그 분석으로 지연과 에러가 어느 구간에서 나는지 추적했으며,
  • 스트리밍 파이프라인이 동시 부하를 견디는지 부하 테스트로 확인했다.

"돌아가게 만들었다"가 아니라 "무엇이 어떻게 도는지 볼 수 있게 만들었다". 이질적 시스템을 하나로 묶은 통합에선 이 관측성이 곧 디버깅 능력이었다.

정부 웹접근성

공공 서비스라 KRDS·WCAG 2.1 AA를 준수하는 프론트. 스크린리더·키보드 내비게이션까지 챙겼다.

기술 스택

  • 프론트: React · Vite · TypeScript · STOMP 웹소켓 · Module Federation(상위 포털 임베드)
  • 백엔드: Spring Boot · Java · 전자정부프레임워크 · MyBatis
  • 범위 밖(팀 담당): RAG 인제스트 파이프라인 · 쿠버네티스 배포 · CI

왜 이 스택

  • STOMP 웹소켓: 토큰을 실시간으로 흘리는 스트리밍 채팅이라, WebSocket 위에 STOMP로 스트림 시작·청크·종료·중단 이벤트를 주고받았다.
  • 전자정부표준프레임워크 · Spring Boot: 공공 SI라 전자정부표준프레임워크가 사실상 필수다. 선택이라기보단 공공 환경의 전제였고, 그 위에서 스트리밍 서버를 설계했다.
  • Module Federation: 상위 포털에 마이크로프론트로 임베드돼야 해서, 프론트를 독립 배포·주입할 수 있게 Module Federation을 썼다.

Decision Log

Decision #01이질적 프로바이더를 단일 스트림 프로토콜로

동기 스트리밍 LLM과 비동기 콜백 RPA를 프론트에선 같은 이벤트로 보이게 추상화했다. 백엔드가 아무리 달라도 사용자에겐 하나의 대화, 새 프로바이더가 붙어도 프론트는 그대로.

Decision #02인용 마커를 스트리밍 경계에서 방어

청크 경계에서 잘리는 인용 마커를 carry 버퍼로 이어붙여 파싱했다. 불안정한 모델 출력을 UI 경계에서 정규화하는 셈이다. 모델을 못 믿는 대신 경계를 튼튼하게 만들었다.

Decision #03관측성을 먼저 심는다 — 안 보이면 못 고친다

실시간·비동기가 섞인 시스템은 로그 없이는 디버깅이 안 된다. 커스텀 메트릭과 구조화 로깅을 먼저 깔아, 멈춘 세션 자동 정리·지연 추적·부하 테스트의 기반을 만들었다.

Decision #04내 범위는 여기까지 — 접점·통합에 집중

RAG 인제스트 파이프라인과 배포 인프라는 팀이 맡았다. 나는 사용자 접점(프론트)과 그걸 받치는 스트리밍·통합 백엔드에 집중했다. "다 했다"가 아니라 "이걸 맡았다".

회고

포트폴리오에서 실제 프로덕션으로, 공무원이 업무에 쓴 생성형 AI 서비스의 사용자 접점과 통합을 맡은 경험이다. 화려한 화면이 아니라, 성격이 다른 여러 AI·자동화 시스템을 하나의 매끄러운 대화로 묶는 통합 설계가 핵심이었다.

회사·부처·화면은 가린 채, 문제를 어떻게 풀었는지만 남긴다. 공개할 수 있는 건 딱 여기까지다.