跳到正文

AI Chat 是如何工作的?

通过对话示例,了解 Chat 产品如何把历史消息和新的提问一起发送给模型。

讲完大语言模型如何生成内容后,我们再将视角转移到现在市面上的各种 Chat 产品上,比如 ChatGPT、DeepSeek App 等产品。

当你在使用这些 Chat 产品的时候,可能会有这样的一个疑惑,跟 LLM 聊多了之后,好像 LLM 会记得你说过的话那样,他们是如何记住的呢?

一、为什么模型能记住你?

你可以打开各种 Chat 产品进行如下测试。先给 LLM 下达类似的指令,你可能会获得如下的回答。

我叫 Jimmy。你后续的回答都要以 Jimmy 开头。

Jimmy,好的,我记住了。后续每次回答我都会以“Jimmy”开头。请问你想聊什么?

然后再问:

今天广州的天气如何?

Jimmy,今天广州天气晴朗,温度在 28 摄氏度。

咦?第一次回答带 Jimmy 还比较好理解。为何第二次回答也会以 Jimmy 开头了?

按照前面学到的生成原理,模型会根据收到的输入和训练得到的参数计算概率。如果它只收到「今天广州的天气如何?」这句话,输入里没有我的名字和要求,为什么还会用 Jimmy 开头?

那第二次的回答里 Jimmy 是从哪里来的呢?

二、你的提问是如何被发送给模型的?

JSON 格式是互联网上最通用的数据交换格式,它易于人阅读和编写,也易于机器解析和生成。你现在不需要完全理解 JSON 格式,只需要知道它是一种数据格式即可。

为了解释清楚回答里的 Jimmy 是从哪里来的,我们需要先简单了解下当用户使用 Chat 产品时,消息是如何传递到模型厂商的服务器的。

你可以简单将模型厂商的服务器理解为一台超级计算机,它会接收来自产品的信息,收到产品的信息后,它会启动模型程序,输入消息内容,然后生成回复,再将回复发送到产品,最后产品会将接收到的信息呈现给用户。

整个过程就像是你寄信,但如果你想要得到回信,就需要使用一种很特殊的写信的格式。如果不按照这个格式写,模型厂商就不会给你回信:

用户
Chat 产品
厂商服务器
模型程序未调用
输入消息
自动演示中

我们一步步来看这个过程。

首先当你在输入框里输入内容,比如「我叫 Jimmy。你后续的回答都要以 Jimmy 开头。」,点击发送后,Chat 产品并不会原封不动地将你输入的这段内容发送给模型厂商的服务器,而是会先将其转换为 JSON 格式,接着再发送给模型厂商的服务器,它大概是这样的:

json
{
  "model": "deepseek-flash",
  "messages": [
    {
      "role": "user",
      "content": "我叫 Jimmy。你后续的回答都要以 Jimmy 开头。"
    },
  ]
}

JSON 格式是互联网上最通用的数据交换格式,它易于人阅读和编写,也易于机器解析和生成。你现在不需要完全理解 JSON 格式,只需要知道它是一种数据格式即可。

你可以简单地将这封「信」看成类似填一个申请表。然后这个表格空白状态大概是这样。如果不按照这个方式写信,就不会得到回信:

json
{
  "model": "",   // ← 填写模型名称,比如 deepseek-flash
  "messages": [
    {
      "role": "",   // ← 填写角色,user 表示用户消息,assistant 表示模型回复
      "content": "",   // ← 填写内容,比如 "我叫 Jimmy。你后续的回答都要以 Jimmy 开头。"
    },
  ]
}

我们可以一行行来分析一下这个 JSON 格式。首先,第一行是调用模型的名称,这里我们使用的是 deepseek-flash 模型。在此输入不同的模型,就是告知模型厂商的服务器使用何种模型进行回答。如果换成 deepseek-pro 模型,就是告知模型厂商的服务器,使用何种模型进行回答。

json
{
  "model": "deepseek-flash",
}

第二行是 messages 数组(数组是什么意思,你也不需要深究,只需要知道它是一种数据结构即可),数组中包含所有历史消息。假设是第一次发送消息,那么数组中只有一条消息,并且为了区分这条消息是谁发送的,会用 role 来区分,user 表示用户消息,assistant 表示模型回复:

json
{
  "messages": [
    {
      "role": "user",
      "content": "我叫 Jimmy。你后续的回答都要以 Jimmy 开头。"
    },
  ]
}

产品发送了这条消息后,服务器收到这封信,会将其交给模型程序,程序运行完后,再以类似的「信」发送给产品:

json
{

  "message": {
    "role": "assistant",
    "content": "Jimmy,好的,我记住了。后续每次回答我都会以“Jimmy”开头。请问你想聊什么?"
  }

}

最后产品会从这封「信」里提取必要的信息,然后通过 UI 显示给用户:

Jimmy,好的,我记住了。后续每次回答我都会以“Jimmy”开头。请问你想聊什么?

三、模型实际收到的内容是什么?

了解完你的提问是如何被发送给模型的,我们再来解答一下为何后续的回答里,LLM 仍然会用 Jimmy 开头的原因。

当用户发送第二条消息的时候,Chat 写的信会不一样,它并不会仅仅包含你第二条消息,而是会包含你第一条消息和模型回复:

json
{
  "model": "deepseek-flash",
  "messages": [
    {
      "role": "user", 
      "content": "我叫 Jimmy。你后续的回答都要以 Jimmy 开头。" // 用户发送的第一条消息
    },
    {
      "role": "assistant",
      "content": "Jimmy,好的,我记住了。后续每次回答我都会以“Jimmy”开头。请问你想聊什么?" // 模型回复的第一条消息
    },
    {
      "role": "user",
      "content": "今天广州的天气如何?" // 用户发送的第二条消息
    }
  ]
}

可以看到这封信里包含了历史的消息记录,这就是为什么后续的回答里,LLM 仍然会用 Jimmy 开头的原因。

模型并不是记住了你,而是它每次都收到了你的所有历史消息。

四、搞懂这个概念有什么用?

结合我们在 什么是 Token? 所学,我们知道每个模型都有其最大的上下文窗口(Context Window),比如 DeepSeek-V4.1-Flash 的上下文窗口是 128000 Token。

结合前面提到的 chat 工作原理的讲解,产品通常会把历史消息一并发送,如果聊得越久,上下文就会越长,甚至超过了模型的上下文窗口。

但受窗口限制,实际发送的可能是裁剪后的版本,换句话说,超过的部分就不会被模型收到,就会出现类似模型忘记了的情况。虽然现在市面上的产品也有一些解决方法来缓解这个问题(后续我们会讲),但效果当然是不如将所有上下文完整输入给模型。

所以最好的方法是,一件事就一个会话,而不是一直用一个会话处理所有问题。这样模型出现忘记的可能性就会低一点了。同时费用也会低一些(因为输入的上下文少一些)。

五、成就挑战