【AI应用开发实战】纯前端实现带记忆功能的ChatGPT聊天机器人🤖

1,827 阅读5分钟

大家好,我是创业时长五年半的张总,喜欢唱、跳、rap、篮球。

最近一年我们做了几款AI产品,主要是SD绘画AI Chat类产品,所以对于各类AI产品开发略有些心得。接下来我会分享如何从0到1开发各种各样的AI产品,以及关于技术、产品、和运营的一些见解。

本文是 《开发ChatGPT聊天机器人的100种姿势》 系列文章的第二篇,我会用N种方式教你从一个简单对话框写到一个完整的应用,每个姿势都力求舒服。

📚系列文章

系列源码可以在Github中查看:GitHub - miaoihan/100way-to-build-ai: 开发AI应用的100种姿势

🚀 码上掘金

🕶所见即所得

📒正文

因为ChatGPT的API请求是无状态的,因此它并不会记住你们之前的对话。

我们人与人之间对话时,当你预备说出下一句话的时候,你的大脑会从你的记忆体里读取你们之前的对话信息,再通过神经元进行信息交互,让大脑中控制语言的中枢,把句子输出。

那么我们要如何实现记忆功能?

很简单,我们只要把完整的对话记录都发给ChatGPT,就能实现带记忆的对话功能了。

我们在之前18行最简代码的基础之上,给messages加上一轮模拟对话,通过将完整的messages传给ChatGPT,他就能记住你们的对话内容了

👨‍💻‍开始操作

  1. 18行最简代码
<html>
<head>
  <script>
    const headers = new Headers();
    headers.append("Content-Type", "application/json");
    headers.append("Authorization", "Bearer sk-UgtZ6M4qw7Ycfzok0cC1E8C690A04b92B38e381c4c5661D5");
    const body = JSON.stringify({
      model: "gpt-3.5-turbo",
      messages: [{ role: "user", content: "你好" }],
    });
    const requestOptions = { method: 'POST', headers, body };
    fetch("https://ai98.vip/v1/chat/completions", requestOptions)
      .then(response => response.text())
      .then(result => console.log(result))
      .catch(error => console.log('error', error));
  </script>
</head>
</html>
  1. messages: [{ role: "user", content: "你好" }]这一句话,扩展成一段历史对话
const body = JSON.stringify({
    model: "gpt-3.5-turbo",
      messages: [
        { role: "user", content: "你好,我是枫" },
        { role: "assistant", content: "你好,有什么可以帮助你的吗?" },
        { role: "user", content: "还记得我是谁么?" },
      ],
    });

改动对比:

  1. 22行完整代码(最简记忆功能)
<html>
<head>
  <script>
    const headers = new Headers();
    headers.append("Content-Type", "application/json");
    headers.append("Authorization", "Bearer sk-UgtZ6M4qw7Ycfzok0cC1E8C690A04b92B38e381c4c5661D5");
    const body = JSON.stringify({
      model: "gpt-3.5-turbo",
      messages: [
        { role: "user", content: "你好,我叫阿枫" },
        { role: "assistant", content: "你好,有什么可以帮助你的吗?" },
        { role: "user", content: "还记得我叫什么名字么?" },
      ],
    });
    const requestOptions = { method: 'POST', headers, body };
    fetch("https://ai98.vip/v1/chat/completions", requestOptions)
      .then(response => response.text())
      .then(result => console.log(result))
      .catch(error => console.log('error', error));
  </script>
</head>
</html>
  1. 控制台查看结果

如果GPT回复了你的名字,那么恭喜你🎉,你的第一个带记忆功能的AI对话应用也写好了

🔥开始实战

既然已经知道如何实现记忆功能,那么我们就来做一个可以接受用户输入,并保存对话信息的最简AI对话机器人。

🕺开发步骤

  1. 先做产品需求

    1. 有输入框,接受用户输入
    2. 有按钮,可以发送信息
    3. 可以看到用户和AI的对话记录
  2. 然后搞产品原型,长这样👇

  1. 最后,开始开发,GO🤘
  • 涉及到DOM的一些基础知识,不熟悉的可以看资料:DOM 概述 - Web API | MDN
  • 文中代码并不是最优的,除非必要,不会引入任何无关的概念
  • 为了小白读者,注释写的很详细

4. 写好UI

<head>
  <title>Chat Application</title>
  <style>
    /* 略 */
  </style>
</head>
<body>
  <!-- 实现用户输入,点击按钮发送请求,获取响应结果,并显示在页面上 -->
  <div id="conversation-history"></div>
  <input type="text" id="input">
  <button onclick="chat()">chat</button>
</body>

5. 新增chat方法

/**
 * 聊天函数
 * 1. 获取用户输入
 * 2. 将用户输入添加到对话记录中
 * 3. 发送请求到GPT接口
 * 4. 将AI的回复添加到对话记录中
 * 5. 将对话记录显示在页面上
 */
function chat() {
  // 获取用户输入
  const userInput = document.getElementById("input").value;
  // 发送后清空输入框
  document.getElementById("input").value = ''
  // 将用户输入添加到对话记录中
  conversationHistory.push({ role: "user", content: userInput });
  // 更新DOM,将对话信息挂载到页面上
  appendToConversationHistory("user", userInput)
  const requestOptions = {
    method: 'POST', headers,
    body: JSON.stringify({
      model: "gpt-3.5-turbo",
      messages: conversationHistory,
    })
  };
  fetch("https://ai98.vip/v1/chat/completions", requestOptions)
    .then(response => response.json())
    .then(result => {
      // 根据OpenAI返回的数据结构拿值
      const aiResponse = result.choices[0].message.content;
      console.log(conversationHistory);
      // 将AI的回复添加到对话记录中
      conversationHistory.push({ role: "assistant", content: aiResponse });
      appendToConversationHistory("assistant", aiResponse)
    })
    .catch(error => console.log('error', error));
}

6. 新增DOM更新方法appendToConversationHistory

// 更新DOM,将对话信息挂载到页面上
function appendToConversationHistory(role, content) {
  const historyContainer = document.getElementById("conversation-history");
  // 添加css样式
  const messageElement = document.createElement("div");
  messageElement.classList.add(`${role}-message`);
  // 插入消息内容
  const contentElement = document.createElement("span");
  contentElement.textContent = `${role}: ${content}`;
  messageElement.appendChild(contentElement);
  // 挂载消息元素
  historyContainer.appendChild(messageElement);
};

7. 60行完整代码(无注释版)

<html>
<head>
  <title>Chat Application</title>
  <style>
    #conversation-history {
      margin-bottom: 1rem;
      border: 1px solid #ccc;
      padding: 0.5rem;
    }
    .user-message {
      color: #007BFF;
    }
    .assistant-message {
      color: #28A745;
    }
  </style>
</head>
<body>
  <div id="conversation-history"></div>
  <input type="text" id="input">
  <button onclick="chat()">chat</button>
  <script>
    const headers = new Headers();
    headers.append("Content-Type", "application/json");
    headers.append("Authorization", "Bearer sk-UgtZ6M4qw7Ycfzok0cC1E8C690A04b92B38e381c4c5661D5");
    let conversationHistory = [];
    function chat() {
      const userInput = document.getElementById("input").value;
      document.getElementById("input").value = ''
      conversationHistory.push({ role: "user", content: userInput });
      appendToConversationHistory("user", userInput)
      const requestOptions = {
        method: 'POST', headers,
        body: JSON.stringify({
          model: "gpt-3.5-turbo",
          messages: conversationHistory,
        })
      };
      fetch("https://ai98.vip/v1/chat/completions", requestOptions)
        .then(response => response.json())
        .then(result => {
          const aiResponse = result.choices[0].message.content;
          console.log(conversationHistory);
          conversationHistory.push({ role: "assistant", content: aiResponse });
          appendToConversationHistory("assistant", aiResponse)
        })
        .catch(error => console.log('error', error));
    }
    function appendToConversationHistory(role, content) {
      const historyContainer = document.getElementById("conversation-history");
      const messageElement = document.createElement("div");
      messageElement.classList.add(`${role}-message`);
      const contentElement = document.createElement("span");
      contentElement.textContent = `${role}: ${content}`;
      messageElement.appendChild(contentElement);
      historyContainer.appendChild(messageElement);
    };
  </script>
</body>
</html>

8. 最终效果

📖 知识点

  • 最简单实现记忆的方法就是携带全部的对话记录,当然这样随着对话的轮数越来越多,消耗的token也会越来越多

  • 不同的大模型会有不同的token上限,最近比较火的kimi就是因为号称“百万上下文”而出圈

  • 除了传递所有对话历史,我们也可以用其他的策略来节省一些token

    • 总结过往对话记录

      当历史对话记录超过一定token时,我们就把之前的对话记录总结一下,生成一个更简短的summary history取代之前的对话

    • 保留N轮对话记录

      当历史对话记录超过N轮对话时,就把之前的对话“忘记”,这样就能记住最近N次的对话,也能省token

📢下期预告

  • 📜 支持Stream流式对话(打字机效果)

  • 🌀 新增加载中的css动效

效果预览:

Sige

🤘关于我

  • 🎤 唱、跳、rap、篮球: 热爱艺术和运动
  • 💡 全栈开发者:开发上主要是Node.js技术栈,也懂点产品和运营
  • 🚀 连续创业者:公司开了五年半了,目前还没黄

如果你对搞AI搞产品搞开发搞流量有兴趣,那么请多多关注我吧,

干货持续更新中...❤️

参考资料