Macaron-V1 连续切三类任务就变差——是后训练覆盖不够还是提示写法问题?

文章导读
连续切三类任务后效果变差,先把归因顺序摆正:多数情况下更值得先查“提示里残留了上一个任务的约束”和“会话历史被带进了下一轮”,再去考虑后训练覆盖不足。区别方法不复杂——用独立会话把三类任务各自单跑一遍,拿到单任务基线;单任务输出正常、连续切换才偏离,优先按提示写法问题处理;单任务本身就复现同样的偏差,才更像覆盖或能力边界问题。下面给的是可重放的现场记录方式、提示清理动作和一段通用调用骨架,不依赖具
📋 目录
  1. 一 复现连续切换后变差的现场
  2. 二 检查提示里是否残留上个任务的格式要求
  3. 三 清空上下文只保留单任务提示再跑
  4. 四 按表现归类落到两种解释上
A A

连续切三类任务后效果变差,先把归因顺序摆正:多数情况下更值得先查“提示里残留了上一个任务的约束”和“会话历史被带进了下一轮”,再去考虑后训练覆盖不足。区别方法不复杂——用独立会话把三类任务各自单跑一遍,拿到单任务基线;单任务输出正常、连续切换才偏离,优先按提示写法问题处理;单任务本身就复现同样的偏差,才更像覆盖或能力边界问题。下面给的是可重放的现场记录方式、提示清理动作和一段通用调用骨架,不依赖具体 SDK。

连续切任务变差别急着归到后训练覆盖。通常先做两件事:查上一轮提示里的格式约束是否残留进下一轮,查会话历史是否把旧任务上下文带了过来。做法是先跑单任务独立会话拿基线,单任务正常而切换后偏离就改提示;单任务也复现同样的偏差,再考虑覆盖不足。判断依据只看日志里能对照的输入输出,不靠主观印象。

复现连续切换后变差的现场

没有稳定可重放的输入序列,后面所有判断都是印象。先固定三件事:任务集合、切换顺序、每轮的埋点。任务各写一条最小输入即可,不要用长业务 prompt 做复现,变量太多。

  1. 把三类任务写成 A、B、C 三条最小提示,每条只保留该任务自己的格式要求和必要示例。
  2. 固定切换顺序,例如 A→B→C→A→B→C,至少来回两轮,让“残留”有机会累积。
  3. 每轮都记:轮次、任务名、完整提示原文(拼接后的最终文本,不是模板)、模型完整输出、生成参数(温度、最大长度、是否带历史)。
  4. 人工标注第几轮开始偏离,偏离指什么写清楚:格式错了、答的是别的任务、内容明显变浅。

日志建议一行一条 JSON,方便后面 diff:

Macaron-V1 连续切三类任务就变差——是后训练覆盖不够还是提示写法问题?
{'round': 4, 'task': 'A', 'history_turns': 3, 'temperature': 0,
 'prompt': '……拼接后的完整提示原文……',
 'output': '……模型原始输出……',
 'deviation': 'json 外壳被上一轮的表格格式覆盖'}

关键点是 temperature 固定为 0 或接近 0,并记录 history_turns。同一份输入换个温度跑出来的差异,会被误当成“切换导致变差”,这是最常见的假现场。

检查提示里是否残留上个任务的格式要求

如果代码里是把多个任务的提示拼成一条消息,或者把上一轮输出回填进下一轮提示,残留几乎必然发生。逐轮比对提示文本,把下面几类仍然生效的旧约束找出来:

  • 格式类:输出 JSON、用表格、分点不超过五条、必须带标题行、字数上限。
  • 角色类:你是一名……、请以专家口吻回答。
  • 示例类:上一个任务留下的 few-shot 样例,被新任务当成输出模板。
  • 历史类:上一轮的问答被原样带入,新任务提示里没有明确说“忽略以上内容”。

清理方式不是加一句“请忽略之前的格式”,那是叠加约束,容易互相打架。更稳的做法是每轮重新构造提示,只保留当前任务的指令,历史要么不带,要么只带与当前任务同类的轮次。清理后保留两个版本对照:旧版(带残留)和新版(只含当前任务约束),同一顺序各跑一遍。

Macaron-V1 连续切三类任务就变差——是后训练覆盖不够还是提示写法问题?
# 清理后的单任务提示结构(示意,替换成你自己的任务描述)
[系统指令] 只按本段要求回答,不使用此前对话中的格式。
[任务] 把下面这段文本改写成一段 100 字以内的说明。
[格式] 纯文本,不要列表,不要标题。
[输入] ……

清空上下文只保留单任务提示再跑

这一步是拿基线,用来把“切换造成的问题”和“任务本身就难”分开。每条任务独立开一次会话,消息列表每轮新建,不复用上一个任务的 messages,响应按任务分文件落盘,文件名带上任务名和轮次。这样单任务结果和切换场景结果可以直接逐条对照。

import json, pathlib, time

def call_model(messages, temperature=0):
    # 换成你自己的调用实现:SDK 或 HTTP 请求都可以
    raise NotImplementedError

TASKS = {
    'A': '任务A的完整提示,只包含A自己的约束',
    'B': '任务B的完整提示,只包含B自己的约束',
    'C': '任务C的完整提示,只包含C自己的约束',
}

out = pathlib.Path('runs'); out.mkdir(exist_ok=True)
for name, prompt in TASKS.items():
    msgs = [{'role': 'user', 'content': prompt}]   # 每轮新建,不拼历史
    resp = call_model(msgs, temperature=0)
    (out / ('single_' + name + '.txt')).write_text(resp, encoding='utf-8')
    time.sleep(1)   # 避免触发频率限制

验证方式很直接:把 single_A 和切换场景第 1 轮的 A 输出并排看,再把 single_A 和切换场景第 4 轮的 A 输出并排看。前一组一致、后一组偏离,说明问题出在轮次累积上;三组都偏离,说明单任务提示本身没写清楚或任务超出现有能力范围。批量逐条跑时注意频率限制,间隔按服务端返回的报错再调整。

Macaron-V1 连续切三类任务就变差——是后训练覆盖不够还是提示写法问题?

按表现归类落到两种解释上

判断顺序建议固定成:先看偏离从第几轮开始,再看同一轮次单任务是否也偏离,最后才看偏离的具体形态。三种常见表现大致可以这样归档,但都需要结合环境确认,不要只凭一次结果下判断。

  • 格式漂移(该出 JSON 出了表格、字数超限、多了标题行):更像提示残留。下一步动作是逐条删掉旧约束,重新构造提示,用同一顺序重跑对照;如果只在带历史时漂移,就改成每轮重建消息列表。
  • 答非所问(回答了上一个任务的内容,或把输入当成了另一个任务的输入):先按提示与上下文残留处理。动作是把历史轮次隔离,每条任务独立会话验证;独立会话下仍答非所问,才去看任务描述里有没有歧义或示例错位。
  • 内容变浅(步骤漏掉、要点变少、不再引用输入细节):这类更接近覆盖或能力边界,但也要先排除提示被前一轮的输出风格带偏。下一步动作是给当前任务补一到两个正例、把长指令拆短,再单独跑;单任务补例后仍无改善,才当作覆盖不足来处理,考虑缩小任务范围或换用更合适的模型。

落到操作上,一次只改一个变量:先清提示、再换独立会话、最后补示例。每改一步都保留上一版日志,否则改完变好了也说不清是哪个动作起的作用。