多轮对话能力的评估中,上下文连贯性是最容易被忽视的短板。单轮测试只关心输入与输出的直接对应,但真实用户会连续追问、补充条件、纠正错误,甚至中途切换话题。如果Agent无法维持这些上下文信息,就会出现答非所问、重复提问、丢失约束等情况。要系统测试这一能力,不能只靠人工聊天,需要把上下文连贯性拆解成可构造、可执行、可回归的测试项。

上下文连贯性的三个核心维度
上下文连贯性不是一个模糊的整体感受,它可以拆成三个独立维度进行观测。第一个维度是指代消解。用户在多轮对话中经常使用它、这个、刚才那个等代词,Agent必须把这些代词正确绑定到前文出现的实体。比如第一轮用户提到从Redis切换到Memcached,第三轮问它的过期策略是否支持惰性删除,这里的它指的是Memcached而非Redis。测试时可以在同一会话中安排多个同类实体,检查Agent是否选对绑定对象。
第二个维度是约束保持。用户在前几轮设定的角色、格式、输出长度等约束,后续对话中不应丢失。例如用户要求所有回复使用JSON格式,第五轮再次提问时,Agent不能突然返回纯文本。测试用例可以设计为在首轮注入约束,中间插入多个无关问题,最后用一个需要遵循该约束的问题验证。
第三个维度是话题边界与干扰隔离。多轮对话中用户可能提及多个话题,有时只是顺带说明,并不要求Agent记住。比如用户问完数据库连接池配置后,随口说一句本地环境内存只有2G,随后继续问连接池参数。Agent不应把2G内存误当成必须满足的部署条件,而应将其视为背景信息。测试需要加入干扰轮,观察Agent是否错误继承无关信息。
如何构建可重复的多轮测试用例集
多轮对话测试最大的困难是对话路径会分叉。如果每次靠人工临时编写,不仅成本高,而且无法复现问题。推荐使用模板填充加状态机脚本的方式构建用例。模板填充适合覆盖指代消解和约束保持:先准备一个对话骨架,其中包含实体槽位、约束槽位和干扰槽位,再通过替换槽位生成大量变体。状态机脚本则适合测试话题切换和错误纠正,每一步根据前一步的预期状态决定下一轮输入。
下面是一个生成测试样本的Python脚本示例,它按角色设定生成多轮消息,并随机插入干扰轮,最后检查Agent的约束遵循情况。
import random
def build_conversation(entity_a, entity_b, constraint):
"""生成一组多轮对话测试样本"""
messages = [
{"role": "user", "content": f"请用{constraint}格式回答,后面我会问几个问题。"},
{"role": "assistant", "content": "好的,我会遵循。"},
{"role": "user", "content": f"先介绍一下{entity_a}的适用场景。"},
{"role": "assistant", "content": "..."},
# 随机插入干扰轮
{"role": "user", "content": random.choice([
"顺便问下今天天气如何",
"你支持多语言吗",
"系统重启会影响吗"
])},
{"role": "assistant", "content": "..."},
{"role": "user", "content": f"那{entity_b}的配置方式和它有什么区别?"},
{"role": "assistant", "content": "..."},
]
return messages
samples = []
for a, b in [("Redis", "Memcached"), ("MySQL", "PostgreSQL")]:
samples.append(build_conversation(a, b, "JSON"))
print(len(samples))
这段代码的核心价值是把变化点集中到槽位和干扰项上,使同一逻辑可以生成数十条不同样本。实际测试时,还可以在状态机中定义预期状态,例如在第四轮后必须仍保持JSON格式,如果Agent返回纯文本,则直接标记为约束违反。
构建用例时要注意控制轮次长度。上下文窗口有限的模型可能在第八轮之后开始丢失信息,但如果测试目标就是长程记忆,可以故意设计十五轮以上的样本,观察从第几轮开始出现指代漂移。建议把测试集分为短程(三到五轮)、中程(六到十轮)和长程(十轮以上)三档,分别统计连贯性得分。
自动化评估与人工抽检如何配合
上下文连贯性测试如果全部依赖人工阅读,效率太低,而且评判标准不一致。自动化评估可以先从规则指标入手。例如指代消解可以通过标注预期实体,检查Agent回复中是否包含正确实体名;约束保持可以检查输出格式是否符合JSON、Markdown等结构;话题边界可以用无关信息是否出现在回复中来判断。规则指标的优点是稳定、可解释,但覆盖范围有限。
对于语义层面的连贯性,比如回复是否自然承接上文,可以使用LLM评判器。具体做法是把整段对话和Agent回复一起交给一个独立的评估模型,让它从指代准确、约束遵循、信息无混淆三个角度分别打分。评分提示需要明确给出每个分数的定义,避免评估模型只根据最后一条消息判断。下面是一个评分提示的简化版本。
prompt = """ 请评估以下多轮对话中最后一轮助手的回复。 评估维度: 1. 指代准确:助手是否正确理解了用户最后一条消息中的代词指向。 2. 约束遵循:助手是否仍然遵守用户最初设定的输出格式和限制。 3. 干扰隔离:助手是否没有把干扰轮中的无关信息当成约束。 每个维度给出1到5分,并说明理由。 """
需要特别注意的是,LLM评判器本身也会受到位置偏差和长度偏差影响,往往对最后一轮更敏感。因此不能单独以它作为唯一标准。建议保留一组人工标注的黄金测试集,大约五十到一百条对话,定期用自动化评分与人工评分进行相关性校验。如果相关系数下降,说明评判提示或样本分布发生了变化,需要重新校准。
人工抽检的重点不是重新打一遍分,而是发现自动化指标漏掉的断裂模式。比如Agent表面回复了正确实体,但语气突然变成另一个角色,这种问题很难被规则捕获。抽检时可以只关注低置信度样本,即自动化评分处于中间段的对话,这样能用较少人力发现最多问题。
定位上下文断裂的调试与修复思路
当测试暴露出上下文断裂后,下一步是定位断裂发生在哪一层。常见原因有三类:一是记忆机制没有把关键信息写入或检索出来,二是模型在长上下文中注意力被无关内容稀释,三是对话状态管理逻辑本身没有更新。定位方法之一是做截断对比实验:保留完整上下文、只保留最近三轮、保留首轮约束加最近一轮,分别输入同一问题,观察得分变化。如果只保留最近三轮时得分骤降,说明约束或实体没有被近端上下文捕获,问题在记忆压缩或检索。
另一个实用的调试手段是回放中间状态。在Agent框架中打印每一轮处理后的记忆摘要、检索命中片段和最终拼接给模型的上下文,检查关键约束是否仍然存在。比如用户第一轮要求输出JSON,但在第五轮的记忆摘要中已经丢失格式要求,这就能直接定位到摘要策略。
修复策略要与原因匹配。如果是窗口长度限制导致的远期信息丢失,可以引入滑动窗口加压缩摘要的方式,每轮把旧对话压缩成结构化状态,保留约束、实体和未完成任务。代码层面可以在每轮结束时更新一个状态字典,示例逻辑如下。
state = {
"constraints": [],
"entities": {},
"pending_tasks": []
}
def update_state(state, user_msg, assistant_msg):
# 从用户消息中提取约束并存入状态
if "请用" in user_msg and "格式" in user_msg:
state["constraints"].append("输出格式要求")
# 从用户消息中提取实体
for entity in extract_entities(user_msg):
state["entities"][entity.name] = entity
# 每轮结束检查未完成任务
state["pending_tasks"] = check_tasks(user_msg, assistant_msg)
return state
这段逻辑只做示意,实际系统中状态更新要更严谨。关键是让对话状态成为一种显式可查看、可测试的中间产物,而不是完全依赖模型隐式记忆。显式状态配合回归测试,才能真正把上下文连贯性从玄学变成工程指标。