언제 쓰나
수천 건을 LLM으로 판정해야 하고, 스크립트를 여러 번 다시 돌려야 할 때. 분류 기준을 다듬으며 반복 실행하는 상황이 기본 전제다.
정본 코드
~/Documents/IMOK/BTC_KOL_PoC/
classify_relevance_gpt.py 논문 주제 관련성 판정
classify_specialty_gpt.py ★ 전문분야 판정 (캐시 설계가 가장 성숙, 정본)
compare_specialty_models.py 모델 간 판정 비교
specialty_resolver.py, institution_resolver.py
collect_pubmed.py / collect_openalex.py / collect_koreamed.py
merge_wos.py / merge_openalex.py / merge_koreamed.py / merge_pmc.py
make_provenance.py, quality_report.py바로 쓰는 코드
python3 scripts/classify_cache.py --compare cache_a.json cache_b.json --field sp
python3 scripts/classify_cache.py --stats specialty_cache.json --field spscripts/classify_cache.py — Cache(모델 기록 강제, todo(), drop_stale(), drop_model()) · compare()(일치율·불일치 조합) · call_with_param_fallback()(신형 배포의 파라미터 거부 대응). 모델 없이 저장하려 하면 ValueError 를 던진다 — 재현 불가 캐시를 만들지 못하게.
캐시가 설계의 중심이다
키(UT/PMID/DOI 등) → 판정 결과 를 JSON에 저장
재실행 시 캐시에 없는 것만 처리 → 재실행 비용 0todo = [r for r in recs if r["key"] not in cache] 한 줄이 전부다. 이게 없으면 기준을 조금 고칠 때마다 전체를 다시 돌리게 되고, 결국 아무도 기준을 안 고치게 된다.
캐시에 판정 모델을 반드시 기록해라
정본 코드의 주석에 실측 교훈이 그대로 남아 있다:
캐시는specialty_cache.json이며 어떤 모델이 판정했는지 함께 기록한다 —relevance_cache.json은 모델을 기록하지 않아 재현 확인이 불가능했다(2026-08-05 실측).
모델을 안 남기면 나중에 "이 판정이 어느 모델 것인지" 알 수 없고, 모델을 바꿔가며 실험한 결과가 한 파일에 뒤섞인다. 캐시 엔트리에는 결과 + 모델 + (가능하면) 판정 시각을 넣어라.
stale 무효화 경로를 만들어라
stale_keys(cache)가 더 이상 유효하지 않은 엔트리를 골라 지운다(예: 분류 체계가 바뀌어 site 값이 현재 목록에 없는 경우). 분류 카테고리를 바꿨는데 캐시가 옛 카테고리로 답하는 게 가장 찾기 어려운 버그다.
대조 실험은 캐시 파일을 갈아끼워서 한다: --cache other_cache.json.
모델 간 비교
compare_specialty_models.py가 두 캐시를 로드해 일치/불일치를 낸다. 모델을 바꾸기 전에 기존 모델과 얼마나 다르게 판정하는지 먼저 재라. 불일치 건만 사람이 보면 검수 대상이 확 준다.
classify_specialty_gpt.py와 compare_specialty_models.py 둘 다 selfcheck()를 가지고 있다. 분류 로직을 고치면 이걸 먼저 돌린다.
신형 모델 배포 대응
정본에 이런 처리가 있다:
# ponytail: 신형 배포는 max_tokens/temperature 를 거부한다. 첫 호출에서 걸리면 빼고 재시도.
kw = {"temperature": 0, "max_tokens": 300}
...
if "temperature" in msg and "unsupported" in msg.lower():
kw.pop("temperature", None)모델 파라미터를 하드코딩하고 예외 처리를 안 하면 배포 교체 때 전체가 죽는다. 거부당하면 그 파라미터를 빼고 한 번 더 시도하는 게 가장 싼 대응이다.
병렬 처리
run(recs, workers=8) — 8 워커 병렬. 캐시 쓰기는 워커 완료 시점에 메인에서 모아 한다. 여러 워커가 캐시 파일에 동시에 쓰면 깨진다.
입력 본문 구성
build_body(rec)가 제목·초록·저널·MeSH 등에서 판정에 필요한 것만 뽑아 프롬프트 본문을 만든다. max_tokens=300으로 출력을 제한한다 — 분류는 짧게 답해야 하고, 길면 파싱이 흔들린다.
load_exclude()로 제외 목록을 별도 관리한다. 사람이 "이건 아니다"라고 한 건을 코드가 아니라 데이터로 둔다.
다중 소스 수집·병합도 여기 있다
collect_*.py(PubMed/OpenAlex/KoreaMed) → merge_*.py(WoS/OpenAlex/KoreaMed/PMC) → make_provenance.py로 각 필드가 어느 소스에서 왔는지 출처를 남긴다. fill_doi.py/recover_doi.py로 빠진 DOI를 메운다.
통합 구현 자체는 biblio-analysis가 더 성숙하다. 이쪽은 소스가 더 많다(OpenAlex, PMC 추가).
연관 skill
biblio-analysis, cancer-type-classify(같은 대량 LLM 분류 + 피드백 패턴), paper-collect