智能体推理基准评测的是智能体在完成任务过程中对信息、工具和状态的推理能力,而不仅仅是模型下一个 token 的概率。AgentX 定位为面向多步工具调用、动态规划和错误恢复场景的智能体推理基准,InferenceX 则是围绕它设计的评测执行与结果分析框架。这类基准与普通 LLM 问答基准最大的区别在于:它必须把“模型输出一段文字”升级为“智能体连续决策并完成一个闭环任务”,评测维度也从“答案对不对”扩展到“步骤是否合理、失败后能否恢复、多路径下是否稳定”。
这篇文章不假设你已经拥有一个成熟的大模型推理平台,也不假设你手头有现成的评测数据。我会从智能体推理基准的设计动机讲起,再用一个可运行的最小实现,把 AgentX 任务集和 InferenceX 评测脚本串起来。最终你可以得到一套能复现、能排查、能扩展到生产环境的基准评测流程。
1. 为什么智能体推理不能直接照搬普通 LLM 基准
1.1 普通 LLM 基准测量的是“知识分布”,不是“决策过程”
传统的 LLM 基准通常围绕单选题、多选题、生成式问答设计。一条样本包含一个 prompt 和一个期望答案,评测时把模型输出与标准答案做匹配,或者让另一个模型打分。这种模式衡量的是模型在训练阶段已经内化的知识、语言能力和模式识别能力,本质上是在测试“模型知不知道正确答案”。
智能体推理基准面对的问题完全不同。智能体需要在一个任务环境中做多步决策,每一步都可能调用工具、读取返回结果、修改记忆、改变计划。例如“查询明天北京到上海最早的高铁并预订二等座”这个任务,模型至少需要完成:
- 理解用户意图中包含时间、出发地、目的地、座位偏好多个约束。
- 从工具列表中选择合适的查询工具。
- 正确抽取工具入参。
- 读取工具返回结果。
- 判断哪一趟车满足“最早”和“二等座”两个条件。
- 调用预订工具,并注意是否已经登录、是否有余票。
- 在某一环节失败时决定重试还是重新查询。
普通问答评测只看最后一段话是否提到列车号,无法判断这些中间过程是否正确。一个只知道“上海虹桥”这个名字的模型也可能在最终答案里猜对,但它在真实任务里完全不可用。因此智能体推理基准必须把“过程正确性”纳入评分体系。
1.2 智能体推理基准需要覆盖的推理能力
在 AgentX 中,我们把智能体推理能力拆成五个可测量的维度。这五个维度不是凭空定义的,而是来自真实业务系统中最常见的失败模式。
| 能力维度 | 评测方式 | 典型失败场景 |
|---|---|---|
| 多步规划 | 给定目标,要求输出完整工具调用序列 | 工具顺序颠倒,先订票再查询余票 |
| 工具选择 | 给定多个工具,要求选出最匹配的那一个 | 使用普通搜索代替数据库查询 |
| 状态追踪 | 在多轮交互中记住已获取的信息 | 用户改时间后仍使用旧日期 |
| 参数抽取 | 从非结构化文本中提取结构化入参 | 把“下周三”解析成错误日期 |
| 错误恢复 | 工具失败后能否修正路径 | 余票不足时不改条件,直接报错 |
这五类能力互不替代。一个模型可能参数抽取很强,但多步规划很弱;也可能规划能力强,但遇到工具返回异常时就崩掉。AgentX 的任务集按这五个维度分别构造样本,评测结果也会按维度拆分,这样团队成员能一眼看出模型在哪类能力上退化。
1.3 结果评测的难点在于“正确答案不唯一”
普通 LLM 基准的答案匹配相对简单,因为答案通常是确定的。智能体任务中,每一步都不一定唯一。以“查询明天从北京到上海的高铁”为例,合法的工具调用可能是search_trains,也可能是query_high_speed_rail,取决于工具命名。更复杂的是,智能体可以先查天气再安排出行,也可以直接查高铁,两条路径都合理。
这给评测框架带来一个很实际的问题:不能只靠字符串相等判断对错,必须建立“归一化 + 规则校验 + 语义判断”三层机制。AgentX 在任务定义中预留accepted_answers字段,允许多个合法答案候选;InferenceX 在计算分数时,先对输出做归一化,再走规则校验器,最后才考虑用 LLM judge 判断语义等价。
2. AgentX 基准任务集应该怎么设计
2.1 任务格式:JSONL 比 JSON 更适合评测集
单文件 JSON 适合配置,不适合评测数据。评测数据要按行追加、按 task_id 去重、按难度筛选,还会在人工 review 时频繁增删样本。JSONL 每行一条独立 JSON,能避免一个 JSON 语法错误导致整个文件不可用,也方便用grep、awk快速定位问题样本。
每条 AgentX 样本至少包含以下字段:
{ "task_id": "agentx-0001", "task_type": "multi_step_planning", "difficulty": "easy", "prompt": "查询明天北京到上海最早的高铁,并预订二等座余票充足的那一班。可用工具:search_trains、book_ticket、get_user_info。", "tools": [ { "name": "search_trains", "description": "查询指定日期、出发地、目的地的车次列表", "parameters": ["date", "from", "to"] }, { "name": "book_ticket", "description": "预订指定车次的指定座位类型", "parameters": ["train_id", "seat_type"] }, { "name": "get_user_info", "description": "获取当前登录用户信息", "parameters": [] } ], "expected_path": ["search_trains", "book_ticket"], "expected_final_answer": { "train_id": "G101", "seat_type": "second_class" }, "max_turns": 5, "judge_type": "rule", "accepted_answers": [ "G101 二等座", "北京南到上海虹桥的 G101" ] }字段设计要回答评测时的三个问题:
- 这个任务要测什么能力,对应
task_type。 - 模型在什么工具环境里工作,对应
tools。 - 什么结果算正确,对应
expected_path、expected_final_answer、accepted_answers。
2.2 工具定义要控制变量
设计智能体任务时,最忌把多个能力混在一起。AgentX 在 easy 样本中会提供完整且明确的工具描述,模型只需要做选择和执行;在 hard 样本中,工具描述会故意写得模糊,或者加入一个干扰工具,让模型必须通过语义匹配和排除法确定正确路径。
工具定义建议使用 JSON Schema 风格,但不必一开始就上完整版本。最小可用结构只需要name、description、parameters。如果你要接入真实 API 执行环境,再扩展required、type、enum等字段。parameters用数组而不是对象,是为了减少初学者理解成本,也方便在评测脚本里直接打印对比。
需要特别注意的是,不要在 prompt 里把工具名写得太直白。如果任务描述已经写了“请调用 search_trains”,那评测的就不是智能体推理,而是字符串复述能力。AgentX 更推荐让工具描述覆盖“查询高铁车次”这一语义,同时提供query_trains和search_flights两个近似工具,让模型通过约束条件选择正确工具。
2.3 难度分级要服务于回归测试
AgentX 把任务划分为 easy、medium、hard 三档,每个档次的定位不同:
| 难度 | 任务特点 | 适合验证什么 |
|---|---|---|
| easy | 单步工具选择,参数直接出现在 prompt | 基础工具调用能力 |
| medium | 两到三步规划,返回结果需要二次解析 | 多步规划、状态追踪 |
| hard | 多分支规划,工具失败后需要恢复 | 错误恢复、复杂规划 |
难度分级对模型迭代非常重要。团队在开发新 prompt 或微调模型时,可以先跑 easy 子集确认基础能力没有退化,再跑 hard 子集观察复杂场景。如果所有任务混在一起,指标下降时无法判断是基础能力坏了还是复杂推理坏了。
2.4 防数据泄露要在设计阶段考虑
大模型评测有一个容易被忽略的风险:评测样本可能和模型训练数据高度重叠。如果任务集公开发布过,或者团队成员经常把样本贴进对话工具讨论,后续训练出的模型很可能“背答案”,导致评测分数虚高。
AgentX 采取几个简单措施:
- 样本在写入任务集时记录
created_at和source,便于追踪来源。 - 公开样本和私有 holdout 样本分离,私有样本只用于最终回归。
- 不在模型 prompt 中直接暴露 task_id,避免模型按 id 记忆答案。
- 定期用 3 到 5 条相似样本做语义查重,发现重复就调整描述。
对于研究项目,这些措施足够;对于生产级评测系统,还需要加入哈希校验和访问审计,后面的工程化章节会展开。
3. InferenceX 评测框架的最小可运行实现
3.1 环境准备与项目结构
InferenceX 的最小实现只需要 Python 3.10 以上环境,不强制依赖深度学习框架。模型调用使用 OpenAI 兼容接口,通过requests直接请求,方便替换成各类开源模型网关。
mkdir inferencex cd inferencex python -m venv .venv source .venv/bin/activate pip install requests为了保持代码清晰,建议按下面结构组织:
inferencex/ ├── data/ │ └── agentx_dev.jsonl ├── evaluator/ │ ├── __init__.py │ ├── loader.py │ ├── model_client.py │ ├── validators.py │ └── metrics.py ├── config.yaml ├── run_eval.py └── output/loader.py负责加载 JSONL 并按难度筛选;model_client.py负责完成模型请求;validators.py负责判断输出是否合格;metrics.py负责聚合指标;run_eval.py是入口脚本。
3.2 任务加载器:读 JSONL 并做基础校验
模型评测最容易被错误数据污染。如果 JSONL 中某一行字段拼错,最后的指标往往不报错,但结果毫无意义。因此在加载阶段就要校验格式。
import json from pathlib import Path from typing import Iterator, Dict, Any REQUIRED_FIELDS = [ "task_id", "task_type", "difficulty", "prompt", "tools", "expected_path", "expected_final_answer", ] def load_tasks(path: Path, difficulty: str = None) -> Iterator[Dict[str, Any]]: if not path.exists(): raise FileNotFoundError(f"task file not found: {path}") seen_ids = set() with path.open("r", encoding="utf-8") as f: for line_no, line in enumerate(f, start=1): line = line.strip() if not line: continue try: task = json.loads(line) except json.JSONDecodeError as exc: raise ValueError( f"invalid json at line {line_no}: {exc.msg}" ) from exc missing = [field for field in REQUIRED_FIELDS if field not in task] if missing: raise ValueError( f"line {line_no} missing fields: {missing}" ) if task["task_id"] in seen_ids: raise ValueError( f"duplicated task_id {task['task_id']} at line {line_no}" ) seen_ids.add(task["task_id"]) if difficulty and task.get("difficulty") != difficulty: continue yield task这里做了三件重要的事:
- 按行解析 JSON,某一行坏了不会影响整个文件。
- 检查必填字段,避免后续计算时报 KeyError。
- 检查 task_id 重复,防止同一个任务被计算两次导致指标失真。
3.3 模型客户端:用 OpenAI 兼容接口隔离具体模型
评测框架不该绑定某一个模型厂商。InferenceX 的最小版本通过 base_url、api_key、model_name 三个环境变量配置模型,请求体使用 OpenAI Chat Completions 格式。这样 OpenAI、本地 vLLM、各类 API 网关都能接入。
import os import json import time import requests from typing import List, Dict, Any class ModelClient: def __init__(self, model_name: str = None, timeout: int = 60): self.base_url = os.getenv("INFERENCEX_BASE_URL", "https://api.openai.com/v1") self.api_key = os.getenv("INFERENCEX_API_KEY", "") self.model_name = model_name or os.getenv("INFERENCEX_MODEL", "gpt-4o-mini") self.timeout = timeout self.session = requests.Session() self.session.headers.update( {"Authorization": f"Bearer {self.api_key}"} ) def generate(self, messages: List[Dict[str, str]], temperature: float = 0.0): url = f"{self.base_url}/chat/completions" payload = { "model": self.model_name, "messages": messages, "temperature": temperature, } try: resp = self.session.post(url, json=payload, timeout=self.timeout) resp.raise_for_status() data = resp.json() return data["choices"][0]["message"]["content"] except requests.exceptions.Timeout as exc: raise TimeoutError(f"model request timed out: {exc}") from exc except requests.exceptions.HTTPError as exc: raise RuntimeError( f"model request failed with status {resp.status_code}: {resp.text}" ) from exc这个实现的关键点是:
- 把模型 API 调用统一封在
generate方法里,评测脚本不用关心 HTTP 细节。 - temperature 默认为 0,降低随机性,保证评测结果更容易复现。
- 超时和 HTTP 状态码都转成明确异常,方便排查,不让脚本静默失败。
实际生产环境中,你还需要在这里加重试、限流和 token 用量记录。最小版本先不做,避免代码复杂度掩盖主流程。
3.4 校验器:从字符串匹配到语义判断分三层
校验器是整个评测框架中最容易写出 bug 的地方。简单字符串匹配实现快,但会把许多语义等价输出误判为错误;而全量使用 LLM judge 成本高,且 judge 自身不稳定。
InferenceX 采用三层校验:
- 归一化:去掉标点、空白、把中文数字转阿拉伯数字。
- 规则校验:任务里配置
expected_final_answer时,检查模型输出是否包含关键字段。 - 语义判断:对复杂任务使用 judge 模型,判断模型输出和参考答案是否语义一致。
import re from typing import Any, Dict def normalize(text: str) -> str: text = text.lower().strip() text = re.sub(r"[\s,。!?、]+", "", text) return text def contains_expected(text: str, expected: Dict[str, Any]) -> bool: normalized = normalize(text) for key, value in expected.items(): if isinstance(value, str): if normalize(value) not in normalized: return False elif isinstance(value, list): if not all(normalize(item) in normalized for item in value): return False return True对于复杂任务,规则校验器容易误伤。比如expected_final_answer要求train_id为G101,模型输出“今天 G101 次列车二等座充足”,归一化后包含g101,可以判对。但如果模型输出“今天上午 10 点那趟车有票”,没有直接写车次,规则判定就会失败。
此时需要引入 judge。Judge 也是一个模型调用,但评测目标不同。它不负责完成任务,只负责判断两个文本是否表达同一含义。
def judge_semantic_equivalence( model_client: ModelClient, output: str, reference: str, ) -> bool: messages = [ { "role": "system", "content": ( "你是一个评测助手。判断模型输出是否与参考答案语义一致。" "只输出 YES 或 NO,不要解释。" ), }, { "role": "user", "content": ( f"模型输出:{output}\n参考答案:{reference}" ), }, ] result = model_client.generate(messages, temperature=0.0).strip().upper() return result.startswith("YES")使用 LLM judge 时要注意两点:
- judge 的 prompt 必须固定,不能每次随机发挥。
- 对 judge 输出做白名单,只有
YES开头才视为命中,其他都按未命中处理,避免 judge 输出“YES 因为参数一致”这种解释被误判。
3.5 指标计算:轨迹准确率、任务完成率、维度拆分
评测脚本需要输出三类指标:
- 整体任务完成率:一条样本最终结果是否被判定为正确。
- 轨迹准确率:工具调用路径和期望路径是否一致,用于反应过程是否合理。
- 分维度指标:按 task_type 拆开计算的完成率。
from collections import defaultdict from typing import Dict, List class MetricsAccumulator: def __init__(self): self.total = 0 self.correct = 0 self.type_total = defaultdict(int) self.type_correct = defaultdict(int) self.failures = [] def add_case(self, task, is_correct: bool, trace=None, error=None): self.total += 1 task_type = task["task_type"] self.type_total[task_type] += 1 if is_correct: self.correct += 1 self.type_correct[task_type] += 1 else: self.failures.append( { "task_id": task["task_id"], "prompt": task["prompt"], "trace": trace, "error": error, } ) def report(self) -> Dict[str, float]: overall = self.correct / self.total if self.total else 0.0 dimension_report = {} for task_type in self.type_total: dimension_report[task_type] = ( self.type_correct[task_type] / self.type_total[task_type] if self.type_total[task_type] else 0.0 ) return { "overall_accuracy": round(overall, 4), "dimension_accuracy": dimension_report, "failure_count": len(self.failures), }指标表除整体准确率外,还要输出每个维度的准确率,便于定位能力短板。比如tool_selection准确率高但error_recovery准确率低,说明问题集中在失败处理环节,而不是工具理解。
3.6 主入口:把加载、推理、校验、统计串起来
最后一环是run_eval.py。它读取配置文件,加载任务,对每个任务构造 prompt,调用模型,走校验器,累计分数,最后输出 JSON 报告。
import argparse import json import yaml from pathlib import Path from evaluator.loader import load_tasks from evaluator.model_client import ModelClient from evaluator.validators import contains_expected, judge_semantic_equivalence from evaluator.metrics import MetricsAccumulator def build_messages(task): tools_desc = "\n".join( f"- {tool['name']}: {tool['description']}" for tool in task["tools"] ) system_prompt = ( "你是一个智能体,能够根据用户目标选择并调用工具。" "请输出工具调用序列,并给出最终答案。" ) user_prompt = f"{task['prompt']}\n可用工具:\n{tools_desc}" return [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ] def main(): parser = argparse.ArgumentParser() parser.add_argument("--task-file", required=True) parser.add_argument("--difficulty", default=None) parser.add_argument("--output", default="output/report.json") args = parser.parse_args() with open("config.yaml", "r", encoding="utf-8") as f: config = yaml.safe_load(f) model_client = ModelClient( model_name=config.get("model_name"), timeout=config.get("timeout", 60), ) accumulator = MetricsAccumulator() tasks = list(load_tasks(Path(args.task_file), difficulty=args.difficulty)) for task in tasks: messages = build_messages(task) try: output = model_client.generate(messages) if task.get("judge_type") == "rule": is_correct = contains_expected( output, task.get("expected_final_answer", {}) ) else: is_correct = judge_semantic_equivalence( model_client, output, json.dumps(task["expected_final_answer"], ensure_ascii=False) ) accumulator.add_case(task, is_correct, trace=output) except Exception as exc: accumulator.add_case(task, False, trace=None, error=str(exc)) report = accumulator.report() print(json.dumps(report, ensure_ascii=False, indent=2)) Path(args.output).parent.mkdir(parents=True, exist_ok=True) with open(args.output, "w", encoding="utf-8") as f: json.dump(report, f, ensure_ascii=False, indent=2) if __name__ == "__main__": main()4. 运行 AgentX 评测并读懂结果
4.1 准备一份最小任务集并运行
先准备data/agentx_dev.jsonl,放 5 条任务足够验证流程。下面是一条带干扰工具的 easy 样本:
{"task_id": "agentx-0001", "task_type": "tool_selection", "difficulty": "easy", "prompt": "用户想查询明天北京到上海的高铁出发时间。可用工具包括 search_trains、search_flights、get_user_info。", "tools": [{"name": "search_trains", "description": "查询指定日期从出发地到目的地的高铁车次"}, {"name": "search_flights", "description": "查询指定日期从出发地到目的地的航班"}, {"name": "get_user_info", "description": "获取当前登录用户信息"}], "expected_path": ["search_trains"], "expected_final_answer": {"tool": "search_trains"}, "max_turns": 3, "judge_type": "rule", "accepted_answers": ["search_trains"]}设置环境变量后,执行:
export INFERENCEX_BASE_URL="https://your-gateway.example.com/v1" export INFERENCEX_API_KEY="your-key" export INFERENCEX_MODEL="your-model-name" python run_eval.py --task-file data/agentx_dev.jsonl --output output/report.json正常输出类似:
{ "overall_accuracy": 0.8, "dimension_accuracy": { "tool_selection": 1.0, "multi_step_planning": 0.5, "error_recovery": 1.0 }, "failure_count": 1 }如果看到零错误但准确率很低,优先检查expected_final_answer的字段是否和模型输出格式对齐。比如模型输出{"tool": "search-trains"},而规则里写search_trains,归一化后也匹配不上,会被误判为错误。
4.2 从失败样本反推模型问题
只读准确率无法指导模型迭代。InferenceX 在失败样本中保留了trace和error,所以要重点看那条失败样本的实际输出。下面是一个典型失败案例:
task_id: agentx-0001 trace: 我先查询航班,因为用户说“出发时间”而不是“高铁出发时间” error: None这个失败说明工具选择阶段出现了语义歧义。模型把“出发时间”理解成了通用查询,选了航班工具。修复方向不是换模型,而是调整任务描述,让用户 Prompt 更明确提到高铁;或者增加一个限制工具描述,让模型明白search_flights只处理飞机航班。这类分析结果应当反馈到 AgentX 任务集和 Prompt 模板中,形成“评测发现问题 -> 修改任务或 prompt -> 再次评测”的循环。
4.3 模型输出格式对结果影响很大
智能体任务中,模型输出有时是结构化 JSON,有时是自然语言。InferenceX 最小实现没有强制要求结构化输出,因此校验更多依赖最终的语义判断。但在实际工程中,建议为所有任务开启 JSON mode 或 function calling 接口,这样:
- 工具调用序列可以明确解析,不再依赖文本里是否出现某个工具名。
- 参数抽取可以独立校验,比如
date字段是否被正确解析。 - 失败样本更容易归类,因为结构化输出有标准 schema。
如果需要改造成 function calling,build_messages要增加tools参数,并把模型的 tool_calls 单独提取。最小版本用文本输出验证流程是合理的,一旦指标稳定,建议升级到结构化协议。
5. 常见问题排查:从现象定位到文件或代码
5.1 JSONL 加载报错或样本数不对
现象:运行脚本时报invalid json at line ...,或者打印的报告里 total 数量明显少于文件行数。
排查链路:
- 第一步,确认文件本身是 UTF-8 编码,没有 BOM 头。Windows 记事本保存文件容易带 BOM,导致第一行解析异常。
- 第二步,用
python -c "import json; print(len([1 for line in open('data/agentx_dev.jsonl') if line.strip()]))"检查非空行数。 - 第三步,看 loader 是否被
difficulty参数过滤。如果指定了--difficulty hard,easy 样本不会进入评测,这是正常行为。
处理建议:加载阶段不要吞异常。任何解析错误都要直接报错并指出行号,因为评测数据是基线,数据坏了后面所有指标都不可信。
5.2 模型请求超时或限流
现象:model request timed out,或者HTTPError返回 429。容易出现在并发执行多条任务时。
排查链路:
- 检查
timeout是否小于模型实际响应时间。长推理场景模型可能 30 秒甚至更久才返回。 - 检查 API 网关的限流策略,是否允许评测脚本在短时间内发起大量请求。
- 检查
temperature=0是否真的被服务端接受,有些服务端对 temperature 有范围限制。
处理建议:
- 最小实现先串行执行,确认任务集和模型输出稳定后再考虑并发。
- 在
ModelClient.generate中加入指数退避重试,遇到 429 或 5xx 时等待 1 秒、2 秒、4 秒再重试。 - 评测脚本记录每次请求的开始时间和结束时间,方便定位慢任务。
5.3 准确率异常高或异常低
现象:整体准确率 100%,但人工抽查发现很多输出其实不对;或者准确率接近 0,但肉眼看去输出是正确的。
排查链路:
- 准确率异常高,先怀疑规则校验器过宽。检查
contains_expected是否只匹配了部分字段,比如只匹配了tool字段而忽略了date字段。这种情况下,模型即使漏掉关键参数也会被判对。 - 准确率异常低,先怀疑格式不匹配。模型输出
search_trains,但规则期望search-trains;或者模型输出了完整解释句,归一化后仍包含关键值,但由于标点处理不彻底导致匹配失败。 - 最后看 judge 输出。如果 judge 频繁返回
NO,需要人工抽检 judge 的判断是否合理,可能需要改写 judge prompt 或引入多个 judge 投票。
处理建议:不要一次只测一个指标。在本地保留一份 5 到 10 条人工标注样本,每次修改校验器后先跑这组样本,确保校验器本身的准确率没有下降。
5.4 评测结果不可复现
现象:同一个模型、同一份任务文件,两次运行得到的准确率不一致。
排查链路:
- 查看模型请求的
temperature。如果服务端默认 temperature 不是 0,结果会有随机波动。 - 查看是否有多实例部署的模型。多个模型副本负载均衡时,不同副本的行为可能不同,尤其是用了动态采样。
- 查看任务文件是否被改动过,任务顺序是否变化。如果是按顺序输出,顺序本身不影响 JSON 报告,但如果并发执行,请求完成顺序不同可能导致输出顺序不同。
处理建议:在报告 JSON 中额外写入评测元信息,包括模型名、数据集文件 hash、推理参数 temperature、运行时间和任务文件路径。下次复现时先比对元信息是否一致。
6. 从评测脚本到生产级基准的工程化建议
6.1 不要只记录准确率,还要保存每条样本的完整输出
生产环境中,指标是给决策者看的,而失败样本是给开发者看的。两者都要保留。InferenceX 的临时实现只保存了失败样本,建议在生产版本中把每条样本的输入、模型输出、校验结果全部按任务 ID 落盘:
{ "task_id": "agentx-0001", "model": "agent-r1-latest", "difficulty": "medium", "prompt": "...", "output": "...", "trace": "...", "is_correct": false, "judge_type": "rule", "cost_usd": 0.0032, "latency_ms": 1843 }这样不仅可以复现问题,还可以计算 token 成本、请求延迟、失败分布,为后续优化提供数据支撑。
6.2 评测数据要纳入版本管理,流程要可追溯
评测数据一旦改动,历史结果就失去可比性。每次新增样本、删除样本、修改描述,都应该记录变更原因。推荐流程:
- 任务文件按版本号或 commit 管理,文件名不要叫
agentx_final.jsonl。 - 每次跑评测前记录当前 commit hash、数据集文件 hash。
- 模型版本要记录准确,不只是“新模型”这种描述。
- 评测报告写入 Git 标签或 release note,方便回溯。
可复用清单:
| 项目 | 需要记录的信息 |
|---|---|
| 模型 | 模型名称、权重版本、推理引擎、温度 |
| 数据 | 数据集文件 hash、任务数量、难度过滤条件 |
| 代码 | 评测框架 commit、配置文件 hash |
| 环境 | Python 版本、依赖版本、API 网关版本 |
| 运行 | 开始时间、结束时间、并发数、总耗时 |
6.3 引入基线样本组,防止“评测集污染”
团队越依赖基准,基准被污染的风险越大。可以在 AgentX 中固定一组regression_baseline,每次改动任务集后,先用基线样本组跑一遍回归。基线样本组一旦被模型调到满分,就换一批 holdout 样本重新做基线。
这个思路类似于软件工程的测试套件。评测任务集也需要一个稳定、独立、少改动的“测试集”,和一个用于迭代优化、允许变化的“开发集”。模型迭代过程中频繁看开发集结果无可厚非,但最终发布前一定要在 holdout 集上重新评测。
6.4 LLM judge 的不稳定性要提前治理
使用 LLM judge 判断语义等价时,可能遇到 judge 自身判断波动。低成本治理方式:
- 固定 judge 模型和 prompt,不随意切换。
- 对难判断样本采用多次采样投票,比如调用 3 次,取多数结果。
- 人工抽检 judge 的判断结果,建立一个小型校准集,统计 judge 与人的一致率。
- 如果 judge 与人的一致率低于 90%,优先调整 judge prompt,而不是增加采样次数。
6.5 评测成本控制:先跑小样本,再跑全量
智能体评测比普通问答评测贵很多,因为一个任务可能多次调用工具,还可能调用 judge。控制成本的顺序:
- 先用 20 到 50 条样本做 smoke test,确认流程没有异常。
- 再按难度和 task_type 分层抽样,跑一个规模适中的子集。
- 全部通过后,再跑全量任务集。
在报告 JSON 中记录total_cost_usd,让成本变化可监控。如果一个任务类型准确率很低,不要反复跑全量,先修复任务描述、校验器或模型 prompt,再跑全量验证。
6.6 生产环境的额外保障
把 InferenceX 接入定时评测或 CI 时,还需要关注:
- 日志:每条样本的请求和响应需要写到独立日志文件,避免和评测报告混在一起。
- 权限:API Key 通过密钥管理服务注入,不要写入配置文件。
- 监控:记录请求成功率、平均延迟、限流次数。
- 回滚:模型版本异常时能快速切到旧版本重新评测。
- 数据备份:任务集和评测报告定期备份,防止误删导致基线丢失。
对于刚开始建设智能体推理基准的团队,不建议一次性实现所有生产特性。先把 JSONL 任务集、规则校验、指标报告和失败样本保存跑通,再逐步加入并发、LLM judge、成本统计和 CI 集成。
AgentX 和 InferenceX 组合起来,真正有价值的地方不在于某个模型的准确率数字,而在于它让你能回答“模型在哪个推理环节退化了,为什么会退化”。多步规划、工具选择、状态追踪、参数抽取、错误恢复,每一项都应独立观测。评测不是为了给模型打分,而是为了把模型能力变成可测量、可定位、可改进的工程对象。