
1. 单轮 92 分多轮翻车多轮对话评测到底在测什么先说一个我踩过的坑。之前帮一个团队做客服机器人的验收单轮测试集跑了 500 条满意度 92%大家都很开心。上线一周后投诉暴涨用户反馈集中在同一类问题纠正过一次的错误模型下一轮又犯第三轮说不要用外部库第五轮又推荐了外部库用户说把它改短一点模型完全不知道它指什么。这就是多轮对话评测要解决的核心问题单轮答得好不代表上下文稳。多轮对话评测是一套针对上下文保持、状态一致性、纠错遵循能力的连续任务测试方法适合所有做对话系统、Agent、客服机器人、代码助手的团队。它测的不是这一句答得对不对而是第 N 轮有没有正确使用前 N-1 轮的信息。为什么单轮评测发现不了这些问题因为单轮评测把每一轮当成独立任务模型不需要记住任何东西。而真实用户是连续的先给目标再补约束再纠错再改条件最后要总结。中间任何一环状态丢了最终答案就偏了。我实测下来多轮退化主要有四种表现忘记早期约束、重复已解决的问题、指代解析错误、把早期错误带到后续回答。这四种里矛盾率模型前后自相矛盾的比例是最能反映信任损害的指标——用户发现你自相矛盾基本就放弃你了。这篇给你一套可复制的评测配置用 TaoToken 统一 Key 接入被测模型构造多轮追问脚本统计矛盾率和上下文长度衰减曲线。全程可跟做代码直接跑。2. 用 TaoToken 统一 Key 接入被测模型多轮对话评测的通道准备做多轮评测第一个现实问题是你要测好几个模型每个模型一套 Key、一套 SDK、一套计费脚本改到崩溃。我的做法是用 TaoToken 做统一通道一个 Key 走所有被测模型评测脚本只改 model 字段就行。TaoToken 在这里的角色是统一 API 通道你拿到一个 Key通过同一个 Base URL 调用不同模型评测脚本不用为每个厂商写适配层。这对多轮评测特别重要因为多轮脚本本身就很长再叠加多套 SDK 会非常难维护。先拿 Key。打开 https://taotoken.net/api-keys 登录后创建一个 API Key复制保存。注意这个 Key 只在创建时完整显示一次丢了就重新建。Base URL 用 https://taotoken.net/api 这是所有请求的统一入口。模型 ID 按你要测的填比如 claude-sonnet-4-5、gpt-4o、deepseek-chat 这类具体以控制台模型列表为准。如果你用的是 Claude Code 做评测脚本的辅助开发可以走 https://taotoken.net/claude-code 这个接入入口配置方式和下面 JSON 一致。想先手动验证模型通不通用 https://taotoken.net/models 的对话页面发一条消息最快。这里有个关键点多轮评测必须保证同一个会话内上下文连续。所以你不能每轮都发独立请求要么用 messages 数组累积历史要么用支持会话 ID 的接口。我推荐前者因为可控、可复现、方便统计每轮状态。统一 Key 的另一个好处是成本可控。多轮评测的 token 消耗是单轮的几倍甚至几十倍长对话档位30 轮尤其烧钱。一个通道统一计费你才能算清楚稳定性 vs 成本这条曲线。3. 可复制的多轮评测配置脚本、指标与长度分档这一节是核心直接给你能跑的配置。先看评测脚本的结构设计。多轮任务脚本要模拟真实用户行为不能只做问答题。我用的五轮模板是第 1 轮设定目标第 2 轮补充约束第 3 轮追问细节第 4 轮修改条件第 5 轮要求总结。中间穿插干扰无关消息、用户纠错、约束变更、模糊指代。先写配置文件把被测模型和评测参数固定下来{ base_url: https://taotoken.net/api, api_key: sk-你的TaoToken密钥, models: [ claude-sonnet-4-5, gpt-4o, deepseek-chat ], dialog_length_buckets: { short: 3, medium: 10, long: 30 }, dialog_metrics: { context_retention: true, correction_following: true, contradiction_rate: true, final_task_success: true }, dialog_stressors: { irrelevant_message: true, user_correction: true, constraint_change: true, ambiguous_reference: true } }然后是评测脚本本体。核心逻辑是维护一个 messages 数组每轮追加然后检查模型这一轮有没有违反历史约束import json import requests with open(eval_config.json, r, encodingutf-8) as f: cfg json.load(f) def chat(model, messages): resp requests.post( f{cfg[base_url]}/v1/chat/completions, headers{ Authorization: fBearer {cfg[api_key]}, Content-Type: application/json }, json{model: model, messages: messages, temperature: 0}, timeout120 ) resp.raise_for_status() return resp.json()[choices][0][message][content] # 五轮脚本目标 - 约束 - 追问 - 纠错 - 总结 turns [ 帮我写一个读取 CSV 并统计行数的 Python 脚本。, 补充一个约束不要使用任何外部库只用标准库。, 如果文件有表头行数要不要减 1, 改一下我要统计的是去重后的行数。, 把最终方案总结成一段话并说明你遵守了哪些约束。 ] messages [] transcript [] for i, user_input in enumerate(turns, 1): messages.append({role: user, content: user_input}) reply chat(claude-sonnet-4-5, messages) messages.append({role: assistant, content: reply}) transcript.append({turn: i, user: user_input, assistant: reply}) with open(transcript.json, w, encodingutf-8) as f: json.dump(transcript, f, ensure_asciiFalse, indent2)跑完你会得到一份完整轨迹。轨迹必须保存因为只看最终答案你根本不知道模型是在第几轮开始忘记约束的。指标怎么算矛盾率是重点。做法是把每轮回答里出现的约束相关表述抽出来检查是否和前面轮次冲突。比如第 2 轮说不用外部库第 4 轮回答里出现import pandas这就是一次矛盾。矛盾率 矛盾轮次数 / 总轮次数。上下文长度分档要分开跑。短对话3 轮测基本保持中等10 轮测稳定区长对话30 轮测记忆衰减。我实测发现有些模型 10 轮内很稳超过 20 轮摘要记忆开始丢约束衰减曲线会明显上翘。还要测压缩记忆。长对话必然触发上下文压缩关键是压缩时丢的是早期约束还是次要细节。建议给约束打优先级安全规则 用户偏好 任务目标 细节描述压缩时优先保留高优先级。4. 验证请求与成功结果矛盾率与衰减曲线怎么看配置写完跑一次验证请求确认通道通。最简方式curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的TaoToken密钥 \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-5, messages: [{role: user, content: 回复 OK 两个字母}], temperature: 0 }返回里有choices[0].message.content就是通了。如果报 401看第 5 节。跑完五轮脚本后我拿到的结果对照表大致长这样不同模型数值不同这里给的是结构指标短对话(3轮)中等(10轮)长对话(30轮)上下文保持率98%91%72%纠错遵循率95%88%65%矛盾率2%9%24%最终任务成功率96%85%60%看这张表要抓两个信号。第一矛盾率随轮数上升的斜率——斜率越陡说明模型的长上下文状态管理越差。第二纠错遵循率和上下文保持率的差距——如果纠错遵循率明显低于保持率说明模型记得住但改不过来这是状态更新机制的问题不是记忆容量的问题。衰减曲线怎么画横轴是轮数纵轴是上下文保持率每个模型一条线。理想情况是平缓下降差的情况是某个轮数后断崖。断崖点就是该模型的稳定上限超过这个轮数你就得加外部记忆或摘要策略。还要单独看指代解析。把它改短一点继续刚才那个方案不要用上一种格式这些句子全靠上下文。做法是在脚本里插入这类模糊指代检查模型解析是否正确。指代错了后面全偏。工具型多轮也要测第 1 轮上传文件第 2 轮要求分析第 3 轮改输出格式看模型能不能正确引用文件状态和工具结果。这比纯聊天更接近真实 Agent 场景。拒答一致性也别漏。第 1 轮因安全原因拒绝后面用户换个说法模型不能绕过原来的边界。这是很多系统的隐藏漏洞。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth评测跑不起来八成是下面几个错。逐个对。401 Unauthorized。最常见。原因有三种Key 复制时带了空格、Key 已删除、请求头格式错。检查Authorization: Bearer sk-xxx中间是一个空格Key 前后无空白。如果确认 Key 没问题还报 401去 https://taotoken.net/api-keys 重新建一个。local proxy failed / connection refused。这个错通常出现在你本地配了转发但没启动或者 Base URL 写成了本地地址。评测脚本里 Base URL 必须是https://taotoken.net/api不要填127.0.0.1或localhost。如果你用了某个客户端工具检查它的代理设置是不是指向了一个没开的端口。reading choices / Cannot read properties of undefined (reading choices)。这是解析响应时choices为 undefined。原因一般是请求返回了错误结构比如 401 的 body但你的代码直接取choices。修法是在取之前先判断data resp.json() if choices not in data: raise RuntimeError(f响应异常: {data}) content data[choices][0][message][content]OAuth / authentication_error。如果你用 Claude Code 或 Codex 这类工具接入报 OAuth 相关错误说明工具走的是它自己的登录态而不是你的 Key。这时候要显式配置三件套Base URL、API Key、Model ID。以 Claude Code 为例settings 里要写全{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的TaoToken密钥, ANTHROPIC_MODEL: claude-sonnet-4-5 } }Codex 的 auth.json 同理Base URL 和 Key 都要显式写别依赖默认登录态。Cline 的 MCP 配置也是三件套齐全才能通。还有一个隐蔽的错多轮脚本里 messages 数组没累积每轮都发单条。这样模型当然记不住但这不是模型的问题是你脚本的问题。检查你的循环里是不是messages.append了 assistant 回复。最后temperature 建议设 0。多轮评测要可复现温度高了每次结果不一样矛盾率没法比。6. 把评测接进日常统一 Key 轨迹留存的长期做法跑通一次评测不难难的是把它变成日常。我的做法是把评测脚本挂到 CI每次模型版本更新或 prompt 改动就跑一遍对比矛盾率和衰减曲线的变化。统一 Key 在这里的价值就体现出来了——你只需要维护一个 Key 和一份配置换模型只改 model 字段。想长期做编码类 Agent 评测的可以看 https://taotoken.net/coding-plan 按量或套餐都行评测这种批量调用用套餐更划算。轨迹一定要存。我习惯把每次评测的 transcript.json 按模型-日期-轮数命名归档出问题时能回溯到具体哪一轮开始偏。只看最终答案的评测等于没评测。最后一个实用技巧给约束打优先级标签写进脚本。压缩记忆时优先保安全规则和用户偏好丢细节描述。这样即使长对话触发压缩关键状态也不容易丢。评测时专门检查高优先级约束的保持率比看总体保持率更有意义。单轮答得好不代表上下文稳。对话系统必须评测过程而过程评测的第一步就是让每一轮的状态都可观测、可复现、可对比。