블로그 목록
ai

Claude Files API로 파일 한 번만 올리고 계속 재사용하기

계약서 PDF 하나를 놓고 Claude한테 이것저것 물어보는 기능을 만들고 있었다. 요약해줘, 위약금 조항 찾아줘, 자동 갱신 조건 알려줘 — 이런 식으로 같은 문서에 대해 질문이 여러 번 이어진다.

처음엔 그냥 매 요청마다 PDF를 base64로 인코딩해서 document 블록에 넣어 보냈다. 동작은 하는데, 문서가 조금만 커져도 요청 페이로드가 부담스럽다. 20페이지짜리 PDF를 질문 다섯 번 하면 같은 문서를 다섯 번 실어 보내는 셈이다. 인코딩 비용도 매번 다시 들고.

Files API — 한 번 올리고 id로 참조

Files API를 쓰면 파일을 미리 한 번 업로드해두고, 이후 요청에서는 file_id로만 참조한다. 문서 본문을 다시 실어 보낼 필요가 없다.

베타라서 files-api-2025-04-14 헤더가 필요한데, SDK를 쓰면 betas 옵션만 넣으면 알아서 붙여준다.

import Anthropic, { toFile } from '@anthropic-ai/sdk';
import fs from 'fs';

const client = new Anthropic();

// 1. 파일을 한 번만 업로드
const uploaded = await client.beta.files.upload({
  file: await toFile(fs.createReadStream('contract.pdf'), undefined, {
    type: 'application/pdf',
  }),
  betas: ['files-api-2025-04-14'],
});
// uploaded.id → "file_011C..." —이걸 저장해두고 재사용

이제 질문할 때마다 문서 대신 file_id만 넘긴다.

async function ask(fileId: string, question: string) {
  const res = await client.beta.messages.create({
    model: 'claude-opus-5',
    max_tokens: 16000,
    betas: ['files-api-2025-04-14'],
    messages: [
      {
        role: 'user',
        content: [
          { type: 'text', text: question },
          { type: 'document', source: { type: 'file', file_id: fileId } },
        ],
      },
    ],
  });
  return res.content.find((b) => b.type === 'text')?.text ?? '';
}

await ask(uploaded.id, '위약금 조항을 찾아서 정리해줘');
await ask(uploaded.id, '자동 갱신 조건이 있으면 알려줘');

알아두면 좋은 것들

몇 가지 실무에서 걸렸던 지점.

  • 베타 헤더는 업로드와 참조 양쪽에 다 필요하다. 업로드할 때만 넣고 messages.create에서 빠뜨리면 file_id를 못 알아본다. 처음에 이걸로 한참 헤맸다.
  • 블록 타입을 파일 종류에 맞춰야 한다. PDF·텍스트는 document, 이미지는 image. MIME 타입과 안 맞으면 거절당한다.
  • 파일은 지울 때까지 남아 있다. 조직당 100GB 한도가 있으니, 일회성으로 쓴 파일은 client.beta.files.delete(id)로 정리해주는 게 좋다. 업로드·조회·삭제 자체는 과금 안 되고, 메시지에서 실제로 읽힌 토큰만 입력 토큰으로 계산된다.

정리하면, 같은 파일에 질문이 두 번 이상 이어질 때는 Files API가 답이다. base64를 매번 붙이던 코드에서 업로드 한 줄만 앞으로 빼면 되니 도입 비용도 거의 없었다.