JavaScript开发者快速上手OpenAI API:从基础调用到实战应用
2026/5/31 11:27:48 网站建设 项目流程

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开发者,我们主要和以下几个核心组件打交道:

  1. 模型(Model):这是AI的“大脑”。不同的模型能力、价格和速度都不同。最常用的是gpt-3.5-turbogpt-4系列,它们是为对话优化的模型,我们通过发送“消息”数组与它们交互。此外还有专门用于文本嵌入的text-embedding模型,用于生成图像的dall-e模型等。选择模型是第一步,也直接关系到成本和效果。
  2. API密钥(API Key):这是你的身份凭证和计费凭证。务必像保护密码一样保护它,绝不能提交到公开的代码仓库(如GitHub)。我们通常会把它放在环境变量中。
  3. 端点(Endpoint):即不同的API功能地址。最常用的是聊天补全端点(https://api.openai.com/v1/chat/completions),用于对话;还有补全端点、嵌入端点等。
  4. 提示词(Prompt)与消息(Messages):这是我们与模型沟通的方式。对于聊天模型,我们构造一个消息数组,每条消息有role(角色:system,user,assistant)和content(内容)。system消息用于设定AI的“人设”和行为指令,这是控制输出质量的关键。

2.2 环境准备与安全配置

我们将在Node.js环境下进行演示。请确保你已安装Node.js(建议版本16+)和npm。

第一步:获取API密钥

  1. 访问OpenAI平台网站,注册并登录。
  2. 点击右上角个人头像,进入“View API keys”。
  3. 点击“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_KEY=sk-...

注意.env文件中不要使用引号包裹密钥值,直接写sk-开头的一串字符即可。许多新手会写成OPENAI_API_KEY="sk-...",这反而可能导致解析错误。

第三步:安装并配置OpenAI官方库OpenAI提供了维护良好的官方Node.js库openai,它封装了API调用、错误处理和TypeScript类型,比直接用fetch更省心。我们已经在上一步安装了它。

创建一个基础配置文件,例如src/config.js

import 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数组:对话的历史记录。每次请求都需要传递完整的上下文。如果你想要多轮对话,就需要在后续请求中把之前所有的userassistant消息都包含进去。
  • temperature参数:这是控制“创意”程度的旋钮。写代码助手通常设为较低值(如0.2-0.5)以保证答案的准确性;写诗歌或故事可以设高(如0.8-1.2)。
  • max_tokens参数:需要根据你输入的长度和期望回复的长度来估算。gpt-3.5-turbo的上下文窗口通常是4096或16385个token(约等于单词数),你的输入和输出总和不能超过这个限制。

3.2 实现多轮对话上下文管理

单次问答意义有限,真正的聊天需要记住之前说过的话。这意味着我们需要在客户端维护一个“消息历史”数组,并在每次请求时将其全部发送。下面是一个简单的上下文管理示例。

创建src/chatWithContext.js

import { 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.js

import { 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的回复是一个字一个字“打”出来的,就像真人打字一样。这里有一个至关重要的细节:流式响应中,每个chunkdelta属性包含了增量内容,而不是完整的消息。最终你需要自己拼接出完整的回复,并手动将其加入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.js

import { 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得到糟糕的结果。以下是一些经过验证的提示词优化技巧:

  1. 明确角色和任务:在system消息中清晰定义。“你是一个JavaScript专家”不如“你是一个专注于ES6及以上版本的JavaScript代码审查助手。你的任务是找出代码中的错误、不规范的写法,并提供优化建议。用中文回复。”
  2. 结构化输出:要求AI按特定格式输出,便于你后续程序化处理。
    system: `请将以下文本分类为“正面”、“中性”或“负面”情感。仅输出一个JSON对象,格式为:{"sentiment": "分类结果", "confidence": 置信度百分比}。不要输出其他任何文字。` user: `这个产品太棒了,我非常喜欢!` // 期望输出: {"sentiment": "正面", "confidence": 95}
  3. 提供示例(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: "新问题:吃瓜群众"} ]
  4. 分步思考(Chain-of-Thought):对于复杂问题,鼓励AI“一步一步想”。可以在system指令中加入“请逐步推理你的答案”。
  5. 设置约束:明确禁止某些行为。“不要道歉”、“不要使用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。

成本控制策略:

  1. 选择合适的模型gpt-3.5-turbogpt-4便宜一个数量级,对于许多非高精度要求的任务(如客服、文本生成、简单分类)完全够用。gpt-4-turbo在能力和成本间取得了更好的平衡。
  2. 设置max_tokens上限:防止意外生成超长文本导致巨额费用。根据场景合理设置。
  3. 管理上下文长度:如前所述,历史消息越长,消耗的token越多(因为每次都要全部发送)。实现上下文窗口管理,例如只保留最近10轮对话,或者当历史超过某个阈值时,用AI自动总结之前的对话内容,然后用总结替换掉旧消息。
  4. 使用流式响应:虽然不影响总token数,但可以提前让用户看到部分内容,如果发现AI“胡言乱语”,可以及时中断请求(通过AbortController),避免为无用的后续内容付费。
  5. 缓存结果:对于常见、重复性的问题(如产品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 调试技巧与工具

  1. 记录完整的请求与响应:在开发阶段,打印出请求的messages和完整的响应对象,这是排查问题最直接的方法。注意不要在生产环境记录包含敏感信息的日志。
  2. 使用OpenAI Playground:OpenAI官网提供的Playground是一个绝佳的调试和Prompt设计工具。你可以直接在网页上交互式地测试不同的参数和消息,看到实时结果和token消耗,然后将配置“复制为代码”直接用到你的项目中。
  3. 估算Token数量:OpenAI提供了tiktoken库用于精确计算token数。你可以提前估算请求是否会超长。对于JavaScript,有社区实现的版本如js-tiktoken
  4. 监控响应时间:API调用可能因为网络或服务器负载而变慢。记录响应时间,对于慢请求考虑设置超时并实施降级方案(如返回缓存、默认回复)。
  5. 处理流式中断:网络不稳定时,流式响应可能中断。前端需要监听streamerror事件,并给用户友好的提示,提供“重试”按钮。

6. 从脚本到应用:构建一个简单的AI聊天机器人

最后,我们将前面所有的知识点串联起来,构建一个简单的命令行或Web聊天机器人。这里以Node.js命令行版本为例,展示一个相对完整的结构。

项目结构:

openai-chatbot/ ├── .env ├── .env.example ├── package.json ├── src/ │ ├── config.js # 初始化配置 │ ├── ChatSession.js # 封装对话会话类 │ ├── utils.js # 工具函数(如token估算) │ └── cli.js # 命令行入口 └── README.md

src/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开发者。剩下的,就是发挥你的想象力,去解决那些真正有趣的问题了。记住,关键不是记住所有参数,而是理解其工作原理和设计模式,然后大胆地去构建、测试和迭代。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询