후보자 준수 에이전트 (Candidate Compliance Agent): 타밀나두 선거 선언서(Affidavits)를 위한 다국어 RAG
요약
인도 타밀나두 선거 후보자의 선언서를 분석하여 유권자에게 정보를 제공하는 다국어 RAG 에이전트 구축 사례입니다. OCR 파이프라인과 주제별 의미론적 문서 구축(Semantic Document Building)을 통해 검색 정확도를 높였습니다.
핵심 포인트
- Tesseract OCR을 활용한 타밀어 및 영어 혼용 스캔 PDF 텍text 추출
- 단순 청킹 대신 주제별 의미론적 문서 구축으로 검색 노이즈 감소
- Groq LLM(Llama 3.3 70B)과 Hindsight Memory를 활용한 에이전트 아키텍처
- 복잡한 문서 구조를 해결하기 위한 의도 라우터 및 구조화된 질의 적용
아무도 이야기하지 않는 문제
인도의 매 선거 주기마다, 후보자들은 많은 사람들에게 쉽게 접근 가능하지 않으며, 이로 인해 유권자들은 자신이 투표하는 후보자에 대한 많은 정보를 알지 못합니다.
내가 만든 것
Candidate Compliance Agent는 누구나 평이한 영어 또는 타밀어로 타밀나두(Tamil Nadu) 선거 후보자 선언서(affidavits)를 질의할 수 있게 해주는 AI 에이전트입니다. 자산, 범죄 기록, 소득 신고, 교육 등에 대해 질문하면 실제 문서로부터 답변을 제공합니다.
라이브 데모: https://affidavit-rag-frontend.vercel.app
기술적 과제
이 프로젝트는 깔끔한 데이터셋을 사용하는 프로젝트가 아니었습니다. 원재료는 다음과 같았습니다:
- 스캔된 PDF (각각 약 6MB)
- 타밀어와 영어가 혼용된 텍스트
- 텍스트 위에 공증 인장이 겹쳐진 인지세(Stamp paper) 배경
- 후보자별로 일관되지 않은 형식
아키텍처 개요
React 프론트엔드 (Vercel)
↓
Flask 백엔드 (Railway)
↓
Hindsight Memory (과거 문맥 회상)
↓
Intent Router (의도 라우터)
↓
Structured Query (구조화된 질의) 또는 Semantic RAG (의미론적 RAG)
↓
Groq LLM (Llama 3.3 70B)
↓
Hindsight Memory (새로운 문맥 유지)
↓
응답 + 후속 질문
기술적 기능 구현 상세 설명
OCR 파이프라인
첫 번째 과제는 스캔된 PDF에서 텍스트를 추출하는 것이었습니다. 저는 타밀어 + 영어 언어 모델이 포함된 Tesseract를 사용했습니다:
text = pytesseract.image_to_string(
...
--psm 6는 각 페이지를 균일한 텍스트 블록으로 취급하며, 이는 구조화된 선언서(affidavit) 양식에 매우 중요합니다.
각 PDF는 pdf2image를 사용하여 300 DPI의 이미지로 변환된 후, 페이지별로 OCR 처리가 되었습니다. 출력 결과는 메타데이터와 함께 JSON으로 저장되었습니다:
{
"text": "...",
"metadata": {
...
의미론적 문서 구축 (Semantic Document Building)
가장 큰 아키텍처적 결정은 데이터를 어떻게 청킹(chunking)할 것인가였습니다.
단순한 접근 방식: 각 페이지를 오버랩(overlap)을 포함하여 500-토큰 청크로 분할합니다. 이렇게 하면 자산, 범죄 기록, 교육 정보가 모두 뒤섞인 600개 이상의 노이즈 섞인 청크가 생성됩니다.
더 나은 접근 방식: 후보자당 주제별로 하나의 의미론적 문서(semantic document)를 구축합니다.
각 후보자는 6개의 문서를 갖게 됩니다:
신원 및 연락처 (Identity and contact)
교육 (Education)
형사 사건 (Criminal cases) (구조화된 데이터)
형사 상세 내용 (Criminal detail) (원문 OCR 텍스트)
소득세 (Income tax)
자산 및 부채 (Assets and liabilities)
def build_docs(candidate, raw_pages):
docs = []
docs.append({"section": "criminal_cases", "text": f"""
...
35명의 후보자 × 6개 섹션 = 600개 이상의 노이즈 섞인 청크 (chunks) 대신 210개의 깨끗한 의미론적 문서 (semantic documents)가 생성됩니다.
결과: 누군가 "특정 후보자에게 형사 사건이 있습니까?"라고 물으면, 검색 시스템은 무작위 페이지 조각 대신 형사 사건 (criminal_cases) 문서를 직접 타격합니다.
임베딩 (Embeddings) 및 벡터 스토어 (Vector Store)
임베딩을 위해 paraphrase-multilingual-MiniLM-L12-v2를 사용했습니다. 이는 타밀어와 영어를 동일한 벡터 공간 (vector space)에서 처리하는 문장 트랜스포머 (sentence transformer) 모델입니다.
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
embeddings = model.encode(texts, batch_size=32).tolist()
collection.add(
documents=texts,
embeddings=embeddings,
...
의도 라우터 (Intent Router)
쿼리가 전달되는 방식에 따라 5가지 의도 카테고리 (intent category)가 존재합니다.
def detect_intent(query):
q = query.lower()
for party in PARTIES:
...
Hindsight 메모리 통합 (Hindsight Memory Integration)
Hindsight는 세션 전반에 걸쳐 문맥 (context)을 저장하고 회상하는 지속성 메모리 (persistent memory) 서비스입니다. 사용자가 다음 날 돌아와 후속 질문을 하면, 에이전트는 이전에 무엇을 물었는지 기억합니다.
from hindsight_client import Hindsight
client = Hindsight(
...
HINDSIGHT에 저장된 메모리 사용하기
1. 과거 문맥 회상
past_memory = recall_memory(session_id, query)
2. RAG 프롬프트에 주입
prompt = f"""
이전 문맥: {past_memory}
선언서(affidavits)로부터 가져온 문맥: {retrieved_context}
질문: {query}
"""
3. 답변 획득
answer = ask_groq(prompt)
4. 이 대화 내용 저장
retain_memory(session_id, f"사용자 질문: {query}. 답변: {answer[:200]}")
이것이 중요한 이유: 후보자를 조사하는 유권자는 여러 세션에 걸쳐 여러 질문을 던질 수 있으며, 점진적으로 더 나아지고 문맥을 파악하는 답변을 얻을 수 있습니다.
문맥적 후속 질문 (Contextual Follow-up Questions)
모든 답변 후에, LLM은 쿼리(query), 검색된 문맥(retrieved context), 그리고 메모리(memory)를 기반으로 4개의 후속 질문을 생성합니다:
답변:
Poornima는 진행 중인 형사 사건이 없습니다.
후속 질문:
- Poornima가 신고한 총 자산은 얼마입니까?
- Poornima가 신고한 연간 소득은 얼마입니까?
- Poornima의 최종 학력은 무엇입니까?
이 질문들은 클릭 가능한 버튼 형태로 나타나며, 클릭 시 입력창에 즉시 채워집니다.
배포 (Deployment)
백엔드 (Backend): Railway 상의 Flask + gunicorn
프론트엔드 (Frontend): Vercel 상의 React
벡터 데이터베이스 (Vector DB): ChromaDB (지속성 유지, GitHub에 커밋됨)
LLM: Groq API (Llama 3.3 70B Versatile)
메모리 (Memory): Hindsight
배운 점 (What I Learned)
-
OCR 품질이 이후의 모든 과정을 결정합니다. 품질이 낮은 OCR은 좋지 않은 임베딩 (embeddings)을 생성합니다. --psm 6 설정 변경을 통해 타밀어 추출 성능이 크게 향상되었습니다.
-
의미론적 청킹 (Semantic chunking)이 토큰 청킹 (token chunking)보다 우수합니다. 주제 중심의 210개 문서를 사용하는 것이 무작위로 잘라낸 600개 이상의 페이지 조각보다 더 나은 검색 결과를 제공합니다.
-
지속성 메모리 (Persistent memory)는 상호작용 모델을 변화시킵니다. Hindsight가 없다면 모든 대화는 초기 상태에서 시작됩니다. Hindsight를 사용하면 에이전트가 시간이 지남에 따라 이해도를 구축합니다.
-
실제 데이터는 항상 튜토리얼에서 제시하는 것보다 훨씬 더 복잡합니다. 인지(Stamp) 용지의 배경, 공증인 직인 오버레이, 혼용된 문자 등은 RAG 튜토리얼에서는 전혀 볼 수 없는 요소들입니다.
향후 계획 (What's Next)
- 타밀나두의 모든 234개 선거구로 확장 (~4,500개의 PDF)
- 새로운 선언서(affidavits)가 제출될 때 선제적 알림 추가
- 타밀어 음성 쿼리 지원
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기