CASE STUDY
LLM 구조화 추출 (SWING)
장문 공고·문서 PDF를 LLM으로 파싱해 등록 폼을 자동으로 채우는 기능입니다. 핵심은 추출이 아니라, 모델이 뱉은 JSON을 검증 없이 저장하지 않는 설계였습니다 — 형식은 스키마로 강제하고, 값은 화이트리스트·길이 제한으로 거르고, 신뢰도는 등급으로 표시해 사람이 마지막에 확인하게 했습니다.
블로그에서 더 읽기 — AI가 채운 칸을 그대로 믿지 않는다 ↗장문 PDF 50~100p
컨텍스트
responseSchema 강제
출력 안정
Strategy로 교체 가능
프로바이더
문제정의
- 정부지원사업 공고는 50~100페이지 PDF가 흔해, 사용자가 사업명·주관기관·접수기간·예산을 손으로 옮겨 적었고 누락·오입력이 잦았습니다.
- LLM은 그럴듯한 JSON을 잘 뱉지만, 가끔 코드펜스·설명을 붙여 파싱을 깼고, 형식이 맞아도 값(분류·길이)이 틀린 답을 자신 있게 내놓았습니다.
- 검증 없이 모델 출력을 DB에 넣으면 모델의 헛소리가 그대로 데이터가 됐습니다.
- 소스가 URL·파일·텍스트로 갈렸고, 운영 인증을 API Key에서 서비스 계정으로 옮길 필요도 있었습니다.
문제해결
- 장문 공고를 통째로 넣기 위해 1M 토큰 컨텍스트의 Gemini를 채택하되, IAiProvider 인터페이스(Strategy)로 추상화해 환경변수 하나로 프로바이더를 교체하도록 했습니다.
- JSON을 프롬프트로 부탁하는 대신 responseMimeType·responseSchema로 출력 구조를 API 제약으로 강제해 파싱 실패를 사실상 없앴습니다.
- 열거형은 화이트리스트로 받아 모델이 없는 값을 지어내면 ETC로 폴백하고, varchar 필드는 서비스 레이어에서 truncate해 저장 에러를 막았습니다.
- 핵심 5필드 추출 여부로 confidence(high/medium/low)를 매기고, 추출 결과를 곧장 저장하지 않고 편집 가능한 폼에 prefill해 사람이 확인 후 등록하게 했습니다.
- URL은 HEAD로 타입을 보고 PDF/HTML을 분기, 파일은 S3 버퍼를 base64로, 텍스트는 길이 제한으로 전처리한 뒤 같은 추출 경로로 모았습니다.
결과
- 스키마 강제로 JSON 파싱 실패가 사실상 사라졌고, 화이트리스트·truncate로 모델 오류가 저장 에러로 번지지 않게 했습니다.
- AI를 최종 권위가 아니라 빈 칸을 채우는 보조로 두어, confidence 배지와 편집 가능한 prefill로 사람이 마지막에 검증하게 했습니다.
- 모델을 인터페이스 뒤로 숨긴 덕에, AI Studio SDK(API Key)에서 Vertex AI SDK(서비스 계정)로의 전환을 프로바이더 구현 파일 교체만으로 끝냈습니다.
기술스택
NestJSGeminiVertex AIAWS S3TypeScriptReact