Streaming
Stream chat completions in real-time with Server-Sent Events
Overview
Stream chat completions token-by-token to provide better user experience and lower perceived latency. Perfect for chatbots, live coding assistants, and interactive applications.
Authentication: API Key or OAuth token
Required Scopes (OAuth): ai:chat
Benefits
- Immediate Feedback - Users see responses as they're generated
- Lower Latency - First token arrives in ~1 second
- Better UX - Feels much faster than waiting for complete response
- Real-time Updates - Show typing indicators and progress
Stream Chat Completion
Generate a streaming chat completion response.
Endpoint: POST /api/v1/chat/completions
import OpenAI from 'openai'
const client = new OpenAI({
apiKey: process.env.GATEWAY_API_KEY,
baseURL: 'https://hypery.ai/api/v1'
})
const stream = await client.chat.completions.create({
model: 'anthropic/claude-3.5-sonnet',
messages: [
{ role: 'user', content: 'Write a poem about coding' }
],
stream: true
})
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || ''
process.stdout.write(content)
}from openai import OpenAI
client = OpenAI(
api_key="your-api-key",
base_url="https://hypery.ai/api/v1"
)
stream = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[
{"role": "user", "content": "Write a poem about coding"}
],
stream=True
)
for chunk in stream:
content = chunk.choices[0].delta.content or ""
print(content, end="")curl -N https://hypery.ai/api/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-3.5-sonnet",
"messages": [
{"role": "user", "content": "Write a poem about coding"}
],
"stream": true
}'Input Parameters
All standard chat completion parameters are supported. Set stream: true to enable streaming.
Response: Server-Sent Events stream
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1699564800,"model":"anthropic/claude-3.5-sonnet","choices":[{"index":0,"delta":{"role":"assistant","content":"In"},"finish_reason":null}]}
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1699564800,"model":"anthropic/claude-3.5-sonnet","choices":[{"index":0,"delta":{"content":" lines"},"finish_reason":null}]}
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1699564800,"model":"anthropic/claude-3.5-sonnet","choices":[{"index":0,"delta":{"content":" of"},"finish_reason":null}]}
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1699564800,"model":"anthropic/claude-3.5-sonnet","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
Output Fields (Per Chunk)
React Integration
Build a streaming chat interface with React:
'use client'
import { useState } from 'react'
import OpenAI from 'openai'
export function ChatStream() {
const [response, setResponse] = useState('')
const [loading, setLoading] = useState(false)
const handleStream = async (prompt) => {
setLoading(true)
setResponse('')
const client = new OpenAI({
apiKey: process.env.NEXT_PUBLIC_GATEWAY_API_KEY,
baseURL: 'https://hypery.ai/api/v1',
dangerouslyAllowBrowser: true
})
const stream = await client.chat.completions.create({
model: 'anthropic/claude-3.5-sonnet',
messages: [{ role: 'user', content: prompt }],
stream: true
})
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || ''
setResponse(prev => prev + content)
}
setLoading(false)
}
return (
<div>
<button onClick={() => handleStream('Tell me a joke')}>
Stream Response
</button>
<div className="whitespace-pre-wrap">{response}</div>
{loading && <span className="animate-pulse">▊</span>}
</div>
)
}# Flask streaming endpoint
from flask import Flask, Response, stream_with_context
from openai import OpenAI
app = Flask(__name__)
@app.route('/stream')
def stream_chat():
client = OpenAI(
api_key="your-api-key",
base_url="https://hypery.ai/api/v1"
)
def generate():
stream = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[{"role": "user", "content": "Tell a story"}],
stream=True
)
for chunk in stream:
content = chunk.choices[0].delta.content or ""
if content:
yield f"data: {content}\n\n"
return Response(
stream_with_context(generate()),
mimetype='text/event-stream'
)Raw Fetch API
Stream without using the OpenAI SDK:
async function streamChat(prompt) {
const response = await fetch(
'https://hypery.ai/api/v1/chat/completions',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'anthropic/claude-3.5-sonnet',
messages: [{ role: 'user', content: prompt }],
stream: true
})
}
)
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').filter(line => line.trim())
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6)
if (data === '[DONE]') return
try {
const parsed = JSON.parse(data)
const content = parsed.choices[0]?.delta?.content || ''
process.stdout.write(content)
} catch (e) {
// Skip invalid JSON
}
}
}
}
}import requests
import json
def stream_chat(prompt):
response = requests.post(
'https://hypery.ai/api/v1/chat/completions',
headers={'Authorization': f'Bearer {api_key}'},
json={
'model': 'anthropic/claude-3.5-sonnet',
'messages': [{'role': 'user', 'content': prompt}],
'stream': True
},
stream=True
)
for line in response.iter_lines():
if line:
line = line.decode('utf-8')
if line.startswith('data: '):
data = line[6:]
if data == '[DONE]':
break
try:
chunk = json.loads(data)
content = chunk['choices'][0]['delta'].get('content', '')
print(content, end='', flush=True)
except json.JSONDecodeError:
passStreaming with Function Calling
Function arguments are streamed incrementally when using tools.
const stream = await client.chat.completions.create({
model: 'anthropic/claude-3.5-sonnet',
messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
tools: [
{
type: 'function',
function: {
name: 'get_weather',
description: 'Get weather for a location',
parameters: {
type: 'object',
properties: {
location: { type: 'string' }
},
required: ['location']
}
}
}
],
stream: true
})
let functionArgs = ''
for await (const chunk of stream) {
const delta = chunk.choices[0].delta
if (delta.tool_calls) {
const args = delta.tool_calls[0]?.function?.arguments || ''
functionArgs += args
}
if (chunk.choices[0].finish_reason === 'tool_calls') {
const parsed = JSON.parse(functionArgs)
console.log('Calling:', parsed)
}
}stream = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[{"role": "user", "content": "What is the weather in Paris?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}
}],
stream=True
)
function_args = ""
for chunk in stream:
delta = chunk.choices[0].delta
if delta.tool_calls:
args = delta.tool_calls[0].function.arguments or ""
function_args += args
if chunk.choices[0].finish_reason == "tool_calls":
import json
parsed = json.loads(function_args)
print(f"Calling: {parsed}")Handle Stream Completion
Detect when the stream finishes and why:
for await (const chunk of stream) {
const choice = chunk.choices[0]
if (choice.delta.content) {
process.stdout.write(choice.delta.content)
}
if (choice.finish_reason === 'stop') {
console.log('\n✓ Stream complete')
} else if (choice.finish_reason === 'length') {
console.log('\n⚠ Max tokens reached')
} else if (choice.finish_reason === 'tool_calls') {
console.log('\n🔧 Function called')
}
}for chunk in stream:
choice = chunk.choices[0]
if choice.delta.content:
print(choice.delta.content, end="", flush=True)
if choice.finish_reason == "stop":
print("\n✓ Stream complete")
elif choice.finish_reason == "length":
print("\n⚠ Max tokens reached")
elif choice.finish_reason == "tool_calls":
print("\n🔧 Function called")Finish Reasons
Error Handling
Handle stream errors gracefully:
try {
const stream = await client.chat.completions.create({
model: 'anthropic/claude-3.5-sonnet',
messages: [{ role: 'user', content: 'Hello' }],
stream: true
})
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || '')
}
} catch (error) {
if (error.code === 'ECONNABORTED') {
console.error('Stream timeout')
} else if (error.status === 429) {
console.error('Insufficient credits')
} else if (error.status === 402) {
console.error('Spending limit exceeded')
} else {
console.error('Stream error:', error.message)
}
}try:
stream = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[{"role": "user", "content": "Hello"}],
stream=True
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")
except Exception as error:
if hasattr(error, 'status_code'):
if error.status_code == 429:
print("Insufficient credits")
elif error.status_code == 402:
print("Spending limit exceeded")
else:
print(f"API error: {error}")
else:
print(f"Stream error: {error}")Best Practices
Implement Timeout
Prevent hanging connections:
async function streamWithTimeout(prompt, timeoutMs = 30000) {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), timeoutMs)
try {
const stream = await client.chat.completions.create({
model: 'anthropic/claude-3.5-sonnet',
messages: [{ role: 'user', content: prompt }],
stream: true
}, { signal: controller.signal })
for await (const chunk of stream) {
// Process chunk
}
} finally {
clearTimeout(timeout)
}
}Buffer UI Updates
Batch updates for smoother rendering:
let buffer = ''
const flushInterval = 50 // ms
const interval = setInterval(() => {
if (buffer) {
setResponse(prev => prev + buffer)
buffer = ''
}
}, flushInterval)
for await (const chunk of stream) {
buffer += chunk.choices[0]?.delta?.content || ''
}
clearInterval(interval)Use Smaller Models
For faster time-to-first-token: