DeepSeek V3では問題なく動作していたJSON出力が、V4 Flashに移行した途端に壊れる・空になるというケースが多数報告されています。この記事では原因の調査過程と、現時点で取れる対策をまとめます。


症状:content が空白トークンで埋まって返ってくる

典型的な症状は以下のようなレスポンスです。completion_tokens が 150〜220 あるにもかかわらず、content がスペースだらけで実質空になっています。

Array
(
    [choices] => Array
        (
            [0] => Array
                (
                    [message] => Array
                        (
                            [role] => assistant
                            [content] =>                        (空白のみ)
                        )
                    [finish_reason] => stop
                )
        )
    [usage] => Array
        (
            [completion_tokens] => 220
            [total_tokens] => 1764
        )
)

トークンは消費されているのに出力が空、という厄介な挙動です。


原因1:thinking モードがデフォルトで有効になった

V4 では「thinking(推論)モード」がデフォルトで搭載されており、モデルの思考プロセスが reasoning_content として別フィールドに格納されます。V3 にはこの仕組みがなかったため、同じコードでも挙動が変わります。

220トークンを消費しつつ content が空の場合、そのトークンが reasoning_content 側に流れている可能性があります。

対策:thinking を明示的に無効化する

$payload = json_encode([
    'model'    => 'deepseek-v4-flash',
    'messages' => $messages,
    'thinking' => ['type' => 'disabled'],  // ← 追加
]);

なお、reasoning_effort: 'none' はV4では無効なパラメータでAPIエラーになるため注意してください。


原因2:temperature: 0 との相性問題

V4のMoE(Mixture of Experts)アーキテクチャでは、temperature: 0 との相性が悪く、空レスポンスの一因になりうると報告されています。公式推奨は temperature: 1.0 です。

$payload = json_encode([
    'model'       => 'deepseek-v4-flash',
    'messages'    => $messages,
    'temperature' => 1.0,   // 0 → 1.0 に変更
    'thinking'    => ['type' => 'disabled'],
    'response_format' => ['type' => 'json_object'],
]);

response_format: json_object を指定している場合、構造は守られるため temperature を上げても JSON の形式は崩れません。

対策:max_tokens を十分に確保する

max_tokens が低すぎると、モデルが JSON を出力し始める前に打ち切られ、空や不完全なレスポンスになることがあります。特に response_format: json_object 使用時は、出力前に内部処理でトークンを消費するため、余裕を持った値を設定してください。

$payload = json_encode([
    'model'           => 'deepseek-v4-flash',
    'messages'        => $messages,
    'max_tokens'      => 4096,   // 実際の出力量より大きめに設定
    'temperature'     => 1.0,
    'thinking'        => ['type' => 'disabled'],
    'response_format' => ['type' => 'json_object'],
]);

期待する JSON が数百トークン程度であっても、max_tokens は 2048〜4096 以上を目安に設定することをおすすめします。設定値が大きくても実際に出力したトークン分しか課金されないため、余裕を持たせてもコスト上のデメリットはありません。


原因3:response_format と thinking の干渉

response_format: json_object 使用時は、プロンプト内に必ず “json” という単語を含める必要があります。含まれていないと空白や壊れた出力になるケースが確認されています。

// systemプロンプトに明示的にJSONを指示する
['role' => 'system', 'content' => 'You must respond in valid JSON format.']

根本的な問題:V4 Flash のサービング不安定

上記の対策をすべて施しても、数回に一度は空レスポンスが返ってくるケースが残ります。これはパラメータの問題ではなく、DeepSeek V4 Flash サーバー側のバグとして広く認識されています。高負荷時に JSON が途中で切れても HTTP 200 で返ってくることもあり、エラーとして検知しにくい点も厄介です。

現状、完全な回避策はなく、リトライで対処するのが現実的です。

function callDeepSeek($messages, $maxRetries = 3) {
    for ($i = 0; $i < $maxRetries; $i++) {
        $response = callApi($messages); // 既存のAPI呼び出し
        $content  = trim($response['choices'][0]['message']['content'] ?? '');

        if ($content !== '') {
            return $content;
        }

        // 空だった場合は少し待ってリトライ
        if ($i < $maxRetries - 1) {
            usleep(300000); // 0.3秒待機
        }
    }
    return false; // 3回すべて空だった場合
}

数回に一度の頻度であれば、3回リトライすればほぼ通ります。


まとめ:DeepSeek V4 は JSON 出力が現時点で不安定、バグ修正待ちの状態

調査・検証の結果、以下のことがわかりました。

  • V3 → V4 移行で thinking モードが追加され、content が空になるケースが増加
  • thinking: disabledtemperature: 1.0・プロンプトへの “json” 明示で頻度は下げられる
  • それでも数回に一度は空レスポンスが発生するサーバー側のバグが残っている
  • 高負荷時のトランケーション(途中切れが HTTP 200 で返る)も報告されている
  • 根本的な修正はDeepSeek側のアップデート待ちとなっており、現時点での完全な回避策はない

現時点のベストプラクティスは「パラメータを最適化しつつ、リトライを必ず実装する」ことです。V4 Flash はまだプレビュー段階であり、安定性の改善がアナウンスされるまではリトライロジックを保険として残しておくことを強くおすすめします。