제2회 공정거래 AI·데이터 활용 공모전 (AI 학습모델 개발 트랙)
공정거래위원회 의결서를 자연어로 검색하고, 근거 의결서와 함께 답변을 받을 수 있는 RAG 시스템입니다.
공식 의결서 ZIP (PDF + 청킹 + 메타) ─┐
├─ load_official_data.py → chunks.jsonl
자체 PDF + JSON 메타데이터 ──────────┘ (chunk_id 보존)
↓ build_index.py
BM25 인덱스 (kiwipiepy) + FAISS 벡터 (mpnet 다국어 768d, fastembed ONNX GPU)
↓ retrieval.py (Hybrid RRF + Jina Reranker v2 multilingual + doc_diversity)
Top-5 distinct docs
↓ local_llm.py (Qwen2.5-7B-Instruct Q4_K_M, llama-cpp CUDA, 외부 API 금지)
근거 발췌 답변
↓ app.py (FastAPI)
REST API (/predict 공모전 포맷, /search, /answer, /classify-violation, /predict-sanction)
| 공식 요건 | 본 시스템 |
|---|---|
| 외부 API 호출 금지 | ✅ 로컬 GGUF (urllib·SDK 둘 다 외부 호출 없음) |
| 8B 이하 LLM | ✅ Qwen2.5-7B-Instruct Q4_K_M (4.3 GB) |
| 응답 30초 이내 | ✅ 30초 deadline 마진 확보 |
| 정확히 5개 chunk_id 반환 | ✅ /predict 포맷 강제 (중복·외부 ID 검증) |
| 공식 chunk_id 보존 | ✅ load_official_data.py 가 변경 없이 통과 |
| 평가 환경 인터넷 차단 | ✅ Dockerfile 에 모델·임베딩 번들 |
| 배열 순서 = ranking | ✅ RRF score 내림차순 정렬 |
# 1. 공식 ZIP 압축 해제 → 어댑터로 변환
python src/load_official_data.py data/official chunks_official.jsonl
# 2. BM25 인덱스 빌드 (kiwipiepy 형태소)
python src/build_index.py chunks_official.jsonl index_official --model nlpai-lab/KURE-v1
# 3. 한국어 SOTA Dense 인덱스 빌드 (KURE-v1, bge-m3 fine-tune, GPU 권장)
python src/build_dense_st.py chunks_official.jsonl index_dense_kure --model nlpai-lab/KURE-v1 --batch 16
# 4. 서버 기동 (EXAONE-3.5-7.8B 자동 탐지, KURE Dense 사용)
.\start_lite.ps1
# 브라우저 http://localhost:8000/ → 시연
# 5. Docker 이미지 빌드 (평가 환경 호환)
docker build -t kftc-track2:submit .
docker save -o kftc-track2.tar kftc-track2:submit
# kftc-track2.tar 를 ftcdatacontest@korea.kr 로 전송구현물 점검 체크리스트
- /predict 가 정확히 5 chunk_id 반환 (
curl http://localhost:8000/predict -d '{"query":"..."}'로 확인) - chunk_id 모두 공식 corpus 에 존재 (재청킹·재명명 안 했는지)
- 응답 시간 < 30초 (warmup 포함)
- Docker 이미지에 인터넷 fetch 코드 없음 (
HF_HUB_OFFLINE=1,TRANSFORMERS_OFFLINE=1설정됨) - LLM 외부 API 코드 제거 (
google.generativeai,groq등 미사용)
- Python 3.10 이상
- 메모리 8GB 이상 (임베딩 단계에서 사용)
- 디스크 5GB 이상 여유 (인덱스 + 모델 캐시)
Linux / macOS (bash):
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtWindows (PowerShell):
python -m venv .venv
.\.venv\Scripts\Activate.ps1
# 실행 정책 오류가 나면: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
pip install -r requirements.txt대회에서 제공한 의결서 데이터(243MB)를 다운로드 받아 data/ 폴더에 압축 해제합니다.
data/
├─ <사건명>.pdf
├─ <사건명>_metadata.json
├─ ...
대회 데이터가 아직 없으면 src/make_sample_data.py로 합성 의결서 PDF·JSON
3건을 생성해 전체 파이프라인을 검증할 수 있습니다.
python src/make_sample_data.py data생성물: data/sample_2024-001.pdf 외 PDF 3개, 동명 _metadata.json 3개.
이후 3-1 단계부터 동일하게 실행하면 됩니다.
아래 명령은 프로젝트 루트(
Kftc/)에서 실행하는 것을 가정합니다.
python src/preprocess.py data chunks.jsonl출력: chunks.jsonl — 청크 단위 JSONL
기본 (사전학습 임베딩):
python src/build_index.py chunks.jsonl indexfine-tuned 임베딩 사용 (3-7 단계 후):
python src/build_index.py chunks.jsonl index --model models/embedding_ft출력: index/bm25.pkl, index/faiss.bin, index/meta.jsonl, index/model_name.txt
python src/retrieval.py index "다단계판매업자 미등록 위반"# 1) Qwen2.5-7B-Instruct Q4_K_M (4.47 GB)
$Url = "https://huggingface.co/bartowski/Qwen2.5-7B-Instruct-GGUF/resolve/main/Qwen2.5-7B-Instruct-Q4_K_M.gguf"
$Dest = "C:\lsc\Kftc\models\llm\Qwen2.5-7B-Instruct-Q4_K_M.gguf"
New-Item -ItemType Directory -Path C:\lsc\Kftc\models\llm -Force | Out-Null
Invoke-WebRequest -Uri $Url -OutFile $Dest -UseBasicParsing
# 2) llama-cpp-python 설치 (CPU wheel)
pip install --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu llama-cpp-python
# 3) 환경변수 설정
$env:LLM_GGUF_PATH = $Dest다른 8B 이하 모델 사용 시
LLM_GGUF_PATH만 교체하면 됨. Llama 시리즈는 사용자 정책상 미사용.
| 모델 | 역할 | 베이스 | 데이터 |
|---|---|---|---|
| Embedding (도메인 적응) | 의결서 검색 정확도 ↑ | intfloat/multilingual-e5-small | 자가합성 (질의↔본문) 360 페어 |
| 위반유형 분류기 | 임의 사실관계 → 위반유형 멀티라벨 | klue/roberta-base | 청크 본문 + 메타데이터 라벨 |
| 조치유형 예측기 | 사실관계 → 처분 카테고리 단일라벨 | klue/roberta-base | 의결서 단위 |
| Re-ranker (CrossEncoder) | RRF 후 정밀 재정렬 | BAAI/bge-reranker-base | (질의, 정답, hard negative) 트리플 |
python src/synthetic_qa.py chunks.jsonl pairs.jsonlpython src/finetune_embedding.py pairs.jsonl models/embedding_ft `
--base intfloat/multilingual-e5-small --epochs 3 --batch-size 8python src/train_classifier.py chunks.jsonl models/violation_clf `
--base klue/roberta-base --epochs 5python src/train_sanction.py chunks.jsonl models/sanction_clf `
--base klue/roberta-base --epochs 8python src/train_reranker.py chunks.jsonl pairs.jsonl models/reranker `
--base BAAI/bge-reranker-base --epochs 2python src/load_official_data.py data/official chunks_official.jsonl
python src/build_index.py chunks_official.jsonl index --model models/embedding_ft중요: 공식 chunk_id 는 절대 변경하지 않습니다 (변경 시 자동 실격).
python app.py
# 또는
uvicorn app:app --host 0.0.0.0 --port 8000기본 바인딩: http://localhost:8000
| 엔드포인트 | 메서드 | 설명 |
|---|---|---|
/ |
GET | 프론트엔드 (vanilla HTML/CSS/JS) |
/static/* |
GET | 정적 자원 |
/health |
GET | 서비스 상태 + 인덱스 통계 + 학습된 모델 로드 여부 |
/search |
POST | 하이브리드 검색만 (LLM 호출 없음) |
/answer |
POST | 검색 + LLM 답변 + 근거 인용 |
/classify-violation |
POST | 위반유형 멀티라벨 분류기 추론 |
/predict-sanction |
POST | 조치유형 예측기 추론 |
/predict |
POST | 공모전 제출 포맷 (chunk_id 5개 반환) |
/docs |
GET | Swagger UI |
# 헬스체크
curl http://localhost:8000/health
# 검색만
$body = @{ query = "다단계판매업자 미등록"; top_k = 3 } | ConvertTo-Json
Invoke-RestMethod -Uri http://localhost:8000/search -Method POST `
-ContentType "application/json; charset=utf-8" -Body $body
# 공모전 포맷 예측
$body = @{ query = "프랜차이즈 본사가 물품 구매를 강요하면 위법인가요?" } | ConvertTo-Json
Invoke-RestMethod -Uri http://localhost:8000/predict -Method POST `
-ContentType "application/json; charset=utf-8" -Body $bodydocker build -t kftc-track2:submit .
docker run -p 8000:8000 kftc-track2:submit[Unit]
Description=KFTC RAG API
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/kftc
EnvironmentFile=/opt/kftc/.env
ExecStart=/opt/kftc/.venv/bin/uvicorn app:app --host 0.0.0.0 --port 8000 --workers 2
Restart=on-failure
[Install]
WantedBy=multi-user.target| 파일 | 역할 |
|---|---|
src/load_official_data.py |
공식 ZIP → chunk_id 보존 JSONL 변환 |
src/build_index.py |
BM25 + FAISS 인덱스 구축 |
src/build_dense_st.py |
SentenceTransformer 기반 Dense 인덱스 |
src/retrieval.py |
Hybrid 검색 + RRF + Reranker |
src/local_llm.py |
로컬 GGUF LLM 추론 (외부 API 없음) |
src/synthetic_qa.py |
학습용 (질의, 정답청크) 페어 합성 |
src/finetune_embedding.py |
임베딩 도메인 fine-tuning |
src/train_classifier.py |
위반유형 멀티라벨 분류기 학습 |
src/train_reranker.py |
Cross-encoder re-ranker 학습 |
app.py / app_lite.py |
FastAPI 서버 |
static/index.html |
프론트엔드 진입점 |
- Re-ranker 고도화 — bge-reranker-v2-m3 로 Top-30 → Top-5 정밀 재정렬
- 유사 사건 추천 — 의결서 임베딩 간 유사도로 "이 사건과 비슷한 다른 의결서" 기능
- 시계열 통계 — 업종별·기간별 심결 추이 시각화
- 답변 캐싱 — 같은 질의 반복 시 Redis로 응답 속도 개선
본 시스템은 BM25 + Dense + RRF 하이브리드 검색과, 근거 문단 인용을 강제하는 설명가능한 RAG(Explainable RAG) 구조를 결합하여 법률 AI의 환각 위험을 구조적으로 억제하는 것을 차별점으로 합니다.