Stream in Node.js
Node.js 18 and later ship fetch with streaming response bodies, so no library is needed. The second tab uses the openai package pointed at Syntic.
// stream.mjs (node stream.mjs)
const res = await fetch('https://app.syntic.ai/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SYNTIC_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'Amara S1.2',
messages: [{ role: 'user', content: 'Describe a lighthouse at night in four sentences.' }],
}),
})
if (!res.ok) {
console.error(res.status, await res.text())
process.exit(1)
}
console.error('request id:', res.headers.get('x-syntic-request-id'))
const decoder = new TextDecoder()
let buffer = ''
let usage = null
let done = false
for await (const bytes of res.body) {
buffer += decoder.decode(bytes, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() // keep a partial line for the next chunk
for (const line of lines) {
if (!line.startsWith('data: ')) continue
const payload = line.slice(6)
if (payload === '[DONE]') { done = true; continue }
const event = JSON.parse(payload)
if (event.error) throw new Error(`stream failed: ${event.detail}`)
const piece = event.choices?.[0]?.delta?.content
if (piece) process.stdout.write(piece)
if (event.usage) usage = event.usage
}
}
process.stdout.write('\n')
if (!done) console.error('[stream ended early; reply is incomplete]')
console.error('usage:', usage)Forward the stream to a browser
In a server route, pass text through as it arrives instead of collecting it first. With Express:
app.post('/api/ask', async (req, res) => {
const upstream = await fetch('https://app.syntic.ai/v1/chat/completions', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.SYNTIC_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'Amara Quick', messages: [{ role: 'user', content: String(req.body.q) }] }),
})
if (!upstream.ok) return res.status(502).json({ error: 'upstream', status: upstream.status })
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
for await (const bytes of upstream.body) res.write(bytes) // events pass through unchanged
res.end()
})The browser can then read /api/ask with the same parsing loop as the fetch tab. The API key stays on the server.
Notes
- The
bufferlogic matters: a network chunk can end in the middle of a line, and parsing half an event throws. - Check
res.okbefore reading. Errors before the stream starts are ordinary JSON. usagearrives near the end of the stream; keep the last value.
Related
Was this page helpful?