ストリーミング出力が必要な理由
非ストリーミング(stream=false)は完全なレスポンスが返るまで待つため、ユーザーは長時間の空白待ちを強いられます。ストリーミング出力(stream=true)は生成内容をトークン単位で段階的に返すため、ユーザーはテキストがリアルタイムで表示されるのを確認でき、体験が大幅に向上します。DeepSeek V4 は OpenAI 互換の SSE(Server-Sent Events)ストリーミングプロトコルをサポートしています。
Python でのストリーミング呼び出し
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'),
base_url='https://api.deepseek.com'
)
stream = client.chat.completions.create(
model='deepseek-v4-flash',
messages=[{'role': 'user', 'content': 'Python を紹介する500字の記事を書いて'}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end='', flush=True)Node.js でのストリーミング呼び出し
import OpenAI from 'openai';
const openai = new OpenAI({
baseURL: 'https://api.deepseek.com',
apiKey: process.env.DEEPSEEK_API_KEY,
});
const stream = await openai.chat.completions.create({
model: 'deepseek-v4-flash',
messages: [{ role: 'user', content: '機械学習とは何か説明してください' }],
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
process.stdout.write(content);
}フロントエンドでの一文字ずつの描画
fetch と ReadableStream を使ってフロントエンドでタイプライター効果を実装します:
async function streamChat(prompt) {
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiKey
},
body: JSON.stringify({
model: 'deepseek-v4-flash',
messages: [{ role: 'user', content: prompt }],
stream: true
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullText = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(l => l.startsWith('data: '));
for (const line of lines) {
if (line === 'data: [DONE]') break;
const data = JSON.parse(line.slice(6));
const content = data.choices[0]?.delta?.content;
if (content) {
fullText += content;
document.getElementById('output').textContent = fullText;
}
}
}
}ストリーミング出力の中断
AbortController を使用してユーザーが生成を停止できるようにします:
const controller = new AbortController();
// 中断シグナルを fetch にバインド
const response = await fetch(url, { signal: controller.signal, ... });
// ユーザーが停止ボタンをクリック
document.getElementById('stopBtn').onclick = () => controller.abort();思考モードのストリーミング処理
思考モードを有効にすると、ストリーミング出力に reasoning_content と content が交互に現れます。それぞれを個別に処理できます:思考プロセスは折りたたみ可能な領域にまとめ、最終的な回答は直接表示します。
ベストプラクティス
- デフォルトでストリーミングを使用:完全な JSON 解析が必要でない限り、stream=true に設定
- 再接続メカニズムを追加:ネットワーク中断時に自動的に再試行
- レンダリングをスロットル:フロントエンドで requestAnimationFrame を使用して DOM 更新をバッチ処理
- エラーハンドリング:ストリーミングエラーをキャッチしてユーザーに通知
- タイムアウト制御:無限待ちを避けるために適切なタイムアウトを設定