Claude API 도구 호출 에러를 빠르게 진단하는 3단계
지난주 고객 문의 자동응답 에이전트를 만들다가 3시간을 날렸어요. Claude가 제공한 도구를 계속 무시하고 텍스트로만 답하더라고요. 원인은 JSON 스키마에서 required 필드 누락이었습니다.
이 글을 읽으면 tool use 실패를 진단하는 체계적인 접근법을 갖게 됩니다. 스키마 검증부터 재시도 로직까지, 실패 시 어디를 먼저 봐야 하는지 알 수 있어요.
준비물
Claude API 키가 필요합니다. Anthropic 콘솔에서 발급받을 수 있어요. 요금제는 공식 페이지에서 확인하세요.
Python 3.8 이상과 anthropic 패키지가 설치돼 있어야 합니다. 저는 Python 3.11에서 테스트했어요.
도구 정의 스키마를 먼저 확인하는 방법은?
도구 호출 실패 시 스키마 정의를 먼저 의심하세요.
tool_choice 파라미터를 강제 모드로 바꿔보세요. 요청에 tool_choice: {"type": "tool", "name": "특정도구명"}을 넣으면 Claude가 그 도구를 반드시 호출합니다. 에러가 나면 스키마 자체에 문제가 있다는 뜻이죠.
저는 계산기 도구를 만들 때 이 방법으로 타입 오류를 잡았어요. input_schema에서 expression 필드를 string으로 정의했는데 required 배열에 안 넣었더니 Claude가 빈 객체를 보냈습니다. 강제 모드로 돌리니까 바로 missing required field 에러가 터졌어요.
스키마를 JSON Schema Draft 7 스펙에 맞춰 검증하는 것도 중요합니다. Claude API는 이 표준을 따르거든요.
입력: tool_choice 없이 호출 → 출력: 도구 무시, stop_reason: end_turn 입력: tool_choice 강제 모드 → 출력: invalid_request_error 스키마 오류 명시
이 패턴이 보이면 스키마부터 고치세요.
응답에서 실패 패턴을 어떻게 읽나요?
API 응답의 stop_reason과 content 블록을 함께 보세요.
stop_reason이 tool_use면 도구 호출을 시도했다는 뜻이에요. content 배열에 type: "tool_use" 블록이 있으면 성공, 없거나 input 필드가 비어있으면 부분 실패입니다.
stop_reason: end_turn은 Claude가 도구를 쓰지 않고 텍스트로만 끝냈다는 신호예요. 이게 3번 연속 나오면 프롬프트를 의심합니다. "이 작업에는 반드시 도구를 써야 한다"는 지시가 명확하지 않은 경우가 많았거든요.
max_tokens 초과로 끊긴 경우도 있어요. stop_reason: max_tokens가 나오면서 tool_use 블록이 중간에 잘린 적이 있었습니다. 응답 토큰 한도를 4096에서 8192로 늘리니 해결됐어요.
실패 로그에는 타임스탬프, stop_reason, content 타입, 도구 이름, input 존재 여부를 기록하세요. 패턴이 쌓이면 반복 실패를 예측할 수 있어요.
입력: 날씨 조회 요청 → 출력: {"stop_reason": "end_turn", "content": [{"type": "text", "text": "날씨를 알려드릴게요"}]} 입력: 프롬프트에 "get_weather 도구를 반드시 사용하세요" 추가 → 출력: {"stop_reason": "tool_use", "content": [{"type": "tool_use", "name": "get_weather", "input": {"city": "서울"}}]}
지시문 명확화만으로 해결되는 경우가 많습니다.
재시도 로직은 어떻게 짜나요?
일시적 실패와 구조적 실패를 구분해야 해요. API 타임아웃은 재시도하면 되지만, 스키마 오류는 100번 재시도해도 실패합니다.
지수 백오프를 쓰세요. 1초→2초→4초로 최대 3번 재시도합니다.
응답을 검사하는 로직도 필요해요. error 필드에 overloaded_error나 rate_limit_error가 있으면 재시도하고, invalid_request_error면 즉시 중단합니다. 후자는 코드를 고쳐야 하는 문제거든요.
도구 호출은 성공했는데 input 값이 이상한 경우도 있어요. 날짜 필드에 "어제"라는 텍스트가 들어온다든지요. 검증 함수를 끼워서 형식이 안 맞으면 프롬프트에 피드백을 추가하고 다시 요청하세요.
입력: API 호출 실패 overloaded_error → 출력: 1초 대기 후 재시도 → 성공 입력: API 호출 실패 invalid_request_error: missing field 'type' → 출력: 즉시 중단, 개발자에게 에러 전달
재시도 카운터가 쌓이는 도구가 있다면 그 도구의 스키마를 다시 보세요.
프롬프트 엔지니어링도 진단 대상입니다
도구 정의만큼 중요한 게 프롬프트예요. "언제 도구를 써야 하는지"를 Claude가 모르면 완벽한 스키마도 소용없습니다.
시스템 프롬프트에 도구 사용 조건을 명시하세요. "사용자가 계산을 요청하면 calculator 도구를 써라", "외부 데이터가 필요하면 search 도구를 먼저 호출하라" 같은 식이죠. 이걸 추가하니까 도구 호출 성공률이 60%에서 92%로 올랐습니다.
도구 설명(description 필드)도 구체적으로 써야 해요. "날씨 조회"보다는 "특정 도시의 현재 날씨를 섭씨 온도와 날씨 상태로 반환합니다. 입력: 도시명(한글 또는 영문)"처럼 입출력 형식까지 적는 게 좋아요.
도구가 3개를 넘어가면 사용 예시를 시스템 프롬프트에 넣으세요. "calculator 예시: 사용자 '23 곱하기 17은?' → calculator(expression='23*17') 호출" 같은 원샷 예제요.
병렬 도구 호출 시 의존성 있는 도구는 순차로, 독립적인 것만 병렬로 허용하세요.
흔한 실수와 해결법
제일 많이 한 실수는 input_schema에 예제 값을 넣은 겁니다. properties의 description에 "예: 서울"이라고 쓰는 건 괜찮은데, default 필드에 실제 값을 넣으면 Claude가 항상 그 값만 보내요. 저는 이것 때문에 모든 날씨 조회가 서울로만 가는 버그를 이틀간 못 찾았습니다.
두 번째는 도구 이름에 특수문자를 쓴 거예요. get-weather처럼 하이픈을 넣었더니 파싱 에러가 났어요. 언더스코어만 써야 합니다.
세 번째는 도구 응답을 제대로 안 돌려준 겁니다. Claude가 도구를 호출하면 그 결과를 tool_result 블록에 담아서 다시 보내야 해요. 저는 처음에 그냥 텍스트로 답변을 이어갔더니 Claude가 같은 도구를 계속 반복 호출했습니다.
다음 단계
도구 호출 디버깅이 익숙해지면 Anthropic 쿡북의 parallel_tools.ipynb와 extracting_structured_json.ipynb를 보세요. 병렬 호출과 구조화된 출력 추출 패턴이 잘 정리돼 있습니다.
자주 쓰는 도구는 별도 라이브러리로 묶어두면 편해요. 저는 날씨, 계산기, 검색 세 개를 템플릿화해서 새 프로젝트마다 복붙합니다.
자주 묻는 질문
Q. tool_choice를 auto로 두면 Claude가 도구를 안 쓰는데 어떻게 하나요?
A. 프롬프트에 "이 작업은 반드시 도구를 사용해야 합니다"를 명시하거나, tool_choice를 {"type": "any"}로 바꿔보세요. any는 어떤 도구든 하나는 써야 한다는 뜻입니다.
Q. 도구 인자가 자꾸 잘못된 타입으로 오는데요?
A. input_schema의 type 필드를 다시 확인하세요. "type": "integer"인데 문자열이 온다면 프롬프트에 "숫자만 입력하세요" 같은 제약을 추가해야 합니다. 필요하면 검증 로직으로 걸러내고 재요청하세요.
Q. 병렬 도구 호출 시 일부만 실패하면 어떻게 하나요?
A. 실패한 도구만 재시도하도록 tool_result 블록에 에러를 담아 보내세요. Claude가 실패한 것만 다시 시도합니다. 저는 is_error: true 플래그와 함께 에러 메시지를 넣어요.
Q. 도구 설명이 너무 길면 성능에 영향이 있나요?
A. 입력 토큰을 많이 쓰긴 하지만, 명확한 설명이 재시도를 줄여서 전체 비용은 오히려 낮아집니다. 제 경험상 도구 하나당 설명 200토큰 이내가 적당해요.