Why Streaming Output is Needed
Non-streaming (stream=false) waits for the complete response before returning, leaving users staring at a long blank wait. Streaming output (stream=true) returns generated content token by token, allowing users to see text appear in real time, greatly improving the experience. DeepSeek V4 supports the OpenAI-compatible SSE (Server-Sent Events) streaming protocol.
Python Streaming Call
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': 'Write a 500-word article introducing Python'}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end='', flush=True)Node.js Streaming Call
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: 'Explain what machine learning is' }],
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
process.stdout.write(content);
}Frontend Character-by-Character Rendering
Implement a typewriter effect on the frontend using 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;
}
}
}
}Interrupting Streaming Output
Use AbortController to let users stop generation:
const controller = new AbortController();
// Bind the abort signal to fetch
const response = await fetch(url, { signal: controller.signal, ... });
// User clicks stop
document.getElementById('stopBtn').onclick = () => controller.abort();Streaming Handling for Thinking Mode
When thinking mode is enabled, reasoning_content and content alternate in the stream. They can be handled separately: collapse the thinking process into an expandable area, and display the final answer directly.
Best Practices
- Use streaming by default: unless full JSON parsing is needed, set stream=true
- Add reconnection logic: automatically retry on network interruptions
- Throttle rendering: use requestAnimationFrame on the frontend to batch DOM updates
- Error handling: catch streaming errors and notify users
- Timeout control: set a reasonable timeout to avoid infinite waits