天工Skywork是昆仑万维自主研发的大语言模型系列,凭借出色的中文语义理解、代码生成和推理能力,已经成为国内开发者常用的模型选择之一。天工官方提供了兼容OpenAI风格的HTTP接口,这意味着任何能够发送HTTP请求的编程语言都可以直接调用它,Node.js自然也不例外。本文将完整讲解如何在Node.js环境下接入天工Skywork模型,从最基础的接口调用,到流式输出、会话管理,再到封装可复用的服务模块,一步一步带你落地。

一、准备工作:获取API Key并搭建环境
调用天工模型的第一步是前往天工开放平台注册账号并创建API Key。创建完成后会得到一个以sk-开头的密钥字符串,这个密钥用于请求头中的身份验证,请妥善保管,切勿硬编码到代码仓库中。
天工模型的接口地址与OpenAI兼容,基础端点为https://api.tyqw.cloud.cn/open/compatible-mode/v1(以官方文档最新地址为准)。请求方式为POST,路径为/chat/completions,请求体采用JSON格式。由于Node.js 18以上版本原生内置了fetch,本文示例全部使用原生方式实现,无需引入任何第三方依赖。
建议在项目根目录创建.env文件存放密钥,并通过process.env读取,这样即使代码上传到Git仓库也不会泄露密钥。如果你的Node.js版本低于18,可以先升级,或者使用axios、node-fetch等库替代,调用逻辑完全一致。
二、基础调用:完成第一次对话请求
先从最简单的非流式调用开始。下面的代码演示了如何向天工模型发送一条用户消息并获取回复,请求头中需要设置Authorization和Content-Type两个字段。
const API_KEY = process.env.SKYWORK_API_KEY;
const BASE_URL = 'https://api.tyqw.cloud.cn/open/compatible-mode/v1';
async function chat(prompt) {
const response = await fetch(`${BASE_URL}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'skywork-plus',
messages: [
{ role: 'user', content: prompt }
],
temperature: 0.7,
max_tokens: 1024
})
});
if (!response.ok) {
throw new Error(`请求失败: ${response.status} ${await response.text()}`);
}
const data = await response.json();
return data.choices[0].message.content;
}
chat('用一句话解释什么是事件循环').then(console.log);返回的JSON中,choices数组的第一项包含完整的回复内容,usage字段记录了本次消耗的token数量。参数方面,model可以按需选择skywork系列的不同版本,temperature控制输出随机性,数值越低回答越确定,max_tokens限制回复长度上限。
需要特别注意错误处理。网络波动、密钥失效、请求频率超限都会导致非200状态码返回,上面的代码通过response.ok做了统一拦截,实际项目中建议进一步区分401(密钥错误)、429(限流)和500(服务端异常),分别采取不同的重试策略。
三、流式输出:实现打字机效果的对话体验
非流式调用需要等待模型生成完整回复后才返回,用户等待时间较长。将stream参数设为true后,接口会以SSE(Server-Sent Events)格式逐块推送内容,前端可以实现打字机效果,体验明显更好。
Node.js中处理SSE流需要读取response.body这个ReadableStream。下面的代码用异步迭代的方式逐行解析数据,每收到一个data:块就取出增量文本并打印:
async function chatStream(prompt) {
const response = await fetch(`${BASE_URL}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'skywork-plus',
messages: [{ role: 'user', content: prompt }],
stream: true
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let fullText = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
// SSE数据以行为单位,按行拆分解析
for (const line of chunk.split('\n')) {
if (!line.startsWith('data:')) continue;
const payload = line.slice(5).trim();
if (payload === '[DONE]') continue;
try {
const json = JSON.parse(payload);
const delta = json.choices?.[0]?.delta?.content || '';
fullText += delta;
process.stdout.write(delta);
} catch (e) {
// 忽略不完整的JSON片段
}
}
}
return fullText;
}
chatStream('写一首关于程序员的短诗');这里有两个容易被忽略的细节。第一,网络传输的分块边界不一定刚好对齐SSE行边界,一个chunk可能包含半行数据,严格来说需要维护缓冲区拼接不完整的行;第二,流结束时服务端会发送data: [DONE]标记,解析时要跳过它。处理好这两点,流式调用就非常稳定了。
四、封装可复用的服务模块
直接在业务代码里写fetch调用会导致逻辑分散、难以维护。推荐封装一个独立的Skywork客户端类,统一处理鉴权、超时、重试和会话历史,业务层只需关心消息本身。
class SkyworkClient {
constructor(apiKey, model = 'skywork-plus') {
this.apiKey = apiKey;
this.model = model;
this.baseUrl = 'https://api.tyqw.cloud.cn/open/compatible-mode/v1';
}
// 带重试的请求封装
async request(body, retries = 3) {
for (let i = 0; i < retries; i++) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 60000);
try {
const res = await fetch(`${this.baseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(body),
signal: controller.signal
});
clearTimeout(timer);
if (res.status === 429) {
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
continue; // 限流时退避重试
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
clearTimeout(timer);
if (i === retries - 1) throw err;
}
}
}
// 多轮对话:外部维护messages数组即可
async chat(messages, options = {}) {
const data = await this.request({
model: this.model,
messages,
temperature: options.temperature ?? 0.7,
max_tokens: options.maxTokens ?? 1024
});
return data.choices[0].message.content;
}
}
// 使用示例:维护对话历史实现多轮上下文
const client = new SkyworkClient(process.env.SKYWORK_API_KEY);
const history = [{ role: 'system', content: '你是一位资深Node.js工程师' }];
history.push({ role: 'user', content: '什么是中间件模式' });
const reply = await client.chat(history);
history.push({ role: 'assistant', content: reply });多轮对话的关键在于把完整的messages数组每次都传给接口,模型本身是无状态的,上下文记忆完全由客户端维护。同时要注意历史消息会持续消耗token,当对话轮次较多时,可以做一个简单的滑动窗口,只保留最近若干轮,或者对早期内容做摘要压缩,控制成本。
五、结合Express构建对话接口
最后给出一个把天工能力暴露成HTTP服务的例子。前端请求这个接口时,服务端把流式响应原样转发,用户在浏览器中就能看到逐字输出的效果:
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/chat', async (req, res) => {
const { prompt } = req.body;
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
const upstream = await fetch(`${BASE_URL}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'skywork-plus',
messages: [{ role: 'user', content: prompt }],
stream: true
})
});
const reader = upstream.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
res.write(decoder.decode(value, { stream: true }));
}
res.end();
});
app.listen(3000, () => console.log('服务已启动在3000端口'));这种代理转发的写法还有额外好处:密钥保存在服务端,前端不接触敏感信息;同时可以在转发前做内容审核、限流、日志记录等统一处理。生产环境建议再加上请求体校验和并发控制,避免恶意刷接口造成费用损耗。
总的来说,Node.js接入天工Skywork模型的门槛并不高,核心就是组织好请求体、处理好SSE流和管理好对话历史。把这三块封装成独立模块后,无论是做智能客服、内容生成还是代码助手,都可以直接复用,快速搭建出稳定的AI能力层。