
Projects
BizKit AI 서버 개발 회고
긴 응답 시간, 외부 의존성, LLM 출력 불안정성을 다루며 FastAPI 기반 AI 서버를 구현한 팀 프로젝트 회고입니다.
프로젝트 개요
BizKit은 팀 CARO가 개발한 서비스로, 개발자의 이력과 활동 데이터를 바탕으로 협업 성향을 분석하고, 상황에 맞는 자기소개를 생성하며, 명함 이미지를 구조화하는 카카오테크 부트캠프 파이널 프로젝트입니다. 이 글은 그중 AI 서버를 설계하고 구현하면서 겪은 문제와 판단을 회고하는 글입니다.
이 글은 BizKit AI 서버 메인글의 회고 글입니다. 프로젝트 구조와 설계 판단은 메인글과 AI 서버 아키텍처 설계에 따로 정리했습니다.
- 구분: 카카오테크 부트캠프 팀 프로젝트
- 역할: AI 서버 설계 및 구현
- 주요 기술: FastAPI, Python, Redis Streams, vLLM, GitHub API, Tavily Search API, OCR, 임베딩 서비스
- 링크: GitHub
AI 서버는 단순히 모델을 호출하는 서버가 아니었습니다. 외부 데이터를 수집하고, 긴 작업을 비동기로 처리하며, LLM이 만든 응답을 서비스가 사용할 수 있는 구조로 제한해야 했습니다.
맡은 역할
저는 팀에서 AI 기능을 담당하는 서버를 구현했습니다. 주요 기능은 세 가지였습니다.
- GitHub 활동 기반 HEX 분석
- 회사와 직무 정보를 반영한 자기소개 생성
- 명함 이미지 OCR 및 연락처 구조화
이 기능들은 모두 일반적인 CRUD API와 성격이 달랐습니다. GitHub API, 검색 API, LLM, OCR 모델처럼 외부 의존성이 많았고, 처리 시간이 길었으며, 응답 형식도 항상 안정적이지 않았습니다.
처음 마주친 문제
가장 먼저 부딪힌 문제는 응답 시간이었습니다. LLM이나 OCR을 동기 API 안에서 바로 처리하면 사용자는 요청 후 긴 시간 동안 기다려야 했습니다. 외부 API가 느려지거나 실패하면 전체 요청도 같이 흔들렸습니다.
또 하나의 문제는 LLM 응답의 불확실성이었습니다. 서비스 화면은 정해진 JSON 구조를 필요로 하지만, 모델은 때때로 설명 문장을 덧붙이거나, 필드를 누락하거나, 점수를 과하게 보정할 수 있었습니다. AI 서버는 좋은 문장을 생성하는 것보다, 프론트엔드와 백엔드가 신뢰할 수 있는 데이터를 반환하는 것이 더 중요했습니다.
해결 방향
처음에는 API 요청 안에서 바로 모델을 호출하는 단순한 흐름을 생각할 수 있었습니다. 하지만 작업 시간이 길고 실패 가능성이 높은 기능을 동기 요청으로 묶으면, 서버도 사용자도 상태를 다루기 어려워집니다.
그래서 작업 접수와 작업 실행을 분리했습니다. FastAPI 서버는 요청을 받으면 Redis Streams에 작업을 등록하고, Worker가 작업을 소비해 실제 처리를 담당하도록 구성했습니다. 사용자는 task_id를 받아 진행 상태와 결과를 조회합니다.
Client
-> FastAPI API Server
-> Redis Streams
-> AI Worker
-> Task Result Store
-> Client Polling
이 구조는 구현량은 늘었지만, 긴 작업을 서비스 흐름 안에서 다루기 쉬워졌습니다. API 서버는 작업 접수에 집중하고, Worker는 기능별 처리 로직에 집중할 수 있었습니다.
HEX 분석에서 배운 점
HEX 분석은 GitHub 활동 데이터를 바탕으로 협업, 소통, 기술, 문서화, 신뢰성, 선호도 6개 축을 계산하는 기능입니다. PR 생성 수, PR 리뷰 수, 이슈 코멘트, 언어 다양성, README 작성 비율, 머지율 같은 데이터를 먼저 규칙 기반으로 계산했습니다.
LLM은 점수를 처음부터 만드는 역할이 아니라, 계산된 지표를 해석하고 요약하는 역할로 제한했습니다. 모델이 임의로 점수를 크게 바꾸지 못하도록 보정 범위와 JSON 스키마를 프롬프트에 명시했습니다.
이 기능을 만들면서 AI 기능에서도 규칙 기반 계산과 생성형 모델의 역할을 분리해야 한다는 점을 배웠습니다. 근거가 있는 값은 코드로 계산하고, 자연어 설명이 필요한 부분에만 LLM을 사용하는 편이 더 안정적이었습니다.
자기소개 생성에서 배운 점
자기소개 생성은 사용자의 회사, 부서, 직무, 프로젝트 정보를 바탕으로 3문장 자기소개를 만드는 기능입니다. 처음에는 입력값을 바로 LLM에 넘기면 될 것처럼 보였지만, 실제로는 사전 검증과 컨텍스트 보강이 중요했습니다.
개발과 무관한 직무가 들어오면 AI 서버가 무리하게 개발자 자기소개를 만들면 안 됩니다. 그래서 임베딩 서비스로 직무 관련성을 먼저 판단하고, 개발과 무관한 요청은 차단했습니다.
회사와 부서 정보는 Tavily Search API로 보강했습니다. 검색 결과가 충분한지, 얼마나 관련 있는지 판단하기 위해 search_confidence를 계산했고, 유사한 요청은 시맨틱 캐시에 저장해 반복적인 LLM 호출을 줄였습니다.
이 과정에서 LLM 기능은 프롬프트 하나로 끝나지 않는다는 점을 다시 확인했습니다. 입력 검증, 검색 컨텍스트, 캐시, 출력 검증이 함께 있어야 서비스 기능으로 사용할 수 있었습니다.
명함 OCR에서 배운 점
명함 OCR은 이미지에서 이름, 회사, 직책, 전화번호, 이메일 같은 정보를 추출하는 기능입니다. OCR 결과는 이미지 품질과 명함 디자인에 따라 흔들릴 수밖에 없습니다.
그래서 OCR 결과를 그대로 반환하지 않고, 연락처 필드별로 후처리하는 흐름이 필요했습니다. 전화번호와 이메일처럼 형식이 비교적 명확한 값은 정규화하고, 확신하기 어려운 값은 기본값이나 빈 값으로 처리하는 방식이 더 안전했습니다.
AI 기능은 결과가 그럴듯해 보이는 것보다, 틀렸을 때 서비스가 어떻게 다룰 수 있는지가 더 중요했습니다.
아쉬웠던 점
가장 아쉬웠던 부분은 기능별 품질 평가를 더 체계적으로 남기지 못한 점입니다. 프롬프트를 수정하거나 후처리 로직을 바꿀 때, 이전보다 좋아졌는지 판단할 수 있는 고정 테스트 세트가 더 필요했습니다.
또한 비동기 작업 구조를 만들면서 상태 전이와 실패 케이스를 더 명확히 문서화했으면 좋았을 것 같습니다. 작업이 pending, processing, completed, failed로 이동하는 기준과 재시도 정책이 분명할수록 팀원이 API를 이해하기 쉬웠을 것입니다.
잘했다고 생각하는 점
LLM을 서비스 로직 한가운데에 그대로 두지 않고, 제한된 역할로 사용하려고 한 점은 좋은 판단이었습니다. 점수 계산, 직무 필터링, 캐시, OCR 후처리처럼 코드가 더 잘하는 부분을 분리했기 때문에 결과를 더 예측 가능하게 만들 수 있었습니다.
또한 Redis Streams 기반 작업 큐를 도입한 덕분에 긴 작업을 API 서버에서 분리할 수 있었습니다. 이 구조는 이후 기능이 늘어나더라도 Worker를 분리하거나 확장하기 좋은 기반이 되었습니다.
배운 점
이번 프로젝트를 통해 AI 서버는 모델 호출 코드만으로 완성되지 않는다는 점을 배웠습니다. 실제 서비스에서 AI 기능을 사용하려면 입력 검증, 외부 API 장애 대응, 출력 스키마 강제, 캐싱, 작업 상태 관리, 후처리까지 함께 설계해야 합니다.
특히 LLM을 사용할 때 중요한 것은 "좋은 답변을 한 번 생성하는 것"보다 "서비스가 반복해서 신뢰할 수 있는 형태로 제한하는 것"이었습니다. 이 경험 이후 AI 기능을 볼 때도 모델 성능만이 아니라, 데이터 흐름과 실패 처리까지 함께 보게 되었습니다.
