大模型时代,OpenAI 已经彻底改变了人机交互的方式。想象一下,如果能创建一个像你一样说话,并且能够讨论你的专业经验和技能的个性化聊天机器人,岂不是非常棒?本文将带你一步步微调 OpenAI 模型,使其能够回答关于你的简历和专业背景的问题,并在 Node.js 应用中使用它。

构建个性化聊天机器人的意义:效率提升与成本控制

在求职过程中,经常会遇到招聘人员反复询问关于经验和技能的常见问题。为了简化这个流程,我希望创建一个聊天机器人,它可以处理初步面试,从而节省双方的时间和精力。这就是构建个性化聊天机器人的初衷,通过微调特定领域知识,能够大大提升信息检索和交流的效率。

训练 OpenAI 模型:数据准备是关键

要构建个性化 AI 聊天机器人,我们需要使用自定义数据微调 OpenAI 模型。虽然这种方法可能会产生一定的成本,但当数据简单而特定时,拥有快速个性化解决方案的好处超过了缺点。

  1. 收集相关数据:从过去的面试或与工作相关的对话中收集与你的专业经验和技能相关的问题和答案。

  2. 格式化数据:将数据结构化为对话格式,具有清晰的问题-答案对。这有助于模型理解上下文并生成适当的响应。例如:

    {
      "messages": [
        { "role": "user", "content": "请简单介绍一下你在xx公司的项目经验?" },
        { "role": "assistant", "content": "在xx公司,我主要负责..." }
      ]
    }
    

    确保数据格式的准确性和一致性,这将直接影响模型的学习效果。

  3. 微调模型:利用 OpenAI 的微调功能,使用你的个性化数据训练模型。监控整个过程,确保模型准确地学习你期望的响应。

准备训练数据:格式规范与内容质量

训练数据的质量是微调成功的关键。不仅要保证数据的格式正确,还要确保数据的质量能够覆盖各种可能的问题和场景,使大模型能够充分学习。

Step 1: 准备训练数据

以下是一些训练数据示例,可以帮助模型优雅地拒绝离题问题,并提供语法正确、内容准确的答案。

  • 示例 1:拒绝离题问题

    {
      "messages": [
        { "role": "user", "content": "你对政治有什么看法?" },
        { "role": "assistant", "content": "抱歉,我只能回答与我们的服务相关的问题。" }
      ]
    }
    
  • 示例 2:纠正不规范输入

    {
      "messages": [
        { "role": "user", "content": "heyyy" },
        { "role": "assistant", "content": "Hey! Hope your day’s going well!" }
      ]
    }
    
  • 示例 3:处理日常问候

    {
      "messages": [
        { "role": "user", "content": "gud morning" },
        { "role": "assistant", "content": "Good morning! How can I help you today?" }
      ]
    }
    
    {
      "messages": [
        { "role": "user", "content": "gud evng" },
        { "role": "assistant", "content": "Good evening! How’s everything going?" }
      ]
    }
    

根据需要构建尽可能多的示例,涵盖你期望的语气、领域或行为。将其保存为 training_data.jsonl 文件。

使用 Node.js 上传训练数据并微调模型

接下来,我们将使用 Node.js 上传准备好的训练数据,并启动 OpenAI 模型的微调过程。

Step 2: 上传训练数据

首先,将你的训练数据上传到 OpenAI。上传后,你将收到一个文件 ID,你需要使用它来启动微调作业。

import "dotenv/config";
import { OpenAI } from "openai";
import fs from "fs";

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

async function uploadFile() {
  try {
    const fileStream = fs.createReadStream("src/ai/data/train-data.jsonl");
    const file = await openai.files.create({
      file: fileStream,
      purpose: "fine-tune",
    });
    console.log("✅ File uploaded:", file.id);
    return file.id;
  } catch (err) {
    console.error("❌ Upload failed:", err);
  }
}

const file_id = await uploadFile(); // Important: Await the promise here
console.log("File ID:", file_id);

请确保在你的 .env 文件中设置了 OPENAI_API_KEY 环境变量。此外,请确保 uploadFile() 函数是 async 并且返回一个 promise,并且你使用 await 来等待 promise 完成,然后再使用 file_id

Step 3: 在 Node.js 中微调模型

async function fineTune(file_id) {
  try {
    const job = await openai.fineTuning.jobs.create({
      training_file: file_id,
      model: "gpt-3.5-turbo", // 要微调的模型
    });
    console.log("Fine-tune started:", job.id);
  } catch (err) {
    console.error("Error:", err);
  }
}

if (file_id) {
    fineTune(file_id);
}

你还可以通过作业 ID 检查训练状态:

async function track(job_id) {
  const events = await openai.fineTuning.jobs.listEvents({ id: job_id });
  console.log(events);
}

完成之后,你将收到你的微调模型 ID,例如:ft:gpt-3.5-turbo:your-org::abc123

恭喜!我们现在有了自定义模型。让我们使用它。

在你的应用中使用微调后的模型:设定行为准则

现在,我们可以在应用程序中使用微调后的模型。在第一个系统消息中,你可以定义任何自定义行为——例如语气、边界或模型应如何响应离题问题。这为你的助手设定了基本规则。

Step 4: 在你的应用中使用微调后的模型

const sessionMessages: Array<{
  role: "system" | "user" | "assistant";
  content: string;
  name?: string;
}> = [
  {
    role: "system",
    content: "你是一位专业且非常友善的全栈开发人员,你的名字是 Ariuka...",
  },
];

export async function chatWithFineTunedModel(userInput: string) {
  sessionMessages.push({ role: "user", content: userInput });
  const response = await openai.chat.completions.create({
    model: process.env.OPENAI_FINE_TUNED_MODEL || "gpt-3.5-turbo",
    messages: sessionMessages,
  });
  const assistantMessage = response.choices[0].message.content ?? "No response provided";
  sessionMessages.push({ role: "assistant", content: assistantMessage });
  return assistantMessage;
}

确保在 .env 文件中设置 OPENAI_FINE_TUNED_MODEL 环境变量,值为微调后的模型 ID。

通过 Embedding 降低 API 使用成本

在创建个性化 AI 的过程中,我发现对于处理常见问题,微调并非总是最有效的解决方案。我发现了一种替代方法,它使用文本 Embedding 和余弦相似度来减少 API 使用并提高性能。以下是我具体的操作:

为了最大限度地减少对 OpenAI API 的依赖,我设计了一个系统来处理常见问题,并在不持续访问微调模型的情况下提供准确的响应。

  1. Embedding 常见问题和答案:我利用 OpenAI 的 text-embedding-ada-002 模型将常见问题及其各自的答案转换为向量表示,称为 Embedding。

    async function getEmbedding(user_query: string): Promise<number[]> {
      const client = new OpenAI();
      const response = await client.embeddings.create({
        model: "text-embedding-ada-002",
        input: user_query,
      });
      return response.data[0].embedding;
    }
    
  2. Embedding 用户查询:接下来,我使用相同的 text-embedding-ada-002 模型将每个用户的查询转换为 Embedding。

  3. 比较余弦相似度:然后,我计算用户查询的 Embedding 与存储的常见问题的 Embedding 之间的余弦相似度。余弦相似度衡量两个向量之间的相似度,范围从 -1(完全不相似)到 1(完全相同)。

    function cosineSimilarity(a: number[], b: number[]): number {
      const dot = a.reduce((sum, ai, idx) => sum + ai * b[idx], 0);
      const normA = Math.sqrt(a.reduce((sum, ai) => sum + ai * ai, 0));
      const normB = Math.sqrt(b.reduce((sum, bi) => sum + bi * bi, 0));
      return dot / (normA * normB);
    }
    
  4. 返回存储的答案:如果余弦相似度得分超过定义的阈值(例如,0.9),我将直接返回与最相似的问题对应的预存储答案,从而绕过微调模型。

    export async function checkSimilarity(userQuestion: string) {
      try {
        const knownEmbeddings = getEmbeddings();
        // Embed known questions
        const userEmbedding = await getEmbedding(userQuestion);
        // Compare
        const similarities = knownEmbeddings.map((item) => ({
          question_id: item?.id || null,
          similarity: cosineSimilarity(userEmbedding, item.embedding),
        }));
        const filtered = similarities.filter((entry) => entry.similarity > 0.9);
        if (filtered.length > 0) return filtered[0].question_id;
        return null;
      } catch (error) {
        console.log(error);
        return null;
      }
    }
    

如果余弦相似度得分超过定义的阈值(例如,0.9),我将直接返回与最相似的问题对应的预存储答案,从而绕过微调模型。

这种方法非常有效地减少了我的 API 使用量,因为它消除了对微调模型的非必要调用来处理常见查询。

余弦相似度阈值的选择:权衡准确率与 API 成本

在利用余弦相似度降低API成本时,选择合适的阈值至关重要。阈值设置过高可能导致许多相似问题无法被识别,从而增加了对微调模型的调用频率,进而增加了API成本。反之,阈值设置过低可能导致不相关的答案被返回,降低用户体验。因此,需要根据实际应用场景和数据特点进行权衡。例如,对于需要高度准确性的应用,可以适当提高阈值;而对于容错率较高的应用,可以适当降低阈值。此外,还可以通过实验评估不同阈值下的准确率和API成本,从而选择最佳阈值。

结论:个性化大模型应用的新方向

通过结合文本 Embedding、余弦相似度和预定义的阈值,我成功创建了一个高效的系统来管理常见问题,同时减少了我对 OpenAI API 的依赖。这种平衡的方法使我能够在保持最佳性能的同时,利用 AI 的强大功能。

通过本文的讲解,你已经了解了如何使用微调和 Embedding 技术构建个性化的聊天机器人。 希望你能动手尝试,构建属于你自己的大模型应用!

如何进一步优化个性化聊天机器人?

  1. 持续优化训练数据: 定期审查和更新训练数据,以确保模型能够适应新的问题和场景。可以收集用户反馈,分析错误回答的原因,并将其添加到训练数据中。

  2. 探索更高级的微调技巧: OpenAI 提供了多种微调选项,例如调整学习率、批量大小等。可以通过实验找到最佳参数组合,以提高模型的性能。

  3. 集成其他 AI 技术: 除了微调和 Embedding,还可以集成其他 AI 技术,例如命名实体识别、情感分析等,以增强聊天机器人的功能和智能化程度。

  4. 构建更完善的用户界面: 设计一个友好的用户界面,使用户可以方便地与聊天机器人进行交互。可以提供问题建议、历史记录查看等功能,提高用户体验。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注