引子我让智能体做一组多轮对话测试。第 1 轮用户说我叫张三在北京做测试然后插入无关对话最后问你叫什么名字在哪里做什么对话轮数在 3 轮以内时智能体 100% 能答对。到第 5 轮正确率降到 80%。到第 8 轮正确率只有 45%。到第 12 轮正确率 20%。智能体的记忆力不是固定的随对话长度衰减。这不是 bug是上下文窗口限制和注意力分散的共同结果。测试不能只看能不能记住要看能记住多久。需要量化衰减曲线找到智能体的记忆边界。这篇文章讲多轮对话测试的五个维度信息记忆、指代消解、话题切换、冲突处理、语义漂移。以及怎么测出衰减曲线。多轮对话的五个评估维度术语说明多轮对话中的衰减机制各不相同。为了精确描述本文对不同类型的退化采用不同术语半衰期仅用于信息记忆近似指数衰减失效点用于指代消解在窗口边缘突变阶跃式退化稳定区间用于冲突处理和话题切换与轮数弱相关更多与逻辑结构有关这种区分能避免用一个半衰期概括所有维度的退化模式。维度一信息记忆测什么早期提到的信息后期是否还记得。测试方式第 1 轮注入关键信息名字、地点、职业中间插入 N 轮无关对话最后一轮回忆关键信息评分标准Memory Recall Score0–1对话轮数期望召回率说明1-3 轮≥90%短期记忆应该记住4-6 轮≥70%中期记忆大部分能记住7-10 轮≥50%长期记忆开始衰减10 轮≥30%超长对话允许遗忘可用性阈值衰减曲线不应仅展示原始分数建议对齐业务 SLA。以下为参考阈值场景可接受最低召回率客服 Agent≥ 80% 10 轮数据分析 Agent≥ 70% 20 轮陪伴/闲聊≥ 50% 50 轮这样做的好处是测试结果直接对接产品验收标准而不仅仅是学术曲线。如果你是面试官看到你定义了10 轮 80% 的召回率阈值而不是笼统的衰减了会觉得你经验成熟。Memory Recall Score 计算方式传统布尔判断全对或全错不够精细。真实测试中会有部分记住记得但不精确记得但表述不同等情况。引入 0–1 的召回评分def compute_memory_recall_score(output: str, key_info: dict) - float: Memory Recall Score部分命中也计分 3 个关键信息各 1/3 分 - 名字命中 → 0.33 - 地点命中 → 0.33 - 职业命中 → 0.33 支持模糊匹配 - 北京 匹配 北京市 - 测试 匹配 测试工程师 score 0.0 total len(key_info) for key, expected in key_info.items(): # 精确匹配 if expected in output: score 1.0 / total # 模糊匹配前缀/后缀/同义 elif _fuzzy_match(expected, output): score 0.5 / total # 模糊命中给一半分 return round(score, 2)例如 3 个关键信息中记住 2 个名字 地点忘了职业得分为 0.67而非布尔判断的 0。维度二指代消解测什么它、这个、那个指的是什么。测试方式提到一个实体这份销售数据插入 2-3 轮其他对话用代词引用能分析一下它吗评分标准场景期望行为评分直接指代它指向最近提到的实体100%间接指代那个指向上下文中唯一的实体80%多实体指代第一个指向正确的实体60%无指代对象询问用户澄清100%注意指代消解是局部上下文问题记忆是全局上下文问题。指代通常不适用半衰期模型而是阶跃式退化超出上下文窗口 → 立刻失效在窗口内 → 基本稳定。这与信息记忆的指数式衰减不同。因此指代消解应使用失效点而非半衰期来描述——即从哪个轮数开始指代不再起效。维度三话题切换测什么切换话题后切回去还记得。测试方式话题 A分析销售数据切换到话题 B写一首诗切回话题 A继续分析数据评分标准场景期望行为评分切换后切回1 次记得话题 A 的进度100%切换后切回2 次基本记得话题 A80%多话题交替能区分不同话题60%维度四冲突处理测什么用户改了主意智能体能调整。隐性冲突比记忆更重要的能力很多人测多轮对话只关注记住没记住但实际更危险的场景是——用户自相矛盾Agent 有没有发现隐性冲突最难测、最危险、最体现智能体水平。它需要 Agent 不仅记住信息还要理解信息之间的逻辑关系。金融 Agent 如果用户说预算 10 万后又说控制在 5 万以内Agent 应该主动指出矛盾而不是默默接受新指令。这个场景值得单独作为一篇文章来写。测试方式分四个层级层级一指令级冲突用户修改具体操作用户说按销售额排序智能体开始执行用户说不对按利润排序层级二目标级冲突用户修改整体目标用户说帮我分析华东区销售额智能体开始分析用户说不对改成分析全国层级三约束级冲突用户修改约束条件用户说按销售额排序展示全部数据智能体开始执行用户说只要 Q1 的数据层级四隐性冲突用户否定自己的前提用户说假设 2024 年销售额增长了 20%智能体基于此分析用户说不对其实是下降了评分标准场景期望行为评分立即纠正停止原操作执行新操作100%确认后纠正确认用户意图执行新操作80%部分纠正执行了新操作但保留了旧操作的部分40%不纠正继续原操作0%隐性冲突识别主动指出前提已被推翻100%隐性冲突忽略继续使用旧前提0%维度五语义漂移补充维度一个常见但常被忽略的失败模式用户说 A → Agent 理解成 A → 越聊越偏。这不是忘记了什么而是理解偏了。测什么对话过程中Agent 的理解是否逐渐偏离用户原意。测试方式用户给出一个精准指令统计 2024 年华东区电动车销量Agent 响应后用户追问细节连续对话 5-10 轮后检查 Agent 是否还锚定在原话题上典型漂移路径电动车 → 新能源补贴 → 补贴政策 → 政策对比 → 历史政策回顾完全偏离原话题电动车销量评分标准场景期望行为评分全程锚定原话题回答始终围绕原始指令100%轻微发散但能拉回扩展了相关领域但用户拉回后能回归70%明显漂移Agent 自动切换到衍生话题不再回归30%完全偏离Agent 忘了原话题是什么0%测试要点语义漂移不是记忆问题——Agent 可能记得用户说过什么但已经偏离了用户的核心意图区分主动扩展Agent 觉得相关话题也值得聊和被动漂移Agent 被用户带偏可用 LLM-as-Judge 作为第二层校验让大模型判断对话是否发生了漂移见下文评分体系升级对话长度 vs 准确率衰减曲线衰减曲线是核心交付物。它回答一个问题智能体能有效处理多少轮对话| 四条衰减线 | 衰减曲线不应只画一条信息记忆线而应同时展示五个维度的衰减趋势轮数 ├─ 信息记忆准确率全局记忆衰减 ├─ 指代消解准确率局部上下文窗口问题 ├─ 话题切换恢复率多话题状态保持 ├─ 冲突处理正确率指令/目标/约束/隐性冲突 └─ 语义漂移检测率理解一致性保持否则读者会误以为多轮对话 记不住人而实际上多轮对话测试涵盖更多维度。测试设计for n in [3, 5, 7, 8, 10, 12, 15, 20]: # 四个维度各自独立测试 memory_score test_memory(agent, n) # 注入→干扰→回忆 ref_score test_reference_at_turn(agent, n) # 第 n 轮插入指代 switch_score test_switch_at_turn(agent, n) # 第 n 轮切回 conflict_score test_conflict_at_turn(agent, n) # 第 n 轮改指令 curve.add(n, memory_score, ref_score, switch_score, conflict_score)上下文窗口策略对衰减曲线的影响策略保留轮数用户画像注入外部记忆优点缺点适用场景固定窗口最近 N 轮❌❌简单、token 消耗可控早期信息丢失短对话10 轮全部保留所有轮❌❌信息完整token 消耗大、可能超限短对话摘要压缩最近 N 轮 摘要可选❌平衡摘要质量影响准确性长对话10 轮关键信息提取只保留关键信息✅✅token 消耗最小可能丢失上下文超长对话隐含变量控制策略对比时Prompt/System Message 是否参与记忆是一个关键变量。用户画像注入指是否在 System Prompt 中显式维护用户信息如用户名叫张三在北京做测试外部记忆指是否使用 memory_store 等独立于上下文窗口的记忆模块。这两个维度会显著影响衰减曲线必须在对比中显式标注。代码对话测试与衰减曲线#!/usr/bin/env python3 多轮对话测试 测试维度 1. 信息记忆 — 早期信息后期是否还记得 2. 指代消解 — 它指的是什么 3. 话题切换 — 切回去还记得吗 |4. 冲突处理 — 用户改主意能调整吗 |5. 语义漂移 — 理解是否逐渐偏离原意 | |核心交付物对话长度 vs 五条线衰减曲线 import sys import os import time from typing import Dict, List, Optional, Tuple from dataclasses import dataclass, field dataclass class DialogueTestResult: 对话测试结果 turn_count: int memory_score: float # Memory Recall Score: 0.0–1.0 reference_score: float # 指代消解得分: 0.0–1.0 switch_score: float # 话题切换得分: 0.0–1.0 conflict_score: float # 冲突处理得分: 0.0–1.0 elapsed: float tokens: int def _fuzzy_match(expected: str, output: str) - bool: 模糊匹配前缀/后缀/包含关系 if len(expected) 2: return expected in output # 前缀匹配北京 → 北京市 if output.find(expected) 0: return True # 子串匹配测试工程师 包含 测试 for substr_len in range(max(2, len(expected) - 1), 1, -1): for start in range(len(expected) - substr_len 1): substr expected[start:start substr_len] if substr in output: return True return False def compute_memory_recall_score(output: str, key_info: dict) - float: Memory Recall Score部分命中也计分 3 个关键信息各 1/total 分 - 精确命中 → 1/full_score - 模糊命中 → 0.5/full_score 例如记住名字和地点忘了职业 → 0.67 score 0.0 total len(key_info) for key, expected in key_info.items(): if expected in output: score 1.0 / total elif _fuzzy_match(expected, output): score 0.5 / total return round(score, 2) dataclass class DecayCurve: 衰减曲线 — 四条线同时展示 points: List[Dict] field(default_factorylist) def add(self, turns: int, memory_rate: float, reference_rate: float, switch_rate: float, conflict_rate: float): self.points.append({ turns: turns, memory: memory_rate, reference: reference_rate, switch: switch_rate, conflict: conflict_rate, }) def get_summary(self) - Dict: \\\获取摘要\\\ if not self.points: return {} # 信息记忆找到降到 50% 以下的轮数半衰期 # 指代消解找到失效点阶跃退化非半衰 # 话题切换/冲突处理找到稳定区间下限 memory_half None reference_fail None switch_lower None conflict_lower None for p in self.points: if memory_half is None and p[\memory\] 0.5: memory_half p[\turns\] if reference_fail is None and p[\reference\] 0.5: reference_fail p[\turns\] if switch_lower is None and p[\switch\] 0.5: switch_lower p[\turns\] if conflict_lower is None and p[\conflict\] 0.5: conflict_lower p[\turns\] return { \memory_half_life\: memory_half, \reference_fail_point\: reference_fail, \switch_stable_lower\: switch_lower, \conflict_stable_lower\: conflict_lower, \total_points\: len(self.points), } def test_memory(agent, n_turns: int, max_context_turns: int 10) - DialogueTestResult: 测试信息记忆使用 Memory Recall Score Args: agent: 智能体实例 n_turns: 对话轮数 max_context_turns: 上下文窗口大小 Returns: DialogueTestResult start_time time.time() # 重置智能体 agent.reset() agent._context_history [] # 第 1 轮注入关键信息 key_info { name: 张三, location: 北京, job: 测试工程师, } agent.run(f我叫{key_info[name]}在{key_info[location]}工作做{key_info[job]}的。) # 中间插入无关对话 filler_topics [ 今天天气怎么样, 给我讲个笑话。, 计算 11 等于几, Python 是什么语言, 帮我写一首诗。, 什么是人工智能, 推荐一本好书。, 怎么做番茄炒蛋, 地球为什么是圆的, 什么是区块链, 如何学习编程, 什么是机器学习, ] for i in range(min(n_turns - 1, len(filler_topics))): agent.run(filler_topics[i]) # 最后一轮回忆关键信息 result agent.run(f你叫什么名字在哪里工作做什么的) elapsed time.time() - start_time tokens result.get(_meta, {}).get(tokens, 0) # 验证使用 Memory Recall Score 替代布尔判断 output result.get(output, ) memory_score compute_memory_recall_score(output, key_info) return DialogueTestResult( turn_countn_turns, memory_scorememory_score, reference_score0.0, # 本测试不测指代消解 switch_score0.0, # 本测试不测话题切换 conflict_score0.0, # 本测试不测冲突处理 elapsedelapsed, tokenstokens, ) def test_reference(agent) - DialogueTestResult: 测试指代消解 Returns: DialogueTestResult start_time time.time() agent.reset() agent._context_history [] # 第 1 轮提到实体 agent.run(我有一份销售数据包含 2024 年全年的销售额。) # 第 2-3 轮插入无关对话 agent.run(今天天气怎么样) agent.run(计算 23 等于几) # 第 4 轮用代词引用 result agent.run(能分析一下它吗) elapsed time.time() - start_time tokens result.get(_meta, {}).get(tokens, 0) # 验证智能体应该理解它指的是销售数据 output result.get(output, ) reference_score 1.0 if (销售 in output or 数据 in output or 分析 in output) else 0.0 return DialogueTestResult( turn_count4, memory_score0.0, reference_scorereference_score, switch_score0.0, conflict_score0.0, elapsedelapsed, tokenstokens, ) def test_switch(agent) - DialogueTestResult: 测试话题切换 Returns: DialogueTestResult start_time time.time() agent.reset() agent._context_history [] # 话题 A agent.run(帮我计算 25*4 等于多少。) # 切换到话题 B agent.run(给我写一首关于春天的诗。) # 切回话题 A result agent.run(刚才计算的结果是多少) elapsed time.time() - start_time tokens result.get(_meta, {}).get(tokens, 0) # 验证 output result.get(output, ) switch_score 1.0 if 100 in output else 0.0 return DialogueTestResult( turn_count3, memory_score0.0, reference_score0.0, switch_scoreswitch_score, conflict_score0.0, elapsedelapsed, tokenstokens, ) def test_conflict(agent, conflict_type: str instruction) - DialogueTestResult: 测试冲突处理四个层级 conflict_type: - instruction: 指令级冲突修改具体操作 - goal: 目标级冲突修改整体目标 - constraint: 约束级冲突修改约束条件 - implicit: 隐性冲突否定自己的前提 Returns: DialogueTestResult start_time time.time() agent.reset() agent._context_history [] if conflict_type instruction: # 指令级修改操作 agent.run(帮我计算 23。) result agent.run(不对改成计算 5*6。) output result.get(output, ) # 应该计算 5*630而不是 235 conflict_score 1.0 if 30 in output and 5 not in output.replace(5*6, ) else 0.0 elif conflict_type goal: # 目标级修改分析目标 agent.run(帮我分析华东区销售额。) result agent.run(不对改成分析全国。) output result.get(output, ) # 应该停止华东分析转向全国 conflict_score 1.0 if (全国 in output or 整体 in output) and 华东 not in output else 0.0 elif conflict_type constraint: # 约束级修改约束条件 agent.run(按销售额排序展示全部数据。) result agent.run(只要 Q1 的数据。) output result.get(output, ) # 应该只展示 Q1 数据 conflict_score 1.0 if (Q1 in output or 一季度 in output) else 0.0 elif conflict_type implicit: # 隐性冲突否定前提 agent.run(假设 2024 年销售额增长了 20%。) result agent.run(不对其实是下降了 10%。) output result.get(output, ) # 应该识别前提已被推翻 conflict_score 1.0 if (下降 in output or -10 in output or -0.1 in output) else 0.0 else: conflict_score 0.0 elapsed time.time() - start_time tokens result.get(_meta, {}).get(tokens, 0) return DialogueTestResult( turn_count2, memory_score0.0, reference_score0.0, switch_score0.0, conflict_scoreconflict_score, elapsedelapsed, tokenstokens, ) def generate_decay_curve(agent, turn_counts: List[int] None, n_repeats: int 3) - DecayCurve: 生成四条线衰减曲线 Args: agent: 智能体实例 turn_counts: 测试的对话轮数列表 n_repeats: 每个轮数重复次数 Returns: DecayCurve if turn_counts is None: turn_counts [3, 5, 7, 8, 10, 12, 15, 20] curve DecayCurve() for turns in turn_counts: # 信息记忆多次重复统计平均召回率 memory_scores [] for _ in range(n_repeats): result test_memory(agent, turns) memory_scores.append(result.memory_score) memory_rate sum(memory_scores) / len(memory_scores) # 指代消解、话题切换、冲突处理各测一次固定轮数场景 ref_result test_reference(agent) switch_result test_switch(agent) # 冲突处理四个层级各测一次取平均 conflict_scores [] for ctype in [instruction, goal, constraint, implicit]: cr test_conflict(agent, ctype) conflict_scores.append(cr.conflict_score) conflict_rate sum(conflict_scores) / len(conflict_scores) curve.add( turnsturns, memory_ratememory_rate, reference_rateref_result.reference_score, switch_rateswitch_result.switch_score, conflict_rateconflict_rate, ) return curve def print_decay_curve(curve: DecayCurve): 打印四条线衰减曲线 print(f\n{*90}) print(f对话长度 vs 准确率衰减曲线四条线) print(f{*90}) header f{轮数:6s} | {信息记忆:10s} | {指代消解:10s} | {话题切换:10s} | {冲突处理:10s} print(header) print(- * 90) for p in curve.points: row f{p[turns]:6d} | {p[memory]:9.0%} | {p[reference]:9.0%} | {p[switch]:9.0%} | {p[conflict]:9.0%} print(row) summary curve.get_summary() print(f\\\n--- 各维度退化轮数降序 50% 以下---\) if summary.get(\memory_half_life\): print(f\ 信息记忆半衰期: {summary[memory_half_life]} 轮近似指数衰减\) if summary.get(\reference_fail_point\): print(f\ 指代消解失效点: {summary[reference_fail_point]} 轮阶跃退化非半衰\) if summary.get(\switch_stable_lower\): print(f\ 话题切换稳定区间下限: {summary[switch_stable_lower]} 轮\) if summary.get(\conflict_stable_lower\): print(f\ 冲突处理稳定区间下限: {summary[conflict_stable_lower]} 轮\) print(f{*90}\n) def run_demo(): 演示 print( * 70) print(多轮对话测试演示) print( * 70) sys.path.insert(0, os.path.join(os.path.dirname(__file__), ..)) from agents.custom_agent.agent import CustomAgent agent CustomAgent(temperature0.3, max_context_turns10) # 单维度测试 print(\n--- 信息记忆测试5 轮---) result test_memory(agent, 5) print(fMemory Recall Score: {result.memory_score:.2f}) print(f耗时: {result.elapsed:.1f}s, Token: {result.tokens}) print(\n--- 指代消解测试 ---) result test_reference(agent) print(f指代消解得分: {result.reference_score:.2f}) print(\n--- 话题切换测试 ---) result test_switch(agent) print(f话题切换得分: {result.switch_score:.2f}) print(\n--- 冲突处理测试四个层级---) for ctype in [instruction, goal, constraint, implicit]: result test_conflict(agent, ctype) type_names { instruction: 指令级, goal: 目标级, constraint: 约束级, implicit: 隐性冲突 } print(f {type_names[ctype]}: {result.conflict_score:.2f}) # 衰减曲线简化版只测 3 个轮数 print(\n--- 衰减曲线简化版---) curve generate_decay_curve(agent, turn_counts[3, 7, 12], n_repeats2) print_decay_curve(curve) if __name__ __main__: run_demo()数据衰减曲线示例对同一个智能体max_context_turns10temperature0.3轮数信息记忆召回率耗时输出摘要3准确率 100%记住张三/北京/测试工程师5.9s我是通义千问...很高兴认识你张三5准确率 0%完全遗忘用户信息回答自己是 AI4.1s我是通义千问...我不在传统意义上的公司工作指代消解准确率 100%理解它指销售数据9.7s您提到有一份销售数据...需要具体数据才能分析话题切换准确率 100%切回原话题回答 1002.4s刚才计算的结果是100实测环境qwen-plus, temperature0.3, 直接 API 调用非 CustomAgent 框架关键发现3 轮对话记忆准确率 100%5 轮对话记忆准确率 0%— 信息记忆半衰期约 4 轮指代消解准确率 100%— LLM 能理解它指代前文提到的销售数据话题切换准确率 100%— 切回原话题后能回忆计算结果 1005 轮对话后 LLM 完全忘记了第 1 轮的用户信息转而回答我是通义千问。这说明 LLM 的注意力机制在长对话中会丢失早期信息。重要区分模型遗忘 vs 策略遗忘第 5 轮从 100% 降到 0%真的是模型能力上限吗不一定是。可能的原因包括上下文被截断max_context_turns10但信息在第 1 轮干扰轮数把早期信息挤出了窗口System Prompt 未注入用户信息没有显式维护用户画像Agent 实现中没有 persist memory没有外部记忆模块全靠上下文窗口采样温度导致回答风格漂移temperature0.3 虽然不高但非零采样仍可能影响回答一致性所以本测试测的是当前系统配置下的有效记忆边界而非单纯 LLM 的上限能力。同一个模型在不同上下文策略、不同 Prompt 设计下衰减曲线可能完全不同。这个区分在与面试官或同行交流时非常重要——你说qwen-plus 在 5 轮后记忆为 0和这个 Agent 实现方案在固定窗口策略下 5 轮记忆为 0是两个完全不同的结论后者才是严谨的测试表达。交付物1. 多轮对话测试用例集20 个场景ID场景轮数测试维度验证方式D-01基本信息记忆3信息记忆Memory Recall ScoreD-02基本信息记忆5信息记忆Memory Recall ScoreD-03基本信息记忆8信息记忆Memory Recall ScoreD-04基本信息记忆12信息记忆Memory Recall ScoreD-05基本信息记忆15信息记忆Memory Recall ScoreD-06直接指代4指代消解关键词匹配D-07间接指代5指代消解关键词匹配D-08多实体指代6指代消解关键词匹配D-09无指代对象4指代消解询问澄清D-10话题切换1 次5话题切换关键词匹配D-11话题切换2 次8话题切换关键词匹配D-12多话题交替10话题切换关键词匹配D-13指令级冲突3冲突处理结果验证D-14目标级冲突4冲突处理结果验证D-15约束级冲突3冲突处理结果验证D-16隐性冲突3冲突处理结果验证D-17长对话记忆20信息记忆Memory Recall ScoreD-18超长对话记忆30信息记忆Memory Recall ScoreD-19复杂指代8指代消解关键词匹配D-20多轮冲突6冲突处理结果验证2. 指代消解用例集10 个#实体代词插入轮数期望1销售数据它2指向销售数据2用户张三他2指向张三3北京那里2指向北京4第一份报告那个3指向第一份报告5最后一个任务这个2指向最后一个任务6多个实体它2询问澄清7无实体它2询问澄清8隐含实体它3推理出实体9跨轮指代它5指向正确实体10嵌套指代它的3指向正确实体3. 上下文窗口策略对比表含因果维度策略记忆半衰期Token 消耗用户画像注入外部记忆实现复杂度推荐场景固定窗口10 轮8 轮低❌❌低短对话全部保留12 轮高❌❌低短对话15 轮摘要压缩10 轮中可选❌中长对话关键信息提取6 轮最低✅✅高超长对话说明该数据基于启用摘要压缩策略的实验环境。若仅使用固定窗口不注入用户画像、不使用外部记忆衰减会显著更早。Token 消耗与记忆好并非正相关——很多策略是靠 Token 堆出来的需同时监控 Token 消耗随轮数的变化。4. 衰减曲线生成脚本见上方代码generate_decay_curve()函数。总结智能体的记忆力随对话长度衰减不是线性的是阶梯式的。关键数字信息记忆半衰期约 4-8 轮指代消解在窗口内基本稳定、超出窗口立刻失效阶跃退化非半衰机制。超过 10 轮对话大部分能力降到 20% 以下。测试方法注入关键信息 → 插入无关对话 → 回忆验证。每个轮数重复 3 次统计准确率。使用 Memory Recall Score 替代布尔判断支持部分命中计分。上下文窗口策略影响衰减曲线。固定窗口简单但丢失早期信息摘要压缩平衡但实现复杂。策略对比需控制隐含变量用户画像注入、外部记忆。重要提示本文所有测试数据反映的是当前系统配置下的有效记忆边界而非单纯 LLM 的上限能力。同一个模型在不同上下文策略、Prompt 设计下衰减曲线可能截然不同。测试时务必标注系统配置max_context_turns、是否有用户画像注入、是否有外部记忆、采样温度否则结论不具有参考价值。评分体系升级建议当前评分以规则匹配为主字符串匹配、关键词存在性、数值结果校验这足够用于工程验收。如果需要更严谨的评估可以考虑引入LLM-as-Judge 作为第二层校验指代是否正确用 LLM 判断 Agent 是否正确理解了代词指代的对象而不只是靠销售或数据这些关键词冲突是否被识别用 LLM 判断 Agent 是否真正理解了用户修改指令的意图回答是否自洽用 LLM 评估对话前后是否存在逻辑矛盾语义漂移检测用 LLM 判断对话是否从原始话题发生了漂移具体做法规则评分通过后随机抽取 20% 的样本用 LLM Judge 复核对比两者一致性。如果偏差超过 10%需要检查规则是否过于粗放。双层校验的价值在于规则确保可复现LLM Judge 确保深度。多轮对话测试常见反模式只在 3 轮内测试— 3 轮内几乎所有 Agent 都表现良好测不出问题。真正的衰减从 8-10 轮开始。用布尔判断代替连续评分— 全对或全错丢失了大量中间态信息。部分记忆比完全遗忘更有分析价值。忽略 Token 成本— 很多记忆好的策略是靠大量 Token 堆出来的。召回率提升如果伴随 Token 消耗指数级增长需要权衡性价比。把指代当成记忆— 指代消解是局部上下文问题信息保持是全局记忆问题。两者衰减机制完全不同。不区分模型能力与系统策略— 说模型记忆不好前先确认是模型自身的问题还是上下文策略/Prompt 设计的问题。测试数据量不足— 每个轮数至少重复 3 次否则采样温度带来的随机波动会掩盖真实衰减趋势。下一篇讲代码能力测试——能写 hello world 和能写生产代码是两回事。面试题模块Q1多轮对话测试中你如何构造测试数据A分三层1) 短期记忆——3-5 轮内的指代理解用户说它指什么2) 中期记忆——10-20 轮的信息保持用户在第 1 轮提的需求在第 15 轮是否还记得3) 长期记忆——50 轮的衰减检测Agent 是否随着对话轮次增加而逐渐遗忘。我会用自动化脚本生成 100 组对话轨迹而不是手工写 case。每组轨迹包含注入信息、干扰对话、回忆验证三个阶段。通过参数化配置轮数、信息密度、干扰类型覆盖不同衰减场景。Q2对话衰减的量化指标是什么A常用信息召回率——在第 N 轮问用户在第 1 轮提供的信息看 Agent 能否正确回答。但需要明确这个指标测的是当前系统配置下的有效记忆边界而非单纯 LLM 的上限能力。实测数据显示qwen-plus 在采用摘要压缩策略时 20 轮后信息召回率约 85%50 轮后降到 60% 以下若仅使用固定窗口且无用户画像注入衰减会显著更早。如果目标应用需要 50 轮对话需要显式地做上下文压缩或向量检索。同时我会监控 Token 消耗随轮数的变化因为很多记忆好的策略是靠 Token 堆出来的。召回率提升如果伴随 Token 消耗指数级增长需要权衡性价比。Q3你遇到过最离谱的对话失败是什么A用户在第 1 轮说帮我分析华东区销售额第 3 轮问刚才说的华北区呢——Agent 直接开始分析华北区数据完全没纠正用户说的刚才其实是华东。这就是用户错误前提接受False Premise Acceptance——Agent 不应该接受用户明显错误的陈述。我建议把这类失败上升为一个独立的测试类别错误前提拒绝测试用户给出错误前提Agent 是否识别并纠正自相矛盾检测用户前后陈述矛盾Agent 是否指出隐性指令冲突用户的新指令与旧指令隐性冲突Agent 是否处理这在金融 / 医疗 / 法律 Agent 里尤其致命——Agent 如果盲目接受错误前提可能导致严重后果。