사용자가 올린 영수증 사진에서 금액과 항목을 뽑아내야 하는 일이 있었다. OCR 라이브러리를 붙일까 하다가, 어차피 뒤에서 정리 작업을 또 해야 하니 그냥 Claude에 이미지째로 던져보기로 했다. 결과가 생각보다 깔끔해서 그 뒤로 스크린샷 분석이나 차트 읽기에도 계속 쓰고 있다.
핵심은 image 블록
텍스트만 보내던 messages.create에 image 타입 콘텐츠 블록을 하나 끼워 넣으면 끝이다. 이미지는 base64로 인코딩해서 넣거나, 접근 가능한 URL을 그대로 줄 수도 있다.
써보기
로컬 파일을 base64로 읽어 넣는 경우다.
import Anthropic from '@anthropic-ai/sdk';
import { readFileSync } from 'fs';
const client = new Anthropic();
const imageData = readFileSync('./receipt.jpg').toString('base64');
const msg = await client.messages.create({
model: 'claude-opus-4-8',
max_tokens: 1024,
messages: [
{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/jpeg',
data: imageData,
},
},
{
type: 'text',
text: '이 영수증에서 각 항목명과 금액, 총액을 JSON으로 뽑아줘.',
},
],
},
],
});
console.log(msg.content[0].type === 'text' && msg.content[0].text);
media_type은 실제 파일 형식과 맞춰야 한다. PNG면 image/png, WebP면 image/webp. 여기를 대충 넣으면 인식이 안 되니 주의.
실제로 쓰면서 알게 된 것
- 이미지를 먼저, 질문을 나중에. 콘텐츠 배열에서
image블록을 앞에 두고 텍스트 지시를 뒤에 붙이면 답변 품질이 더 안정적이었다. 여러 장을 비교시킬 땐 각 이미지 앞에 "이미지 1:" 같은 라벨 텍스트를 끼워두면 헷갈리지 않는다. - 해상도를 무작정 키우지 말자. 긴 변 기준 약 1568px가 넘어가면 내부에서 리사이즈된다. 그보다 큰 원본을 보내봐야 토큰만 더 먹고 이득이 없다. 미리 줄여서 보내는 게 비용에 낫다.
- 구조화가 필요하면 스키마를 못박아라. "JSON으로"라고만 하면 가끔 설명을 덧붙인다. 원하는 필드를 예시까지 딱 정해주거나 structured outputs를 같이 쓰면 파싱이 편해진다.
정리
이미지 분석에 별도 OCR 파이프라인을 깔 필요 없이, image 블록 하나면 스크린샷·차트·영수증을 바로 텍스트나 JSON으로 뽑을 수 있다. media_type 정확히 맞추기, 이미지 먼저·질문 나중, 해상도는 1568px 안쪽으로 — 이 세 가지만 챙기면 대부분의 문서 읽기 작업은 무난하게 끝난다.