LLM에 뭔가 물어보다 보면 자꾸 막히는 지점이 있다. "그건 제 학습 데이터 이후의 일이라 모릅니다." 최신 라이브러리 버전, 어제 나온 발표, 지금 환율 — 이런 걸 물으면 그럴싸하게 지어내거나 아예 답을 못 한다.
이걸 해결하려고 예전엔 직접 검색 API를 붙였다. 구글이나 Bing 검색 결과를 긁어와서, 프롬프트에 끼워 넣고, 다시 Claude한테 던지는 식이다. RAG 파이프라인 하나를 통째로 만드는 셈이라 손이 많이 갔다.
그런데 Claude API에 서버 사이드 web_search 도구가 생기고 나서 이 과정이 거의 사라졌다. 검색을 Anthropic 쪽에서 대신 돌려준다. 내가 검색 API 키를 발급받을 필요도, 결과를 파싱해서 다시 넣어줄 필요도 없다. 그냥 도구 하나를 선언만 하면 된다.
도구 선언
tools 배열에 web_search를 넣는 게 전부다. 클라이언트에서 실행할 함수 같은 게 없다. 모델이 알아서 검색하고, 결과를 같은 응답 안에 담아 돌려준다.
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_search_20260209', name: 'web_search' }],
messages: [
{ role: 'user', content: 'Next.js 최신 안정 버전이 뭐고, 주요 변경점 알려줘' },
],
});
for (const block of response.content) {
if (block.type === 'text') {
console.log(block.text);
}
}
여기서 web_search_20260209는 도구 버전이다. 날짜가 붙어 있어서 낯설어 보이는데, 그냥 그대로 쓰면 된다. Opus 4.8·4.7, Sonnet 5 같은 최신 모델에서 동작하고, 구형 모델이라면 web_search_20250305를 쓰면 된다.
검색 범위를 좁히고 싶다면
아무 데나 검색하게 두면 결과 품질이 들쭉날쭉할 때가 있다. 도메인을 제한하면 훨씬 안정적이다.
tools: [{
type: 'web_search_20260209',
name: 'web_search',
max_uses: 3, // 한 응답에서 검색 횟수 제한
allowed_domains: ['nextjs.org', 'react.dev'],
}]
max_uses로 검색 횟수를 묶어두면 비용과 지연도 같이 잡힌다. 반대로 특정 사이트를 빼고 싶으면 blocked_domains를 쓴다.
한 가지 주의할 점
서버 도구는 에러가 나도 예외를 던지지 않는다. HTTP 200으로 오되, 결과 블록 안에 { error_code: 'max_uses_exceeded' } 같은 객체가 들어온다. 성공일 땐 검색 결과가 리스트, 실패일 땐 객체 — 이 차이로 분기하면 된다. try/catch만 믿고 있으면 조용히 놓친다.
직접 검색 파이프라인을 짜본 입장에선, 도구 한 줄로 끝나는 게 좀 허무할 정도다. 최신 정보가 필요한 챗봇이나 리서치 기능을 붙일 때 제일 먼저 꺼내볼 만한 카드다.