🧬 연구 허브

Skill

lit-relevance-classify 바로 실행

수천 건을 LLM으로 분류하고 결과를 캐시한다. 어느 모델이 판정했는지 함께 남겨 재현할 수 있다.

언제 쓰나

수천 건을 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 sp

scripts/classify_cache.pyCache(모델 기록 강제, todo(), drop_stale(), drop_model()) · compare()(일치율·불일치 조합) · call_with_param_fallback()(신형 배포의 파라미터 거부 대응). 모델 없이 저장하려 하면 ValueError 를 던진다 — 재현 불가 캐시를 만들지 못하게.

캐시가 설계의 중심이다

키(UT/PMID/DOI 등) → 판정 결과   를 JSON에 저장
재실행 시 캐시에 없는 것만 처리 → 재실행 비용 0

todo = [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.pycompare_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