Upstage Document Parse + Information Extraction + Solar LLM을 엮어서
학술 논문을 연구 실행 가능한 결과물로 변환하는 CLI + Agent Skill을 만들었다.
GitHub: hseo1o2/upstage-research-cli
npm: upstage-research-cli
현재 코드 구현이 필요한 실험 연구를 진행하며 논문을 작성하고 있다. 이 과정에서는 구현, 실행, 검증, 수정으로 이어지는 이러한 작업 사이클이 계속 반복됐다.
선행연구 논문 10~20편 읽기
→ 각 방법론 비교 정리
→ 타깃 논문의 평가 지표 / 베이스라인 수치 옮겨 적기
→ 평가 코드 직접 작성
→ 다음 실험...
연구 자체가 아닌데 시간을 상당히 잡아먹는 작업이었다. 특히 논문마다 평가 지표 표기가 다르고, 베이스라인 수치를 표에서 직접 찾아 복사하는 과정이 반복될수록 자동화가 필요하다는 생각이 들었다.
거기다 더 근본적인 문제가 있었다. 일반 PDF 파서로 학술 논문을 읽으면 텍스트가 순서 없이 뒤섞이거나 결과 테이블이 날아간다. 그 상태로 LLM에 넘기면 잘못된 수치를 추출하거나 엉뚱한 답을 내놓는다.
이 두 가지를 해결하기 위해 upstage-research-cli를 만들었다.
학술 논문 PDF는 일반 문서와 레이아웃 구조가 다르다.
┌────────────────────────────────────────────┐
│ Title / Abstract │
├──────────────┬─────────────────────────────┤
│ 2단 본문 │ 수식 (1), (2), (3)... │
│ ├─────────────────────────────┤
│ │ Algorithm 1 │
│ │ ┌──────────────────────┐ │
│ │ │ 1: for each x do │ │
│ │ └──────────────────────┘ │
├──────────────┴─────────────────────────────┤
│ Table 2. Main Results │
│ ┌──────────┬───────┬────────┬──────────┐ │
│ │ Model │ BLEU │ ROUGE │ BERTScore│ │
│ ├──────────┼───────┼────────┼──────────┤ │
│ │ Baseline │ 28.3 │ 41.2 │ 0.871 │ │
│ │ Ours │ 32.4 │ 44.7 │ 0.883 │ │
│ └──────────┴───────┴────────┴──────────┘ │
└────────────────────────────────────────────┘
일반 파서로 읽으면 2단 레이아웃이 섞이고, 알고리즘 박스 순서가 뒤틀리고, 테이블 행/열 관계가 무너진다. Upstage Document Parse는 이 구조를 보존한 채 마크다운으로 변환한다. 이 단계가 무너지면 이후 추출과 생성도 신뢰할 수 없다.
선행연구 논문 여러 편을 입력하면, 방법론을 비교한 표와 내 연구에 적용할 수 있는 포인트를 출력하는 스킬이다.
upstage-research analyze-methods paper1.pdf paper2.pdf paper3.pdf \
--context "시계열 이상 탐지 연구, transformer 기반 방법론 탐색 중"
파이프라인
논문 PDF 여러 개
↓ [Document Parse] 수식 · 테이블 · 알고리즘 박스 보존 파싱
↓ [Information Extraction] 방법론 핵심 필드 구조화 추출
↓ [Solar LLM] 비교 분석 + 내 연구 컨텍스트 기반 적용 포인트 제안
IE에 넘기는 스키마는 특정 분야 전용이 아니라, 방법론 논문을 비교할 때 거의 항상 필요한 공통 축을 기준으로 잡았다.
처음에는 더 많은 필드를 뽑아보려고 했지만, 실제로 여러 논문을 비교해보면 너무 세부적인 필드는 오히려 논문마다 표기 방식이 달라져 비교가 어려워졌다. 그래서 최종적으로는 다음처럼 비교에 직접 쓰이는 필드만 남겼다.
model_architecture: 모델이 어떤 구조를 중심으로 설계되었는지training_strategy: pretraining, finetuning, self-supervised learning, retrieval augmentation 같은 학습 방식datasets: 실험에 사용한 데이터셋main_contribution: 저자들이 주장하는 핵심 기여limitations: 논문 스스로 인정하는 한계나, 실험 설정상 드러나는 제약evaluation_metrics: 논문이 어떤 기준으로 성능을 입증하는지이 스키마의 목적은 "논문을 완벽하게 요약하는 것"이 아니다.
오히려 서로 다른 논문을 같은 축 위에 올려놓고 비교할 수 있게 만드는 것에 가깝다. 예를 들어 한 논문은 transformer 구조를, 다른 논문은 graph-based 구조를 쓰더라도 model_architecture와 training_strategy를 같은 자리에서 비교할 수 있어야 한다. 그래야 "이 논문은 어떤 구조를 택했고, 왜 그런 선택을 했는가"를 빠르게 읽을 수 있다.
출력도 같은 철학으로 설계했다. 단순히 논문마다 요약문을 길게 붙이는 대신, 먼저 비교표로 큰 그림을 잡고, 그 다음에 각 논문 상세 분석을 붙이고, 마지막에 내 연구에 적용 가능한 포인트를 정리하는 순서다.
즉 결과물은 보통 이런 흐름을 가진다.
비교표에서 전체 지형을 본다.
어떤 논문이 어떤 구조를 쓰고, 어떤 데이터셋에서, 어떤 지표로 평가했는지 한 번에 훑는다.
각 논문 상세 분석에서 맥락을 읽는다.
단순 필드 값만으로는 알 수 없는 핵심 아이디어, 실험 설계, 한계, 내 연구와의 관련성을 본다.
마지막으로 적용 포인트를 본다.
여기서 중요한 건 "이 논문이 무엇에 관한 것인가"라는 설명을 넘어서, "이 논문에서 어떤 부분을 내 연구 설계에 가져올 수 있는가" 를 정리해주는 것이다.
예를 들어 어떤 논문은 모델 구조 자체보다도 데이터셋 구성 방식이 더 유용할 수 있고, 어떤 논문은 성능 수치보다 평가 프로토콜이 더 중요할 수 있다.
paper-method-analyzer는 이런 차이를 반영해서, 논문 전체를 균등하게 요약하기보다 연구자가 실제로 재사용할 만한 부분이 무엇인지로 결과를 정렬하려고 했다.
그래서 이 스킬의 목표는 단순 논문 요약이 아니라, 문헌 정리 → 비교 → 적용 아이디어 도출까지 이어지는 중간 레이어를 자동화하는 것이다.
논문 한 편을 입력하면, 실험 섹션에서 평가 지표와 베이스라인 수치, 데이터셋 정보를 추출하고 그 결과를 바탕으로 즉시 실행 가능한 평가 코드를 생성하는 스킬이다. --include-prompt 옵션을 주면 LLM-as-judge 평가 프롬프트도 함께 출력된다.
upstage-research eval-codegen paper.pdf \
--lang python \
--framework pytorch \
--include-prompt
파이프라인
논문 PDF
↓ [Document Parse] 실험 섹션 + 결과 테이블 구조화 파싱
↓ [Information Extraction] 평가 지표 · 베이스라인 수치 · 데이터셋 정확 추출
↓ [Solar LLM] 평가 코드 생성 + LLM-as-judge 프롬프트 생성
이 스킬의 목표는 논문을 읽고 "어떤 평가를 했는지"를 설명하는 데서 끝나는 것이 아니다. 핵심은 그 논문을 재현하거나 내 실험에 맞게 변형할 때 바로 쓸 수 있는 평가 레이어를 만드는 것이다.
즉 출력은 보통 세 가지로 구성된다.
평가 코드: 논문이 사용한 지표를 계산하는 코드
재현 체크리스트: 데이터셋, 베이스라인, 구현 세부사항, 주의할 점
LLM-as-judge 프롬프트: 선택적으로 생성되는 자연어 평가 기준
여기서 중요한 건 “전체 학습 파이프라인”을 대신 생성하는 것이 아니라는 점이다. 이 스킬이 만드는 것은 모델 학습 코드가 아니라, 논문 기준으로 내 모델 출력을 평가하기 위한 실행 가능한 평가 artifact다.
연구자가 이미 학습 코드를 가지고 있을 때, 마지막 평가 단계만 논문 기준에 맞게 꽂아 넣을 수 있도록 하는 데 초점을 맞췄다.
이 과정에서 IE가 특히 중요하다. 실험 결과 테이블에서 수치를 추출할 때 Solar에 직접 “베이스라인 숫자를 뽑아줘”라고 요청하면, 행/열이 잘못 매핑되거나 metric 이름과 숫자가 엇갈리는 문제가 자주 발생했다. 특히 논문 테이블은 다음과 같은 경우가 많다.
이런 상황에서는 자유 생성형 LLM보다, 스키마를 명시하고 필드를 강제하는 Information Extraction이 훨씬 안정적이었다.
IE는 task_type, evaluation_metrics, datasets, baselines, implementation_details 같은 구조를 미리 정의해두고 그 안에 맞는 JSON을 반환하기 때문에, 이후 코드 생성 단계에서 훨씬 신뢰할 수 있는 입력이 된다.
예를 들면 추출 결과는 대략 이런 형태가 된다.
{
"evaluation_metrics": [
{ "name": "Accuracy", "value": 94.36 }
],
"baselines": [
{ "model_name": "Human Performance", "scores": { "Accuracy": 94.36 } }
]
}
이렇게 구조화된 결과를 기반으로 나중 단계에서:
결국 paper-eval-codegen의 핵심은 단순 코드 생성이 아니라, 논문의 실험 섹션을 구조화된 평가 사양으로 바꾼 뒤, 그 사양을 실행 가능한 코드와 검증 가능한 리포트로 변환하는 것에 가깝다.
| API | 역할 | 선택 이유 |
|---|---|---|
| Document Parse | 논문 PDF → 구조화 마크다운 | 수식 · 테이블 · 알고리즘 박스가 뭉개지지 않음 |
| Information Extraction | 고정 필드 정확 추출 | 스키마 기반이라 수치 추출 일관성이 높음 |
| Solar LLM | 분석 · 비교 · 코드 생성 | 유연한 컨텍스트 이해와 생성 |
세 API가 직렬로 연결되는 구조인데, 각 단계가 독립적이라 디버깅이 쉽다. DP 출력이 이상하면 파싱 단계 문제, IE 결과가 이상하면 스키마 문제, 코드가 이상하면 Solar 프롬프트 문제로 좁혀진다.
DocVQA: A Dataset for VQA on Document Images 논문을 대상으로 실행한 결과다.
upstage-research eval-codegen DocVQA-2007.00398.pdf \
--lang python --include-prompt
생성된 평가 코드의 핵심 부분은 이렇다.
# Metric: Accuracy | Baseline: Human Performance 94.36% (Table 3)
def normalize_answer(text: str) -> str:
return text.strip().lower()
def compute_accuracy(predictions: list, references: list) -> dict:
correct = sum(
normalize_answer(p) == normalize_answer(r)
for p, r in zip(predictions, references)
)
return {
"accuracy": round(correct / len(predictions) * 100, 2),
"human_ceiling": 94.36
}
재현 체크리스트에는 정확한 split 사용, 질문 카테고리별 분석, 정답 복수인 경우 처리 방식 등 논문의 숨겨진 전제들이 함께 올라왔다.
검증 결과는 metrics / dataset / baseline / evidence / protocol 모두 4/4였다. 이 케이스가 깔끔하게 통과된 이유는 평가 지표가 명확하고(Accuracy), 데이터셋 정체가 분명하고, evaluator를 결정론적으로 렌더링할 수 있었기 때문이다.
한 가지 짚어두고 싶은 건, 생성되는 결과가 전체 학습 파이프라인이 아니라는 점이다. 목표는 연구자가 재현 환경에 꽂을 수 있는 평가 레이어를 만드는 것이다. "이 논문의 기준으로 내 모델을 평가하려면 이렇게 하면 된다"는 시작점을 제공한다.
SEARCH ARENA: ANALYZING SEARCH-AUGMENTED LLMs 논문을 단일 입력으로 실행했다.
upstage-research analyze-methods SEARCH-ARENA.pdf \
--context "search-augmented LLM evaluation and citation reliability 연구 중"
분석 리포트가 뽑아낸 핵심 내용은 이렇다.
### 핵심 방법론
검색 강화 LLM 평가를 위한 크라우드소싱 아레나 + 공개 API 기반 자동 평가.
새 모델 학습이 아니라, 배포된 시스템과 설정을 평가하는 접근.
### 데이터셋
Search Arena, SimpleQA, BrowseComp, CORAL, WildChat
### 한계
주관적 선호도 의존, 인구통계적 편향, citation-surface 편향, 상관관계 한계
### 내 연구 적용 포인트
1. Search Arena 방식을 human-preference 벤치마킹에 재사용 가능
2. citation reliability 평가 프로토콜 설계 시 참고
3. 검색 컨텍스트 크기 / 추론 설정 비교 실험 구성 방법 참조
논문 한 편을 넣어도 단순 요약이 아니라 연구 계획 관점의 적용 포인트까지 연결되는 게 이 스킬의 목표다.
처음 설계에서 실제로 어긋났던 지점들을 기록해두려고 한다. 이 프로젝트는 처음부터 지금과 같은 구조를 갖고 있었던 것이 아니다. 논문 작업과 실험을 진행하면서 여러 번 부딪히고, 그때마다 설계를 수정하고 방향을 틀어온 끝에 지금의 형태가 만들어졌다.
초기 설계는 단순했다.
Document Parse로 논문 파싱
→ Information Extraction으로 평가 필드 추출
→ Solar에게 코드 생성 요청
이 흐름 자체는 깔끔해 보였다. 문제는 “그럴듯한 코드”와 “실제로 논문 기준에 맞게 동작하는 코드” 사이에 생각보다 큰 차이가 있다는 점이었다.
실제로 초반에 생성된 코드에서는 이런 문제가 반복됐다.
undefined 헬퍼 함수를 참조즉 Solar가 잘 못해서라기보다, 자유 생성으로는 안전한 evaluator를 계속 일관되게 만들기 어렵다는 것이 드러난 것이다. 특히 BLEU, ROUGE, F1, mAP처럼 이미 잘 알려진 평가 패밀리에 대해서는 LLM이 매번 새 코드를 쓰게 할 이유가 없었다.
그래서 방향을 바꿨다.
이 전환이 품질을 가장 많이 끌어올린 변화였다. 결과적으로 paper-eval-codegen은 “LLM이 코드 전체를 쓰는 도구”에서, LLM이 구조화된 guidance를 주고 로컬 로직이 안전하게 evaluator를 만드는 도구로 바뀌었다.
초기에는 도메인을 먼저 분류하고, 그에 맞는 라이브러리를 선택하는 구조를 생각했다.
evaluate, sacrebleutorchmetrics, pycocotoolsgymnasium처음에는 이게 합리적으로 보였다. 하지만 실제 논문을 여러 장르에 걸쳐 돌려보니 도메인 경계가 생각보다 불분명했다.
예를 들면:
도메인 분류가 틀리면 그 다음 단계 전체가 흔들린다. 잘못된 라이브러리를 선택하고, 잘못된 metric family를 타고, 결과적으로 논문과 다른 evaluator가 생성된다.
그래서 도메인은 더 이상 강한 기준으로 쓰지 않기로 했다. 최종적으로는:
task_type, 추출된 metric 이름, formula, dataset, evidence를 기반으로 수행config/ 하위 레지스트리로 분리즉 구조는 이런 식으로 바뀌었다.
config/
├── metric-kind-registry.json # 지표 패밀리 분류
├── eval-discovery-registry.json # alias 해소 (ACC → Accuracy)
└── evidence-alias-registry.json # 데이터셋 표기 정규화
이 전환의 중요한 의미는, “논문별 예외 처리”에서 “패밀리 기반 일반화”로 방향을 바꿨다는 점이다. 조건문이 50개를 넘기 시작했을 때, 그건 기능이 늘어난 게 아니라 과적합 위험이 커지고 있다는 신호였다.
초기에는 npm run build나 Python py_compile 같은 식으로 코드가 돌기만 하면 어느 정도 성공이라고 생각했다. 하지만 실제로는 컴파일 성공과 의미적 정확성이 완전히 다른 문제였다.
예를 들어:
즉, 실행 가능성은 필요조건일 뿐 충분조건이 아니었다.
그래서 검증 리포트를 별도 산출물로 추가했다.
verification:
compile: ✓
metrics_grounded: 4/5 # NDCG@10 미확인
baselines_grounded: 3/4 # BERT4Rec row 불일치
evidence_score: 0.81
이 검증 리포트가 들어가고 나서 품질 대화의 기준이 바뀌었다.
예전에는:
였다면,
이후에는:
를 묻게 됐다.
즉 검증 리포트는 단순한 부가 기능이 아니라, 이 프로젝트를 “한 번 잘 돌아가는 데모”에서 지속적으로 품질을 관리할 수 있는 시스템으로 바꾼 장치였다.
초반에는 한두 편의 논문으로 수동 확인하는 방식이 많았다. 문제는 어떤 수정이:
구분할 방법이 없다는 점이었다.
그래서 결국 회귀 manifest와 regression-batch를 추가했다.
upstage-research regression-batch \
--manifest fixtures/regression/release-gate-main-47.json \
--cache-only
이후에는 품질을 “느낌”으로 말하지 않고:
으로 분리해서 볼 수 있게 됐다.
이게 중요했던 이유는, 이 프로젝트의 진짜 난이도가 출력을 한 번 잘 만드는 것이 아니라 다양한 논문 20편, 50편, 그 이상에서 품질을 유지하는 것이었기 때문이다.
CLI만으로는 연구자가 직접 터미널을 열어서 커맨드를 실행해야 한다. AI 코딩 에이전트를 쓰는 환경에서는 자연어로 트리거할 수 있어야 한다고 생각해서, agentskills.io 오픈 표준을 함께 구현했다.
SKILL.md 하나로 Claude Code, OpenAI Codex, Cursor, GitHub Copilot 등 26개 이상의 에이전트에서 동작한다.
---
name: paper-eval-codegen
description: >-
논문 PDF의 실험 섹션을 파싱하여 즉시 실행 가능한 평가 코드와
LLM-as-judge 프롬프트를 생성합니다. "평가 코드", "eval code",
"논문 재현", "reproduce", "metric", "benchmark" 등의 맥락에서
자동으로 활성화됩니다.
compatibility:
tools:
- bash
---
설치는 한 커맨드로 끝난다.
npm install -g upstage-research-cli
cd my-research-project
upstage-research install --skills
# → .claude/skills/에 SKILL.md 복사, API 키 설정
이후 에이전트에서 "이 논문 평가 코드 뽑아줘"라고 입력하면 스킬이 자동으로 활성화된다. SKILL.md는 CLI의 핵심 동작이 안정화된 후 마지막에 작성했다. 스킬이 약속하는 워크플로우와 CLI가 실제로 구현한 것이 일치해야 하기 때문이다.
안정적으로 동작하는 영역
NLP, CV, 추천시스템, RL, 시계열 등 표준 벤치마크를 사용하는 논문. 평가 지표가 명확하고, 데이터셋 정체가 분명하고, evaluator를 결정론적으로 렌더링할 수 있는 경우.
아직 안 되는 영역
단백질 설계, 구조 생성 등 바이오 논문. TM-score, pLDDT 같은 지표는 외부 툴이 필요하고 평가 프로토콜이 논문마다 다르다. 이 케이스들은 현재 버전의 한계로 명시하고 별도 challenge set으로 분리해 추적하고 있다. 메인 릴리스 게이트와 섞이면 품질 신호가 노이즈가 되기 때문이다.
다음에 집중할 방향
프롬프트를 더 추가하는 것보다, 세 가지 방향이 우선이다. 첫째로 현재 fallback으로 처리되는 그래프 메트릭, 생성 다양성 지표 등을 결정론적 렌더링으로 끌어올리는 것. 둘째로 바이오 논문의 TM-score처럼 외부 도구 연동이 필요한 케이스 통합. 셋째로 베이스라인 수치가 어느 테이블 몇 행에서 왔는지를 더 정확하게 pinpoint하는 provenance 강화.
"그럴듯한 출력을 한 번 만드는 것"과 "50편의 논문에서 일관된 품질을 유지하는 것" 사이의 거리가 생각보다 컸다.
LLM 파트가 아니라 그 이후가 어려웠다. 모델이 답한 다음에 그 결과를 얼마나 신뢰할 수 있는지 측정하고, 신뢰할 수 없는 부분을 결정론적으로 대체하는 구조를 만드는 것 — 그게 이 프로젝트에서 실제로 어려웠던 부분이다.
GitHub: hseo1o2/upstage-research-cli
npm: upstage-research-cli