长对话里让模型记住「Version1.0 的代码」或「图片 A」往往会失败。本文解释背后的上下文机制,给出编辑消息开分支、显式重建上下文、把基准放到会话之外、用 API 自管 messages 数组四种做法,并说明图片编号与 seed 为何不可靠。
跟模型聊得久了,几乎都会撞上同一堵墙:前面它给过一版很满意的代码骨架,或者生成过一张风格正好的图,你想着「就以这个为基准,后面都在它上面改」,于是郑重地给它起名 Version1.0 或图片 A。结果又聊了几十轮,你再提这个名字,它要么一脸茫然地重新造一份,要么给你一个似是而非的东西。
这并非模型「不听话」,而是「打个 tag 之后让它自己回去」这个用法本身就和它的工作机制不匹配。下面先把原因说清楚,再给几种实际可用的做法,代码和图片两种情况分开处理。
先搞清楚:它没有记忆,只有上下文
一个必须先建立的认知:模型端并没有保存你的对话历史。每一轮回复,都是把「这次请求里它还能看到的那些文本」重新送进去算出来的。由此推出三件事:
- 上下文有长度上限,对话越长,最早的内容越容易被截断或重要性被稀释;
- 你说的「这是 Version1.0」只是一句普通文本,没有任何特殊地位,轮次一多就被淹没了;
- 模型很会「配合」你:你问它图片编号是多少,它可能真的编一个 gen_id 出来,语气还很笃定。这不代表系统里真有这个编号。
所以真正可行的思路只有两条:要么让那段内容重新出现在上下文里,要么在会话之外把它存下来。指望它自己「记得」,是在赌一个不存在的能力。
做法一:编辑消息,从某个节点开一条分支
网页端每条自己发出的提问旁边都有一个编辑入口(铅笔图标)。点开它,把问题改成新的内容重新提交,会话就会从那个位置分叉出另一条线,原来的分支仍然保留,可以来回切换。
适合的场景:你想从某个节点换一条路重走,比如让它「基于刚才这版代码,改用另一种数据结构重写」。
需要知道的限制:
- 分叉点之后的原有对话不会自动带过去,新分支里它是「没发生过」的;
- 它解决的是「重来」,不是「在保留全部后续讨论的前提下跳回旧版本继续」。
做法二:显式重建上下文(最土,但最可靠)
既然它只有上下文,那就在需要的时候把基准内容重新塞回去。步骤可以固定成一套:
- 新开一个会话,第一条消息就是基准本身——完整代码块、或者那张图重新上传;
- 附上规则,用固定锚点,例如「以下代码块标记为 V1.0,后续所有修改都基于它,每次输出完整文件而不是片段」;
- 一个会话只推进一个目标,别把「改接口」和「调样式」混在一起聊;
- 聊到一定长度就「存档」:让它输出当前完整文件加一份变更摘要,自己保存下来;
- 下一轮用新文件作基准,再开新会话。
关键动作是——不要问「你还记得 V1.0 吗」,直接把 V1.0 贴出来。前者得到的是它的自我评估,后者才是事实。
做法三:把基准放到会话之外
如果这类修改是长期的,基准就不该只活在聊天记录里。
- 本地用版本管理:每次拿到完整文件就提交一次,版本号由你掌控,模型只是「生成器」。这样即使哪天上下文全丢了,你也能从仓库里把它捞回来。
- 规则写进自定义指令:要求它每次输出完整文件、开头注明版本号和本轮改了什么。这条约束比任何口头约定都稳。
- 善用项目或自定义 GPT 的知识文件:把长期不变的规范、模板放进去,省去每次粘贴。但要注意,这类知识通常是按需检索注入的,不保证每次逐字进入上下文,所以最关键的约束仍然要在当轮对话里点名。
图片的情况:编号和 seed 都靠不住
图片比代码更难,因为代码至少能整段复述,图片不能。两个常见误区:
- 让它「给每张图编号」:这个约定可以写进自定义指令里作为辅助,比如要求每张图附带一个序号,但别指望它在长对话里稳定遵守。模型答应得好好的,下一轮就编一个看起来很像的 ID 出来,是常态。
- 要 seed 再复现:多数图像生成接口并不对外提供可用于精确复现的种子参数,具体以官方最新文档为准。把「拿到 seed 就能还原同一张图」当成通用方案,大概率落空。
更稳的做法是:
- 满意的图第一时间保存到本地,别只留在会话里;
- 后续要改,重新上传这张图作为参考,明确说「在它基础上改 X」;
- 优先用平台提供的编辑、局部重绘类功能,而不是靠文字描述去找回那张图;
- 把这张图的风格写成一段可复用的文字描述(构图、光线、色调、材质),和图片一起给,双保险。
做法四:走 API,自己管 messages 数组
如果是写代码解决,最干净的方式是自己维护消息数组,每次请求把该带的历史带上,不需要的裁掉。思路大致如下:
import openai
client = openai.OpenAI()
base = open("version_1_0.py", encoding="utf-8").read()
messages = [
{"role": "system", "content": "以下代码是唯一基准版本 V1.0,所有修改基于它,输出完整文件。"},
{"role": "system", "content": base},
]
def ask(text):
messages.append({"role": "user", "content": text})
resp = client.chat.completions.create(
model="填入当前可用的模型名",
messages=messages,
)
reply = resp.choices[0].message.content
messages.append({"role": "assistant", "content": reply})
return reply
要点:基准代码作为常驻消息永远不裁;只保留最近若干轮对话;发请求前估算一下 token 用量,别等报错才发现超了。这样「回到某个节点」就变成数组操作,精确可控。
如果这类脚本需要长时间挂着跑,可以考虑放在一台常开的机器上,比如整机放在新加坡机房的独享远程 Mac,按月租用、远程连接,避免本地电脑休眠导致进程中断。这只是运行环境的取舍,和上下文问题本身无关。
几种做法的取舍
| 做法 | 适用场景 | 成本 | 可靠性 |
|---|---|---|---|
| 编辑消息开分支 | 想从某节点换路重来 | 最低 | 中,旧分支内容不跟随 |
| 重建上下文 | 单次任务,需要精确基准 | 低,但要动手粘贴 | 高 |
| 基准文件化 | 长期迭代的项目、模板 | 中,需要维护文件 | 最高 |
| API 自管 messages | 批量、自动化、可编程 | 高,要写代码 | 高,取决于你的裁剪逻辑 |
检查清单与常见坑
- 别问「你还记得吗」,改成「这是基准,基于它做 X」;
- 用固定锚点:版本号、文件名、代码块标记,前后保持一致;
- 一次会话一个目标,及时「存档」并另起新会话;
- 不要相信模型自报的编号、记忆状态和内部机制描述;
- 对话越长模型表现越容易下滑,感觉它开始答非所问,往往就是该切会话了;
- 模型能力和产品功能变化很快,涉及具体接口参数、功能入口时,以官方最新说明为准。
小结
「在对话里打 tag 然后回到 tag」这个需求,本质上是要模型做它架构上做不到的事。把它翻译成可执行的动作就是:需要旧内容时,把旧内容重新放回上下文;长期依赖的基准,放到会话之外的仓库或知识文件里;能写代码就用 API 自己管理消息数组。至于图片,保存原图、重新上传、优先用编辑功能,比任何编号约定都靠谱。