Claude한테 툴을 쥐여줬는데 안 쓸 때가 있다. 특히 "이 문장에서 정보 뽑아서 save_contact 툴로 넘겨"라고 시켰는데, 툴을 호출하는 대신 "네, 이름은 홍길동이고 번호는..." 하고 그냥 텍스트로 답해버린다. 추출 파이프라인 입장에선 이게 제일 곤란하다. 반드시 툴 호출이 와야 뒷단이 돌아가는데, 올 때도 있고 안 올 때도 있으니까.
기본값(tool_choice 생략 또는 auto)은 모델이 툴을 쓸지 말지 스스로 판단한다. 꼭 불러야 하는 상황이면 판단을 맡기지 말고 강제하면 된다.
네 가지 모드
auto— 기본값. 모델이 알아서 결정.any— 툴 중 하나는 반드시 호출. 어떤 걸 쓸지는 모델이 고름.tool— 지정한 그 툴을 무조건 호출.none— 이번 턴엔 툴 금지. 텍스트로만 답하게.
추출/라우팅처럼 결과가 꼭 구조화돼서 나와야 하면 tool로 특정 툴을 못 박는다.
const res = await client.messages.create({
model: 'claude-sonnet-5-5',
max_tokens: 1024,
tools: [
{
name: 'save_contact',
description: '연락처 정보를 저장한다',
input_schema: {
type: 'object',
properties: {
name: { type: 'string' },
phone: { type: 'string' },
},
required: ['name', 'phone'],
},
},
],
tool_choice: { type: 'tool', name: 'save_contact' }, // ← 이 툴 강제
messages: [
{ role: 'user', content: '홍길동, 010-1234-5678 저장해줘' },
],
});
const block = res.content.find((b) => b.type === 'tool_use');
console.log(block.input); // { name: '홍길동', phone: '010-1234-5678' }
이제 서두 텍스트 없이 stop_reason: 'tool_use'로 깔끔하게 떨어진다. input_schema가 곧 내 출력 스키마가 되니까, 사실상 구조화 추출을 툴로 구현하는 셈이다. JSON 파싱하다 깨질 걱정도 없다.
함정 두 개
any나tool로 강제하면 확장된 사고(extended thinking)와 같이 못 쓴다. 추론이 필요한 작업이면auto를 유지해야 한다.- 툴을 강제하면 Claude가 여러 툴을 한 번에 부르려 할 수 있다. 딱 하나만 받고 싶으면
disable_parallel_tool_use: true를 같이 넣는다.
tool_choice: { type: 'any', disable_parallel_tool_use: true },
"알아서 판단해"가 필요한 대화형 에이전트는 auto가 맞다. 하지만 "이 입력은 무조건 이 형식으로"가 목적이라면 tool_choice 한 줄로 못 박는 게, 프롬프트에 "꼭 툴 써"라고 사정하는 것보다 훨씬 확실하다.