const express = require("express"); const router = express.Router(); const VOLCENGINE_API_KEY = process.env.VOLCENGINE_API_KEY; const VOLCENGINE_MODEL = process.env.VOLCENGINE_MODEL || "your_endpoint_id"; const VOLCENGINE_BASE_URL = process.env.VOLCENGINE_BASE_URL || "https://ark.cn-beijing.volces.com/api/v3"; router.post("/chat", async (req, res) => { try { if (!VOLCENGINE_API_KEY) { return res.status(500).json({ error: "缺少 VOLCENGINE_API_KEY,请先在 .env 中配置", }); } const { messages, model, temperature } = req.body || {}; if (!Array.isArray(messages) || messages.length === 0) { return res.status(400).json({ error: "请求体 messages 必须是非空数组", }); } const response = await fetch(`${VOLCENGINE_BASE_URL}/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${VOLCENGINE_API_KEY}`, }, body: JSON.stringify({ model: model || VOLCENGINE_MODEL, messages, temperature: typeof temperature === "number" ? temperature : 0.7, }), }); const data = await response.json(); if (!response.ok) { return res.status(response.status).json({ error: "调用火山引擎 Chat 接口失败", detail: data, }); } return res.json({ id: data.id, model: data.model, content: data?.choices?.[0]?.message?.content || "", raw: data, }); } catch (error) { return res.status(500).json({ error: "服务内部错误", detail: error.message, }); } }); router.post("/chat/stream", async (req, res) => { try { if (!VOLCENGINE_API_KEY) { return res.status(500).json({ error: "缺少 VOLCENGINE_API_KEY,请先在 .env 中配置", }); } const { messages, model, temperature } = req.body || {}; if (!Array.isArray(messages) || messages.length === 0) { return res.status(400).json({ error: "请求体 messages 必须是非空数组", }); } res.setHeader("Content-Type", "text/event-stream; charset=utf-8"); res.setHeader("Cache-Control", "no-cache, no-transform"); res.setHeader("Connection", "keep-alive"); res.flushHeaders(); const controller = new AbortController(); req.on("close", () => { controller.abort(); }); const response = await fetch(`${VOLCENGINE_BASE_URL}/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${VOLCENGINE_API_KEY}`, }, body: JSON.stringify({ model: model || VOLCENGINE_MODEL, messages, temperature: typeof temperature === "number" ? temperature : 0.7, stream: true, }), signal: controller.signal, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); res.write( `data: ${JSON.stringify({ error: "调用火山引擎流式接口失败", detail: errorData, })}\n\n`, ); return res.end(); } if (!response.body) { res.write(`data: ${JSON.stringify({ error: "未获取到流式响应体" })}\n\n`); return res.end(); } const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { const trimmed = line.trim(); if (!trimmed.startsWith("data:")) continue; const payload = trimmed.slice(5).trim(); if (payload === "[DONE]") { res.write("data: [DONE]\n\n"); res.end(); return; } try { const parsed = JSON.parse(payload); const delta = parsed?.choices?.[0]?.delta?.content; if (delta) { res.write(`data: ${JSON.stringify({ delta })}\n\n`); } } catch (_error) { // 忽略非 JSON 片段,继续解析后续流内容 } } } res.write("data: [DONE]\n\n"); return res.end(); } catch (error) { if (!res.headersSent) { return res.status(500).json({ error: "服务内部错误", detail: error.message, }); } res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`); return res.end(); } }); module.exports = router;