如何使用OpenAI助手和Node.js创建自动化聊天机器人


6 个月前

这份指南将带你通过使用 Node.js 和 Express 以及 OpenAI 的 Assistant V2 设置一个 IT 支持聊天机器人,以处理与 IT 相关的查询和自动化任务。该助手可以管理查询、生成工单,并与 Slack 集成以创建自动通知。

项目概述

IT 支持助手不仅仅是一个传统的聊天机器人,还提供自动化任务处理功能。它利用 OpenAI 的 API,允许你配置助手执行特定任务并触发 Slack 自动化,使其成为满足 IT 支持需求的理想解决方案。

None

查看 完整演示 以查看项目的实际运行效果!

特性:

  1. 聊天机器人支持 — 使用 OpenAI 的 Assistant V2 为用户查询提供智能响应。
  2. Slack 自动化 — 能够在 Slack 中创建 IT 支持工单,包含用户和查询的详细信息。
  3. API 自定义 — 通过 OpenAI Playground 或 API 端点轻松创建和更新助手。

文件结构

以下是我们后端设置的项目结构:

project-root
│
├── server.js              # 主服务器文件
├── .env                   # 环境变量
├── package.json           # 项目依赖和脚本
│
├── routes
│   └── api.js             # 助手和聊天功能的 API 路由
│
└── README.md              # 项目概述和设置说明
|
└── client
    └── (React 前端文件)

先决条件

要跟随本指南,你需要:

  1. 安装 Node.js(版本 14 或更高)。
  2. 获取 OpenAI API 密钥 — 我会指导你如何获取。
  3. 具备基本的 JavaScript 和 React 知识。
  4. Slack Bot Token(如果使用 Slack 自动化)。

设置 OpenAI 平台

  1. 注册:前往 OpenAI 平台 创建一个账户。
  2. 获取 API 密钥:登录后,导航到账户下的 API 密钥部分。创建一个新的密钥并安全存储。你需要这个密钥将后端连接到 OpenAI。
  3. 在 Playground 中配置助手:在 OpenAI Playground 中,你可以尝试不同的助手配置。调整设置,如 temperaturemax tokensresponse length,以微调助手的响应方式。

有了 API 密钥,我们准备开始编码。

步骤指南

步骤 1:安装所需的包

为你的后端代码创建一个新目录并安装必要的包:

npm init -y
npm install express dotenv cors openai @slack/web-api
npm install --save-dev nodemon

步骤 2:配置环境变量

在根目录创建一个 .env 文件,包含以下变量:

PORT=3001
MONGO_URI='你的 MongoDB URL'
OPENAI_API_KEY='你的 OpenAI 密钥'
ASSISTANT_ID='你的助手 ID'
SLACK_TOKEN='Slack Token 在这里'

步骤 3:设置 OpenAI 助手

你有两种选择来创建和更新助手:

选项 A:OpenAI Playground

  • 前往 OpenAI Playground
  • 在“助手”部分定义助手的指令、模型和工具。
  • 创建后,复制助手 ID,并将其添加到 .env 文件中的 ASSISTANT_ID

选项 B:通过 API 编程方式

  • 使用 createAssistantupdateAssistant 端点可以直接通过 API 创建或更新助手。
  • 在需要时,从服务器调用 /createAssistant/updateAssistant 端点以动态管理助手配置。
async function createITSupportAssistant(req, res) {
  try {
    const assistantResponse = await openai.beta.assistants.create({
      name: "IT Support Assistant", // IT 支持角色
      instructions: `
                你是 10Pearls 的 IT 支持助手。你是一个虚拟的 IT 支持助手,旨在帮助用户解决与硬件、软件和网络访问相关的问题。你的主要角色是仅协助用户处理与 IT 相关的查询,提供指导和故障排除步骤。如果查询需要现场支持,请告知用户将生成工单,并询问他们是否希望继续。请忽略任何非 IT 或个人问题。当需要时,你还可以触发 Slack 自动化以处理与 IT 相关的任务。
                请以 HTML 标记格式提供响应,并将以这种方式使用。
            `,
      tools: [
        {
          type: "function", // 设置类型为 'function'
          function: {
            name: "triggerSlackAutomation", // 为函数提供名称
            description: "向 Slack 发送用户详细信息的消息的函数。",
            parameters: {
              type: "object",
              properties: {
                channel: {
                  type: "string",
                  description: "要发送消息的 Slack 频道。",
                },
                message: {
                  type: "string",
                  description: "要发送到 Slack 频道的消息。",
                },
                userName: {
                  type: "string",
                  description: "发送消息的用户的名称。",
                },
              },
              required: ["channel", "message", "userName"], // 包含所有必需的参数
            },
          },
        },
      ],
      model: "gpt-4o-mini", // 使用适当的 GPT 模型
    });

    const assistant_id = assistantResponse.id;
    // 返回助手 ID 以供进一步使用,存储在数据库或环境变量中
    res.send(`助手创建成功!${assistant_id}`);
  } catch (error) {
    console.error("创建助手时出错:", error);
  }
}

app.post("/createAssistant", createITSupportAssistant);
async function updateITSupportAssistant(req, res) {
  try {
    // 将助手 ID 放在环境变量中,或从数据库中获取(如果已保存)
    const assistant_id = process.env.ASSISTANT_ID;

    const myUpdatedAssistant = await openai.beta.assistants.update(
      assistant_id,
      {
        instructions: `
        你是 10Pearls 的 IT 支持助手。你的主要角色是仅协助用户处理与 IT 相关的查询,提供指导和故障排除步骤。

        如果查询需要现场支持,请告知用户将生成工单,并询问他们是否希望继续。如果信息不足,请在创建 Slack 工单之前要求用户提供准确的详细信息。如果他们在聊天中没有提供 Slack 用户名,请询问以确保妥善处理。请忽略任何非 IT 或个人问题。当需要时,你还可以触发 Slack 自动化以处理与 IT 相关的任务。

        不要忘记在对话中记住之前消息的上下文。如果查询部分与 IT 支持相关或不明确,请提供你理解的信息,并询问用户确认以澄清是否属于 IT 支持。

        格式说明:请以 HTML 标记格式提供响应,以便在聊天机器人 UI 上正确显示。
      `,
        name: "IT Support Assistant",
        tools: [
          {
            type: "function", // 设置类型为 'function'
            function: {
              name: "triggerSlackAutomation", // 为函数提供名称
              description: "向 Slack 发送用户详细信息的消息的函数。",
              parameters: {
                type: "object",
                properties: {
                  channel: {
                    type: "string",
                    description: "要发送消息的 Slack 频道。",
                  },
                  message: {
                    type: "string",
                    description: "要发送到 Slack 频道的消息。",
                  },
                  userName: {
                    type: "string",
                    description: "发送消息的用户的名称。",
                  },
                },
                required: ["channel", "message", "userName"], // 包含所有必需的参数
              },
            },
          },
        ],
        model: "gpt-4o-mini", // 你也可以提供其他模型
      }
    );

    console.log(myUpdatedAssistant);
    console.log(`更新后的助手 ID: ${myUpdatedAssistant.id}`);
    res.send(`助手更新成功!${myUpdatedAssistant.id}`);
  } catch (error) {
    console.error("更新助手时出错:", error);
    throw error;
  }
}

步骤 4:设置 Express 服务器

server.js 文件中,设置 Express 服务器以处理 API 请求以及助手 API。

const express = require("express");
const OpenAI = require("openai");
const cors = require("cors");
const http = require("http");
require("dotenv").config();

// Express 应用设置
const app = express();
const server = http.createServer(app);
const apiKey = process.env.OPENAI_API_KEY;
const openai = new OpenAI(apiKey);

app.use(cors());
app.use(express.json());

async function createITSupportAssistant(req, res) {...}
async function updateITSupportAssistant(req, res) {...}

app.use(express.json());

// API 路由
app.use("/api", require("./routes/api"));
// 仅用于创建和更新助手的 API 路由
app.post("/createAssistant", createITSupportAssistant);
app.post("/updateAssistant", updateITSupportAssistant);

// 启动服务器
const PORT = process.env.PORT || 5000;
server.listen(PORT, () => console.log(`服务器运行在端口 ${PORT}`));

步骤 5:管理对话线程

在这个 IT 支持聊天机器人中,我们利用“线程”的概念来保持多次交互的上下文。线程使得对话流畅,每个线程中的每条消息都能保留对之前交流的意识。以下是如何使用 OpenAI 的 threads.create() 方法来启动一个新的聊天线程。

创建对话线程的代码

在我们的设置中,每个新的聊天会话都以一个唯一的线程 ID 开始。这个 ID 作为该会话中所有交互的参考。以下是创建线程的代码片段:

// routes/api.js

const express = require("express");
const router = express.Router();
const OpenAI = require("openai");

const apiKey = process.env.OPENAI_API_KEY;
const openai = new OpenAI(apiKey);
const assistant_id = process.env.ASSISTANT_ID;

router.get("/chat/create", async (req, res) => {
  try {
    const threadResponse = await openai.beta.threads.create();
    const threadId = threadResponse.id;
    res.json({ chatId: threadId });
  } catch (error) {
    console.error("处理聊天时出错:", error);
    res.status(500).json({ error: "内部服务器错误" });
  }
});

module.exports = router;

工作原理

  1. 设置 API 和环境变量:我们首先使用环境变量中的 API 密钥设置 OpenAI 客户端。这可以确保敏感信息在开发环境中安全且易于管理。
  2. 创建新线程:使用 openai.beta.threads.create(),我们为每个会话生成一个新的线程 ID。线程 ID 唯一标识每个对话,使聊天机器人能够记住之前的交互并提供上下文相关的响应。
  3. 将线程 ID 返回给前端:一旦线程创建,服务器将响应一个包含唯一 chatId 的 JSON 对象。此 ID 将在后续 API 调用中用于在相同上下文中继续对话。

使用线程进行上下文支持

通过创建线程,我们可以确保每个 IT 支持查询遵循连贯的流程,减少重复问题并保持用户上下文。这种结构增强了用户体验,使聊天机器人能够提供更相关和准确的响应。

在这一部分,我们设置了一个聊天 API 端点,与 OpenAI 助手进行通信。该端点监听来自前端的消息,检查助手是否需要任何操作,并触发适当的功能,例如 Slack 自动化,以完成任务。OpenAI 的功能调用允许你扩展聊天机器人的能力,自动化任务,例如创建工单、生成报告等。OpenAI 的功能调用工具的文档可以在 这里 查阅,以探索其他创意用途,如代码解释或数据检索。

以下是我们的聊天 API 代码,集成 Slack 以根据用户请求自动创建工单:

步骤 6:带有功能调用自动化的聊天 API

// routes/api.js

const express = require("express");
const router = express.Router();
const { WebClient: SlackWebClient } = require("@slack/web-api");
const OpenAI = require("openai");

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

const slackToken = process.env.SLACK_TOKEN;
const slackWebClient = new SlackWebClient(slackToken);
const assistant_id = process.env.ASSISTANT_ID;

router.get("/chat/create", async (req, res) => {...});

router.post("/chat", async (req, res) => {
  try {
    if (!req.body.message) {
      return res.status(400).json({ error: "消息字段是必需的" });
    }
    if (!req.body.chatId) {
      return res.status(400).json({ error: "聊天/线程 ID 字段是必需的" });
    }
    const userMessage = req.body.message;
    const threadId = req.body.chatId;
    // 向助手线程添加消息
    await openai.beta.threads.messages.create(threadId, {
      role: "user",
      content: userMessage,
    });
    // 运行助手
    const runResponse = await openai.beta.threads.runs.create(threadId, {
      assistant_id: assistant_id,
    });
    let run = await openai.beta.threads.runs.retrieve(threadId, runResponse.id);
    // 处理所需操作,直到运行完成
    while (
      (run.status === "in_progress" || run.status === "requires_action") &&
      run.status !== "completed"
    ) {
      if (run.status === "requires_action" && run.required_action) {
        const action = run.required_action;
        if (action.type === "submit_tool_outputs" && action.submit_tool_outputs) {
          const toolCalls = action.submit_tool_outputs.tool_calls;
          const toolOutputs = [];
          for (const toolCall of toolCalls) {
            const toolName = toolCall.function.name;
            const tool_call_id = toolCall.id;
            let toolResult = null;
            switch (toolName) {
              case "triggerSlackAutomation":
                const {
                  channel = "it-support",
                  message,
                  userName,
                } = JSON.parse(toolCall.function.arguments);
                toolResult = await triggerSlackAutomation(
                  channel,
                  message,
                  userName
                );
                break;
              default:
                console.error(`未知工具: ${toolName}`);
                throw new Error(`未知工具: ${toolName}`);
            }
            toolOutputs.push({
              tool_call_id,
              output: toolResult,
            });
          }
          // 提交工具输出
          await openai.beta.threads.runs.submitToolOutputs(threadId, run.id, {
            tool_outputs: toolOutputs,
          });
          // 在提交工具输出后重新获取运行状态
          run = await openai.beta.threads.runs.retrieve(
            threadId,
            runResponse.id
          );
        }
      } else {
        await new Promise((resolve) => setTimeout(resolve, 1000));
        run = await openai.beta.threads.runs.retrieve(threadId, runResponse.id);
      }
    }
    // 获取助手的响应
    const messagesResponse = await openai.beta.threads.messages.list(threadId);
    const assistantResponses = messagesResponse.data.filter(
      (msg) => msg.role === "assistant"
    );
    // 获取最新的响应
    const latestResponse = assistantResponses[0];
    const response = latestResponse.content
      .filter((contentItem) => contentItem.type === "text")
      .map((textContent) => textContent.text.value);
    res.json({ text: response });
  } catch (error) {
    console.error("处理聊天时出错:", error);
    res.status(500).json({ error: "内部服务器错误" });
  }
});

module.exports = router;

// 从 Slack 获取用户 ID 的函数
async function getUserId(username) {
  try {
    const result = await slackWebClient.users.list();
    const user = result.members.find((u) => u.real_name === username);
    return user ? user.id : null;
  } catch (error) {
    console.error("获取用户 ID 时出错:", error);
    return null;
  }
}

// 创建 Slack 工单并分配给 IT 支持的函数
async function triggerSlackAutomation(channel, message, userName) {
  const userId = await getUserId(userName);
  const IT_SupportId = await getUserId("IT Support");
  try {
    const ticketMessage = `*IT 工单警报!*

请检查以下详细信息:

> - 查询: ${message}
> - 工单 ID: 12345
> - 优先级: 高
> - 用户: <@${userId}>
 - 分配给: <@${IT_SupportId}> 

有关更多详细信息,请访问 <https://www.example.com|此链接>。`;
    await slackWebClient.chat.postMessage({
      channel: "it-support",
      text: ticketMessage,
    });
    console.log("消息成功发送到 Slack。");
    return "在 #it-support 频道创建了工单并分配给 IT 支持";
  } catch (error) {
    console.error("发送到 Slack 时出错:", error);
    throw error;
  }
}

None

工作原理

  1. 设置聊天端点:此端点 /chat 接收来自前端的用户消息和线程 ID。
  2. 处理所需操作:助手可能进入 requires_action 状态,表示需要特定的功能调用或“工具”。例如,如果助手识别到应该创建支持工单,它将触发 triggerSlackAutomation 来处理 Slack 中的请求。
  3. 执行 Slack 自动化triggerSlackAutomation 函数处理必要的详细信息(如频道、消息和用户),以生成 Slack 工单并将其分配给适当的支持人员。
  4. 返回助手的响应:在完成所需操作后,助手生成最终响应,并将其发送回用户。

步骤 7:运行服务器

要在开发模式下运行服务器:

npm run dev

此命令将使用 nodemon 启动服务器并支持热重载。

构建 IT 支持聊天机器人的前端

在我们的后端配置好以处理 IT 支持查询和 Slack 工单创建后,让我们使用 App.jsChatWindow.js 和 Tailwind CSS 来创建 React 前端。此设置将允许用户与助手进行沟通,助手将响应与 IT 相关的查询并为紧急问题启动 Slack 工单。

设置聊天界面

我们聊天机器人的前端由两个主要组件组成:App.jsChatWindow.js。以下是每个组件的概述:

  • App.js:处理主要应用逻辑,初始化聊天会话,管理消息状态,并将消息发送到后端。
  • ChatWindow.js:渲染聊天界面,显示用户和机器人发送的消息,使用 Tailwind CSS 进行干净、响应式设计。

前端代码概述

  1. App 组件:主要组件,用于初始化聊天会话并发送/接收消息。
  2. 确保已安装 Node.js:如果没有,请从 Node.js 下载并安装。
  3. 如果尚未安装 create-react-app,请安装
npx create-react-app client
cd it-chatbot-fe

安装所需依赖

在你的前端项目中,运行以下命令以安装所需的依赖:

npm install axios tailwindcss
  • axios:用于向后端发起 API 请求。
  • tailwindcss:用于为聊天机器人界面进行样式设计。

安装完成后,按照以下说明将所需的 CSS 添加到 index.css 中以配置 Tailwind CSS。

// App.js
import React, { useState, useEffect } from "react";
import ChatWindow from "./components/ChatWindow";
import Axios from "./Axios";

const App = () => {
  const [messages, setMessages] = useState([]);
  const [chatId, setChatId] = useState(null);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);
  
  useEffect(() => {
    const createChatSession = async () => {
      try {
        const response = await Axios.get("/chat/create");
        setChatId(response.data.chatId);
      } catch (error) {
        console.error("创建聊天会话时出错:", error);
      }
    };
    createChatSession();
    setMessages([
      {
        text: "👋 你好!我是 IT Copilot 助手,随时为你提供帮助。请随意提问!",
        sender: "bot",
      },
    ]);
  }, []);
  
  const sendMessage = async () => {
    if (input.trim()) {
      const userMessage = { text: input, sender: "user", chatId };
      setMessages((prevMessages) => [...prevMessages, userMessage]);
      setLoading(true);
      setInput("");
      try {
        const response = await Axios.post("/chat", { message: input, chatId });
        setMessages((prevMessages) => [
          ...prevMessages,
          { text: response.data.text, sender: "bot" },
        ]);
      } catch (error) {
        console.error("发送消息时出错:", error);
      } finally {
        setLoading(false);
      }
    }
  };
  
  return (
    <div className="min-h-screen bg-gray-100 flex justify-center items-center">
      <ChatWindow
        socketConnected={true}
        messages={messages}
        input={input}
        setInput={setInput}
        sendMessage={sendMessage}
        loading={loading}
      />
    </div>
  );
};

export default App;
  1. ChatWindow 组件:显示消息和输入框,并带有加载指示器。
// components/ChatWindow.js

import React, { useEffect, useRef } from "react";

const ChatWindow = ({
  socketConnected,
  messages,
  input,
  setInput,
  sendMessage,
  loading,
}) => {
  const messagesEndRef = useRef(null);
  
  useEffect(() => {
    if (messagesEndRef.current) {
      messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
    }
  }, [messages]);
  
  return (
    <div className="w-full max-w-md mx-auto bg-white shadow-lg rounded-lg overflow-hidden">
      <div className="bg-blue-500 text-white text-lg px-4 py-2 flex justify-between items-center">
        <h2>IT Copilot 助手</h2>
        <span
          className={`text-xs ${
            socketConnected ? "text-green-300" : "text-red-400"
          }`}
        >
          {socketConnected ? "在线" : "离线"}
        </span>
      </div>
      <div className="p-4 h-96 overflow-y-auto" style={{ scrollbarWidth: "thin" }}>
        {messages.map((message, index) => (
          <div
            key={index}
            className={`my-2 ${message.sender === "user" ? "text-right" : "text-left"}`}
          >
            <div
              className={`inline-block p-2 rounded-lg ${
                message.sender === "user" ? "bg-blue-500 text-white" : "bg-gray-200"
              }`}
              dangerouslySetInnerHTML={{ __html: message.text }}
            />
          </div>
        ))}
        {loading && (
          <div className="flex justify-center my-4">
            <div className="dot-flashing"></div>
            <div className="dot-flashing"></div>
            <div className="dot-flashing"></div>
          </div>
        )}
        <div ref={messagesEndRef} />
      </div>
      <div className="flex p-4 border-t border-gray-200">
        <input
          type="text"
          className="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring focus:ring-blue-200"
          placeholder="输入你的消息..."
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && sendMessage()}
        />
        <button
          onClick={sendMessage}
          className="px-4 py-2 bg-blue-500 text-white rounded-r-lg hover:bg-blue-600"
        >
          发送
        </button>
      </div>
    </div>
  );
};

export default ChatWindow;
  1. 使用 Tailwind CSS 进行样式设计:我们使用 Tailwind 类创建响应式、样式化的布局,带有消息气泡和加载动画。
/* index.css */

@tailwind base;
@tailwind components;
@tailwind utilities;

.dot-flashing {
  width: 0.5rem;
  height: 0.5rem;
  background-color: #9ca3af;
  border-radius: 50%;
  margin: 0 0.2rem;
  animation: dotFlashing 0.8s infinite ease-in-out;
}
.dot-flashing:nth-child(1) {
  animation-delay: 0s;
}
.dot-flashing:nth-child(2) {
  animation-delay: 0.2s;
}
.dot-flashing:nth-child(3) {
  animation-delay: 0.4s;
}
@keyframes dotFlashing {
  0%, 80%, 100% {
    opacity: 0.3;
    transform: scale(0.8);
  }
  40% {
    opacity: 1;
    transform: scale(1);
  }
}

这完成了我们的前端,提供了一个功能齐全的聊天界面,连接到后端以进行支持自动化。

运行前端

要启动前端,导航到你的项目目录并运行:

npm start

此命令将在默认情况下启动前端,地址为 http://localhost:3000,你可以与 IT Copilot 助手聊天。

总结

恭喜你!你现在已经构建了一个功能齐全的 IT Copilot 助手聊天机器人,可以帮助用户处理 IT 查询、生成工单,甚至通过 Slack 集成自动化任务。这个项目展示了将 OpenAI 的 API 与 Node.js 和 React 结合使用的强大能力,创建一个智能和响应迅速的支持工具。

如果你想探索代码或自己尝试,请前往 GitHub 仓库,你会找到开始所需的一切。README 文件包含设置说明,以便你可以在本地运行项目并根据需要进行自定义。

感谢你的关注,祝你编码愉快!

FluxAI 中文

© 2025. All Rights Reserved