免费文章 · 03 让模型用好上下文与记忆
一次请求里实际装了什么
我们刚让助手查过一份资料,紧接着追问,它却答不上来。明明聊天记录还在,为什么还需要重复说明?
要弄清这个问题,我们得先知道:这一轮究竟有哪些信息被交给了模型?
模型在一次调用中实际收到的信息,就是这里所说的上下文。除了我们刚输入的问题,应用规则、历史对话、参考材料、工具说明和工具返回的结果,都可能包含在其中。但这些内容并不会因为存在于应用中,就自动进入模型的输入。
这一节,我们先看看一次请求究竟包含哪些信息,再以《凡人修仙传》第一章中的年龄与排行为例,比较同一句追问在不同上下文中的回答。通过这些对照,我们就能进一步分清:资料是没有提供给模型,还是已经提供,却没有被正确利用。
聊天还在页面上,不等于这次已经提供
假设我们先让助手查询某个版本的发布时间,再追问:“这个版本的负责人是谁?”我们自然知道“这个版本”指的是哪一个,因为前面的问答还显示在页面上。但模型能否理解这句话,取决于第二次请求究竟包含了哪些信息。
这里就需要区分三件事:页面展示的聊天记录、应用保存的历史,以及本次请求实际提供给模型的上下文。
它们并不一定完全相同。页面可能只展示最终回答,应用可能保存更完整的查询过程,而真正发送给模型的只是其中一部分。例如,在普通的 Chat Completions 调用中,历史消息需要由应用明确传入,并不会因为它们还显示在网页上,就自动成为模型的上下文。
从图中可以看到,负责人的信息虽然保存在记录里,但这次发送给模型的只有“负责人是谁?”。缺少前面的版本信息,模型就无法确定我们究竟在问谁。信息被保存下来,不代表这次已经提供。
因此,当助手看起来“忘了”某些信息时,我们可以先检查本次输入。如果缺少资料,就需要补充上下文;如果已经提供,再进一步检查信息的组织方式,以及模型是否正确利用。
一次输入里的信息各有作用
那么,一次请求究竟应该包含哪些信息?还是以版本查询为例,我们按用途看看它们分别承担什么作用。这些分类只是为了方便理解,并不代表每一类都有独立的 API 字段。
信息 | 在本次任务中的作用 | 例子 |
|---|---|---|
应用规则 | 约定回答方式与依据 | 只按提供的记录回答;信息不足就说明 |
当前问题 | 确定现在要完成什么 | 这个版本的负责人是谁 |
历史对话 | 补充此前的要求、决定和相关信息 | 第一轮问的是哪个版本 |
参考材料与环境信息 | 提供当前需要的事实和背景 | 发布说明、当前日期、选中的工作区 |
工具说明 | 告诉模型能使用哪些工具、需要什么参数 |
|
工具结果 | 提供工具实际取得的信息 | 该版本的时间、负责人和备注 |
这里尤其需要区分工具说明和工具结果。知道“可以查询版本记录”,并不意味着已经取得了负责人信息。模型先根据工具说明提出调用请求,应用执行查询后,再把结果交给模型,它才能利用查到的资料回答。
因此,一次用户提问可能需要多次模型调用:第一次决定查询什么,第二次根据取得的资料回答。每次调用收到的上下文都可能不同。 后面使用 Agent 时,工具结果和人的反馈也会以类似的方式参与下一轮判断。
当前日期、所在位置和选中的工作区也是如此。这些环境信息需要由应用提供,或者通过工具取得。助手能够说出这些信息,并不意味着模型天然知道当前环境,我们还需要看信息来自哪里、是否适用于当前任务。
同一句追问,分别保留什么
我们以《凡人修仙传》第一章《山边小村》为例。第一轮让模型查询韩立的年龄,只要求回答年龄。工具读取的原文中,既写着“今年刚十岁”,也写着“他在家里排行老四”,但模型最终只回答了“十岁”。
接下来,我们保持追问“同一段原文中,韩立在家里排行第几?”不变,只改变提供给模型的历史记录。先比较三种条件:模型分别能看到哪些信息?其中有没有明确的排行依据?
保留范围 | 保留的信息 | 有没有排行依据 |
|---|---|---|
仅本轮问题 | 应用规则与当前问题 | 没有提供原文 |
保留聊天文字 | 再加入年龄问题与“十岁”的回答 | 有年龄问答,没有排行原文 |
保留完整历史 | 再加入工具调用及其返回的原文 | 原文包含“他在家里排行老四” |
为了看清历史记录的影响,三组使用相同的模型、生成设置和应用规则,只改变保留的信息。追问时也不提供可用工具,避免模型通过重新查询补齐资料。
这样,我们就能比较:同一句追问,历史记录保留的内容不同,模型实际获得的信息会有什么差别。
图 1 展示了这份课堂实录的三组结果。仅本轮问题的一组说明缺少原文,无法确定排行;保留聊天文字的一组回答“第四”;保留完整历史的一组则直接引用了“他在家里排行老四”。
结合前面的对照条件,我们可以看到:聊天文字组虽然答对了,但本次输入只有年龄问答,并没有排行原文;完整历史组才保留了工具返回的这条依据。因此,检查上下文时,关键是确认当前问题需要的事实,是否真正进入了模型的输入。
为什么只保留聊天文字,也可能答出“第四”?
有些同学运行时,可能会发现“保留聊天文字”这一组也回答了“老四”。《凡人修仙传》广为人知,相关原文、人物介绍和讨论可能出现在模型的训练材料中。因此,即使这次没有提供排行原文,模型也可能凭已有知识答对。
这些在训练过程中学到的知识,通常称为模型的内置知识,以模型参数中的信息为基础。就像我们没有重新翻书,也可能记得熟悉小说中的人物信息,模型不一定每次都需要从当前输入中获取答案。
不过,内置知识并不保证准确。模型在训练时究竟接触过哪些材料,这次回答又依据了什么,都不能仅凭一次回答确定。
因此,答对了,不等于这次请求已经提供了原文。 我们要检查的是当前问题需要的资料有没有真正进入模型的输入。尤其是依据指定原文或当前创作设定完成任务时,不能只看回答是否正确,还要核对模型实际收到了哪些材料。
沿实际请求找到信息落点
点击“保留完整历史”一行的“查看”,就能按消息顺序检查本次输入。先展开“助手工具调用”,确认读取了哪份资料;再查看对应的“工具结果”,找到《山边小村》的原文。
可以看到,韩立的年龄和排行都包含在工具返回的原文中。 而只保留聊天文字时,这段工具结果没有进入下一次输入,留下的“十岁”也无法提供排行依据。
查看工具调用与原文返回的对应关系
工具调用的 id 与工具结果的 tool_call_id 对应,可以用来确认这份结果属于哪次查询。图 3 展示了这组对应字段和返回内容的开头;要核对“他在家里排行老四”,还需要在实验页面中查看工具结果的完整原文。
这里需要注意,assistant 消息除了聊天文字,还可能包含工具调用。如果只保留对话正文,就可能丢失工具取得的信息。
本次三组追问都没有提供可用工具,也没有重新查询。如果首轮回答包含排行信息,聊天文字组同样能获得这条依据,但本次首轮只回答了年龄。
资料进入上下文,不代表模型一定能正确利用。 如果完整工具记录中已有排行依据,模型却仍然答错,就需要进一步检查它是怎样使用这些信息的。
消息还要按模型格式转换成 token
前面我们看到,请求中包含角色、消息内容和工具定义。但模型并不是直接读取整份 JSON,而是需要按照对应的对话模板组织这些信息,加入角色、消息边界等标记,再转换成 token。不同模型使用的模板也可能不同。
因此,请求的 JSON 结构,并不等于模型实际处理的文本序列。应用规则、历史对话、工具说明和返回结果,以及对话格式中的标记,都可能占用 token。温度、输出上限等参数则用于控制生成过程,不能简单地用整份 JSON 的字符数估算上下文长度。要查看实际用量,可以参考服务返回的 usage。
至于业务资料,可以使用 JSON、Markdown 或普通文字来组织。重要的是让字段和信息之间的关系清楚,而不是认为某一种格式总会更好。应用也可以在发送前去掉无关内容,只要保留当前任务需要的事实和条件即可。
用了服务端会话,还需要检查上下文吗?
需要。有些 Responses 接口支持由服务端接续历史,客户端不必每次重新发送完整对话。例如,阿里百炼可以通过 previous_response_id 关联上一轮响应。如果原响应启用了保存,还可以查询关联的历史输入。
因此,检查上下文不能只看客户端这次发送了什么,还要看服务端接续了哪些内容。 历史由谁保存,与模型实际收到了什么,是两个不同的问题。
另外,接续历史也不意味着所有设置都会自动继承。例如,百炼 Responses 不会通过 previous_response_id 自动继承上一轮的 instructions。当前规则、工具和历史如何生效,仍需按照具体接口确认。
上下文窗口要给生成留出空间
整理好资料后,还需要考虑一个问题:一次请求究竟能容纳多少信息?上下文窗口限制了模型一次生成能够处理的 token 总量,既要容纳输入,也要为接下来的回答留出空间。部分模型还会分别限制最大输入和输出长度;思考 token 怎样计入,则取决于具体接口。
例如,假设上下文窗口为 32,000 token,当前输入已经占用 26,000,那么最多还剩 6,000。即使单独的输出上限允许 8,000,也不能因此突破窗口限制。这里的数字只是为了说明容量关系。
随着对话继续,新的消息和工具结果也可能进入下一次输入,让上下文逐渐变长。完整保存记录方便以后查阅,但不意味着每次都要把全部历史交给模型。我们可以根据任务筛选资料、按需读取或进行摘要,关键是保留当前需要的事实、条件及它们之间的关系。后面的课程会进一步比较这些方法。
把检查方法用到新的任务
后面进行小说创作练习时,我们也会遇到类似的问题。即使人物设定和前面的章节都保存在工作台中,也不代表模型在续写时已经获得了这些信息。我们还需要检查,这次请求是否包含了已确认的人物设定、相关前文和新的写作要求。
如果人物设定更新过,就要核对这次采用的是哪个版本;如果提供了“读取设定”的工具,还要确认它有没有被调用、返回了什么。工具具备读取资料的能力,不代表模型已经取得了这些资料。
助手之前读过《凡人修仙传》第一章。现在,应用发起一次新的校对请求,只向模型提供两项内容:
- 人物介绍:“韩立十岁,在家中排行第三。”
- 校对要求:“请根据第一章原文,检查这段介绍是否准确。”
这次请求没有提供第一章原文,也不允许调用工具。这些资料够用吗?需要补充什么,是否要把之前的聊天记录全部传入?
查看答案与解析
资料还不够。虽然助手之前读过第一章,但当时取得的原文并没有进入这次请求。要根据原文校对,就需要补充相关内容。
例如,提供“他在家里排行老四,今年刚十岁”,就能确认年龄正确,排行应改为老四。我们需要的是与本次任务有关的原文,而不是全部聊天记录。
到这里,我们已经学会从实际请求中检查:模型是否拿到了完成任务所需的信息。不过,这里还会延伸出另一个问题:信息已经提供,模型就一定能用好吗?
讨论
登录后参与讨论