- Published on
Claude 3.5 Tool Use 400 오류 - tool_schema 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Claude 3.5에서 Tool Use를 붙이다 보면, 모델 응답이 아니라 API 레벨에서 바로 400 이 떨어지는 순간이 있습니다. 특히 에러 메시지에 tool_schema 또는 tools[].input_schema 같은 단어가 보이면, 원인은 거의 확정입니다. 도구 입력 스키마(JSON Schema)가 Claude가 요구하는 제약을 만족하지 못했거나, 모델이 생성한 tool input이 스키마 검증을 통과하지 못한 케이스입니다.
이 글은 다음을 목표로 합니다.
tool_schema관련400의 대표 원인과 재현 패턴 정리- Claude 3.5 Tool Use에서 안전한 JSON Schema 작성 템플릿 제시
- 운영에서 재발 방지용 검증/로깅/테스트 방법
이미 일반적인 400 케이스와 JSON 스키마 기본을 다룬 글이 있다면, 아래 글도 함께 보면 좋습니다.
1) tool_schema 400은 “스키마가 엄격해서” 터진다
Claude의 Tool Use는 대략 이런 흐름입니다.
- 클라이언트가
tools배열로 도구 정의를 보냄 - 각 도구는
input_schema로 입력 형태를 JSON Schema로 선언 - 모델이
tool_use를 선택하면,inputJSON을 생성 - 서버가
input을input_schema로 검증 - 검증 실패 시 요청 자체가
400으로 실패하거나(플랫폼/버전에 따라), 혹은 tool 결과 단계에서 실패
즉, tool_schema 에러는 보통 둘 중 하나입니다.
- 도구 정의(
tools[].input_schema)가 유효하지 않음: 요청 즉시400 - 모델이 만든 도구 입력이 스키마에 맞지 않음: tool 호출 단계에서
400또는 “tool input validation failed” 류 에러
이 글은 두 경우를 모두 다룹니다.
2) 가장 많이 터지는 원인 TOP 7
원인 1: input_schema.type 이 object 가 아님
Tool input은 사실상 “파라미터 객체”로 취급됩니다. 따라서 input_schema 최상단은 거의 항상 type: "object" 여야 합니다.
잘못된 예:
{
"type": "string"
}
올바른 예:
{
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query"],
"additionalProperties": false
}
원인 2: required 에 있는데 properties 에 없음
스키마 작성 중 리팩터링하다가 required 만 남는 경우가 흔합니다.
{
"type": "object",
"properties": {
"q": { "type": "string" }
},
"required": ["query"],
"additionalProperties": false
}
위는 query 가 properties 에 없어서 검증기에서 실패할 수 있습니다.
원인 3: additionalProperties 를 안 잠그고, 모델이 잡필드를 생성
Claude는 맥락에 따라 친절하게 필드를 더 만들어 넣습니다. 예를 들어 debug: true, notes: ... 같은 값이 섞이면 스키마에 걸립니다.
운영에서는 보통 아래 중 하나를 택합니다.
- 엄격 모드:
additionalProperties: false로 잠그고 프롬프트로 “정해진 필드만 출력” 강하게 지시 - 관대 모드:
additionalProperties: true또는 미지정(기본 허용)으로 두고 서버에서 필터링
대부분은 엄격 모드 + 프롬프트 강화 + 사전 검증 조합이 안정적입니다.
{
"type": "object",
"properties": {
"user_id": { "type": "string" },
"limit": { "type": "integer", "minimum": 1, "maximum": 100 }
},
"required": ["user_id"],
"additionalProperties": false
}
원인 4: oneOf / anyOf / allOf 조합이 플랫폼 제약과 충돌
JSON Schema는 표현력이 강하지만, Tool Use 환경에서는 일부 조합이 제한되거나, 모델이 분기를 제대로 못 맞춰서 실패할 수 있습니다.
특히 다음 패턴이 위험합니다.
oneOf로 분기했는데, 모델이 두 분기 필드를 섞어서 생성anyOf로 느슨하게 했는데도 내부 검증기가 기대와 달라 실패
가능하면 분기 대신 명시적 type 필드(디스크리미네이터) 를 두고, 서버에서 라우팅하는 방식이 더 실전적입니다.
원인 5: 숫자 타입 혼동(integer vs number)
모델이 "10" 같은 문자열을 내거나, 소수로 내는 경우가 있습니다. 스키마가 integer 인데 10.5 가 오면 실패합니다.
- 정수만 받으면
integer - 소수 가능하면
number - 문자열로 받아서 서버에서 파싱할 거면
string으로 받고 패턴 검증
원인 6: 날짜/시간 포맷을 과하게 강제
format: "date-time" 같은 제약은 편하지만, 모델이 2026-2-3 처럼 느슨한 값을 내면 실패합니다.
운영에서는 다음 중 하나를 선택하세요.
format을 빼고 서버에서 파싱/검증- 프롬프트로 ISO-8601 예시를 강하게 주고, 실패 시 재시도
원인 7: 도구 이름/설명/스키마가 너무 길거나 불명확
이건 명세 위반이라기보다 “모델이 스키마를 잘 못 지켜서” 간접적으로 400 을 유발합니다.
name은 짧고 동사 중심description에 입력 규칙을 명확히properties에 각 필드description을 꼼꼼히
3) 안전한 input_schema 템플릿
아래 템플릿은 실전에서 가장 덜 터지는 형태입니다.
- 최상단
object - 명확한
properties와required additionalProperties: false- 가능한 범위 제약(
minLength,minimum,enum)을 넣어 모델을 가이드
{
"type": "object",
"properties": {
"query": {
"type": "string",
"minLength": 1,
"description": "검색할 키워드. 공백만 있는 값은 금지."
},
"top_k": {
"type": "integer",
"minimum": 1,
"maximum": 20,
"default": 5,
"description": "반환할 최대 결과 수(1~20)."
},
"lang": {
"type": "string",
"enum": ["ko", "en"],
"default": "ko",
"description": "언어 코드."
}
},
"required": ["query"],
"additionalProperties": false
}
프롬프트에도 “스키마 준수”를 중복으로 박아라
스키마가 엄격할수록, 프롬프트에도 다음을 명시하는 게 좋습니다.
- 도구 호출 시
properties에 정의된 키만 사용 - 문자열/정수 타입 주의
- 날짜는 ISO-8601
이런 중복은 과해 보이지만, 운영 안정성은 확실히 올라갑니다.
4) Node.js(Anthropic SDK) 예제: 올바른 Tool 정의
아래 예시는 Claude 3.5 계열 모델에서 Tool Use를 호출하는 전형적인 형태입니다. (SDK 버전에 따라 필드명이 다를 수 있으니, 핵심은 tools[].input_schema 구조를 참고하세요.)
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const tools = [
{
name: "search_docs",
description: "사내 문서에서 키워드로 검색한다.",
input_schema: {
type: "object",
properties: {
query: { type: "string", minLength: 1 },
top_k: { type: "integer", minimum: 1, maximum: 10, default: 5 }
},
required: ["query"],
additionalProperties: false
}
}
] as const;
const msg = await client.messages.create({
model: "claude-3-5-sonnet-latest",
max_tokens: 512,
tools,
messages: [
{
role: "user",
content: "내부 보안 가이드에서 JWT 키 회전에 대한 문서를 찾아줘."
}
]
});
console.log(JSON.stringify(msg, null, 2));
여기서 400 이 난다면, 우선은 다음을 의심하세요.
input_schema가 JSON Schema로서 유효한지required/properties불일치additionalProperties: false인데 모델이 다른 키를 만들었는지
JWT/JWKS 같은 운영 이슈를 다루는 API 게이트웨이 환경이라면, 아래 글처럼 “캐시/키회전” 문제도 함께 엮여 장애로 보일 수 있어, 원인 분리에도 도움이 됩니다.
5) 모델이 스키마를 어기는 경우: “검증 + 재시도” 패턴
운영에서 가장 현실적인 해법은 모델의 tool input을 서버에서 JSON Schema로 검증하고, 실패하면 다음 중 하나를 수행하는 겁니다.
- 같은 요청을 “스키마 준수” 문구를 더 강하게 해서 재시도
- 모델에게 “방금 입력이 스키마를 위반했으니 수정해라”라고 피드백 후 재생성
- 혹은 tool 호출을 포기하고 일반 답변으로 degrade
Python 예제: jsonschema 로 tool input 검증
from jsonschema import validate, ValidationError
TOOL_SCHEMA = {
"type": "object",
"properties": {
"query": {"type": "string", "minLength": 1},
"top_k": {"type": "integer", "minimum": 1, "maximum": 10}
},
"required": ["query"],
"additionalProperties": False
}
def validate_tool_input(tool_input: dict) -> tuple[bool, str | None]:
try:
validate(instance=tool_input, schema=TOOL_SCHEMA)
return True, None
except ValidationError as e:
return False, e.message
bad = {"query": "jwt rotation", "top_k": "10"} # top_k 타입 불일치
ok, err = validate_tool_input(bad)
print(ok, err)
이 검증 로직을 넣으면, “Claude가 이상한 값을 냈다”를 감이 아니라 명확한 에러 메시지로 수집할 수 있습니다.
6) 디버깅 체크리스트: 15분 안에 원인 좁히기
다음 순서로 보면 대부분 빠르게 해결됩니다.
1단계: 요청에서 tools 를 제거하고 정상 응답 확인
tools없이도 응답이 오면 네트워크/인증 문제가 아니라 “tool schema” 문제일 확률이 큼
2단계: input_schema 를 최소 형태로 축소
예를 들어 필수 키 하나만 남깁니다.
{
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query"],
"additionalProperties": false
}
이 상태에서 성공하면, 원인은 대개 아래 중 하나입니다.
oneOf같은 복잡한 제약format강제enum값 불일치
3단계: 모델이 실제로 생성한 tool input을 원문 그대로 로깅
- 공백, 숫자 문자열, 누락 키, 추가 키가 흔한 원인
- 로깅할 때 개인정보/토큰은 마스킹
4단계: 스키마 검증기를 로컬에 붙여 재현
- Node:
ajv - Python:
jsonschema
이 단계에서 “재현 가능한 실패”로 바뀌면 해결은 거의 끝입니다.
파이프라인에서 에러가 삼켜져 원인 파악이 늦어지는 경우도 많습니다. CI나 배치에서 비슷한 상황을 겪는다면 아래 글의 pipefail/에러 전파 패턴이 디버깅에 도움이 됩니다.
7) 실전 팁: 스키마를 “모델 친화적으로” 만들기
스키마는 검증기용이기도 하지만, 동시에 모델에게 주는 인터페이스 문서입니다. 다음 원칙이 효과가 좋습니다.
- 필드명은 짧고 의미가 명확하게:
userIdvsuid는 팀 컨벤션에 맞추되 일관성 유지 description을 적극 활용: 무엇을 넣어야 하는지 예시까지 적기enum은 최소화: 정말 필요한 곳에만- 숫자는 범위를 주기:
minimum/maximum - 문자열은
minLength로 빈 문자열 방지 additionalProperties: false를 쓰면, 프롬프트에 “정의된 키만”을 반드시 명시
8) 결론: tool_schema 400은 스키마/검증 체계로 끝낸다
Claude 3.5 Tool Use의 400 tool_schema 는 운이 나빠서가 아니라, 보통 아래 중 하나로 설명됩니다.
input_schema자체가 JSON Schema로 유효하지 않음- 모델이 생성한 tool input이 스키마를 위반함(타입/누락/추가 키)
해결 전략은 명확합니다.
input_schema를 단순하고 엄격하게(object+required+additionalProperties: false)- 서버에서 JSON Schema 검증을 반드시 수행
- 실패 시 재시도 또는 수정 루프를 설계
이 3가지만 갖추면, Tool Use는 “가끔 터지는 기능”이 아니라 “운영 가능한 인터페이스”로 바뀝니다.