最近在业务里折腾 Agent 应用评测和 LLM 基准对比和几个团队交流后发现一个普遍现象大家花大量时间调 Prompt、配工具、接知识库但测试和评估环节却非常薄弱。很多人还在用“人工跑几条用例肉眼看效果”的方式判断 Agent 好不好用。这种模式在单轮 Chat 场景勉强能凑合一旦进入 Agentic 场景——多轮推理、工具调用、长上下文、自主规划——就完全不够用了。本文将围绕 Agentic 测试流程Agentic Test Processes和 LLM 基准评估LLM Benchmarks展开。我会先讲清楚这两个概念到底是什么、解决什么问题然后给出一套可复用的 Agent 测试脚本和一个轻量基准评估思路最后分享我在工程实践中踩过的坑以及沉淀下来的最佳实践。适合读者正在做 LLM 应用、Agent 产品、RAG 系统评测的开发者想系统了解模型评估体系的算法工程师以及想给团队搭建自动化评测流程的技术负责人。读完你至少能回答三个问题Agent 应该测什么、怎么测基准评估的分数是怎么来的以及如何把评估流程落地到日常开发中。1. 背景为什么 Agent 测试和 LLM 基准评估值得认真做1.1 从 Chat 到 Agent测试对象变了传统 LLM 应用是一个“单轮问答”形态用户提问模型回答。测试时只需要准备一批问题和标准答案对比输出相似度即可。但 Agent 应用完全不同。Agent 的核心是“循环”模型先理解任务再决定调用哪个工具拿到工具结果后继续推理直到完成任务。这个过程中每一步都可能出错第一步就把任务理解偏了工具名称选错或者参数格式不对拿到工具结果后没有正确利用而是自顾自编答案陷入死循环反复调用同一个工具被用户输入或工具返回内容诱导执行了计划外的行为。这些问题在单轮评测中几乎测不出来。所以 Agentic 测试流程的核心思路是把“多轮交互整条链路”作为测试对象而不是只测“模型生成了什么文本”。1.2 什么是 Agentic 测试流程通俗地说Agentic 测试流程就是围绕“Agent 的自主行为”设计的一套测试方法。它不只看最终答案对不对还要看过程是否合理、工具调用是否规范、失败时是否能正确恢复。一个完整的 Agentic 测试流程通常包含场景设计把真实业务拆成具体的任务场景用例编排每个场景包含用户输入、期望行为、可接受的边界执行引擎自动跑 Agent记录完整轨迹结果判定通过规则、断言或模型评判判断用例通过还是失败回归机制模型升级、Prompt 调整、工具变更后自动重跑。这套流程解决的核心问题是Agent 的行为具有高不确定性同一个输入在不同时间可能得到不同结果。如果不把测试流程自动化、标准化你永远分不清一次失败是“偶发问题”还是“系统缺陷”。1.3 LLM 基准评估是什么LLM 基准评估Benchmark是另一件事用一套固定的、公开的测试集去衡量模型在不同能力维度上的表现。常见的评估维度包括综合知识考察模型的知识广度和多选推理能力典型如 MMLU、C-Eval代码生成考察模型能否根据函数注释生成正确代码典型如 HumanEval数学推理考察多步计算与逻辑推理典型如 GSM8K通用 Agent 行为考察模型在复杂环境中的规划与工具使用能力典型如 AgentBench、SWE-bench。注意这里我没有写具体版本号因为这些数据集和榜单更新非常快你动手实践时一定要以对应项目仓库的最新说明为准。1.4 容易混淆的三个概念很多刚接触的同学会把下面三个词混在一起Eval评估一个统称指任何对模型或应用效果的衡量过程Benchmark基准一套标准化的测试集用于横向对比不同模型比如公开榜单Test测试针对你具体业务场景设计的验证用例目的是保证产品质量。打个比方Benchmark 像“全国统一高考”用来横向比较所有考生Eval 像是“学校组织的常规考试”范围可以自己定Test 像是“岗位入职考核”题目完全贴合你的业务。企业落地时三者都需要但优先级通常是 Test Eval Benchmark。2. 环境准备与工具选型2.1 环境清单在动手写测试代码之前先把环境理清楚。以下是一份基础清单组件说明操作系统Windows / macOS / Linux 均可建议使用 Linux 服务器跑回归Python3.10 或更高版本LLM 访问方式OpenAI 兼容 API、国内大模型 API或本地部署的模型服务Agent 框架可选项LangChain、LlamaIndex 或自研框架测试框架pytest 或自研脚本版本控制Git用于管理测试用例和回归历史版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示测试流程的设计思路。2.2 被测对象最小 Agent为了不让示例被某个具体框架绑死我会写一个极简 Agent它维护一个消息列表循环调用 LLMLLM 返回“工具调用指令”或“最终答案”Agent 根据指令执行工具并把结果追加回上下文。真实项目中你完全可以用 LangChain 的 AgentExecutor、LlamaIndex 的 AgentRunner或者自研框架替换。测试流程本身与框架无关重要的是“可记录的输入输出轨迹”。2.3 评测框架怎么选市面上的 LLM 评测框架很多比如 DeepEval、promptfoo以及各云厂商自带的评测服务。选型时建议关注四点是否支持自定义测试集和评判规则是否能记录和回放完整轨迹是否方便接入现有 CI 流程是否支持 LLM-as-Judge用模型辅助评判主观答案。如果团队刚起步我建议先不要引入重框架。用一个几百行的 Python 脚本把流程跑通理解清楚之后再迁移到成熟框架成本更低也不容易被框架的抽象带偏。3. 核心原理测试与评估要拆到哪一层3.1 Agent 测试的三层拆解我习惯把 Agent 测试拆成三层每一层的目的和手段都不同单元层测试单个 Prompt 模板、单个工具函数、单次 LLM 调用的输入输出。比如“工具参数解析是否正常”“Prompt 在边界输入下是否崩溃”。这一层可以用最朴素的单元测试覆盖速度快、成本低。流程层测试 Agent 的完整循环。给定一个用户输入让它最多跑 N 步断言每一步的工具调用是否符合预期、最终是否完成任务。这是 Agentic 测试的核心。场景层模拟真实用户的完整操作路径。比如 agentic RAG 场景下用户先问“帮我找上季度的销售数据”Agent 要完成“检索→筛选→汇总→回答”一系列动作。这一层要结合业务数据做端到端验证。三层各司其职单元层解决“零件是否合格”流程层解决“装配是否顺畅”场景层解决“整车能不能上路”。3.2 基准评估的四个构成要素任何基准评估无论简单还是复杂都有四个构成要素数据集一批带标准答案的题目必须有明确的答案判定规则模型接口统一的模型调用方式保证对比公平采样策略固定随机种子、控制 temperature 等参数避免因为采样波动导致分数失真评分函数把模型输出映射成可比较的分数可能是精确匹配、模糊匹配也可能是代码测试用例通过率。理解这四个要素之后你会发现“刷榜”有很多操作空间比如测试集泄漏进训练数据、答案判定宽松、采样次数取最大值等。所以看到 benchmark 分数时先别急着膜拜看看它的数据是否隔离、判定是否严格。3.3 为什么 Agent 效果不能只靠分数这里想特别提醒一点LLM benchmark 分数高不代表你的 Agent 产品好用。原因很简单。Benchmark 考的是模型的“单点能力”比如一道数学题、一段代码但 Agent 产品是“系统能力”它涉及检索质量、工具设计、上下文管理、异常恢复等大量非模型因素。举个我遇到的例子某个模型在通用推理 benchmark 上表现很好但在我们的 agentic RAG 场景里它频繁地“跳过检索步骤直接编答案”因为它的能力分布和业务场景不匹配。反过来一个小参数模型在 benchmark 上排名不高但因为工具调用格式稳定、响应快反而更适合我们当时的业务。所以benchmark 适合“选模型”和“看趋势”业务测试才适合“保交付”。两者是互补关系不是替代关系。4. 实战一搭建一套可复用的 Agent 测试流程下面我们动手写一套最小但完整的 Agent 测试流程。为了让示例可以直接运行我会用一个 Mock LLM 模拟真实模型的工具调用行为方便你理解测试框架本身。接入真实模型时只需替换 LLM 实现即可。4.1 项目结构agent_test_demo/ ├── agent_core.py # 极简 Agent 与工具定义 ├── mock_llm.py # Mock LLM用于离线测试 ├── test_agent.py # 测试用例与执行脚本 └── README.md4.2 被测 Agent 核心代码先定义工具和一个极简 Agent 循环。# agent_core.py 极简 Agent 实现便于演示测试流程。 实际项目中可替换为 LangChain / LlamaIndex / 自研框架。 from typing import Callable, Dict, List class Tool: def __init__(self, name: str, description: str, func: Callable): self.name name self.description description self.func func def calculator(expression: str) - str: 演示工具计算数学表达式。生产环境请使用安全表达式解析库。 try: result eval(expression) return f计算结果: {result} except Exception as e: return f表达式错误: {e} def get_weather(city: str) - str: 演示工具查询城市天气伪实现。真实项目中替换为天气 API。 return f{city} 天气: 多云, 24℃ TOOLS: List[Tool] [ Tool(namecalculator, description计算数学表达式, funccalculator), Tool(nameget_weather, description查询城市天气, funcget_weather), ] class Agent: 极简 Agent循环调用 LLM直到得到最终答案或达到最大步数。 def __init__(self, llm, tools: List[Tool], max_steps: int 5): self.llm llm self.tool_map {t.name: t for t in tools} self.max_steps max_steps def run(self, query: str) - Dict: messages [{role: user, content: query}] trace [] for _ in range(self.max_steps): response self.llm.complete(messages, self.tool_map) trace.append(response) if response.get(finish): return {answer: response.get(answer, ), trace: trace} tool_name response.get(tool_name, ) tool_args response.get(tool_args, {}) if tool_name in self.tool_map: result self.tool_map[tool_name].func(**tool_args) messages.append({ role: tool, name: tool_name, content: result, }) else: messages.append({ role: assistant, content: 工具不存在请检查工具名称。, }) return {answer: 达到最大步数未完成, trace: trace}这个 Agent 的核心逻辑很简单每次循环让 LLM 返回一个动作动作要么是“调用某个工具”要么是“结束并给出答案”。trace记录每一步的决策方便测试后定位问题。需要说明的是真实接入 OpenAI 兼容接口时LLM 返回的内容通常是函数调用function call结构需要你按对应 SDK 解析。上面代码把“解析结果”抽象成了一个接口这正是为了测试方便。4.3 Mock LLM 与测试脚本为了让测试在离线环境也能跑我写一个 MockLLM它按预设剧本一步步返回动作。# mock_llm.py class MockLLM: 测试用固定行为 LLM按预设脚本返回动作用于离线复现。 def __init__(self, script): self.script script self.cursor 0 def complete(self, messages, tools): if self.cursor len(self.script): step self.script[self.cursor] self.cursor 1 return step return {finish: True, answer: 无更多脚本步骤}接着编写测试用例和执行脚本。# test_agent.py from agent_core import Agent, TOOLS from mock_llm import MockLLM def run_case(case): 执行单个测试用例返回是否通过。 llm MockLLM(case[script]) agent Agent(llmllm, toolsTOOLS, max_steps5) result agent.run(case[query]) passed result[answer] case[expected_answer] print(f[{PASS if passed else FAIL}] {case[name]}) if not passed: print(f query: {case[query]}) print(f expected: {case[expected_answer]}) print(f actual: {result[answer]}) for i, step in enumerate(result[trace]): print(f step {i}: {step}) return passed # 用例 1正确调用计算器并返回结果 case_calculator_ok { name: calculator_success, query: 1 2 * 3 等于多少, script: [ {tool_name: calculator, tool_args: {expression: 1 2 * 3}}, {finish: True, answer: 7}, ], expected_answer: 7, } # 用例 2模型应对天气查询调用天气工具 case_weather_ok { name: weather_success, query: 北京今天天气怎么样, script: [ {tool_name: get_weather, tool_args: {city: 北京}}, {finish: True, answer: 北京 天气: 多云, 24℃}, ], expected_answer: 北京 天气: 多云, 24℃, } # 用例 3模型连续调用两次工具才得到答案 case_multi_step { name: multi_step_success, query: 先算 3 * 7再算结果加 1, script: [ {tool_name: calculator, tool_args: {expression: 3 * 7}}, {tool_name: calculator, tool_args: {expression: 21 1}}, {finish: True, answer: 22}, ], expected_answer: 22, } # 用例 4模型调用不存在的工具应被 Agent 拦截 case_unknown_tool { name: unknown_tool_rejected, query: 帮我执行一个未知操作, script: [ {tool_name: not_exist_tool, tool_args: {}}, {finish: True, answer: 工具不存在请检查工具名称。}, ], expected_answer: 工具不存在请检查工具名称。, } def main(): cases [ case_calculator_ok, case_weather_ok, case_multi_step, case_unknown_tool, ] passed_count sum(1 for c in cases if run_case(c)) print(f\n总计: {passed_count}/{len(cases)} 通过) if __name__ __main__: main()4.4 运行与结果解读在项目目录下执行python test_agent.py预期输出类似[PASS] calculator_success [PASS] weather_success [PASS] multi_step_success [PASS] unknown_tool_rejected 总计: 4/4 通过这里每个“脚本”其实就是模型在当前场景下的一种行为路径。你可以把它理解为我们希望模型在给定情况下走某条路。如果真实模型走了别的路用例就会失败trace会清楚地展示它每一步做了什么。接入真实模型时只需要把MockLLM替换为真实模型客户端# real_llm.py伪代码需按实际 SDK 调整 class RealLLM: def __init__(self, client, model): self.client client self.model model def complete(self, messages, tools): # 把 messages 和 tools 序列化为模型需要的格式 # 调用 self.client.chat.completions.create(...) # 解析返回的 function_call转换成统一的 response 字典 pass统一的response字典结构如下{ finish: False, # 是否结束 answer: , # finishTrue 时的最终回答 tool_name: calculator, # finishFalse 时的工具名 tool_args: {expression: 12} # 工具参数 }只要保证这个协议不变你的测试用例就和具体模型、具体框架解耦了。5. 实战二轻量 LLM 基准评估脚本Agent 测试解决了“业务链路对不对”的问题。但遇到“该选哪个模型”“升级模型后效果是升是降”这类问题就需要 benchmark 了。5.1 挑选合适的基准市面上公开基准很多先按能力维度分类再结合你的业务场景挑选能力维度典型基准适合场景综合知识与推理MMLU、C-Eval通用能力摸底、中英文对比代码生成HumanEval 等代码助手、代码补全业务数学推理GSM8K 等需要多步计算的业务Agent 工具调用AgentBench、SWE-benchAgent 产品选型需要注意Agent 类 benchmark 运行成本高、环境复杂不适合频繁跑。日常开发中更实用的是从公开数据集中抽取一个小样本组成自己的“回归集”每次模型变更时快速验证。5.2 编写评测脚本下面是一个轻量评测脚本结构清晰你可以直接改造。# benchmark_runner.py 轻量基准评估脚本示例结构 用同一批题目评估一个或多个模型输出准确率。 实际使用时请把 DEMO_SET 替换为公开 benchmark 数据集的子集。 import random import re DEMO_SET [ {question: 一个笼子里有鸡和兔子共 35 个头94 只脚兔子有多少只, answer: 12}, {question: 将字符串 hello 反转输出。, answer: olleh}, {question: 3 的 4 次方是多少, answer: 81}, ] def normalize(text: str) - str: 简单归一化转小写、去首尾空格、去标点。 return re.sub(r[^\w\u4e00-\u9fff], , text.strip().lower()) def evaluate_model(model_id: str, generate_fn, dataset: list, sample_sizeNone, seed42): 对同一批题目跑一个模型返回准确率统计。 random.seed(seed) if sample_size is None: sample dataset else: sample random.sample(dataset, min(sample_size, len(dataset))) hit 0 total 0 for item in sample: output generate_fn(item[question]) total 1 if normalize(output) normalize(item[answer]): hit 1 else: print(f[MISS] Q: {item[question]}) print(f expected: {item[answer]}) print(f actual : {output}) accuracy hit / total if total 0 else 0.0 return {model: model_id, accuracy: accuracy, hit: hit, total: total}然后写一个简单的generate_fn接入真实模型# main.py伪代码按实际 SDK 调整 def build_generate_fn(client, model, temperature0.0): def generate_fn(question): # 调用模型 # resp client.chat.completions.create( # modelmodel, # messages[{role: user, content: question}], # temperaturetemperature, # ) # return resp.choices[0].message.content # 伪代码返回固定值仅演示结构 return 12 return generate_fn if __name__ __main__: from benchmark_runner import evaluate_model, DEMO_SET # 伪代码这里需要替换为你的真实 client client None gen_a build_generate_fn(client, model-a) gen_b build_generate_fn(client, model-b) for gen in [gen_a, gen_b]: result evaluate_model(demo-model, gen, DEMO_SET, sample_size3) print(result)5.3 对比结果怎么解读跑完脚本后你会得到类似下面的结果{model: model-a, accuracy: 0.667, hit: 2, total: 3} {model: model-b, accuracy: 1.0, hit: 3, total: 3}解读时注意三点样本量小分数波动大。不要因为一次 0.667 和 1.0 的差异就下结论建议多次采样取均值并记录每次的 seed。温度参数必须固定。评测时统一使用 temperature0否则同一个模型跑两次分数都不一样无法对比。数据集不能换。模型 A 用了一组题模型 B 用了另一组题分数就没有可比性。如果条件允许建议把评测脚本接入 CI每次更新模型版本、Prompt 或工具定义后自动跑一遍回归集把分数变化记录到 Git 提交信息里。6. 常见问题与排查思路6.1 高频问题速查表问题现象常见原因解决思路同一用例多次运行结果不一致采样温度过高、模型非确定性评测时固定 temperature0多次运行取统计结果Agent 反复调用同一工具不结束缺少步数上限或停止条件不明确设置 max_steps超时判失败检查上下文是否积累了无用信息工具参数格式经常出错模型对工具 schema 理解不足精简函数描述、补充参数示例、使用强制 JSON 输出Benchmark 分数虚高测试集泄漏进训练数据使用隔离测试集关注发布时间与训练数据时间窗用例执行成本太高调用量过大、没有缓存分级回归先跑小样本对相同输入做结果缓存新模型上线后业务效果变差只看了 benchmark 分数没跑业务回归建立业务场景测试集上线前必须过业务回归6.2 排查清单当你遇到 Agent 相关的问题可以按下面顺序排查先复现用固定 seed、固定 temperature、完整 trace 复现一次看 trace工具调用是否合理、哪一步开始偏离看输入上下文是不是历史工具结果污染了后续判断缩小范围把问题定位到“Prompt 问题”“工具设计问题”还是“模型能力问题”修改后回归改一处跑一次回归集对比通过率变化。这套清单听起来朴素但非常有效。大部分 Agent 问题都不是玄学而是“上下文没管好”或者“工具设计不清晰”。7. 最佳实践与工程建议7.1 测试分层与回归策略在生产项目中我会把测试分成三级对应不同的触发时机提交级每次代码提交时跑单元层测试几分钟内完成拦截低级错误流水线级合并代码或发布前跑流程层测试覆盖主要 Agent 场景周期性级每周或每轮模型升级后跑场景层测试和 benchmark 回归输出趋势报告。分级的好处是成本可控。如果你所有用例都放在提交级一次改动可能触发几百次 LLM 调用既慢又贵。7.2 用例数据管理测试用例是团队资产建议像代码一样管理所有用例放入 Git评审变更每个用例包含场景描述、输入、预期结果、判定方式定期清理失效用例避免用例集膨胀导致回归成本失控敏感业务数据脱敏后再入用例库。另外用例数量要克制。我的经验是初始 3050 个高质量业务用例比 200 个重复用例更有效。先用少量用例跑通流程再逐步补充边界场景。7.3 安全与权限边界Agent 测试涉及工具调用执行安全边界一定要提前设计工具函数放在沙箱环境执行禁止访问生产系统遵循最小权限原则Agent 只能调用它完成当前任务所必需的工具涉及数据库、文件删除、支付等敏感操作时必须加人工确认或权限校验对用户输入和工具返回内容保持警惕防止提示注入导致工具被恶意调用。这些不只是测试阶段的建议Agent 上生产前必须完整检查一遍。7.4 成本与性能控制LLM 评测的成本很容易被忽视。一个用例集从 50 条涨到 500 条调用量增长 10 倍如果每条用例跑 3 次取均值成本再乘 3。控制成本的思路相同输入结果缓存减少重复调用先用小模型过滤明显不合格的用例再让大模型复核设置并发上限和超时时间避免异常调用拖垮服务记录每次评测的 token 消耗建立成本看板。7.5 可观测性建设最后一点也是我认为最重要的一点可观测性。没有完整轨迹的测试就是碰运气。每个测试用例执行时至少要记录完整的消息列表含工具返回结果模型 raw response不要只存解析后的字段每一步的耗时和 token 数最终判定结果和失败原因。这些数据最好落成 JSON 日志或 trace 文件方便后续重放和定位。网上一些团队在探索“meta context engineering via agentic skill evolution”这类方向——通过让 Agent 在不断评估中优化自身上下文构建策略。这类玩法虽然还在早期但前提都是你得有高质量、可回放的评测数据。没有可观测性再高级的优化方法都无从谈起。8. 总结与下一步学习方向本文系统梳理了 Agentic 测试流程和 LLM 基准评估的完整知识体系。核心要点可以概括成几句话Agentic 测试测的是“多轮自主行为”不是单次文本生成测试要分层单元层、流程层、场景层各有各的目的Benchmark 适合选模型和看趋势业务测试才适合保交付评估体系要保证可复现固定 seed、固定 temperature、统一数据集安全、成本、可观测性是评测系统能持续运转的三个底座。接下来你可以按这个路径继续深入先把手头的 Agent 项目跑通本文的测试脚本积累第一版业务用例集然后搭建一个最简单的 benchmark 回归脚本把每次模型变更的分数记录下来最后考虑引入成熟的评测框架把流程接入 CI。如果你正准备给一个真实业务做 Agent 评测建议从 20 个左右的高质量场景用例开始先跑一周看看效果。如果本文对你有帮助可以收藏备用遇到具体报错或设计问题欢迎在评论区带上你的 trace 片段一起讨论。