2026년 8월 18일 기준, 딥시크 공식 API는 생각 모드에서 도구 호출이 발생하면 이후 요청마다 reasoning_content를 완전히 되돌려 보내지 않을 때 400을 반환한다고 명시합니다. (api-docs.deepseek.com)
증상 → 가장 빠른 해결법
첫 요청은 성공하지만 도구 결과를 넣은 다음 요청에서 400이 발생합니다. 공식 API와 vLLM용 원시 HTTP 재현 요청을 따로 만들고, 한 요청 안에서 reasoning_content와 reasoning을 섞지 마세요. 공식 API는 도구 호출 뒤 reasoning_content를 보존하고, vLLM은 배포 버전에 따라 reasoning을 기준으로 확인해야 합니다.
이 글은 SDK와 에이전트 프레임워크 없이 재현 자료를 만들어야 하는 AI 에이전트 개발자에게 맞습니다. 게이트웨이와 클라이언트, 추론 서버 중 어디서 필드가 사라졌는지 확인하려는 백엔드 엔지니어와 두 서버를 함께 회귀 시험하려는 플랫폼 팀도 대상입니다.
마지막 업데이트: 2026년 8월 18일. 딥시크 공식 생각 모드 문서와 현재 vLLM 추론 출력 문서, 프로토콜 소스에서 내용을 다시 확인했습니다. vLLM의 필드 호환 동작은 배포 버전과 파서에 따라 달라질 수 있으므로 실제 운영 버전에서 재검증해야 합니다. (api-docs.deepseek.com)
실패 요청과 성공 요청의 기준선
먼저 두 요청을 나란히 놓고 비교해야 합니다. 프레임워크 로그에서 보이는 모델 이름만으로는 실제 서버를 판단할 수 없습니다.
다음 항목을 먼저 기록하세요.
- 실제
base_url - 요청이 향한 서버의 응답 헤더
- 모델 아이디
- 생각 모드 활성화 방식
- 도구 호출 파서와 서버 버전
- 직렬화 직전의 전체
messages - 직렬화 직후의 실제 요청 본문
공식 API를 대상으로 하는 최소 요청은 다음처럼 구성합니다.
curl https://api.deepseek.com/chat/completions \
-H "Authorization: Bearer $DEEPSEEK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-pro",
"messages": [
{
"role": "user",
"content": "현재 날짜를 알려 주세요."
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_date",
"description": "현재 날짜를 반환합니다.",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
}
],
"thinking": {
"type": "enabled"
}
}'
위 예시는 실제 비밀 키와 사용자 데이터, 도구 결과를 포함하지 않습니다. 재현 자료에는 content, 추론 필드, tool_calls, finish_reason, 응답 아이디만 남기고 전체 추론 문장은 제거하는 편이 안전합니다.
딥시크의 공식 API 스키마는 assistant 메시지에 reasoning_content, content, tool_calls를 별도 필드로 둡니다. 반대로 현재 vLLM 문서는 추론 결과의 기본 이름을 reasoning으로 설명하며, reasoning_content는 이전 이름이라고 안내합니다. 이 차이는 양방향으로 자동 치환해도 된다는 뜻이 아닙니다. (api-docs.deepseek.com)
첫 요청과 두 번째 요청의 분리
첫 번째 응답 저장
첫 응답에서 필요한 값만 추출합니다.
{
"id": "resp_redacted",
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": "",
"reasoning_content": "<삭제된 추론 내용>",
"tool_calls": [
{
"id": "call_redacted",
"type": "function",
"function": {
"name": "get_date",
"arguments": "{}"
}
}
]
}
}
실제 응답에는 추론 내용이 들어갈 수 있습니다. 재현 파일에서는 <삭제된 추론 내용> 같은 자리표시자를 사용하세요. 단, 테스트 요청을 다시 보낼 때는 공식 API가 요구하는 실제 값이 필요합니다. 값을 임의의 짧은 문장으로 바꾸면 필드 존재성만 시험할 뿐, 원래 계약을 재현하지 못합니다.
다음 네 가지를 직렬화 직후 검사합니다.
role이assistant인지reasoning_content또는 대상 서버가 요구하는 추론 필드가 남아 있는지tool_calls의 호출 아이디가 보존되는지content가 빈 문자열이어도 필드 자체가 유지되는지
객체를 딕셔너리로 바꾸는 과정, 로그 마스킹, 중간 저장소의 필터링은 서버 계약과 별개의 계층입니다. 원시 응답에는 필드가 있었는데 전송 직전 사라졌다면 서버보다 클라이언트나 중계 계층을 먼저 의심해야 합니다.
도구 결과 추가
도구 결과는 assistant 응답의 호출 아이디와 정확히 연결해야 합니다.
{
"role": "tool",
"tool_call_id": "call_redacted",
"content": "<삭제된 도구 결과>"
}
이제 같은 대화에 assistant 응답과 tool 결과를 붙입니다.
[
{
"role": "user",
"content": "현재 날짜를 알려 주세요."
},
{
"role": "assistant",
"content": "",
"reasoning_content": "<첫 응답의 실제 추론 내용>",
"tool_calls": [
{
"id": "call_redacted",
"type": "function",
"function": {
"name": "get_date",
"arguments": "{}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_redacted",
"content": "<실제 도구 결과>"
}
]
공식 API의 생각 모드에서 도구 호출이 있었던 경우, 후속 요청에는 해당 assistant 메시지의 reasoning_content가 필요합니다. 딥시크 공식 문서도 도구 호출이 없는 일반 생각 모드 대화와 도구 호출이 있는 대화를 구분합니다. (api-docs.deepseek.com)
일부러 실패시키는 재현 순서
문제를 바로 고치지 말고 실패 샘플을 먼저 고정하세요. 그래야 수정 뒤 성공이 실제 해결인지 확인할 수 있습니다.
실패 샘플
공식 API를 시험할 때 assistant 메시지에서 reasoning_content를 제거합니다.
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_redacted",
"type": "function",
"function": {
"name": "get_date",
"arguments": "{}"
}
}
]
}
이 요청에서 400이 발생하면 다음 값을 저장합니다.
- HTTP 상태 코드
- 오류 본문
- 전송한
base_url - 직렬화된
messages - 호출 아이디
- 생각 모드 관련 요청 필드
오류 본문만 저장하면 안 됩니다. 같은 400이라도 잘못된 도구 인자, 지원하지 않는 매개변수, 필수 추론 필드 누락은 원인이 다를 수 있습니다.
성공 샘플
실제 첫 응답의 추론 필드를 같은 assistant 메시지 안에 복원합니다. 그 뒤 동일한 tool 결과를 붙여 다시 요청합니다.
공식 API에서 성공하는 기준은 상태 코드 하나가 아닙니다.
- assistant 메시지에
reasoning_content가 존재합니다. tool_calls의 호출 아이디와 tool 메시지의tool_call_id가 일치합니다.- user, assistant, tool 순서가 유지됩니다.
- 요청이 실제 공식 API 주소로 전달됩니다.
- 후속 응답의
finish_reason과 도구 호출 유무가 기록됩니다.
도구 호출이 정상 처리되더라도 모든 다중 호출이 해결됐다고 판단하면 안 됩니다. 같은 대화에서 두 번째 도구 호출, 세 번째 assistant 응답까지 이어지는 연속 흐름을 별도로 실행해야 합니다.
공식 API와 vLLM의 필드 경계
두 서버는 같은 대화 뼈대를 사용해도 출력 필드와 입력 호환성이 같다고 볼 수 없습니다.
딥시크 공식 API
- 도구 호출 뒤 후속 요청에
reasoning_content를 완전히 전달해야 합니다. reasoning_content는 assistant 메시지에 둡니다.- 도구 호출 아이디와 도구 결과의 연결을 유지해야 합니다.
- 공식 문서가 확인한 400 조건을 우선 계약으로 취급합니다. (api-docs.deepseek.com)
vLLM
- 현재 문서는 추론 출력 이름을
reasoning으로 설명합니다. - 문서에는
reasoning_content가 이전 이름이라고 적혀 있습니다. - 프로토콜 소스에는 이전 필드를 새 이름으로 정규화하는 동작이 보이지만, 모든 파서와 배포 버전의 입력 동작을 의미하지는 않습니다.
- 도구 호출은 추론 내용이 아니라 일반
content에서 파싱될 수 있으므로, 도구 호출이 실제 어느 필드에 담겼는지 응답에서 확인해야 합니다. (github.com)
따라서 vLLM에서 받은 reasoning을 딥시크 공식 API로 그대로 보내는 방식은 안전한 공통 규칙이 아닙니다. 내부 메시지 모델에는 internal_reasoning 같은 별도 속성을 두고, 전송 직전에 다음처럼 분기하세요.
- 공식 API 출구:
internal_reasoning을reasoning_content로 변환 - 최신 vLLM 출구: 목표 버전의 계약에 맞춰
reasoning으로 변환 - 구버전 또는 호환 모드: 실제 응답과 프로토콜 정의를 확인한 뒤 별도 처리
- 도구 호출:
tool_calls와tool_call_id를 이름 변경 없이 보존
vLLM의 DeepSeek V4 파서는 생각 블록과 도구 호출을 하나의 상태 기계로 처리한다고 설명합니다. 이 동작은 서버 내부 파싱 규칙에 대한 설명이지, 딥시크 공식 API의 입력 필드 계약을 대신하지 않습니다. (docs.vllm.ai)
자주 생기는 오해와 확인 지점
첫 번째 요청만 성공하면 도구 정의는 정상인가요?
그렇다고 단정할 수 없습니다. 첫 요청은 모델이 응답을 생성하는 단계입니다. 다음 요청은 이전 assistant 응답을 다시 입력하는 단계입니다. 두 단계에서 요구하는 메시지 보존 조건이 다르면 첫 호출은 성공하고 두 번째 호출만 실패합니다.
reasoning_content와 reasoning을 모두 넣으면 호환성이 좋아지나요?
대개 반대입니다. 한 payload에 두 필드를 함께 넣으면 어느 필드가 우선되는지 서버 버전과 중계 계층에 따라 달라질 수 있습니다. 먼저 하나의 대상 서버를 정하고, 그 서버가 문서와 실제 응답에서 사용하는 필드만 보내세요.
도구 결과를 새 user 메시지로 넣어도 되나요?
도구 호출 흐름을 재현하는 목적이라면 피해야 합니다. assistant의 호출 아이디와 tool 메시지의 tool_call_id가 연결되어야 합니다. user 메시지로 바꾸면 모델의 대화 순서가 달라져 원래 오류를 재현하지 못할 수 있습니다.
회귀 시험 체크리스트
아래 항목을 한 서버씩 완료한 뒤 두 번째 서버로 넘어가세요.
- [ ] 공식 API와 vLLM의 실제
base_url을 별도로 기록했습니다. - [ ] 동일한 도구 정의와 사용자 입력을 사용했습니다.
- [ ] 첫 응답에서 content, 추론 필드, tool_calls, finish_reason, 응답 아이디를 저장했습니다.
- [ ] 공식 API 실패 샘플에서
reasoning_content제거 상태를 고정했습니다. - [ ] 공식 API 성공 샘플에서 원래 assistant 메시지를 복원했습니다.
- [ ] vLLM 응답에서
reasoning과 이전 이름의 호환 여부를 직접 확인했습니다. - [ ] assistant의 호출 아이디와 tool 메시지의 호출 아이디가 일치합니다.
- [ ] 단일 도구 호출과 연속 도구 호출을 각각 실행했습니다.
- [ ] 무도구 요청도 별도 시험했습니다.
- [ ] 서버 전환 전후에 메시지 순서와 필드 이름을 비교했습니다.
- [ ] 상태 코드뿐 아니라 오류 본문과 실제 요청 본문을 저장했습니다.
- [ ] 목표
base_url이 회귀 시험마다 바뀌지 않았는지 검사했습니다. - [ ] 실패 샘플은 계속 실패하고 성공 샘플은 두 서버에서 각각 통과하는지 확인했습니다.
원시 HTTP 요청을 계속 보관하기 어렵다면 MacHTML 콘솔 환경에서 서버 주소와 실행 기록을 분리해 관리하는 방법도 검토할 수 있습니다. 계정과 키는 로그에 남기지 말고 환경 변수로 주입하세요. 운영 전환 전에 MacHTML 도움말의 접속과 환경 관리 안내도 함께 확인하면 됩니다.
현재 개발 환경과 클라우드 맥의 선택
노트북 한 대에서 공식 API와 vLLM을 번갈아 실행하면 다음 문제가 생깁니다.
- 서버 주소와 모델 아이디가 셸 기록에 섞입니다.
- 로컬 프로세스가 종료되면 동일한 대화 상태를 다시 만들기 어렵습니다.
- 파서 버전과 실행 옵션이 바뀌어 실패 원인이 달라집니다.
- 네트워크, 권한, 포트 충돌이 모델 응답 오류처럼 보일 수 있습니다.
장기간 동일한 추론 서버를 운영하고 물리 장치나 로컬 GPU에 직접 접근해야 한다면 자체 장비가 더 적합할 수 있습니다. 반대로 프레임워크 업그레이드 전후의 비교, 공식 API와 자가 호스팅 서버의 교차 시험, 짧은 회귀 기간이 목적이라면 격리된 클라우드 맥 환경이 더 관리하기 쉽습니다. 특히 로컬 장비에서 두 개의 깨끗한 실행 환경을 오래 유지하기 어렵다면 MacHTML의 맥 렌탈 구성으로 재현 스크립트를 고정해 두는 편이 현실적인 선택이 될 수 있습니다.
최종 기준은 “400이 사라졌는가”가 아닙니다. 공식 API와 vLLM에서 각각 같은 실패 조건을 재현하고, 대상 서버에 맞는 필드 변환을 거친 성공 요청을 다시 통과시키며, 두 결과를 탈취 정보 없이 공유할 수 있어야 수정이 끝난 것입니다.
자주 묻는 질문
딥시크 연동 오류를 검증할 전용 맥 환경을 시작해 보세요
MacHTML의 전용 물리 맥을 이용하면 다중 요청과 도구 호출 흐름을 실제 환경에서 안정적으로 재현할 수 있습니다. M4 칩과 독점 자원을 갖춘 원격 맥에서 클라이언트와 중계 계층, 추론 서버를 단계별로 점검할 수 있습니다. 서울과 도쿄를 비롯한 여러 지역의 노드를 선택해 네트워크 지연과 서버 주소에 따른 차이를 비교할 수 있습니다. 일일, 주간, 월간 또는 분기 단위의 유연한 이용 기간으로 필요한 만큼만 테스트 환경을 구성할 수 있습니다.