- Published on
LangChain Tool Calling 400 invalid_request 오류 9가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 400 invalid_request가 떨어질 때 가장 난감한 지점은, LangChain 코드가 “그럴듯하게” 보이는데도 실제로는 OpenAI(또는 호환) API가 요구하는 스키마와 미묘하게 어긋나는 경우가 많다는 점입니다. 특히 Tool Calling은 요청 페이로드에 tools, tool_choice, messages(또는 input)가 함께 얽히면서, 작은 불일치가 즉시 400으로 이어집니다.
이 글은 LangChain에서 Tool Calling을 사용할 때 자주 발생하는 400 invalid_request를 9가지 유형으로 나눠, 원인과 해결책을 빠르게 찾을 수 있도록 구성했습니다. Structured Outputs와 스키마 관련 400을 더 깊게 다룬 글은 OpenAI Structured Outputs 400 해결 - JSON Schema도 함께 참고하면 좋습니다.
먼저: 400을 “재현 가능하게” 만드는 디버깅 습관
1) LangChain에서 실제로 나가는 요청을 로그로 확인
LangChain은 추상화가 두껍기 때문에, “내가 생각한 요청”과 “실제로 나간 요청”이 다를 수 있습니다. 아래처럼 디버깅을 켜고, 에러 응답의 error.message를 반드시 확보하세요.
import os
from langchain_openai import ChatOpenAI
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "YOUR_LANGSMITH_KEY" # 선택
llm = ChatOpenAI(
model="gpt-4.1-mini",
temperature=0,
)
# 호출 시점의 예외 메시지에 실제 API 에러가 포함되는지 확인
2) 실패한 요청을 “최소 입력”으로 줄이기
- 시스템 프롬프트, 긴 컨텍스트, 여러 툴을 한 번에 넣지 말고
- 툴 1개, 메시지 1개로 축소해서 400이 계속 나는지 확인합니다.
이 과정을 거치면 아래 9가지 중 어디에 해당하는지 훨씬 빨리 좁혀집니다.
오류 1) tools 스키마가 OpenAI 규격과 다름
증상
invalid_request와 함께tools[0].function혹은tools[0].type관련 메시지- 혹은 “Unknown field” 류의 메시지
원인
Tool Calling은 대체로 다음 형태를 요구합니다.
type:"function"function:{ name, description, parameters }parameters: JSON Schema 오브젝트
LangChain에서 툴을 직접 dict로 만들거나, 커스텀 변환을 하다가 필드명이 틀어지면 400이 납니다.
해결
LangChain의 @tool 또는 StructuredTool을 사용해 표준 형태로 생성되게 하세요.
from langchain_core.tools import tool
@tool
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
tools = [add]
직접 dict를 만들어야 한다면 type과 function.parameters가 JSON Schema인지(특히 type: object, properties, required)를 점검하세요.
오류 2) parameters가 JSON Schema가 아니라 “예시 JSON”인 경우
증상
invalid_request+schema혹은parameters관련 에러
원인
parameters에 아래처럼 “입력 예시”를 넣는 실수를 자주 합니다.
# 잘못된 예: parameters에 예시 JSON을 넣음
bad_tool = {
"type": "function",
"function": {
"name": "search",
"description": "search docs",
"parameters": {"query": "hello"}
}
}
해결
parameters는 반드시 JSON Schema여야 합니다.
ok_tool = {
"type": "function",
"function": {
"name": "search",
"description": "search docs",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"],
"additionalProperties": False
}
}
}
스키마 엄격 모드에서의 400은 특히 흔합니다. 더 자세한 스키마 설계 팁은 OpenAI Structured Outputs 400 해결 - JSON Schema를 권장합니다.
오류 3) tool_choice 값이 모델/엔드포인트와 호환되지 않음
증상
tool_choice관련invalid_request- “tool_choice must be ...” 같은 메시지
원인
tool_choice는 API/모델에 따라 허용 형태가 다릅니다. LangChain에서 다음을 섞어 쓰다 400이 납니다.
"auto""required"- 특정 함수 강제 선택 형태
또한 tools를 넘기지 않았는데 tool_choice만 지정하면 실패합니다.
해결
tools가 있을 때만tool_choice를 설정- 강제 호출이 필요하면 “특정 함수 지정”을 올바른 형태로 전달
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
# LangChain 버전에 따라 bind_tools 인자가 다를 수 있음
llm_with_tools = llm.bind_tools(
tools=[add],
tool_choice="auto", # 우선 auto로 검증
)
강제 호출이 필요하다면 먼저 auto로 400이 사라지는지 확인한 뒤, 그 다음에 강제 호출을 적용하는 순서가 안전합니다.
오류 4) Tool 이름이 규칙을 위반하거나 중복됨
증상
invalid_request+tool name관련- 혹은 “duplicate tool name”
원인
- 툴 이름에 공백, 특수문자 등이 들어감
- 같은 이름의 툴을 여러 개 등록
- LangChain에서 자동 생성된 이름이 충돌
해결
- 명시적으로 유니크한
name을 부여 - 툴 리스트를 합치기 전에 이름 충돌 검사
from langchain_core.tools import StructuredTool
def add_impl(a: int, b: int) -> int:
return a + b
add_tool = StructuredTool.from_function(
func=add_impl,
name="math_add",
description="Add two integers",
)
오류 5) additionalProperties 또는 required 설정 불일치로 스키마가 모순됨
증상
invalid_request+ JSON Schema validation 관련
원인
아래 같은 “스키마 모순”이 있으면 400이 날 수 있습니다.
required에 정의되지 않은 필드가 포함additionalProperties: False인데 모델이 생성해야 할 필드가properties에 없음properties는 있는데 최상위type이object가 아님
해결
스키마를 단순화해 단계적으로 강화하세요.
parameters = {
"type": "object",
"properties": {
"a": {"type": "integer"},
"b": {"type": "integer"}
},
"required": ["a", "b"],
"additionalProperties": False
}
그리고 툴 입력을 Pydantic 기반으로 만들면(버전에 따라 pydantic v1/v2 호환 주의) 이런 실수를 크게 줄일 수 있습니다.
오류 6) 메시지 포맷 불일치: messages에 잘못된 role/content 구조
증상
invalid_request+messages[0]...같은 인덱스 기반 에러
원인
LangChain의 메시지 타입을 섞어 쓰거나, 커스텀으로 messages를 만들면서 아래 실수를 합니다.
role이 허용되지 않는 값content가 문자열이 아닌데 포맷을 맞추지 않음- tool 결과를
toolrole로 넣어야 하는데assistant로 넣음
해결
LangChain 메시지 클래스를 사용하고, 툴 실행 결과는 LangChain이 붙이도록(에이전트/툴 실행 루프) 맡기는 편이 안전합니다.
from langchain_core.messages import SystemMessage, HumanMessage
messages = [
SystemMessage(content="You are a helpful assistant."),
HumanMessage(content="1 더하기 2는? 툴을 써도 돼."),
]
resp = llm_with_tools.invoke(messages)
직접 tool 결과 메시지를 구성해야 한다면, LangChain 버전별 tool message 포맷을 확인하고 그대로 따르세요.
오류 7) 모델이 Tool Calling을 지원하지 않거나, 엔드포인트가 다름
증상
- 같은 코드가 모델만 바꾸면 400
This model does not support tools류의 메시지
원인
- Tool Calling 미지원 모델 사용
- OpenAI 호환 서버(게이트웨이, 프록시, 사설 LLM)에서
tools필드를 미지원 - Chat Completions 스타일과 Responses API 스타일을 혼용
해결
- Tool Calling 지원 모델로 변경
- 사용하는 LangChain 통합이 어떤 API 스타일을 쓰는지 확인
- 사설 게이트웨이라면
tools지원 여부를 문서로 확인
모델/엔드포인트 이슈는 애플리케이션 레벨에서 타임아웃/재시도와 함께 나타나기도 합니다. 네트워크/서버 지연까지 포함해 운영 관점에서 점검하려면 OpenAI Responses API 408 타임아웃 재현과 해결 실전 가이드도 같이 보면 좋습니다.
오류 8) tool 입력 타입 불일치: 문자열로 와야 하는데 객체로 보냄(또는 반대)
증상
invalid_request+type mismatch또는expected string류
원인
LangChain에서 툴 인자를 자동으로 직렬화하는 과정에서,
- 이미 dict인데 JSON 문자열로 한 번 더 인코딩
- 반대로 문자열이어야 하는 필드에 dict를 그대로 전달
특히 “툴이 내부적으로 HTTP 호출을 하는데 body를 문자열로 받도록 구현”한 경우에 많이 발생합니다.
해결
툴 함수 시그니처를 “최종적으로 받고 싶은 타입”으로 명확히 하세요. 문자열 JSON을 받지 말고, 가능한 한 구조화된 인자를 받도록 설계합니다.
from langchain_core.tools import tool
from pydantic import BaseModel
class SearchArgs(BaseModel):
query: str
top_k: int = 5
@tool(args_schema=SearchArgs)
def search(query: str, top_k: int = 5) -> str:
"""Search documents by query."""
return f"query={query}, top_k={top_k}"
이렇게 하면 스키마와 런타임 타입이 일치해 400 가능성이 크게 줄어듭니다.
오류 9) LangChain 버전/의존성 불일치로 tool 스키마 생성이 깨짐
증상
- 최근 업그레이드 이후 갑자기 400
- 같은 코드가 로컬에서는 되는데 CI/서버에서는 400
원인
langchain,langchain-core,langchain-openai버전 조합이 맞지 않음pydanticv1/v2 혼재로 JSON Schema 생성이 달라짐- 툴 스키마 생성 시
nullable,anyOf등이 예상과 다르게 생성되어 API가 거부
해결
- 의존성 버전을 “세트로” 고정하세요.
- 배포 환경과 로컬의 lockfile을 일치시키세요.
pip show langchain langchain-core langchain-openai pydantic
# requirements.txt 예시(프로젝트 상황에 맞게 고정)
langchain==0.3.XX
langchain-core==0.3.XX
langchain-openai==0.3.XX
pydantic==2.XX.X
또한 툴 스키마를 로그로 덤프해서, 실제로 생성된 JSON Schema가 API가 허용하는 형태인지 확인하는 것이 핵심입니다.
import json
# StructuredTool/@tool로 만든 경우에도 내부적으로 schema를 만들 수 있음
# 아래는 개념 예시이며, 실제 접근자는 버전에 따라 다를 수 있음
schema = add.args_schema.model_json_schema() # pydantic v2 기준
print(json.dumps(schema, ensure_ascii=False, indent=2))
실전 체크리스트: 400 invalid_request를 10분 내로 끝내는 순서
tools를 완전히 제거한 호출이 정상인지 확인(모델/엔드포인트 기본 동작 점검)- 툴 1개만 추가하고
tool_choice는auto - 툴 이름 중복/규칙 위반 확인
parameters가 JSON Schema인지 확인(특히type: object,properties,required)additionalProperties: False를 잠시 제거해 스키마 엄격성으로 인한 400인지 분리- Pydantic 기반
args_schema로 옮겨 타입 불일치 제거 - 의존성 버전 고정 및 로컬/서버 동일화
마무리: “요청 페이로드가 정확히 무엇이냐”가 답이다
LangChain Tool Calling의 400 invalid_request는 대부분 “툴 스키마” 또는 “메시지/옵션 조합”의 미세한 불일치에서 발생합니다. 해결의 지름길은 추상 레이어를 믿기보다, 실제로 전송된 tools JSON과 에러 메시지의 필드 경로를 1:1로 대조하는 것입니다.
스키마가 조금만 복잡해져도 400이 잦다면, 툴 입력을 Pydantic으로 표준화하고, JSON Schema를 점진적으로 강화하는 방식으로 운영 안정성을 확보하세요. Structured Outputs 관점에서의 스키마 설계는 OpenAI Structured Outputs 400 해결 - JSON Schema가 직접적인 도움이 됩니다.