Career-HY RAG ์์คํ ์ ๋ค์ํ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ์คํํ์ฌ ๊ฒ์ ์ฑ๋ฅ์ ์ต์ ํํ๊ธฐ ์ํ ํ์ดํ๋ผ์ธ์ ๋๋ค.
- ํ๋ก์ ํธ ๊ฐ์
- ์ฃผ์ ๊ธฐ๋ฅ
- ์ค์น ๋ฐ ์ค์
- ์ฌ์ฉ ๋ฐฉ๋ฒ
- ์ํคํ ์ฒ
- ์ค์ ์๋น์ค ํตํฉ ๊ฐ์ด๋
- ์คํ ๊ฒฐ๊ณผ ๋ฐ ๋ถ์
- ์ฐธ๊ณ ์๋ฃ
- ๊ฒ์ ์ฑ๋ฅ ์ต์ ํ: ๋ค์ํ ์๋ฒ ๋ฉ ๋ชจ๋ธ, ์ฒญํน ์ ๋ต, ๊ฒ์ ์๊ณ ๋ฆฌ์ฆ ๋น๊ต
- ์ฒด๊ณ์ ์คํ: YAML ๊ธฐ๋ฐ ์ค์ ์ผ๋ก ์ฌํ ๊ฐ๋ฅํ ์คํ ํ๊ฒฝ ์ ๊ณต
- GT ๋ฐ์ดํฐ์ ์์ฑ: ํด๋ฌ์คํฐ๋ง ๊ธฐ๋ฐ Ground Truth ๋ฐ์ดํฐ์ ์๋ ์์ฑ
- ์ค์ ์๋น์ค ์ ์ฉ: StructuredDocumentLoader ๋ฐ ๋ฉํ๋ฐ์ดํฐ ํ์ฅ ๊ธฐ๋ฅ ์ ๊ณต
- ๐ณ Docker ๊ธฐ๋ฐ: ์ผ๊ด๋ ์คํ ํ๊ฒฝ ๋ณด์ฅ
- ๐พ ์๋ฒ ๋ฉ ์บ์ฑ: ๋์ผ ์ค์ ์ฌ์คํ ์ API ๋น์ฉ ์ ์ฝ
- ๐ ๋ค์ํ ํ๊ฐ ์งํ: Recall@k, Precision@k, MRR, MAP, nDCG@k
- ๐ง ๋ชจ๋ํ ์ํคํ ์ฒ: ์ฌ์ด ํ์ฅ์ฑ๊ณผ ์ ์ง๋ณด์
- ๐ YAML ์ค์ : ์ฝ๋ ์์ ์์ด ์คํ ํ๋ผ๋ฏธํฐ ์กฐ์
- ๐ฏ ์น์ ๋ณ ์ฒญํน: ์ฑ์ฉ๊ณต๊ณ ์ ๊ตฌ์กฐํ๋ ์ ๋ณด(์ฐ๋์ฌํญ, ์๊ฒฉ์๊ฑด, ์ฃผ์์ ๋ฌด) ํ์ฉ
- ๐ GT ์๋ ์์ฑ: ํด๋ฌ์คํฐ๋ง ๊ธฐ๋ฐ Ground Truth ๋ฐ์ดํฐ์ ์์ฑ
- ๐ ํตํฉ ํ์ดํ๋ผ์ธ: GT ์์ฑ ๊ด๋ จ ๊ธฐ๋ฅ์ ํ๋์ ํ์ดํ๋ผ์ธ์ผ๋ก ํตํฉ
- โก ๋์ ํ๊ฐ: ์ ๋ต ๊ฐ์์ ๋ฐ๋ฅธ ์๋ top_k ์กฐ์
- ๐ ๋ฉํ๋ฐ์ดํฐ ํ์ฅ: deadline, start_date, crawling_time ์ง์
์ฒด๊ณ์ ์ด๊ณ ์ฌํ ๊ฐ๋ฅํ RAG ์คํ์ ์ํ ์ข ํฉ ํ์ดํ๋ผ์ธ์ ๋๋ค.
- ๋ค์ํ ์๋ฒ ๋ฉ ๋ชจ๋ธ ์ง์: OpenAI, Snowflake ๋ฑ
- ๋ค์ํ ์ฒญํน ์ ๋ต: Recursive, Fixed, No Chunk
- ๋ฒกํฐ ๊ฒ์ ์์คํ : ChromaDB, FAISS
- ์ข ํฉ ํ๊ฐ ์์คํ : ๊ฒ์ ์ฑ๋ฅ + LangSmith ์ ์ฑํ๊ฐ
- Recall@k: ์ ์ฒด ๊ด๋ จ ๋ฌธ์ ์ค ์์ k๊ฐ ๊ฒ์ ๊ฒฐ๊ณผ์ ํฌํจ๋ ๊ด๋ จ ๋ฌธ์์ ๋น์จ
- Precision@k: ์์ k๊ฐ ๊ฒ์ ๊ฒฐ๊ณผ ์ค ์ค์ ๋ก ๊ด๋ จ ์๋ ๋ฌธ์์ ๋น์จ
- MRR@k: ๊ฐ ์ฟผ๋ฆฌ์ ์ฒซ ๋ฒ์งธ ๊ด๋ จ ๋ฌธ์ ์์์ ์ญ์ ํ๊ท (์์ k๊ฐ ๋ด)
- MAP: ๋ชจ๋ ๊ด๋ จ ๋ฌธ์ ์์๋ฅผ ๊ณ ๋ คํ ์ข ํฉ์ ์ฑ๋ฅ
- nDCG@k: ์์๊ฐ ๋์์๋ก ๋ ์ค์ํ๋ค๊ณ ๊ฐ์ ํ ์ฑ๋ฅ ์ธก์
- R-recall: Recall@(์ ๋ต๊ฐ์) - ์ ๋ต ๋ฌธ์ ๊ฐ์๋งํผ์ recall ๊ณ์ฐ
- Hit@k_count: ์์ k๊ฐ ๊ฒ์ ๊ฒฐ๊ณผ ์ค ์ ๋ต ๋ฌธ์์ ๊ฐ์
- ๋์ top_k ์ง์: R-recall ๊ณ์ฐ์ ์ํด ์ ๋ต ๊ฐ์์ ๋ฐ๋ผ top_k ์๋ ์กฐ์
evaluation_top_k = min(max(base_top_k, gt_count), 60)- ์ ๋ต์ด ๋ง์ ์ฟผ๋ฆฌ์์๋ ์ ํํ ํ๊ฐ ๊ฐ๋ฅ
- Recommendation Quality: ์ถ์ฒ ํ์ง ์ ๋ฐ
- Personalization Score: ๊ฐ์ธํ ์์ค
- Response Helpfulness: ๋์ ์ ๋
- Profile Alignment: ํ๋กํ ์ผ์น๋
์ฑ์ฉ๊ณต๊ณ ์ ๊ตฌ์กฐํ๋ ์ ๋ณด๋ฅผ ํ์ฉํ ์น์ ๋ณ ์ฒญํน ์์คํ ์ ๋๋ค.
- ์น์ ๋ณ ์ฒญํน: ์ฐ๋์ฌํญ(preferred), ์๊ฒฉ์๊ฑด(qualifications), ์ฃผ์์ ๋ฌด(job_duties) ๋ณ๋ ์ฒ๋ฆฌ
- JobPostParser:
unstructured๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ธฐ๋ฐ PDF ํ์ฑ - ๋ฉํ๋ฐ์ดํฐ ๋ณด์กด: ์๋ณธ JSON ๋ฉํ๋ฐ์ดํฐ ์ ์ฒด ๋ณด์กด (deadline, start_date, crawling_time ํฌํจ)
- Context ์ฃผ์
: ๊ฐ ์ฒญํฌ์
[ํ์ฌ: ...] [์ง๋ฌด: ...]์๋ ์ถ๊ฐ
- ์ด ์ฒญํฌ: 2,039๊ฐ (1,473๊ฐ ๋ฌธ์)
- ์น์
๋ณ ๋ถํฌ:
preferred: 1,036๊ฐ (50.8%)qualifications: 398๊ฐ (19.5%)job_duties: 279๊ฐ (13.7%)full_text(fallback): 326๊ฐ (16.0%)
from implementations.loaders.structured_loader import StructuredDocumentLoader
loader = StructuredDocumentLoader(
strategy="fast", # ๋๋ "hi_res"
target_sections=["preferred", "qualifications", "job_duties"],
include_context=True
)
chunks = loader.load_from_documents(documents)์ฑ์ฉ๊ณต๊ณ ์ ์๊ฐ ์ ๋ณด๋ฅผ ํฌํจํ ํ์ฅ๋ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ง์ํฉ๋๋ค.
- deadline: ์ฑ์ฉ๊ณต๊ณ ๋ง๊ฐ์ผ
- start_date: ์ฑ์ฉ๊ณต๊ณ ์์์ผ
- crawling_time: ๋ฐ์ดํฐ ํฌ๋กค๋ง ์๊ฐ
- ๊ธฐ๋ณธ ํ๋: rec_idx, title, company, url, tags ๋ฑ
- StructuredDocumentLoader: ๋ชจ๋ ์ฒญํฌ์ ์๋ณธ ๋ฉํ๋ฐ์ดํฐ ์ ์ฒด ๋ณด์กด
- Fallback ์ฒญํฌ: ๊ฒฝ๋/์ผ๋ฐ fallback ๋ชจ๋ ๋ฉํ๋ฐ์ดํฐ ๋ณด์กด
- ๋ฒกํฐ DB ์ ์ฅ: ChromaDB์ ๋ฉํ๋ฐ์ดํฐ ์ ์ฒด ์ ์ฅ
- ์๋ต ์์ฑ: Response Generator์์ ๋ฉํ๋ฐ์ดํฐ ํ์ฉ
# ์ฒญํฌ ๋ฉํ๋ฐ์ดํฐ ํ์ธ
chunk = chunks[0]
print(chunk.metadata.get("deadline")) # ๋ง๊ฐ์ผ
print(chunk.metadata.get("start_date")) # ์์์ผ
print(chunk.metadata.get("crawling_time")) # ํฌ๋กค๋ง ์๊ฐ- ํ๋กฌํํธ์ ๋ง๊ฐ์ผ ์ ๋ณด ์๋ ํฌํจ
- ์๋ต ์์ฑ ์ ์๊ฐ ์ ๋ณด ํ์ฉ ๊ฐ๋ฅ
ํด๋ฌ์คํฐ๋ง ๊ธฐ๋ฐ Ground Truth ๋ฐ์ดํฐ์ ์๋ ์์ฑ ์์คํ ์ ๋๋ค.
- Phase 1: ์ค๋ถ๋ฅ ๊ธฐ๋ฐ ์ด๊ธฐ ํด๋ฌ์คํฐ ์์ฑ
- Phase 2: ์ ์ฌ ์ค๋ถ๋ฅ ๋ณํฉ (๊ท์น ๊ธฐ๋ฐ)
- Phase 3: ๋ํ ๋ฌธ์ ์ ํ (์ฟผ๋ฆฌ-๋ฌธ์ ์ ์ฌ๋ ๊ธฐ๋ฐ)
- Phase 4: ํต๊ณ ๋ฐ ๊ฒฐ๊ณผ ์ ์ฅ
clustering_results_tag_based/*_classification.json: ๋๋ถ๋ฅ๋ณ ๋ถ๋ฅ ๊ฒฐ๊ณผsimilarity_rules_template.json: ์ ์ฌ ์ค๋ถ๋ฅ ๋ณํฉ ๊ท์น
gt_generation_results/gt_clusters.json: ํด๋ฌ์คํฐ ์ ๋ณด ๋ฐ ๋ํ ๋ฌธ์gt_generation_results/gt_clusters_summary.csv: ํด๋ฌ์คํฐ ์์ฝ ์ ๋ณดgt_generation_results/gt_generation_statistics.txt: ํต๊ณ ์ ๋ณด
# ์ ์ฒด ํ์ดํ๋ผ์ธ ์คํ (๊ธฐ๋ณธ)
python gt_generation_pipeline.py
# CSV โ JSONL ๋ณํ
python gt_generation_pipeline.py --convert-csv data/gt.csv data/output.jsonl
# ํ๊ฐ์ฉ ๋ฐ์ดํฐ ์์ฑ
python gt_generation_pipeline.py --create-eval data/gt_analysis.csv data/eval.jsonl
# ๊ท์น ๊ฒ์ฆ๋ง ์คํ
python gt_generation_pipeline.py --validate-rules- ์ ์ฌ๋ ๊ท์น ๊ฒ์ฆ: ๊ท์น ํ์ผ์ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ์ปค๋ฒ๋ฆฌ์ง ๋ถ์
- CSV โ JSONL ๋ณํ: GT CSV๋ฅผ ํ๊ฐ ํ์ดํ๋ผ์ธ ํ์์ผ๋ก ๋ณํ
- ํ๊ฐ์ฉ ๋ฐ์ดํฐ ์์ฑ: GT Analysis CSV๋ฅผ ํ๊ฐ์ฉ JSONL๋ก ๋ณํ
- ๋ํ ๋ฌธ์ ์ ํ: SentenceTransformer ๊ธฐ๋ฐ ์ฟผ๋ฆฌ-๋ฌธ์ ์ ์ฌ๋ ๊ณ์ฐ
ํ๊ทธ + ์ ๋ชฉ ๊ธฐ๋ฐ์ผ๋ก ์ฑ์ฉ๊ณต๊ณ ๋ฅผ ์ง๋ฌด๋ณ๋ก ํด๋ฌ์คํฐ๋งํ๋ ์์คํ ์ ๋๋ค.
- ํ๊ทธ ๊ธฐ๋ฐ ๋ถ๋ฅ: ๋๋ถ๋ฅ/์ค๋ถ๋ฅ ์๋ ํ ๋น
- ๋ค์ค ์นดํ ๊ณ ๋ฆฌ ์ง์: ํ๋์ ๋ฌธ์๊ฐ ์ฌ๋ฌ ๋๋ถ๋ฅ์ ์ํ ์ ์์
- UMAP + HDBSCAN: ์ฐจ์ ์ถ์ ๋ฐ ํด๋ฌ์คํฐ๋ง
python job_clustering_pipeline.pyclustering_results_tag_based/: ๋๋ถ๋ฅ๋ณ ๋ถ๋ฅ ๊ฒฐ๊ณผclustering_results/: ํด๋ฌ์คํฐ๋ง ๊ฒฐ๊ณผ ๋ฐ ์๊ฐํ
- Python 3.8 ์ด์
- Docker ๋ฐ Docker Compose
- AWS ์๊ฒฉ ์ฆ๋ช (S3 ์ ๊ทผ์ฉ)
- OpenAI API Key (์๋ฒ ๋ฉ ๋ฐ LLM์ฉ)
git clone <repository-url>
cd Experiment.env ํ์ผ ์์ฑ:
# OpenAI API
OPENAI_API_KEY=your_api_key
# AWS S3
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_DEFAULT_REGION=ap-northeast-2
# LangSmith (์ ํ)
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=your_langsmith_keydocker-compose build
docker-compose up -dpip install -r requirements.txt
# ํด๋ฌ์คํฐ๋ง/GT ์์ฑ์ฉ ์ถ๊ฐ ์์กด์ฑ
pip install -r requirements_clustering.txtExperiment/
โโโ configs/ # ์คํ ์ค์ ํ์ผ๋ค
โ โโโ baseline_search.yaml
โ โโโ new_eval_baseline.yaml
โ โโโ new_eval_baseline_recursive.yaml
โโโ core/ # ํต์ฌ ํ์ดํ๋ผ์ธ
โ โโโ interfaces/ # ์ถ์ ์ธํฐํ์ด์ค (ABC)
โ โโโ pipeline.py # ๋ฉ์ธ ์คํ ํ์ดํ๋ผ์ธ
โ โโโ config.py # ์ค์ ๊ด๋ฆฌ
โโโ implementations/ # ๊ตฌํ์ฒด๋ค
โ โโโ embedders/ # ์๋ฒ ๋ฉ ๋ชจ๋ธ
โ โโโ chunkers/ # ์ฒญํน ์ ๋ต
โ โโโ retrievers/ # ๊ฒ์ ์์คํ
โ โโโ evaluators/ # ํ๊ฐ ์งํ ๊ณ์ฐ
โ โโโ loaders/ # StructuredDocumentLoader
โ โโโ parsers/ # JobPostParser
โโโ utils/ # ์ ํธ๋ฆฌํฐ
โ โโโ data_loader.py # S3 ๋ฐ์ดํฐ ๋ก๋
โ โโโ embedding_cache.py # ์๋ฒ ๋ฉ ์บ์ฑ ์์คํ
โ โโโ factory.py # ์ปดํฌ๋ํธ ํฉํ ๋ฆฌ
โโโ data/ # Ground Truth ๋ฐ์ดํฐ
โ โโโ gt_eval_fullquery_cluster_ids.jsonl
โโโ cache/ # ์๋ฒ ๋ฉ ์บ์ ์ ์ฅ์
โโโ results/ # ์คํ ๊ฒฐ๊ณผ
โโโ run_experiment.sh # ๋ฉ์ธ ์คํ ์คํ ์คํฌ๋ฆฝํธ
โโโ docker-compose.yml # Docker ๊ตฌ์ฑ
โโโ Dockerfile # Docker ์ด๋ฏธ์ง ์ ์
โโโ requirements.txt # Python ์์กด์ฑ
configs/ ๋๋ ํ ๋ฆฌ์ ์คํ ์ค์ ํ์ผ ์์ฑ:
# ์คํ ๊ธฐ๋ณธ ์ ๋ณด
experiment_name: "baseline"
description: "ํ์ฌ ์๋น์ค์ ๋์ผํ ๋ฒ ์ด์ค๋ผ์ธ ์ค์ "
output_dir: "results"
# ์๋ฒ ๋ฉ ์ค์
embedder:
type: "openai"
model_name: "text-embedding-ada-002"
batch_size: 5
# ์ฒญํน ์ค์
chunker:
type: "no_chunk"
chunk_size: null
chunk_overlap: null
# ๊ฒ์ ์์คํ
์ค์
retriever:
type: "chroma"
collection_name: "job-postings-baseline"
persist_directory: "/tmp/chroma_baseline"
top_k: 10
# LLM ์ค์
llm:
type: "openai"
model_name: "gpt-4o-mini"
temperature: 0.7
max_tokens: 1000
# ๋ฐ์ดํฐ ์ค์
data:
s3_bucket: "career-hi"
pdf_prefix: "initial-dataset/pdf/"
json_prefix: "initial-dataset/json/"
test_queries_path: "data/gt_eval_fullquery_cluster_ids.jsonl"
use_structured_loader: false # StructuredDocumentLoader ์ฌ์ฉ ์ฌ๋ถ
# ํ๊ฐ ์ค์
evaluation:
mode: "retrieval_only" # ๋๋ "dual"
metrics: ["recall@k", "precision@k", "mrr", "map", "ndcg@k"]
k_values: [1, 3, 5, 10]# Docker ํ๊ฒฝ
./run_experiment.sh configs/baseline_search.yaml
# ๋ก์ปฌ ํ๊ฒฝ
python run_experiment.py configs/baseline_search.yamlresults/: ์คํ ๊ฒฐ๊ณผ JSON ํ์ผ- LangSmith ์น ์ธํฐํ์ด์ค: https://smith.langchain.com
ํ๋ก์ ํธ์๋ ๋ค์ํ ์คํ ์๋๋ฆฌ์ค๋ฅผ ์ํ ์ค์ ํ์ผ์ด ํฌํจ๋์ด ์์ต๋๋ค.
- ์ฉ๋: ๊ธฐ๋ณธ ๊ฒ์ ์ฑ๋ฅ ํ๊ฐ ์คํ
- ํน์ง:
- Recursive Chunking ๋ฐฉ์ ์ฌ์ฉ (chunk_size: 700, chunk_overlap: 100)
- StructuredDocumentLoader ๋ฏธ์ฌ์ฉ (์ ์ฒด ํ ์คํธ ํ์ฑ)
- ๋ฐ์ดํฐ ๋ฒ์ : v3
- ์ฌ์ฉ ์๋๋ฆฌ์ค: ๊ธฐ๋ณธ ๊ฒ์ ์ฑ๋ฅ ๋ฒค์น๋งํฌ ์ธก์
- ์ฉ๋: StructuredDocumentLoader ๊ธฐ๋ฐ ์น์ ๋ณ ์ฒญํน ์คํ
- ํน์ง:
- StructuredDocumentLoader ์ฌ์ฉ (์น์ ๋ณ ์ฒญํน)
- ์ฒญํน ๋ฐฉ์: no_chunk (์น์ ๋จ์๋ก๋ง ๋ถํ )
- ํ๊ฒ ์น์ : preferred, qualifications, job_duties
- ๋ฐ์ดํฐ ๋ฒ์ : v4
- ์ฌ์ฉ ์๋๋ฆฌ์ค: ์น์ ๋ณ ๊ตฌ์กฐํ๋ ์ฒญํน์ ํจ๊ณผ ์ธก์
- ์ฉ๋: StructuredDocumentLoader + Recursive Chunking ํ์ด๋ธ๋ฆฌ๋ ์คํ
- ํน์ง:
- StructuredDocumentLoader ์ฌ์ฉ (์น์ ๋ณ ์ฒญํน)
- Recursive Chunking ์ถ๊ฐ ์ ์ฉ (chunk_size: 500, chunk_overlap: 75)
- ํ๊ฒ ์น์ : preferred, qualifications, job_duties
- ๋ฐ์ดํฐ ๋ฒ์ : v4
- ์ฌ์ฉ ์๋๋ฆฌ์ค: ํ์ด๋ธ๋ฆฌ๋ ์ฒญํน ์ ๋ต ์ฑ๋ฅ ํ๊ฐ
python job_clustering_pipeline.pypython gt_generation_pipeline.pygt_generation_results/gt_clusters.json: ํด๋ฌ์คํฐ ์ ๋ณดgt_generation_results/gt_clusters_summary.csv: ์์ฝ ์ ๋ณดgt_generation_results/gt_generation_statistics.txt: ํต๊ณ
python job_clustering_pipeline.py- ๋ฐ์ดํฐ ๋ก๋: ~5์ด
- ์๋ฒ ๋ฉ ์์ฑ: ~2-3๋ถ (์ฒซ ์คํ ์ ๋ชจ๋ธ ๋ค์ด๋ก๋: +30์ด)
- UMAP ์ฐจ์ ์ถ์: ~30์ด
- HDBSCAN ํด๋ฌ์คํฐ๋ง: ~10์ด
- ์ด ์์ ์๊ฐ: ์ฝ 3-4๋ถ
clustering_results_tag_based/: ๋๋ถ๋ฅ๋ณ ๋ถ๋ฅ ๊ฒฐ๊ณผclustering_results/: ํด๋ฌ์คํฐ๋ง ๊ฒฐ๊ณผ ๋ฐ ์๊ฐํ
core/pipeline.py: ๋ฉ์ธ ์คํ ํ์ดํ๋ผ์ธcore/config.py: ์ค์ ๊ด๋ฆฌcore/interfaces/: ์ถ์ ์ธํฐํ์ด์ค ์ ์
implementations/embedders/: ์๋ฒ ๋ฉ ๋ชจ๋ธ ๊ตฌํimplementations/chunkers/: ์ฒญํน ์ ๋ต ๊ตฌํimplementations/retrievers/: ๊ฒ์ ์์คํ ๊ตฌํimplementations/evaluators/: ํ๊ฐ ์งํ ๊ณ์ฐimplementations/loaders/: StructuredDocumentLoaderimplementations/parsers/: JobPostParser
utils/data_loader.py: S3 ๋ฐ์ดํฐ ๋ก๋utils/embedding_cache.py: ์๋ฒ ๋ฉ ์บ์ฑutils/factory.py: ์ปดํฌ๋ํธ ํฉํ ๋ฆฌ
S3 ๋ฐ์ดํฐ ๋ก๋
โ
StructuredDocumentLoader (์น์
๋ณ ์ฒญํน)
โ
์๋ฒ ๋ฉ ์์ฑ (์บ์ฑ)
โ
๋ฒกํฐ DB ์ ์ฅ (ChromaDB/FAISS)
โ
๊ฒ์ ๋ฐ ํ๊ฐ
โ
๊ฒฐ๊ณผ ์ ์ฅ
- ์ ์ฒด ์ฟผ๋ฆฌ์ ๋ํ ๊ฒ์ ์ฑ๋ฅ ์ธก์
- ์ง์ ์งํ: Recall@k, Precision@k, MRR@k, MAP, nDCG@k, R-recall, Hit@k_count
- ๋์ top_k: ์ ๋ต ๊ฐ์์ ๋ฐ๋ผ ๊ฒ์ ๋ฒ์ ์๋ ์กฐ์ (์ต๋ 60๊ฐ)
- ํ๊ฐ ์์: ์ฌ์ฉ์ ์์ฒญ ์์๋๋ก ์งํ ๊ณ์ฐ ๋ฐ ์ถ๋ ฅ
- ์ํ ์ฟผ๋ฆฌ์ ๋ํ ์๋ต ์์ฑ ๋ฐ ์ ์ฑํ๊ฐ
- Profile-based Sampling (15๊ฐ ๊ณ ์ ํ๋กํ)
- 4๊ฐ์ง ์ ์ฑ ํ๊ฐ ์งํ:
- Recommendation Quality: ์ถ์ฒ ํ์ง ์ ๋ฐ
- Personalization Score: ๊ฐ์ธํ ์์ค
- Response Helpfulness: ๋์ ์ ๋
- Profile Alignment: ํ๋กํ ์ผ์น๋
- GT ์ ๋ขฐ์ฑ ๋ถ์กฑ: ๊ธฐ์กด GT๋ ์ ๋ต ๋ฐ์ดํฐ๊ฐ 5๊ฐ๋ก ์ ํ๋์ด ์ ๋ขฐ์ฑ์ด ๋ฎ์
- ๋ ธ์ด์ฆ ๋ฌธ์ : ๋จ์ ํ ์คํธ ์ฒญํน์ผ๋ก ๋ฌด์๋ฏธํ ํ ์คํธ ํฌํจ โ ๋ ธ์ด์ฆ๊ฐ ๋ง์
- ํ๊ทธ ๊ธฐ๋ฐ ํด๋ฌ์คํฐ๋ง: ํ ์คํธ์์ tag ์ ๋ณด๋ฅผ ์ด์ฉํด์ ์ ๋ต ๊ตฐ์ง ์์ฑ
- StructuredDocumentLoader ๊ตฌํ: JobPostParser๋ฅผ ํ์ฉํด ์น์
๋ณ ์ฒญํน
- ์น์
ํ์
๋ณ๋ก ์ฒญํฌ ๋ถ๋ฆฌ:
preferred,qualifications,job_duties - ๊ฐ chunk์ ์น์ ํ์ ๋ฉํ๋ฐ์ดํฐ ํฌํจ
- ์น์
ํ์
๋ณ๋ก ์ฒญํฌ ๋ถ๋ฆฌ:
- ์ค์ ํ์ผ:
configs/baseline_search.yaml - Chunker:
recursive(chunk_size: 700, chunk_overlap: 100) - StructuredDocumentLoader: ์ฌ์ฉ ์ํจ
- ๋ชฉ์ : ๊ธฐ๋ณธ ๊ฒ์ ์ฑ๋ฅ ๋ฒค์น๋งํฌ ์ธก์
- ๋ฐ์ดํฐ ๋ฒ์ : v3
- ์ฒ๋ฆฌ๋ ๋ฌธ์ ์: 4,121๊ฐ
- ์ค์ ํ์ผ:
configs/new_eval_baseline.yaml - Chunker:
no_chunk(StructuredDocumentLoader๊ฐ ์ํ) - StructuredDocumentLoader: ์ฌ์ฉ (
use_structured_loader: true) - Target Section:
preferred,qualifications,job_duties - ๋ชฉ์ : ์น์ ๋ณ ์ฒญํน ํจ๊ณผ ์ธก์
- ๋ฐ์ดํฐ ๋ฒ์ : v4
- ์ฒ๋ฆฌ๋ ๋ฌธ์ ์: 2,503๊ฐ
- ์ค์ ํ์ผ:
configs/new_eval_baseline_recursive.yaml - Chunker:
recursive(chunk_size: 500, chunk_overlap: 75) - StructuredDocumentLoader: ์ฌ์ฉ (
use_structured_loader: true) - Target Section:
preferred,qualifications,job_duties - ๋ชฉ์ : ์น์ ๋ณ ์ฒญํน + Recursive chunking ํ์ด๋ธ๋ฆฌ๋ ํจ๊ณผ ์ธก์
- ๋ฐ์ดํฐ ๋ฒ์ : v4
- ์ฒ๋ฆฌ๋ ๋ฌธ์ ์: 3,124๊ฐ
| Metric | Baseline | ์น์ ์ฒญํน | ์น์ +Recursive | ์ต์ฐ์ |
|---|---|---|---|---|
| ndcg@10 | 0.2205 | 0.2591 | 0.2547 | โ ์น์ ์ฒญํน |
| mrr@10 | 0.4227 | 0.4606 | 0.4468 | โ ์น์ ์ฒญํน |
| precision@3 | 0.2479 | 0.2308 | 0.2350 | โ Baseline |
| precision@5 | 0.2128 | 0.2205 | 0.2077 | โ ์น์ ์ฒญํน |
| precision@10 | 0.1628 | 0.1679 | 0.1667 | โ ์น์ ์ฒญํน |
| precision@20 | 0.1314 | 0.1353 | 0.1321 | โ ์น์ ์ฒญํน |
| recall@10 | 0.1061 | 0.1086 | 0.1090 | โ ์น์ +Recursive |
| recall@20 | 0.1547 | 0.1609 | 0.1591 | โ ์น์ ์ฒญํน |
| r_recall | 0.1204 | 0.1345 | 0.1297 | โ ์น์ ์ฒญํน |
| hit@10_count | 1.628 | 1.680 | 1.667 | โ ์น์ ์ฒญํน |
| hit@20_count | 2.628 | 2.705 | 2.641 | โ ์น์ ์ฒญํน |
๊ฒฐ๋ก : ์น์ ๋ณ ์ฒญํน์ด ๋๋ถ๋ถ์ ๊ฒ์ ์ฑ๋ฅ ์งํ์์ ์ต์ฐ์ ์ฑ๋ฅ์ ๋ณด์
| Metric | Baseline | ์น์ ์ฒญํน | ์น์ +Recursive | ์ต์ฐ์ |
|---|---|---|---|---|
| recommendation_quality | 4.2 | 4.2 | 4.3 | โ ์น์ +Recursive |
| personalization_score | 4.5 | 4.5 | 4.4 | โ Baseline/์น์ ์ฒญํน |
| response_helpfulness | 4.4 | 4.2 | 4.3 | โ Baseline |
| profile_alignment | 4.0 | 4.0 | 4.1 | โ ์น์ +Recursive |
๊ฒฐ๋ก : ์์ฑ ํ์ง์ ์ธ ๋ฐฉ๋ฒ ๋ชจ๋ ์ ์ฌํ๋, ์น์ +Recursive๊ฐ ์ฝ๊ฐ ์ฐ์ (์ฐจ์ด๋ ๋งค์ฐ ์์)
๊ธฐ์กด GT (v1): RAG ์ ๋์ ํ๊ฐ๋ฅผ ์ํด Agent๋ก ์์ฑํ GT์ ๋๋ค. GT ํ๋๋น ์ด๊ธฐ ์์งํด๋์ ์ฑ์ฉ๊ณต๊ณ ๋ฐ์ดํฐ์ ์์ ์ฑ์ฉ ๊ณต๊ณ ๋ฅผ ๋๋ค์ผ๋ก ์ ํํ ํ ์ ์ฌ๋๊ฐ ๋์ 4๊ฐ์ ์ฑ์ฉ ๊ณต๊ณ ๋ฅผ ์ถ๊ฐํ์ฌ ์ด 5๊ฐ์ ์ฑ์ฉ๊ณต๊ณ ๋ฅผ ์ ๋ณํฉ๋๋ค. Agent๋ 5๊ฐ์ ์ฑ์ฉ ๊ณต๊ณ ๋ฅผ ํ์ธํ ํ ํด๋น ์ฑ์ฉ ๊ณต๊ณ ์ ์ง์ํ์ ๋ฒํ ํ์ ํ๋กํ ๋ฐ์ดํฐ๋ฅผ ์์ฑํฉ๋๋ค. ์ด๋ ํ์๋ํ๊ต ์๊ฐํธ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํ ์ ์๋ API๋ฅผ tool๋ก ์ด์ฉํฉ๋๋ค.
์๋ก์ด GT (v2): ํ๊ทธ ๊ธฐ๋ฐ ํด๋ฌ์คํฐ๋ง์ ํตํด ์์ฑ๋ GT์ ๋๋ค. ๊ฐ์ ํด๋ฌ์คํฐ์ ์ํ ๋ชจ๋ ์ฑ์ฉ๊ณต๊ณ ๋ฅผ ์ ๋ต์ผ๋ก ์ฌ์ฉํ์ฌ ๊ฐ๋ณ ํฌ๊ธฐ์ GT๋ฅผ ์์ฑํฉ๋๋ค. ํ๊ท 19.8๊ฐ์ ์ ๋ต ๋ฌธ์๋ฅผ ํฌํจํ๋ฉฐ, ์ต๋ 57๊ฐ๊น์ง ํฌํจํ ์ ์์ต๋๋ค. ํด๋ฌ์คํฐ ์ ์ฒด๋ฅผ ํฌํจํ๋ฏ๋ก ๊ธฐ์กด GT๋ณด๋ค ์ ๋ขฐ์ฑ์ด ๋์ต๋๋ค.
- ํ์ผ:
data/gt_eval_fullquery_cluster_ids.jsonl - ์ด ์ฟผ๋ฆฌ ์: 79๊ฐ
- ํ๊ท GT ๋ฌธ์ ์: 19.8๊ฐ
- ์ต๋ GT ๋ฌธ์ ์: 57๊ฐ
- ์ต์ GT ๋ฌธ์ ์: 1๊ฐ
- ํด๋ฌ์คํฐ ๊ธฐ๋ฐ: ๊ฐ์ ํด๋ฌ์คํฐ์ ๋ชจ๋ ๋ฌธ์๋ฅผ GT๋ก ์ฌ์ฉ
- ๊ฐ๋ณ ํฌ๊ธฐ: ์ฟผ๋ฆฌ๋ณ GT ๋ฌธ์์๊ฐ ๋ค๋ฆ
- ์ค๋ณต ํ์ฉ: ๊ฐ์ ๋ฌธ์๊ฐ ์ฌ๋ฌ ์ฟผ๋ฆฌ์ GT์ ํฌํจ๋ ์ ์์
์ง๋ฌธ: [์ฌ์ฉ์ ์ง๋ฌธ]
์ ๊ณต: [์ ๊ณต ์ ๋ณด]
๊ด์ฌ ์ง๋ฌด: [๊ด์ฌ ์ง๋ฌด]
์๊ฒฉ์ฆ: [์๊ฒฉ์ฆ ๋ชฉ๋ก]
๋์๋ฆฌ/๋์ธํ๋: [ํ๋ ๋ด์ญ]
์๊ฐ ์ด๋ ฅ:
[๊ฐ์๋ช
] | [ํต์ฌ ์ญ๋] | [๊ฐ์ ๊ฐ์] | [ํ์ต ๋ชฉํ]- GT ๋ฌธ์ ์๊ฐ top_k๋ณด๋ค ํฐ ๊ฒฝ์ฐ, R-recall ๊ณ์ฐ ๋ถ๊ฐ
- ์: GT 32๊ฐ, top_k=20 โ ์ต๋ recall 20/32 = 0.625
gt_count = len(ground_truth)
evaluation_top_k = min(max(base_top_k, gt_count), 60)
# ์ต์ top_k์ GT ๊ฐ์ ์ค ํฐ ๊ฐ ์ฌ์ฉ, ์ต๋ 60๊ฐ๋ก ์ ํ์ด๋ฅผ ํตํด ๊ฐ๋ณ ํฌ๊ธฐ GT์ ๋ํด ์ ํํ R-recall ๊ณ์ฐ์ด ๊ฐ๋ฅํฉ๋๋ค.
์ด ์น์ ์ ForkExperiment์์ ๊ฐ๋ฐํ ๊ธฐ๋ฅ์ ์ค์ Career-HY ์๋น์ค์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํฉ๋๋ค.
pip install unstructured[pdf]>=0.10.0implementations/loaders/structured_loader.pyโ ์๋น์ค์loaders/structured_loader.pyimplementations/parsers/job_post_parser.pyโ ์๋น์ค์parsers/job_post_parser.py
- S3 JSON ๋ฉํ๋ฐ์ดํฐ์์
deadline,start_date,crawling_timeํฌํจ - ์๋ณธ ๋ฉํ๋ฐ์ดํฐ ์ ์ฒด ๋ณด์กด
from loaders.structured_loader import StructuredDocumentLoader
loader = StructuredDocumentLoader(
strategy="fast",
target_sections=["preferred", "qualifications", "job_duties"],
include_context=True
)
chunks = loader.load_from_documents(documents)- ๋ฉํ๋ฐ์ดํฐ ์ ์ฒด ์ ์ฅ (primitive ํ์ ๋ง)
- ChromaDB/FAISS ํธํ์ฑ ํ์ธ
RecommendedJob๋ชจ๋ธ์deadline,start_date,crawling_timeํ๋ ์ถ๊ฐ- ๊ฒ์ ๊ฒฐ๊ณผ์์ ๋ฉํ๋ฐ์ดํฐ ์ถ์ถ
- ํ๋กฌํํธ์ ์๊ฐ ์ ๋ณด ํฌํจ
- ํ์ฌ ์๋น์ค ์ฝ๋๋ฒ ์ด์ค ๊ตฌ์กฐ ํ์
- S3 JSON ๋ฉํ๋ฐ์ดํฐ ๊ตฌ์กฐ ํ์ธ
- ๊ธฐ์กด ๋ฒกํฐ DB ๋ฐฑ์
- ํ ์คํธ ํ๊ฒฝ ์ค๋น
- StructuredDocumentLoader ํ์ผ ์ถ๊ฐ
- JobPostParser ํ์ผ ์ถ๊ฐ
- ๋ฐ์ดํฐ ๋ก๋ ์์
- ์ฒญํน ๋ก์ง ๋ณ๊ฒฝ
- ๋ฒกํฐ DB ์ ์ฅ ๋ก์ง ํ์ธ
- Response Generator ์์
- ํ๋กฌํํธ ๋น๋ ์์
- ๋จ์ ํ ์คํธ ์์ฑ
- ํตํฉ ํ ์คํธ ์์ฑ
- ์ฑ๋ฅ ํ ์คํธ
- A/B ํ ์คํธ ์ค๋น
- ์คํ ์ด์ง ํ๊ฒฝ ๋ฐฐํฌ
- ๋ชจ๋ํฐ๋ง ์ค์
- ํ๋ก๋์ ๋ฐฐํฌ
- ๋กค๋ฐฑ ๊ณํ ์๋ฆฝ
- ํ์ฑ ์๋:
fast์ ๋ต์ดhi_res๋ณด๋ค ๋น ๋ฆ (์ฝ 10๋ฐฐ) - ์ ํ๋:
hi_res๊ฐ ๋ ์ ํํ์ง๋ง ๋๋ฆผ - ๊ถ์ฅ: ํ๋ก๋์
์์๋
fast์ฌ์ฉ
- ๊ธฐ์กด ๋ฒกํฐ DB๋ ์๋ก์ด ๊ตฌ์กฐ์ ํธํ๋์ง ์์ ์ ์์
- ๋ง์ด๊ทธ๋ ์ด์ ๋๋ ์ ์ปฌ๋ ์ ์์ฑ ํ์
- PDF ํ์ฑ ์คํจ ์ fallback ์ฒ๋ฆฌ ํ์
- ๋ฉํ๋ฐ์ดํฐ ๋๋ฝ ์
None์ฒ๋ฆฌ
์์ธํ ๋ด์ฉ์ SERVICE_INTEGRATION_GUIDE.md ์ฐธ๊ณ (ํฅํ ํตํฉ ๋ฌธ์๋ก ์ด๋ ์์ )
EXPERIMENT_SUMMARY_20251203.md: ์คํ ์์ฝ ๋ฐ ๋ณ๊ฒฝ์ฌํญGT_GENERATION_STRATEGY.md: GT ์์ฑ ์ ๋ต ์์ธCLUSTERING_README.md: ํด๋ฌ์คํฐ๋ง ๊ฐ์ด๋STRUCTURED_CHUNKS_REPORT.md: ๊ตฌ์กฐํ ์ฒญํฌ ๋ฆฌํฌํธ
- Docker ๊ณต์ ๋ฌธ์
- OpenAI Embeddings API
- ChromaDB ๋ฌธ์
- LangChain Text Splitters
- RAG ํ๊ฐ ์งํ ๊ฐ์ด๋
- S3 ๋ฒํท:
career-hi - PDF ๊ฒฝ๋ก:
initial-dataset/pdf/(1,473๊ฐ ํ์ผ) - JSON ๊ฒฝ๋ก:
initial-dataset/json/(1,473๊ฐ ํ์ผ) - Ground Truth:
data/gt_eval_fullquery_cluster_ids.jsonl(79๊ฐ ์ฟผ๋ฆฌ)
์ด ํ๋ก์ ํธ๋ Career-HY ํ์ ๋ด๋ถ ์คํ์ฉ์ผ๋ก ๊ฐ๋ฐ๋์์ต๋๋ค.
์์ฑ์ผ: 2025-12-03
๋ฒ์ : 2.0 (ํตํฉ ๋ฌธ์)