upstage-research-cli: 논문 PDF에서 평가 코드까지, Upstage API로 연구 워크플로우 자동화하기

현서·2026년 4월 6일
post-thumbnail

Upstage Document Parse + Information Extraction + Solar LLM을 엮어서
학술 논문을 연구 실행 가능한 결과물로 변환하는 CLI + Agent Skill을 만들었다.

GitHub: hseo1o2/upstage-research-cli
npm: upstage-research-cli


📚 목차

  1. 배경: 왜 이걸 만들었는가
  2. 왜 일반 PDF 파서로는 부족한가
  3. paper-method-analyzer: 방법론 비교와 연구 적용 포인트 도출
  4. paper-eval-codegen: 실험 섹션을 평가 코드로 변환하기
  5. DP / IE / Solar: API 역할 분담
  6. 실제 실행 예시: DocVQA와 Search Arena
  7. 구현 과정에서의 피봇과 트러블슈팅
  8. Agent Skills 표준과 배포
  9. 현재 동작 범위와 확장 과제

1. 배경

현재 코드 구현이 필요한 실험 연구를 진행하며 논문을 작성하고 있다. 이 과정에서는 구현, 실행, 검증, 수정으로 이어지는 이러한 작업 사이클이 계속 반복됐다.

선행연구 논문 10~20편 읽기
  → 각 방법론 비교 정리
  → 타깃 논문의 평가 지표 / 베이스라인 수치 옮겨 적기
  → 평가 코드 직접 작성
  → 다음 실험...

연구 자체가 아닌데 시간을 상당히 잡아먹는 작업이었다. 특히 논문마다 평가 지표 표기가 다르고, 베이스라인 수치를 표에서 직접 찾아 복사하는 과정이 반복될수록 자동화가 필요하다는 생각이 들었다.

거기다 더 근본적인 문제가 있었다. 일반 PDF 파서로 학술 논문을 읽으면 텍스트가 순서 없이 뒤섞이거나 결과 테이블이 날아간다. 그 상태로 LLM에 넘기면 잘못된 수치를 추출하거나 엉뚱한 답을 내놓는다.

이 두 가지를 해결하기 위해 upstage-research-cli를 만들었다.


2. 왜 일반 PDF 파서로는 안 되는가

학술 논문 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는 이 구조를 보존한 채 마크다운으로 변환한다. 이 단계가 무너지면 이후 추출과 생성도 신뢰할 수 없다.


3. paper-method-analyzer

선행연구 논문 여러 편을 입력하면, 방법론을 비교한 표와 내 연구에 적용할 수 있는 포인트를 출력하는 스킬이다.

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_architecturetraining_strategy를 같은 자리에서 비교할 수 있어야 한다. 그래야 "이 논문은 어떤 구조를 택했고, 왜 그런 선택을 했는가"를 빠르게 읽을 수 있다.

출력도 같은 철학으로 설계했다. 단순히 논문마다 요약문을 길게 붙이는 대신, 먼저 비교표로 큰 그림을 잡고, 그 다음에 각 논문 상세 분석을 붙이고, 마지막에 내 연구에 적용 가능한 포인트를 정리하는 순서다.

즉 결과물은 보통 이런 흐름을 가진다.

  1. 비교표에서 전체 지형을 본다.
    어떤 논문이 어떤 구조를 쓰고, 어떤 데이터셋에서, 어떤 지표로 평가했는지 한 번에 훑는다.

  2. 각 논문 상세 분석에서 맥락을 읽는다.
    단순 필드 값만으로는 알 수 없는 핵심 아이디어, 실험 설계, 한계, 내 연구와의 관련성을 본다.

  3. 마지막으로 적용 포인트를 본다.
    여기서 중요한 건 "이 논문이 무엇에 관한 것인가"라는 설명을 넘어서, "이 논문에서 어떤 부분을 내 연구 설계에 가져올 수 있는가" 를 정리해주는 것이다.

예를 들어 어떤 논문은 모델 구조 자체보다도 데이터셋 구성 방식이 더 유용할 수 있고, 어떤 논문은 성능 수치보다 평가 프로토콜이 더 중요할 수 있다.

paper-method-analyzer는 이런 차이를 반영해서, 논문 전체를 균등하게 요약하기보다 연구자가 실제로 재사용할 만한 부분이 무엇인지로 결과를 정렬하려고 했다.

그래서 이 스킬의 목표는 단순 논문 요약이 아니라, 문헌 정리 → 비교 → 적용 아이디어 도출까지 이어지는 중간 레이어를 자동화하는 것이다.


4. paper-eval-codegen

논문 한 편을 입력하면, 실험 섹션에서 평가 지표와 베이스라인 수치, 데이터셋 정보를 추출하고 그 결과를 바탕으로 즉시 실행 가능한 평가 코드를 생성하는 스킬이다. --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 이름과 숫자가 엇갈리는 문제가 자주 발생했다. 특히 논문 테이블은 다음과 같은 경우가 많다.

  • 축약된 헤더 (Acc, EM, HR@10)
  • 여러 metric이 한 셀에 함께 들어감
  • validation / test split이 한 표에 같이 존재
  • baseline과 upper bound가 같은 표에 혼재

이런 상황에서는 자유 생성형 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 } }
  ]
}

이렇게 구조화된 결과를 기반으로 나중 단계에서:

  • 어떤 metric family로 evaluator를 렌더링할지 결정하고
  • 어떤 baseline 값을 체크리스트에 넣을지 정하고
  • 어떤 evidence snippet이 이 결과를 뒷받침하는지 연결한다.

결국 paper-eval-codegen의 핵심은 단순 코드 생성이 아니라, 논문의 실험 섹션을 구조화된 평가 사양으로 바꾼 뒤, 그 사양을 실행 가능한 코드와 검증 가능한 리포트로 변환하는 것에 가깝다.


5. API 역할 분담

API역할선택 이유
Document Parse논문 PDF → 구조화 마크다운수식 · 테이블 · 알고리즘 박스가 뭉개지지 않음
Information Extraction고정 필드 정확 추출스키마 기반이라 수치 추출 일관성이 높음
Solar LLM분석 · 비교 · 코드 생성유연한 컨텍스트 이해와 생성

세 API가 직렬로 연결되는 구조인데, 각 단계가 독립적이라 디버깅이 쉽다. DP 출력이 이상하면 파싱 단계 문제, IE 결과가 이상하면 스키마 문제, 코드가 이상하면 Solar 프롬프트 문제로 좁혀진다.


6. 실제 실행 예시

paper-eval-codegen — DocVQA

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를 결정론적으로 렌더링할 수 있었기 때문이다.

한 가지 짚어두고 싶은 건, 생성되는 결과가 전체 학습 파이프라인이 아니라는 점이다. 목표는 연구자가 재현 환경에 꽂을 수 있는 평가 레이어를 만드는 것이다. "이 논문의 기준으로 내 모델을 평가하려면 이렇게 하면 된다"는 시작점을 제공한다.


paper-method-analyzer — Search Arena

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. 검색 컨텍스트 크기 / 추론 설정 비교 실험 구성 방법 참조

논문 한 편을 넣어도 단순 요약이 아니라 연구 계획 관점의 적용 포인트까지 연결되는 게 이 스킬의 목표다.


7. 구현 과정에서의 피봇과 트러블슈팅

처음 설계에서 실제로 어긋났던 지점들을 기록해두려고 한다. 이 프로젝트는 처음부터 지금과 같은 구조를 갖고 있었던 것이 아니다. 논문 작업과 실험을 진행하면서 여러 번 부딪히고, 그때마다 설계를 수정하고 방향을 틀어온 끝에 지금의 형태가 만들어졌다.

7.1. "LLM이 평가 코드를 전부 생성할 수 있다"는 가정

초기 설계는 단순했다.

Document Parse로 논문 파싱
  → Information Extraction으로 평가 필드 추출
  → Solar에게 코드 생성 요청

이 흐름 자체는 깔끔해 보였다. 문제는 “그럴듯한 코드”와 “실제로 논문 기준에 맞게 동작하는 코드” 사이에 생각보다 큰 차이가 있다는 점이었다.

실제로 초반에 생성된 코드에서는 이런 문제가 반복됐다.

  • undefined 헬퍼 함수를 참조
  • placeholder 변수가 그대로 남아 있음
  • import한 라이브러리와 실제 호출 코드가 불일치
  • 컴파일은 되지만 논문이 정의한 metric과 다른 계산을 수행
  • baseline 비교가 논문 표와 맞지 않음

즉 Solar가 잘 못해서라기보다, 자유 생성으로는 안전한 evaluator를 계속 일관되게 만들기 어렵다는 것이 드러난 것이다. 특히 BLEU, ROUGE, F1, mAP처럼 이미 잘 알려진 평가 패밀리에 대해서는 LLM이 매번 새 코드를 쓰게 할 이유가 없었다.

그래서 방향을 바꿨다.

  • 일반적인 평가 패밀리는 결정론적으로 렌더링
  • Solar는 커스텀 지표, 프로토콜이 복잡한 경우, 혹은 LLM-as-judge prompt 생성처럼 생성이 필요한 부분에만 사용

이 전환이 품질을 가장 많이 끌어올린 변화였다. 결과적으로 paper-eval-codegen은 “LLM이 코드 전체를 쓰는 도구”에서, LLM이 구조화된 guidance를 주고 로컬 로직이 안전하게 evaluator를 만드는 도구로 바뀌었다.

7.2. "도메인 분류가 라우팅 기준이 될 수 있다"는 가정

초기에는 도메인을 먼저 분류하고, 그에 맞는 라이브러리를 선택하는 구조를 생각했다.

  • NLP → evaluate, sacrebleu
  • CV → torchmetrics, pycocotools
  • RL → gymnasium
  • 추천시스템 → ranking metric evaluator

처음에는 이게 합리적으로 보였다. 하지만 실제 논문을 여러 장르에 걸쳐 돌려보니 도메인 경계가 생각보다 불분명했다.

예를 들면:

  • 멀티모달 논문은 NLP인지 CV인지 단정하기 어렵고
  • RAG 논문은 NLP인지 IR인지 평가 관점에 따라 달라지며
  • 그래프 분류 논문은 일반 classification과 GNN evaluation이 섞인다
  • OCR/VQA 논문은 document understanding, vision, language가 동시에 걸친다

도메인 분류가 틀리면 그 다음 단계 전체가 흔들린다. 잘못된 라이브러리를 선택하고, 잘못된 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개를 넘기 시작했을 때, 그건 기능이 늘어난 게 아니라 과적합 위험이 커지고 있다는 신호였다.

7.3. "컴파일 성공이 곧 품질"이라는 가정

초기에는 npm run build나 Python py_compile 같은 식으로 코드가 돌기만 하면 어느 정도 성공이라고 생각했다. 하지만 실제로는 컴파일 성공과 의미적 정확성이 완전히 다른 문제였다.

예를 들어:

  • baseline 수치가 논문의 잘못된 row에서 추출될 수 있고
  • evidence snippet이 출력 내용을 실제로 뒷받침하지 않을 수 있고
  • metric 이름은 맞지만, 논문이 의도한 formula나 프로토콜과는 다를 수 있다

즉, 실행 가능성은 필요조건일 뿐 충분조건이 아니었다.

그래서 검증 리포트를 별도 산출물로 추가했다.

verification:
  compile:metrics_grounded:     4/5   # NDCG@10 미확인
  baselines_grounded:   3/4   # BERT4Rec row 불일치
  evidence_score:       0.81

이 검증 리포트가 들어가고 나서 품질 대화의 기준이 바뀌었다.

예전에는:

  • “돌아가냐?”
  • “출력이 그럴듯하냐?”

였다면,

이후에는:

  • “어느 metric이 실제 evidence에 grounded되어 있나?”
  • “baseline row가 논문 표와 맞나?”
  • “protocol fidelity가 낮은 이유가 뭔가?”
  • “이 결과를 지금 release gate에 넣어도 되나?”

를 묻게 됐다.

즉 검증 리포트는 단순한 부가 기능이 아니라, 이 프로젝트를 “한 번 잘 돌아가는 데모”에서 지속적으로 품질을 관리할 수 있는 시스템으로 바꾼 장치였다.

7.4. "몇 편만 잘 되면 충분하다"는 가정

초반에는 한두 편의 논문으로 수동 확인하는 방식이 많았다. 문제는 어떤 수정이:

  • 특정 논문 하나만 통과시키는 패치인지
  • 논문 패밀리 전체를 안정화하는 일반화인지

구분할 방법이 없다는 점이었다.

그래서 결국 회귀 manifest와 regression-batch를 추가했다.

upstage-research regression-batch \
  --manifest fixtures/regression/release-gate-main-47.json \
  --cache-only

이후에는 품질을 “느낌”으로 말하지 않고:

  • compile/run 비율
  • verification score
  • dataset / baseline / evidence fidelity
  • release gate vs challenge set

으로 분리해서 볼 수 있게 됐다.

이게 중요했던 이유는, 이 프로젝트의 진짜 난이도가 출력을 한 번 잘 만드는 것이 아니라 다양한 논문 20편, 50편, 그 이상에서 품질을 유지하는 것이었기 때문이다.


8. Agent Skills 표준과 배포

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가 실제로 구현한 것이 일치해야 하기 때문이다.


9. 현재 동작 범위와 확장 과제

안정적으로 동작하는 영역

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

profile
LLM부터 풀스택까지, 만들면서 기록합니다

0개의 댓글