feat: 新增火山引擎 chat 接口demo

This commit is contained in:
2026-04-15 17:38:01 +08:00
parent 2c1cd85e77
commit 8631a4c9a5
4 changed files with 212 additions and 1 deletions

View File

@@ -6,3 +6,7 @@ QWEN_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
DEEPSEEK_API_KEY=your_deepseek_api_key DEEPSEEK_API_KEY=your_deepseek_api_key
DEEPSEEK_MODEL=deepseek-chat DEEPSEEK_MODEL=deepseek-chat
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
VOLCENGINE_API_KEY=your_volcengine_api_key
VOLCENGINE_MODEL=your_endpoint_id
VOLCENGINE_BASE_URL=https://ark.cn-beijing.volces.com/api/v3

View File

@@ -16,6 +16,7 @@ cp .env.example .env
`.env` 中填入你的 `QWEN_API_KEY`DashScope API Key `.env` 中填入你的 `QWEN_API_KEY`DashScope API Key
如需调用 DeepSeek再填入 `DEEPSEEK_API_KEY` 如需调用 DeepSeek再填入 `DEEPSEEK_API_KEY`
如需调用火山引擎,再填入 `VOLCENGINE_API_KEY``VOLCENGINE_MODEL`(方舟 Endpoint ID
## 3. 启动服务 ## 3. 启动服务
@@ -134,6 +135,44 @@ curl -N -X POST http://localhost:3000/api/deepseek/chat/stream \
└── platforms └── platforms
├── deepseek ├── deepseek
│ └── router.js │ └── router.js
── qwen ── qwen
│ └── router.js
└── volcengine
└── router.js └── router.js
``` ```
## 8. 调用火山引擎 Chat 接口 demo
### 火山普通接口地址
`POST /api/volcengine/chat`
### 火山普通请求示例
```bash
curl -X POST http://localhost:3000/api/volcengine/chat \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role":"system","content":"你是一个简洁的助手"},
{"role":"user","content":"你好,请介绍一下你自己"}
]
}'
```
### 火山流式接口地址
`POST /api/volcengine/chat/stream`
### 火山流式请求示例
```bash
curl -N -X POST http://localhost:3000/api/volcengine/chat/stream \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role":"system","content":"你是一个简洁的助手"},
{"role":"user","content":"请用三句话介绍流式输出"}
]
}'
```

View File

@@ -2,6 +2,7 @@ require("dotenv").config();
const express = require("express"); const express = require("express");
const qwenRouter = require("./platforms/qwen/router"); const qwenRouter = require("./platforms/qwen/router");
const deepseekRouter = require("./platforms/deepseek/router"); const deepseekRouter = require("./platforms/deepseek/router");
const volcengineRouter = require("./platforms/volcengine/router");
const app = express(); const app = express();
app.use(express.json()); app.use(express.json());
@@ -14,6 +15,7 @@ app.get("/health", (_req, res) => {
app.use("/api/qwen", qwenRouter); app.use("/api/qwen", qwenRouter);
app.use("/api/deepseek", deepseekRouter); app.use("/api/deepseek", deepseekRouter);
app.use("/api/volcengine", volcengineRouter);
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`); console.log(`Server running at http://localhost:${PORT}`);

View File

@@ -0,0 +1,166 @@
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;