AI APIを本番環境に組み込む際、最も厄介なバグの一つが「リクエストは成功しているのに応答テキストが空文字列になる」現象です。本稿では、ECサイトのAIカスタマーサービスを急成長させている開発チームの実例をベースに、この問題の根本原因と安全な対策を解説します。

問題の概要:なぜ空応答が発生するのか

APIが空白の応答を返す主な原因として、以下の3つがあります。

特にコンテンツフィルターが働いた場合、多くの開発者は空のcontentフィールドに気づかず、後続の処理でNone参照エラー空白文字列処理の問題に苦しみます。

実践的なコード例:Pythonでの安全な応答処理

import requests
import json
from typing import Optional

class HolySheepAIClient:
    """HolySheep AI APIの安全なラッパー"""
    
    def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"):
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
    
    def chat_completion(self, prompt: str, max_tokens: int = 1024) -> dict:
        """
        チャット補完リクエストを実行し、空応答を適切に処理する
        
        Returns:
            dict: {
                "text": str,          # 生成テキスト(空の場合あり)
                "finish_reason": str, # stop/content_filter/length
                "content_filter": bool, # フィルターされたか
                "error": Optional[str]  # エラーがある場合のメッセージ
            }
        """
        url = f"{self.base_url}/chat/completions"
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        payload = {
            "model": "gpt-4.1",
            "messages": [
                {"role": "user", "content": prompt}
            ],
            "max_tokens": max_tokens,
            "temperature": 0.7
        }
        
        response = requests.post(url, headers=headers, json=payload, timeout=30)
        
        if response.status_code != 200:
            return {
                "text": "",
                "finish_reason": "error",
                "content_filter": False,
                "error": f"HTTP {response.status_code}: {response.text}"
            }
        
        data = response.json()
        choices = data.get("choices", [])
        
        if not choices:
            return {
                "text": "",
                "finish_reason": "no_choices",
                "content_filter": False,
                "error": "API応答にchoicesが含まれません"
            }
        
        first_choice = choices[0]
        message = first_choice.get("message", {})
        text = message.get("content", "") or ""
        finish_reason = first_choice.get("finish_reason", "unknown")
        
        # content_filter 检测:finish_reason が content_filter の場合
        content_filter_triggered = finish_reason == "content_filter"
        
        # 空文字列でも内容をログに残す(デバッグ用)
        if not text:
            print(f"[WARNING] 空応答を検出: finish_reason={finish_reason}")
        
        return {
            "text": text,
            "finish_reason": finish_reason,
            "content_filter": content_filter_triggered,
            "error": None
        }


def handle_customer_inquiry(client: HolySheepAIClient, user_message: str) -> str:
    """
    ECサイトのカスタマーサービス対応
    
    空応答の場合はフォールバックメッセージを返す
    """
    result = client.chat_completion(
        prompt=f"あなたはECサイトのAI客服です。以下の問い合わせに回答してください:{user_message}"
    )
    
    if result["content_filter"]:
        return "申し訳ございません。一時的にサービスを提供できません。"
    
    if not result["text"]:
        return "現在込み合っています。しばらくしてから再度お試しください。"
    
    return result["text"]


使用例

if __name__ == "__main__": client = HolySheepAIClient(api_key="YOUR_HOLYSHEEP_API_KEY") response = client.chat_completion("商品の在庫を確認する方法を教えてください") print(f"finish_reason: {response['finish_reason']}") print(f"content_filter: {response['content_filter']}") print(f"応答: {response['text']}")

JavaScript/Node.jsでの実装例

/**
 * HolySheep AI API 応答プロセッサー
 * 空文字列とfinish_reasonを安全に処理
 */

const https = require('https');

class HolySheepAIClient {
    constructor(apiKey) {
        this.apiKey = apiKey;
        this.baseUrl = 'api.holysheep.ai';
        this.basePath = '/v1/chat/completions';
    }

    async chatCompletion(messages, options = {}) {
        const {
            model = 'gpt-4.1',
            maxTokens = 1024,
            temperature = 0.7
        } = options;

        const postData = JSON.stringify({
            model,
            messages,
            max_tokens: maxTokens,
            temperature
        });

        const options_ = {
            hostname: this.baseUrl,
            path: this.basePath,
            method: 'POST',
            headers: {
                'Authorization': Bearer ${this.apiKey},
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(postData)
            }
        };

        return new Promise((resolve, reject) => {
            const req = https.request(options_, (res) => {
                let data = '';
                
                res.on('data', (chunk) => {
                    data += chunk;
                });
                
                res.on('end', () => {
                    try {
                        const jsonData = JSON.parse(data);
                        const result = this._parseResponse(jsonData);
                        resolve(result);
                    } catch (error) {
                        reject(new Error(JSON解析エラー: ${error.message}));
                    }
                });
            });

            req.on('error', (error) => {
                reject(new Error(リクエストエラー: ${error.message}));
            });

            req.setTimeout(30000, () => {
                req.destroy();
                reject(new Error('リクエストタイムアウト'));
            });

            req.write(postData);
            req.end();
        });
    }

    _parseResponse(jsonData) {
        // HTTPエラー応答のチェック
        if (jsonData.error) {
            return {
                text: '',
                finishReason: 'api_error',
                contentFilter: false,
                error: jsonData.error.message || '不明なAPIエラー'
            };
        }

        const choices = jsonData.choices || [];
        
        if (choices.length === 0) {
            return {
                text: '',
                finishReason: 'no_choices',
                contentFilter: false,
                error: 'choicesが空です'
            };
        }

        const firstChoice = choices[0];
        const message = firstChoice.message || {};
        
        // undefined/nullを空文字列に変換
        const text = (message.content === null || message.content === undefined) 
            ? '' 
            : String(message.content);
        
        const finishReason = firstChoice.finish_reason || 'unknown';
        
        // コンテンツフィルター判定
        const contentFilter = finishReason === 'content_filter';

        // デバッグログ
        if (!text) {
            console.warn([HolySheep] 空応答: finish_reason=${finishReason});
        }

        return {
            text,
            finishReason,
            contentFilter,
            error: null
        };
    }
}

// 使用例
async function main() {
    const client = new HolySheepAIClient('YOUR_HOLYSHEEP_API_KEY');

    try {
        const result = await client.chatCompletion([
            { role: 'user', content: 'おすすめ商品を教えてください' }
        ]);

        if (result.contentFilter) {
            console.log('コンテンツフィルターが作動しました');
            console.log('フォールバック応答を返します');
        }

        if (!result.text) {
            console.log('空応答を検出');
            console.log(理由: ${result.finishReason});
        }

        console.log('応答:', result.text);
    } catch (error) {
        console.error('エラー:', error.message);
    }
}

main();

finish_reasonの詳細な解釈ガイド

API応答のfinish_reasonフィールドは、生成がなぜ終了したかを示します。 각각の値に応じて適切な処理を行う必要があります。

よくあるエラーと対処法

1. 空文字列导致的NullPointerException

問題:応答のcontentnullの場合にそのまま後続処理に渡す

解決:常にcontent || ""または同等の空文字列変換を行う

# 悪い例
text = message["content"]  # None の可能性がある

良い例

text = message.get("content") or ""

2. コンテンツフィルター未検出による不適切な応答表示

問題finish_reason=content_filterなのに空テキストをそのまま表示しようとする

解決:必ずfinish_reasonをチェックし、content_filter時は代替メッセージを表示する

if result["finish_reason"] == "content_filter":
    return "申し訳ございません。一時的にこのサービスを提供できません。"

3. max_tokens設定不足による中途半端な応答

問題finish_reason=lengthで返答が途中で切れる

解決:用途に応じてmax_tokensを適切に増加させる。DeepSeek V3.2の場合、$0.42/MTokと低コストなので多めに設定しても経済的です

# 長い回答が必要な場合はmax_tokensを増やす
result = client.chat_completion(
    prompt=long_prompt,
    max_tokens=2048  # デフォルト1024では不足する場合がある
)

4. レート制限による空応答の見逃し

問題:429 Too Many Requests時に空のbodyが返り、エラー処理が動作しない

解決:HTTPステータスコードのチェックを必ず実装し、429時は指数バックオフでリトライする

import time

def chat_with_retry(client, prompt, max_retries=3):
    for attempt in range(max_retries):
        response = client.chat_completion(prompt)
        
        if response.get("error"):
            if "429" in str(response["error"]) or "rate limit" in str(response["error"]).lower():
                wait_time = 2 ** attempt
                time.sleep(wait_time)
                continue
            return response
        
        return response
    
    return {"text": "", "error": "リトライ上限超過"}

HolySheep AIを選ぶ理由:コストと信頼性

本稿のコード例は全て今すぐ登録すれば誰でも試せるHolySheep AI APIを使用しています。HolySheep AIの主要な利点は以下の通りです:

出力価格の面では、DeepSeek V3.2が$0.42/MTokと最も経済的で、Gemini 2.5 Flashが$2.50/MTok、長い応答を多用するRAGシステムにも適しています。

まとめ

API応答が空文字列になる問題は、コンテンツフィルター、finish_reasonの不正な解釈、max_tokens設定の不足など、複数の要因で発生します。本稿で示したように、

  1. 常にcontent || ""で空文字列変換を行う
  2. finish_reasonを必ずチェックしてcontent_filterを検出する
  3. 適切なmax_tokensを設定する
  4. エラー時のリトライロジックを実装する

これらの対策を実装することで、空応答によるサービス障害を未然に防ぎ、安定したAIサービスを構築できます。

👉 HolySheep AI に登録して無料クレジットを獲得