1. 项目概述为什么现在要上手OpenAI API如果你是一名JavaScript开发者最近几个月肯定被各种AI应用刷屏了。从能写代码的Copilot到能聊天的ChatGPT背后都离不开大语言模型LLM的驱动。而OpenAI API就是让我们普通开发者也能在自己的应用里接入这种强大能力的最直接入口。它不是一个遥不可及的“黑科技”本质上就是一个功能极其强大的HTTP接口你用熟悉的fetch或者axios就能调用。这个项目标题“Getting Started with OpenAI API in JavaScript”直译过来是“在JavaScript中开始使用OpenAI API”听起来像是一篇入门教程。但我的理解是它真正的价值在于帮你跨过“从知道到做到”的门槛。网上官方文档虽然详尽但面对海量的模型选项、复杂的参数、流式响应和错误处理新手很容易懵。我见过不少朋友卡在第一步密钥怎么配请求体怎么构造返回的庞大数据怎么处理更别提那些影响效果和成本的细节了。所以这篇内容我会带你走完一个完整的、可落地的流程。不仅仅是“Hello World”而是从一个真实的、有前后端交互的Node.js项目角度出发涵盖从账户准备、环境搭建、核心调用、到错误处理、成本控制和实战优化的全过程。我会分享那些官方文档里不会写的“坑”比如为什么你的流式输出不完整、如何设计提示词Prompt才能让模型更“听话”、以及怎么用最少的代码实现一个可用的AI对话功能。无论你是想给自己的博客加个智能摘要还是想开发一个创意写作助手从这里开始都够了。2. 核心概念与准备工作在动手写代码之前我们必须把几个核心概念和准备工作理清楚。这就像盖房子前要打地基和备料跳过这一步后面的代码写得再漂亮也容易出问题。2.1 理解OpenAI API的核心组件OpenAI API不是一个单一的接口而是一系列模型和功能的集合。对于JavaScript开发者我们主要和以下几个核心组件打交道模型Model这是AI的“大脑”。不同的模型能力、价格和速度都不同。最常用的是gpt-3.5-turbo和gpt-4系列它们是为对话优化的模型我们通过发送“消息”数组与它们交互。此外还有专门用于文本嵌入的text-embedding模型用于生成图像的dall-e模型等。选择模型是第一步也直接关系到成本和效果。API密钥API Key这是你的身份凭证和计费凭证。务必像保护密码一样保护它绝不能提交到公开的代码仓库如GitHub。我们通常会把它放在环境变量中。端点Endpoint即不同的API功能地址。最常用的是聊天补全端点https://api.openai.com/v1/chat/completions用于对话还有补全端点、嵌入端点等。提示词Prompt与消息Messages这是我们与模型沟通的方式。对于聊天模型我们构造一个消息数组每条消息有role角色system,user,assistant和content内容。system消息用于设定AI的“人设”和行为指令这是控制输出质量的关键。2.2 环境准备与安全配置我们将在Node.js环境下进行演示。请确保你已安装Node.js建议版本16和npm。第一步获取API密钥访问OpenAI平台网站注册并登录。点击右上角个人头像进入“View API keys”。点击“Create new secret key”生成一个新密钥。生成后立即复制保存因为它只显示一次。第二步初始化项目并安全存储密钥打开终端执行以下命令# 创建一个新项目目录 mkdir openai-js-starter cd openai-js-starter # 初始化npm项目一路回车即可 npm init -y # 安装官方OpenAI Node.js库和dotenv用于管理环境变量 npm install openai dotenv接下来安全地配置你的API密钥。在项目根目录创建两个文件.env和.env.example。.env文件务必加入.gitignore不要提交OPENAI_API_KEY你的_真实_API_密钥_放在这里.env.example文件用于说明项目需要的环境变量可以提交OPENAI_API_KEYsk-...注意.env文件中不要使用引号包裹密钥值直接写sk-开头的一串字符即可。许多新手会写成OPENAI_API_KEYsk-...这反而可能导致解析错误。第三步安装并配置OpenAI官方库OpenAI提供了维护良好的官方Node.js库openai它封装了API调用、错误处理和TypeScript类型比直接用fetch更省心。我们已经在上一步安装了它。创建一个基础配置文件例如src/config.jsimport dotenv from dotenv; import OpenAI from openai; // 加载.env文件中的环境变量 dotenv.config(); // 初始化OpenAI客户端它会自动从process.env.OPENAI_API_KEY读取密钥 const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); // 简单验证密钥是否已加载非必须用于调试 if (!process.env.OPENAI_API_KEY) { console.error(错误未找到OPENAI_API_KEY环境变量。请检查.env文件。); process.exit(1); } export { openai };至此你的项目地基已经打牢。接下来我们就可以开始真正的API调用了。3. 实现基础聊天交互功能现在我们来构建最核心的功能与AI模型进行对话。我们将从最简单的单次问答开始逐步增加复杂度实现多轮对话和流式响应。3.1 你的第一个AI对话请求让我们先实现一个最简单的函数向gpt-3.5-turbo模型发送一条用户消息并获取回复。创建文件src/basicChat.js。import { openai } from ./config.js; async function getSingleChatResponse(userInput) { try { const completion await openai.chat.completions.create({ model: gpt-3.5-turbo, // 指定使用的模型 messages: [ // system消息设定AI的角色。这是可选的但强烈建议提供能极大改善回复质量。 { role: system, content: 你是一个乐于助人的JavaScript编程助手。 }, // user消息是我们的问题 { role: user, content: userInput } ], temperature: 0.7, // 控制输出的随机性0-2。值越高输出越随机、有创意值越低输出越确定、保守。 max_tokens: 150, // 限制生成回复的最大长度约等于单词数。注意这包括输入和输出设置过低可能导致回复被截断。 }); // 返回AI的回复内容 return completion.choices[0].message.content; } catch (error) { // 错误处理我们稍后详细讲这里先简单抛出 console.error(调用OpenAI API时出错:, error); throw error; } } // 调用示例 (async () { const answer await getSingleChatResponse(如何用JavaScript反转一个字符串); console.log(AI回复:, answer); })();运行这个脚本你应该能看到AI返回的关于反转字符串的方法。这里有几个关键点messages数组对话的历史记录。每次请求都需要传递完整的上下文。如果你想要多轮对话就需要在后续请求中把之前所有的user和assistant消息都包含进去。temperature参数这是控制“创意”程度的旋钮。写代码助手通常设为较低值如0.2-0.5以保证答案的准确性写诗歌或故事可以设高如0.8-1.2。max_tokens参数需要根据你输入的长度和期望回复的长度来估算。gpt-3.5-turbo的上下文窗口通常是4096或16385个token约等于单词数你的输入和输出总和不能超过这个限制。3.2 实现多轮对话上下文管理单次问答意义有限真正的聊天需要记住之前说过的话。这意味着我们需要在客户端维护一个“消息历史”数组并在每次请求时将其全部发送。下面是一个简单的上下文管理示例。创建src/chatWithContext.jsimport { openai } from ./config.js; class ChatSession { constructor(systemPrompt 你是一个有用的助手。) { // 初始化消息历史包含system指令 this.messages [{ role: system, content: systemPrompt }]; } async sendMessage(userInput) { // 将用户输入加入历史 this.messages.push({ role: user, content: userInput }); try { const completion await openai.chat.completions.create({ model: gpt-3.5-turbo, messages: this.messages, // 发送全部历史 temperature: 0.7, }); const assistantReply completion.choices[0].message.content; // 将AI回复也加入历史以便后续对话引用 this.messages.push({ role: assistant, content: assistantReply }); return assistantReply; } catch (error) { console.error(对话出错:, error); // 如果出错从历史中移除刚才加入的user消息避免状态不一致 this.messages.pop(); throw error; } } // 可选清空对话历史但保留system指令 clearHistory() { this.messages [this.messages[0]]; } // 获取当前对话轮数不计system消息 getTurnCount() { return this.messages.length - 1; } } // 使用示例 (async () { const chat new ChatSession(你是一个知识渊博的历史学家用中文回答。); console.log(await chat.sendMessage(请简述罗马帝国的衰落原因。)); console.log(--- 对话轮数:, chat.getTurnCount(), ---\n); // AI此时已经记住了上一轮对话 console.log(await chat.sendMessage(它对后世有什么影响)); console.log(--- 对话轮数:, chat.getTurnCount(), ---\n); chat.clearHistory(); console.log(历史已清空新问题:); console.log(await chat.sendMessage(今天天气怎么样)); // AI将不再记得罗马帝国 })();这个ChatSession类封装了上下文管理。但请注意随着对话轮数增加messages数组会越来越大最终可能超过模型的上下文长度限制。在实际生产中你需要实现更智能的上下文窗口管理例如只保留最近N条消息或者对历史消息进行总结压缩。3.3 接入流式响应Streaming提升体验默认的API调用是“阻塞”的你必须等待AI生成全部内容后才能收到回复。对于长文本这会导致用户长时间等待体验很差。流式响应Streaming允许你像接收视频流一样逐字逐句地实时接收AI生成的内容。这在构建聊天界面时几乎是必备功能。下面是使用官方库实现流式响应的方式。创建src/streamingChat.jsimport { openai } from ./config.js; import readline from readline; // Node.js内置模块用于命令行交互 // 创建命令行交互接口 const rl readline.createInterface({ input: process.stdin, output: process.stdout }); function askQuestion(prompt) { return new Promise((resolve) rl.question(prompt, resolve)); } async function streamingChat() { const messages [{ role: system, content: 你是一个幽默的聊天伙伴。 }]; console.log(开始流式聊天 (输入 退出 结束)\n); while (true) { const userInput await askQuestion(\n你: ); if (userInput.toLowerCase() 退出) break; messages.push({ role: user, content: userInput }); console.log(\nAI: ); try { const stream await openai.chat.completions.create({ model: gpt-3.5-turbo, messages: messages, stream: true, // 关键参数开启流式响应 temperature: 0.7, }); let fullResponse ; // 逐块处理流数据 for await (const chunk of stream) { const content chunk.choices[0]?.delta?.content || ; process.stdout.write(content); // 逐字打印到控制台 fullResponse content; } console.log(); // 换行 // 将完整的AI回复加入消息历史 messages.push({ role: assistant, content: fullResponse }); } catch (error) { console.error(\n流式请求出错:, error.message); // 出错时移除刚才添加的user消息 messages.pop(); } } rl.close(); console.log(聊天结束。); } // 启动聊天 streamingChat().catch(console.error);运行这个脚本你会看到AI的回复是一个字一个字“打”出来的就像真人打字一样。这里有一个至关重要的细节流式响应中每个chunk的delta属性包含了增量内容而不是完整的消息。最终你需要自己拼接出完整的回复并手动将其加入messages历史以便后续对话使用。很多新手会忘记这一步导致上下文丢失。实操心得在Web前端如React/Vue中实现流式响应原理类似。你需要使用fetch并处理ReadableStream或者使用官方库的流式支持。核心是将收到的数据块实时更新到UI状态中。注意处理好连接中断、错误重试等边界情况。4. 高级功能与实战技巧掌握了基础对话我们可以探索一些更高级的功能和优化技巧这些能让你的AI应用更强大、更可控、也更经济。4.1 使用Function Calling实现“AI调用工具”这是OpenAI API一个革命性的功能。它允许你定义一些“函数”工具AI在对话中如果认为需要可以“请求”你调用这些函数并等你提供函数执行结果后再继续对话。这相当于赋予了AI使用外部工具的能力。一个经典场景是用户问“北京今天天气怎么样”。AI自己无法获取实时天气但它可以“决定”调用你定义的get_current_weather函数你调用真实天气API拿到数据后把结果返回给AI由AI组织成自然语言回复给用户。下面是一个模拟的示例创建src/functionCalling.jsimport { openai } from ./config.js; // 1. 定义你可以提供的“工具”函数 const tools [ { type: function, function: { name: get_current_weather, description: 获取指定城市的当前天气, parameters: { type: object, properties: { location: { type: string, description: 城市名称例如北京上海, }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位摄氏度或华氏度, }, }, required: [location], }, }, }, ]; // 2. 模拟的工具函数实现 function executeFunctionCall(toolCall) { const functionName toolCall.function.name; const args JSON.parse(toolCall.function.arguments); if (functionName get_current_weather) { // 这里应该调用真实的天气API我们模拟一个结果 return JSON.stringify({ location: args.location, temperature: args.unit celsius ? 22°C : 72°F, condition: 晴朗, forecast: [晴朗, 多云, 微风], }); } return JSON.stringify({ error: 未知函数: ${functionName} }); } async function chatWithFunctionCalling(userQuery) { const messages [ { role: system, content: 你是一个有帮助的助手可以查询天气。如果用户问天气请调用get_current_weather函数。用中文回复。 }, { role: user, content: userQuery }, ]; console.log(用户: ${userQuery}); try { // 第一轮AI判断是否需要调用函数 const response await openai.chat.completions.create({ model: gpt-3.5-turbo, messages: messages, tools: tools, // 关键传入工具定义 tool_choice: auto, // 让AI自动决定是否调用工具 }); const responseMessage response.choices[0].message; messages.push(responseMessage); // 将AI的回复可能包含工具调用请求加入历史 // 3. 检查AI是否想调用工具 const toolCalls responseMessage.tool_calls; if (toolCalls) { console.log(AI决定调用工具: ${toolCalls.map(tc tc.function.name).join(, )}); // 4. 执行每个被请求的工具调用 for (const toolCall of toolCalls) { const functionResponse executeFunctionCall(toolCall); // 5. 将工具执行结果作为一条新消息发送给AI messages.push({ role: tool, tool_call_id: toolCall.id, // 必须与请求的ID对应 content: functionResponse, }); } // 6. 获取AI基于工具结果生成的最终回复 const secondResponse await openai.chat.completions.create({ model: gpt-3.5-turbo, messages: messages, // 此时消息历史包含了系统指令、用户问题、AI的工具调用请求、工具执行结果 }); const finalReply secondResponse.choices[0].message.content; messages.push({ role: assistant, content: finalReply }); console.log(AI (最终回复): ${finalReply}); return finalReply; } else { // AI没有调用工具直接回复 console.log(AI: ${responseMessage.content}); return responseMessage.content; } } catch (error) { console.error(出错:, error); throw error; } } // 测试 (async () { await chatWithFunctionCalling(今天上海天气如何); console.log(\n---\n); await chatWithFunctionCalling(你好吗); })();这个过程看似复杂但逻辑清晰定义工具 → AI请求调用 → 你执行函数 → 返回结果给AI → AI生成最终回复。这为AI应用接入了无限的可能性查数据库、发邮件、操作文件等等。注意事项Function Calling会产生额外的token消耗主要是工具定义的描述部分并且一次对话可能涉及多次API调用请求调用一次返回结果后再生成一次成本和控制逻辑都需要考虑。4.2 优化提示词Prompt Engineering以控制输出模型的表现极大程度上取决于你给它的指令Prompt。糟糕的Prompt得到糟糕的结果。以下是一些经过验证的提示词优化技巧明确角色和任务在system消息中清晰定义。“你是一个JavaScript专家”不如“你是一个专注于ES6及以上版本的JavaScript代码审查助手。你的任务是找出代码中的错误、不规范的写法并提供优化建议。用中文回复。”结构化输出要求AI按特定格式输出便于你后续程序化处理。system: 请将以下文本分类为“正面”、“中性”或“负面”情感。仅输出一个JSON对象格式为{sentiment: 分类结果, confidence: 置信度百分比}。不要输出其他任何文字。 user: 这个产品太棒了我非常喜欢 // 期望输出: {sentiment: 正面, confidence: 95}提供示例Few-Shot Learning在消息中给出一两个输入输出的例子能显著提升模型在特定任务上的表现。messages: [ {role: system, content: 将中文俚语翻译成英文并给出字面翻译和含义解释。}, {role: user, content: 打酱油}, {role: assistant, content: English: None of my business. Literal: Buying soy sauce. Explanation: Used to describe someone who is just a bystander, not involved in the matter.}, {role: user, content: 新问题吃瓜群众} ]分步思考Chain-of-Thought对于复杂问题鼓励AI“一步一步想”。可以在system指令中加入“请逐步推理你的答案”。设置约束明确禁止某些行为。“不要道歉”、“不要使用Markdown格式”、“回答不超过100字”。一个综合性的优质Prompt示例const systemPrompt 你是一个专业的科技文章翻译助手。你的任务是将英文科技新闻翻译成地道的中文。 请遵循以下规则 1. 专业术语保持英文原名并在括号内提供通用中文译名如Transformer (变换器模型)。 2. 长句拆分为符合中文阅读习惯的短句。 3. 保留原文的客观语气不添加个人评论。 4. 如果原文有代码片段保持原样不翻译。 5. 最终输出格式先给出翻译全文然后在“---”后列出本条新闻中出现的3-5个关键专业术语及其解释。 现在请翻译以下内容;不断迭代和测试你的Prompt是提升AI应用质量性价比最高的方式。4.3 成本控制与用量监控OpenAI API按使用量计费主要依据消耗的token数量。Token可以粗略理解为单词的一部分对于英文1个token约等于0.75个单词对于中文1个汉字大约对应1.2到2个token。成本控制策略选择合适的模型gpt-3.5-turbo比gpt-4便宜一个数量级对于许多非高精度要求的任务如客服、文本生成、简单分类完全够用。gpt-4-turbo在能力和成本间取得了更好的平衡。设置max_tokens上限防止意外生成超长文本导致巨额费用。根据场景合理设置。管理上下文长度如前所述历史消息越长消耗的token越多因为每次都要全部发送。实现上下文窗口管理例如只保留最近10轮对话或者当历史超过某个阈值时用AI自动总结之前的对话内容然后用总结替换掉旧消息。使用流式响应虽然不影响总token数但可以提前让用户看到部分内容如果发现AI“胡言乱语”可以及时中断请求通过AbortController避免为无用的后续内容付费。缓存结果对于常见、重复性的问题如产品FAQ可以将AI的回复缓存起来下次用户问相同或类似问题时直接返回缓存无需再次调用API。用量监控实践OpenAI官方库的响应对象中包含了usage字段详细列出了本次请求消耗的token数。const completion await openai.chat.completions.create({/*...*/}); console.log(本次消耗:, completion.usage); // 输出: { prompt_tokens: 25, completion_tokens: 150, total_tokens: 175 }你应该在应用中记录这些数据并设置每日/每月预算警报。OpenAI平台后台也提供了用量仪表盘。对于生产环境强烈建议在调用API的代码层添加一个用量统计和限流中间件。5. 常见问题、错误处理与调试在实际开发中你一定会遇到各种错误和意外情况。这里整理了一份“避坑指南”。5.1 常见错误码与解决方案错误码含义可能原因与解决方案401认证失败API密钥错误、过期或未提供。检查.env文件、环境变量名是否正确密钥是否有效。429请求过多超过速率限制RPM-每分钟请求数TPM-每分钟token数。解决方案1. 降低请求频率2. 实现指数退避重试机制3. 申请提升限额。400错误请求请求参数无效。常见原因messages格式错误、model名称拼写错误、max_tokens值过大超过模型上下文限制。仔细检查请求体。500/503服务器内部错误OpenAI服务器端问题。通常是暂时的等待一段时间后重试。实现重试逻辑时对于5xx错误可以重试。context_length_exceeded上下文超长输入的token数历史消息当前问题超过了模型的最大上下文长度。必须缩短消息历史。5.2 实现健壮的错误处理与重试机制一个生产级的调用应该包含完善的错误处理。下面是一个增强版的调用函数示例import { openai } from ./config.js; async function robustChatCompletion(messages, model gpt-3.5-turbo, maxRetries 3) { let lastError; for (let attempt 1; attempt maxRetries; attempt) { try { const completion await openai.chat.completions.create({ model: model, messages: messages, temperature: 0.7, max_tokens: 500, // 可以设置超时但注意openai库可能依赖axios或fetch的配置 }); return completion; // 成功则直接返回 } catch (error) { lastError error; console.error(第 ${attempt} 次尝试失败:, error.message); // 检查错误类型决定是否重试 if (error.status 429) { // 速率限制等待一段时间再重试指数退避 const delayMs Math.min(1000 * Math.pow(2, attempt), 30000); // 最大等待30秒 console.log(速率限制等待 ${delayMs}ms 后重试...); await new Promise(resolve setTimeout(resolve, delayMs)); continue; // 继续重试循环 } else if (error.status 500) { // 服务器错误短暂等待后重试 console.log(服务器错误等待 2s 后重试...); await new Promise(resolve setTimeout(resolve, 2000)); continue; } else if (error.status 400 error.message.includes(context_length)) { // 上下文过长无法通过重试解决直接抛出 throw new Error(上下文过长请缩短对话历史。原始错误: ${error.message}); } else { // 其他客户端错误401, 404, 400非上下文问题等重试无意义直接抛出 throw error; } } } // 重试多次后仍失败 throw new Error(API调用失败已重试${maxRetries}次。最后错误: ${lastError.message}); } // 使用示例 (async () { try { const response await robustChatCompletion([ { role: user, content: 你好 } ]); console.log(成功:, response.choices[0].message.content); } catch (error) { console.error(最终失败:, error.message); // 这里可以触发告警、降级策略等 } })();5.3 调试技巧与工具记录完整的请求与响应在开发阶段打印出请求的messages和完整的响应对象这是排查问题最直接的方法。注意不要在生产环境记录包含敏感信息的日志。使用OpenAI PlaygroundOpenAI官网提供的Playground是一个绝佳的调试和Prompt设计工具。你可以直接在网页上交互式地测试不同的参数和消息看到实时结果和token消耗然后将配置“复制为代码”直接用到你的项目中。估算Token数量OpenAI提供了tiktoken库用于精确计算token数。你可以提前估算请求是否会超长。对于JavaScript有社区实现的版本如js-tiktoken。监控响应时间API调用可能因为网络或服务器负载而变慢。记录响应时间对于慢请求考虑设置超时并实施降级方案如返回缓存、默认回复。处理流式中断网络不稳定时流式响应可能中断。前端需要监听stream的error事件并给用户友好的提示提供“重试”按钮。6. 从脚本到应用构建一个简单的AI聊天机器人最后我们将前面所有的知识点串联起来构建一个简单的命令行或Web聊天机器人。这里以Node.js命令行版本为例展示一个相对完整的结构。项目结构openai-chatbot/ ├── .env ├── .env.example ├── package.json ├── src/ │ ├── config.js # 初始化配置 │ ├── ChatSession.js # 封装对话会话类 │ ├── utils.js # 工具函数如token估算 │ └── cli.js # 命令行入口 └── README.mdsrc/ChatSession.js(增强版包含上下文管理和基础错误处理)import { openai } from ./config.js; export class EnhancedChatSession { constructor(systemPrompt, model gpt-3.5-turbo, maxHistoryTurns 10) { this.model model; this.maxHistoryTurns maxHistoryTurns * 2; // user和assistant各算一轮 this.messages systemPrompt ? [{ role: system, content: systemPrompt }] : []; } async sendMessage(userInput, streamCallback null) { this.addMessage(user, userInput); const requestPayload { model: this.model, messages: this.messages, temperature: 0.7, max_tokens: 1000, }; try { let fullResponse ; if (streamCallback) { // 流式模式 requestPayload.stream true; const stream await openai.chat.completions.create(requestPayload); for await (const chunk of stream) { const content chunk.choices[0]?.delta?.content || ; streamCallback(content); fullResponse content; } } else { // 非流式模式 const completion await openai.chat.completions.create(requestPayload); fullResponse completion.choices[0].message.content; console.log([Token用量] 输入: ${completion.usage.prompt_tokens}, 输出: ${completion.usage.completion_tokens}); } this.addMessage(assistant, fullResponse); this.maybeTrimContext(); // 如果历史太长进行修剪 return fullResponse; } catch (error) { console.error(发送消息失败:, error.message); // 出错时回滚最后添加的user消息 if (this.messages.length 0 this.messages[this.messages.length - 1].role user) { this.messages.pop(); } throw error; } } addMessage(role, content) { this.messages.push({ role, content }); } maybeTrimContext() { // 简单的修剪策略如果超过限制移除最早的一对user/assistant消息保留system while (this.messages.length this.maxHistoryTurns 1) { // 1 是system消息 // 找到第一个非system消息的索引 const firstNonSystemIndex this.messages.findIndex(msg msg.role ! system); if (firstNonSystemIndex ! -1) { // 移除找到的消息及其后一条假设是成对的这里简化处理移除最早的两条 this.messages.splice(firstNonSystemIndex, 2); } else { break; } } } clearHistory() { const systemMsg this.messages.find(m m.role system); this.messages systemMsg ? [systemMsg] : []; } }src/cli.js(命令行交互入口)import readline from readline; import { EnhancedChatSession } from ./ChatSession.js; const rl readline.createInterface({ input: process.stdin, output: process.stdout }); function ask(prompt) { return new Promise(resolve rl.question(prompt, resolve)); } async function main() { console.log( 简易AI聊天机器人 (输入 /quit 退出, /clear 清空历史, /help 帮助) \n); const systemPrompt await ask(请设定AI的角色和指令 (直接回车使用默认): ) || 你是一个有用且友好的助手。; const useStream (await ask(启用流式响应(y/n, 默认y): )).toLowerCase() ! n; const chat new EnhancedChatSession(systemPrompt); while (true) { const userInput await ask(\n你: ); if (userInput /quit) break; if (userInput /clear) { chat.clearHistory(); console.log(对话历史已清空。); continue; } if (userInput /help) { console.log(命令列表:\n /quit - 退出\n /clear - 清空对话历史\n /help - 显示此帮助); continue; } if (!userInput.trim()) continue; process.stdout.write(AI: ); try { if (useStream) { await chat.sendMessage(userInput, (chunk) process.stdout.write(chunk)); process.stdout.write(\n); } else { const reply await chat.sendMessage(userInput); console.log(reply); } } catch (error) { console.error(\n请求出错: ${error.message}); } } rl.close(); console.log(再见); } main().catch(console.error);这个简单的机器人具备了多轮对话、流式响应、上下文管理、基础命令和错误处理。你可以在此基础上增加更多功能比如将历史对话保存到文件或数据库。支持切换不同的模型和参数。集成Function Calling来实现更复杂的功能。添加一个简单的Web界面使用Express.js SSE或WebSocket实现流式推送。走到这一步你已经从一个OpenAI API的初学者变成了一个能够构建出实用AI功能的JavaScript开发者。剩下的就是发挥你的想象力去解决那些真正有趣的问题了。记住关键不是记住所有参数而是理解其工作原理和设计模式然后大胆地去构建、测试和迭代。
JavaScript开发者快速上手OpenAI API:从基础调用到实战应用
1. 项目概述为什么现在要上手OpenAI API如果你是一名JavaScript开发者最近几个月肯定被各种AI应用刷屏了。从能写代码的Copilot到能聊天的ChatGPT背后都离不开大语言模型LLM的驱动。而OpenAI API就是让我们普通开发者也能在自己的应用里接入这种强大能力的最直接入口。它不是一个遥不可及的“黑科技”本质上就是一个功能极其强大的HTTP接口你用熟悉的fetch或者axios就能调用。这个项目标题“Getting Started with OpenAI API in JavaScript”直译过来是“在JavaScript中开始使用OpenAI API”听起来像是一篇入门教程。但我的理解是它真正的价值在于帮你跨过“从知道到做到”的门槛。网上官方文档虽然详尽但面对海量的模型选项、复杂的参数、流式响应和错误处理新手很容易懵。我见过不少朋友卡在第一步密钥怎么配请求体怎么构造返回的庞大数据怎么处理更别提那些影响效果和成本的细节了。所以这篇内容我会带你走完一个完整的、可落地的流程。不仅仅是“Hello World”而是从一个真实的、有前后端交互的Node.js项目角度出发涵盖从账户准备、环境搭建、核心调用、到错误处理、成本控制和实战优化的全过程。我会分享那些官方文档里不会写的“坑”比如为什么你的流式输出不完整、如何设计提示词Prompt才能让模型更“听话”、以及怎么用最少的代码实现一个可用的AI对话功能。无论你是想给自己的博客加个智能摘要还是想开发一个创意写作助手从这里开始都够了。2. 核心概念与准备工作在动手写代码之前我们必须把几个核心概念和准备工作理清楚。这就像盖房子前要打地基和备料跳过这一步后面的代码写得再漂亮也容易出问题。2.1 理解OpenAI API的核心组件OpenAI API不是一个单一的接口而是一系列模型和功能的集合。对于JavaScript开发者我们主要和以下几个核心组件打交道模型Model这是AI的“大脑”。不同的模型能力、价格和速度都不同。最常用的是gpt-3.5-turbo和gpt-4系列它们是为对话优化的模型我们通过发送“消息”数组与它们交互。此外还有专门用于文本嵌入的text-embedding模型用于生成图像的dall-e模型等。选择模型是第一步也直接关系到成本和效果。API密钥API Key这是你的身份凭证和计费凭证。务必像保护密码一样保护它绝不能提交到公开的代码仓库如GitHub。我们通常会把它放在环境变量中。端点Endpoint即不同的API功能地址。最常用的是聊天补全端点https://api.openai.com/v1/chat/completions用于对话还有补全端点、嵌入端点等。提示词Prompt与消息Messages这是我们与模型沟通的方式。对于聊天模型我们构造一个消息数组每条消息有role角色system,user,assistant和content内容。system消息用于设定AI的“人设”和行为指令这是控制输出质量的关键。2.2 环境准备与安全配置我们将在Node.js环境下进行演示。请确保你已安装Node.js建议版本16和npm。第一步获取API密钥访问OpenAI平台网站注册并登录。点击右上角个人头像进入“View API keys”。点击“Create new secret key”生成一个新密钥。生成后立即复制保存因为它只显示一次。第二步初始化项目并安全存储密钥打开终端执行以下命令# 创建一个新项目目录 mkdir openai-js-starter cd openai-js-starter # 初始化npm项目一路回车即可 npm init -y # 安装官方OpenAI Node.js库和dotenv用于管理环境变量 npm install openai dotenv接下来安全地配置你的API密钥。在项目根目录创建两个文件.env和.env.example。.env文件务必加入.gitignore不要提交OPENAI_API_KEY你的_真实_API_密钥_放在这里.env.example文件用于说明项目需要的环境变量可以提交OPENAI_API_KEYsk-...注意.env文件中不要使用引号包裹密钥值直接写sk-开头的一串字符即可。许多新手会写成OPENAI_API_KEYsk-...这反而可能导致解析错误。第三步安装并配置OpenAI官方库OpenAI提供了维护良好的官方Node.js库openai它封装了API调用、错误处理和TypeScript类型比直接用fetch更省心。我们已经在上一步安装了它。创建一个基础配置文件例如src/config.jsimport dotenv from dotenv; import OpenAI from openai; // 加载.env文件中的环境变量 dotenv.config(); // 初始化OpenAI客户端它会自动从process.env.OPENAI_API_KEY读取密钥 const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); // 简单验证密钥是否已加载非必须用于调试 if (!process.env.OPENAI_API_KEY) { console.error(错误未找到OPENAI_API_KEY环境变量。请检查.env文件。); process.exit(1); } export { openai };至此你的项目地基已经打牢。接下来我们就可以开始真正的API调用了。3. 实现基础聊天交互功能现在我们来构建最核心的功能与AI模型进行对话。我们将从最简单的单次问答开始逐步增加复杂度实现多轮对话和流式响应。3.1 你的第一个AI对话请求让我们先实现一个最简单的函数向gpt-3.5-turbo模型发送一条用户消息并获取回复。创建文件src/basicChat.js。import { openai } from ./config.js; async function getSingleChatResponse(userInput) { try { const completion await openai.chat.completions.create({ model: gpt-3.5-turbo, // 指定使用的模型 messages: [ // system消息设定AI的角色。这是可选的但强烈建议提供能极大改善回复质量。 { role: system, content: 你是一个乐于助人的JavaScript编程助手。 }, // user消息是我们的问题 { role: user, content: userInput } ], temperature: 0.7, // 控制输出的随机性0-2。值越高输出越随机、有创意值越低输出越确定、保守。 max_tokens: 150, // 限制生成回复的最大长度约等于单词数。注意这包括输入和输出设置过低可能导致回复被截断。 }); // 返回AI的回复内容 return completion.choices[0].message.content; } catch (error) { // 错误处理我们稍后详细讲这里先简单抛出 console.error(调用OpenAI API时出错:, error); throw error; } } // 调用示例 (async () { const answer await getSingleChatResponse(如何用JavaScript反转一个字符串); console.log(AI回复:, answer); })();运行这个脚本你应该能看到AI返回的关于反转字符串的方法。这里有几个关键点messages数组对话的历史记录。每次请求都需要传递完整的上下文。如果你想要多轮对话就需要在后续请求中把之前所有的user和assistant消息都包含进去。temperature参数这是控制“创意”程度的旋钮。写代码助手通常设为较低值如0.2-0.5以保证答案的准确性写诗歌或故事可以设高如0.8-1.2。max_tokens参数需要根据你输入的长度和期望回复的长度来估算。gpt-3.5-turbo的上下文窗口通常是4096或16385个token约等于单词数你的输入和输出总和不能超过这个限制。3.2 实现多轮对话上下文管理单次问答意义有限真正的聊天需要记住之前说过的话。这意味着我们需要在客户端维护一个“消息历史”数组并在每次请求时将其全部发送。下面是一个简单的上下文管理示例。创建src/chatWithContext.jsimport { openai } from ./config.js; class ChatSession { constructor(systemPrompt 你是一个有用的助手。) { // 初始化消息历史包含system指令 this.messages [{ role: system, content: systemPrompt }]; } async sendMessage(userInput) { // 将用户输入加入历史 this.messages.push({ role: user, content: userInput }); try { const completion await openai.chat.completions.create({ model: gpt-3.5-turbo, messages: this.messages, // 发送全部历史 temperature: 0.7, }); const assistantReply completion.choices[0].message.content; // 将AI回复也加入历史以便后续对话引用 this.messages.push({ role: assistant, content: assistantReply }); return assistantReply; } catch (error) { console.error(对话出错:, error); // 如果出错从历史中移除刚才加入的user消息避免状态不一致 this.messages.pop(); throw error; } } // 可选清空对话历史但保留system指令 clearHistory() { this.messages [this.messages[0]]; } // 获取当前对话轮数不计system消息 getTurnCount() { return this.messages.length - 1; } } // 使用示例 (async () { const chat new ChatSession(你是一个知识渊博的历史学家用中文回答。); console.log(await chat.sendMessage(请简述罗马帝国的衰落原因。)); console.log(--- 对话轮数:, chat.getTurnCount(), ---\n); // AI此时已经记住了上一轮对话 console.log(await chat.sendMessage(它对后世有什么影响)); console.log(--- 对话轮数:, chat.getTurnCount(), ---\n); chat.clearHistory(); console.log(历史已清空新问题:); console.log(await chat.sendMessage(今天天气怎么样)); // AI将不再记得罗马帝国 })();这个ChatSession类封装了上下文管理。但请注意随着对话轮数增加messages数组会越来越大最终可能超过模型的上下文长度限制。在实际生产中你需要实现更智能的上下文窗口管理例如只保留最近N条消息或者对历史消息进行总结压缩。3.3 接入流式响应Streaming提升体验默认的API调用是“阻塞”的你必须等待AI生成全部内容后才能收到回复。对于长文本这会导致用户长时间等待体验很差。流式响应Streaming允许你像接收视频流一样逐字逐句地实时接收AI生成的内容。这在构建聊天界面时几乎是必备功能。下面是使用官方库实现流式响应的方式。创建src/streamingChat.jsimport { openai } from ./config.js; import readline from readline; // Node.js内置模块用于命令行交互 // 创建命令行交互接口 const rl readline.createInterface({ input: process.stdin, output: process.stdout }); function askQuestion(prompt) { return new Promise((resolve) rl.question(prompt, resolve)); } async function streamingChat() { const messages [{ role: system, content: 你是一个幽默的聊天伙伴。 }]; console.log(开始流式聊天 (输入 退出 结束)\n); while (true) { const userInput await askQuestion(\n你: ); if (userInput.toLowerCase() 退出) break; messages.push({ role: user, content: userInput }); console.log(\nAI: ); try { const stream await openai.chat.completions.create({ model: gpt-3.5-turbo, messages: messages, stream: true, // 关键参数开启流式响应 temperature: 0.7, }); let fullResponse ; // 逐块处理流数据 for await (const chunk of stream) { const content chunk.choices[0]?.delta?.content || ; process.stdout.write(content); // 逐字打印到控制台 fullResponse content; } console.log(); // 换行 // 将完整的AI回复加入消息历史 messages.push({ role: assistant, content: fullResponse }); } catch (error) { console.error(\n流式请求出错:, error.message); // 出错时移除刚才添加的user消息 messages.pop(); } } rl.close(); console.log(聊天结束。); } // 启动聊天 streamingChat().catch(console.error);运行这个脚本你会看到AI的回复是一个字一个字“打”出来的就像真人打字一样。这里有一个至关重要的细节流式响应中每个chunk的delta属性包含了增量内容而不是完整的消息。最终你需要自己拼接出完整的回复并手动将其加入messages历史以便后续对话使用。很多新手会忘记这一步导致上下文丢失。实操心得在Web前端如React/Vue中实现流式响应原理类似。你需要使用fetch并处理ReadableStream或者使用官方库的流式支持。核心是将收到的数据块实时更新到UI状态中。注意处理好连接中断、错误重试等边界情况。4. 高级功能与实战技巧掌握了基础对话我们可以探索一些更高级的功能和优化技巧这些能让你的AI应用更强大、更可控、也更经济。4.1 使用Function Calling实现“AI调用工具”这是OpenAI API一个革命性的功能。它允许你定义一些“函数”工具AI在对话中如果认为需要可以“请求”你调用这些函数并等你提供函数执行结果后再继续对话。这相当于赋予了AI使用外部工具的能力。一个经典场景是用户问“北京今天天气怎么样”。AI自己无法获取实时天气但它可以“决定”调用你定义的get_current_weather函数你调用真实天气API拿到数据后把结果返回给AI由AI组织成自然语言回复给用户。下面是一个模拟的示例创建src/functionCalling.jsimport { openai } from ./config.js; // 1. 定义你可以提供的“工具”函数 const tools [ { type: function, function: { name: get_current_weather, description: 获取指定城市的当前天气, parameters: { type: object, properties: { location: { type: string, description: 城市名称例如北京上海, }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位摄氏度或华氏度, }, }, required: [location], }, }, }, ]; // 2. 模拟的工具函数实现 function executeFunctionCall(toolCall) { const functionName toolCall.function.name; const args JSON.parse(toolCall.function.arguments); if (functionName get_current_weather) { // 这里应该调用真实的天气API我们模拟一个结果 return JSON.stringify({ location: args.location, temperature: args.unit celsius ? 22°C : 72°F, condition: 晴朗, forecast: [晴朗, 多云, 微风], }); } return JSON.stringify({ error: 未知函数: ${functionName} }); } async function chatWithFunctionCalling(userQuery) { const messages [ { role: system, content: 你是一个有帮助的助手可以查询天气。如果用户问天气请调用get_current_weather函数。用中文回复。 }, { role: user, content: userQuery }, ]; console.log(用户: ${userQuery}); try { // 第一轮AI判断是否需要调用函数 const response await openai.chat.completions.create({ model: gpt-3.5-turbo, messages: messages, tools: tools, // 关键传入工具定义 tool_choice: auto, // 让AI自动决定是否调用工具 }); const responseMessage response.choices[0].message; messages.push(responseMessage); // 将AI的回复可能包含工具调用请求加入历史 // 3. 检查AI是否想调用工具 const toolCalls responseMessage.tool_calls; if (toolCalls) { console.log(AI决定调用工具: ${toolCalls.map(tc tc.function.name).join(, )}); // 4. 执行每个被请求的工具调用 for (const toolCall of toolCalls) { const functionResponse executeFunctionCall(toolCall); // 5. 将工具执行结果作为一条新消息发送给AI messages.push({ role: tool, tool_call_id: toolCall.id, // 必须与请求的ID对应 content: functionResponse, }); } // 6. 获取AI基于工具结果生成的最终回复 const secondResponse await openai.chat.completions.create({ model: gpt-3.5-turbo, messages: messages, // 此时消息历史包含了系统指令、用户问题、AI的工具调用请求、工具执行结果 }); const finalReply secondResponse.choices[0].message.content; messages.push({ role: assistant, content: finalReply }); console.log(AI (最终回复): ${finalReply}); return finalReply; } else { // AI没有调用工具直接回复 console.log(AI: ${responseMessage.content}); return responseMessage.content; } } catch (error) { console.error(出错:, error); throw error; } } // 测试 (async () { await chatWithFunctionCalling(今天上海天气如何); console.log(\n---\n); await chatWithFunctionCalling(你好吗); })();这个过程看似复杂但逻辑清晰定义工具 → AI请求调用 → 你执行函数 → 返回结果给AI → AI生成最终回复。这为AI应用接入了无限的可能性查数据库、发邮件、操作文件等等。注意事项Function Calling会产生额外的token消耗主要是工具定义的描述部分并且一次对话可能涉及多次API调用请求调用一次返回结果后再生成一次成本和控制逻辑都需要考虑。4.2 优化提示词Prompt Engineering以控制输出模型的表现极大程度上取决于你给它的指令Prompt。糟糕的Prompt得到糟糕的结果。以下是一些经过验证的提示词优化技巧明确角色和任务在system消息中清晰定义。“你是一个JavaScript专家”不如“你是一个专注于ES6及以上版本的JavaScript代码审查助手。你的任务是找出代码中的错误、不规范的写法并提供优化建议。用中文回复。”结构化输出要求AI按特定格式输出便于你后续程序化处理。system: 请将以下文本分类为“正面”、“中性”或“负面”情感。仅输出一个JSON对象格式为{sentiment: 分类结果, confidence: 置信度百分比}。不要输出其他任何文字。 user: 这个产品太棒了我非常喜欢 // 期望输出: {sentiment: 正面, confidence: 95}提供示例Few-Shot Learning在消息中给出一两个输入输出的例子能显著提升模型在特定任务上的表现。messages: [ {role: system, content: 将中文俚语翻译成英文并给出字面翻译和含义解释。}, {role: user, content: 打酱油}, {role: assistant, content: English: None of my business. Literal: Buying soy sauce. Explanation: Used to describe someone who is just a bystander, not involved in the matter.}, {role: user, content: 新问题吃瓜群众} ]分步思考Chain-of-Thought对于复杂问题鼓励AI“一步一步想”。可以在system指令中加入“请逐步推理你的答案”。设置约束明确禁止某些行为。“不要道歉”、“不要使用Markdown格式”、“回答不超过100字”。一个综合性的优质Prompt示例const systemPrompt 你是一个专业的科技文章翻译助手。你的任务是将英文科技新闻翻译成地道的中文。 请遵循以下规则 1. 专业术语保持英文原名并在括号内提供通用中文译名如Transformer (变换器模型)。 2. 长句拆分为符合中文阅读习惯的短句。 3. 保留原文的客观语气不添加个人评论。 4. 如果原文有代码片段保持原样不翻译。 5. 最终输出格式先给出翻译全文然后在“---”后列出本条新闻中出现的3-5个关键专业术语及其解释。 现在请翻译以下内容;不断迭代和测试你的Prompt是提升AI应用质量性价比最高的方式。4.3 成本控制与用量监控OpenAI API按使用量计费主要依据消耗的token数量。Token可以粗略理解为单词的一部分对于英文1个token约等于0.75个单词对于中文1个汉字大约对应1.2到2个token。成本控制策略选择合适的模型gpt-3.5-turbo比gpt-4便宜一个数量级对于许多非高精度要求的任务如客服、文本生成、简单分类完全够用。gpt-4-turbo在能力和成本间取得了更好的平衡。设置max_tokens上限防止意外生成超长文本导致巨额费用。根据场景合理设置。管理上下文长度如前所述历史消息越长消耗的token越多因为每次都要全部发送。实现上下文窗口管理例如只保留最近10轮对话或者当历史超过某个阈值时用AI自动总结之前的对话内容然后用总结替换掉旧消息。使用流式响应虽然不影响总token数但可以提前让用户看到部分内容如果发现AI“胡言乱语”可以及时中断请求通过AbortController避免为无用的后续内容付费。缓存结果对于常见、重复性的问题如产品FAQ可以将AI的回复缓存起来下次用户问相同或类似问题时直接返回缓存无需再次调用API。用量监控实践OpenAI官方库的响应对象中包含了usage字段详细列出了本次请求消耗的token数。const completion await openai.chat.completions.create({/*...*/}); console.log(本次消耗:, completion.usage); // 输出: { prompt_tokens: 25, completion_tokens: 150, total_tokens: 175 }你应该在应用中记录这些数据并设置每日/每月预算警报。OpenAI平台后台也提供了用量仪表盘。对于生产环境强烈建议在调用API的代码层添加一个用量统计和限流中间件。5. 常见问题、错误处理与调试在实际开发中你一定会遇到各种错误和意外情况。这里整理了一份“避坑指南”。5.1 常见错误码与解决方案错误码含义可能原因与解决方案401认证失败API密钥错误、过期或未提供。检查.env文件、环境变量名是否正确密钥是否有效。429请求过多超过速率限制RPM-每分钟请求数TPM-每分钟token数。解决方案1. 降低请求频率2. 实现指数退避重试机制3. 申请提升限额。400错误请求请求参数无效。常见原因messages格式错误、model名称拼写错误、max_tokens值过大超过模型上下文限制。仔细检查请求体。500/503服务器内部错误OpenAI服务器端问题。通常是暂时的等待一段时间后重试。实现重试逻辑时对于5xx错误可以重试。context_length_exceeded上下文超长输入的token数历史消息当前问题超过了模型的最大上下文长度。必须缩短消息历史。5.2 实现健壮的错误处理与重试机制一个生产级的调用应该包含完善的错误处理。下面是一个增强版的调用函数示例import { openai } from ./config.js; async function robustChatCompletion(messages, model gpt-3.5-turbo, maxRetries 3) { let lastError; for (let attempt 1; attempt maxRetries; attempt) { try { const completion await openai.chat.completions.create({ model: model, messages: messages, temperature: 0.7, max_tokens: 500, // 可以设置超时但注意openai库可能依赖axios或fetch的配置 }); return completion; // 成功则直接返回 } catch (error) { lastError error; console.error(第 ${attempt} 次尝试失败:, error.message); // 检查错误类型决定是否重试 if (error.status 429) { // 速率限制等待一段时间再重试指数退避 const delayMs Math.min(1000 * Math.pow(2, attempt), 30000); // 最大等待30秒 console.log(速率限制等待 ${delayMs}ms 后重试...); await new Promise(resolve setTimeout(resolve, delayMs)); continue; // 继续重试循环 } else if (error.status 500) { // 服务器错误短暂等待后重试 console.log(服务器错误等待 2s 后重试...); await new Promise(resolve setTimeout(resolve, 2000)); continue; } else if (error.status 400 error.message.includes(context_length)) { // 上下文过长无法通过重试解决直接抛出 throw new Error(上下文过长请缩短对话历史。原始错误: ${error.message}); } else { // 其他客户端错误401, 404, 400非上下文问题等重试无意义直接抛出 throw error; } } } // 重试多次后仍失败 throw new Error(API调用失败已重试${maxRetries}次。最后错误: ${lastError.message}); } // 使用示例 (async () { try { const response await robustChatCompletion([ { role: user, content: 你好 } ]); console.log(成功:, response.choices[0].message.content); } catch (error) { console.error(最终失败:, error.message); // 这里可以触发告警、降级策略等 } })();5.3 调试技巧与工具记录完整的请求与响应在开发阶段打印出请求的messages和完整的响应对象这是排查问题最直接的方法。注意不要在生产环境记录包含敏感信息的日志。使用OpenAI PlaygroundOpenAI官网提供的Playground是一个绝佳的调试和Prompt设计工具。你可以直接在网页上交互式地测试不同的参数和消息看到实时结果和token消耗然后将配置“复制为代码”直接用到你的项目中。估算Token数量OpenAI提供了tiktoken库用于精确计算token数。你可以提前估算请求是否会超长。对于JavaScript有社区实现的版本如js-tiktoken。监控响应时间API调用可能因为网络或服务器负载而变慢。记录响应时间对于慢请求考虑设置超时并实施降级方案如返回缓存、默认回复。处理流式中断网络不稳定时流式响应可能中断。前端需要监听stream的error事件并给用户友好的提示提供“重试”按钮。6. 从脚本到应用构建一个简单的AI聊天机器人最后我们将前面所有的知识点串联起来构建一个简单的命令行或Web聊天机器人。这里以Node.js命令行版本为例展示一个相对完整的结构。项目结构openai-chatbot/ ├── .env ├── .env.example ├── package.json ├── src/ │ ├── config.js # 初始化配置 │ ├── ChatSession.js # 封装对话会话类 │ ├── utils.js # 工具函数如token估算 │ └── cli.js # 命令行入口 └── README.mdsrc/ChatSession.js(增强版包含上下文管理和基础错误处理)import { openai } from ./config.js; export class EnhancedChatSession { constructor(systemPrompt, model gpt-3.5-turbo, maxHistoryTurns 10) { this.model model; this.maxHistoryTurns maxHistoryTurns * 2; // user和assistant各算一轮 this.messages systemPrompt ? [{ role: system, content: systemPrompt }] : []; } async sendMessage(userInput, streamCallback null) { this.addMessage(user, userInput); const requestPayload { model: this.model, messages: this.messages, temperature: 0.7, max_tokens: 1000, }; try { let fullResponse ; if (streamCallback) { // 流式模式 requestPayload.stream true; const stream await openai.chat.completions.create(requestPayload); for await (const chunk of stream) { const content chunk.choices[0]?.delta?.content || ; streamCallback(content); fullResponse content; } } else { // 非流式模式 const completion await openai.chat.completions.create(requestPayload); fullResponse completion.choices[0].message.content; console.log([Token用量] 输入: ${completion.usage.prompt_tokens}, 输出: ${completion.usage.completion_tokens}); } this.addMessage(assistant, fullResponse); this.maybeTrimContext(); // 如果历史太长进行修剪 return fullResponse; } catch (error) { console.error(发送消息失败:, error.message); // 出错时回滚最后添加的user消息 if (this.messages.length 0 this.messages[this.messages.length - 1].role user) { this.messages.pop(); } throw error; } } addMessage(role, content) { this.messages.push({ role, content }); } maybeTrimContext() { // 简单的修剪策略如果超过限制移除最早的一对user/assistant消息保留system while (this.messages.length this.maxHistoryTurns 1) { // 1 是system消息 // 找到第一个非system消息的索引 const firstNonSystemIndex this.messages.findIndex(msg msg.role ! system); if (firstNonSystemIndex ! -1) { // 移除找到的消息及其后一条假设是成对的这里简化处理移除最早的两条 this.messages.splice(firstNonSystemIndex, 2); } else { break; } } } clearHistory() { const systemMsg this.messages.find(m m.role system); this.messages systemMsg ? [systemMsg] : []; } }src/cli.js(命令行交互入口)import readline from readline; import { EnhancedChatSession } from ./ChatSession.js; const rl readline.createInterface({ input: process.stdin, output: process.stdout }); function ask(prompt) { return new Promise(resolve rl.question(prompt, resolve)); } async function main() { console.log( 简易AI聊天机器人 (输入 /quit 退出, /clear 清空历史, /help 帮助) \n); const systemPrompt await ask(请设定AI的角色和指令 (直接回车使用默认): ) || 你是一个有用且友好的助手。; const useStream (await ask(启用流式响应(y/n, 默认y): )).toLowerCase() ! n; const chat new EnhancedChatSession(systemPrompt); while (true) { const userInput await ask(\n你: ); if (userInput /quit) break; if (userInput /clear) { chat.clearHistory(); console.log(对话历史已清空。); continue; } if (userInput /help) { console.log(命令列表:\n /quit - 退出\n /clear - 清空对话历史\n /help - 显示此帮助); continue; } if (!userInput.trim()) continue; process.stdout.write(AI: ); try { if (useStream) { await chat.sendMessage(userInput, (chunk) process.stdout.write(chunk)); process.stdout.write(\n); } else { const reply await chat.sendMessage(userInput); console.log(reply); } } catch (error) { console.error(\n请求出错: ${error.message}); } } rl.close(); console.log(再见); } main().catch(console.error);这个简单的机器人具备了多轮对话、流式响应、上下文管理、基础命令和错误处理。你可以在此基础上增加更多功能比如将历史对话保存到文件或数据库。支持切换不同的模型和参数。集成Function Calling来实现更复杂的功能。添加一个简单的Web界面使用Express.js SSE或WebSocket实现流式推送。走到这一步你已经从一个OpenAI API的初学者变成了一个能够构建出实用AI功能的JavaScript开发者。剩下的就是发挥你的想象力去解决那些真正有趣的问题了。记住关键不是记住所有参数而是理解其工作原理和设计模式然后大胆地去构建、测试和迭代。