얼마 전에 web_search 도구 얘기를 썼는데, 쓰다 보니 결이 다른 상황이 자꾸 생겼다. 검색은 "최신 React 버전 뭐야?"처럼 모델이 알아서 찾아 헤매야 할 때 좋다. 그런데 "이 링크 좀 읽고 요약해줘" 하면서 URL을 딱 던지는 경우엔 검색이 오히려 번거롭다. 검색어를 만들어 돌리고, 결과 목록에서 그 페이지를 다시 골라야 하니까.
이럴 때 쓰라고 있는 게 web_fetch 도구다. 검색이 아니라, 지정된 URL 하나를 서버에서 그대로 긁어와 본문을 모델 컨텍스트에 넣어준다. 내가 fetch 코드를 짜고, HTML을 파싱해서, 텍스트만 발라낸 뒤 프롬프트에 끼워 넣던 과정이 통째로 사라진다.
도구 선언
web_search와 똑같이 tools 배열에 넣는 게 전부다. 클라이언트에서 실행할 함수는 없다.
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const response = await client.messages.create({
model: 'claude-opus-4-8',
max_tokens: 4096,
tools: [{ type: 'web_fetch_20260209', name: 'web_fetch' }],
messages: [
{
role: 'user',
content: '이 글 핵심만 정리해줘: https://nextjs.org/blog/next-15',
},
],
});
for (const block of response.content) {
if (block.type === 'text') {
console.log(block.text);
}
}
web_fetch_20260209은 도구 버전이다. Opus 4.6·4.7·4.8, Sonnet 5 같은 최신 모델에서 돌아가고, 구형 모델이면 기본형인 web_fetch_20250910을 쓴다.
제일 중요한 제약 하나
web_fetch는 대화에 이미 등장한 URL만 가져온다. 모델이 "아무 주소나 상상해서" 긁어오는 게 아니다. 사용자 메시지든 앞선 검색 결과든, 컨텍스트 어딘가에 그 URL이 실제로 있어야 한다. 처음엔 이게 불편해 보였는데, 생각해보면 SSRF 같은 사고를 막는 안전장치라 오히려 맘이 편하다.
범위를 더 조이고 싶으면 옵션을 건다.
tools: [{
type: 'web_fetch_20260209',
name: 'web_fetch',
max_uses: 2, // 한 응답에서 가져오기 횟수 제한
allowed_domains: ['nextjs.org'], // 이 도메인만 허용
max_content_tokens: 20000, // 긴 문서 잘라 비용 방어
citations: { enabled: true }, // 답변에 출처 인용 붙이기
}]
max_content_tokens는 특히 챙겨두는 게 좋다. 안 걸면 긴 문서가 통째로 들어와서 토큰이 훅 나간다.
주의할 점
web_search와 마찬가지로 서버 도구는 에러가 나도 예외를 던지지 않는다. HTTP 200으로 오되 web_fetch_tool_result 블록 안에 { error_code: 'url_not_accessible' } 같은 객체가 담겨온다. try/catch만 믿으면 조용히 놓치니, 결과 블록 타입을 직접 확인하는 분기를 넣어둬야 한다.
정리하면 web_search는 "찾아줘", web_fetch는 "이거 읽어줘"다. 사용자가 링크를 들고 오는 기능 — 아티클 요약, 문서 Q&A 같은 데선 이쪽이 훨씬 직관적이다. 둘을 같이 선언해두면 모델이 상황 봐서 알아서 골라 쓴다.