AIAgent

2026 딥시크 V4 2차 요청 400: 로그 점검법

2026 딥시크 V4 2차 요청 400: 로그 점검법

딥시크 공식 문서는 사고 모드에서 도구 호출이 포함된 대화의 후속 요청에 reasoning_content가 빠지면 400 오류가 발생할 수 있다고 명시합니다. 따라서 딥시크 V4 2차 요청 400이 발생했다면 재시도나 서버 확장보다 먼저 같은 세션의 응답, 메모리 객체, 저장 기록, 최종 요청을 순서대로 비교해야 합니다. 공식 API와 vLLM을 함께 사용한다면 reasoning_contentreasoning을 출구 단계에서 분리해 매핑해야 합니다. (api-docs.deepseek.com)

이 글은 첫 번째 function calling은 성공했지만 도구 결과를 추가한 다음 요청에서 실패하는 개발자를 위한 글입니다. 에이전트 메시지를 보관하는 백엔드, API 게이트웨이, 세션 데이터베이스, vLLM 운영팀도 대상입니다. 단순한 마이그레이션 안내가 아니라 어느 단계에서 필드가 사라졌는지 입증하는 로그 점검법에 초점을 둡니다.

마지막 업데이트: 2026년 8월 15일. 딥시크 공식 사고 모드·도구 호출 문서와 현재 vLLM 추론 출력 문서를 기준으로 확인했습니다. 필드명이나 이전 필드 호환 정책이 바뀌면 배포 버전의 프로토콜 정의를 다시 확인해야 합니다. (api-docs.deepseek.com)

첫 성공과 다음 400은 서로 다른 고장 신호입니다

첫 요청에서 모델이 tool_calls를 정상적으로 반환했다면 도구 정의, 함수 이름, 기본 JSON 형식은 일단 통과했을 가능성이 높습니다. 두 번째 요청에서만 400이 발생한다면 다음 네 가지를 우선 의심해야 합니다.

  • assistant 메시지의 reasoning_content가 다음 요청에서 빠졌습니다.
  • 저장소에는 값이 있지만 SDK가 새 객체를 만들면서 필드를 버렸습니다.
  • 공식 API용 reasoning_content와 vLLM용 reasoning을 변환하지 않았습니다.
  • assistant → tool → user 또는 assistant → tool 순서와 tool_call_id 연결이 깨졌습니다.

도구 자체를 여러 번 재시도하는 방식은 원인 확인에 도움이 되지 않습니다. 같은 세션에서 아래 세 항목을 확보하는 편이 낫습니다.

  1. 첫 번째 모델 응답의 탈취 방지 로그
  2. 도구 결과가 추가된 직후의 내부 메시지 배열
  3. 400이 발생한 최종 HTTP 요청의 탈취 방지 로그

로그에는 전체 추론 내용이나 실제 도구 결과를 남길 필요가 없습니다. 필드 이름, 값의 존재 여부, 메시지 인덱스, 값의 길이, tool_call_id만으로도 대부분의 누락 지점을 찾을 수 있습니다.

첫 번째 고장 유형: 최종 요청에 추론 필드가 없습니다

가장 빠른 판별법은 400 직전 요청의 assistant 메시지를 보는 것입니다.

{
  "role": "assistant",
  "content": "",
  "tool_calls": [
    {
      "id": "call_masked",
      "type": "function",
      "function": {
        "name": "get_data",
        "arguments": "{\"key\":\"masked\"}"
      }
    }
  ]
}

이 구조에 reasoning_content가 전혀 없다면 도구 정의보다 대화 기록 생성 로직을 먼저 봐야 합니다. 딥시크 공식 사고 모드에서는 도구 호출을 수행한 assistant 메시지의 reasoning_content를 후속 요청에 계속 전달해야 합니다. 공식 예제도 응답 메시지를 그대로 추가하거나, content, reasoning_content, tool_calls를 함께 복사하는 방식을 사용합니다. (api-docs.deepseek.com)

흔한 삭제 지점은 다음과 같습니다.

  • message.contentmessage.tool_calls만 저장하는 DTO
  • 직렬화 허용 목록에 없는 추가 필드
  • content를 가진 assistant 메시지를 제거하는 정리 함수
  • 스트리밍 조각을 합칠 때 reasoning_content를 누락하는 병합기
  • 모델 응답을 일반 채팅 메시지로 다시 만드는 변환기

공식 API의 최소 형태는 다음처럼 확인할 수 있습니다.

{
  "role": "assistant",
  "content": "",
  "reasoning_content": "masked",
  "tool_calls": ["masked"]
}

reasoning_content의 실제 내용은 로그에서 마스킹해야 합니다. 중요한 것은 값의 길이와 존재 여부입니다. 값이 응답 직후에는 있었지만 저장 직후 사라졌다면 모델 문제가 아닙니다. 메시지 보존 계층의 문제입니다.

두 번째 고장 유형: 공식 API와 vLLM의 필드 계약이 다릅니다

현재 vLLM 문서는 추론 출력의 주 필드를 reasoning으로 설명하고, reasoning_content를 이전 이름으로 안내합니다. 또한 도구 호출 파서는 reasoning이 아니라 content에서 함수 호출 정보를 읽는다고 설명합니다. 이 차이를 무시하면 한쪽 엔드포인트에서 받은 메시지를 다른 쪽으로 그대로 재전송할 때 문제가 생깁니다. (docs.vllm.ai)

구분 딥시크 공식 API 현재 vLLM 문서 기준
추론 출력 필드 reasoning_content reasoning
도구 호출 필드 tool_calls tool_calls
함수 호출 해석 위치 응답 메시지의 도구 호출 구조 content에서 도구 호출을 해석
후속 요청 처리 도구 호출이 있으면 추론 필드 재전송 필요 배포 버전의 입력 프로토콜을 별도 검증
주의점 사고 모드의 도구 호출 후 필드 누락 시 400 가능 이전 필드 호환을 영구 계약으로 보면 안 됨

이 표에서 중요한 점은 tool_calls라는 이름이 같다는 이유로 전체 메시지 계약까지 같다고 보면 안 된다는 것입니다. vLLM의 최신 문서와 이전 버전 문서에는 필드명이 다르게 표시된 시기가 있습니다. 따라서 특정 버전에서 reasoning_content 입력이 받아들여졌다는 사례를 모든 버전에 적용하면 안 됩니다. (docs.vllm.ai)

내부 객체는 한 가지 이름으로 통일하고, 전송 직전에 목적지에 맞게 바꾸는 구조가 안전합니다.

internal = {
    "content": assistant.get("content"),
    "reasoning": assistant.get("reasoning")
}

if endpoint_kind == "deepseek_api":
    outbound["reasoning_content"] = internal["reasoning"]
elif endpoint_kind == "vllm":
    outbound["reasoning"] = internal["reasoning"]

이때 내부 객체에 두 필드를 무조건 복사하는 방식은 권장되지 않습니다. 필드 두 개를 모두 넣으면 문제가 사라지는 것처럼 보일 수 있지만, 실제로는 어느 엔드포인트 계약을 지키는지 확인할 수 없게 됩니다. 목적지 유형, vLLM 버전, 요청 경로를 함께 기록해야 합니다.

중간 단계 비교: 필드는 어디에서 사라졌을까요?

두 번째 요청을 만들 때는 내용의 전체 값보다 필드 집합과 메시지 인덱스를 비교해야 합니다. 아래처럼 단계별 스냅샷을 남기면 데이터베이스와 게이트웨이를 빠르게 분리할 수 있습니다.

확인 지점 기록할 값 해석
모델 응답 직후 role, 필드 이름, 값 존재 여부, tool_calls 개수 모델 SDK가 값을 받았는지 확인
메모리 배열 추가 직후 메시지 인덱스, 필드 집합, tool_call_id 에이전트 루프가 값을 보존했는지 확인
데이터베이스 저장 직후 스키마 필드, 직렬화 결과, null 처리 저장 계층에서 제거됐는지 확인
큐 소비 직후 소비 전후 필드 집합, 객체 형식 재구성 또는 허용 목록 문제 확인
게이트웨이 전송 직전 최종 JSON 키, 헤더, 엔드포인트 유형 출구 변환과 라우팅 확인

각 지점에서 다음과 같은 축약 로그를 남길 수 있습니다.

{
  "message_index": 2,
  "role": "assistant",
  "keys": ["content", "reasoning_content", "tool_calls"],
  "reasoning_present": true,
  "reasoning_length": 1842,
  "tool_call_ids": ["call_masked"]
}

reasoning_length는 실제 추론 내용을 노출하지 않으면서 보존 여부를 확인하게 해줍니다. 값이 null인지, 빈 문자열인지, 키 자체가 없는지도 구분해야 합니다. 일부 정리 로직은 빈 문자열을 제거하고, 일부 직렬화기는 알 수 없는 키를 조용히 버립니다.

특히 스트리밍 응답은 별도로 확인해야 합니다. 추론 조각과 일반 내용 조각을 하나의 문자열로 합치면서 필드 경계가 사라질 수 있습니다. 마지막 청크만 저장하는 구현이라면 첫 청크에 포함된 추론 정보가 유실될 수 있습니다. vLLM 문서도 추론 출력과 도구 호출을 서로 다른 처리 대상으로 설명합니다. (docs.vllm.ai)

세 번째 고장 유형: 필드는 맞지만 메시지 체인이 틀렸습니다

reasoning_content 또는 reasoning이 최종 요청에 존재한다면 다음 순서를 확인해야 합니다.

user
assistant + reasoning 필드 + tool_calls
tool + 대응하는 tool_call_id

여러 도구를 호출한 경우에는 assistant 메시지의 모든 tool_calls에 대응하는 tool 메시지가 있어야 합니다. tool_call_id가 바뀌거나, 도구 결과가 assistant 메시지보다 먼저 배치되거나, 이전 assistant 메시지를 임의로 합치면 400의 원인이 될 수 있습니다.

또한 사고 모드에서 지원되지 않거나 의미가 달라지는 추가 파라미터도 확인해야 합니다. 딥시크 공식 문서는 사고 모드에서 temperature, top_p, presence_penalty, frequency_penalty가 지원되지 않는다고 안내합니다. 호환성을 위해 요청이 즉시 거부되지 않을 수 있지만, 값이 적용된다고 가정해서는 안 됩니다. (api-docs.deepseek.com)

따라서 아래 두 결론을 구분해야 합니다.

  • 추론 필드가 없고, 공식 API로 보내는 요청이라면: 필드 보존 또는 출구 매핑 문제일 가능성이 큽니다.
  • 추론 필드는 있고, 메시지 순서나 식별자가 다르다면: 대화 체인 재구성 문제를 우선 봅니다.
  • 둘 다 정상인데도 400이면: 오류 본문, tool_choice, 사고 모드 파라미터, 배포 버전의 입력 계약을 확인합니다.

오류 본문 없이 상태 코드만 저장하면 원인 판별력이 크게 떨어집니다. 비밀키, 전체 추론 내용, 도구 인자는 제거하되 오류 코드와 메시지, 엔드포인트 유형, 모델 식별자는 남겨야 합니다.

네 번째 단계: 최소 회화로 수정 위치를 확정합니다

수정 뒤에는 전체 업무 흐름을 바로 재시험하지 말고, 실패한 한 세션을 고정해 세 경로로 재생합니다.

  1. 딥시크 공식 API에 직접 전송합니다.
  2. 실제 배포된 vLLM 엔드포인트에 직접 전송합니다.
  3. SDK, 데이터베이스, 큐, 게이트웨이를 포함한 전체 경로로 전송합니다.

각 경로의 입력은 동일해야 합니다. 다만 출구 필드는 목적지 계약에 따라 달라질 수 있습니다. 공식 API 경로에는 reasoning_content, vLLM 경로에는 실제 배포 버전이 요구하는 reasoning 또는 호환 필드를 적용합니다. vLLM의 이전 필드 지원 여부는 문서와 실행 중인 버전에서 직접 확인해야 합니다. (docs.vllm.ai)

수정 전후에는 다음 차이를 기록합니다.

  • 메시지 인덱스가 바뀌었는가
  • assistant 메시지의 추론 필드가 유지됐는가
  • tool_call_id가 그대로인가
  • 게이트웨이가 새 필드를 제거하지 않았는가
  • 공식 API와 vLLM에 서로 다른 출구 변환이 적용됐는가
  • 오류가 발생한 정확한 요청이 동일하게 재생됐는가

조건에 따른 선택

  • 최종 공식 API 요청에 reasoning_content가 없다면 → 저장과 직렬화 계층부터 수정합니다.
  • 내부에는 reasoning만 있고 공식 API로 보내면 → 출구 어댑터에서 reasoning_content로 변환합니다.
  • vLLM 응답을 공식 API로 재사용한다면 → 그대로 전달하지 말고 버전별 변환 규칙을 적용합니다.
  • 필드와 순서가 모두 정상인데 400이면 → 오류 본문과 추가 파라미터를 기준으로 별도 재현합니다.
  • 장기적으로 여러 엔드포인트를 전환해야 한다면 → 전역 필드 복사가 아니라 엔드포인트별 계약 테스트를 둡니다.

재발 방지를 위한 로그 보관 기준

다중 function calling 장애는 모델 응답만 저장해서는 재현하기 어렵습니다. 한 회화 단위로 다음 메타데이터를 묶어 보관해야 합니다.

  • 세션 식별자와 요청 순번
  • 엔드포인트 종류
  • 모델 이름과 실행 중인 vLLM 버전
  • 메시지별 role과 인덱스
  • 추론 필드 이름과 존재 여부
  • 추론 값의 길이
  • tool_call_id와 함수 이름
  • 직렬화 전후의 키 차이
  • 오류 상태 코드와 오류 본문

민감한 추론 내용은 해시나 길이로 대체하면 됩니다. 데이터베이스 스키마가 바뀔 때는 기존 회화 기록을 새 객체로 변환하는 테스트도 필요합니다. 오래된 기록을 읽어 새 메시지를 만들 때 필드가 빠지는 문제가 실제 운영에서 자주 숨어 있기 때문입니다.

이런 회귀 환경은 고정된 실행 조건에서 검증해야 합니다. ProxyMac의 콘솔 사용 안내를 통해 테스트 환경 접근 경로를 확인하고, 환경별 접속 문제는 도움말 페이지에서 먼저 분리하면 로그 재생 자체의 변수를 줄일 수 있습니다.

자주 묻는 내용

딥시크 V4에서 첫 도구 호출은 성공했는데 다음 요청만 실패하는 이유는 무엇인가요?

첫 번째 요청이 성공했다면 도구 정의와 기본 호출 형식은 통과했을 가능성이 큽니다. 이후 요청에서만 실패하는 경우에는 assistant 메시지의 reasoning_content가 저장 과정에서 빠졌거나, tool 메시지와 다음 요청의 순서가 바뀌었거나, 공식 API용 필드가 vLLM 형식으로 그대로 전달된 경우를 먼저 확인해야 합니다.

reasoning_content를 데이터베이스에 저장했는데도 다중 요청이 실패할 수 있나요?

가능합니다. 저장 여부와 최종 요청 포함 여부는 별개입니다. 데이터베이스 스키마에는 값이 남아 있어도 SDK 변환기, 게이트웨이 허용 목록, 큐 소비자, 빈 문자열 정리 로직이 해당 필드를 제거할 수 있습니다. 저장 직후와 외부 전송 직전의 필드 목록을 각각 비교해야 합니다.

vLLM에서 받은 reasoning을 딥시크 공식 API에 그대로 보내도 되나요?

그대로 보내는 방식은 피해야 합니다. 현재 vLLM 문서는 추론 출력의 주 필드로 reasoning을 사용하고, reasoning_content를 이전 이름으로 설명합니다. 반면 딥시크 공식 사고 모드의 도구 호출 대화는 reasoning_content 재전송을 요구합니다. 내부 표준 필드를 만든 뒤 목적지별 출구 변환을 적용해야 합니다.

다중 tool call에서 누락된 필드를 찾으려면 어떤 로그를 남겨야 하나요?

한 세션의 첫 assistant 응답, tool 결과, 다음 요청을 한 묶음으로 보존해야 합니다. 각 단계에서 메시지 인덱스, role, content 존재 여부, 추론 필드 이름, tool_call_id, tool_calls 개수, 최종 직렬화 결과를 기록합니다. 추론 내용과 도구 인자는 마스킹하고, 필드 집합과 순서 차이를 중심으로 비교합니다.

첫 번째 요청 성공 뒤 두 번째 요청에서만 400이 난다면 현재 환경을 무작정 확장할 이유는 없습니다. 기존 방식은 재시도에 의존하고, 중간 게이트웨이에서 알 수 없는 필드를 삭제하며, 공식 API와 vLLM의 계약 차이를 한 객체로 처리하는 약점이 있습니다. 반면 고정된 클라우드 맥 환경에서 실패 회화를 반복 재생하면 SDK, 데이터베이스, 게이트웨이, 출구 어댑터 중 어느 계층을 고쳐야 하는지 비교하기 쉽습니다. macOS 클라이언트, Xcode 자동화, 장시간 온라인 작업까지 함께 검증해야 한다면 ProxyMac의 클라우드 맥 환경을 임시 회귀 테스트 용도로 검토할 수 있습니다. 장기 고정 부하나 물리 장비 연결이 필요하다면 직접 장비를 운영하는 편이 더 적합합니다.

안정적인 맥 개발 환경으로 오류를 직접 점검하세요

ProxyMac은 전용 맥 미니를 제공해 요청 흐름과 실행 결과를 일정한 환경에서 재현할 수 있도록 지원합니다.
셸 접속과 원격 화면을 통해 개발 도구를 자유롭게 실행하고 오류 발생 과정을 단계별로 확인할 수 있습니다.