Node.js 调用 Nbility:从 CLI 小工具到网页应用
面向 Node.js / JavaScript 开发者的 Nbility OpenAI-compatible API 接入教程:环境变量、openai npm SDK、CLI 小工具、Express 网页接口、流式输出、错误处理、日志与上线检查。


如果你已经会用 Python 调 AI API,下一步很自然就是把能力接进 Node.js:写一个命令行小工具、给内部系统加一个总结按钮、做一个网页聊天框,或者把 AI 接到现有的 Express / Next.js 后端里。
这篇文章用 Node.js 演示从 0 到 1 接入 Nbility 的 OpenAI-compatible Chat Completions API。代码使用官方 openai npm 包,通过 baseURL: "https://api.nbility.dev/v1" 指向 Nbility。这样业务代码仍然保持 OpenAI-compatible 写法,模型、Key、用量和成本可以在统一入口里管理。
你会完成什么

这篇会做三件事:
- 一个最小 Node.js 聊天脚本;
- 一个可在终端里调用的 CLI 小工具;
- 一个 Express 网页接口,并演示如何把模型流式输出转给浏览器。
Nbility 的 Chat Completions 文档说明:接口为 POST /v1/chat/completions,请求体包含 model、messages,stream: true 时返回 SSE 流。OpenAI 官方 Node SDK 支持 Chat Completions,也支持 Server-Sent Events streaming,所以这套写法可以直接用于 JavaScript / TypeScript 项目。
准备项目
先创建一个干净目录:
mkdir nodejs-nbility-demo
cd nodejs-nbility-demo
npm init -y
npm install openai dotenv express
把项目切到 ESM 模式,方便使用 import:
npm pkg set type=module
创建 .env:
NBILITY_API_KEY=[REDACTED]
NBILITY_BASE_URL=https://api.nbility.dev/v1
NBILITY_MODEL=gpt-4o
PORT=3000
注意:真实 API Key 只放在服务器环境变量、Secret 管理器或本地 .env。不要放进前端代码、Git 仓库、截图或浏览器 LocalStorage。
最小聊天脚本
新建 chat-once.js:
import 'dotenv/config';
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: process.env.NBILITY_API_KEY,
baseURL: process.env.NBILITY_BASE_URL ?? 'https://api.nbility.dev/v1',
timeout: 60_000,
maxRetries: 2,
});
const completion = await client.chat.completions.create({
model: process.env.NBILITY_MODEL ?? 'gpt-4o',
messages: [
{ role: 'system', content: '你是一个简洁、可靠的技术助手。' },
{ role: 'user', content: '用三句话解释 Node.js 为什么适合做 API 后端。' },
],
temperature: 0.3,
max_tokens: 300,
});
console.log(completion.choices[0]?.message?.content ?? '');
console.log('usage:', completion.usage ?? null);
运行:
node chat-once.js
如果能看到回答,说明四件事已经跑通:Node 环境、SDK、Base URL、API Key / 模型名。
做成 CLI 小工具
很多团队一开始不需要完整网页,只需要一个能在终端里调用的小工具:输入一个问题,返回一个答案。新建 ask.js:
#!/usr/bin/env node
import 'dotenv/config';
import OpenAI from 'openai';
const prompt = process.argv.slice(2).join(' ').trim();
if (!prompt) {
console.error('Usage: node ask.js "你的问题"');
process.exit(1);
}
const client = new OpenAI({
apiKey: process.env.NBILITY_API_KEY,
baseURL: process.env.NBILITY_BASE_URL ?? 'https://api.nbility.dev/v1',
timeout: 60_000,
maxRetries: 2,
});
try {
const completion = await client.chat.completions.create({
model: process.env.NBILITY_MODEL ?? 'gpt-4o',
messages: [
{ role: 'system', content: '你是一个简洁、可靠的中文技术助手。' },
{ role: 'user', content: prompt },
],
temperature: 0.2,
max_tokens: 800,
});
console.log(completion.choices[0]?.message?.content ?? '');
} catch (error) {
console.error(formatOpenAIError(error));
process.exit(1);
}
function formatOpenAIError(error) {
const status = error?.status;
const message = error?.message ?? String(error);
if (status === 401 || status === 403) {
return '认证失败:检查 NBILITY_API_KEY、Bearer 权限、账号余额或模型权限。';
}
if (status === 400 || status === 404) {
return `请求参数可能有误:检查模型名、messages、max_tokens。详情:${message}`;
}
if (status === 429) {
return '请求太频繁或额度压力过高:请稍后重试,或给任务加队列。';
}
if (status >= 500) {
return '上游服务临时异常:可以稍后重试,或临时切换模型。';
}
return `请求失败:${message}`;
}
运行:
node ask.js "把这段发布说明改得更简洁:我们今天修复了登录和账单问题。"
如果要全局使用,可以在 package.json 里加:
{
"bin": {
"ask-nbility": "./ask.js"
}
}
然后本地链接:
chmod +x ask.js
npm link
ask-nbility "给我 5 个客服 FAQ 机器人测试问题"
CLI 适合做内部工具:生成 commit message、总结日志、改写客服话术、把长文压缩成摘要。先从 CLI 做起,通常比一上来写完整 Web UI 更快验证价值。
接到 Express 网页接口
当 CLI 稳定后,就可以把同样的逻辑接进网页应用。新建 server.js:
import 'dotenv/config';
import express from 'express';
import OpenAI from 'openai';
const app = express();
app.use(express.json({ limit: '1mb' }));
const client = new OpenAI({
apiKey: process.env.NBILITY_API_KEY,
baseURL: process.env.NBILITY_BASE_URL ?? 'https://api.nbility.dev/v1',
timeout: 60_000,
maxRetries: 2,
});
app.post('/api/chat', async (req, res) => {
const prompt = String(req.body?.prompt ?? '').trim();
if (!prompt) {
return res.status(400).json({ error: 'prompt is required' });
}
try {
const completion = await client.chat.completions.create({
model: process.env.NBILITY_MODEL ?? 'gpt-4o',
messages: [
{ role: 'system', content: '你是一个可靠的产品助手。' },
{ role: 'user', content: prompt },
],
temperature: 0.3,
max_tokens: 800,
});
res.json({
answer: completion.choices[0]?.message?.content ?? '',
usage: completion.usage ?? null,
});
} catch (error) {
const status = error?.status ?? 500;
res.status(status >= 400 && status < 600 ? status : 500).json({
error: 'chat_request_failed',
message: safeErrorMessage(error),
});
}
});
app.listen(Number(process.env.PORT ?? 3000), () => {
console.log(`server listening on http://localhost:${process.env.PORT ?? 3000}`);
});
function safeErrorMessage(error) {
const status = error?.status;
if (status === 401 || status === 403) return 'authentication failed';
if (status === 429) return 'rate limited';
if (status >= 500) return 'temporary upstream error';
return error?.message ?? 'unknown error';
}
启动:
node server.js
测试:
curl -s http://localhost:3000/api/chat \
-H 'Content-Type: application/json' \
-d '{"prompt":"帮我写一个产品更新公告,100 字以内"}'
这里要注意一个安全边界:浏览器前端不能直接持有 API Key。前端应该请求你的后端 /api/chat,由后端读取环境变量并调用 Nbility。
网页应用里的流式输出

普通 JSON 接口要等完整回答生成后才返回。聊天 UI 更适合流式输出:后端边收到模型 token,边把内容推给浏览器。
在 Express 里新增 /api/chat-stream:
app.post('/api/chat-stream', async (req, res) => {
const prompt = String(req.body?.prompt ?? '').trim();
if (!prompt) {
return res.status(400).json({ error: 'prompt is required' });
}
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?.();
try {
const stream = await client.chat.completions.create({
model: process.env.NBILITY_MODEL ?? 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
stream: true,
temperature: 0.3,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) {
res.write(`data: ${JSON.stringify({ delta })}\n\n`);
}
}
res.write('event: done\ndata: {}\n\n');
res.end();
} catch (error) {
res.write(`event: error\ndata: ${JSON.stringify({ message: safeErrorMessage(error) })}\n\n`);
res.end();
}
});
浏览器端如果用 EventSource,默认只能发 GET;而上面的接口是 POST,更适合用 fetch 读取 ReadableStream。最小示例:
async function askStream(prompt, onDelta) {
const response = await fetch('/api/chat-stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const events = buffer.split('\n\n');
buffer = events.pop() ?? '';
for (const event of events) {
const line = event.split('\n').find((x) => x.startsWith('data: '));
if (!line) continue;
const payload = JSON.parse(line.slice(6));
if (payload.delta) onDelta(payload.delta);
}
}
}
MDN 的 SSE 文档提醒:SSE 是服务端到浏览器的单向连接;如果不是 HTTP/2,同域连接数量可能受浏览器限制。实际生产里,聊天流式输出要注意网关不要缓冲响应,比如 Nginx、CDN、Serverless 平台都可能影响“边生成边显示”。
生产环境要补的三件事
1. 输入限制
不要把用户输入原样无限制转给模型。至少加:
prompt 最大长度
单用户频率限制
单 IP 频率限制
敏感业务操作二次确认
2. 日志和用量
如果返回里有 usage,记录下来:
user_id
route
model
prompt_tokens
completion_tokens
total_tokens
latency_ms
status
error_type
后面一旦有人问“为什么这个月 token 变贵了”,你才有数据可查。
3. 错误分类
不要所有错误都返回“系统繁忙”。建议分类:
400 / 404:参数、模型名、messages 格式问题,不要重试
401 / 403:Key、权限、余额问题,不要重试
429:限流或并发压力,排队或指数退避
5xx / timeout:临时错误,有限重试或切换模型
Nbility 放在中间层的好处是:你的 Node.js 代码只关心 OpenAI-compatible 调用形状,模型切换、账单观察和多模型入口可以集中处理。对小团队来说,这比在代码里硬接多个供应商更容易维护。
上线检查清单
[ ] API Key 只存在后端环境变量或 Secret 中
[ ] 前端永远不直接调用模型 API
[ ] baseURL 使用 https://api.nbility.dev/v1
[ ] 模型名、端口、超时都可通过环境变量配置
[ ] Express JSON body 设置合理大小限制
[ ] CLI 和 Web 接口都有错误处理
[ ] 429 / 5xx 有有限重试或队列退避
[ ] usage、latency、user_id、route 写入日志
[ ] 流式输出经过真实浏览器和线上网关验证
参考链接
- Nbility API overview: https://nbility.dev/docs/api
- Nbility Chat Completions API: https://nbility.dev/docs/api/chat/completions
- OpenAI Node SDK GitHub: https://github.com/openai/openai-node
- openai npm package: https://www.npmjs.com/package/openai
- OpenAI Chat Completions API reference: https://platform.openai.com/docs/api-reference/chat/create
- Node.js
process.env: https://nodejs.org/api/process.html#processenv - Express 4.x API reference: https://expressjs.com/en/4x/api.html
- MDN Server-Sent Events guide: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events

