第一章:环境搭建与基础调用
1.1 初始化项目并安装依赖
npm init -y
npm install openai express dotenv cors morgan express-rate-limit
openai 为官方 SDK(v4+),express 构建 API,其余是生产级中间件。
1.2 环境变量配置
创建 .env 文件:
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
MODEL_NAME=gpt-4o-mini
⚠️ .env 禁止提交到 Git,建议提供 .env.example 模板。生产环境使用密钥管理服务。
1.3 最小可用示例
import OpenAI from 'openai';
import 'dotenv/config';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function chat() {
const completion = await openai.chat.completions.create({
model: process.env.MODEL_NAME,
messages: [{ role: 'user', content: '你好,请用一句话介绍你自己。' }],
});
console.log(completion.choices[0].message.content);
}
chat();
运行 node src/index.js,成功输出回答即表示调用成功。
第二章:构建核心 API 服务
将 GPT 封装为 RESTful API,便于前端或其他服务调用。
2.1 Express 路由与请求校验
import { Router } from 'express';
import { chatCompletion } from '../services/openaiService.js';
import { validateBody } from '../middleware/validation.js';
const router = Router();
router.post('/chat', validateBody, async (req, res, next) => {
try {
const result = await chatCompletion(req.body.messages);
res.json({ success: true, data: result });
} catch (err) {
next(err);
}
});
export default router;
使用 joi 校验 messages 数组的 role 和 content 字段。
2.2 直接返回 vs 流式返回
直接返回适合短文本;流式返回(SSE)设置 stream: true,逐字推送,适合聊天机器人。
流式实现关键代码:
export async function streamChat(messages, res) {
const stream = await openai.chat.completions.create({
model: process.env.MODEL_NAME, messages, stream: true,
});
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) res.write(`data: ${JSON.stringify({ content })}\n\n`);
}
res.write('data: [DONE]\n\n');
res.end();
}
可在路由上增加 ?stream=true 参数动态切换。
2.3 生产级中间件
import cors from 'cors';
import rateLimit from 'express-rate-limit';
import morgan from 'morgan';
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }));
app.use(rateLimit({ windowMs: 60 * 1000, max: 30 }));
app.use(morgan('combined'));
配置 CORS 白名单、频率限制、访问日志,让服务从“能跑”升级到“能上线”。
第三章:多轮对话与上下文管理
聊天机器人需要维护对话历史。
3.1 消息结构与 Token 压缩
每条消息包含 role(user/assistant/system)和 content,整个会话用数组保存。使用 tiktoken 实现压缩函数,超出最大 token 时移除最早的非 system 消息:
import { getEncoding } from 'tiktoken';
const encoding = getEncoding('cl100k_base');
export function trimConversation(messages, maxTokens = 4096) {
let total = 0;
const reversed = messages.slice().reverse();
const keep = [];
for (const msg of reversed) {
const tokens = encoding.encode(msg.content).length;
if (total + tokens > maxTokens) break;
total += tokens;
keep.unshift(msg);
}
return keep;
}
也可将早期消息摘要后替换,但会额外消耗 token。
3.2 Redis 持久化
使用 sessionId 将会话存入 Redis,支持多实例部署:
import { createClient } from 'redis';
const client = createClient({ url: process.env.REDIS_URL });
await client.connect();
export async function getSession(sessionId) {
const data = await client.get(`chat:${sessionId}`);
return data ? JSON.parse(data) : [];
}
export async function saveSession(sessionId, messages) {
await client.set(`chat:${sessionId}`, JSON.stringify(messages), { EX: 3600 });
}
第四章:函数调用与动态模型选择
让 GPT 执行外部操作(如查数据库、调用第三方 API)。
4.1 业务场景:查询订单状态
定义 functions 参数描述接口,GPT 返回 tool_calls 后由服务端执行并回传结果。
const functions = [
{
name: 'get_order_status',
description: '根据订单ID查询订单状态',
parameters: {
type: 'object',
properties: { orderId: { type: 'string', description: '订单编号' } },
required: ['orderId'],
},
},
];
执行失败时返回错误描述给 GPT,让其调整回答。
4.2 自动化模型选择与 Token 监控
根据请求特征动态选择模型:
export function selectModel(userMessage, ip) {
if (userMessage.length < 50) return 'gpt-4o-mini';
if (userMessage.length > 500) return 'gpt-4o';
return process.env.DEFAULT_MODEL;
}
从响应获取 usage 数据,累计成本并与阈值比较,通过 webhook 通知。
第五章:错误处理与生产部署
5.1 通用错误处理与重试
export function errorHandler(err, req, res, next) {
console.error(err.stack);
if (err.status === 429) return res.status(429).json({ error: '请求过于频繁,请稍后重试' });
if (err.status === 401) return res.status(401).json({ error: 'API Key 无效' });
res.status(500).json({ error: '服务内部错误' });
}
使用 async-retry 实现指数退避,区分可重试与不可重试错误:
import retry from 'async-retry';
export async function callWithRetry(fn, maxRetries = 3) {
return retry(async (bail, attempt) => {
try {
return await fn();
} catch (err) {
if (err.status === 401) bail(err);
throw err;
}
}, { retries: maxRetries, minTimeout: 1000, factor: 2 });
}
5.2 Docker 部署
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "src/index.js"]
配合 docker-compose.yml 携带 Redis。生产建议使用 PM2 管理进程,Nginx 反向代理并关闭缓冲(proxy_buffering off)以支持 SSE 流式传输。
附录:常见问题 FAQ
Q:用官方 SDK 还是手动请求? A:官方 SDK 封装更完善(流式、重试、类型提示),推荐优先使用。
Q:如何估算成本? A:查看模型定价(每千 token),结合响应 usage 数据计算,建议添加日预算告警。
Q:函数调用返回参数格式不对怎么办? A:使用严格 JSON Schema 约束参数,解析失败时让 GPT 重新生成,循环调用限制 3 次内。
Q:流式传输中客户端断开怎么办? A:服务端监听 req.on('close') 主动释放 GPT 流,客户端实现指数退避重连。