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