Node.js调用OpenAI API指南:从入门到工具调用

第一节:最基础的LLM对话

让我们从最简单的对话开始,理解OpenAI API的基本用法。

消息的基本结构

每个消息都有两个核心字段:

javascript 复制代码
interface Message {
  role: 'user' | 'assistant';  // 先只关注这两个角色
  content: string;              // 消息内容
}

最简单的对话示例

javascript 复制代码
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

// 最基础的对话
async function simpleChat() {
  const messages = [
    {
      role: 'user' as const,
      content: '你好,请介绍一下自己'
    }
  ];

  const response = await openai.chat.completions.create({
    model: 'gpt-3.5-turbo',
    messages: messages
  });

  console.log(response.choices[0].message.content);
}

多轮对话的实现

javascript 复制代码
class SimpleChat {
  private messages: Message[] = [];
  private openai: OpenAI;

  constructor(apiKey: string) {
    this.openai = new OpenAI({ apiKey });
  }

  // 发送用户消息并获取回复
  async sendMessage(userInput: string): Promise<string> {
    // 1. 添加用户消息
    this.messages.push({
      role: 'user',
      content: userInput
    });

    // 2. 调用API
    const response = await this.openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: this.messages
    });

    const assistantReply = response.choices[0].message.content || '';

    // 3. 保存AI回复
    this.messages.push({
      role: 'assistant',
      content: assistantReply
    });

    return assistantReply;
  }

  // 查看对话历史
  getHistory(): Message[] {
    return [...this.messages];
  }
}

使用示例

javascript 复制代码
async function demo() {
  const chat = new SimpleChat(process.env.OPENAI_API_KEY!);

  // 第一轮对话
  const reply1 = await chat.sendMessage('你好');
  console.log('AI:', reply1);

  // 第二轮对话(AI会记住之前的内容)
  const reply2 = await chat.sendMessage('我刚才说了什么?');
  console.log('AI:', reply2);

  // 查看完整对话历史
  console.log('对话历史:', chat.getHistory());
}

关键要点:

  • 每次API调用都要传入完整的消息历史
  • 用户消息用role: 'user',AI回复用role: 'assistant'
  • 消息顺序很重要,按时间顺序排列

第二节:使用System消息设定AI行为

现在我们学习如何通过System消息来"调教"AI的行为。

System消息的作用

System消息就像给AI的"工作说明书",告诉它应该如何表现。

javascript 复制代码
interface Message {
  role: 'system' | 'user' | 'assistant';  // 新增system角色
  content: string;
}

带System消息的聊天类

javascript 复制代码
class SystemChat {
  private messages: Message[] = [];
  private openai: OpenAI;

  constructor(apiKey: string, systemPrompt: string) {
    this.openai = new OpenAI({ apiKey });
    
    // System消息总是放在第一位
    this.messages.push({
      role: 'system',
      content: systemPrompt
    });
  }

  async sendMessage(userInput: string): Promise<string> {
    this.messages.push({
      role: 'user',
      content: userInput
    });

    const response = await this.openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: this.messages
    });

    const assistantReply = response.choices[0].message.content || '';
    
    this.messages.push({
      role: 'assistant',
      content: assistantReply
    });

    return assistantReply;
  }
}

不同的System Prompt示例

javascript 复制代码
// 1. 专业助手
const technicalAssistant = new SystemChat(
  process.env.OPENAI_API_KEY!,
  '你是一个专业的技术文档助手。请用简洁、准确的语言回答编程相关问题,并提供代码示例。'
);

// 2. 创意写手
const creativeWriter = new SystemChat(
  process.env.OPENAI_API_KEY!,
  '你是一个富有创意的作家。请用生动、有趣的语言回答问题,多使用比喻和故事。'
);

// 3. 严格的老师
const strictTeacher = new SystemChat(
  process.env.OPENAI_API_KEY!,
  '你是一个严格的编程老师。当学生问问题时,不要直接给答案,而是引导他们思考,问一些启发性问题。'
);

对比测试

javascript 复制代码
async function compareSystemPrompts() {
  const question = '什么是递归?';

  console.log('=== 技术助手 ===');
  const tech = await technicalAssistant.sendMessage(question);
  console.log(tech);

  console.log('\n=== 创意写手 ===');
  const creative = await creativeWriter.sendMessage(question);
  console.log(creative);

  console.log('\n=== 严格老师 ===');
  const teacher = await strictTeacher.sendMessage(question);
  console.log(teacher);
}

System消息的最佳实践:

  • 明确定义AI的角色和专业领域
  • 指定回答的风格和格式
  • 设定行为边界和限制
  • 保持简洁但具体

第三节:工具调用 - 让AI具备行动能力

现在我们学习最强大的功能:让AI调用外部工具。

工具调用的消息结构

工具调用涉及两个新的消息角色:

javascript 复制代码
interface Message {
  role: 'system' | 'user' | 'assistant' | 'tool';
  content: string | null;
  tool_calls?: ToolCall[];     // assistant消息可能包含工具调用
  tool_call_id?: string;       // tool消息必须有这个ID
}

interface ToolCall {
  id: string;
  type: 'function';
  function: {
    name: string;
    arguments: string;  // JSON字符串
  };
}

定义工具

首先定义AI可以使用的工具:

javascript 复制代码
const tools = [
  {
    type: 'function' as const,
    function: {
      name: 'get_weather',
      description: '获取指定城市的天气信息',
      parameters: {
        type: 'object',
        properties: {
          city: {
            type: 'string',
            description: '城市名称'
          }
        },
        required: ['city']
      }
    }
  },
  {
    type: 'function' as const,
    function: {
      name: 'calculate',
      description: '执行数学计算',
      parameters: {
        type: 'object',
        properties: {
          expression: {
            type: 'string',
            description: '数学表达式,如 "2 + 3 * 4"'
          }
        },
        required: ['expression']
      }
    }
  }
];

实现工具函数

javascript 复制代码
// 模拟天气API
async function getWeather(city: string): Promise<string> {
  // 实际项目中这里会调用真实的天气API
  const weatherData = {
    '北京': '晴天,25°C',
    '上海': '多云,22°C',
    '广州': '雨天,28°C'
  };
  
  return weatherData[city as keyof typeof weatherData] || '未知城市';
}

// 计算器
function calculate(expression: string): string {
  try {
    // 注意:实际项目中需要安全的表达式求值
    const result = eval(expression);
    return `${expression} = ${result}`;
  } catch (error) {
    return '计算错误';
  }
}

支持工具调用的聊天类

kotlin 复制代码
class ToolChat {
  private messages: Message[] = [];
  private openai: OpenAI;
  private tools: any[];

  constructor(apiKey: string, systemPrompt: string, tools: any[]) {
    this.openai = new OpenAI({ apiKey });
    this.tools = tools;
    
    this.messages.push({
      role: 'system',
      content: systemPrompt
    });
  }

  async sendMessage(userInput: string): Promise<string> {
    // 1. 添加用户消息
    this.messages.push({
      role: 'user',
      content: userInput
    });

    // 2. 调用API(包含工具定义)
    const response = await this.openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: this.messages,
      tools: this.tools,
      tool_choice: 'auto'  // 让AI自动决定是否使用工具
    });

    const message = response.choices[0].message;

    // 3. 处理AI的回复
    if (message.tool_calls) {
      // AI想要调用工具
      return await this.handleToolCalls(message);
    } else {
      // 普通回复
      this.messages.push({
        role: 'assistant',
        content: message.content || ''
      });
      return message.content || '';
    }
  }

  private async handleToolCalls(message: any): Promise<string> {
    // 1. 保存AI的工具调用消息
    this.messages.push({
      role: 'assistant',
      content: message.content,
      tool_calls: message.tool_calls
    });

    // 2. 执行每个工具调用
    for (const toolCall of message.tool_calls) {
      const { name, arguments: args } = toolCall.function;
      const parsedArgs = JSON.parse(args);
      
      let result: string;
      
      // 根据工具名称执行对应函数
      switch (name) {
        case 'get_weather':
          result = await getWeather(parsedArgs.city);
          break;
        case 'calculate':
          result = calculate(parsedArgs.expression);
          break;
        default:
          result = '未知工具';
      }

      // 3. 保存工具执行结果
      this.messages.push({
        role: 'tool',
        content: result,
        tool_call_id: toolCall.id
      });
    }

    // 4. 让AI基于工具结果给出最终回复
    const finalResponse = await this.openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: this.messages,
      tools: this.tools
    });

    const finalMessage = finalResponse.choices[0].message.content || '';
    
    this.messages.push({
      role: 'assistant',
      content: finalMessage
    });

    return finalMessage;
  }
}

使用示例

javascript 复制代码
async function toolDemo() {
  const chat = new ToolChat(
    process.env.OPENAI_API_KEY!,
    '你是一个智能助手,可以查询天气和进行计算。',
    tools
  );

  // 测试天气查询
  console.log('用户: 北京今天天气怎么样?');
  const weather = await chat.sendMessage('北京今天天气怎么样?');
  console.log('AI:', weather);

  // 测试计算
  console.log('\n用户: 帮我算一下 15 * 8 + 32');
  const calc = await chat.sendMessage('帮我算一下 15 * 8 + 32');
  console.log('AI:', calc);
}

工具调用的关键要点:

  • AI会自动判断是否需要使用工具
  • 工具调用是异步的,需要等待执行结果
  • 每个工具调用都有唯一的ID用于关联结果
  • AI会基于工具结果给出最终的用户友好回复

第四节:流式响应 - 让AI回复更自然

当AI需要生成长篇回复时,用户不想等待完整答案,而是希望看到实时的"打字"效果。这就需要流式响应。

启用流式响应

javascript 复制代码
class StreamingChat {
  private openai: OpenAI;
  private messages: Message[] = [];

  constructor(apiKey: string, systemPrompt: string) {
    this.openai = new OpenAI({ apiKey });
    this.messages.push({
      role: 'system',
      content: systemPrompt
    });
  }

  async sendMessageStream(userInput: string): Promise<void> {
    // 添加用户消息
    this.messages.push({
      role: 'user',
      content: userInput
    });

    // 启用流式响应
    const stream = await this.openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: this.messages,
      stream: true  // 关键:启用流式响应
    });

    let fullResponse = '';

    // 处理流式数据
    for await (const chunk of stream) {
      const content = chunk.choices[0]?.delta?.content || '';
      if (content) {
        fullResponse += content;
        // 实时输出每个片段
        process.stdout.write(content);
      }
    }

    // 保存完整回复
    this.messages.push({
      role: 'assistant',
      content: fullResponse
    });
  }
}

在Web应用中使用流式响应

javascript 复制代码
// 后端API路由
app.post('/api/chat/stream', async (req, res) => {
  const { message } = req.body;
  
  // 设置SSE响应头
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*'
  });

  try {
    const stream = await openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: [{ role: 'user', content: message }],
      stream: true
    });

    for await (const chunk of stream) {
      const content = chunk.choices[0]?.delta?.content || '';
      if (content) {
        // 发送SSE数据
        res.write(`data: ${JSON.stringify({ content })}\n\n`);
      }
    }

    res.write('data: [DONE]\n\n');
    res.end();
  } catch (error) {
    res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
    res.end();
  }
});// 后端API路由app.post('/api/chat/stream', async(req, res)=>{  const{message}=req.body;    // 设置SSE响应头  res.writeHead(200, {    'Content-Type':'text/event-stream',    'Cache-Control':'no-cache',    'Connection':'keep-alive',    'Access-Control-Allow-Origin':'*'  });  try{    conststream=awaitopenai.chat.    completions.create({      model:'gpt-3.5-turbo',      messages:[{role:'user', content:      message}],      stream:true    });    forawait(constchunkofstream){      constcontent=chunk.choices[0]?.      delta?.content||'';      if(content){        // 发送SSE数据        res.write(`data: ${JSON.stringify        ({content})}\n\n`);      }    }    res.write('data: [DONE]\n\n');    res.end();  }catch(error){    res.write(`data: ${JSON.stringify({    error:error.message})}\n\n`);    res.end();  }});

前端接收流式数据

javascript 复制代码
// 前端JavaScript
function sendStreamMessage(message: string) {
  const eventSource = new EventSource('/api/chat/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ message })
  });

  const responseDiv = document.getElementById('response');
  responseDiv.innerHTML = '';

  eventSource.onmessage = (event) => {
    if (event.data === '[DONE]') {
      eventSource.close();
      return;
    }

    try {
      const data = JSON.parse(event.data);
      if (data.content) {
        responseDiv.innerHTML += data.content;
      }
    } catch (error) {
      console.error('解析数据失败:', error);
    }
  };

  eventSource.onerror = (error) => {
    console.error('流式连接错误:', error);
    eventSource.close();
  };
}

关键要点:

  • 设置stream: true启用流式响应
  • 使用for await循环处理数据流
  • 前端用EventSource接收SSE数据
  • 记得在完整回复后保存到消息历史

总结

通过本文的学习,我们从零开始掌握了在Node.js中调用OpenAI API的核心技能。OpenAI API为我们打开了AI应用开发的大门,但真正的价值在于将这些技术应用到实际场景中,解决真实的问题。现在你已经具备了基础技能,是时候开始构建属于你自己的AI应用了!

记住 :最好的学习方式就是实践。选择一个你感兴趣的场景,动手构建一个AI助手,在实践中不断完善和优化。

相关推荐
r0ad1 小时前
四大主流AI Agent框架选型梳理
llm·agent
智泊AI3 小时前
GPU并行计算是什么?GPU并行计算的原理是什么?
llm
前端老鹰3 小时前
Node.js 日志处理利器:pino 模块全面解析
后端·node.js
yaocheng的ai分身4 小时前
主流大模型的Cache机制对比
llm
JiaLin_Denny4 小时前
Node.js 版本兼容问题:minimatch@10.0.3和minio@7.0.28 冲突的解决
node.js·node安装包冲突
数据智能老司机5 小时前
构建由 LLM 驱动的 Neo4j 应用程序——揭开 RAG 的神秘面纱
langchain·llm·aigc
rogerogers5 小时前
告别 Node 版本混乱!fnm 让你的开发环境丝滑起飞
javascript·node.js
数据智能老司机5 小时前
构建由 LLM 驱动的 Neo4j 应用程序——构建智能应用的知识图谱基础理解
langchain·llm·aigc
数据智能老司机5 小时前
构建由 LLM 驱动的 Neo4j 应用程序——使用电影数据集构建你的Neo4j图数据库
langchain·llm·aigc
NeverSettle_6 小时前
AI SSE 技术文档
前端·node.js