From bec48dca72f23cf0e4d0901083cf6cc5f72edd1f Mon Sep 17 00:00:00 2001 From: z2_cc <170238968@qq.com> Date: Wed, 13 May 2026 11:38:08 +0800 Subject: [PATCH] feat: add DeepSeek Chat API streaming wrapper --- api/llm/deepseek.js | 87 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 api/llm/deepseek.js diff --git a/api/llm/deepseek.js b/api/llm/deepseek.js new file mode 100644 index 0000000..12e38ca --- /dev/null +++ b/api/llm/deepseek.js @@ -0,0 +1,87 @@ +// api/llm/deepseek.js +const https = require('https'); +const http = require('http'); + +function createStream(messages, options = {}) { + const apiKey = process.env.DEEPSEEK_API_KEY; + const baseUrl = process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com'; + const model = process.env.DEEPSEEK_CHAT_MODEL || 'deepseek-chat'; + + if (!apiKey) { + throw new Error('DEEPSEEK_API_KEY environment variable is required'); + } + + const body = JSON.stringify({ + model, + messages, + stream: true, + temperature: options.temperature || 0.7, + max_tokens: options.maxTokens || 2048, + }); + + const url = new URL('/chat/completions', baseUrl); + const transport = url.protocol === 'https:' ? https : http; + + const reqOptions = { + hostname: url.hostname, + port: url.port || (url.protocol === 'https:' ? 443 : 80), + path: url.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + 'Content-Length': Buffer.byteLength(body), + }, + }; + + return { reqOptions, transport, body }; +} + +/** + * Stream DeepSeek chat completions as an async iterator of content strings. + * @param {Array<{role: string, content: string}>} messages + * @param {object} options + * @yields {string} content delta + */ +async function* streamChat(messages, options = {}) { + const { reqOptions, transport, body } = createStream(messages, options); + + const res = await new Promise((resolve, reject) => { + const req = transport.request(reqOptions, resolve); + req.on('error', reject); + req.write(body); + req.end(); + }); + + if (res.statusCode !== 200) { + const errBody = await new Promise(r => { + let data = ''; + res.on('data', c => data += c); + res.on('end', () => r(data)); + }); + throw new Error(`DeepSeek API error ${res.statusCode}: ${errBody}`); + } + + let buffer = ''; + for await (const chunk of res) { + buffer += chunk.toString(); + const lines = buffer.split('\n'); + buffer = lines.pop(); + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || !trimmed.startsWith('data: ')) continue; + const data = trimmed.slice(6); + if (data === '[DONE]') return; + try { + const parsed = JSON.parse(data); + const content = parsed.choices?.[0]?.delta?.content; + if (content) yield content; + } catch { + // skip malformed lines + } + } + } +} + +module.exports = { streamChat };