很多人第一次接入大模型 API 时,都会产生一个错觉:
模型好像“记得”刚才我们说过的话。
等真正做成一个多轮聊天产品,这个错觉很快就会被现实击碎。
大多数大模型的对话接口,本质上都是无状态 API。服务端不会保存你的历史对话,也不会理解“刚才”和“前面”的含义。每一次调用,模型只认你这一次传过去的内容。
多轮对话能成立,完全依赖应用端如何构造提示词上下文。
这篇文章从最基础的上下文拼接讲起,一步步过渡到真实产品中必须面对的工程问题,最后给出一套在生产环境中可落地的完整方案。
如果你是工程师,会知道该怎么写代码。
如果你是产品经理,会明白哪些能力必须在产品层面提前设计。
在对话类 API 中,模型并没有会话状态这个概念。
模型接收到的是一个 messages 数组,其中每一项都只是文本和角色标识。
模型返回的结果,也只是基于这次输入的一次性生成。
多轮对话之所以成立,是因为我们在下一轮请求中,把上一轮的问答再次发给了模型。
从模型视角看,并不存在“第几轮对话”,只有一段越来越长的文本。
在最简单的场景下,多轮对话的实现逻辑非常直接。
每一轮请求时:
把用户的提问追加到 messages
把模型的回答也追加到 messages
下一轮请求时,把整个 messages 再传给模型
示例代码如下,用 Python 展示基本结构。
from openaiimportOpenAIclient = OpenAI(api_key="<DeepSeekAPIKey>",base_url="https://api.deepseek.com")messages = [{"role":"system","content":"You are a helpful assistant."}]# 第一轮messages.append({"role":"user","content":"What's the highest mountain in the world?"})resp = client.chat.completions.create(model="deepseek-chat",messages=messages)messages.append({"role":"assistant","content": resp.choices[0].message["content"]})# 第二轮messages.append({"role":"user","content":"What is the second?"})resp = client.chat.completions.create(model="deepseek-chat",messages=messages)messages.append({"role":"assistant","content": resp.choices[0].message["content"]})
在第二轮请求时,模型真正看到的内容是:
[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":"What's the highest mountain in the world?"},{"role":"assistant","content":"The highest mountain in the world is Mount Everest."},{"role":"user","content":"What is the second?"}]
正是因为第一轮的问答被完整保留,模型才能理解 What is the second 指的是什么。
这个方式在 Demo 阶段完全够用,但在真实产品中问题会迅速暴露。
一旦进入真实使用场景,多轮对话会很快遇到几个不可回避的问题。
模型对一次请求可接收的 token 数是有限的。
对话一旦变长,早期内容就会被截断,模型会突然表现得“失忆”。
用户在一个会话中,很可能会频繁切换话题。
当无关历史被全部拼进提示词,模型的注意力会被严重稀释,回答质量明显下降。
每多传一段历史,调用成本和响应时间都会同步增长。
在高并发场景下,这会直接影响产品可用性。
一个非常典型的场景是:
用户聊 A 话题
中途突然问了一个完全无关的问题
再回到 A 话题继续追问
如果只是顺序拼接历史,模型很容易给出前后不一致的回答。
我们以 Dify 的真实请求为例,看一下多轮对话在工程层面是怎么实现的。
参数中没有 conversation_id,表示新会话。
{"query":"你是一个人吗?","conversation_id":""}
构造出的提示词只有 system prompt 和用户问题。
System:你是一个可以进行多轮对话的客服小姐姐。User:你是一个人吗?
请求中带上 conversation_id 和 parent_message_id。
{"query":"那你是男的还是女的?","conversation_id":"8f8f9ee2-2a7d-4118-8aad-bc5711cabe32"}此时系统会从数据库中取回历史消息,重新构造完整提示词。
System:你是一个可以进行多轮对话的客服小姐姐。User:你是一个人吗?Assistant:哈哈,我不是真人哦……User:那你是男的还是女的?
逻辑完全一致,只是历史更长。
可以看到,多轮对话的核心并不在模型,而在应用端如何重建上下文。
在真实系统中,最难处理的情况并不在于对话变长,而在于话题跳转。
当用户突然问一个和当前上下文无关的问题,再回到原话题继续问时,模型极容易出现以下问题:
忽略早期设定
给出自相矛盾的回答
语气和角色发生漂移
这并不是模型能力不足,而是上下文构造方式不合理。
答案是明确的,会。
原因主要集中在三点:
无关上下文占据了有限的注意力空间
关键历史被截断或被噪声淹没
模型无法判断哪些历史仍然重要
当上下文长度逼近上限时,这种问题会成倍放大。
真正可行的方案,一定发生在应用层,而不是寄希望于模型“自己理解”。
应用端需要完成三件事:
判断当前问题和历史是否相关
只把真正相关的内容交给模型
用更短的形式保留长期记忆
下面是一套在真实产品中已经被大量验证的实现方式。
每一条 user 和 assistant 消息:
存文本
存时间
存 embedding 向量
关联 conversation_id
新问题到来时:
与最近几轮消息计算向量相似度
相似度过低,判定为新话题
优先级顺序建议为:
system prompt
当前话题摘要
与新问题最相关的历史片段
最近几轮完整对话
当前用户问题
当历史过长时:
对早期对话做结构化摘要
用摘要替代原始长文本
摘要本身也参与向量检索
在真正调用模型前:
预估 token 数
超限时优先压缩摘要或移除低相关片段
这件事不只是技术问题。
产品层面至少需要考虑:
会话是否支持话题分段
是否允许用户显式开启新话题
是否支持对话历史回溯
是否提供长对话的记忆说明
这些设计,会直接决定多轮对话在真实用户面前是否可信。
写在最后
多轮对话看起来像模型能力,实质上是应用工程能力。
真正稳定的智能体系统,一定在以下几个地方下过功夫:
上下文选择
话题判断
记忆压缩
成本控制
当你把这些能力补齐后,大模型才会看起来“真的在和用户对话”。
| 欢迎光临 链载Ai (https://www.lianzai.com/) | Powered by Discuz! X3.5 |