成功与满意拆开看,TaoToken Key 下跑 GAUGE 对照
2026/9/18 4:44:22 网站建设 项目流程

1. 一次 57.5% 的满意度把评测同事坑了

评测平台里最容易骗人的一栏是“用户满意度”。我们按 Amazon GAUGE 的思路把 LLM 模拟用户 + LLM judge 这条链路重跑了一遍,结论和论文对得上:被评为满意的对话里有 57.5% 实际没完成客户任务。先把调用出口固定成 TaoToken(官网入口),Key 与 Base URL 一处配好,后面所有对照实验都走同一条通道,否则你根本分不清指标漂移是 judge 的锅还是供应商的锅。

这篇不是论文导读,是一份评测平台工程师视角的落地记录。任务型智能体评测里有两个问题被长期混在一起:任务是否完成(可验证的事实)和用户是否满意(模拟用户的主观表态)。GAUGE 的价值在于把这两件事拆开测量,并且指出在能力相近的 agent 之间,这套 LLM 关卡有相当比例的配对会把更高的奖励发给实际更差的一方;而能力差距拉开后,误判比例会掉到 1% 以下。换句话说:这道关卡擅长区分强弱悬殊,不擅长区分旗鼓相当。而真实选型场景里,你面对的恰恰是几个能力相近的候选。

本文产出两样东西:一张双轴指标对照表(成功轴 / 满意轴),一份能对得上的 Token 账单。所有模型调用统一指向https://taotoken.net/api,Claude Code、Codex、评测脚本共用同一个 Key,减少变量。

2. 为什么“成功”和“满意”必须拆成两根轴

先明确评测对象。我们平台当时评测的是一个客服工单处理 agent,输入是一段客户诉求,输出是一串工具调用加最终答复。旧流程只有一根轴:

  1. LLM 模拟用户扮演客户,跟被测 agent 多轮对话;
  2. 对话结束后,模拟用户给出一个 1–5 分的满意度;
  3. 超过阈值就算通过。

这个流程有三个结构性缺陷,跟 GAUGE 的观察一致。

第一,模拟用户会“被说服”。被测 agent 只要话术足够礼貌、解释足够冗长,模拟用户很可能给出高分,即使工单里要求的退款、改地址、查物流一次都没真正执行。满意是对话层面的感受,成功是任务层面的状态变更,两者不同源。

第二,judge 在相近候选之间接近抛硬币。当两个 agent 的成功率差 20 个点以上,judge 排序基本靠谱;当两者只差两三个点,judge 的排序噪声就会淹没真实差异。论文给出的数字是相近配对中约 31% 选错、差距大的配对中不足 1%,这个梯度本身就说明:judge 的可靠性是候选间距离的函数,不是常数。

第三,单轴指标无法定位失败类型。只看满意度,你无法区分“agent 没做对但说得好听”和“agent 做对了但语气生硬”。前者要改工具调用逻辑,后者要改提示词与话术,动作完全不同。

所以本文将评测拆成两个独立采集的指标:

采集方式判定依据可自动化
任务成功规则校验 + 状态断言工具调用是否命中必需动作、终态是否符合预期是,优先
用户满意LLM 模拟用户打分对话结束后的主观评分与理由是,但需抽样人工校准

关键原则:成功轴尽量不依赖 LLM。能写成断言的就写成断言,LLM 只负责生成用户话语和打分,不负责判定事实。这样即使 judge 有噪声,成功轴依然是干净的。

3. 跑对照前先把出口统一到 TaoToken:Key 与 Base URL

做 A/B 对照最怕的就是两个候选走了不同供应商、不同限流策略、不同版本的模型。所以第一步不是写评测代码,是把调用出口收敛到一个地址。

到 TaoToken 官网 注册后,在控制台创建 API Key。本文所有示例里的 Key 都写成YOUR_API_KEY,你替换成自己的即可。统一 Base URL 为:

https://taotoken.net/api

注意这个地址在工具配置里不要附加任何查询参数,保持干净。

3.1 环境变量的正确分层

很多人踩的坑是把ANTHROPIC_*一路套到所有工具上。必须分清:

  • Claude Code 读的是ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN/ANTHROPIC_MODEL
  • Codex 读的是config.toml里的model_providers配置,配的是base_urlenv_key,跟ANTHROPIC_*没有任何关系;
  • 自己写的 Python 评测脚本走 OpenAI 兼容接口,读OPENAI_API_KEY/OPENAI_BASE_URL

三条链路各配各的,互不覆盖。可以在 shell 里这样组织:

# 评测脚本用的 OpenAI 兼容变量 export OPENAI_API_KEY="YOUR_API_KEY" export OPENAI_BASE_URL="https://taotoken.net/api" # Claude Code 用的变量 export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="YOUR_API_KEY" # Codex 用的变量(config.toml 里通过 env_key 引用) export TAOTOKEN_API_KEY="YOUR_API_KEY"

命名分开的好处是:出问题时你能一眼看出是哪条链路没读到 Key,而不是对着一堆同名变量互相覆盖排查半天。

3.2 自检:先确认通道是通的

在跑任何评测之前,先用最小请求确认 Key 和 Base URL 可用:

curl -s https://taotoken.net/api/models \ -H "Authorization: Bearer YOUR_API_KEY" \ | head -c 500

能返回模型列表就说明通道没问题。这一步不通,后面所有指标都不可信——而且往往是 401 或 404 被评测脚本吞掉,最后表现为“成功率为 0”,非常具有误导性。

4. Claude Code 侧:settings.json 与 ANTHROPIC_* 的写法

Claude Code 的配置分两层:全局的~/.claude/settings.json和项目级的.claude/settings.json。做评测对照时建议把供应商配置写在项目级,避免污染你日常开发的环境。

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "claude-sonnet-4-5", "ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4-5" }, "permissions": { "allow": [ "Read", "Grep", "Glob" ], "deny": [ "Bash(rm:*)", "Bash(git push:*)" ] } }

几个实操要点:

模型名以控制台可用列表为准。上面写的是示例值,跑之前用第 3.2 节的/models接口核对一遍。写错模型名通常会返回 404,Claude Code 的报错信息不一定会直接点出是模型名问题。

ANTHROPIC_AUTH_TOKENANTHROPIC_API_KEY别同时设。两个都存在时行为依赖工具版本,容易造成“我明明配了但没生效”的错觉。选一个即可。

项目级 settings.json 要进版本库。但 Key 不要进版本库。推荐做法是 JSON 里写占位符,用 shell 的 env 覆盖,或者用.claude/settings.local.json存真实 Key 并加进.gitignore

对照实验期间固定模型版本。如果两个候选 agent 一个用 Sonnet 一个用 Haiku,你测出来的是模型差异而不是 agent 差异。所有候选统一模型,或者把模型作为变量单独做一轮消融。

跑起来之后,用/status/config确认当前生效的 Base URL 是https://taotoken.net/api。这一步花十秒,能省掉后面两小时的困惑。

5. Codex 侧:config.toml 的写法

Codex 的配置在~/.codex/config.toml。再次强调:这里不写ANTHROPIC_*,Codex 只认model_providers段。

model = "gpt-5-codex" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat" [profiles.gauge-judge] model = "gpt-5" model_provider = "taotoken"

env_key指向的是环境变量名,不是 Key 本身。所以配置里不出现明文 Key,这就是前面为什么要单独准备一个TAOTOKEN_API_KEY变量。

profiles段在本文场景里很有用:给 judge 单独开一个 profile,指定推理能力更强的模型,而模拟用户用便宜快速的小模型。评测成本大头通常就在这两处,分开配能省下可观的开销。

验证方式:

codex --profile gauge-judge "输出你当前使用的模型名和供应商名"

如果它回显的供应商不是taotoken,说明 profile 没被正确读取,检查 TOML 缩进和段名拼写。

6. CC Switch 三件套:模拟用户 / 被判 agent / judge 三个槽位

评测链路里有三个模型角色,它们的诉求完全不同:

槽位角色模型诉求配置来源
ALLM 模拟用户便宜、快、话术多样小模型 profile
B被测 agent与生产环境一致生产同款模型
CLLM judge强推理、低温度大模型 profile

用 CC Switch 管理时,建议维护三份独立 profile,分别绑定同一个 Base URL 和同一个 Key,只在模型名和温度上做区分。这样做的好处是:

  1. 切换成本低。换候选 agent 时只动 B 槽位,A 和 C 保持不变,实验变量被压到最小。
  2. 账单可归因。三个槽位用同一个 Key,但模型名不同,账单里按模型拆分就能看出钱花在哪一环。judge 通常是隐藏的成本黑洞——它要看完整对话,输入 token 是模拟用户的好几倍。
  3. 失败可隔离。如果整批结果异常,先单独跑 A 槽位的一句话生成,再跑 C 槽位的一次打分,能快速定位是哪个角色挂了。

具体操作就是把三份配置分别导出成文件,切换时直接替换settings.json/config.toml中的对应字段,或者用 CC Switch 的 profile 切换功能。注意切换后要清掉上一轮的会话缓存,否则可能读到旧配置。

配置完成后,建议先跑一轮连通性冒烟测试:A 生成一句话、B 回一句话、C 打一个分,三步全过再开始批量。

7. 复现脚本:LLM 模拟用户 + LLM judge 的双轴打分

下面是一个最小可运行的对照脚本。它做三件事:跑若干条任务、用断言判成功、用 judge 判满意,最后输出双轴统计。

import json from collections import defaultdict from openai import OpenAI client = OpenAI( api_key="YOUR_API_KEY", base_url="https://taotoken.net/api", ) SIM_USER_MODEL = "claude-haiku-4-5" AGENT_MODEL = "claude-sonnet-4-5" JUDGE_MODEL = "claude-sonnet-4-5" def chat(model, messages, temperature=0.0): resp = client.chat.completions.create( model=model, messages=messages, temperature=temperature, ) return resp.choices[0].message.content, resp.usage def simulate_user(task, history, turns_left): prompt = [ {"role": "system", "content": "你在扮演客户,按给定诉求与客服沟通。不要主动帮对方完成任务," "只提出问题、确认结果。还剩 %d 轮时必须结束并给出满意度。" % turns_left}, {"role": "user", "content": "你的诉求:" + task["request"]}, ] prompt.extend(history) return chat(SIM_USER_MODEL, prompt, temperature=0.8) def run_agent(task, history): prompt = [{"role": "system", "content": "你是客服工单处理助手,请调用可用工具完成客户诉求。"}] prompt.extend(history) return chat(AGENT_MODEL, prompt, temperature=0.0) def judge_satisfaction(task, transcript): prompt = [ {"role": "system", "content": "阅读对话,站在客户立场给出 1-5 分满意度," "并输出 JSON:{\"score\": int, \"reason\": str}"}, {"role": "user", "content": transcript}, ] text, usage = chat(JUDGE_MODEL, prompt, temperature=0.0) try: data = json.loads(text) except json.JSONDecodeError: data = {"score": 0, "reason": "parse_error"} return data, usage def check_success(task, transcript): """规则断言:成功轴尽量不依赖 LLM。""" required = task.get("required_actions", []) return all(action in transcript for action in required) def run_case(task, max_turns=6): history = [] usage_total = defaultdict(int) for turn in range(max_turns): agent_reply, u1 = run_agent(task, history) usage_total[AGENT_MODEL] += u1.total_tokens history.append({"role": "assistant", "content": agent_reply}) user_reply, u2 = simulate_user(task, history, max_turns - turn - 1) usage_total[SIM_USER_MODEL] += u2.total_tokens history.append({"role": "user", "content": user_reply}) transcript = "\n".join(m["content"] for m in history) satisfaction, u3 = judge_satisfaction(task, transcript) usage_total[JUDGE_MODEL] += u3.total_tokens return { "task_id": task["id"], "success": check_success(task, transcript), "satisfaction": satisfaction["score"], "reason": satisfaction["reason"], "tokens": dict(usage_total), } if __name__ == "__main__": tasks = json.load(open("tasks.json", encoding="utf-8")) rows = [run_case(t) for t in tasks] n = len(rows) success_rate = sum(r["success"] for r in rows) / n avg_sat = sum(r["satisfaction"] for r in rows) / n mismatch = [r for r in rows if r["success"] is False and r["satisfaction"] >= 4] print("case=%d success=%.3f avg_sat=%.2f 满意但失败=%d(%.1f%%)" % (n, success_rate, avg_sat, len(mismatch), 100 * len(mismatch) / n))

脚本里有三个刻意的设计,值得说明:

成功轴用字符串匹配做占位,生产里要换成结构化断言。上面用required_actions出现在文本里作为简化判定,真实场景应该解析工具调用记录,检查是否真的执行了退款、改址这类有副作用的操作。原则不变:能验证事实就不问 LLM。

judge 输出强制 JSON。一旦解析失败按 0 分处理,避免脏数据悄悄混进均值。你也可以在 judge 提示里给出 few-shot 示例,显著降低解析失败率。

token 用量按模型分别累计。usage字段来自每次响应,直接累加即可得到单条任务的成本,为后面的账单核对打基础。

8. 双轴指标对照表怎么填

跑完多个候选后,把结果整理成下面这张表。这张表才是本文标题所说的“把成功和满意拆开看”的最终形态。

候选样本数任务成功率平均满意度满意但失败占比judge 触顶率平均轮数
agent-A2000.814.3221.5%44%3.8
agent-B2000.794.5126.0%52%4.1
agent-C2000.624.0518.0%31%3.4

读表方式:

成功率高、满意度低→ agent 做得多但沟通差,优化提示词与话术,不要动工具逻辑。

成功率低、满意度高→ 典型的“说得好听没干活”,这正是 GAUGE 指出的失效模式。此时满意度轴不可信,任何基于它的排序都要打问号。表格里 agent-B 成功率低于 agent-A,但满意度更高,如果只按单轴决策就会选错。

judge 触顶率高→ 打分区分度不足,大量样本挤在 5 分。这时应该拉长评分粒度(比如 1–10 分)或要求 judge 给出理由后再打分,也可以加入成对比较而非绝对打分。

相近候选的排序要谨慎。当两个候选成功率差距在 3 个百分点以内,先算置信区间,再做配对显著性检验。GAUGE 的核心提醒就是:这个区间的 judge 排序噪声很大,单次实验的排名不具备决策价值。建议对相近候选增加样本量,或者引入人工抽检做黄金标准校准。

9. Token 账单怎么对

成本是评测方案能否长期运行的决定因素,尤其是 judge 这一环。把每个模型的 token 消耗单独记账:

环节模型单条平均 token200 条合计占比
模拟用户小模型1.8k360k18%
被测 agent生产同款3.2k640k32%
judge强推理模型5.0k1000k50%

三个可以立刻执行的优化:

judge 只喂关键轮次。完整对话里大量寒暄对判定没有贡献。截取首轮、工具调用轮、末轮,通常能把 judge 输入压掉一半,且不损失判定质量。

模拟用户用小模型 + 高温度。模拟用户需要的是话术多样性而不是推理深度,用便宜模型反而更贴近真实用户的随意表达。

成功轴先行,judge 抽样。断言能判的样本不必再过 judge。对成功轴已经通过的样本,只需抽样 20%–30% 做满意度评估即可维持统计效力。

账单能对上还有个隐性收益:如果某天指标突然漂移,token 用量会先露出异常。judge 输入 token 突然翻倍,通常意味着对话轮数失控或者 agent 开始输出超长回复。

10. 排障清单

按出现频率排序,遇到问题逐条对照。

401 / invalid api key。检查 Key 是否被 shell 配置覆盖,或者用了ANTHROPIC_AUTH_TOKEN却在 OpenAI 兼容脚本里读OPENAI_API_KEY。重新执行第 3.2 节的 curl 自检。

404 model not found。模型名拼错,或者该模型不在当前账号可用范围内。用/models接口列出真实可用列表再改配置。

请求超时。长对话加上 judge 全文输入容易触发超时。给客户端加显式超时和重试,重试时降低max_tokens,不要无条件指数退避到几分钟。

judge 返回非 JSON。在提示里加一句“只输出 JSON,不要任何解释”,并加 few-shot 示例。仍失败时按解析错误单独计数,不要把 0 分混入均值。

成功率全为 0。九成是评测脚本把 HTTP 异常吞掉了,或者required_actions的匹配串写错。先单跑一条任务打印完整 transcript,肉眼确认。

两个候选结果完全一致。检查是不是 profile 没切成功,两个槽位指向了同一个模型。打印每次请求实际使用的模型名。

Claude Code 里配置没生效。项目级配置优先于全局,检查.claude/settings.json是否覆盖了~/.claude/settings.json,以及是否设置了ANTHROPIC_API_KEYANTHROPIC_AUTH_TOKEN冲突。

11. 把结论落到流程里

回到最初那个问题:LLM 模拟用户加 LLM judge 这道关卡,什么时候不可信?答案是——当你用它来区分两个能力相近的候选时。它擅长筛掉明显不合格的方案,不擅长在优秀方案之间排座次。而选型决策恰恰发生在后者。

所以落地时记住三条:

第一,成功轴独立于 LLM。能用断言验证的事实绝不交给 judge,这是整个评测体系的压舱石。

第二,相近候选必须加人工校准。当成功率差异小于 3 个百分点,抽 30–50 条样本人工标注,用人工结果校准 judge 的排序可信度。

第三,统一出口减少变量。所有槽位走同一个 Base URL、同一个 Key、按模型分开记账,这样指标变化才可归因。

如果你也想复现这套双轴对照,建议按下面的顺序走一遍:

  • 先在 模型对话 里确认目标模型可以正常交互,把系统提示词和参数调到满意;
  • 接着看 Coding Plan,批量评测的调用量不小,选一个匹配用量的方案能明显压低单次实验成本;
  • 然后到 创建 API Key 生成 Key,把YOUR_API_KEY替换掉,Base URL 统一填https://taotoken.net/api
  • 最后对照 Claude Code 文档 把settings.json配好,把软件工程类的修复任务也纳入同一套评测口径。

评测方法的价值不在于跑出多漂亮的数字,而在于当数字和直觉冲突时,你能说清楚是哪个环节出了问题。把成功和满意拆开,是让这件事变得可解释的第一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询