Skip to main content

Streaming Chat Responses

Handle streaming responses from chatbots for real-time user experience.

Why Stream?

BenefitExplanation
Faster UXUsers see text as it's generated
Better perceptionShortens perceived response time
Long contentWorks better for generated stories, code
Cost feedbackUsers can stop long generations early

Basic Streaming

Python Example

import requests

def chat_stream(chatbot_id, message):
"""Stream chatbot response"""
response = requests.post(
"http://localhost/api/v1/chatbot/chat",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"chatbot_id": chatbot_id,
"message": message,
"stream": True
},
stream=True
)

print("Bot: ", end="", flush=True)

for line in response.iter_lines():
if line:
data = line.decode('utf-8')
if "response" in data:
response_text = data.replace("response:", "").strip()
print(response_text, end="", flush=True)

print() # Newline after complete

# Usage
chat_stream("your-chatbot-id", "Tell me a long story about AI")

JavaScript Example

async function chatStream(chatbotId, message) {
const response = await fetch('http://localhost/api/v1/chatbot/chat', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatbot_id: chatbotId,
message,
stream: true
})
});

if (!response.ok) {
throw new Error('Stream request failed');
}

const reader = response.body.getReader();
const decoder = new TextDecoder();

process.stdout.write('Bot: ');

while (true) {
const { done, value } = await reader.read();
if (done) break;

const chunk = decoder.decode(value);
const lines = chunk.split('\n');

for (const line of lines) {
if (line.startsWith('response:')) {
const content = line.slice(9);
process.stdout.write(content);
}
}
}

process.stdout.write('\n');
}

// Usage
chatStream('your-chatbot-id', 'Tell me about the future of AI')
.catch(console.error);

Web Application Streaming

React Component

import React, { useState, useCallback, useRef } from 'react';

function StreamingChat() {
const [chatbotId] = useState('your-chatbot-id');
const [message, setMessage] = useState('');
const [streamingResponse, setStreamingResponse] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const responseEndRef = useRef(null);

const startStream = useCallback(async () => {
if (!message.trim() || isStreaming) return;

setIsStreaming(true);
setStreamingResponse('');
responseEndRef.current = null;

try {
const response = await fetch('http://localhost/api/v1/chatbot/chat', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatbot_id: chatbotId,
message,
stream: true
})
});

if (!response.ok) {
throw new Error('Stream failed');
}

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
const { done, value } = await reader.read();
if (done) break;

const chunk = decoder.decode(value);
const lines = chunk.split('\n');

for (const line of lines) {
if (line.startsWith('response:')) {
const content = line.slice(9);
setStreamingResponse(prev => prev + content);
}
}
}

setIsStreaming(false);
} catch (error) {
console.error('Stream error:', error);
setIsStreaming(false);
}
}, [chatbotId, message, isStreaming]);

const stopStream = () => {
setIsStreaming(false);
if (responseEndRef.current) {
responseEndRef.current.abort();
}
};

return (
<div className="max-w-4xl mx-auto p-6">
<div className="mb-4">
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
className="w-full p-2 border rounded"
rows="3"
placeholder="Type your message..."
disabled={isStreaming}
/>
</div>

<div className="space-x-2 mb-4">
<button
onClick={startStream}
disabled={isStreaming || !message.trim()}
className="px-6 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
>
{isStreaming ? 'Streaming...' : 'Send'}
</button>

{isStreaming && (
<button
onClick={stopStream}
className="px-4 py-2 bg-red-500 text-white rounded"
>
Stop
</button>
)}
</div>

<div className="border rounded-lg p-4 bg-gray-50 min-h-32">
<pre className="whitespace-pre-wrap">
{streamingResponse || <span className="text-gray-400">Response will appear here...</span>}
</pre>
</div>
</div>
);
}

export default StreamingChat;

Vue.js Component

<template>
<div>
<div class="mb-4">
<textarea
v-model="message"
@keydown.enter="startStream"
class="w-full p-2 border rounded"
rows="3"
placeholder="Type your message..."
:disabled="isStreaming"
/>
</div>

<div class="space-x-2 mb-4">
<button
@click="startStream"
:disabled="isStreaming || !message"
class="px-6 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
>
{{ isStreaming ? 'Streaming...' : 'Send' }}
</button>

<button
v-if="isStreaming"
@click="stopStream"
class="px-4 py-2 bg-red-500 text-white rounded"
>
Stop
</button>
</div>

<div class="border rounded-lg p-4 bg-gray-50 min-h-32">
<pre class="whitespace-pre-wrap">
{{ streamingResponse || 'Response will appear here...' }}
</pre>
</div>
</div>
</template>

<script>
export default {
data() {
return {
chatbotId: 'your-chatbot-id',
message: '',
streamingResponse: '',
isStreaming: false,
controller: null
};
},

methods: {
async startStream() {
if (!this.message.trim() || this.isStreaming) return;

this.isStreaming = true;
this.streamingResponse = '';

try {
const response = await fetch('http://localhost/api/v1/chatbot/chat', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatbot_id: this.chatbotId,
message: this.message,
stream: true
})
});

if (!response.ok) {
throw new Error('Stream failed');
}

this.controller = new AbortController();
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
const { done, value } = await reader.read();
if (done) break;

const chunk = decoder.decode(value);
const lines = chunk.split('\n');

for (const line of lines) {
if (line.startsWith('response:')) {
const content = line.slice(9);
this.streamingResponse += content;
}
}
}

this.isStreaming = false;
} catch (error) {
console.error('Stream error:', error);
this.isStreaming = false;
}
},

stopStream() {
if (this.controller) {
this.controller.abort();
}
this.isStreaming = false;
}
}
};
</script>

Streaming with Typing Effect

class TypingEffectStream {
async chatWithTyping(chatbotId, message) {
const response = await fetch('http://localhost/api/v1/chatbot/chat', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatbot_id: chatbotId,
message,
stream: true
})
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

let wordBuffer = '';

while (true) {
const { done, value } = await reader.read();
if (done) break;

const chunk = decoder.decode(value);
const lines = chunk.split('\n');

for (const line of lines) {
if (line.startsWith('response:')) {
const newContent = line.slice(9);
const words = newContent.split(' ');
wordBuffer += newContent;

// Type out words with natural delay
while (wordBuffer.length > 0) {
const words = wordBuffer.split(' ');
const word = words.shift();
process.stdout.write((wordBuffer.length > 0 ? ' ' : '') + word);
wordBuffer = words.join(' ');

if (wordBuffer.length < 5) {
await new Promise(r => setTimeout(r, 50)); // Fast typing
} else if (wordBuffer.length < 15) {
await new Promise(r => setTimeout(r, 80)); // Medium typing
} else {
await new Promise(r => setTimeout(r, 120)); // Slow typing for punctuation
}
}
}
}
}
}

// Usage
const streamer = new TypingEffectStream();
streamer.chatWithTyping('your-chatbot-id', 'Tell me a long story')
.catch(console.error);

Stream Completion Detection

async function chatWithCompletionDetection(chatbotId, message) {
let fullResponse = '';
let complete = false;

const response = await fetch('http://localhost/api/v1/chatbot/chat', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
chatbot_id: chatbotId,
message,
stream: true
})
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (!complete) {
const { done, value } = await reader.read();
if (done) break;

const chunk = decoder.decode(value);
const lines = chunk.split('\n');

for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(5));

if (data.finish_reason) {
complete = true;
}

if (data.choices && data.choices[0]?.delta?.content) {
const content = data.choices[0].delta.content;
fullResponse += content;
process.stdout.write(content);
}
}
}
}

process.stdout.write('\n');
console.log('Complete response:', fullResponse);
}

chatWithCompletionDetection('your-chatbot-id', 'Hello!')
.catch(console.error);

Best Practices

  1. Always Use Conversation IDs - Maintain context across streaming requests
  2. Handle Errors - Implement proper error handling for streams
  3. Stop Capability - Allow users to cancel long generations
  4. Buffer Display - Show text as it arrives for better UX
  5. Complete Detection - Detect when stream is finished
  6. Timeout Handling - Set appropriate timeouts to prevent hanging

Next Steps