AI 기반 문서·데이터 분석 플랫폼
문서의 내용을 검색하고 질문에 답하는 AI 플랫폼을 풀스택으로 개발했습니다. Gemma 4를 연동하고, 답변에 필요한 근거를 찾는 과정과 답변 품질을 평가하는 기능을 개선했습니다.
- 맡은 범위
풀스택 개발
검색·답변 품질 및 처리 속도 개선
- 기술 환경
- React
- TypeScript
- Tailwind CSS
- Vite
- TanStack Query
- Zustand
- Python
- Django
- MariaDB
- SQLGlot
- Gemma 4
- Ragas
- tokenizers
- Vitest
배경과 요구사항
문서에서 근거를 찾아 답하는 AI 기능에서는 답변뿐 아니라 평가 결과도 믿을 수 있어야 한다고 보았습니다. 긴 답변은 평가 입력 한도를 넘었고, 평가 모델이 문장을 다시 쓰면서 검증 대상의 의미도 달라졌습니다.
판단과 검증
평가 대상은 고정하고, 모델의 판정 결과를 검증
- 설계 이유
- Ragas로 평가할 문장을 서버에서 고정한 뒤 모델이 반환한 결과를 문장 ID로 대조하고, 긴 입력은 여러 묶음으로 나눴습니다. 원문 인용 / 근거 일치 / 답변의 설명 범위를 각각 확인하는 방식입니다.
- 확인
- 실제 문서 답변에서 추출한 25개 주장을 5개 묶음으로 나누어 끝까지 평가했습니다. ID 누락·중복과 잘못된 응답 형식을 검사해, 형식이 깨진 판정이 정상 결과로 저장되지 않도록 확인했습니다.
모델 대기와 애플리케이션 병목을 분리해서 개선
- 설계 이유
- 응답이 느린 원인을 구분하려고 모델 응답을 고정한 상태에서 내부 처리를 측정했습니다. 반복되는 토큰 수 계산을 줄였고, 같은 요청에서 이미 처리한 문서 페이지는 다시 활용합니다.
- 확인
- 워밍업 후 개선 전후 각각 20회를 측정한 결과, 내부 처리 중앙값이 2,969ms에서 672ms로 줄었습니다. 모델 호출 횟수는 유지했습니다.
결과와 적용 범위
긴 답변의 평가를 끝까지 처리하고 반복 계산으로 발생하던 내부 지연을 줄였습니다. 답변은 먼저 보여 주고 Ragas 평가는 비동기로 이어가므로, 사용자는 평가가 끝나기 전에 답변을 확인할 수 있습니다.
- 애플리케이션 내부 처리 시간의 중앙값
- 2,969ms → 672ms
- 같은 모델 응답을 사용하는 시험에서 애플리케이션 처리 시간을 비교했습니다. 준비 실행 후 전후 각 20회 측정했으며, 모델·네트워크 대기 시간은 제외했습니다.
- 입력 한도를 넘던 긴 답변 평가 개선
- 25개 항목 평가 완료
- 실제 문서에 대한 Gemma 4 답변에서 검증할 내용을 추출해 5개 묶음으로 평가했습니다. 가장 긴 요청도 8,164토큰으로, 8,192토큰의 입력 한도를 넘지 않았습니다.
- 품질 평가를 기다리지 않는 답변 제공
- 답변을 먼저 제공하고 평가는 별도로 실행
- Ragas 평가가 끝나기 전에 생성된 답변을 보여 줍니다. 사용자의 새 질문을 처리하는 동안에는 다음 평가 요청을 잠시 미룹니다.
구현 과정과 기술적 판단
배경
검색된 문장만 모델에 전달하면 앞뒤 문단에 있는 조건이나 예외가 빠졌습니다. 여러 내용을 한꺼번에 묻는 질문에서는 답할 수 있는 내용이 있어도 설명 대신 원문만 표시되는 문제가 있었습니다. 긴 답변을 평가할 때는 모델의 입력 한도를 넘었고, 같은 텍스트의 길이를 반복해서 계산하는 작업도 문서 분석을 지연시켰습니다.
설계 이유
- 검색어가 들어 있는 문장뿐 아니라 그 내용을 이해하는 데 필요한 앞뒤 문단과 참조된 표도 함께 전달하기로 했습니다. 여러 문서를 분석할 때도 각 근거가 어느 문서에서 나왔는지 추적할 수 있도록 했습니다.
- 원문을 정확히 인용했는지, 답변이 문서에 근거하고 있는지, 질문의 각 항목에 답했는지는 서로 다른 문제였습니다. 한 가지 점수로 판단하지 않고 각각 확인하도록 검증 과정을 나눴습니다.
- 평가 모델이 검증 대상 문장을 다시 쓰는 과정에서 내용을 바꾸는 문제가 있었습니다. 각 항목에 ID를 붙여 평가 대상 문장을 고정하고, 반환된 평가 결과를 원문 항목과 대조하도록 구성했습니다.
- 모델의 응답 속도와 애플리케이션 내부의 처리 시간을 구분해야 병목을 찾을 수 있었습니다. 같은 모델 응답을 사용하는 시험을 구성해 반복 계산에 걸리는 시간을 비교했습니다.
- 사용자가 이미 생성된 답변을 보기 위해 품질 평가까지 기다릴 필요는 없었습니다. 답변을 먼저 보여 주고 평가는 별도로 실행하되, 새 질문이 들어오면 다음 평가 요청을 잠시 미루도록 했습니다.
구현 내용
- AI가 SQL 오류를 고치면서 조회 / 집계 조건까지 바꾸는 사례를 확인했습니다. SQLGlot으로 수정 전후 조건을 대조하고, 조건이 달라지거나 유지 여부를 확인할 수 없으면 실행 전에 중단하도록 보완했습니다.
- 검색된 문장의 앞뒤 문단과 문서에서 참조한 절·표를 함께 찾도록 검색 범위를 보완했습니다. 근거가 부족한 부분은 추가로 검토하고, 최종 답변을 만들 때도 원문과 출처를 전달해 문맥이 빠지지 않도록 했습니다.
- Ragas로 답변이 문서에 근거하고 있는지 평가하는 과정에서, 긴 답변이 모델의 입력 한도를 넘는 문제가 있었습니다. 검증할 내용을 여러 묶음으로 나누고, 평가 결과의 ID를 원래 항목과 대조해 누락·중복을 확인한 뒤 점수를 계산했습니다.
- 설명을 요청한 질문을 표 생성 요청으로 잘못 분류하던 조건을 수정했습니다. 질문의 각 항목에 대한 답과 근거 번호를 따로 받고, 인용문은 그 번호에 해당하는 원문에서 직접 가져오도록 했습니다. 답할 수 있는 내용은 유지하고, 근거를 찾지 못한 항목만 구분해 표시했습니다.
- 문서의 여러 표를 합치는 과정에서 같은 텍스트의 토큰 수를 반복 계산하던 코드를 수정했습니다. 같은 요청 안에서는 이미 분석한 페이지의 결과도 재사용했습니다. 답변은 먼저 저장해 보여 주고 Ragas 평가는 별도 작업으로 실행해, 사용자가 평가 완료까지 기다리지 않도록 했습니다.
- 미디어 자료를 분석할 때는 앞뒤 대사를 함께 전달해 주어가 빠지거나 답변을 지나치게 유보하는 문제를 보완했습니다. 사용자가 답변의 근거를 직접 확인할 수 있도록 문서의 해당 페이지나 미디어 재생 구간으로 이동하는 기능도 개발했습니다.
세부 처리
- React·TypeScript로 화면을 만들고 Django API, MariaDB 저장·조회 기능, 문서 처리와 Gemma 4 연동을 개발했습니다. 모델이 만든 SQL이 사용자가 선택한 데이터 범위를 벗어나지 않는지 확인하고, 표에 나온 수치·단위와 각 행의 출처도 검증했습니다.
- 모델에 맞는 토크나이저를 연결해 실제 요청에 포함되는 텍스트와 출력 형식 정보의 토큰 수를 계산했습니다. 긴 문서는 문장 중간이 끊기지 않도록 나누고, 최종 답변에 사용할 근거에서 빠진 부분은 검토하지 못한 범위로 남겼습니다.
- Ragas Faithfulness를 사용해 답변에서 검증할 내용을 추출하고, 문서의 근거로 뒷받침되는 항목의 비율을 계산했습니다. 평가 프로그램은 별도 프로세스에서 실행하고, 답변·근거·설정이 바뀌면 이전 평가 결과를 다시 사용하지 않도록 했습니다.
- 실제 모델을 호출하는 검증과 저장해 둔 오류 응답을 재생하는 검증을 함께 사용했습니다. 응답을 고정한 자동 테스트도 별도로 실행해 수정 후 같은 문제가 다시 발생하는지 확인했고, 일반 문서와 미디어 자료의 처리 결과를 각각 점검했습니다.
확인
- 같은 모델 응답을 사용하고 준비 실행 후 변경 전후를 각 20회 측정했습니다. 애플리케이션 처리 시간의 중앙값은 문서 분석에서 2,969ms → 672ms, 지정 문서 검색에서 485ms → 312ms로 줄었습니다. 정상 처리 과정의 모델 호출 횟수는 유지했습니다.
- 실제 긴 답변에서 검증할 내용 25개를 추출하고 5개 묶음으로 나누어 평가를 완료했습니다. 가장 긴 입력은 8,164토큰이었고, 내용을 추출하는 요청을 포함해 총 6회 호출했습니다. 자동 테스트에서는 ID가 빠지거나 중복된 결과, 잘못된 자료형의 판정을 거부하는지도 확인했습니다.
- 실제 실행에서 평가가 끝나기 전에 답변이 표시되는 것을 확인했습니다. 새 질문을 처리하는 동안 다음 평가 요청이 보류되는지, 이미 끝난 평가 단계의 결과를 다시 사용할 수 있는지도 점검했습니다.
- 실제 질문의 답변을 원문과 대조하고, 인용문이나 단위가 달라진 경우와 문서에 근거가 없는 질문을 테스트했습니다. 브라우저에서는 출처 페이지로 이동하고 미디어를 재생한 뒤에도 입력 내용과 선택 상태가 유지되는지 확인했습니다.