Node.jsJavaScriptNbilityOpenAI-compatibleExpressStreamingCLI

Node.js 调用 Nbility:从 CLI 小工具到网页应用

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

Node.js 调用 Nbility:从 CLI 小工具到网页应用

Node.js 调用 Nbility 封面

如果你已经会用 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 小工具场景图

这篇会做三件事:

  1. 一个最小 Node.js 聊天脚本;
  2. 一个可在终端里调用的 CLI 小工具;
  3. 一个 Express 网页接口,并演示如何把模型流式输出转给浏览器。

Nbility 的 Chat Completions 文档说明:接口为 POST /v1/chat/completions,请求体包含 modelmessagesstream: 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。

网页应用里的流式输出

Node.js 网页应用流式输出场景图

普通 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 写入日志
[ ] 流式输出经过真实浏览器和线上网关验证

参考链接

相关文章

Python 调用 Nbility:聊天、流式输出、错误处理
PythonNbilityOpenAI-compatible

Python 调用 Nbility:聊天、流式输出、错误处理

面向 Python 开发者的 Nbility OpenAI-compatible API 入门实战:环境变量、openai-python 客户端、Chat Completions、流式输出、超时重试、错误分类、日志与成本控制。

给小红书/推文生成配图工作流
小红书XTwitter

给小红书/推文生成配图工作流

从标题、卖点和平台尺寸出发,用 AI 生成小红书首图、轮播卡片和 X/Twitter 推文配图,并用脚本控制安全区、比例、导出和审核。

用 Nbility 跑通你的 Agent 工作流

获取 API Key,统一接入 OpenAI 兼容模型和开发工具。

管理 API Key